Skip to content

Commit 3bc025f

Browse files
committed
Add Instruction::deoptimize() and CodeUnits::original_bytes()
- deoptimize() maps specialized opcodes back to their base adaptive variant - original_bytes() produces deoptimized bytecode with zeroed CACHE entries - co_code now returns deoptimized bytes, _co_code_adaptive returns current bytes - Marshal serialization uses original_bytes() instead of raw transmute
1 parent b81cd34 commit 3bc025f

5 files changed

Lines changed: 146 additions & 15 deletions

File tree

crates/compiler-core/src/bytecode.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,30 @@ impl CodeUnits {
525525
units[index].arg = OpArgByte::from(value);
526526
}
527527

528+
/// Produce a clean copy of the bytecode suitable for serialization
529+
/// (marshal) and `co_code`. Specialized opcodes are mapped back to their
530+
/// base variants via `deoptimize()` and all CACHE entries are zeroed.
531+
pub fn original_bytes(&self) -> Vec<u8> {
532+
let units = unsafe { &*self.0.get() };
533+
let mut out = Vec::with_capacity(units.len() * 2);
534+
let len = units.len();
535+
let mut i = 0;
536+
while i < len {
537+
let op = units[i].op.deoptimize();
538+
let caches = op.cache_entries();
539+
out.push(u8::from(op));
540+
out.push(u8::from(units[i].arg));
541+
// Zero-fill all CACHE entries (counter + cached data)
542+
for _ in 0..caches {
543+
i += 1;
544+
out.push(0); // op = Cache = 0
545+
out.push(0); // arg = 0
546+
}
547+
i += 1;
548+
}
549+
out
550+
}
551+
528552
/// Initialize adaptive warmup counters for all cacheable instructions.
529553
/// Called lazily at RESUME (first execution of a code object).
530554
/// Uses the `arg` byte of the first CACHE entry, preserving `op = Instruction::Cache`.

crates/compiler-core/src/bytecode/instruction.rs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,119 @@ impl Instruction {
512512
})
513513
}
514514

515+
/// Map a specialized opcode back to its adaptive (base) variant.
516+
/// `_PyOpcode_Deopt`
517+
pub fn deoptimize(self) -> Self {
518+
match self {
519+
// LOAD_ATTR specializations
520+
Self::LoadAttrClass
521+
| Self::LoadAttrClassWithMetaclassCheck
522+
| Self::LoadAttrGetattributeOverridden
523+
| Self::LoadAttrInstanceValue
524+
| Self::LoadAttrMethodLazyDict
525+
| Self::LoadAttrMethodNoDict
526+
| Self::LoadAttrMethodWithValues
527+
| Self::LoadAttrModule
528+
| Self::LoadAttrNondescriptorNoDict
529+
| Self::LoadAttrNondescriptorWithValues
530+
| Self::LoadAttrProperty
531+
| Self::LoadAttrSlot
532+
| Self::LoadAttrWithHint => Self::LoadAttr { idx: Arg::marker() },
533+
// BINARY_OP specializations
534+
Self::BinaryOpAddFloat
535+
| Self::BinaryOpAddInt
536+
| Self::BinaryOpAddUnicode
537+
| Self::BinaryOpExtend
538+
| Self::BinaryOpInplaceAddUnicode
539+
| Self::BinaryOpMultiplyFloat
540+
| Self::BinaryOpMultiplyInt
541+
| Self::BinaryOpSubscrDict
542+
| Self::BinaryOpSubscrGetitem
543+
| Self::BinaryOpSubscrListInt
544+
| Self::BinaryOpSubscrListSlice
545+
| Self::BinaryOpSubscrStrInt
546+
| Self::BinaryOpSubscrTupleInt
547+
| Self::BinaryOpSubtractFloat
548+
| Self::BinaryOpSubtractInt => Self::BinaryOp { op: Arg::marker() },
549+
// CALL specializations
550+
Self::CallAllocAndEnterInit
551+
| Self::CallBoundMethodExactArgs
552+
| Self::CallBoundMethodGeneral
553+
| Self::CallBuiltinClass
554+
| Self::CallBuiltinFast
555+
| Self::CallBuiltinFastWithKeywords
556+
| Self::CallBuiltinO
557+
| Self::CallIsinstance
558+
| Self::CallLen
559+
| Self::CallListAppend
560+
| Self::CallMethodDescriptorFast
561+
| Self::CallMethodDescriptorFastWithKeywords
562+
| Self::CallMethodDescriptorNoargs
563+
| Self::CallMethodDescriptorO
564+
| Self::CallNonPyGeneral
565+
| Self::CallPyExactArgs
566+
| Self::CallPyGeneral
567+
| Self::CallStr1
568+
| Self::CallTuple1
569+
| Self::CallType1 => Self::Call {
570+
nargs: Arg::marker(),
571+
},
572+
// CALL_KW specializations
573+
Self::CallKwBoundMethod | Self::CallKwNonPy | Self::CallKwPy => Self::CallKw {
574+
nargs: Arg::marker(),
575+
},
576+
// TO_BOOL specializations
577+
Self::ToBoolAlwaysTrue
578+
| Self::ToBoolBool
579+
| Self::ToBoolInt
580+
| Self::ToBoolList
581+
| Self::ToBoolNone
582+
| Self::ToBoolStr => Self::ToBool,
583+
// COMPARE_OP specializations
584+
Self::CompareOpFloat | Self::CompareOpInt | Self::CompareOpStr => {
585+
Self::CompareOp { op: Arg::marker() }
586+
}
587+
// CONTAINS_OP specializations
588+
Self::ContainsOpDict | Self::ContainsOpSet => Self::ContainsOp(Arg::marker()),
589+
// FOR_ITER specializations
590+
Self::ForIterGen | Self::ForIterList | Self::ForIterRange | Self::ForIterTuple => {
591+
Self::ForIter {
592+
target: Arg::marker(),
593+
}
594+
}
595+
// LOAD_GLOBAL specializations
596+
Self::LoadGlobalBuiltin | Self::LoadGlobalModule => Self::LoadGlobal(Arg::marker()),
597+
// STORE_ATTR specializations
598+
Self::StoreAttrInstanceValue | Self::StoreAttrSlot | Self::StoreAttrWithHint => {
599+
Self::StoreAttr { idx: Arg::marker() }
600+
}
601+
// LOAD_SUPER_ATTR specializations
602+
Self::LoadSuperAttrAttr | Self::LoadSuperAttrMethod => {
603+
Self::LoadSuperAttr { arg: Arg::marker() }
604+
}
605+
// STORE_SUBSCR specializations
606+
Self::StoreSubscrDict | Self::StoreSubscrListInt => Self::StoreSubscr,
607+
// UNPACK_SEQUENCE specializations
608+
Self::UnpackSequenceList | Self::UnpackSequenceTuple | Self::UnpackSequenceTwoTuple => {
609+
Self::UnpackSequence {
610+
size: Arg::marker(),
611+
}
612+
}
613+
// SEND specializations
614+
Self::SendGen => Self::Send {
615+
target: Arg::marker(),
616+
},
617+
// LOAD_CONST specializations
618+
Self::LoadConstImmortal | Self::LoadConstMortal => {
619+
Self::LoadConst { idx: Arg::marker() }
620+
}
621+
// RESUME specializations
622+
Self::ResumeCheck => Self::Resume { arg: Arg::marker() },
623+
// Everything else maps to itself
624+
_ => self,
625+
}
626+
}
627+
515628
/// Number of CACHE code units that follow this instruction.
516629
/// _PyOpcode_Caches
517630
pub fn cache_entries(self) -> usize {

crates/compiler-core/src/marshal.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -662,9 +662,8 @@ pub fn serialize_value<W: Write, D: Dumpable>(
662662

663663
pub fn serialize_code<W: Write, C: Constant>(buf: &mut W, code: &CodeObject<C>) {
664664
write_len(buf, code.instructions.len());
665-
// SAFETY: it's ok to transmute CodeUnit to [u8; 2]
666-
let (_, instructions_bytes, _) = unsafe { code.instructions.align_to() };
667-
buf.write_slice(instructions_bytes);
665+
let original = code.instructions.original_bytes();
666+
buf.write_slice(&original);
668667

669668
write_len(buf, code.locations.len());
670669
for (start, end) in &*code.locations {

crates/vm/src/builtins/code.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -684,7 +684,12 @@ impl PyCode {
684684

685685
#[pygetset]
686686
pub fn co_code(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef {
687-
// SAFETY: CodeUnit is #[repr(C)] with size 2, so we can safely transmute to bytes
687+
vm.ctx.new_bytes(self.code.instructions.original_bytes())
688+
}
689+
690+
#[pygetset]
691+
pub fn _co_code_adaptive(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef {
692+
// Return current (possibly quickened/specialized) bytecode
688693
let bytes = unsafe {
689694
core::slice::from_raw_parts(
690695
self.code.instructions.as_ptr() as *const u8,
@@ -694,12 +699,6 @@ impl PyCode {
694699
vm.ctx.new_bytes(bytes.to_vec())
695700
}
696701

697-
#[pygetset]
698-
pub fn _co_code_adaptive(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef {
699-
// RustPython doesn't have adaptive/specialized bytecode, so return regular co_code
700-
self.co_code(vm)
701-
}
702-
703702
#[pygetset]
704703
pub fn co_freevars(&self, vm: &VirtualMachine) -> PyTupleRef {
705704
let names = self

crates/vm/src/frame.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2293,11 +2293,7 @@ impl ExecutingFrame<'_> {
22932293
Instruction::RaiseVarargs { kind } => self.execute_raise(vm, kind.get(arg)),
22942294
Instruction::Resume { .. } => {
22952295
// Lazy quickening: initialize adaptive counters on first execution
2296-
if !self
2297-
.code
2298-
.quickened
2299-
.swap(true, atomic::Ordering::Relaxed)
2300-
{
2296+
if !self.code.quickened.swap(true, atomic::Ordering::Relaxed) {
23012297
self.code.instructions.quicken();
23022298
}
23032299
// Check if bytecode needs re-instrumentation

0 commit comments

Comments
 (0)