Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 12 additions & 13 deletions majit/majit-metainterp/src/jitcode/assembler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1350,19 +1350,18 @@ impl JitCodeBuilder {
reds_r: &[u8],
reds_f: &[u8],
) {
// Capture the bytecode offset of the OPCODE byte (before
// `write_insn` pushes it). jtransform.py:1690-1712 emits exactly
// one `jit_merge_point` per portal jitcode; assert the second
// call so a double-emit lowerer bug fails loud rather than
// overwriting the offset and leaving the validator on the wrong
// payload.
assert!(
self.jit_merge_point_offset.is_none(),
"JitCodeBuilder::jit_merge_point called twice; \
jtransform.py:1690-1712 emits exactly one merge point per \
portal jitcode",
);
self.jit_merge_point_offset = Some(self.code.len());
// Capture the bytecode offset of the first OPCODE byte (before
// `write_insn` pushes it). PyPy's portal dispatch loop executes
// `jit_merge_point()` at every bytecode dispatch, so a lowered
// portal jitcode can contain more than one merge point when pyre
// materializes several Python loop headers in one dispatch body.
// The offset is only used by `register_dispatch_jitcode` for
// schema validation, so keeping the first one preserves that
// validation without rejecting later structurally identical merge
// points.
if self.jit_merge_point_offset.is_none() {
self.jit_merge_point_offset = Some(self.code.len());
}
if (-128..=127).contains(&jdindex) {
self.write_insn("jit_merge_point/cIRFIRF");
self.push_u8((jdindex & 0xFF) as u8);
Expand Down
19 changes: 2 additions & 17 deletions majit/majit-metainterp/src/optimizeopt/optimizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3238,14 +3238,6 @@ impl Optimizer {
OptContext::with_inputarg_types(32, &types)
});

// RPython parity: export virtual state BEFORE flush() and
// force_at_the_end_of_preamble(). In RPython, get_virtual_state
// is called inside _jump_to_existing_trace on the original Box
// objects whose PtrInfo reflects the pre-flush optimizer state.
// flush() may force pending virtuals, changing their PtrInfo to
// non-virtual. Capture the virtual state before any forcing.
let pre_force_vs = crate::optimizeopt::virtualstate::export_state(&jump_args, &ctx);

self.flush(&mut ctx);

// unroll.py:204-205: force_at_the_end_of_preamble for each jump arg
Expand All @@ -3256,13 +3248,6 @@ impl Optimizer {
}
ctx.current_pass_idx = saved_pass_idx;

// RPython parity: jump_op.getarglist() returns the ORIGINAL Boxes.
// force_at_the_end_of_preamble recurses into virtual fields but
// does NOT force the top-level virtuals. So get_virtual_state on
// the original args still sees Virtual PtrInfo.
// DO NOT resolve jump_args here — pass originals so virtual_state
// is computed from the pre-force state.

// unroll.py:206-211: jump_to_existing_trace(force_boxes=False)
// RPython iterates ALL target_tokens; preamble (virtual_state=None)
// is skipped inside jump_to_existing_trace (unroll.py:327-328).
Expand All @@ -3275,7 +3260,7 @@ impl Optimizer {
&mut ctx,
false,
&pre_opt_jump_args,
Some(pre_force_vs.clone()),
None,
) {
Ok(vs) => vs,
// unroll.py:209-210: except InvalidLoop → jump_to_preamble
Expand Down Expand Up @@ -3328,7 +3313,7 @@ impl Optimizer {
&mut ctx,
true,
&pre_opt_jump_args,
Some(pre_force_vs),
None,
) {
Ok(vs) => vs,
// unroll.py:224-225: except InvalidLoop: pass
Expand Down
114 changes: 113 additions & 1 deletion majit/majit-metainterp/src/resume.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4095,7 +4095,23 @@ impl ResumeDataLoopMemo {
numb_state.append_short(tagged);
continue;
}
let box_type = snapshot_box.tp.unwrap_or_else(|| env.get_type(opref));
// resume.py:201-212:
//
// box = iter.get(...)
// box = box.get_box_replacement()
// ...
// if box.type == 'r':
//
// The type used for virtual classification is the replacement
// box's type, not the original snapshot slot's fallback type. A
// snapshot slot can carry an Int fallback from tracing but forward
// to a Ref virtual after optimization; keeping the stale fallback
// would number that virtual as a TAGBOX and the subsequent
// optimizer.py:681 fail-arg force would materialize it.
let box_type = opref
.ty()
.or(snapshot_box.tp)
.unwrap_or_else(|| env.get_type(opref));
let is_virtual = match box_type {
majit_ir::Type::Ref => env.is_virtual_ref(opref),
majit_ir::Type::Int => env.is_virtual_raw(opref),
Expand Down Expand Up @@ -5073,6 +5089,102 @@ mod tests {
assert_eq!(val, 1);
}

#[test]
fn test_number_boxes_uses_replacement_type_for_virtual_classification() {
use majit_ir::OpRef;
struct RefOnlyVirtualEnv {
constants: HashMap<u32, (i64, majit_ir::Type)>,
replacements: HashMap<u32, majit_ir::OpRef>,
types: HashMap<u32, majit_ir::Type>,
virtuals: std::collections::HashSet<u32>,
virtual_fields: HashMap<u32, majit_ir::VirtualFieldsInfo>,
}

impl RefOnlyVirtualEnv {
fn new() -> Self {
Self {
constants: HashMap::new(),
replacements: HashMap::new(),
types: HashMap::new(),
virtuals: std::collections::HashSet::new(),
virtual_fields: HashMap::new(),
}
}
}

impl BoxEnv for RefOnlyVirtualEnv {
fn get_box_replacement(&self, opref: majit_ir::OpRef) -> majit_ir::OpRef {
self.replacements
.get(&opref.raw())
.copied()
.unwrap_or(opref)
}

fn get_box_replacement_not_const(&self, opref: majit_ir::OpRef) -> majit_ir::OpRef {
self.get_box_replacement(opref)
}

fn is_const(&self, opref: majit_ir::OpRef) -> bool {
self.constants.contains_key(&opref.raw())
}

fn get_const(&self, opref: majit_ir::OpRef) -> (i64, majit_ir::Type) {
self.constants
.get(&opref.raw())
.copied()
.unwrap_or((0, majit_ir::Type::Int))
}

fn get_type(&self, opref: majit_ir::OpRef) -> majit_ir::Type {
self.types
.get(&opref.raw())
.copied()
.unwrap_or(majit_ir::Type::Int)
}

fn is_virtual_ref(&self, opref: majit_ir::OpRef) -> bool {
self.virtuals.contains(&opref.raw())
}

fn is_virtual_raw(&self, _opref: majit_ir::OpRef) -> bool {
false
}

fn get_virtual_fields(
&self,
opref: majit_ir::OpRef,
) -> Option<majit_ir::VirtualFieldsInfo> {
self.virtual_fields.get(&opref.raw()).cloned()
}
}

let mut memo = ResumeDataLoopMemo::new();
let mut env = RefOnlyVirtualEnv::new();

// RPython resume.py reads box.type after get_box_replacement().
// Model a stale Int-typed snapshot slot that now forwards to a Ref
// virtual, the shape produced by optimized boxed-int locals.
let source = OpRef::int_op(1);
let target = OpRef::ref_op(2);
env.replacements.insert(source.raw(), target);
env.virtuals.insert(target.raw());
env.types.insert(target.raw(), majit_ir::Type::Ref);

let snapshot = Snapshot::single_frame_boxes(
0,
10,
vec![SnapshotBox::typed(source, majit_ir::Type::Int)],
);
let numb_state = memo.number(&snapshot, &env, -1).unwrap();
let items = crate::resumecode::unpack_all(&numb_state.create_numbering());

let (val, tagbits) = untag(items[6] as i16);
assert_eq!(tagbits, TAGVIRTUAL);
assert_eq!(val, 0);
assert_eq!(numb_state.num_boxes, 0);
assert_eq!(numb_state.num_virtuals, 1);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

#[test]
fn test_multi_frame_snapshot() {
use majit_ir::OpRef;
Expand Down
10 changes: 4 additions & 6 deletions majit/majit-metainterp/src/warmstate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,8 @@ const DEFAULT_MAX_INLINE_DEPTH: u32 = 7;
/// rlib/jit.py:592 trace_limit = 6000
const DEFAULT_TRACE_LIMIT: u32 = crate::trace_ctx::DEFAULT_TRACE_LIMIT as u32;

/// warmspot.py:93 retrace_limit=5
const DEFAULT_RETRACE_LIMIT: u32 = 5;
/// rlib/jit.py:595 retrace_limit = 0.
const DEFAULT_RETRACE_LIMIT: u32 = 0;
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/// rlib/jit.py:598 max_unroll_loops = 0
const DEFAULT_MAX_UNROLL_LOOPS: u32 = 0;
Expand Down Expand Up @@ -455,8 +455,7 @@ impl WarmEnterState {
pureop_historylength: 16,
memory_manager: {
let mut m = crate::memmgr::MemoryManager::new(0);
// warmspot.py:93 test default retrace_limit=5 (rlib/jit.py:588
// PARAMETERS is 0, applied in production via set_user_param).
// rlib/jit.py:595 PARAMETERS default retrace_limit=0.
m.retrace_limit = DEFAULT_RETRACE_LIMIT;
// rlib/jit.py:598 / pyjitpl.py:2946: default 0 means
// the first cancelled unrolled compile immediately retries
Expand Down Expand Up @@ -492,8 +491,7 @@ impl WarmEnterState {
pureop_historylength: 16,
memory_manager: {
let mut m = crate::memmgr::MemoryManager::new(0);
// warmspot.py:93 test default retrace_limit=5 (rlib/jit.py:588
// PARAMETERS is 0, applied in production via set_user_param).
// rlib/jit.py:595 PARAMETERS default retrace_limit=0.
m.retrace_limit = DEFAULT_RETRACE_LIMIT;
// rlib/jit.py:598 / pyjitpl.py:2946: default 0 means
// the first cancelled unrolled compile immediately retries
Expand Down
3 changes: 3 additions & 0 deletions pyre/pyre-jit-trace/src/jitcode_dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9296,6 +9296,7 @@ mod tests {
concrete_frame_addr: 0,
pre_opcode_registers_r: None,
pre_opcode_semantic_depth: None,
suppress_guard_no_exception_for_opcode: false,
};

let ret_byte = *insns_opname_to_byte()
Expand Down Expand Up @@ -9368,6 +9369,7 @@ mod tests {
concrete_frame_addr: 0,
pre_opcode_registers_r: None,
pre_opcode_semantic_depth: None,
suppress_guard_no_exception_for_opcode: false,
};

let raise_byte = *insns_opname_to_byte()
Expand Down Expand Up @@ -9439,6 +9441,7 @@ mod tests {
concrete_frame_addr: 0,
pre_opcode_registers_r: None,
pre_opcode_semantic_depth: None,
suppress_guard_no_exception_for_opcode: false,
};
let ret_byte = *insns_opname_to_byte()
.get("ref_return/r")
Expand Down
Loading
Loading