Skip to content

Commit 4889e82

Browse files
committed
ZJIT: Introduce the InvokeSuperDirect HIR instruction
The new instruction is an optimized version of `InvokeSuper` when we know the `super` target is an ISEQ.
1 parent aa7acfb commit 4889e82

4 files changed

Lines changed: 369 additions & 5 deletions

File tree

zjit/src/codegen.rs

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -397,8 +397,10 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
397397
// Give up SendWithoutBlockDirect for 6+ args since asm.ccall() doesn't support it.
398398
Insn::SendWithoutBlockDirect { cd, state, args, .. } if args.len() + 1 > C_ARG_OPNDS.len() => // +1 for self
399399
gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), SendFallbackReason::TooManyArgsForLir),
400-
Insn::SendWithoutBlockDirect { cme, iseq, recv, args, state, .. } => gen_send_without_block_direct(cb, jit, asm, *cme, *iseq, opnd!(recv), opnds!(args), &function.frame_state(*state)),
400+
Insn::SendWithoutBlockDirect { cme, iseq, recv, args, state, .. } => gen_send_iseq_direct(cb, jit, asm, *cme, *iseq, opnd!(recv), opnds!(args), &function.frame_state(*state), None),
401401
&Insn::InvokeSuper { cd, blockiseq, state, reason, .. } => gen_invokesuper(jit, asm, cd, blockiseq, &function.frame_state(state), reason),
402+
Insn::InvokeSuperDirect { recv, current_cme, super_cme, args, state, .. } =>
403+
gen_invokesuper_direct(cb, jit, asm, *current_cme, *super_cme, opnd!(recv), opnds!(args), &function.frame_state(*state)),
402404
&Insn::InvokeBlock { cd, state, reason, .. } => gen_invokeblock(jit, asm, cd, &function.frame_state(state), reason),
403405
// Ensure we have enough room fit ec, self, and arguments
404406
// TODO remove this check when we have stack args (we can use Time.new to test it)
@@ -1299,8 +1301,10 @@ fn gen_send_without_block(
12991301
)
13001302
}
13011303

1302-
/// Compile a direct jump to an ISEQ call without block
1303-
fn gen_send_without_block_direct(
1304+
/// Compile a direct call to an ISEQ method.
1305+
/// If `block_handler` is provided, it's used as the specval for the new frame (for forwarding blocks).
1306+
/// Otherwise, `VM_BLOCK_HANDLER_NONE` is used.
1307+
fn gen_send_iseq_direct(
13041308
cb: &mut CodeBlock,
13051309
jit: &mut JITState,
13061310
asm: &mut Assembler,
@@ -1309,6 +1313,7 @@ fn gen_send_without_block_direct(
13091313
recv: Opnd,
13101314
args: Vec<Opnd>,
13111315
state: &FrameState,
1316+
block_handler: Option<Opnd>,
13121317
) -> lir::Opnd {
13131318
gen_incr_counter(asm, Counter::iseq_optimized_send_count);
13141319

@@ -1335,7 +1340,8 @@ fn gen_send_without_block_direct(
13351340
let bmethod_specval = (capture.ep.addr() | 1).into();
13361341
(bmethod_frame_type, bmethod_specval)
13371342
} else {
1338-
(VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL, VM_BLOCK_HANDLER_NONE.into())
1343+
let specval = block_handler.unwrap_or_else(|| VM_BLOCK_HANDLER_NONE.into());
1344+
(VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL, specval)
13391345
};
13401346

13411347
// Set up the new frame
@@ -1459,6 +1465,37 @@ fn gen_invokesuper(
14591465
)
14601466
}
14611467

1468+
/// Compile an optimized super call to an ISEQ method
1469+
fn gen_invokesuper_direct(
1470+
cb: &mut CodeBlock,
1471+
jit: &mut JITState,
1472+
asm: &mut Assembler,
1473+
current_cme: *const rb_callable_method_entry_t,
1474+
super_cme: *const rb_callable_method_entry_t,
1475+
recv: lir::Opnd,
1476+
args: Vec<lir::Opnd>,
1477+
state: &FrameState,
1478+
) -> lir::Opnd {
1479+
// Guard that ep[VM_ENV_DATA_INDEX_ME_CREF] matches current_cme, ensuring that we're calling
1480+
// `super` from the expected method context.
1481+
asm_comment!(asm, "guard super method entry");
1482+
let lep = gen_get_lep(jit, asm);
1483+
let ep_me_opnd = Opnd::mem(64, lep, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_ME_CREF);
1484+
let ep_me = asm.load(ep_me_opnd);
1485+
asm.cmp(ep_me, Opnd::UImm(current_cme as u64));
1486+
asm.jne(side_exit(jit, state, SideExitReason::GuardSuperMethodEntry));
1487+
1488+
// Get the super method's ISEQ since we'll be dispatching to that.
1489+
let super_iseq = unsafe { get_def_iseq_ptr((*super_cme).def) };
1490+
1491+
// Read the block handler from the LEP to forward the caller's block.
1492+
asm_comment!(asm, "load block handler from LEP");
1493+
let block_handler = asm.load(Opnd::mem(64, lep, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL));
1494+
1495+
// Generate the code to invoke the `super` target method.
1496+
gen_send_iseq_direct(cb, jit, asm, super_cme, super_iseq, recv, args, state, Some(block_handler))
1497+
}
1498+
14621499
/// Compile a string resurrection
14631500
fn gen_string_copy(asm: &mut Assembler, recv: Opnd, chilled: bool, state: &FrameState) -> Opnd {
14641501
// TODO: split rb_ec_str_resurrect into separate functions

zjit/src/hir.rs

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,7 @@ pub enum SideExitReason {
493493
GuardNotFrozen,
494494
GuardLess,
495495
GuardGreaterEq,
496+
GuardSuperMethodEntry,
496497
PatchPoint(Invariant),
497498
CalleeSideExit,
498499
ObjToStringFallback,
@@ -884,6 +885,17 @@ pub enum Insn {
884885
state: InsnId,
885886
reason: SendFallbackReason,
886887
},
888+
/// Optimized super call to an ISEQ method
889+
InvokeSuperDirect {
890+
recv: InsnId,
891+
cd: *const rb_call_data,
892+
/// The CME of the method containing the super call (for runtime guard)
893+
current_cme: *const rb_callable_method_entry_t,
894+
/// The resolved super method's CME (for PatchPoint and to get target ISEQ)
895+
super_cme: *const rb_callable_method_entry_t,
896+
args: Vec<InsnId>,
897+
state: InsnId,
898+
},
887899
InvokeBlock {
888900
cd: *const rb_call_data,
889901
args: Vec<InsnId>,
@@ -1279,6 +1291,13 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
12791291
write!(f, " # SendFallbackReason: {reason}")?;
12801292
Ok(())
12811293
}
1294+
Insn::InvokeSuperDirect { recv, args, .. } => {
1295+
write!(f, "InvokeSuperDirect {recv}")?;
1296+
for arg in args {
1297+
write!(f, ", {arg}")?;
1298+
}
1299+
Ok(())
1300+
}
12821301
Insn::InvokeBlock { args, reason, .. } => {
12831302
write!(f, "InvokeBlock")?;
12841303
for arg in args {
@@ -2018,6 +2037,14 @@ impl Function {
20182037
state,
20192038
reason,
20202039
},
2040+
&InvokeSuperDirect { recv, cd, current_cme, super_cme, ref args, state } => InvokeSuperDirect {
2041+
recv: find!(recv),
2042+
cd,
2043+
current_cme,
2044+
super_cme,
2045+
args: find_vec!(args),
2046+
state,
2047+
},
20212048
&InvokeBlock { cd, ref args, state, reason } => InvokeBlock {
20222049
cd,
20232050
args: find_vec!(args),
@@ -2203,6 +2230,7 @@ impl Function {
22032230
Insn::Send { .. } => types::BasicObject,
22042231
Insn::SendForward { .. } => types::BasicObject,
22052232
Insn::InvokeSuper { .. } => types::BasicObject,
2233+
Insn::InvokeSuperDirect { .. } => types::BasicObject,
22062234
Insn::InvokeBlock { .. } => types::BasicObject,
22072235
Insn::InvokeBuiltin { return_type, .. } => return_type.unwrap_or(types::BasicObject),
22082236
Insn::Defined { pushval, .. } => Type::from_value(*pushval).union(types::NilClass),
@@ -2968,6 +2996,74 @@ impl Function {
29682996
self.push_insn_id(block, insn_id);
29692997
};
29702998
}
2999+
Insn::InvokeSuper { recv, cd, blockiseq, args, state, .. } => {
3000+
// Don't handle calls with literal blocks (e.g., super { ... })
3001+
if !blockiseq.is_null() {
3002+
self.push_insn_id(block, insn_id); continue;
3003+
}
3004+
3005+
// Don't handle calls with complex arguments (kwarg, splat, kw_splat, blockarg, forwarding)
3006+
let ci = unsafe { get_call_data_ci(cd) };
3007+
let flags = unsafe { rb_vm_ci_flag(ci) };
3008+
if unspecializable_call_type(flags) {
3009+
self.push_insn_id(block, insn_id); continue;
3010+
}
3011+
3012+
let frame_state = self.frame_state(state);
3013+
3014+
// Get the profiled CME from the current method
3015+
let Some(profiles) = self.profiles.as_ref() else {
3016+
self.push_insn_id(block, insn_id); continue;
3017+
};
3018+
let Some(current_cme) = profiles.payload.profile.get_super_method_entry(frame_state.insn_idx) else {
3019+
// No profile or polymorphic
3020+
self.push_insn_id(block, insn_id); continue;
3021+
};
3022+
3023+
// Get defined_class and method ID from the profiled CME
3024+
let current_defined_class = unsafe { (*current_cme).defined_class };
3025+
let mid = unsafe { get_def_original_id((*current_cme).def) };
3026+
3027+
// Compute superclass: RCLASS_SUPER(RCLASS_ORIGIN(defined_class))
3028+
let superclass = unsafe { rb_class_get_superclass(RCLASS_ORIGIN(current_defined_class)) };
3029+
if superclass.nil_p() {
3030+
self.push_insn_id(block, insn_id); continue;
3031+
}
3032+
3033+
// Look up the super method
3034+
let super_cme = unsafe { rb_callable_method_entry(superclass, mid) };
3035+
if super_cme.is_null() {
3036+
self.push_insn_id(block, insn_id); continue;
3037+
}
3038+
3039+
// Check if it's an ISEQ method
3040+
let def_type = unsafe { get_cme_def_type(super_cme) };
3041+
if def_type != VM_METHOD_TYPE_ISEQ {
3042+
// TODO: Handle CFUNCs
3043+
self.push_insn_id(block, insn_id); continue;
3044+
}
3045+
3046+
// Add PatchPoints for method redefinition
3047+
// TODO: Add guard that ep[-2] matches current_cme
3048+
self.push_insn(block, Insn::PatchPoint {
3049+
invariant: Invariant::MethodRedefined {
3050+
klass: unsafe { (*super_cme).defined_class },
3051+
method: mid,
3052+
cme: super_cme
3053+
},
3054+
state
3055+
});
3056+
3057+
let send_direct = self.push_insn(block, Insn::InvokeSuperDirect {
3058+
recv,
3059+
cd,
3060+
current_cme,
3061+
super_cme,
3062+
args,
3063+
state
3064+
});
3065+
self.make_equal_to(insn_id, send_direct);
3066+
}
29713067
_ => { self.push_insn_id(block, insn_id); }
29723068
}
29733069
}
@@ -3974,7 +4070,8 @@ impl Function {
39744070
| &Insn::CCallWithFrame { recv, ref args, state, .. }
39754071
| &Insn::SendWithoutBlockDirect { recv, ref args, state, .. }
39764072
| &Insn::InvokeBuiltin { recv, ref args, state, .. }
3977-
| &Insn::InvokeSuper { recv, ref args, state, .. } => {
4073+
| &Insn::InvokeSuper { recv, ref args, state, .. }
4074+
| &Insn::InvokeSuperDirect { recv, ref args, state, .. } => {
39784075
worklist.push_back(recv);
39794076
worklist.extend(args);
39804077
worklist.push_back(state);
@@ -4590,6 +4687,7 @@ impl Function {
45904687
| Insn::Send { recv, ref args, .. }
45914688
| Insn::SendForward { recv, ref args, .. }
45924689
| Insn::InvokeSuper { recv, ref args, .. }
4690+
| Insn::InvokeSuperDirect { recv, ref args, .. }
45934691
| Insn::CCallWithFrame { recv, ref args, .. }
45944692
| Insn::CCallVariadic { recv, ref args, .. }
45954693
| Insn::InvokeBuiltin { recv, ref args, .. }

0 commit comments

Comments
 (0)