Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
080a38a
jit: stop forcing the appended value's box in the list-append fold
youknowone Aug 12, 2026
d3b2285
jit: preserve box identity across short preambles
youknowone Aug 13, 2026
b8b8c24
jit: lazily load frozen indirect call targets
youknowone Aug 13, 2026
f002860
jit: load the build-time descr pool per index instead of as one table
youknowone Aug 14, 2026
b2bda7d
descr: mint the ExecutionContext group as a non-GC-managed struct
youknowone Aug 14, 2026
d080fcd
optimizeopt: skip GUARD_GC_TYPE when the descr names no type id
youknowone Aug 14, 2026
557e3ba
majit: bind a rebuilt short preamble's inputarg domain for the next r…
youknowone Aug 14, 2026
5d9901a
bench: re-record the synth jit-stats this branch moves
youknowone Aug 14, 2026
4211648
bench: restore the three small jit-stats deltas to their committed va…
youknowone Aug 14, 2026
01b22f8
gate-triage: register PYRE_DESCR_DEMAND, and re-record four CI-confir…
youknowone Aug 14, 2026
e058c70
optimizeopt: resolve a layout guard's runtime tid, or decline the sho…
youknowone Aug 14, 2026
c462db1
optimizeopt: assert import_state's source/target on box identity
youknowone Aug 14, 2026
ccabf45
jit: keep the first index for a folded runtime fnaddr
youknowone Aug 14, 2026
6f1d5e8
bench: re-record the six wasm jit-stats baselines this branch moves
youknowone Aug 14, 2026
1093a27
descr: mint the four PyCode field descrs as one group
youknowone Aug 15, 2026
404fbba
majit: log the pre-optimization trace under jit-log-noopt
youknowone Aug 15, 2026
c1ea97b
majit: cache ordinary heap fields read off a virtualizable receiver
youknowone Aug 15, 2026
8a3f267
bench: re-record twelve synthetic jitstats baselines
youknowone Aug 15, 2026
8467699
bench: re-record eleven synthetic wasm jitstats baselines
youknowone Aug 15, 2026
ce706d1
bench: correct three synthetic wasm jitstats baselines
youknowone Aug 15, 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
36 changes: 36 additions & 0 deletions majit/majit-ir/src/ptr_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,16 @@ pub struct VirtualizableFieldState {
/// Tracked array field values: (array_field_index, element_values).
/// Indices correspond to VirtualizableInfo::array_fields order.
pub arrays: Vec<(u32, Vec<Operand>)>,
/// Ordinary (non-virtualizable) heap fields read off this object, keyed by
/// `FieldDescr::index_in_parent` — the same `_fields` role
/// `AbstractStructPtrInfo` plays at `info.py:175-214`.
///
/// `fields` above is a different index space (`VirtualizableInfo::
/// static_fields` order), so the two cannot share storage. Upstream needs
/// no such split: it has no virtualizable-specific `PtrInfo` subclass, so
/// a virtualizable frame carries a plain `InstancePtrInfo` and every field
/// read off it lands in the one `_fields` list the heap cache consults.
pub heap_fields: Vec<(u32, FieldEntry)>,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Expose virtualizable heap fields during state export

Include this new storage in PtrInfo::all_items(). PtrInfo::setfield can now put an ordinary field value here, but OptUnroll::expand_info uses all_items() to discover and recursively export nested values, and that method still returns an empty vector for Virtualizable. If such a field contains a virtual object at a loop boundary, the next iteration can recover its operand from the field cache without importing the object's PtrInfo, allowing an unmaterialized virtual to be treated as a concrete reference. Upstream has one _fields collection exposed verbatim by all_items(), so the split storage must preserve that behavior.

AGENTS.md reference: AGENTS.md:L288-L290

Useful? React with 👍 / 👎.

/// info.py:91-92
pub last_guard_pos: i32,
}
Expand Down Expand Up @@ -1155,6 +1165,15 @@ impl PtrInfo {
}
v.fields.push((field_idx, value));
}
PtrInfo::Virtualizable(v) => {
for entry in &mut v.heap_fields {
if entry.0 == field_idx {
entry.1 = FieldEntry::Value(value.clone());
return;
}
}
v.heap_fields.push((field_idx, FieldEntry::Value(value)));
}
_ => {}
}
}
Expand All @@ -1171,6 +1190,13 @@ impl PtrInfo {
v.fields.retain(|(k, _)| *k != field_idx);
v.fields.push((field_idx, FieldEntry::Preamble(pop)));
}
// The catch-all below re-seats `self` as an `InstancePtrInfo`,
// which would drop the tracked virtualizable state. Store the
// hoisted field alongside the other ordinary heap fields instead.
PtrInfo::Virtualizable(v) => {
v.heap_fields.retain(|(k, _)| *k != field_idx);
v.heap_fields.push((field_idx, FieldEntry::Preamble(pop)));
}
_ => {
*self = PtrInfo::Instance(InstancePtrInfo {
descr: None,
Expand Down Expand Up @@ -1204,6 +1230,10 @@ impl PtrInfo {
.fields
.iter()
.any(|(k, e)| *k == field_idx && e.is_preamble()),
PtrInfo::Virtualizable(v) => v
.heap_fields
.iter()
.any(|(k, e)| *k == field_idx && e.is_preamble()),
_ => false,
}
}
Expand Down Expand Up @@ -1276,6 +1306,7 @@ impl PtrInfo {
}
PtrInfo::Virtual(v) => v.fields.retain(|(k, _)| *k != field_idx),
PtrInfo::VirtualStruct(v) => v.fields.retain(|(k, _)| *k != field_idx),
PtrInfo::Virtualizable(v) => v.heap_fields.retain(|(k, _)| *k != field_idx),
_ => {}
}
}
Expand Down Expand Up @@ -1334,6 +1365,11 @@ impl PtrInfo {
.iter()
.find(|(k, _)| *k == field_idx)
.map(|(_, v)| FieldEntry::Value(v.clone())),
PtrInfo::Virtualizable(v) => v
.heap_fields
.iter()
.find(|(k, _)| *k == field_idx)
.map(|(_, e)| e.clone()),
_ => None,
}
}
Expand Down
35 changes: 20 additions & 15 deletions majit/majit-metainterp/src/blackhole.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ pub enum BhReturnType {
/// Re-export BhDescr from codewriter::jitcode — shared descriptor type
/// between codewriter assembler and blackhole interpreter.
/// RPython `history.py:AbstractDescr` parity.
pub use majit_translate::jitcode::{BhCallDescr, BhDescr};
pub use majit_translate::jitcode::{BhCallDescr, BhDescr, DescrTable, EMPTY_DESCR_TABLE};

/// Per-jitdriver static data visible to the blackhole interpreter.
///
Expand Down Expand Up @@ -201,7 +201,7 @@ pub struct BlackholeInterpreter {
/// stores the assembler list itself, so the table is shared and never
/// copied; :154 is its only consumer and only reads. This has the same
/// lifetime shape as the sibling `cpu: Option<&'static dyn Backend>`.
pub descrs: &'static [BhDescr],
pub descrs: &'static dyn DescrTable,
/// RPython `blackhole.py:289` `self.op_catch_exception = builder.op_catch_exception`.
pub op_catch_exception: u8,
/// RPython `blackhole.py:290` `self.op_rvmprof_code = builder.op_rvmprof_code`.
Expand Down Expand Up @@ -385,7 +385,7 @@ impl Default for BlackholeInterpreter {
Self {
cpu: None,
// blackhole.py:280 `EMPTY_LIST_I = [] # shared`.
descrs: &[],
descrs: EMPTY_DESCR_TABLE,
// RPython blackhole.py:289 — copied from builder in `acquire_interp`.
// Sentinel `u8::MAX` matches RPython's `insns.get('…', -1)` fallback.
op_catch_exception: u8::MAX,
Expand Down Expand Up @@ -2043,7 +2043,7 @@ pub struct BlackholeInterpBuilder {
pub op_rvmprof_code: u8,
/// RPython `blackhole.py:103` `self.descrs`.
/// Populated by `setup_descrs()` from the assembler's descriptor table.
pub descrs: &'static [BhDescr],
pub descrs: &'static dyn DescrTable,
/// Dispatch table: opcode byte → handler fn pointer.
/// RPython builds `dispatch_loop` closure via `unrolling_iterable`;
/// Rust uses indirect call through this table.
Expand Down Expand Up @@ -2080,7 +2080,7 @@ impl BlackholeInterpBuilder {
op_catch_exception: u8::MAX,
op_rvmprof_code: u8::MAX,
// blackhole.py:280 `EMPTY_LIST_I = [] # shared`.
descrs: &[],
descrs: EMPTY_DESCR_TABLE,
dispatch_table: std::sync::Arc::new(Vec::new()),
jitdrivers_sd: Vec::new(),
}
Expand Down Expand Up @@ -2222,7 +2222,7 @@ impl BlackholeInterpBuilder {
}

/// RPython `blackhole.py:102-103` `setup_descrs(descrs)`.
pub fn setup_descrs(&mut self, descrs: &'static [BhDescr]) {
pub fn setup_descrs(&mut self, descrs: &'static dyn DescrTable) {
self.descrs = descrs;
}

Expand Down Expand Up @@ -4741,13 +4741,10 @@ mod tests {
fn test_clone_context_from_mirrors_acquire_interp_fields() {
let mut builder = super::build_inline_call_only_bh_builder();
let mut parent = builder.acquire_interp();
let table: &'static [BhDescr] = Box::leak(
vec![
BhDescr::VableField { index: 1 },
BhDescr::VableArray { index: 2 },
]
.into_boxed_slice(),
);
let table: &'static [BhDescr; 2] = Box::leak(Box::new([
BhDescr::VableField { index: 1 },
BhDescr::VableArray { index: 2 },
]));
parent.descrs = table;
// Make the parent's vable / jitdriver state non-default so
// the assertion below distinguishes "copied" from
Expand All @@ -4766,7 +4763,10 @@ mod tests {
assert_eq!(callee.op_rvmprof_code, parent.op_rvmprof_code);
assert_eq!(callee.op_live, parent.op_live);
assert!(
std::ptr::eq(callee.descrs.as_ptr(), parent.descrs.as_ptr()),
std::ptr::eq(
std::ptr::from_ref(callee.descrs).cast::<()>(),
std::ptr::from_ref(parent.descrs).cast::<()>(),
),
"clone_context_from must alias the parent table"
);
assert_eq!(callee.virtualizable_ptr, parent.virtualizable_ptr);
Expand Down Expand Up @@ -6874,7 +6874,12 @@ fn read_descr<'a>(bh: &'a BlackholeInterpreter, code: &[u8], pos: usize) -> (&'a
});
return (descr, pos + 2);
}
let descr = &bh.descrs[descr_idx]; // RPython: no fallback, index must be valid
let descr = bh.descrs.get(descr_idx).unwrap_or_else(|| {
panic!(
"d-arg descrs[{descr_idx}] is out of range for {} entries",
bh.descrs.len()
)
}); // RPython: no fallback, index must be valid
(descr, pos + 2)
}

Expand Down
42 changes: 15 additions & 27 deletions majit/majit-metainterp/src/jitcode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,22 +292,15 @@ impl RuntimeBhDescr {
}
}

/// Newtype so the write-once, read-only global descr pool can live in a
/// `static`. `RuntimeBhDescr` is `!Sync` only because its
/// `Call(JitCallTarget)` variant carries raw function-address pointers; the
/// pool this crate installs never holds that variant (build-time jitcodes'
/// residual-call targets resolve from a funcptr register at dispatch time),
/// and the pool is written once through the `OnceLock` and thereafter only
/// read, so sharing it across threads is sound — the same `unsafe impl`
/// rationale as [`JitCode`] itself above.
struct GlobalDescrPool(Vec<RuntimeBhDescr>);

// SAFETY: `GlobalDescrPool` is written once (via `OnceLock::set`) and read-only
// thereafter; the raw pointers `RuntimeBhDescr` can carry are stable code
// addresses, and the pool this crate installs carries none. `OnceLock<T>: Sync`
// additionally requires `T: Send`.
unsafe impl Send for GlobalDescrPool {}
unsafe impl Sync for GlobalDescrPool {}
/// Runtime view of the process-global build-time descriptor pool.
pub trait RuntimeDescrTable: Sync {
fn get(&self, index: usize) -> Option<&'static RuntimeBhDescr>;
fn len(&self) -> usize;

fn is_empty(&self) -> bool {
self.len() == 0
}
}

/// Process-global build-time descr pool — RPython's single shared
/// `Assembler.descrs` (`assembler.py:23`). Runtime-emitted jitcodes keep a
Expand All @@ -318,27 +311,22 @@ unsafe impl Sync for GlobalDescrPool {}
/// crate (`pyre-jit-trace`) from its build-time `ALL_DESCRS` / `ALL_JITCODES`
/// tables; `majit-metainterp` cannot build it because those tables live above
/// it.
static GLOBAL_BUILD_DESCR_POOL: std::sync::OnceLock<GlobalDescrPool> = std::sync::OnceLock::new();
static GLOBAL_BUILD_DESCR_POOL: std::sync::OnceLock<&'static dyn RuntimeDescrTable> =
std::sync::OnceLock::new();

/// Install the process-global build-time descr pool. Idempotent: the first
/// call wins and later calls are ignored (the pool is a frozen build artifact,
/// identical across callers). See `GLOBAL_BUILD_DESCR_POOL`.
///
/// `build` runs only on the call that installs the pool. It takes a closure
/// rather than a built `Vec` because the callers sit on hot paths — the jd1
/// driver installs before every `_unpackiterable_unknown_length` walk — and
/// building the pool clones every `BhDescr` in the binary (including each call
/// descr's `EffectInfo` raw descr sets). Materializing that just to have
/// `OnceLock::set` drop it is the whole cost of the call.
pub fn init_global_build_descr_pool(build: impl FnOnce() -> Vec<RuntimeBhDescr>) {
GLOBAL_BUILD_DESCR_POOL.get_or_init(|| GlobalDescrPool(build()));
pub fn init_global_build_descr_pool(table: &'static dyn RuntimeDescrTable) {
let _ = GLOBAL_BUILD_DESCR_POOL.set(table);
}

/// The installed global build-time descr pool, or `None` if the embedding
/// crate has not installed one (e.g. a standalone metainterp unit test that
/// only exercises runtime-built jitcodes).
pub(crate) fn global_build_descr_pool() -> Option<&'static [RuntimeBhDescr]> {
GLOBAL_BUILD_DESCR_POOL.get().map(|pool| pool.0.as_slice())
pub(crate) fn global_build_descr_pool() -> Option<&'static dyn RuntimeDescrTable> {
GLOBAL_BUILD_DESCR_POOL.get().copied()
}

/// Per-`JitCode` descrs. Pyre's analog of
Expand Down
2 changes: 1 addition & 1 deletion majit/majit-metainterp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ pub use jit_state::{
bridge_decode_red,
};
pub use jitcode::{
BC_GOTO, JitArgKind, JitCallArg, JitCode, JitCodeBuilder, RuntimeBhDescr,
BC_GOTO, JitArgKind, JitCallArg, JitCode, JitCodeBuilder, RuntimeBhDescr, RuntimeDescrTable,
init_global_build_descr_pool, insns, live_slots_for_state_field_jit,
};
pub use jitdriver::{
Expand Down
Loading
Loading