-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Instruction 3.14 #6805
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Instruction 3.14 #6805
Conversation
📝 WalkthroughWalkthroughAligns RustPython's bytecode instruction set and opcodes with CPython 3.14, replacing CPython 3.13 definitions, introducing new opcodes (BinarySlice, BuildTemplate, GetLen, etc.), removing deprecated instruction variants (BuildListFromTuples, BuildMapForCall, etc.), and updating the pyc magic number. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/compiler-core/src/bytecode/instruction.rs (1)
416-463: RustPython cannot deserialize CPython 3.14 bytecode using opcodes 121‑127 or 226‑233.CPython 3.14 assigns opcodes throughout the entire range 0–255. Specifically, values 121–127 are assigned to
STORE_FAST_LOAD_FASTthroughFAST_FUNCTION, and values 226–233 are assigned toBUILD_MAP__RETURN_VALUEthroughMATCH_CLASS__RETURN_VALUE. The currentTryFrom<u8>implementation rejects these ranges, causing valid CPython 3.14 bytecode to fail deserialization. These gaps must be filled with the corresponding CPython 3.14 opcodes to maintain compatibility.
🤖 Fix all issues with AI agents
In `@crates/compiler-core/src/bytecode/instruction.rs`:
- Around line 17-199: The GetAwaitable opcode in the Instruction enum is missing
the required oparg ("where") per CPython 3.14; update the
Instruction::GetAwaitable variant to include an oparg field (e.g. GetAwaitable {
oparg: Arg<u32> } or a small enum type Arg<GetAwaitableWhere>) so
decoding/encoding and semantics can read 0/1/2 contexts correctly; search for
Instruction::GetAwaitable and adjust any places that construct, pattern-match,
encode/decode, or format Instruction to handle the new oparg field (and add a
small GetAwaitableWhere type if you prefer typed values instead of raw u32).
In `@crates/vm/src/version.rs`:
- Around line 131-132: The constant PYC_MAGIC_NUMBER is set incorrectly to 2996;
update the value of pub const PYC_MAGIC_NUMBER in version.rs to 3627 so it
matches CPython 3.14's magic number (as defined in CPython's
pycore_magic_number) to ensure .pyc files from CPython 3.14 are read correctly.
🧹 Nitpick comments (1)
crates/compiler-core/src/bytecode/instruction.rs (1)
872-1011: Add disassembly formatting for newly added opcodes to avoid placeholder output.Several new variants (e.g.,
LoadCommonConstant,LoadSmallInt,LoadSpecial,LoadFastBorrow,BinarySlice,StoreSlice) currently fall through toRUSTPYTHON_PLACEHOLDER, makingdisoutput harder to read. Consider adding explicit formatters.♻️ Suggested disassembly cases
@@ - Self::BinarySubscr => w!(BINARY_SUBSCR), + Self::BinarySubscr => w!(BINARY_SUBSCR), + Self::BinarySlice => w!(BINARY_SLICE), @@ - Self::LoadBuildClass => w!(LOAD_BUILD_CLASS), - Self::LoadFromDictOrDeref(i) => w!(LOAD_FROM_DICT_OR_DEREF, cell_name = i), - Self::LoadConst { idx } => fmt_const("LOAD_CONST", arg, f, idx), + Self::LoadBuildClass => w!(LOAD_BUILD_CLASS), + Self::LoadCommonConstant { idx } => w!(LOAD_COMMON_CONSTANT, idx), + Self::LoadSmallInt { idx } => w!(LOAD_SMALL_INT, idx), + Self::LoadSpecial { arg } => w!(LOAD_SPECIAL, arg), + Self::LoadFromDictOrDeref(i) => w!(LOAD_FROM_DICT_OR_DEREF, cell_name = i), + Self::LoadConst { idx } => fmt_const("LOAD_CONST", arg, f, idx), Self::LoadDeref(idx) => w!(LOAD_DEREF, cell_name = idx), Self::LoadFast(idx) => w!(LOAD_FAST, varname = idx), Self::LoadFastAndClear(idx) => w!(LOAD_FAST_AND_CLEAR, varname = idx), + Self::LoadFastBorrow(idx) => w!(LOAD_FAST_BORROW, varname = idx), @@ - Self::StoreSubscr => w!(STORE_SUBSCR), + Self::StoreSubscr => w!(STORE_SUBSCR), + Self::StoreSlice => w!(STORE_SLICE),
| /// Currently aligned with CPython 3.14. | ||
| /// | ||
| /// ## See also | ||
| /// - [CPython opcode IDs](https://github.com/python/cpython/blob/627894459a84be3488a1789919679c997056a03c/Include/opcode_ids.h) | ||
| /// - [CPython opcode IDs](https://github.com/python/cpython/blob/v3.14.2/Include/opcode_ids.h) | ||
| #[derive(Clone, Copy, Debug)] | ||
| #[repr(u8)] | ||
| pub enum Instruction { | ||
| // ==================== No-argument instructions (opcode < 44) ==================== | ||
| Cache = 0, // Placeholder | ||
| BeforeAsyncWith = 1, | ||
| BeforeWith = 2, | ||
| // No-argument instructions (opcode < HAVE_ARGUMENT=44) | ||
| Cache = 0, // Placeholder | ||
| BinarySlice = 1, // Placeholder | ||
| BuildTemplate = 2, | ||
| BinaryOpInplaceAddUnicode = 3, // Placeholder | ||
| BinarySlice = 4, // Placeholder | ||
| BinarySubscr = 5, | ||
| CheckEgMatch = 6, | ||
| CheckExcMatch = 7, | ||
| CleanupThrow = 8, | ||
| DeleteSubscr = 9, | ||
| EndAsyncFor = 10, | ||
| EndFor = 11, // Placeholder | ||
| EndSend = 12, | ||
| ExitInitCheck = 13, // Placeholder | ||
| FormatSimple = 14, | ||
| FormatWithSpec = 15, | ||
| GetAIter = 16, | ||
| CallFunctionEx = 4, | ||
| CheckEgMatch = 5, | ||
| CheckExcMatch = 6, | ||
| CleanupThrow = 7, | ||
| DeleteSubscr = 8, | ||
| EndFor = 9, // Placeholder | ||
| EndSend = 10, | ||
| ExitInitCheck = 11, // Placeholder | ||
| FormatSimple = 12, | ||
| FormatWithSpec = 13, | ||
| GetAIter = 14, | ||
| GetANext = 15, | ||
| GetIter = 16, | ||
| Reserved = 17, | ||
| GetANext = 18, | ||
| GetIter = 19, | ||
| GetLen = 20, | ||
| GetYieldFromIter = 21, | ||
| InterpreterExit = 22, // Placeholder | ||
| LoadAssertionError = 23, // Placeholder | ||
| LoadBuildClass = 24, | ||
| LoadLocals = 25, // Placeholder | ||
| MakeFunction = 26, | ||
| MatchKeys = 27, | ||
| MatchMapping = 28, | ||
| MatchSequence = 29, | ||
| Nop = 30, | ||
| PopExcept = 31, | ||
| PopTop = 32, | ||
| PushExcInfo = 33, | ||
| PushNull = 34, | ||
| ReturnGenerator = 35, // Placeholder | ||
| ReturnValue = 36, | ||
| SetupAnnotations = 37, | ||
| StoreSlice = 38, // Placeholder | ||
| StoreSubscr = 39, | ||
| ToBool = 40, | ||
| UnaryInvert = 41, | ||
| UnaryNegative = 42, | ||
| UnaryNot = 43, | ||
| WithExceptStart = 44, | ||
| // ==================== With-argument instructions (opcode > 44) ==================== | ||
| GetLen = 18, | ||
| GetYieldFromIter = 19, | ||
| InterpreterExit = 20, // Placeholder | ||
| LoadBuildClass = 21, | ||
| LoadLocals = 22, // Placeholder | ||
| MakeFunction = 23, | ||
| MatchKeys = 24, | ||
| MatchMapping = 25, | ||
| MatchSequence = 26, | ||
| Nop = 27, | ||
| NotTaken = 28, // Placeholder | ||
| PopExcept = 29, | ||
| PopIter = 30, // Placeholder | ||
| PopTop = 31, | ||
| PushExcInfo = 32, | ||
| PushNull = 33, | ||
| ReturnGenerator = 34, // Placeholder | ||
| ReturnValue = 35, | ||
| SetupAnnotations = 36, | ||
| StoreSlice = 37, // Placeholder | ||
| StoreSubscr = 38, | ||
| ToBool = 39, | ||
| UnaryInvert = 40, | ||
| UnaryNegative = 41, | ||
| UnaryNot = 42, | ||
| WithExceptStart = 43, | ||
| // CPython 3.14 opcodes with arguments (44-120) | ||
| BinaryOp { | ||
| op: Arg<BinaryOperator>, | ||
| } = 44, | ||
| /// Build an Interpolation from value, expression string, and optional format_spec on stack. | ||
| /// | ||
| /// oparg encoding: (conversion << 2) | has_format_spec | ||
| /// - has_format_spec (bit 0): if 1, format_spec is on stack | ||
| /// - conversion (bits 2+): 0=None, 1=Str, 2=Repr, 3=Ascii | ||
| /// | ||
| /// Stack: [value, expression_str, format_spec?] -> [interpolation] | ||
| BuildInterpolation { | ||
| oparg: Arg<u32>, | ||
| } = 45, | ||
| BuildConstKeyMap { | ||
| size: Arg<u32>, | ||
| } = 46, // Placeholder | ||
| BuildList { | ||
| size: Arg<u32>, | ||
| } = 47, | ||
| } = 46, | ||
| BuildMap { | ||
| size: Arg<u32>, | ||
| } = 48, | ||
| } = 47, | ||
| BuildSet { | ||
| size: Arg<u32>, | ||
| } = 49, | ||
| } = 48, | ||
| BuildSlice { | ||
| argc: Arg<BuildSliceArgCount>, | ||
| } = 50, | ||
| } = 49, | ||
| BuildString { | ||
| size: Arg<u32>, | ||
| } = 51, | ||
| } = 50, | ||
| BuildTuple { | ||
| size: Arg<u32>, | ||
| } = 52, | ||
| } = 51, | ||
| Call { | ||
| nargs: Arg<u32>, | ||
| } = 53, | ||
| CallFunctionEx = 54, | ||
| } = 52, | ||
| CallIntrinsic1 { | ||
| func: Arg<IntrinsicFunction1>, | ||
| } = 55, | ||
| } = 53, | ||
| CallIntrinsic2 { | ||
| func: Arg<IntrinsicFunction2>, | ||
| } = 56, | ||
| } = 54, | ||
| CallKw { | ||
| nargs: Arg<u32>, | ||
| } = 57, | ||
| } = 55, | ||
| CompareOp { | ||
| op: Arg<ComparisonOperator>, | ||
| } = 58, | ||
| ContainsOp(Arg<Invert>) = 59, | ||
| } = 56, | ||
| ContainsOp(Arg<Invert>) = 57, | ||
| ConvertValue { | ||
| oparg: Arg<ConvertValueOparg>, | ||
| } = 60, | ||
| } = 58, | ||
| Copy { | ||
| index: Arg<u32>, | ||
| } = 61, | ||
| } = 59, | ||
| CopyFreeVars { | ||
| count: Arg<u32>, | ||
| } = 62, // Placeholder | ||
| } = 60, // Placeholder | ||
| DeleteAttr { | ||
| idx: Arg<NameIdx>, | ||
| } = 63, | ||
| DeleteDeref(Arg<NameIdx>) = 64, | ||
| DeleteFast(Arg<NameIdx>) = 65, | ||
| DeleteGlobal(Arg<NameIdx>) = 66, | ||
| DeleteName(Arg<NameIdx>) = 67, | ||
| } = 61, | ||
| DeleteDeref(Arg<NameIdx>) = 62, | ||
| DeleteFast(Arg<NameIdx>) = 63, | ||
| DeleteGlobal(Arg<NameIdx>) = 64, | ||
| DeleteName(Arg<NameIdx>) = 65, | ||
| DictMerge { | ||
| index: Arg<u32>, | ||
| } = 68, // Placeholder | ||
| } = 66, | ||
| DictUpdate { | ||
| index: Arg<u32>, | ||
| } = 69, | ||
| EnterExecutor = 70, // Placeholder | ||
| ExtendedArg = 71, | ||
| } = 67, | ||
| EndAsyncFor = 68, | ||
| ExtendedArg = 69, | ||
| ForIter { | ||
| target: Arg<Label>, | ||
| } = 72, | ||
| GetAwaitable = 73, // TODO: Make this instruction to hold an oparg | ||
| } = 70, | ||
| GetAwaitable = 71, // TODO: Make this instruction to hold an oparg | ||
| ImportFrom { | ||
| idx: Arg<NameIdx>, | ||
| } = 74, | ||
| } = 72, | ||
| ImportName { | ||
| idx: Arg<NameIdx>, | ||
| } = 75, | ||
| IsOp(Arg<Invert>) = 76, | ||
| } = 73, | ||
| IsOp(Arg<Invert>) = 74, | ||
| JumpBackward { | ||
| target: Arg<Label>, | ||
| } = 77, | ||
| } = 75, | ||
| JumpBackwardNoInterrupt { | ||
| target: Arg<Label>, | ||
| } = 78, // Placeholder | ||
| } = 76, // Placeholder | ||
| JumpForward { | ||
| target: Arg<Label>, | ||
| } = 79, | ||
| } = 77, | ||
| ListAppend { | ||
| i: Arg<u32>, | ||
| } = 80, | ||
| } = 78, | ||
| ListExtend { | ||
| i: Arg<u32>, | ||
| } = 81, // Placeholder | ||
| } = 79, | ||
| LoadAttr { | ||
| idx: Arg<NameIdx>, | ||
| } = 82, | ||
| } = 80, | ||
| LoadCommonConstant { | ||
| idx: Arg<u32>, | ||
| } = 81, // Placeholder | ||
| LoadConst { | ||
| idx: Arg<u32>, | ||
| } = 83, | ||
| LoadDeref(Arg<NameIdx>) = 84, | ||
| LoadFast(Arg<NameIdx>) = 85, | ||
| LoadFastAndClear(Arg<NameIdx>) = 86, | ||
| LoadFastCheck(Arg<NameIdx>) = 87, // Placeholder | ||
| } = 82, | ||
| LoadDeref(Arg<NameIdx>) = 83, | ||
| LoadFast(Arg<NameIdx>) = 84, | ||
| LoadFastAndClear(Arg<NameIdx>) = 85, | ||
| LoadFastBorrow(Arg<NameIdx>) = 86, // Placeholder | ||
| LoadFastBorrowLoadFastBorrow { | ||
| arg: Arg<u32>, | ||
| } = 87, // Placeholder | ||
| LoadFastCheck(Arg<NameIdx>) = 88, // Placeholder | ||
| LoadFastLoadFast { | ||
| arg: Arg<u32>, | ||
| } = 88, // Placeholder | ||
| LoadFromDictOrDeref(Arg<NameIdx>) = 89, | ||
| LoadFromDictOrGlobals(Arg<NameIdx>) = 90, // Placeholder | ||
| LoadGlobal(Arg<NameIdx>) = 91, | ||
| LoadName(Arg<NameIdx>) = 92, | ||
| } = 89, // Placeholder | ||
| LoadFromDictOrDeref(Arg<NameIdx>) = 90, | ||
| LoadFromDictOrGlobals(Arg<NameIdx>) = 91, // Placeholder | ||
| LoadGlobal(Arg<NameIdx>) = 92, | ||
| LoadName(Arg<NameIdx>) = 93, | ||
| LoadSmallInt { | ||
| idx: Arg<u32>, | ||
| } = 94, // Placeholder | ||
| LoadSpecial { | ||
| arg: Arg<u32>, | ||
| } = 95, // Placeholder | ||
| LoadSuperAttr { | ||
| arg: Arg<u32>, | ||
| } = 93, | ||
| MakeCell(Arg<NameIdx>) = 94, // Placeholder | ||
| } = 96, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
CPython 3.14 GET_AWAITABLE opcode oparg specification
💡 Result:
In CPython 3.14 the GET_AWAITABLE opcode takes an oparg "where" that indicates the context where the awaitable is produced:
- 0: general (normal await).
- 1: after a call to aenter (i.e. awaiting the result of aenter).
- 2: after a call to aexit (i.e. awaiting the result of aexit).
(Documentation shows the same "where" operand meaning as in earlier versions.) [1][2]
Sources: Python dis documentation (3.14, 3.11). [1] https://docs.python.org/3.14/library/dis.html [2] https://docs.python.org/3.11/library/dis.html
Fix GetAwaitable to include oparg field for CPython 3.14 compatibility.
CPython 3.14's GET_AWAITABLE opcode requires an oparg ("where") that indicates the context (0=general, 1=after aenter, 2=after aexit). The current variant lacks this field, which will cause incorrect decoding and incomplete execution semantics. The TODO comment acknowledges this gap—implement the fix to match CPython's specification.
🤖 Prompt for AI Agents
In `@crates/compiler-core/src/bytecode/instruction.rs` around lines 17 - 199, The
GetAwaitable opcode in the Instruction enum is missing the required oparg
("where") per CPython 3.14; update the Instruction::GetAwaitable variant to
include an oparg field (e.g. GetAwaitable { oparg: Arg<u32> } or a small enum
type Arg<GetAwaitableWhere>) so decoding/encoding and semantics can read 0/1/2
contexts correctly; search for Instruction::GetAwaitable and adjust any places
that construct, pattern-match, encode/decode, or format Instruction to handle
the new oparg field (and add a small GetAwaitableWhere type if you prefer typed
values instead of raw u32).
| // Must be aligned to Lib/importlib/_bootstrap_external.py | ||
| pub const PYC_MAGIC_NUMBER: u16 = 2997; | ||
| pub const PYC_MAGIC_NUMBER: u16 = 2996; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
CPython 3.14 pyc magic number
💡 Result:
CPython 3.14 pyc magic number: 3627 (4-byte magic token = b'\x2b\x0e\x0d\x0a'). [1][2]
Sources:
- Include/internal/pycore_magic_number.h (PYC_MAGIC_NUMBER = 3627). [1]
- Explanation of pyc magic/token format (little-endian 2-byte number + CR LF). [2]
Change PYC_MAGIC_NUMBER to 3627 to match CPython 3.14.
Line 132 sets the value to 2996, but CPython 3.14's magic number is 3627 (defined in Include/internal/pycore_magic_number.h). Using the incorrect value will cause RustPython to misread .pyc files from CPython 3.14.
🤖 Prompt for AI Agents
In `@crates/vm/src/version.rs` around lines 131 - 132, The constant
PYC_MAGIC_NUMBER is set incorrectly to 2996; update the value of pub const
PYC_MAGIC_NUMBER in version.rs to 3627 so it matches CPython 3.14's magic number
(as defined in CPython's pycore_magic_number) to ensure .pyc files from CPython
3.14 are read correctly.
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.