Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
39a6333
interp: publish the exec/eval locals mapping across the builtins plant
youknowone Sep 3, 2026
d7e1792
Collect on bigint mul/and/or/xor; nursery-allocate call-frame locals
youknowone Sep 5, 2026
c386bce
Skip unmodified vable store-back; use parent field descrs
youknowone Sep 5, 2026
49e6196
Add AllVirtuals, multi-root nursery alloc, and wasm CA barrier
youknowone Sep 6, 2026
0c0f01b
Restore skip of unmodified vable store-back after stash replay
youknowone Sep 6, 2026
42185de
wasm: pass wb and gc_maps to CheckMemoryError
youknowone Sep 6, 2026
7045b34
bench: record fib_recursive dynasm at 3 bridges
youknowone Sep 6, 2026
51b1206
wasm: compare num_ref_homes after WasmModuleData wrap
youknowone Sep 6, 2026
c032b29
opt: restore main preamble export for immutable field hoist
youknowone Sep 6, 2026
ca0fde7
Restore guard-share, branch-orgpc, and one-frame resume sizes
youknowone Sep 7, 2026
c7f6eec
wasm: restore WasmFailDescr force fields and rd_locs
youknowone Sep 7, 2026
78d9247
wasm: lower CallMallocNursery with reload wb and gc_maps
youknowone Sep 7, 2026
8844aae
wasm: expect index+1 force token and force-slot failargs
youknowone Sep 7, 2026
6edbd82
docs: drop PYRE_DIAG124C from the live gate list
youknowone Sep 7, 2026
e4459f3
walker: restore guarded_branch_core and mirror preflight
youknowone Sep 7, 2026
ee5caa1
bench: re-record dynasm jitstats after branch-core restore
youknowone Sep 7, 2026
d302f07
bench: re-record for_iter_direct_store_double under check.py env
youknowone Sep 7, 2026
212ebd2
Root savedata across force and fix wasm force decode
youknowone Sep 7, 2026
0718923
test: expect branch-guard resume at the not-taken arm
youknowone Sep 7, 2026
a9ba717
Keep last-use call Refs and publish wasm savedata
youknowone Sep 7, 2026
37581ce
Fix cranelift force gcmap slots and refresh wasm jitstats
youknowone Sep 8, 2026
73cd38a
Restore live PyFrame when vable identity decodes empty
youknowone Sep 8, 2026
24b9d97
Root AllVirtuals across init and refresh leftover review nits
youknowone Sep 8, 2026
b05d53e
Do not call OpRef::raw on inline constants
youknowone Sep 8, 2026
efa4b3b
Allocate JIT exceptions in non-moving oldgen
youknowone Sep 8, 2026
9c8d431
Publish residual GCREF args before any normalize
youknowone Sep 8, 2026
d8b1a7a
Do not override a live vable identity with the JITFRAME
youknowone Sep 8, 2026
b99ea43
Follow residual exception forwarding; skip non-object GC slots
youknowone Sep 9, 2026
8e2eb99
Fix multi-root large alloc and review gcmap/root slots
youknowone Sep 9, 2026
5bb8c01
Build WalkContext test fixture from WalkFrameState
youknowone Sep 9, 2026
69dd3fd
Size wasm test frames and root savedata across boxed
youknowone Sep 9, 2026
2b50ada
Scale wasm/dynasm ceiling from max-pypy-ratio
youknowone Sep 10, 2026
4d320d5
Revert wasm/dynasm ceiling scale from max-pypy-ratio
youknowone Sep 10, 2026
3a97060
Keep wasm force frames live across boxed deadframes
youknowone Sep 10, 2026
1fff947
Remap leftover vable InputArgs in densify and patch
youknowone Sep 10, 2026
2baa99d
Rebuild reduced vable tails before patch_new_loop
youknowone Sep 10, 2026
18015a2
Record gettrace on inlined execute_frame enter/leave
youknowone Sep 10, 2026
d59c6e6
Pass wb and gc_maps through wasm memory-error helpers
youknowone Sep 10, 2026
4b46092
Forward densified vable slots; do not remap leftover Refs
youknowone Sep 11, 2026
b8f54d3
Split InputArg/Op forwarding; leave leftover body Refs
youknowone Sep 11, 2026
0cc7483
Do not rebuild a leftover-empty vable tail
youknowone Sep 11, 2026
5ce598c
Walk leftover-empty vable tails from entry mints
youknowone Sep 11, 2026
a3aac5f
GETFIELD leftover-empty slots the assembler will not pass
youknowone Sep 11, 2026
636f643
Record leftover-empty GETFIELD jitstats and wasm allowances
youknowone Sep 11, 2026
96cc0aa
Format leftover-empty vable walk for cargo fmt
youknowone Sep 11, 2026
73f7f32
Do not GETFIELD leftover-empty slots past the mint
youknowone Sep 11, 2026
6eb407d
Wrap leftover-empty fit_walk_lengths for cargo fmt
youknowone Sep 11, 2026
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
2 changes: 2 additions & 0 deletions majit/gc-root-brackets.baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"unbracketed_calls_fns": 826,
"unmatched_seeds": [
"majit_gc::standalone_alloc_fast_nursery_collecting_typed_rooted",
"majit_gc::standalone_alloc_fast_nursery_collecting_typed_roots",
"majit_gc::standalone_alloc_nursery_collecting_typed_rooted"
]
},
Expand All @@ -32,6 +33,7 @@
"unbracketed_calls_fns": 836,
"unmatched_seeds": [
"majit_gc::standalone_alloc_fast_nursery_collecting_typed_rooted",
"majit_gc::standalone_alloc_fast_nursery_collecting_typed_roots",
"majit_gc::standalone_alloc_nursery_collecting_typed_rooted"
]
}
Expand Down
213 changes: 187 additions & 26 deletions majit/majit-backend-cranelift/src/compiler.rs

Large diffs are not rendered by default.

18 changes: 7 additions & 11 deletions majit/majit-backend-dynasm/src/aarch64/assembler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6108,11 +6108,6 @@ impl<'a> AssemblerARM64<'a> {
.copied()
.expect("call_assembler missing rewritten jitframe arg");
let vable_loc = arglocs.get(1).copied();
// aarch64/regalloc.py:661-664 routes CALL_ASSEMBLER through
// `_call(..., gc_level=2)`, which spills all managed registers.
// x19 is already saved by the JIT prologue, so use it as scratch
// here without an extra call-site stack save.
dynasm!(self.mc ; .arch aarch64 ; mov x19, x29);
self.emit_load_to_rax(frame_loc);

let descr_arc = op.getdescr();
Expand Down Expand Up @@ -6143,9 +6138,6 @@ impl<'a> AssemblerARM64<'a> {

if !is_resolved {
let force_addr = crate::call_assembler_force_fn_addr() as i64;
dynasm!(self.mc ; .arch aarch64
; mov x29, x19
);
if force_addr != 0 {
if let Some(vloc) = vable_loc {
self.emit_load_to_rax(vloc);
Expand Down Expand Up @@ -6190,9 +6182,13 @@ impl<'a> AssemblerARM64<'a> {
} else if let Some(entry_label) = self.self_entry_label {
dynasm!(self.mc ; .arch aarch64 ; bl =>entry_label);
}
dynasm!(self.mc ; .arch aarch64
; mov x29, x19
);
// aarch64/callbuilder.py `CallBuilderARM64.pop_gcmap` calls
// `AssemblerARM64._reload_frame_if_necessary` before clearing
// the map. The callee footer restores the pre-call x29 from the
// C stack, but that address may be stale after a minor collection
// moved the caller jitframe. Reload x29 from the shadow stack;
// keeping the old address in an allocatable callee-saved register
// is both non-orthodox and unsound.
self.pop_pending_call_gcmap_after_collect(pushed_gcmap);

let fast_path = self.mc.new_dynamic_label();
Expand Down
7 changes: 6 additions & 1 deletion majit/majit-backend-dynasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ use std::sync::OnceLock;
/// _prepare_resume_from_failure` hands it to the resumed frame so an
/// exception guard unwinds into its handler instead of resuming the
/// no-exception continuation. `0` = no pending exception.
pub type BlackholeFn = fn(usize, *const i64, usize, *const i64, usize, i64) -> Option<i64>;
pub type BlackholeFn = fn(usize, *const i64, usize, *const i64, usize, i64, usize) -> Option<i64>;
Comment thread
youknowone marked this conversation as resolved.

/// Bridge compilation: raw values, descr identity, and optional GUARD_VALUE
/// operand → compiled?
Expand Down Expand Up @@ -811,13 +811,18 @@ fn handle_fail_resume_guard(
// re-reading `jf_guard_exc` here would observe the post-`grab_exc_value`
// null and drop the exception.
let bh_result = CA_BLACKHOLE_FN.get().and_then(|blackhole| {
// compile.py ResumeGuardForcedDescr.handle_fail reads savedata from
// this same deadframe after any bridge attempt. The libc jitframe
// tracer updates the field if that attempt collected the nursery.
let savedata = unsafe { (*frame_ptr).jf_savedata };
blackhole(
descr_raw,
raw_values.as_ptr(),
raw_values.len(),
raw_values.as_ptr(),
raw_values.len(),
guard_exc_root.0 as i64,
savedata,
)
});
if let Some(bh_result) = bh_result {
Expand Down
69 changes: 32 additions & 37 deletions majit/majit-backend-dynasm/src/regalloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2741,8 +2741,11 @@ impl<'a> RegAlloc<'a> {
| GuardKind::Overflow
| GuardKind::NotInvalidated
| GuardKind::FutureCondition
| GuardKind::NotForced
| GuardKind::AlwaysFails => self.consider_guard_no_args_j2(fail_args, i, output),
GuardKind::NotForced if op.opcode == OpCode::GuardNotForced2 => {
self.consider_guard_not_forced_2_j2(fail_args, i, output)
}
GuardKind::NotForced => self.consider_guard_no_args_j2(fail_args, i, output),
GuardKind::Exception => {
self.consider_guard_exception_j2(args, fail_args, op, i, output)
}
Expand Down Expand Up @@ -5230,29 +5233,31 @@ impl<'a> RegAlloc<'a> {
}
}

/// llsupport/regalloc.py locs_for_call_assembler parity.
/// RPython syncs args to stack, then before_call spills everything.
/// We force-sync register args to frame first via _sync_var_to_stack,
/// then before_call spills remaining. arglocs after before_call are
/// all Frame or Immed — no register-clobber issues during calloc.
/// llsupport/regalloc.py `locs_for_call_assembler` parity.
///
/// RPython syncs only argument 1 (the virtualizable) to the frame. The
/// callee jitframe in argument 0 stays in its current location, captured
/// before `before_call`.
fn consider_call_assembler(&mut self, op: &Op, i: usize, output: &mut Vec<RegAllocOp>) {
// llsupport/regalloc.py: self.rm._sync_var_to_stack(op.getarg(k))
// Force all register-held args to frame before before_call.
for arg in op.getarglist().iter() {
if arg.is_constant() {
continue;
}
let arg = arg.to_opref();
let tp = self.tp(arg);
assert!(matches!(op.num_args(), 1 | 2));
if op.num_args() == 2 {
let vable = op.arg(1).to_opref();
let tp = self.tp(vable);
if tp == Type::Float {
self.xrm
._sync_var_to_stack(arg, tp, &mut self.longevity, &mut self.fm);
._sync_var_to_stack(vable, tp, &mut self.longevity, &mut self.fm);
} else {
self.rm
._sync_var_to_stack(arg, tp, &mut self.longevity, &mut self.fm);
._sync_var_to_stack(vable, tp, &mut self.longevity, &mut self.fm);
}
}

let mut arglocs = Vec::with_capacity(op.num_args());
for arg in op.getarglist().iter() {
let arg = arg.to_opref();
arglocs.push(self.loc(arg, self.tp(arg)));
}

let type_index = OpTypeIndex::from_parts(
self.inputargs,
self.operations,
Expand All @@ -5276,14 +5281,6 @@ impl<'a> RegAlloc<'a> {
&type_index,
);

// After before_call, all args are in Frame or Const — safe for calloc.
let mut arglocs: Vec<Loc> = Vec::new();
for arg in op.getarglist().iter() {
let arg = arg.to_opref();
let tp = self.tp(arg);
arglocs.push(self.loc_must_exist(arg, tp));
}

let result_tp = op.opcode.result_type();
let result_loc = if result_tp != Type::Void {
let r = if result_tp == Type::Float {
Expand All @@ -5307,20 +5304,24 @@ impl<'a> RegAlloc<'a> {
i: usize,
output: &mut Vec<RegAllocOp>,
) {
for &arg in args {
if arg.is_constant() {
continue;
}
let tp = self.tp(arg);
assert!(matches!(args.len(), 1 | 2));
if args.len() == 2 {
let vable = args[1];
let tp = self.tp(vable);
if tp == Type::Float {
self.xrm
._sync_var_to_stack(arg, tp, &mut self.longevity, &mut self.fm);
._sync_var_to_stack(vable, tp, &mut self.longevity, &mut self.fm);
} else {
self.rm
._sync_var_to_stack(arg, tp, &mut self.longevity, &mut self.fm);
._sync_var_to_stack(vable, tp, &mut self.longevity, &mut self.fm);
}
}

let mut arglocs = Vec::with_capacity(args.len());
for &arg in args {
arglocs.push(self.loc(arg, self.tp(arg)));
}

let type_index = OpTypeIndex::from_parts(
self.inputargs,
self.operations,
Expand All @@ -5344,12 +5345,6 @@ impl<'a> RegAlloc<'a> {
&type_index,
);

let mut arglocs: Vec<Loc> = Vec::new();
for &arg in args {
let tp = self.tp(arg);
arglocs.push(self.loc_must_exist(arg, tp));
}

let result_tp = op.opcode.result_type();
let result_loc = if result_tp != Type::Void {
let dst = dst.unwrap_or(op.pos.get());
Expand Down
56 changes: 55 additions & 1 deletion majit/majit-backend-dynasm/src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,9 @@ fn register_active_hooks(supports_guard_gc_type: bool) {
majit_gc::set_active_alloc_nursery_collecting_typed_rooted(Some(
dynasm_alloc_nursery_collecting_typed_rooted,
));
majit_gc::set_active_alloc_nursery_collecting_typed_roots(Some(
dynasm_alloc_nursery_collecting_typed_roots,
));
majit_gc::set_active_alloc_oldgen_typed(Some(dynasm_alloc_oldgen_typed));
majit_gc::set_active_collect_generation(Some(dynasm_collect_generation));
majit_gc::set_active_collect_step(Some(dynasm_collect_step));
Expand Down Expand Up @@ -706,6 +709,35 @@ unsafe fn dynasm_alloc_nursery_collecting_typed_rooted(
}
}

unsafe fn dynasm_alloc_nursery_collecting_typed_roots(
type_id: u32,
size: usize,
roots: *mut GcRef,
root_count: usize,
needs_write_barrier: *mut bool,
) -> GcRef {
if let Some(r) = gc_box::with_mut(|g| unsafe {
g.alloc_fast_nursery_collecting_typed_roots(
type_id,
size,
roots,
root_count,
needs_write_barrier,
)
}) {
return r;
}
unsafe {
majit_gc::standalone_alloc_fast_nursery_collecting_typed_roots(
type_id,
size,
roots,
root_count,
needs_write_barrier,
)
}
}

/// Host-side old-gen allocation trampoline. Used by
/// pyre-object allocators (`w_int_new`, `w_float_new`) whose
/// callers cannot register the returned pointer as a GC root before
Expand Down Expand Up @@ -3218,6 +3250,7 @@ impl Backend for DynasmBackend {
// grab_exc_value (llmodel.py): read jf_guard_exc off the deadframe
// tip before the libc jitframe chain is freed (same as execute_token).
let exception_value = GcRef(unsafe { (*result_jf).jf_guard_exc });
let savedata = GcRef(unsafe { (*result_jf).jf_savedata });
let guard_value_operand = majit_backend::guard_value_counter_slot(descr_fd)
.map(|slot| unsafe { crate::llmodel::get_int_value_direct(result_jf, slot) as i64 });

Expand All @@ -3232,7 +3265,7 @@ impl Backend for DynasmBackend {
outputs,
typed_outputs,
exit_layout,
savedata: None,
savedata: (!savedata.is_null()).then_some(savedata),
exception_value,
fail_index: descr_fd.fail_index_per_trace(),
trace_id: descr_fd.trace_id(),
Expand Down Expand Up @@ -3316,6 +3349,27 @@ impl Backend for DynasmBackend {
}
}

fn set_savedata_ref(&self, frame: &mut DeadFrame, data: GcRef) {
match frame {
DeadFrame::JitFrame(jf) => {
// llmodel.py set_savedata_ref is a GCREF field store.
majit_gc::gc_write_barrier(jf.jf_gcref());
jf.set_savedata_ref(data);
}
DeadFrame::LibcJitFrame(jf) => jf.set_savedata_ref(data),
DeadFrame::Boxed(_) => panic!("dynasm deadframe is a jitframe"),
}
}

fn get_savedata_ref(&self, frame: &DeadFrame) -> Option<GcRef> {
let data = match frame {
DeadFrame::JitFrame(jf) => jf.get_savedata_ref(),
DeadFrame::LibcJitFrame(jf) => jf.get_savedata_ref(),
DeadFrame::Boxed(_) => panic!("dynasm deadframe is a jitframe"),
};
(!data.is_null()).then_some(data)
}

fn clear_stored_exception(&self) {
crate::jit_exc_clear();
}
Expand Down
Loading
Loading