Skip to content

Commit ae767b6

Browse files
authored
ZJIT: Inline Kernel#block_given? (ruby#14914)
Fix Shopify#832
1 parent a763e6d commit ae767b6

4 files changed

Lines changed: 148 additions & 3 deletions

File tree

test/ruby/test_zjit.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1962,6 +1962,27 @@ def test
19621962
}, call_threshold: 2
19631963
end
19641964

1965+
def test_block_given_p
1966+
assert_compiles "false", "block_given?"
1967+
assert_compiles '[false, false, true]', %q{
1968+
def test = block_given?
1969+
[test, test, test{}]
1970+
}, call_threshold: 2, insns: [:opt_send_without_block]
1971+
end
1972+
1973+
def test_block_given_p_from_block
1974+
# This will do some EP hopping to find the local EP,
1975+
# so it's slightly different than doing it outside of a block.
1976+
1977+
assert_compiles '[false, false, true]', %q{
1978+
def test
1979+
yield_self { yield_self { block_given? } }
1980+
end
1981+
1982+
[test, test, test{}]
1983+
}, call_threshold: 2
1984+
end
1985+
19651986
def test_invokeblock_without_block_after_jit_call
19661987
assert_compiles '"no block given (yield)"', %q{
19671988
def test(*arr, &b)

zjit/src/codegen.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
449449
Insn::LoadSelf => gen_load_self(),
450450
&Insn::LoadIvarEmbedded { self_val, id, index } => gen_load_ivar_embedded(asm, opnd!(self_val), id, index),
451451
&Insn::LoadIvarExtended { self_val, id, index } => gen_load_ivar_extended(asm, opnd!(self_val), id, index),
452+
&Insn::IsBlockGiven => gen_is_block_given(jit, asm),
452453
&Insn::ArrayMax { state, .. }
453454
| &Insn::FixnumDiv { state, .. }
454455
| &Insn::Throw { state, .. }
@@ -525,6 +526,8 @@ fn gen_defined(jit: &JITState, asm: &mut Assembler, op_type: usize, obj: VALUE,
525526
// `yield` goes to the block handler stowed in the "local" iseq which is
526527
// the current iseq or a parent. Only the "method" iseq type can be passed a
527528
// block handler. (e.g. `yield` in the top level script is a syntax error.)
529+
//
530+
// Similar to gen_is_block_given
528531
let local_iseq = unsafe { rb_get_iseq_body_local_iseq(jit.iseq) };
529532
if unsafe { rb_get_iseq_body_type(local_iseq) } == ISEQ_TYPE_METHOD {
530533
let lep = gen_get_lep(jit, asm);
@@ -550,6 +553,19 @@ fn gen_defined(jit: &JITState, asm: &mut Assembler, op_type: usize, obj: VALUE,
550553
}
551554
}
552555

556+
/// Similar to gen_defined for DEFINED_YIELD
557+
fn gen_is_block_given(jit: &JITState, asm: &mut Assembler) -> Opnd {
558+
let local_iseq = unsafe { rb_get_iseq_body_local_iseq(jit.iseq) };
559+
if unsafe { rb_get_iseq_body_type(local_iseq) } == ISEQ_TYPE_METHOD {
560+
let lep = gen_get_lep(jit, asm);
561+
let block_handler = asm.load(Opnd::mem(64, lep, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL));
562+
asm.cmp(block_handler, VM_BLOCK_HANDLER_NONE.into());
563+
asm.csel_e(Qfalse.into(), Qtrue.into())
564+
} else {
565+
Qfalse.into()
566+
}
567+
}
568+
553569
/// Get a local variable from a higher scope or the heap. `local_ep_offset` is in number of VALUEs.
554570
/// We generate this instruction with level=0 only when the local variable is on the heap, so we
555571
/// can't optimize the level=0 case using the SP register.

zjit/src/cruby_methods.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ pub fn init() -> Annotations {
188188
}
189189

190190
annotate!(rb_mKernel, "itself", inline_kernel_itself);
191+
annotate!(rb_mKernel, "block_given?", inline_kernel_block_given_p);
191192
annotate!(rb_cString, "bytesize", types::Fixnum, no_gc, leaf);
192193
annotate!(rb_cString, "to_s", types::StringExact);
193194
annotate!(rb_cString, "getbyte", inline_string_getbyte);
@@ -247,6 +248,13 @@ fn inline_kernel_itself(_fun: &mut hir::Function, _block: hir::BlockId, recv: hi
247248
None
248249
}
249250

251+
fn inline_kernel_block_given_p(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option<hir::InsnId> {
252+
let &[] = args else { return None; };
253+
// TODO(max): In local iseq types that are not ISEQ_TYPE_METHOD, rewrite to Constant false.
254+
let result = fun.push_insn(block, hir::Insn::IsBlockGiven);
255+
return Some(result);
256+
}
257+
250258
fn inline_array_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option<hir::InsnId> {
251259
if let &[index] = args {
252260
if fun.likely_a(index, types::Fixnum, state) {

zjit/src/hir.rs

Lines changed: 103 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -610,8 +610,12 @@ pub enum Insn {
610610
IsMethodCfunc { val: InsnId, cd: *const rb_call_data, cfunc: *const u8, state: InsnId },
611611
/// Return C `true` if left == right
612612
IsBitEqual { left: InsnId, right: InsnId },
613+
// TODO(max): In iseq body types that are not ISEQ_TYPE_METHOD, rewrite to Constant false.
613614
Defined { op_type: usize, obj: VALUE, pushval: VALUE, v: InsnId, state: InsnId },
614615
GetConstantPath { ic: *const iseq_inline_constant_cache, state: InsnId },
616+
/// Kernel#block_given? but without pushing a frame. Similar to [`Insn::Defined`] with
617+
/// `DEFINED_YIELD`
618+
IsBlockGiven,
615619

616620
/// Get a global variable named `id`
617621
GetGlobal { id: ID, state: InsnId },
@@ -870,6 +874,7 @@ impl Insn {
870874
Insn::NewRange { .. } => true,
871875
Insn::NewRangeFixnum { .. } => false,
872876
Insn::StringGetbyteFixnum { .. } => false,
877+
Insn::IsBlockGiven => false,
873878
_ => true,
874879
}
875880
}
@@ -1065,6 +1070,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
10651070
Insn::GuardBlockParamProxy { level, .. } => write!(f, "GuardBlockParamProxy l{level}"),
10661071
Insn::PatchPoint { invariant, .. } => { write!(f, "PatchPoint {}", invariant.print(self.ptr_map)) },
10671072
Insn::GetConstantPath { ic, .. } => { write!(f, "GetConstantPath {:p}", self.ptr_map.map_ptr(ic)) },
1073+
Insn::IsBlockGiven => { write!(f, "IsBlockGiven") },
10681074
Insn::CCall { cfunc, args, name, return_type: _, elidable: _ } => {
10691075
write!(f, "CCall {}@{:p}", name.contents_lossy(), self.ptr_map.map_ptr(cfunc))?;
10701076
for arg in args {
@@ -1562,6 +1568,7 @@ impl Function {
15621568
result@(Const {..}
15631569
| Param {..}
15641570
| GetConstantPath {..}
1571+
| IsBlockGiven
15651572
| PatchPoint {..}
15661573
| PutSpecialObject {..}
15671574
| GetGlobal {..}
@@ -1828,6 +1835,7 @@ impl Function {
18281835
Insn::Defined { pushval, .. } => Type::from_value(*pushval).union(types::NilClass),
18291836
Insn::DefinedIvar { pushval, .. } => Type::from_value(*pushval).union(types::NilClass),
18301837
Insn::GetConstantPath { .. } => types::BasicObject,
1838+
Insn::IsBlockGiven { .. } => types::BoolExact,
18311839
Insn::ArrayMax { .. } => types::BasicObject,
18321840
Insn::GetGlobal { .. } => types::BasicObject,
18331841
Insn::GetIvar { .. } => types::BasicObject,
@@ -3009,6 +3017,7 @@ impl Function {
30093017
| &Insn::LoadSelf
30103018
| &Insn::GetLocal { .. }
30113019
| &Insn::PutSpecialObject { .. }
3020+
| &Insn::IsBlockGiven
30123021
| &Insn::IncrCounter(_)
30133022
| &Insn::IncrCounterPtr { .. } =>
30143023
{}
@@ -8492,13 +8501,23 @@ mod opt_tests {
84928501
use super::tests::assert_contains_opcode;
84938502

84948503
#[track_caller]
8495-
fn hir_string(method: &str) -> String {
8496-
let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method));
8504+
fn hir_string_function(function: &Function) -> String {
8505+
format!("{}", FunctionPrinter::without_snapshot(function))
8506+
}
8507+
8508+
#[track_caller]
8509+
fn hir_string_proc(proc: &str) -> String {
8510+
let iseq = crate::cruby::with_rubyvm(|| get_proc_iseq(proc));
84978511
unsafe { crate::cruby::rb_zjit_profile_disable(iseq) };
84988512
let mut function = iseq_to_hir(iseq).unwrap();
84998513
function.optimize();
85008514
function.validate().unwrap();
8501-
format!("{}", FunctionPrinter::without_snapshot(&function))
8515+
hir_string_function(&function)
8516+
}
8517+
8518+
#[track_caller]
8519+
fn hir_string(method: &str) -> String {
8520+
hir_string_proc(&format!("{}.method(:{})", "self", method))
85028521
}
85038522

85048523
#[test]
@@ -10671,6 +10690,87 @@ mod opt_tests {
1067110690
");
1067210691
}
1067310692

10693+
#[test]
10694+
fn test_inline_kernel_block_given_p() {
10695+
eval("
10696+
def test = block_given?
10697+
test
10698+
");
10699+
assert_snapshot!(hir_string("test"), @r"
10700+
fn test@<compiled>:2:
10701+
bb0():
10702+
EntryPoint interpreter
10703+
v1:BasicObject = LoadSelf
10704+
Jump bb2(v1)
10705+
bb1(v4:BasicObject):
10706+
EntryPoint JIT(0)
10707+
Jump bb2(v4)
10708+
bb2(v6:BasicObject):
10709+
PatchPoint MethodRedefined(Object@0x1000, block_given?@0x1008, cme:0x1010)
10710+
PatchPoint NoSingletonClass(Object@0x1000)
10711+
v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)]
10712+
v21:BoolExact = IsBlockGiven
10713+
IncrCounter inline_cfunc_optimized_send_count
10714+
CheckInterrupts
10715+
Return v21
10716+
");
10717+
}
10718+
10719+
#[test]
10720+
fn test_inline_kernel_block_given_p_in_block() {
10721+
eval("
10722+
TEST = proc { block_given? }
10723+
TEST.call
10724+
");
10725+
assert_snapshot!(hir_string_proc("TEST"), @r"
10726+
fn block in <compiled>@<compiled>:2:
10727+
bb0():
10728+
EntryPoint interpreter
10729+
v1:BasicObject = LoadSelf
10730+
Jump bb2(v1)
10731+
bb1(v4:BasicObject):
10732+
EntryPoint JIT(0)
10733+
Jump bb2(v4)
10734+
bb2(v6:BasicObject):
10735+
PatchPoint MethodRedefined(Object@0x1000, block_given?@0x1008, cme:0x1010)
10736+
PatchPoint NoSingletonClass(Object@0x1000)
10737+
v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)]
10738+
v21:BoolExact = IsBlockGiven
10739+
IncrCounter inline_cfunc_optimized_send_count
10740+
CheckInterrupts
10741+
Return v21
10742+
");
10743+
}
10744+
10745+
#[test]
10746+
fn test_elide_kernel_block_given_p() {
10747+
eval("
10748+
def test
10749+
block_given?
10750+
5
10751+
end
10752+
test
10753+
");
10754+
assert_snapshot!(hir_string("test"), @r"
10755+
fn test@<compiled>:3:
10756+
bb0():
10757+
EntryPoint interpreter
10758+
v1:BasicObject = LoadSelf
10759+
Jump bb2(v1)
10760+
bb1(v4:BasicObject):
10761+
EntryPoint JIT(0)
10762+
Jump bb2(v4)
10763+
bb2(v6:BasicObject):
10764+
PatchPoint MethodRedefined(Object@0x1000, block_given?@0x1008, cme:0x1010)
10765+
PatchPoint NoSingletonClass(Object@0x1000)
10766+
v23:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)]
10767+
IncrCounter inline_cfunc_optimized_send_count
10768+
v14:Fixnum[5] = Const Value(5)
10769+
CheckInterrupts
10770+
Return v14
10771+
");
10772+
}
10773+
1067410774
#[test]
1067510775
fn const_send_direct_integer() {
1067610776
eval("

0 commit comments

Comments
 (0)