diff -pruN 0.16.1-2/.github/workflows/cis.yml 0.17.0-1/.github/workflows/cis.yml
--- 0.16.1-2/.github/workflows/cis.yml	2025-01-21 18:22:03.000000000 +0000
+++ 0.17.0-1/.github/workflows/cis.yml	2025-09-03 19:54:21.000000000 +0000
@@ -20,7 +20,7 @@ jobs:
     name: Lint code
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
       - name: Set up Python
         uses: actions/setup-python@v5
         with:
@@ -42,8 +42,6 @@ jobs:
       fail-fast: false
       matrix:
         include:
-          - python-version: "3.8"
-            toxenv: py38
           - python-version: "3.9"
             toxenv: py39
           - python-version: "3.10"
@@ -54,8 +52,10 @@ jobs:
             toxenv: py312
           - python-version: "3.13"
             toxenv: py313
+          - python-version: "3.14-dev"
+            toxenv: py314
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
       - name: Get history and tags for SCM versioning to work
         run: |
           git fetch --prune --unshallow
diff -pruN 0.16.1-2/.github/workflows/docs.yml 0.17.0-1/.github/workflows/docs.yml
--- 0.16.1-2/.github/workflows/docs.yml	2025-01-21 18:22:03.000000000 +0000
+++ 0.17.0-1/.github/workflows/docs.yml	2025-09-03 19:54:21.000000000 +0000
@@ -19,7 +19,7 @@ jobs:
     name: Docs building
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
       - name: Get history and tags for SCM versioning to work
         run: |
           git fetch --prune --unshallow
diff -pruN 0.16.1-2/.github/workflows/frameworks.yml 0.17.0-1/.github/workflows/frameworks.yml
--- 0.16.1-2/.github/workflows/frameworks.yml	2025-01-21 18:22:03.000000000 +0000
+++ 0.17.0-1/.github/workflows/frameworks.yml	2025-09-03 19:54:21.000000000 +0000
@@ -21,10 +21,10 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
+        python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
 
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
         with:
           fetch-depth: 0
 
diff -pruN 0.16.1-2/.github/workflows/release.yml 0.17.0-1/.github/workflows/release.yml
--- 0.16.1-2/.github/workflows/release.yml	2025-01-21 18:22:03.000000000 +0000
+++ 0.17.0-1/.github/workflows/release.yml	2025-09-03 19:54:21.000000000 +0000
@@ -13,7 +13,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
       - name: Get history and tags for SCM versioning to work
         run: |
           git fetch --prune --unshallow
@@ -43,7 +43,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
       - name: Get history and tags for SCM versioning to work
         run: |
           git fetch --prune --unshallow
@@ -72,20 +72,20 @@ jobs:
     if: github.event_name == 'push'
     needs: [build_wheel, build_sdist]
     runs-on: ubuntu-latest
+    environment:
+      name: pypi
+      url: https://pypi.org/p/kiwisolver
+    permissions:
+      id-token: write
     steps:
       - name: Download all the dists
-        uses: actions/download-artifact@v4.1.8
+        uses: actions/download-artifact@v5.0.0
         with:
           pattern: cibw-*
           path: dist
           merge-multiple: true
 
       - uses: pypa/gh-action-pypi-publish@release/v1
-        with:
-          user: __token__
-          password: ${{ secrets.pypi_password }}
-          # To test:
-          # repository_url: https://test.pypi.org/legacy/
 
   github-release:
     name: >-
@@ -101,15 +101,14 @@ jobs:
 
     steps:
     - name: Download all the dists
-      uses: actions/download-artifact@v4.1.8
+      uses: actions/download-artifact@v5.0.0
       with:
         pattern: cibw-*
         path: dist
         merge-multiple: true
     - name: Sign the dists with Sigstore
-      uses: sigstore/gh-action-sigstore-python@v2.1.0
+      uses: sigstore/gh-action-sigstore-python@v3.0.1
       with:
-        password: ${{ secrets.pypi_password }}
         inputs: >-
           ./dist/*.tar.gz
           ./dist/*.whl
diff -pruN 0.16.1-2/debian/changelog 0.17.0-1/debian/changelog
--- 0.16.1-2/debian/changelog	2025-03-06 22:00:30.000000000 +0000
+++ 0.17.0-1/debian/changelog	2025-09-08 07:34:30.000000000 +0000
@@ -1,3 +1,17 @@
+python-bytecode (0.17.0-1) unstable; urgency=medium
+
+  * Team upload.
+  * New upstream release.
+
+ -- Colin Watson <cjwatson@debian.org>  Mon, 08 Sep 2025 08:34:30 +0100
+
+python-bytecode (0.16.2-1) unstable; urgency=medium
+
+  * Team upload.
+  * New upstream release.
+
+ -- Colin Watson <cjwatson@debian.org>  Wed, 13 Aug 2025 11:13:08 +0100
+
 python-bytecode (0.16.1-2) unstable; urgency=medium
 
   * Team upload.
diff -pruN 0.16.1-2/debian/patches/pydevd-add-offset-to-instructions.patch 0.17.0-1/debian/patches/pydevd-add-offset-to-instructions.patch
--- 0.16.1-2/debian/patches/pydevd-add-offset-to-instructions.patch	2025-03-06 22:00:30.000000000 +0000
+++ 0.17.0-1/debian/patches/pydevd-add-offset-to-instructions.patch	2025-09-08 07:34:30.000000000 +0000
@@ -1,6 +1,7 @@
 From: Fabio Zadrozny <fabiofz@gmail.com>
 Date: Wed, 14 Dec 2022 19:47:25 +0100
 Subject: Add offset to instructions from bytecode library.
+
 Last-Update: 2025-02-20
 Origin: https://github.com/fabioz/PyDev.Debugger/commit/ab9958b70c69f14e69fda6023ec26106b921b13d
 Forwarded: not-needed
@@ -12,9 +13,11 @@ Forwarded: not-needed
  src/bytecode/instr.py    |  6 ++++--
  2 files changed, 15 insertions(+), 6 deletions(-)
 
+diff --git a/src/bytecode/concrete.py b/src/bytecode/concrete.py
+index 32c6452..b52ea79 100644
 --- a/src/bytecode/concrete.py
 +++ b/src/bytecode/concrete.py
-@@ -83,7 +83,7 @@
+@@ -91,7 +91,7 @@ class ConcreteInstr(BaseInstr[int]):
      # For ConcreteInstr the argument is always an integer
      _arg: int
  
@@ -23,7 +26,7 @@ Forwarded: not-needed
  
      def __init__(
          self,
-@@ -93,12 +93,14 @@
+@@ -101,12 +101,14 @@ class ConcreteInstr(BaseInstr[int]):
          lineno: Union[int, None, _UNSET] = UNSET,
          location: Optional[InstrLocation] = None,
          extended_args: Optional[int] = None,
@@ -39,7 +42,7 @@ Forwarded: not-needed
  
      def _check_arg(self, name: str, opcode: int, arg: int) -> None:
          if opcode_has_argument(opcode):
-@@ -178,7 +180,11 @@
+@@ -186,7 +188,11 @@ class ConcreteInstr(BaseInstr[int]):
          else:
              arg = UNSET
          name = _opcode.opname[op]
@@ -52,7 +55,7 @@ Forwarded: not-needed
  
      def use_cache_opcodes(self) -> int:
          if sys.version_info >= (3, 13):
-@@ -781,6 +787,7 @@
+@@ -789,6 +795,7 @@ class ConcreteBytecode(_bytecode._BaseBytecodeList[Union[ConcreteInstr, SetLinen
                      arg,
                      location=instr.location,
                      extended_args=nb_extended_args,
@@ -60,7 +63,7 @@ Forwarded: not-needed
                  )
                  instructions[index] = instr
                  nb_extended_args = 0
-@@ -1092,7 +1099,7 @@
+@@ -1117,7 +1124,7 @@ class ConcreteBytecode(_bytecode._BaseBytecodeList[Union[ConcreteInstr, SetLinen
                      instr_index = len(instructions)
                      jumps.append((instr_index, jump_target))
  
@@ -69,9 +72,11 @@ Forwarded: not-needed
  
              # We now insert the TryEnd entries
              if current_instr_offset in ex_end:
+diff --git a/src/bytecode/instr.py b/src/bytecode/instr.py
+index ac13f77..bf7446d 100644
 --- a/src/bytecode/instr.py
 +++ b/src/bytecode/instr.py
-@@ -555,7 +555,7 @@
+@@ -620,7 +620,7 @@ A = TypeVar("A", bound=object)
  class BaseInstr(Generic[A]):
      """Abstract instruction."""
  
@@ -80,7 +85,7 @@ Forwarded: not-needed
  
      # Work around an issue with the default value of arg
      def __init__(
-@@ -565,8 +565,10 @@
+@@ -630,8 +630,10 @@ class BaseInstr(Generic[A]):
          *,
          lineno: Union[int, None, _UNSET] = UNSET,
          location: Optional[InstrLocation] = None,
@@ -91,7 +96,7 @@ Forwarded: not-needed
          if location:
              self._location = location
          elif lineno is UNSET:
-@@ -691,7 +693,7 @@
+@@ -757,7 +759,7 @@ class BaseInstr(Generic[A]):
              return (_effect, 0)
  
      def copy(self: T) -> T:
diff -pruN 0.16.1-2/doc/changelog.rst 0.17.0-1/doc/changelog.rst
--- 0.16.1-2/doc/changelog.rst	2025-01-21 18:22:03.000000000 +0000
+++ 0.17.0-1/doc/changelog.rst	2025-09-03 19:54:21.000000000 +0000
@@ -1,6 +1,40 @@
 ChangeLog
 =========
 
+03-09-2025: Version 0.17.0
+--------------------------
+
+New features:
+
+- Add support for Python 3.14 PR #166
+
+  Support for Python 3.14, comes with a number of changes reflecting changes in
+  CPython bytecode itself:
+
+  - introduced an enum for BINARY_OP argument which now supports subscribe.
+    When disassembling the enum is always used, when creating bytecode from
+    scratch integer values are coerced into the right enum member.
+  - support BUILD_TEMPLATE, BUILD_INTERPOLATION, LOAD_SMALL_INT, LOAD_FAST_BORROW
+    and LOAD_FAST_BORROW_LOAD_FAST_BORROW
+  - LOAD_COMMON_CONSTANT, LOAD_SPECIAL whose argument is described using dedicated
+    enums CommonConstant, SpecialMethod
+  - CONVERT_VALUE (FORMAT_VALUE in Python < 3.13) now use the FormatValue enum.
+    When disassembling the enum is always used, when creating bytecode from
+    scratch integer values are coerced into the right enum member.
+
+Bugfixes:
+
+- properly set the next_block attribute of the new block created by
+  ControlFlowGraph.split_block. PR #170
+
+2025-04-14: Version 0.16.2
+--------------------------
+
+Bugfixes:
+
+- fix ControlFlowGraph dead block detection by accounting for fall-through
+  edges. PR #161
+
 2025-01-21: Version 0.16.1
 --------------------------
 
diff -pruN 0.16.1-2/pyproject.toml 0.17.0-1/pyproject.toml
--- 0.16.1-2/pyproject.toml	2025-01-21 18:22:03.000000000 +0000
+++ 0.17.0-1/pyproject.toml	2025-09-03 19:54:21.000000000 +0000
@@ -2,7 +2,7 @@
   name = "bytecode"
   description = "Python module to generate and modify bytecode"
   readme = "README.rst"
-  requires-python = ">=3.8"
+  requires-python = ">=3.9"
   license = { file = "COPYING" }
   authors = [{ name = "Victor Stinner", email = "victor.stinner@gmail.com" }]
   maintainers = [{ name = "Matthieu C. Dartiailh", email = "m.dartiailh@gmail.com" }]
@@ -13,12 +13,12 @@
     "Natural Language :: English",
     "Operating System :: OS Independent",
     "Programming Language :: Python :: 3",
-    "Programming Language :: Python :: 3.8",
     "Programming Language :: Python :: 3.9",
     "Programming Language :: Python :: 3.10",
     "Programming Language :: Python :: 3.11",
     "Programming Language :: Python :: 3.12",
     "Programming Language :: Python :: 3.13",
+    "Programming Language :: Python :: 3.14",
     "Topic :: Software Development :: Libraries :: Python Modules",
   ]
   dependencies = ["typing_extensions;python_version<'3.10'"]
@@ -36,6 +36,18 @@
   requires      = ["setuptools>=61.2", "wheel", "setuptools_scm[toml]>=3.4.3"]
   build-backend = "setuptools.build_meta"
 
+[dependency-groups]
+  dev = [
+    "mypy>=1.16.1",
+    "pytest>=8",
+    "pytest-cov>=6",
+    "ruff>=0.12.0",
+  ]
+  test = [
+    "pytest>=8",
+    "pytest-cov",
+  ]
+
 [tool.setuptools_scm]
   write_to = "src/bytecode/version.py"
   write_to_template = """
@@ -74,7 +86,7 @@ __version__ = "{version}"
       extra-standard-library = ["opcode"]
 
     [tool.ruff.lint.mccabe]
-      max-complexity = 42
+      max-complexity = 43
 
 [tool.mypy]
   follow_imports  = "normal"
diff -pruN 0.16.1-2/src/bytecode/cfg.py 0.17.0-1/src/bytecode/cfg.py
--- 0.16.1-2/src/bytecode/cfg.py	2025-01-21 18:22:03.000000000 +0000
+++ 0.17.0-1/src/bytecode/cfg.py	2025-09-03 19:54:21.000000000 +0000
@@ -710,6 +710,7 @@ class ControlFlowGraph(_bytecode.BaseByt
         del block[index:]
 
         block2 = BasicBlock(instructions)
+        block2.next_block = block.next_block
         block.next_block = block2
 
         for block in self[block_index + 1 :]:
@@ -731,12 +732,18 @@ class ControlFlowGraph(_bytecode.BaseByt
             if id(block) in seen_block_ids:
                 continue
             seen_block_ids.add(id(block))
+            fall_through = True
             for i in block:
-                if isinstance(i, Instr) and isinstance(i.arg, BasicBlock):
-                    stack.append(i.arg)
+                if isinstance(i, Instr):
+                    if isinstance(i.arg, BasicBlock):
+                        stack.append(i.arg)
+                    if i.is_final():
+                        fall_through = False
                 elif isinstance(i, TryBegin):
                     assert isinstance(i.target, BasicBlock)
                     stack.append(i.target)
+            if fall_through and block.next_block:
+                stack.append(block.next_block)
 
         return [b for b in self if id(b) not in seen_block_ids]
 
@@ -865,9 +872,7 @@ class ControlFlowGraph(_bytecode.BaseByt
                     #   the new one since the blocks are disconnected.
                     if last_instr.is_final() and temp:
                         old_block.append(TryEnd(try_begins[active_try_begin][-1]))
-                        new_tb = TryBegin(
-                            active_try_begin.target, active_try_begin.push_lasti
-                        )
+                        new_tb = active_try_begin.copy()
                         block.append(new_tb)
                         # Add this new TryBegin to the map to properly update
                         # the target.
diff -pruN 0.16.1-2/src/bytecode/concrete.py 0.17.0-1/src/bytecode/concrete.py
--- 0.16.1-2/src/bytecode/concrete.py	2025-01-21 18:22:03.000000000 +0000
+++ 0.17.0-1/src/bytecode/concrete.py	2025-09-03 19:54:21.000000000 +0000
@@ -26,18 +26,25 @@ import bytecode as _bytecode
 from bytecode.flags import CompilerFlags
 from bytecode.instr import (
     _UNSET,
+    BINARY_OPS,
     BITFLAG2_OPCODES,
     BITFLAG_OPCODES,
+    COMMON_CONSTANT_OPS,
     DUAL_ARG_OPCODES,
     DUAL_ARG_OPCODES_SINGLE_OPS,
+    FORMAT_VALUE_OPS,
     INTRINSIC,
     INTRINSIC_1OP,
     INTRINSIC_2OP,
     PLACEHOLDER_LABEL,
+    SPECIAL_OPS,
     UNSET,
     BaseInstr,
+    BinaryOp,
     CellVar,
+    CommonConstant,
     Compare,
+    FormatValue,
     FreeVar,
     Instr,
     InstrArg,
@@ -46,6 +53,7 @@ from bytecode.instr import (
     Intrinsic2Op,
     Label,
     SetLineno,
+    SpecialMethod,
     TryBegin,
     TryEnd,
     _check_arg_int,
@@ -1056,7 +1064,10 @@ class ConcreteBytecode(_bytecode._BaseBy
                         arg = locals_lookup[c_arg]
                 elif opcode in _opcode.hasname:
                     if opcode in BITFLAG_OPCODES:
-                        arg = (bool(c_arg & 1), self.names[c_arg >> 1])
+                        arg = (
+                            bool(c_arg & 1),
+                            self.names[c_arg >> 1],
+                        )
                     elif opcode in BITFLAG2_OPCODES:
                         arg = (bool(c_arg & 1), bool(c_arg & 2), self.names[c_arg >> 2])
                     else:
@@ -1082,6 +1093,20 @@ class ConcreteBytecode(_bytecode._BaseBy
                     arg = Intrinsic1Op(c_arg)
                 elif opcode in INTRINSIC_2OP:
                     arg = Intrinsic2Op(c_arg)
+                elif opcode in BINARY_OPS:
+                    arg = BinaryOp(c_arg)
+                elif opcode in COMMON_CONSTANT_OPS:
+                    arg = CommonConstant(c_arg)
+                elif opcode in SPECIAL_OPS:
+                    arg = SpecialMethod(c_arg)
+                elif opcode in FORMAT_VALUE_OPS:
+                    if opcode in BITFLAG_OPCODES:
+                        arg = (
+                            bool(c_arg & 1),
+                            FormatValue(c_arg >> 1),
+                        )
+                    else:
+                        arg = FormatValue(c_arg)
                 else:
                     arg = c_arg
 
@@ -1143,7 +1168,7 @@ class ConcreteBytecode(_bytecode._BaseBy
 
 
 class _ConvertBytecodeToConcrete:
-    # XXX document attributes
+    # FIXME document attributes
 
     #: Default number of passes of compute_jumps() before giving up.  Refer to
     #: assemble_jump_offsets() in compile.c for background.
@@ -1316,9 +1341,13 @@ class _ConvertBytecodeToConcrete:
                         isinstance(arg, tuple)
                         and len(arg) == 2
                         and isinstance(arg[0], bool)
-                        and isinstance(arg[1], str)
                     ), arg
-                    index = self.add(self.names, arg[1])
+                    if isinstance(arg[1], str):
+                        index = self.add(self.names, arg[1])
+                    elif isinstance(arg, FormatValue):
+                        index = int(arg)
+                    else:
+                        assert False, arg  # noqa
                     c_arg = int(arg[0]) + (index << 1)
                 elif opcode in BITFLAG2_OPCODES:
                     assert (
diff -pruN 0.16.1-2/src/bytecode/flags.py 0.17.0-1/src/bytecode/flags.py
--- 0.16.1-2/src/bytecode/flags.py	2025-01-21 18:22:03.000000000 +0000
+++ 0.17.0-1/src/bytecode/flags.py	2025-09-03 19:54:21.000000000 +0000
@@ -6,7 +6,7 @@ from typing import Optional, Union
 import bytecode as _bytecode
 
 from .instr import DUAL_ARG_OPCODES, CellVar, FreeVar
-from .utils import PY311, PY312, PY313
+from .utils import PY311, PY312, PY313, PY314
 
 
 class CompilerFlags(IntFlag):
@@ -48,7 +48,7 @@ ASYNC_OPCODES = (
     _opcode.opmap["GET_AWAITABLE"],
     _opcode.opmap["GET_AITER"],
     _opcode.opmap["GET_ANEXT"],
-    _opcode.opmap["BEFORE_ASYNC_WITH"],
+    *((_opcode.opmap["BEFORE_ASYNC_WITH"],) if not PY314 else ()),  # Removed in 3.14+
     *((_opcode.opmap["SETUP_ASYNC_WITH"],) if not PY311 else ()),  # Removed in 3.11+
     _opcode.opmap["END_ASYNC_FOR"],
     *((_opcode.opmap["ASYNC_GEN_WRAP"],) if PY311 and not PY312 else ()),  # New in 3.11
diff -pruN 0.16.1-2/src/bytecode/instr.py 0.17.0-1/src/bytecode/instr.py
--- 0.16.1-2/src/bytecode/instr.py	2025-01-21 18:22:03.000000000 +0000
+++ 0.17.0-1/src/bytecode/instr.py	2025-09-03 19:54:21.000000000 +0000
@@ -13,7 +13,7 @@ except ImportError:
     from typing_extensions import TypeGuard  # type: ignore
 
 import bytecode as _bytecode
-from bytecode.utils import PY311, PY312, PY313
+from bytecode.utils import PY311, PY312, PY313, PY314
 
 # --- Instruction argument tools and
 
@@ -22,20 +22,48 @@ MIN_INSTRUMENTED_OPCODE = getattr(_opcod
 # Instructions relying on a bit to modify its behavior.
 # The lowest bit is used to encode custom behavior.
 BITFLAG_OPCODES = (
-    (_opcode.opmap["LOAD_GLOBAL"], _opcode.opmap["LOAD_ATTR"])
-    if PY312
-    else (_opcode.opmap["LOAD_GLOBAL"],)
-    if PY311
-    else ()
+    (
+        _opcode.opmap["BUILD_INTERPOLATION"],
+        _opcode.opmap["LOAD_GLOBAL"],
+        _opcode.opmap["LOAD_ATTR"],
+    )
+    if PY314
+    else (
+        (_opcode.opmap["LOAD_GLOBAL"], _opcode.opmap["LOAD_ATTR"])
+        if PY312
+        else ((_opcode.opmap["LOAD_GLOBAL"],) if PY311 else ())
+    )
 )
 
 BITFLAG2_OPCODES = (_opcode.opmap["LOAD_SUPER_ATTR"],) if PY312 else ()
 
+# Binary op opcode which has a dedicated arg
+BINARY_OPS = (_opcode.opmap["BINARY_OP"],) if PY311 else ()
+
 # Intrinsic related opcodes
 INTRINSIC_1OP = (_opcode.opmap["CALL_INTRINSIC_1"],) if PY312 else ()
 INTRINSIC_2OP = (_opcode.opmap["CALL_INTRINSIC_2"],) if PY312 else ()
 INTRINSIC = INTRINSIC_1OP + INTRINSIC_2OP
 
+# Small integer related opcode
+SMALL_INT_OPS = (_opcode.opmap["LOAD_SMALL_INT"],) if PY314 else ()
+
+# Special method loading related opcodes
+SPECIAL_OPS = (_opcode.opmap["LOAD_SPECIAL"],) if PY314 else ()
+
+# Common constant loading related opcodes
+COMMON_CONSTANT_OPS = (_opcode.opmap["LOAD_COMMON_CONSTANT"],) if PY314 else ()
+
+# Value formatting related opcodes (only handle CONVERT_VALUE and BUILD_INTERPOLATION)
+FORMAT_VALUE_OPS = (
+    (
+        _opcode.opmap["CONVERT_VALUE"],
+        _opcode.opmap["BUILD_INTERPOLATION"],
+    )
+    if PY314
+    else ((_opcode.opmap["CONVERT_VALUE"],) if PY313 else ())
+)
+
 HASJABS = () if PY313 else _opcode.hasjabs
 if sys.version_info >= (3, 13):
     HASJREL = _opcode.hasjump
@@ -51,6 +79,11 @@ if PY313:
         _opcode.opmap["STORE_FAST_LOAD_FAST"],
         _opcode.opmap["STORE_FAST_STORE_FAST"],
     )
+    if PY314:
+        DUAL_ARG_OPCODES = (
+            *DUAL_ARG_OPCODES,
+            _opcode.opmap["LOAD_FAST_BORROW_LOAD_FAST_BORROW"],
+        )
     DUAL_ARG_OPCODES_SINGLE_OPS = {
         _opcode.opmap["LOAD_FAST_LOAD_FAST"]: ("LOAD_FAST", "LOAD_FAST"),
         _opcode.opmap["STORE_FAST_LOAD_FAST"]: ("STORE_FAST", "LOAD_FAST"),
@@ -129,6 +162,8 @@ class BinaryOp(enum.IntEnum):
     INPLACE_SUBTRACT = 23
     INPLACE_TRUE_DIVIDE = 24
     INPLACE_XOR = 25
+    if PY314:
+        SUBSCR = 26
 
 
 @enum.unique
@@ -156,6 +191,34 @@ class Intrinsic2Op(enum.IntEnum):
     INTRINSIC_SET_FUNCTION_TYPE_PARAMS = 4
 
 
+@enum.unique
+class FormatValue(enum.IntEnum):
+    STR = 1
+    REPR = 2
+    ASCII = 3
+
+
+@enum.unique
+class SpecialMethod(enum.IntEnum):
+    """Special method names used with LOAD_SPECIAL"""
+
+    ENTER = 0
+    EXIT = 1
+    AENTER = 2
+    AEXIT = 3
+
+
+@enum.unique
+class CommonConstant(enum.IntEnum):
+    """Common constants names used with LOAD_COMMON_CONSTANT"""
+
+    ASSERTION_ERROR = 0
+    NOT_IMPLEMENTED_ERROR = 1
+    BUILTIN_TUPLE = 2
+    BUILTIN_ALL = 3
+    BUILTIN_ANY = 4
+
+
 # This make type checking happy but means it won't catch attempt to manipulate an unset
 # statically. We would need guard on object attribute narrowed down through methods
 class _UNSET(int):
@@ -336,15 +399,15 @@ STATIC_STACK_EFFECTS: Dict[str, Tuple[in
     "CHECK_EXC_MATCH": (-2, 2),  # (TOS1, TOS) -> (TOS1, bool)
     "CHECK_EG_MATCH": (-2, 2),  # (TOS, TOS1) -> non-matched, matched or TOS1, None)
     "PREP_RERAISE_STAR": (-2, 1),  # (TOS1, TOS) -> new exception group)
-    **{k: (-1, 1) for k in (o for o in _opcode.opmap if (o.startswith("UNARY_")))},
-    **{
-        k: (-2, 1)
-        for k in (
+    **dict.fromkeys((o for o in _opcode.opmap if o.startswith("UNARY_")), (-1, 1)),
+    **dict.fromkeys(
+        (
             o
             for o in _opcode.opmap
-            if (o.startswith("BINARY_") or o.startswith("INPLACE_"))
-        )
-    },
+            if o.startswith("BINARY_") or o.startswith("INPLACE_")
+        ),
+        (-2, 1),
+    ),
     # Python 3.12 changes not covered by dis.stack_effect
     "BINARY_SLICE": (-3, 1),
     # "STORE_SLICE" handled by dis.stack_effect
@@ -357,6 +420,7 @@ STATIC_STACK_EFFECTS: Dict[str, Tuple[in
     "FORMAT_SIMPLE": (-1, 1),  # new in 3.13
     "FORMAT_SPEC": (-2, 1),  # new in 3.13
     "TO_BOOL": (-1, 1),  # new in 3.13
+    "BUILD_TEMPLATE": (-2, 1),  # new in 3.14
 }
 
 
@@ -385,6 +449,7 @@ DYNAMIC_STACK_EFFECTS: Dict[
     "FORMAT_VALUE": lambda effect, arg, jump: (effect - 1, 1),
     # FOR_ITER needs TOS to be an iterator, hence a prerequisite of 1 on the stack
     "FOR_ITER": lambda effect, arg, jump: (effect, 0) if jump else (-1, 2),
+    "BUILD_INTERPOLATION": lambda effect, arg, jump: (-(2 + (arg & 1)), 1),
     **{
         # Instr(UNPACK_* , n) pops 1 and pushes n
         k: lambda effect, arg, jump: (-1, effect + 1)
@@ -660,6 +725,7 @@ class BaseInstr(Generic[A]):
             arg = None
         # 3.11 where LOAD_GLOBAL arg encode whether or we push a null
         # 3.12 does the same for LOAD_ATTR
+        # 3.14 does this for BUILD_INTERPOLATION
         elif self._opcode in BITFLAG_OPCODES and isinstance(self._arg, tuple):
             assert len(self._arg) == 2
             arg = self._arg[0]
@@ -802,8 +868,15 @@ InstrArg = Union[
     FreeVar,
     "_bytecode.BasicBlock",
     Compare,
+    FormatValue,
+    BinaryOp,
+    Intrinsic1Op,
+    Intrinsic2Op,
+    CommonConstant,
+    SpecialMethod,
     Tuple[bool, str],
     Tuple[bool, bool, str],
+    Tuple[bool, FormatValue],
     Tuple[Union[str, CellVar, FreeVar], Union[str, CellVar, FreeVar]],
 ]
 
@@ -817,7 +890,7 @@ class Instr(BaseInstr[InstrArg]):
             arg = const_key(arg)
         return (self._location, self._name, arg)
 
-    def _check_arg(self, name: str, opcode: int, arg: InstrArg) -> None:
+    def _check_arg(self, name: str, opcode: int, arg: InstrArg) -> None:  # noqa: C901
         if name == "EXTENDED_ARG":
             raise ValueError(
                 "only concrete instruction can contain EXTENDED_ARG, "
@@ -854,7 +927,7 @@ class Instr(BaseInstr[InstrArg]):
                     and isinstance(arg[1], str)
                 ):
                     raise TypeError(
-                        "operation %s argument must be a tuple[bool, str], "
+                        "operation %s argument must be a tuple[bool, str | FormatValue], "
                         "got %s (value=%s)" % (name, type(arg).__name__, str(arg))
                     )
 
@@ -910,6 +983,37 @@ class Instr(BaseInstr[InstrArg]):
                     "Compare, got %s" % (name, type(arg).__name__)
                 )
 
+        elif opcode in BINARY_OPS:
+            if not isinstance(arg, BinaryOp):
+                if isinstance(arg, int):
+                    try:
+                        arg = BinaryOp(arg)
+                    except Exception as e:
+                        raise TypeError(
+                            "operation %s argument type must be "
+                            "coercible to BinaryOp, got %s" % (name, type(arg).__name__)
+                        ) from e
+                else:
+                    raise TypeError(
+                        "operation %s argument type must be "
+                        "BinaryOp, got %s" % (name, type(arg).__name__)
+                    )
+
+        # We do not enforce constant immortality since which constants are
+        # immortal may differ between recompilation and execution.
+
+        elif opcode in SMALL_INT_OPS:
+            if not isinstance(arg, int):
+                raise TypeError(
+                    "operation %s argument type must be "
+                    "int, got %s" % (name, type(arg).__name__)
+                )
+            if arg < 0 or arg > 255:
+                raise ValueError(
+                    "operation %s argument type must be an "
+                    "int between 0 and 255, got %s" % (name, arg)
+                )
+
         elif opcode in INTRINSIC_1OP:
             if not isinstance(arg, Intrinsic1Op):
                 raise TypeError(
@@ -924,5 +1028,45 @@ class Instr(BaseInstr[InstrArg]):
                     "Intrinsic2Op, got %s" % (name, type(arg).__name__)
                 )
 
+        elif opcode in SPECIAL_OPS:
+            if not isinstance(arg, SpecialMethod):
+                raise TypeError(
+                    "operation %s argument type must be "
+                    "SpecialMethod, got %s" % (name, type(arg).__name__)
+                )
+        elif opcode in COMMON_CONSTANT_OPS:
+            if not isinstance(arg, CommonConstant):
+                raise TypeError(
+                    "operation %s argument type must be "
+                    "CommonConstant, got %s" % (name, type(arg).__name__)
+                )
+
+        elif opcode in FORMAT_VALUE_OPS:
+            if opcode in BITFLAG_OPCODES:
+                if not (
+                    isinstance(arg, tuple)
+                    and len(arg) == 2
+                    and isinstance(arg[0], bool)
+                    and isinstance(arg[1], FormatValue)
+                ):
+                    raise TypeError(
+                        "operation %s argument must be a tuple[bool, FormatValue], "
+                        "got %s (value=%s)" % (name, type(arg).__name__, str(arg))
+                    )
+            elif not isinstance(arg, FormatValue):
+                if isinstance(arg, int):
+                    try:
+                        arg = FormatValue(arg)
+                    except Exception as e:
+                        raise TypeError(
+                            "operation %s argument must be a FormatValue] "
+                            "got %s (value=%s)" % (name, type(arg).__name__, str(arg))
+                        ) from e
+                else:
+                    raise TypeError(
+                        "operation %s argument must be a FormatValue] "
+                        "got %s (value=%s)" % (name, type(arg).__name__, str(arg))
+                    )
+
         elif opcode_has_argument(opcode):
             _check_arg_int(arg, name)
diff -pruN 0.16.1-2/src/bytecode/utils.py 0.17.0-1/src/bytecode/utils.py
--- 0.16.1-2/src/bytecode/utils.py	2025-01-21 18:22:03.000000000 +0000
+++ 0.17.0-1/src/bytecode/utils.py	2025-09-03 19:54:21.000000000 +0000
@@ -5,3 +5,4 @@ PY310: Final[bool] = sys.version_info >=
 PY311: Final[bool] = sys.version_info >= (3, 11)
 PY312: Final[bool] = sys.version_info >= (3, 12)
 PY313: Final[bool] = sys.version_info >= (3, 13)
+PY314: Final[bool] = sys.version_info >= (3, 14)
diff -pruN 0.16.1-2/tests/__init__.py 0.17.0-1/tests/__init__.py
--- 0.16.1-2/tests/__init__.py	2025-01-21 18:22:03.000000000 +0000
+++ 0.17.0-1/tests/__init__.py	2025-09-03 19:54:21.000000000 +0000
@@ -181,16 +181,21 @@ class TestCase(unittest.TestCase):
                 list(dis.findlinestarts(code1)), list(dis.findlinestarts(code2))
             )
 
-        # If names have been re-ordered compared the output of dis.instructions
+        # If names or consts have been re-ordered compared the output of dis.instructions
         if sys.version_info >= (3, 12) and (
-            code1.co_names != code2.co_names or code1.co_varnames != code2.co_varnames
+            code1.co_consts != code2.co_consts
+            or code1.co_names != code2.co_names
+            or code1.co_varnames != code2.co_varnames
         ):
             instrs1 = list(dis.get_instructions(code1))
             instrs2 = list(dis.get_instructions(code2))
             self.assertEqual(len(instrs1), len(instrs2))
             for i1, i2 in zip(instrs1, instrs2):
                 self.assertEqual(i1.opcode, i2.opcode)
-                self.assertEqual(i1.argval, i2.argval)
+                if isinstance(i1.argval, types.CodeType):
+                    pass
+                else:
+                    self.assertEqual(i1.argval, i2.argval)
         elif sys.version_info >= (3, 9):
             self.assertSequenceEqual(code1.co_code, code2.co_code)
         # On Python 3.8 it happens that fast storage index vary in a roundtrip
diff -pruN 0.16.1-2/tests/exception_handling_cases.py 0.17.0-1/tests/exception_handling_cases.py
--- 0.16.1-2/tests/exception_handling_cases.py	2025-01-21 18:22:03.000000000 +0000
+++ 0.17.0-1/tests/exception_handling_cases.py	2025-09-03 19:54:21.000000000 +0000
@@ -215,14 +215,14 @@ def try_except_in_else():
 
 def try_finally_in_else():
     try:
-        a = 1
+        a = "a"
     except ValueError as e:
         return
     else:
         try:
             pass
         finally:
-            a = 1
+            a = "a"
 
 
 def try_except_in_finally():
@@ -264,55 +264,55 @@ def try_except_group():
 
 
 def with_no_store():
-    with contextlib.nullcontext(1):
-        a = 1
+    with contextlib.nullcontext("1"):
+        a = "1"
     return a
 
 
 def with_store():
-    with contextlib.nullcontext(1) as b:
-        a = 1
+    with contextlib.nullcontext("1") as b:
+        a = "1"
     return a
 
 
 def try_with():
     try:
-        with contextlib.nullcontext(1):
-            a = 1
+        with contextlib.nullcontext("1"):
+            a = "1"
     except Exception:
-        return min(1, 2)
+        return min("1", "2")
 
     return a
 
 
 def with_try():
-    with contextlib.nullcontext(1):
+    with contextlib.nullcontext("1"):
         try:
-            b = 1
+            b = "1"
         except Exception:
-            return min(1, 2)
+            return min("1", "2")
 
     return b
 
 
 async def async_with_no_store():
     async with contextlib.nullcontext():
-        a = 1
+        a = "1"
     return a
 
 
 async def async_with_store():
     async with contextlib.nullcontext() as b:
-        a = 1
+        a = "1"
     return a
 
 
 async def try_async_with():
     try:
         async with contextlib.nullcontext(1):
-            a = 1
+            a = "1"
     except Exception:
-        return min(1, 2)
+        return min("1", "2")
 
     return a
 
@@ -320,9 +320,9 @@ async def try_async_with():
 async def async_with_try():
     async with contextlib.nullcontext(1):
         try:
-            b = 1
+            b = "1"
         except Exception:
-            return min(1, 2)
+            return min(1.0, 2.0)
 
     return b
 
diff -pruN 0.16.1-2/tests/frameworks/function.py 0.17.0-1/tests/frameworks/function.py
--- 0.16.1-2/tests/frameworks/function.py	2025-01-21 18:22:03.000000000 +0000
+++ 0.17.0-1/tests/frameworks/function.py	2025-09-03 19:54:21.000000000 +0000
@@ -121,7 +121,7 @@ def _collect_functions(module):
                     seen_functions.add(o)
                     o = cast(FullyNamedFunction, o)
                     o.__fullname__ = (
-                        ".".join((c.__fullname__, o.__name__))
+                        ".".join((c.__fullname__, o.__name__ or ""))
                         if c.__fullname__
                         else o.__name__
                     )
diff -pruN 0.16.1-2/tests/test_bytecode.py 0.17.0-1/tests/test_bytecode.py
--- 0.16.1-2/tests/test_bytecode.py	2025-01-21 18:22:03.000000000 +0000
+++ 0.17.0-1/tests/test_bytecode.py	2025-09-03 19:54:21.000000000 +0000
@@ -7,8 +7,8 @@ import types
 import unittest
 
 from bytecode import Bytecode, ConcreteInstr, FreeVar, Instr, Label, SetLineno
-from bytecode.instr import BinaryOp, InstrLocation
-from bytecode.utils import PY313
+from bytecode.instr import BinaryOp, FormatValue, InstrLocation
+from bytecode.utils import PY310, PY311, PY312, PY313, PY314
 
 from . import TestCase, get_code
 
@@ -168,42 +168,60 @@ class BytecodeTests(TestCase):
         bytecode = Bytecode.from_code(code)
         label_else = Label()
         label_exit = Label()
-        if sys.version_info < (3, 10):
-            self.assertEqual(
+        if PY314:
+            self.assertInstructionListEqual(
                 bytecode,
                 [
+                    Instr("RESUME", 0, lineno=0),
                     Instr("LOAD_NAME", "test", lineno=1),
+                    Instr("TO_BOOL", lineno=1),
                     Instr("POP_JUMP_IF_FALSE", label_else, lineno=1),
-                    Instr("LOAD_CONST", 1, lineno=2),
+                    Instr("NOT_TAKEN", lineno=1),
+                    Instr("LOAD_SMALL_INT", 1, lineno=2),
                     Instr("STORE_NAME", "x", lineno=2),
-                    Instr("JUMP_FORWARD", label_exit, lineno=2),
+                    Instr("LOAD_CONST", None, lineno=2),
+                    Instr("RETURN_VALUE", lineno=2),
                     label_else,
-                    Instr("LOAD_CONST", 2, lineno=4),
+                    Instr("LOAD_SMALL_INT", 2, lineno=4),
                     Instr("STORE_NAME", "x", lineno=4),
-                    label_exit,
                     Instr("LOAD_CONST", None, lineno=4),
                     Instr("RETURN_VALUE", lineno=4),
                 ],
             )
-        # Control flow handling appears to have changed under Python 3.10
-        elif sys.version_info < (3, 11):
-            self.assertEqual(
+        elif PY313:
+            self.assertInstructionListEqual(
                 bytecode,
                 [
+                    Instr("RESUME", 0, lineno=0),
                     Instr("LOAD_NAME", "test", lineno=1),
+                    Instr("TO_BOOL", lineno=1),
                     Instr("POP_JUMP_IF_FALSE", label_else, lineno=1),
                     Instr("LOAD_CONST", 1, lineno=2),
                     Instr("STORE_NAME", "x", lineno=2),
-                    Instr("LOAD_CONST", None, lineno=2),
-                    Instr("RETURN_VALUE", lineno=2),
+                    Instr("RETURN_CONST", None, lineno=2),
                     label_else,
                     Instr("LOAD_CONST", 2, lineno=4),
                     Instr("STORE_NAME", "x", lineno=4),
-                    Instr("LOAD_CONST", None, lineno=4),
-                    Instr("RETURN_VALUE", lineno=4),
+                    Instr("RETURN_CONST", None, lineno=4),
+                ],
+            )
+        elif PY312:
+            self.assertInstructionListEqual(
+                bytecode,
+                [
+                    Instr("RESUME", 0, lineno=0),
+                    Instr("LOAD_NAME", "test", lineno=1),
+                    Instr("POP_JUMP_IF_FALSE", label_else, lineno=1),
+                    Instr("LOAD_CONST", 1, lineno=2),
+                    Instr("STORE_NAME", "x", lineno=2),
+                    Instr("RETURN_CONST", None, lineno=2),
+                    label_else,
+                    Instr("LOAD_CONST", 2, lineno=4),
+                    Instr("STORE_NAME", "x", lineno=4),
+                    Instr("RETURN_CONST", None, lineno=4),
                 ],
             )
-        elif sys.version_info < (3, 12):
+        elif PY311:
             self.assertInstructionListEqual(
                 bytecode,
                 [
@@ -221,37 +239,40 @@ class BytecodeTests(TestCase):
                     Instr("RETURN_VALUE", lineno=4),
                 ],
             )
-        elif sys.version_info < (3, 13):
-            self.assertInstructionListEqual(
+        elif PY310:
+            # Control flow handling appears to have changed under Python 3.10
+            self.assertEqual(
                 bytecode,
                 [
-                    Instr("RESUME", 0, lineno=0),
                     Instr("LOAD_NAME", "test", lineno=1),
                     Instr("POP_JUMP_IF_FALSE", label_else, lineno=1),
                     Instr("LOAD_CONST", 1, lineno=2),
                     Instr("STORE_NAME", "x", lineno=2),
-                    Instr("RETURN_CONST", None, lineno=2),
+                    Instr("LOAD_CONST", None, lineno=2),
+                    Instr("RETURN_VALUE", lineno=2),
                     label_else,
                     Instr("LOAD_CONST", 2, lineno=4),
                     Instr("STORE_NAME", "x", lineno=4),
-                    Instr("RETURN_CONST", None, lineno=4),
+                    Instr("LOAD_CONST", None, lineno=4),
+                    Instr("RETURN_VALUE", lineno=4),
                 ],
             )
+
         else:
-            self.assertInstructionListEqual(
+            self.assertEqual(
                 bytecode,
                 [
-                    Instr("RESUME", 0, lineno=0),
                     Instr("LOAD_NAME", "test", lineno=1),
-                    Instr("TO_BOOL", lineno=1),
                     Instr("POP_JUMP_IF_FALSE", label_else, lineno=1),
                     Instr("LOAD_CONST", 1, lineno=2),
                     Instr("STORE_NAME", "x", lineno=2),
-                    Instr("RETURN_CONST", None, lineno=2),
+                    Instr("JUMP_FORWARD", label_exit, lineno=2),
                     label_else,
                     Instr("LOAD_CONST", 2, lineno=4),
                     Instr("STORE_NAME", "x", lineno=4),
-                    Instr("RETURN_CONST", None, lineno=4),
+                    label_exit,
+                    Instr("LOAD_CONST", None, lineno=4),
+                    Instr("RETURN_VALUE", lineno=4),
                 ],
             )
 
@@ -307,22 +328,67 @@ class BytecodeTests(TestCase):
                 [
                     Instr("RESUME", 0, lineno=1),
                 ]
-                if sys.version_info >= (3, 11)
+                if PY311
                 else []
             )
             + [
-                Instr("LOAD_CONST", 33, lineno=2),
+                Instr("LOAD_SMALL_INT", 33, lineno=2)
+                if PY314
+                else Instr("LOAD_CONST", 33, lineno=2),
                 Instr("STORE_FAST", "x", lineno=2),
                 Instr("LOAD_FAST", "x", lineno=3),
                 Instr("STORE_FAST", "y", lineno=3),
             ]
             + (
-                [Instr("RETURN_CONST", None, lineno=3)]
-                if sys.version_info >= (3, 12)
-                else [
+                [
                     Instr("LOAD_CONST", None, lineno=3),
                     Instr("RETURN_VALUE", lineno=3),
                 ]
+                if PY314
+                else (
+                    [Instr("RETURN_CONST", None, lineno=3)]
+                    if PY312
+                    else [
+                        Instr("LOAD_CONST", None, lineno=3),
+                        Instr("RETURN_VALUE", lineno=3),
+                    ]
+                )
+            ),
+        )
+
+    def test_from_code_str_format(self):
+        code = get_code(
+            """
+            def func(a):
+                return f"{a!r}"
+        """,
+            function=True,
+        )
+        code = Bytecode.from_code(code)
+        self.assertInstructionListEqual(
+            code,
+            (
+                [
+                    Instr("RESUME", 0, lineno=1),
+                ]
+                if PY311
+                else []
+            )
+            + (
+                [
+                    Instr("LOAD_FAST_BORROW", "a", lineno=2)
+                    if PY314
+                    else Instr("LOAD_FAST", "a", lineno=2),
+                    Instr("CONVERT_VALUE", FormatValue.REPR, lineno=2),
+                    Instr("FORMAT_SIMPLE", lineno=2),
+                    Instr("RETURN_VALUE", lineno=2),
+                ]
+                if PY313
+                else [
+                    Instr("LOAD_FAST", "a", lineno=2),
+                    Instr("FORMAT_VALUE", 2, lineno=2),
+                    Instr("RETURN_VALUE", lineno=2),
+                ]
             ),
         )
 
@@ -334,37 +400,43 @@ class BytecodeTests(TestCase):
         code.first_lineno = 3
         code.extend(
             [
-                Instr("LOAD_CONST", 7),
+                Instr("LOAD_SMALL_INT" if PY314 else "LOAD_CONST", 7),
                 Instr("STORE_NAME", "x"),
                 SetLineno(4),
-                Instr("LOAD_CONST", 8),
+                Instr("LOAD_SMALL_INT" if PY314 else "LOAD_CONST", 8),
                 Instr("STORE_NAME", "y"),
                 SetLineno(5),
-                Instr("LOAD_CONST", 9),
+                Instr("LOAD_SMALL_INT" if PY314 else "LOAD_CONST", 9),
                 Instr("STORE_NAME", "z"),
             ]
         )
 
         concrete = code.to_concrete_bytecode()
-        self.assertEqual(concrete.consts, [7, 8, 9])
+        self.assertEqual(concrete.consts, [] if PY314 else [7, 8, 9])
         self.assertEqual(concrete.names, ["x", "y", "z"])
         self.assertListEqual(
             list(concrete),
             [
                 ConcreteInstr(
-                    "LOAD_CONST", 0, location=InstrLocation(3, None, None, None)
+                    "LOAD_SMALL_INT" if PY314 else "LOAD_CONST",
+                    7 if PY314 else 0,
+                    location=InstrLocation(3, None, None, None),
                 ),
                 ConcreteInstr(
                     "STORE_NAME", 0, location=InstrLocation(3, None, None, None)
                 ),
                 ConcreteInstr(
-                    "LOAD_CONST", 1, location=InstrLocation(4, None, None, None)
+                    "LOAD_SMALL_INT" if PY314 else "LOAD_CONST",
+                    8 if PY314 else 1,
+                    location=InstrLocation(4, None, None, None),
                 ),
                 ConcreteInstr(
                     "STORE_NAME", 1, location=InstrLocation(4, None, None, None)
                 ),
                 ConcreteInstr(
-                    "LOAD_CONST", 2, location=InstrLocation(5, None, None, None)
+                    "LOAD_SMALL_INT" if PY314 else "LOAD_CONST",
+                    9 if PY314 else 2,
+                    location=InstrLocation(5, None, None, None),
                 ),
                 ConcreteInstr(
                     "STORE_NAME", 2, location=InstrLocation(5, None, None, None)
@@ -379,18 +451,14 @@ class BytecodeTests(TestCase):
             [
                 Instr("LOAD_NAME", "print"),
                 Instr("LOAD_CONST", "%s"),
-                Instr(
-                    "LOAD_GLOBAL", (False, "a") if sys.version_info >= (3, 11) else "a"
-                ),
-                Instr("BINARY_OP", BinaryOp.ADD)
-                if sys.version_info >= (3, 11)
-                else Instr("BINARY_ADD"),
+                Instr("LOAD_GLOBAL", (False, "a") if PY311 else "a"),
+                Instr("BINARY_OP", BinaryOp.ADD) if PY311 else Instr("BINARY_ADD"),
             ]
             # For 3.12+ we need a NULL before a CALL to a free function
-            + ([Instr("PUSH_NULL")] if sys.version_info >= (3, 12) else [])
+            + ([Instr("PUSH_NULL")] if PY312 else [])
             + [
                 # On 3.11 we should have a pre-call
-                Instr("CALL" if sys.version_info >= (3, 11) else "CALL_FUNCTION", 1),
+                Instr("CALL" if PY311 else "CALL_FUNCTION", 1),
                 Instr("RETURN_VALUE"),
             ]
         )
@@ -455,7 +523,7 @@ class BytecodeTests(TestCase):
             "XOR",
             "OR",
         )
-        if sys.version_info >= (3, 11):
+        if PY311:
             operations += ("REMAINDER",)
         else:
             operations += ("MODULO",)
@@ -468,8 +536,8 @@ class BytecodeTests(TestCase):
                 with self.subTest(op):
                     code = Bytecode()
                     code.first_lineno = 1
-                    if sys.version_info >= (3, 11):
-                        if op == "SUBSCR":
+                    if PY311:
+                        if op == "SUBSCR" and not PY314:
                             i = Instr("BINARY_SUBSCR")
                         else:
                             i = Instr("BINARY_OP", getattr(BinaryOp, op))
@@ -511,8 +579,8 @@ class BytecodeTests(TestCase):
                 with self.subTest(op):
                     code = Bytecode()
                     code.first_lineno = 1
-                    if sys.version_info >= (3, 11):
-                        if op == "SUBSCR":
+                    if PY311:
+                        if op == "SUBSCR" and not PY314:
                             i = Instr("BINARY_SUBSCR")
                         else:
                             i = Instr("BINARY_OP", getattr(BinaryOp, op))
@@ -580,14 +648,14 @@ class BytecodeTests(TestCase):
     def test_negative_size_build_const_map(self):
         code = Bytecode()
         code.first_lineno = 1
-        code.extend([Instr("LOAD_CONST", ("a",)), Instr("BUILD_CONST_KEY_MAP", 1)])
+        code.extend([Instr("LOAD_CONST", ("a",)), Instr("BUILD_MAP", 1)])
         with self.assertRaises(RuntimeError):
             code.compute_stacksize()
 
     def test_negative_size_build_const_map_with_disable_check_of_pre_and_post(self):
         code = Bytecode()
         code.first_lineno = 1
-        code.extend([Instr("LOAD_CONST", ("a",)), Instr("BUILD_CONST_KEY_MAP", 1)])
+        code.extend([Instr("LOAD_CONST", ("a",)), Instr("BUILD_MAP", 1)])
         co = code.to_code(check_pre_and_post=False)
         self.assertEqual(co.co_stacksize, 1)
 
@@ -688,7 +756,7 @@ class BytecodeTests(TestCase):
         )
         # Under 3.12+ FOR_ITER does not pop the iterator on completion so this
         # does not fail a coarse stack effect computation.
-        if sys.version_info >= (3, 12):
+        if PY312:
             self.skipTest("Irrelevant on 3.12+")
         with self.assertRaises(RuntimeError):
             # Use compute_stacksize since the code is so broken that conversion
@@ -713,7 +781,7 @@ class BytecodeTests(TestCase):
                 self.assertCodeObjectEqual(origin, as_code)
                 if inspect.iscoroutinefunction(f):
                     # contextlib.nullcontext support async context only in 3.10+
-                    if sys.version_info >= (3, 10):
+                    if PY310:
                         asyncio.run(f())
                 else:
                     f()
@@ -745,7 +813,7 @@ class BytecodeTests(TestCase):
                     pass
 
     def test_empty_try_block(self):
-        if sys.version_info < (3, 11):
+        if not PY311:
             self.skipTest("Exception tables were introduced in 3.11")
 
         import bytecode as b
diff -pruN 0.16.1-2/tests/test_cfg.py 0.17.0-1/tests/test_cfg.py
--- 0.16.1-2/tests/test_cfg.py	2025-01-21 18:22:03.000000000 +0000
+++ 0.17.0-1/tests/test_cfg.py	2025-09-03 19:54:21.000000000 +0000
@@ -20,7 +20,7 @@ from bytecode import (
     dump_bytecode,
 )
 from bytecode.concrete import OFFSET_AS_INSTRUCTION
-from bytecode.utils import PY311, PY313
+from bytecode.utils import PY311, PY312, PY313, PY314
 
 from . import TestCase, disassemble as _disassemble
 
@@ -36,7 +36,7 @@ def disassemble(
         block = blocks[-1]
         test = (
             (block[-1].name == "RETURN_CONST" and block[-1].arg is None)
-            if sys.version_info >= (3, 12)
+            if PY312 and not PY314
             else (
                 block[-2].name == "LOAD_CONST"
                 and block[-2].arg is None
@@ -47,7 +47,7 @@ def disassemble(
             raise ValueError(
                 "unable to find implicit RETURN_VALUE <None>: %s" % block[-2:]
             )
-        if sys.version_info >= (3, 12):
+        if PY312 and not PY314:
             del block[-1]
         else:
             del block[-2:]
@@ -70,7 +70,7 @@ class BlockTests(unittest.TestCase):
         block.extend(
             [
                 Instr(
-                    "JUMP_FORWARD" if sys.version_info >= (3, 11) else "JUMP_ABSOLUTE",
+                    "JUMP_FORWARD" if PY311 else "JUMP_ABSOLUTE",
                     block2,
                 ),
                 Instr("NOP"),
@@ -87,7 +87,7 @@ class BlockTests(unittest.TestCase):
         block.extend(
             [
                 Instr(
-                    "JUMP_FORWARD" if sys.version_info >= (3, 11) else "JUMP_ABSOLUTE",
+                    "JUMP_FORWARD" if PY311 else "JUMP_ABSOLUTE",
                     label,
                 )
             ]
@@ -246,7 +246,7 @@ class BytecodeBlocksTests(TestCase):
                 Instr("LOAD_NAME", "test", lineno=1),
                 Instr(
                     "POP_JUMP_FORWARD_IF_FALSE"
-                    if (3, 12) > sys.version_info >= (3, 11)
+                    if PY311 and not PY312
                     else "POP_JUMP_IF_FALSE",
                     blocks[2],
                     lineno=1,
@@ -279,7 +279,7 @@ class BytecodeBlocksTests(TestCase):
                 Instr("LOAD_NAME", "test", lineno=1),
                 Instr(
                     "POP_JUMP_FORWARD_IF_FALSE"
-                    if (3, 12) > sys.version_info >= (3, 11)
+                    if PY311 and not PY312
                     else "POP_JUMP_IF_FALSE",
                     label,
                     lineno=1,
@@ -304,7 +304,7 @@ class BytecodeBlocksTests(TestCase):
                 Instr("UNARY_NOT"),
                 Instr(
                     "POP_JUMP_FORWARD_IF_FALSE"
-                    if (3, 12) > sys.version_info >= (3, 11)
+                    if PY311 and not PY312
                     else "POP_JUMP_IF_FALSE",
                     label,
                 ),
@@ -322,7 +322,7 @@ class BytecodeBlocksTests(TestCase):
                 Instr("UNARY_NOT"),
                 Instr(
                     "POP_JUMP_FORWARD_IF_FALSE"
-                    if (3, 12) > sys.version_info >= (3, 11)
+                    if PY311 and not PY312
                     else "POP_JUMP_IF_FALSE",
                     cfg[2],
                 ),
@@ -339,7 +339,7 @@ class BytecodeBlocksTests(TestCase):
                 Instr("LOAD_NAME", "test", lineno=1),
                 Instr(
                     "POP_JUMP_FORWARD_IF_FALSE"
-                    if (3, 12) > sys.version_info >= (3, 11)
+                    if PY311 and not PY312
                     else "POP_JUMP_IF_FALSE",
                     label,
                     lineno=1,
@@ -366,7 +366,7 @@ class BytecodeBlocksTests(TestCase):
                 Instr("LOAD_NAME", "test", lineno=1),
                 Instr(
                     "POP_JUMP_FORWARD_IF_FALSE"
-                    if (3, 12) > sys.version_info >= (3, 11)
+                    if PY311 and not PY312
                     else "POP_JUMP_IF_FALSE",
                     label2,
                     lineno=1,
@@ -404,23 +404,23 @@ class BytecodeBlocksTests(TestCase):
                 Instr("COMPARE_OP", Compare.EQ, lineno=2),
                 Instr(
                     "POP_JUMP_BACKWARD_IF_FALSE"
-                    if (3, 12) > sys.version_info >= (3, 11)
+                    if PY311 and not PY312
                     else "POP_JUMP_IF_FALSE",
                     label_loop_start,
                     lineno=2,
                 ),
                 Instr(
-                    "JUMP_FORWARD" if sys.version_info >= (3, 11) else "JUMP_ABSOLUTE",
+                    "JUMP_FORWARD" if PY311 else "JUMP_ABSOLUTE",
                     label_loop_exit,
                     lineno=3,
                 ),
                 Instr(
-                    "JUMP_BACKWARD" if sys.version_info >= (3, 11) else "JUMP_ABSOLUTE",
+                    "JUMP_BACKWARD" if PY311 else "JUMP_ABSOLUTE",
                     label_loop_start,
                     lineno=4,
                 ),
                 Instr(
-                    "JUMP_BACKWARD" if sys.version_info >= (3, 11) else "JUMP_ABSOLUTE",
+                    "JUMP_BACKWARD" if PY311 else "JUMP_ABSOLUTE",
                     label_loop_start,
                     lineno=4,
                 ),
@@ -441,7 +441,7 @@ class BytecodeBlocksTests(TestCase):
                 Instr("COMPARE_OP", Compare.EQ, lineno=2),
                 Instr(
                     "POP_JUMP_BACKWARD_IF_FALSE"
-                    if (3, 12) > sys.version_info >= (3, 11)
+                    if PY311 and not PY312
                     else "POP_JUMP_IF_FALSE",
                     blocks[1],
                     lineno=2,
@@ -449,21 +449,21 @@ class BytecodeBlocksTests(TestCase):
             ],
             [
                 Instr(
-                    "JUMP_FORWARD" if sys.version_info >= (3, 11) else "JUMP_ABSOLUTE",
+                    "JUMP_FORWARD" if PY311 else "JUMP_ABSOLUTE",
                     blocks[6],
                     lineno=3,
                 )
             ],
             [
                 Instr(
-                    "JUMP_BACKWARD" if sys.version_info >= (3, 11) else "JUMP_ABSOLUTE",
+                    "JUMP_BACKWARD" if PY311 else "JUMP_ABSOLUTE",
                     blocks[1],
                     lineno=4,
                 )
             ],
             [
                 Instr(
-                    "JUMP_BACKWARD" if sys.version_info >= (3, 11) else "JUMP_ABSOLUTE",
+                    "JUMP_BACKWARD" if PY311 else "JUMP_ABSOLUTE",
                     blocks[1],
                     lineno=4,
                 )
@@ -518,10 +518,13 @@ class BytecodeBlocksFunctionalTests(Test
         code = disassemble("x = 1", remove_last_return_none=True)
         self.assertBlocksEqual(
             code,
-            ([Instr("RESUME", 0, lineno=0)] if sys.version_info >= (3, 11) else [])
-            + [Instr("LOAD_CONST", 1, lineno=1), Instr("STORE_NAME", "x", lineno=1)],
+            ([Instr("RESUME", 0, lineno=0)] if PY311 else [])
+            + [
+                Instr("LOAD_SMALL_INT" if PY314 else "LOAD_CONST", 1, lineno=1),
+                Instr("STORE_NAME", "x", lineno=1),
+            ],
         )
-        if sys.version_info >= (3, 11):
+        if PY311:
             del code[0][0]
         return code
 
@@ -531,18 +534,26 @@ class BytecodeBlocksFunctionalTests(Test
 
         label = code.split_block(code[0], 2)
         self.assertIs(label, code[1])
+        self.assertIs(code[0].next_block, label)
+        self.assertIs(label.next_block, None)
         self.assertBlocksEqual(
             code,
-            [Instr("LOAD_CONST", 1, lineno=1), Instr("STORE_NAME", "x", lineno=1)],
+            [
+                Instr("LOAD_SMALL_INT" if PY314 else "LOAD_CONST", 1, lineno=1),
+                Instr("STORE_NAME", "x", lineno=1),
+            ],
             [Instr("NOP", lineno=1)],
         )
         self.check_getitem(code)
 
         label2 = code.split_block(code[0], 1)
         self.assertIs(label2, code[1])
+        self.assertIs(code[0].next_block, label2)
+        self.assertIs(label2.next_block, label)
+        self.assertIs(label.next_block, None)
         self.assertBlocksEqual(
             code,
-            [Instr("LOAD_CONST", 1, lineno=1)],
+            [Instr("LOAD_SMALL_INT" if PY314 else "LOAD_CONST", 1, lineno=1)],
             [Instr("STORE_NAME", "x", lineno=1)],
             [Instr("NOP", lineno=1)],
         )
@@ -561,9 +572,14 @@ class BytecodeBlocksFunctionalTests(Test
         # split at the end of the last block requires to add a new empty block
         label = code.split_block(code[0], 2)
         self.assertIs(label, code[1])
+        self.assertIs(code[0].next_block, label)
+        self.assertIs(label.next_block, None)
         self.assertBlocksEqual(
             code,
-            [Instr("LOAD_CONST", 1, lineno=1), Instr("STORE_NAME", "x", lineno=1)],
+            [
+                Instr("LOAD_SMALL_INT" if PY314 else "LOAD_CONST", 1, lineno=1),
+                Instr("STORE_NAME", "x", lineno=1),
+            ],
             [],
         )
         self.check_getitem(code)
@@ -572,9 +588,14 @@ class BytecodeBlocksFunctionalTests(Test
         # add a new block
         label = code.split_block(code[0], 2)
         self.assertIs(label, code[1])
+        self.assertIs(code[0].next_block, label)
+        self.assertIs(label.next_block, None)
         self.assertBlocksEqual(
             code,
-            [Instr("LOAD_CONST", 1, lineno=1), Instr("STORE_NAME", "x", lineno=1)],
+            [
+                Instr("LOAD_SMALL_INT" if PY314 else "LOAD_CONST", 1, lineno=1),
+                Instr("STORE_NAME", "x", lineno=1),
+            ],
             [],
         )
 
@@ -584,8 +605,13 @@ class BytecodeBlocksFunctionalTests(Test
         # FIXME: is it really useful to support that?
         block = code.split_block(code[0], 0)
         self.assertIs(block, code[0])
+        self.assertIs(code[0].next_block, None)
         self.assertBlocksEqual(
-            code, [Instr("LOAD_CONST", 1, lineno=1), Instr("STORE_NAME", "x", lineno=1)]
+            code,
+            [
+                Instr("LOAD_SMALL_INT" if PY314 else "LOAD_CONST", 1, lineno=1),
+                Instr("STORE_NAME", "x", lineno=1),
+            ],
         )
 
     def test_split_block_error(self):
@@ -616,7 +642,7 @@ class BytecodeBlocksFunctionalTests(Test
                 *([Instr("TO_BOOL", lineno=4)] if PY313 else []),
                 Instr(
                     "POP_JUMP_FORWARD_IF_FALSE"
-                    if (3, 12) > sys.version_info >= (3, 11)
+                    if PY311 and not PY312
                     else "POP_JUMP_IF_FALSE",
                     block2,
                     lineno=4,
@@ -712,6 +738,20 @@ class BytecodeBlocksFunctionalTests(Test
         other_block = BasicBlock()
         self.assertRaises(ValueError, blocks.get_block_index, other_block)
 
+    def test_get_dead_blocks(self):
+        def condition():
+            pass
+
+        def test():
+            if condition():
+                print("1")
+            else:
+                print("2")
+
+        bytecode = Bytecode.from_code(test.__code__)
+        cfg = ControlFlowGraph.from_bytecode(bytecode)
+        assert len(cfg.get_dead_blocks()) == 0
+
 
 class CFGStacksizeComputationTests(TestCase):
     def check_stack_size(self, func):
@@ -937,15 +977,13 @@ class CFGStacksizeComputationTests(TestC
                         Instr("LOAD_FAST", "x"),
                         Instr(
                             "POP_JUMP_FORWARD_IF_FALSE"
-                            if (3, 12) > sys.version_info >= (3, 11)
+                            if PY311 and not PY312
                             else "POP_JUMP_IF_FALSE",
                             label_else,
                         ),
                         Instr(
                             "LOAD_GLOBAL",
-                            (False, f"f{i}")
-                            if sys.version_info >= (3, 11)
-                            else f"f{i}",
+                            (False, f"f{i}") if PY311 else f"f{i}",
                         ),
                         Instr("RETURN_VALUE"),
                         label_else,
@@ -965,7 +1003,7 @@ class CFGRoundTripTests(TestCase):
         for f in ehc.TEST_CASES:
             # 3.12 use one less exception table entry causing to optimize this case
             # less than we could otherwise
-            if sys.version_info >= (3, 12) and f.__name__ == "try_except_finally":
+            if PY312 and f.__name__ == "try_except_finally":
                 continue
             print(f.__name__)
             with self.subTest(f.__name__):
diff -pruN 0.16.1-2/tests/test_concrete.py 0.17.0-1/tests/test_concrete.py
--- 0.16.1-2/tests/test_concrete.py	2025-01-21 18:22:03.000000000 +0000
+++ 0.17.0-1/tests/test_concrete.py	2025-09-03 19:54:21.000000000 +0000
@@ -21,7 +21,7 @@ from bytecode import (
     SetLineno,
 )
 from bytecode.concrete import OFFSET_AS_INSTRUCTION, ExceptionTableEntry
-from bytecode.utils import PY313
+from bytecode.utils import PY310, PY311, PY312, PY313, PY314
 
 from . import TestCase, get_code
 
@@ -256,22 +256,25 @@ class ConcreteBytecodeTests(TestCase):
         self.assertEqual(code.freevars, [])
         self.assertInstructionListEqual(
             list(code),
-            (
-                [ConcreteInstr("RESUME", 0, lineno=0)]
-                if sys.version_info >= (3, 11)
-                else []
-            )
+            ([ConcreteInstr("RESUME", 0, lineno=0)] if PY311 else [])
             + [
                 ConcreteInstr("LOAD_CONST", 0, lineno=1),
                 ConcreteInstr("STORE_NAME", 0, lineno=1),
             ]
             + (
-                [ConcreteInstr("RETURN_CONST", 1, lineno=1)]
-                if sys.version_info >= (3, 12)
-                else [
-                    ConcreteInstr("LOAD_CONST", 1, lineno=1),
+                [
+                    ConcreteInstr("LOAD_SMALL_INT", 1, lineno=1),
                     ConcreteInstr("RETURN_VALUE", lineno=1),
                 ]
+                if PY314
+                else (
+                    [ConcreteInstr("RETURN_CONST", 1, lineno=1)]
+                    if PY312
+                    else [
+                        ConcreteInstr("LOAD_CONST", 1, lineno=1),
+                        ConcreteInstr("RETURN_VALUE", lineno=1),
+                    ]
+                )
             ),
         )
         # FIXME: test other attributes
@@ -309,22 +312,35 @@ class ConcreteBytecodeTests(TestCase):
             )
             + [
                 SetLineno(fl + 3),
-                ConcreteInstr("LOAD_CONST", 1),
+                ConcreteInstr(
+                    "LOAD_SMALL_INT" if PY314 else "LOAD_CONST", 7 if PY314 else 1
+                ),
                 ConcreteInstr("STORE_FAST", 0),
                 SetLineno(fl + 4),
-                ConcreteInstr("LOAD_CONST", 2),
+                ConcreteInstr(
+                    "LOAD_SMALL_INT" if PY314 else "LOAD_CONST", 8 if PY314 else 2
+                ),
                 ConcreteInstr("STORE_FAST", 1),
                 SetLineno(fl + 5),
-                ConcreteInstr("LOAD_CONST", 3),
+                ConcreteInstr(
+                    "LOAD_SMALL_INT" if PY314 else "LOAD_CONST", 9 if PY314 else 3
+                ),
                 ConcreteInstr("STORE_FAST", 2),
             ]
             + (
-                [ConcreteInstr("RETURN_CONST", 0)]
-                if sys.version_info >= (3, 12)
-                else [
-                    ConcreteInstr("LOAD_CONST", 0),
+                [
+                    ConcreteInstr("LOAD_CONST", 1),
                     ConcreteInstr("RETURN_VALUE"),
                 ]
+                if PY314
+                else (
+                    [ConcreteInstr("RETURN_CONST", 0)]
+                    if PY312
+                    else [
+                        ConcreteInstr("LOAD_CONST", 0),
+                        ConcreteInstr("RETURN_VALUE"),
+                    ]
+                )
             )
         )
 
@@ -337,7 +353,7 @@ class ConcreteBytecodeTests(TestCase):
             )
         else:
             self.assertEqual(code.co_lnotab, f.__code__.co_lnotab)
-            if sys.version_info >= (3, 10):
+            if PY310:
                 self.assertEqual(code.co_linetable, f.__code__.co_linetable)
 
     def test_negative_lnotab(self):
@@ -438,19 +454,30 @@ class ConcreteBytecodeTests(TestCase):
                 else []
             )
             + [
-                ConcreteInstr("LOAD_CONST", 0),
+                ConcreteInstr(
+                    "LOAD_SMALL_INT" if PY314 else "LOAD_CONST", 7 if PY314 else 0
+                ),
                 ConcreteInstr("STORE_NAME", 0),
                 SetLineno(201),
-                ConcreteInstr("LOAD_CONST", 1),
+                ConcreteInstr(
+                    "LOAD_SMALL_INT" if PY314 else "LOAD_CONST", 8 if PY314 else 1
+                ),
                 ConcreteInstr("STORE_NAME", 1),
             ]
             + (
-                [ConcreteInstr("RETURN_CONST", 2)]
-                if sys.version_info >= (3, 12)
-                else [
-                    ConcreteInstr("LOAD_CONST", 2),
+                [
+                    ConcreteInstr("LOAD_CONST", 1),
                     ConcreteInstr("RETURN_VALUE"),
                 ]
+                if PY314
+                else (
+                    [ConcreteInstr("RETURN_CONST", 2)]
+                    if PY312
+                    else [
+                        ConcreteInstr("LOAD_CONST", 2),
+                        ConcreteInstr("RETURN_VALUE"),
+                    ]
+                )
             )
         )
         concrete.consts = [None, 7, 8]
@@ -467,7 +494,7 @@ class ConcreteBytecodeTests(TestCase):
             )
         else:
             self.assertSequenceEqual(code.co_lnotab, base_code.co_lnotab)
-            if sys.version_info >= (3, 10):
+            if PY310:
                 self.assertSequenceEqual(code.co_linetable, base_code.co_linetable)
 
     def test_to_bytecode_consts(self):
@@ -754,7 +781,7 @@ class ConcreteFromCodeTests(TestCase):
                 code.co_filename,
                 code.co_name,
                 code.co_firstlineno,
-                code.co_linetable if sys.version_info >= (3, 10) else code.co_lnotab,
+                code.co_linetable if PY310 else code.co_lnotab,
                 code.co_freevars,
                 code.co_cellvars,
             )
@@ -797,7 +824,20 @@ class ConcreteFromCodeTests(TestCase):
 
         # without EXTENDED_ARG
         concrete = ConcreteBytecode.from_code(code_obj)
-        if sys.version_info >= (3, 11):
+        if PY314:
+            ann_code = concrete.consts[0]
+            func_code = concrete.consts[1]
+            names = ["foo"]
+            consts = [ann_code, func_code, None]
+            const_offset = 1
+            name_offset = 1
+            first_instrs = [
+                ConcreteInstr("RESUME", 0, lineno=0),
+                ConcreteInstr("LOAD_CONST", 0, lineno=1),
+                ConcreteInstr("MAKE_FUNCTION", lineno=1),
+                ConcreteInstr("LOAD_CONST", 1, lineno=1),
+            ]
+        elif PY311:
             func_code = concrete.consts[2]
             names = ["int", "foo"]
             consts = ["x", "y", func_code, None]
@@ -809,8 +849,10 @@ class ConcreteFromCodeTests(TestCase):
                 ConcreteInstr("LOAD_CONST", 1, lineno=1),
                 ConcreteInstr("LOAD_NAME", 0, lineno=1),
                 ConcreteInstr("BUILD_TUPLE", 4, lineno=1),
+                ConcreteInstr("LOAD_CONST", 1 + const_offset, lineno=1),
+                ConcreteInstr("LOAD_CONST", 2 + const_offset, lineno=1),
             ]
-        elif sys.version_info >= (3, 10):
+        elif PY310:
             func_code = concrete.consts[2]
             names = ["int", "foo"]
             consts = ["x", "y", func_code, "foo", None]
@@ -822,6 +864,8 @@ class ConcreteFromCodeTests(TestCase):
                 ConcreteInstr("LOAD_CONST", 1, lineno=1),
                 ConcreteInstr("LOAD_NAME", 0, lineno=1),
                 ConcreteInstr("BUILD_TUPLE", 4, lineno=1),
+                ConcreteInstr("LOAD_CONST", 1 + const_offset, lineno=1),
+                ConcreteInstr("LOAD_CONST", 2 + const_offset, lineno=1),
             ]
         elif (
             sys.version_info >= (3, 7)
@@ -837,6 +881,8 @@ class ConcreteFromCodeTests(TestCase):
                 ConcreteInstr("LOAD_CONST", 0, lineno=1),
                 ConcreteInstr("LOAD_CONST", 0 + const_offset, lineno=1),
                 ConcreteInstr("BUILD_CONST_KEY_MAP", 2, lineno=1),
+                ConcreteInstr("LOAD_CONST", 1 + const_offset, lineno=1),
+                ConcreteInstr("LOAD_CONST", 2 + const_offset, lineno=1),
             ]
         else:
             func_code = concrete.consts[1]
@@ -849,45 +895,56 @@ class ConcreteFromCodeTests(TestCase):
                 ConcreteInstr("LOAD_NAME", 0, lineno=1),
                 ConcreteInstr("LOAD_CONST", 0 + const_offset, lineno=1),
                 ConcreteInstr("BUILD_CONST_KEY_MAP", 2, lineno=1),
+                ConcreteInstr("LOAD_CONST", 1 + const_offset, lineno=1),
+                ConcreteInstr("LOAD_CONST", 2 + const_offset, lineno=1),
             ]
 
         self.assertSequenceEqual(concrete.names, names)
         self.assertSequenceEqual(concrete.consts, consts)
-        expected = (
-            first_instrs
-            + [
-                ConcreteInstr("LOAD_CONST", 1 + const_offset, lineno=1),
-                ConcreteInstr("LOAD_CONST", 2 + const_offset, lineno=1),
-                *(
-                    [
-                        ConcreteInstr("MAKE_FUNCTION", lineno=1),
-                        ConcreteInstr("SET_FUNCTION_ATTRIBUTE", 4, lineno=1),
-                    ]
-                    if PY313
-                    else [ConcreteInstr("MAKE_FUNCTION", 4, lineno=1)]
-                ),
-                ConcreteInstr("STORE_NAME", name_offset, lineno=1),
-            ]
-            + (
-                [ConcreteInstr("RETURN_CONST", 3 + const_offset, lineno=1)]
-                if sys.version_info >= (3, 12)
-                else [
+        expected = [
+            *first_instrs,
+            *(
+                [
+                    ConcreteInstr("MAKE_FUNCTION", lineno=1),
+                    ConcreteInstr("SET_FUNCTION_ATTRIBUTE", 4, lineno=1),
+                ]
+                if PY313
+                else [ConcreteInstr("MAKE_FUNCTION", 4, lineno=1)]
+            ),
+            ConcreteInstr("STORE_NAME", name_offset, lineno=1),
+            *(
+                [
                     ConcreteInstr("LOAD_CONST", 3 + const_offset, lineno=1),
                     ConcreteInstr("RETURN_VALUE", lineno=1),
                 ]
-            )
-        )
+                if PY314
+                else (
+                    [ConcreteInstr("RETURN_CONST", 3 + const_offset, lineno=1)]
+                    if PY312
+                    else [
+                        ConcreteInstr("LOAD_CONST", 3 + const_offset, lineno=1),
+                        ConcreteInstr("RETURN_VALUE", lineno=1),
+                    ]
+                )
+            ),
+        ]
+
         self.assertInstructionListEqual(list(concrete), expected)
 
         # with EXTENDED_ARG
         concrete = ConcreteBytecode.from_code(code_obj, extended_arg=True)
-        # With future annotation the int annotation is stringified and
-        # stored as constant this the default behavior under Python 3.10
-        if sys.version_info >= (3, 11):
+        if PY314:
+            ann_code = concrete.consts[0]
+            func_code = concrete.consts[1]
+            names = ["foo"]
+            consts = [ann_code, func_code, None]
+        elif PY311:
             func_code = concrete.consts[2]
             names = ["int", "foo"]
             consts = ["x", "y", func_code, None]
-        elif sys.version_info >= (3, 10):
+        # With future annotation the int annotation is stringified and
+        # stored as constant this is the default behavior under Python 3.10
+        elif PY310:
             func_code = concrete.consts[2]
             names = ["int", "foo"]
             consts = ["x", "y", func_code, "foo", None]
@@ -1334,7 +1391,7 @@ class ConcreteFromCodeTests(TestCase):
         bytecode = ConcreteBytecode.from_code(test.__code__, extended_arg=True)
         bytecode.to_code()
 
-    # XXX add tests for linenumbers which are None
+    # FIXME add tests for linenumbers which are None
 
     def test_packing_lines(self):
         import dis
@@ -1362,7 +1419,7 @@ class ConcreteFromCodeTests(TestCase):
                 self.assertCodeObjectEqual(origin, as_code)
                 f.__code__ = as_code
                 if inspect.iscoroutinefunction(f):
-                    if sys.version_info >= (3, 10):
+                    if PY310:
                         asyncio.run(f())
                 else:
                     f()
diff -pruN 0.16.1-2/tests/test_instr.py 0.17.0-1/tests/test_instr.py
--- 0.16.1-2/tests/test_instr.py	2025-01-21 18:22:03.000000000 +0000
+++ 0.17.0-1/tests/test_instr.py	2025-09-03 19:54:21.000000000 +0000
@@ -6,6 +6,7 @@ import unittest
 from bytecode import (
     UNSET,
     BasicBlock,
+    BinaryOp,
     CellVar,
     Compare,
     FreeVar,
@@ -14,21 +15,29 @@ from bytecode import (
     SetLineno,
 )
 from bytecode.instr import (
+    BINARY_OPS,
     BITFLAG2_OPCODES,
     BITFLAG_OPCODES,
+    COMMON_CONSTANT_OPS,
     DUAL_ARG_OPCODES,
+    FORMAT_VALUE_OPS,
     INTRINSIC_1OP,
     INTRINSIC_2OP,
+    SMALL_INT_OPS,
+    SPECIAL_OPS,
+    CommonConstant,
+    FormatValue,
     InstrLocation,
     Intrinsic1Op,
     Intrinsic2Op,
+    SpecialMethod,
     opcode_has_argument,
 )
-from bytecode.utils import PY311, PY313
+from bytecode.utils import PY311, PY312, PY313, PY314
 
 from . import TestCase
 
-# XXX  tests for location and lineno setter
+# FIXME  tests for location and lineno setter
 
 # Starting with Python 3.11 jump opcode have changed quite a bit. We define here
 # opcode useful to test for both Python < 3.11 and Python >= 3.11
@@ -141,7 +150,11 @@ class InstrTests(TestCase):
         self.assertIn("_x_", r)
 
     def test_reject_pseudo_opcode(self):
-        if sys.version_info >= (3, 12):
+        if PY314:
+            with self.assertRaises(ValueError) as e:
+                Instr("INSTRUMENTED_END_FOR", "x")
+            self.assertIn("is an instrumented or pseudo opcode", str(e.exception))
+        elif PY312:
             with self.assertRaises(ValueError) as e:
                 Instr("LOAD_METHOD", "x")
             self.assertIn("is an instrumented or pseudo opcode", str(e.exception))
@@ -160,9 +173,9 @@ class InstrTests(TestCase):
         Instr(UNCONDITIONAL_JUMP, block)
 
         # hasfree
-        self.assertRaises(TypeError, Instr, "LOAD_DEREF", "x")
-        Instr("LOAD_DEREF", CellVar("x"))
-        Instr("LOAD_DEREF", FreeVar("x"))
+        self.assertRaises(TypeError, Instr, "STORE_DEREF", "x")
+        Instr("STORE_DEREF", CellVar("x"))
+        Instr("STORE_DEREF", FreeVar("x"))
 
         # haslocal
         self.assertRaises(TypeError, Instr, "LOAD_FAST", 1)
@@ -203,7 +216,10 @@ class InstrTests(TestCase):
             self.assertRaises(TypeError, Instr, name, ("arg",))
             self.assertRaises(TypeError, Instr, name, ("", "arg"))
             self.assertRaises(TypeError, Instr, name, (False, 1))
-            Instr(name, (True, "arg"))
+            if opcode.opmap[name] in FORMAT_VALUE_OPS:
+                Instr(name, (True, FormatValue.ASCII))
+            else:
+                Instr(name, (True, "arg"))
 
         # Instructions using 2 bitflag in their oparg
         for name in (opcode.opname[op] for op in BITFLAG2_OPCODES):
@@ -229,6 +245,39 @@ class InstrTests(TestCase):
             self.assertRaises(TypeError, Instr, name, 1)
             Instr(name, Intrinsic2Op.INTRINSIC_PREP_RERAISE_STAR)
 
+        for name in (opcode.opname[op] for op in BINARY_OPS):
+            Instr(name, BinaryOp.ADD)
+            Instr(name, BinaryOp.ADD.value)
+            self.assertRaises(TypeError, Instr, name, "")
+
+        for name in (opcode.opname[op] for op in SPECIAL_OPS):
+            Instr(name, SpecialMethod.EXIT)
+            self.assertRaises(TypeError, Instr, name, SpecialMethod.EXIT.value)
+
+        for name in (opcode.opname[op] for op in COMMON_CONSTANT_OPS):
+            Instr(name, CommonConstant.BUILTIN_ALL)
+            self.assertRaises(
+                TypeError,
+                Instr,
+                name,
+                CommonConstant.BUILTIN_ALL.value,
+            )
+
+        for name in (opcode.opname[op] for op in SMALL_INT_OPS):
+            Instr(name, 1)
+            self.assertRaises(ValueError, Instr, name, 256)
+
+        for op, name in ((op, opcode.opname[op]) for op in FORMAT_VALUE_OPS):
+            if op in BITFLAG_OPCODES:
+                Instr(name, (True, FormatValue.STR))
+                Instr(name, (False, FormatValue.STR))
+                self.assertRaises(TypeError, Instr, name, True, FormatValue.STR)
+                self.assertRaises(TypeError, Instr, name, False, FormatValue.STR)
+            else:
+                Instr(name, FormatValue.STR)
+                Instr(name, FormatValue.STR.value)
+                self.assertRaises(TypeError, Instr, name, "STR")
+
     def test_require_arg(self):
         i = Instr(CALL, 3)
         self.assertTrue(i.require_arg())
diff -pruN 0.16.1-2/tests/test_misc.py 0.17.0-1/tests/test_misc.py
--- 0.16.1-2/tests/test_misc.py	2025-01-21 18:22:03.000000000 +0000
+++ 0.17.0-1/tests/test_misc.py	2025-09-03 19:54:21.000000000 +0000
@@ -8,7 +8,7 @@ import unittest
 import bytecode
 from bytecode import BasicBlock, Bytecode, ControlFlowGraph, Instr, Label
 from bytecode.concrete import OFFSET_AS_INSTRUCTION
-from bytecode.utils import PY313
+from bytecode.utils import PY311, PY312, PY313, PY314
 
 from . import disassemble
 
@@ -39,7 +39,32 @@ class DumpCodeTests(unittest.TestCase):
 
         # without line numbers
         enum_repr = "<Compare.EQ_CAST: 18>" if PY313 else "<Compare.EQ: 2>"
-        if sys.version_info >= (3, 12):
+        if PY314:
+            expected = f"""
+    RESUME 0
+    LOAD_FAST_BORROW 'test'
+    LOAD_SMALL_INT 1
+    COMPARE_OP {enum_repr}
+    POP_JUMP_IF_FALSE <label_instr8>
+    NOT_TAKEN
+    LOAD_SMALL_INT 1
+    RETURN_VALUE
+
+label_instr8:
+    LOAD_FAST_BORROW 'test'
+    LOAD_SMALL_INT 2
+    COMPARE_OP {enum_repr}
+    POP_JUMP_IF_FALSE <label_instr16>
+    NOT_TAKEN
+    LOAD_SMALL_INT 2
+    RETURN_VALUE
+
+label_instr16:
+    LOAD_SMALL_INT 3
+    RETURN_VALUE
+
+    """
+        elif PY312:
             expected = f"""
     RESUME 0
     LOAD_FAST 'test'
@@ -59,7 +84,7 @@ label_instr12:
     RETURN_CONST 3
 
     """
-        elif sys.version_info >= (3, 11):
+        elif PY311:
             expected = f"""
     RESUME 0
     LOAD_FAST 'test'
@@ -107,7 +132,32 @@ label_instr13:
         self.check_dump_bytecode(code, expected[1:].rstrip(" "))
 
         # with line numbers
-        if sys.version_info >= (3, 12):
+        if PY314:
+            expected = f"""
+    L.  1   0: RESUME 0
+    L.  2   1: LOAD_FAST_BORROW 'test'
+            2: LOAD_SMALL_INT 1
+            3: COMPARE_OP {enum_repr}
+            4: POP_JUMP_IF_FALSE <label_instr8>
+            5: NOT_TAKEN
+    L.  3   6: LOAD_SMALL_INT 1
+            7: RETURN_VALUE
+
+label_instr8:
+    L.  4   9: LOAD_FAST_BORROW 'test'
+           10: LOAD_SMALL_INT 2
+           11: COMPARE_OP {enum_repr}
+           12: POP_JUMP_IF_FALSE <label_instr16>
+           13: NOT_TAKEN
+    L.  5  14: LOAD_SMALL_INT 2
+           15: RETURN_VALUE
+
+label_instr16:
+    L.  6  17: LOAD_SMALL_INT 3
+           18: RETURN_VALUE
+
+    """
+        elif PY312:
             expected = f"""
     L.  1   0: RESUME 0
     L.  2   1: LOAD_FAST 'test'
@@ -127,7 +177,7 @@ label_instr12:
     L.  6  13: RETURN_CONST 3
 
     """
-        elif sys.version_info >= (3, 11):
+        elif PY311:
             expected = f"""
     L.  1   0: RESUME 0
     L.  2   1: LOAD_FAST 'test'
@@ -209,7 +259,41 @@ label_instr13:
 
         # without line numbers
         enum_repr = "<Compare.EQ_CAST: 18>" if PY313 else "<Compare.EQ: 2>"
-        if sys.version_info >= (3, 12):
+        if PY314:
+            expected = textwrap.dedent(
+                f"""
+            block1:
+                RESUME 0
+                LOAD_FAST_BORROW 'test'
+                LOAD_SMALL_INT 1
+                COMPARE_OP {enum_repr}
+                POP_JUMP_IF_FALSE <block3>
+                -> block2
+
+            block2:
+                NOT_TAKEN
+                LOAD_SMALL_INT 1
+                RETURN_VALUE
+
+            block3:
+                LOAD_FAST_BORROW 'test'
+                LOAD_SMALL_INT 2
+                COMPARE_OP {enum_repr}
+                POP_JUMP_IF_FALSE <block5>
+                -> block4
+
+            block4:
+                NOT_TAKEN
+                LOAD_SMALL_INT 2
+                RETURN_VALUE
+
+            block5:
+                LOAD_SMALL_INT 3
+                RETURN_VALUE
+
+            """
+            )
+        elif PY312:
             expected = textwrap.dedent(
                 f"""
             block1:
@@ -238,7 +322,7 @@ label_instr13:
 
             """
             )
-        elif sys.version_info >= (3, 11):
+        elif PY311:
             expected = textwrap.dedent(
                 f"""
             block1:
@@ -304,7 +388,41 @@ label_instr13:
         self.check_dump_bytecode(code, expected.lstrip())
 
         # with line numbers
-        if sys.version_info >= (3, 12):
+        if PY314:
+            expected = textwrap.dedent(
+                f"""
+            block1:
+                L.  1   0: RESUME 0
+                L.  2   1: LOAD_FAST_BORROW 'test'
+                        2: LOAD_SMALL_INT 1
+                        3: COMPARE_OP {enum_repr}
+                        4: POP_JUMP_IF_FALSE <block3>
+                -> block2
+
+            block2:
+                        0: NOT_TAKEN
+                L.  3   1: LOAD_SMALL_INT 1
+                        2: RETURN_VALUE
+
+            block3:
+                L.  4   0: LOAD_FAST_BORROW 'test'
+                        1: LOAD_SMALL_INT 2
+                        2: COMPARE_OP {enum_repr}
+                        3: POP_JUMP_IF_FALSE <block5>
+                -> block4
+
+            block4:
+                        0: NOT_TAKEN
+                L.  5   1: LOAD_SMALL_INT 2
+                        2: RETURN_VALUE
+
+            block5:
+                L.  6   0: LOAD_SMALL_INT 3
+                        1: RETURN_VALUE
+
+            """
+            )
+        elif PY312:
             expected = textwrap.dedent(
                 f"""
             block1:
@@ -333,7 +451,7 @@ label_instr13:
 
             """
             )
-        elif sys.version_info >= (3, 11):
+        elif PY311:
             expected = textwrap.dedent(
                 f"""
             block1:
@@ -411,7 +529,32 @@ label_instr13:
         code = code.to_concrete_bytecode()
 
         # without line numbers
-        if sys.version_info >= (3, 13):
+        if PY314:
+            # COMPARE_OP use the 4 lowest bits as a cache
+            expected = """
+  0    RESUME 0
+  2    LOAD_FAST_BORROW 0
+  4    LOAD_SMALL_INT 1
+  6    COMPARE_OP 88
+  8    CACHE 0
+ 10    POP_JUMP_IF_FALSE 3
+ 12    CACHE 0
+ 14    NOT_TAKEN
+ 16    LOAD_SMALL_INT 1
+ 18    RETURN_VALUE
+ 20    LOAD_FAST_BORROW 0
+ 22    LOAD_SMALL_INT 2
+ 24    COMPARE_OP 88
+ 26    CACHE 0
+ 28    POP_JUMP_IF_FALSE 3
+ 30    CACHE 0
+ 32    NOT_TAKEN
+ 34    LOAD_SMALL_INT 2
+ 36    RETURN_VALUE
+ 38    LOAD_SMALL_INT 3
+ 40    RETURN_VALUE
+"""
+        elif PY313:
             # COMPARE_OP use the 4 lowest bits as a cache
             expected = """
   0    RESUME 0
@@ -432,7 +575,7 @@ label_instr13:
  30    RETURN_CONST 3
 """
 
-        elif sys.version_info >= (3, 12):
+        elif PY312:
             # COMPARE_OP use the 4 lowest bits as a cache
             expected = """
   0    RESUME 0
@@ -450,7 +593,7 @@ label_instr13:
  24    RETURN_CONST 2
  26    RETURN_CONST 3
 """
-        elif sys.version_info >= (3, 11):
+        elif PY311:
             expected = """
   0    RESUME 0
   2    LOAD_FAST 0
@@ -492,7 +635,31 @@ label_instr13:
         self.check_dump_bytecode(code, expected.lstrip("\n"))
 
         # with line numbers
-        if sys.version_info >= (3, 13):
+        if PY314:
+            expected = """
+L.  1   0: RESUME 0
+L.  2   2: LOAD_FAST_BORROW 0
+        4: LOAD_SMALL_INT 1
+        6: COMPARE_OP 88
+        8: CACHE 0
+       10: POP_JUMP_IF_FALSE 3
+       12: CACHE 0
+       14: NOT_TAKEN
+L.  3  16: LOAD_SMALL_INT 1
+       18: RETURN_VALUE
+L.  4  20: LOAD_FAST_BORROW 0
+       22: LOAD_SMALL_INT 2
+       24: COMPARE_OP 88
+       26: CACHE 0
+       28: POP_JUMP_IF_FALSE 3
+       30: CACHE 0
+       32: NOT_TAKEN
+L.  5  34: LOAD_SMALL_INT 2
+       36: RETURN_VALUE
+L.  6  38: LOAD_SMALL_INT 3
+       40: RETURN_VALUE
+"""
+        elif PY313:
             expected = """
 L.  1   0: RESUME 0
 L.  2   2: LOAD_FAST 0
@@ -511,7 +678,7 @@ L.  4  16: LOAD_FAST 0
 L.  5  28: RETURN_CONST 2
 L.  6  30: RETURN_CONST 3
 """
-        elif sys.version_info >= (3, 12):
+        elif PY312:
             expected = """
 L.  1   0: RESUME 0
 L.  2   2: LOAD_FAST 0
@@ -528,7 +695,7 @@ L.  4  14: LOAD_FAST 0
 L.  5  24: RETURN_CONST 2
 L.  6  26: RETURN_CONST 3
 """
-        elif sys.version_info >= (3, 11):
+        elif PY311:
             expected = """
 L.  1   0: RESUME 0
 L.  2   2: LOAD_FAST 0
diff -pruN 0.16.1-2/tox.ini 0.17.0-1/tox.ini
--- 0.16.1-2/tox.ini	2025-01-21 18:22:03.000000000 +0000
+++ 0.17.0-1/tox.ini	2025-09-03 19:54:21.000000000 +0000
@@ -1,5 +1,5 @@
 [tox]
-envlist = py3, py38, py39, py310, py311, py312, py313, fmt, docs
+envlist = py3, py38, py39, py310, py311, py312, py313, py314, fmt, docs
 isolated_build = true
 
 [testenv]
