Skip to content

Commit aebfac6

Browse files
committed
ZJIT: Materialize side exits from JITFrame metadata
1 parent 3513044 commit aebfac6

11 files changed

Lines changed: 357 additions & 235 deletions

File tree

vm.c

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2853,10 +2853,9 @@ rb_zjit_materialize_frames(const rb_execution_context_t *ec, rb_control_frame_t
28532853
cfp->block_code = NULL;
28542854
}
28552855

2856-
// Materialize Ruby stack slots kept off the VM stack. On side exit,
2857-
// the exiting frame is already written by compile_exit_save_state()
2858-
// and skipped here after materialize_exit_trampoline clears its
2859-
// jit_return, so this restores older ZJIT frames from stack maps.
2856+
// Materialize Ruby stack slots kept off the VM stack. Regular
2857+
// JITFrames restore one frame at a time. A side-exit JITFrame starts
2858+
// at the current frame and may span older inlined frames.
28602859
int32_t stack_size = (int32_t)jit_frame->stack_size;
28612860
if (stack_size > 0) {
28622861
VALUE *stack = cfp->sp;
@@ -2872,6 +2871,13 @@ rb_zjit_materialize_frames(const rb_execution_context_t *ec, rb_control_frame_t
28722871
else if (ZJIT_STACK_MAP_SKIP_P(entry)) {
28732872
stack -= ZJIT_STACK_MAP_SKIP_SIZE(entry);
28742873
}
2874+
else if (ZJIT_STACK_MAP_CONST_P(entry)) {
2875+
size_t index = ZJIT_STACK_MAP_CONST_INDEX(entry);
2876+
VM_ASSERT(jit_frame->side_exit != NULL);
2877+
VM_ASSERT(index < jit_frame->side_exit->constants_size);
2878+
stack--;
2879+
*stack = jit_frame->side_exit->constants[index];
2880+
}
28752881
else {
28762882
stack--;
28772883
*stack = entry;
@@ -2884,6 +2890,31 @@ rb_zjit_materialize_frames(const rb_execution_context_t *ec, rb_control_frame_t
28842890
cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
28852891
}
28862892
}
2893+
2894+
void rb_zjit_record_exit_stack(const char *reason);
2895+
void rb_zjit_exit_recompile(rb_execution_context_t *ec, VALUE compiled_iseq);
2896+
2897+
// Materialize a side exit while its native frame and saved registers are still
2898+
// available, then run optional cold-path instrumentation and recompilation.
2899+
void
2900+
rb_zjit_materialize_side_exit(rb_execution_context_t *ec, rb_control_frame_t *cfp, VALUE *sp)
2901+
{
2902+
const zjit_jit_frame_t *jit_frame = CFP_ZJIT_FRAME(cfp);
2903+
zjit_side_exit_t *side_exit = jit_frame->side_exit;
2904+
VM_ASSERT(side_exit != NULL);
2905+
2906+
// EP may point to an escaped heap environment for block frames, so restore
2907+
// SP from the live ZJIT SP register rather than deriving it from cfp->ep.
2908+
cfp->sp = sp + jit_frame->sp_size;
2909+
rb_zjit_materialize_frames(ec, cfp);
2910+
2911+
if (side_exit->reason != NULL) {
2912+
rb_zjit_record_exit_stack(side_exit->reason);
2913+
}
2914+
if (side_exit->compiled_iseq != NULL) {
2915+
rb_zjit_exit_recompile(ec, (VALUE)side_exit->compiled_iseq);
2916+
}
2917+
}
28872918
#endif
28882919

28892920
static inline VALUE

zjit.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ const zjit_jit_frame_t rb_zjit_c_frame = (zjit_jit_frame_t) {
4242
.pc = 0,
4343
.iseq = 0,
4444
.materialize_block_code = false,
45+
.sp_size = 0,
46+
.side_exit = NULL,
4547
};
4648

4749
void rb_zjit_profile_disable(const rb_iseq_t *iseq);

zjit.h

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
// zero, so RB_SPECIAL_CONST_P is false.
1818
#define ZJIT_STACK_MAP_VREG_TAG 0x08
1919
#define ZJIT_STACK_MAP_SKIP_TAG 0x10
20+
#define ZJIT_STACK_MAP_CONST_TAG 0x18
2021
#define ZJIT_STACK_MAP_TAG_MASK 0xff
2122
#define ZJIT_STACK_MAP_SHIFT 8
2223

@@ -44,6 +45,28 @@ ZJIT_STACK_MAP_SKIP_SIZE(VALUE entry)
4445
return entry >> ZJIT_STACK_MAP_SHIFT;
4546
}
4647

48+
static inline bool
49+
ZJIT_STACK_MAP_CONST_P(VALUE entry)
50+
{
51+
return (entry & ZJIT_STACK_MAP_TAG_MASK) == ZJIT_STACK_MAP_CONST_TAG;
52+
}
53+
54+
static inline size_t
55+
ZJIT_STACK_MAP_CONST_INDEX(VALUE entry)
56+
{
57+
return entry >> ZJIT_STACK_MAP_SHIFT;
58+
}
59+
60+
typedef struct zjit_side_exit {
61+
// The compiled unit to recompile after taking this exit, or NULL.
62+
const rb_iseq_t *compiled_iseq;
63+
// Heap VALUE constants referenced by tagged stack-map entries.
64+
VALUE *constants;
65+
// Perfetto side-exit reason, or NULL when this exit is not traced.
66+
const char *reason;
67+
uint32_t constants_size;
68+
} zjit_side_exit_t;
69+
4770
// JITFrame is defined here as the single source of truth and imported into
4871
// Rust via bindgen. C code reads fields directly; Rust uses an impl block.
4972
typedef struct zjit_jit_frame {
@@ -59,11 +82,19 @@ typedef struct zjit_jit_frame {
5982
// Always false for C frames.
6083
bool materialize_block_code;
6184

85+
// Number of operand-stack slots in the top frame. The side-exit
86+
// materializer adds this to the live ZJIT SP register.
87+
uint32_t sp_size;
88+
89+
// Side-exit-only metadata. NULL for entry and C-call JITFrames.
90+
zjit_side_exit_t *side_exit;
91+
6292
// Number of stack map entries in stack[].
6393
uint32_t stack_size;
6494
// Flexible array of stack map entries. Each entry is either an immediate
6595
// VALUE, a tagged native-stack index from cfp->jit_return for a value
66-
// kept by the JIT, or a tagged count of VM stack slots to skip.
96+
// kept by the JIT, a tagged side-exit constant-table index, or a tagged
97+
// count of VM stack slots to skip.
6798
VALUE stack[];
6899
} zjit_jit_frame_t;
69100

@@ -92,6 +123,7 @@ void rb_zjit_invalidate_no_singleton_class(VALUE klass);
92123
void rb_zjit_invalidate_root_box(void);
93124
void rb_zjit_jit_frame_update_references(zjit_jit_frame_t *jit_frame);
94125
void rb_zjit_materialize_frames(const rb_execution_context_t *ec, rb_control_frame_t *cfp);
126+
void rb_zjit_materialize_side_exit(rb_execution_context_t *ec, rb_control_frame_t *cfp, VALUE *sp);
95127
size_t rb_zjit_hash_new_size(void);
96128
bool rb_zjit_class_allocate_instance_fastpath(VALUE klass, size_t *size_out, shape_id_t *shape_id_out);
97129

zjit/bindgen/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ fn main() {
336336
.allowlist_var("ZJIT_STACK_MAP_SHIFT")
337337
.allowlist_var("ZJIT_STACK_MAP_VREG_TAG")
338338
.allowlist_var("ZJIT_STACK_MAP_SKIP_TAG")
339+
.allowlist_var("ZJIT_STACK_MAP_CONST_TAG")
339340
.allowlist_var("ZJIT_JIT_RETURN_C_FRAME")
340341
.allowlist_function("rb_assert_holding_vm_lock")
341342
.allowlist_function("rb_jit_shape_complex_p")

zjit/src/backend/arm64/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1644,7 +1644,6 @@ impl Assembler {
16441644
let (assignments, num_stack_slots) = trace_compile_phase("linear_scan", || asm.linear_scan(intervals.clone(), regs.len(), &preferred_registers));
16451645

16461646
asm.stack_state.num_spill_slots = num_stack_slots;
1647-
asm.stack_state.num_side_exit_stack_map_slots = asm.side_exit_stack_map_slots(&assignments);
16481647
let stack_slot_count = asm.stack_state.stack_slot_count();
16491648
if stack_slot_count > Self::MAX_FRAME_STACK_SLOTS {
16501649
return Err(CompileError::NativeStackTooLarge);
@@ -1782,7 +1781,7 @@ mod tests {
17821781

17831782
let val64 = asm.add(CFP, Opnd::UImm(64));
17841783
asm.store(Opnd::mem(64, SP, 0x10), val64);
1785-
let side_exit = Target::SideExit(Box::new(SideExitTarget { reason: SideExitReason::Interrupt, exit: SideExit { pc: 0.into(), iseq: std::ptr::null(), stack: vec![], locals: vec![], stack_map: None, recompile: None } }));
1784+
let side_exit = Target::SideExit(Box::new(SideExitTarget { reason: SideExitReason::Interrupt, exit: SideExit { pc: 0.into(), iseq: std::ptr::null(), stack: vec![], locals: vec![], stack_map: None, recompile: None, trace_reason: None } }));
17861785
asm.push_insn(Insn::Joz(val64, side_exit));
17871786
asm.mov(C_ARG_OPNDS[0], C_RET_OPND.with_num_bits(32));
17881787
asm.mov(C_ARG_OPNDS[1], Opnd::mem(64, SP, -8));

0 commit comments

Comments
 (0)