Skip to content

Commit 1c39cc8

Browse files
authored
ZJIT: Use SP for reloading locals (ruby#16746)
* ZJIT: Use SP for reloading locals * ZJIT: Deduplicate post-send NoEPEscape patch point generation
1 parent 8d92985 commit 1c39cc8

4 files changed

Lines changed: 163 additions & 48 deletions

File tree

zjit/src/codegen_tests.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,24 @@ fn test_send_with_local_written_by_blockiseq() {
652652
"), @"[1, 2]");
653653
}
654654

655+
#[test]
656+
fn test_no_ep_escape_patch_point_after_send_does_not_repeat_send() {
657+
eval(r#"
658+
$send_count = 0
659+
660+
def test
661+
captured = nil
662+
tap do |_|
663+
$send_count += 1
664+
-> { captured } if $send_count == 2
665+
end
666+
$send_count
667+
end
668+
"#);
669+
assert_contains_opcode("test", YARVINSN_send);
670+
assert_snapshot!(assert_compiles_allowing_exits("[test, test, test]"), @"[1, 2, 3]");
671+
}
672+
655673
#[test]
656674
fn test_send_without_block() {
657675
assert_snapshot!(inspect("

zjit/src/hir.rs

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4720,6 +4720,17 @@ impl Function {
47204720
self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass: recv_class, method: method_id, cme }, state });
47214721
}
47224722

4723+
/// Side exit back to the state after a block-backed send.
4724+
/// Using the pre-send snapshot would re-execute the send in the interpreter.
4725+
fn gen_post_send_no_ep_escape_patch_point(&mut self, block: BlockId, state: &FrameState, insn_idx: u32) {
4726+
let iseq = state.iseq;
4727+
let mut reload_state = state.clone();
4728+
reload_state.insn_idx = insn_idx as usize;
4729+
reload_state.pc = unsafe { rb_iseq_pc_at_idx(iseq, insn_idx) };
4730+
let reload_exit_id = self.push_insn(block, Insn::Snapshot { state: reload_state.without_locals() });
4731+
self.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoEPEscape(iseq), state: reload_exit_id });
4732+
}
4733+
47234734
fn count_not_inlined_cfunc(&mut self, block: BlockId, cme: *const rb_callable_method_entry_t) {
47244735
let owner = unsafe { (*cme).owner };
47254736
let called_id = unsafe { (*cme).called_id };
@@ -8004,13 +8015,23 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
80048015
// Reload locals that may have been modified by the blockiseq.
80058016
// TODO: Avoid reloading locals that are not referenced by the blockiseq
80068017
// or not used after this. Max thinks we could eventually DCE them.
8007-
let ep = fun.push_insn(block, Insn::GetEP { level: 0 });
8018+
if !ep_escaped && !state.locals.is_empty() {
8019+
fun.gen_post_send_no_ep_escape_patch_point(block, &state, insn_idx);
8020+
}
8021+
let mut base: Option<InsnId> = None;
80088022
for local_idx in 0..state.locals.len() {
80098023
let ep_offset = local_idx_to_ep_offset(iseq, local_idx);
80108024
let ep_offset_u32 = u32::try_from(ep_offset)
80118025
.unwrap_or_else(|_| panic!("Could not convert ep_offset {ep_offset} to u32"));
8012-
// TODO: We could use `use_sp: true` with PatchPoint.
8013-
let val = fun.get_local_from_ep(block, ep, ep_offset_u32, 0, types::BasicObject);
8026+
let recv = *base.get_or_insert_with(|| {
8027+
let base_insn = if !ep_escaped { Insn::LoadSP } else { Insn::GetEP { level: 0 } };
8028+
fun.push_insn(block, base_insn)
8029+
});
8030+
let val = if !ep_escaped {
8031+
fun.get_local_from_sp(block, recv, ep_offset_u32, types::BasicObject)
8032+
} else {
8033+
fun.get_local_from_ep(block, recv, ep_offset_u32, 0, types::BasicObject)
8034+
};
80148035
state.setlocal(ep_offset_u32, val);
80158036
}
80168037
}
@@ -8040,13 +8061,23 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
80408061

80418062
if !blockiseq.is_null() {
80428063
// Reload locals that may have been modified by the blockiseq.
8043-
let ep = fun.push_insn(block, Insn::GetEP { level: 0 });
8064+
if !ep_escaped && !state.locals.is_empty() {
8065+
fun.gen_post_send_no_ep_escape_patch_point(block, &state, insn_idx);
8066+
}
8067+
let mut base: Option<InsnId> = None;
80448068
for local_idx in 0..state.locals.len() {
80458069
let ep_offset = local_idx_to_ep_offset(iseq, local_idx);
80468070
let ep_offset_u32 = u32::try_from(ep_offset)
80478071
.unwrap_or_else(|_| panic!("Could not convert ep_offset {ep_offset} to u32"));
8048-
// TODO: We could use `use_sp: true` with PatchPoint.
8049-
let val = fun.get_local_from_ep(block, ep, ep_offset_u32, 0, types::BasicObject);
8072+
let recv = *base.get_or_insert_with(|| {
8073+
let base_insn = if !ep_escaped { Insn::LoadSP } else { Insn::GetEP { level: 0 } };
8074+
fun.push_insn(block, base_insn)
8075+
});
8076+
let val = if !ep_escaped {
8077+
fun.get_local_from_sp(block, recv, ep_offset_u32, types::BasicObject)
8078+
} else {
8079+
fun.get_local_from_ep(block, recv, ep_offset_u32, 0, types::BasicObject)
8080+
};
80508081
state.setlocal(ep_offset_u32, val);
80518082
}
80528083
}
@@ -8077,13 +8108,23 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
80778108
// Reload locals that may have been modified by the blockiseq.
80788109
// TODO: Avoid reloading locals that are not referenced by the blockiseq
80798110
// or not used after this. Max thinks we could eventually DCE them.
8080-
let ep = fun.push_insn(block, Insn::GetEP { level: 0 });
8111+
if !ep_escaped && !state.locals.is_empty() {
8112+
fun.gen_post_send_no_ep_escape_patch_point(block, &state, insn_idx);
8113+
}
8114+
let mut base: Option<InsnId> = None;
80818115
for local_idx in 0..state.locals.len() {
80828116
let ep_offset = local_idx_to_ep_offset(iseq, local_idx);
80838117
let ep_offset_u32 = u32::try_from(ep_offset)
80848118
.unwrap_or_else(|_| panic!("Could not convert ep_offset {ep_offset} to u32"));
8085-
// TODO: We could use `use_sp: true` with PatchPoint.
8086-
let val = fun.get_local_from_ep(block, ep, ep_offset_u32, 0, types::BasicObject);
8119+
let recv = *base.get_or_insert_with(|| {
8120+
let base_insn = if !ep_escaped { Insn::LoadSP } else { Insn::GetEP { level: 0 } };
8121+
fun.push_insn(block, base_insn)
8122+
});
8123+
let val = if !ep_escaped {
8124+
fun.get_local_from_sp(block, recv, ep_offset_u32, types::BasicObject)
8125+
} else {
8126+
fun.get_local_from_ep(block, recv, ep_offset_u32, 0, types::BasicObject)
8127+
};
80878128
state.setlocal(ep_offset_u32, val);
80888129
}
80898130
}
@@ -8114,13 +8155,23 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
81148155
// Reload locals that may have been modified by the blockiseq.
81158156
// TODO: Avoid reloading locals that are not referenced by the blockiseq
81168157
// or not used after this. Max thinks we could eventually DCE them.
8117-
let ep = fun.push_insn(block, Insn::GetEP { level: 0 });
8158+
if !ep_escaped && !state.locals.is_empty() {
8159+
fun.gen_post_send_no_ep_escape_patch_point(block, &state, insn_idx);
8160+
}
8161+
let mut base: Option<InsnId> = None;
81188162
for local_idx in 0..state.locals.len() {
81198163
let ep_offset = local_idx_to_ep_offset(iseq, local_idx);
81208164
let ep_offset_u32 = u32::try_from(ep_offset)
81218165
.unwrap_or_else(|_| panic!("Could not convert ep_offset {ep_offset} to u32"));
8122-
// TODO: We could use `use_sp: true` with PatchPoint.
8123-
let val = fun.get_local_from_ep(block, ep, ep_offset_u32, 0, types::BasicObject);
8166+
let recv = *base.get_or_insert_with(|| {
8167+
let base_insn = if !ep_escaped { Insn::LoadSP } else { Insn::GetEP { level: 0 } };
8168+
fun.push_insn(block, base_insn)
8169+
});
8170+
let val = if !ep_escaped {
8171+
fun.get_local_from_sp(block, recv, ep_offset_u32, types::BasicObject)
8172+
} else {
8173+
fun.get_local_from_ep(block, recv, ep_offset_u32, 0, types::BasicObject)
8174+
};
81248175
state.setlocal(ep_offset_u32, val);
81258176
}
81268177
}

zjit/src/hir/opt_tests.rs

Lines changed: 76 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1410,12 +1410,13 @@ mod hir_opt_tests {
14101410
bb3(v9:BasicObject, v10:BasicObject):
14111411
PatchPoint NoSingletonClass(C@0x1008)
14121412
PatchPoint MethodRedefined(C@0x1008, fun_new_map@0x1010, cme:0x1018)
1413-
v25:ArraySubclass[class_exact:C] = GuardType v10, ArraySubclass[class_exact:C]
1414-
v26:BasicObject = SendDirect v25, 0x1040, :fun_new_map (0x1050)
1415-
v16:CPtr = GetEP 0
1416-
v17:BasicObject = LoadField v16, :o@0x1058
1413+
v27:ArraySubclass[class_exact:C] = GuardType v10, ArraySubclass[class_exact:C]
1414+
v28:BasicObject = SendDirect v27, 0x1040, :fun_new_map (0x1050)
1415+
PatchPoint NoEPEscape(test)
1416+
v18:CPtr = LoadSP
1417+
v19:BasicObject = LoadField v18, :o@0x1000
14171418
CheckInterrupts
1418-
Return v26
1419+
Return v28
14191420
");
14201421
}
14211422

@@ -1446,12 +1447,13 @@ mod hir_opt_tests {
14461447
bb3(v9:BasicObject, v10:BasicObject):
14471448
PatchPoint NoSingletonClass(C@0x1008)
14481449
PatchPoint MethodRedefined(C@0x1008, bar@0x1010, cme:0x1018)
1449-
v26:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C]
1450-
v27:BasicObject = CCallWithFrame v26, :Enumerable#bar@0x1040, block=0x1048
1451-
v16:CPtr = GetEP 0
1452-
v17:BasicObject = LoadField v16, :o@0x1050
1450+
v28:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C]
1451+
v29:BasicObject = CCallWithFrame v28, :Enumerable#bar@0x1040, block=0x1048
1452+
PatchPoint NoEPEscape(test)
1453+
v18:CPtr = LoadSP
1454+
v19:BasicObject = LoadField v18, :o@0x1000
14531455
CheckInterrupts
1454-
Return v27
1456+
Return v29
14551457
");
14561458
}
14571459

@@ -3641,10 +3643,10 @@ mod hir_opt_tests {
36413643
v11:Fixnum[1] = Const Value(1)
36423644
v13:Fixnum[2] = Const Value(2)
36433645
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
3644-
v23:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)]
3645-
v24:BasicObject = SendDirect v23, 0x1038, :foo (0x1048), v11, v13
3646+
v22:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)]
3647+
v23:BasicObject = SendDirect v22, 0x1038, :foo (0x1048), v11, v13
36463648
CheckInterrupts
3647-
Return v24
3649+
Return v23
36483650
");
36493651
}
36503652

@@ -3675,12 +3677,54 @@ mod hir_opt_tests {
36753677
bb3(v8:BasicObject, v9:NilClass):
36763678
v13:Fixnum[1] = Const Value(1)
36773679
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
3678-
v32:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v8, ObjectSubclass[class_exact*:Object@VALUE(0x1000)]
3679-
v19:CPtr = GetEP 0
3680-
v20:BasicObject = LoadField v19, :a@0x1038
3680+
v34:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v8, ObjectSubclass[class_exact*:Object@VALUE(0x1000)]
3681+
v36:Fixnum[1] = Const Value(1)
36813682
PatchPoint NoEPEscape(test)
3683+
v21:CPtr = LoadSP
3684+
v22:BasicObject = LoadField v21, :a@0x1038
36823685
CheckInterrupts
3683-
Return v20
3686+
Return v22
3687+
");
3688+
}
3689+
3690+
#[test]
3691+
fn reload_local_across_send_after_ep_escape() {
3692+
eval("
3693+
def foo(&block) = 1
3694+
def test
3695+
a = 1
3696+
lambda { a }
3697+
foo {|| }
3698+
a
3699+
end
3700+
test
3701+
test
3702+
");
3703+
assert_snapshot!(hir_string("test"), @"
3704+
fn test@<compiled>:4:
3705+
bb1():
3706+
EntryPoint interpreter
3707+
v1:BasicObject = LoadSelf
3708+
v2:NilClass = Const Value(nil)
3709+
Jump bb3(v1, v2)
3710+
bb2():
3711+
EntryPoint JIT(0)
3712+
v5:BasicObject = LoadArg :self@0
3713+
v6:NilClass = Const Value(nil)
3714+
Jump bb3(v5, v6)
3715+
bb3(v8:BasicObject, v9:NilClass):
3716+
v13:Fixnum[1] = Const Value(1)
3717+
SetLocal :a, l0, EP@3, v13
3718+
PatchPoint MethodRedefined(Object@0x1000, lambda@0x1008, cme:0x1010)
3719+
v45:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v8, ObjectSubclass[class_exact*:Object@VALUE(0x1000)]
3720+
v46:BasicObject = CCallWithFrame v45, :Kernel#lambda@0x1038, block=0x1040
3721+
v20:CPtr = GetEP 0
3722+
v21:BasicObject = LoadField v20, :a@0x1048
3723+
PatchPoint MethodRedefined(Object@0x1000, foo@0x1049, cme:0x1050)
3724+
v32:CPtr = GetEP 0
3725+
v33:BasicObject = LoadField v32, :a@0x1048
3726+
CheckInterrupts
3727+
Return v33
36843728
");
36853729
}
36863730

@@ -4248,12 +4292,12 @@ mod hir_opt_tests {
42484292
v17:ArrayExact = NewArray
42494293
v22:TrueClass = Const Value(true)
42504294
v24:BasicObject = Send v12, 0x1008, :each_line, v22 # SendFallbackReason: Complex argument passing
4251-
v25:CPtr = GetEP 0
4252-
v26:BasicObject = LoadField v25, :s@0x1030
4253-
v27:BasicObject = LoadField v25, :a@0x1031
42544295
PatchPoint NoEPEscape(test)
4296+
v27:CPtr = LoadSP
4297+
v28:BasicObject = LoadField v27, :s@0x1000
4298+
v29:BasicObject = LoadField v27, :a@0x1030
42554299
CheckInterrupts
4256-
Return v27
4300+
Return v29
42574301
");
42584302
}
42594303

@@ -8150,9 +8194,9 @@ mod hir_opt_tests {
81508194
v11:ArrayExact = ArrayDup v10
81518195
PatchPoint NoSingletonClass(Array@0x1008)
81528196
PatchPoint MethodRedefined(Array@0x1008, map@0x1010, cme:0x1018)
8153-
v22:BasicObject = SendDirect v11, 0x1040, :map (0x1050)
8197+
v21:BasicObject = SendDirect v11, 0x1040, :map (0x1050)
81548198
CheckInterrupts
8155-
Return v22
8199+
Return v21
81568200
");
81578201
}
81588202

@@ -8186,17 +8230,17 @@ mod hir_opt_tests {
81868230
v13:ArrayExact = NewArray
81878231
PatchPoint SingleRactorMode
81888232
PatchPoint StableConstantNames(0x1000, A)
8189-
v36:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
8233+
v38:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
81908234
PatchPoint StableConstantNames(0x1010, B)
8191-
v39:ArrayExact[VALUE(0x1018)] = Const Value(VALUE(0x1018))
8235+
v41:ArrayExact[VALUE(0x1018)] = Const Value(VALUE(0x1018))
81928236
PatchPoint NoSingletonClass(Array@0x1020)
81938237
PatchPoint MethodRedefined(Array@0x1020, zip@0x1028, cme:0x1030)
8194-
v43:BasicObject = CCallVariadic v36, :Array#zip@0x1058, v39
8195-
v22:CPtr = GetEP 0
8196-
v23:BasicObject = LoadField v22, :result@0x1060
8238+
v45:BasicObject = CCallVariadic v38, :Array#zip@0x1058, v41
81978239
PatchPoint NoEPEscape(test)
8240+
v24:CPtr = LoadSP
8241+
v25:BasicObject = LoadField v24, :result@0x1060
81988242
CheckInterrupts
8199-
Return v23
8243+
Return v25
82008244
");
82018245
}
82028246

@@ -8341,10 +8385,10 @@ mod hir_opt_tests {
83418385
Jump bb3(v4)
83428386
bb3(v6:BasicObject):
83438387
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
8344-
v19:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)]
8345-
v20:BasicObject = SendDirect v19, 0x1038, :foo (0x1048)
8388+
v18:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)]
8389+
v19:BasicObject = SendDirect v18, 0x1038, :foo (0x1048)
83468390
CheckInterrupts
8347-
Return v20
8391+
Return v19
83488392
");
83498393
}
83508394

zjit/src/hir/tests.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1983,8 +1983,9 @@ pub(crate) mod hir_build_tests {
19831983
Jump bb3(v6, v7)
19841984
bb3(v9:BasicObject, v10:BasicObject):
19851985
v15:BasicObject = Send v10, 0x1008, :each # SendFallbackReason: Uncategorized(send)
1986-
v16:CPtr = GetEP 0
1987-
v17:BasicObject = LoadField v16, :a@0x1030
1986+
PatchPoint NoEPEscape(test)
1987+
v18:CPtr = LoadSP
1988+
v19:BasicObject = LoadField v18, :a@0x1000
19881989
CheckInterrupts
19891990
Return v15
19901991
");
@@ -2266,8 +2267,9 @@ pub(crate) mod hir_build_tests {
22662267
Jump bb3(v6, v7)
22672268
bb3(v9:BasicObject, v10:BasicObject):
22682269
v16:BasicObject = InvokeSuperForward v9, 0x1008, v10 # SendFallbackReason: InvokeSuperForward: not yet specialized
2269-
v17:CPtr = GetEP 0
2270-
v18:BasicObject = LoadField v17, :...@0x1010
2270+
PatchPoint NoEPEscape(test)
2271+
v19:CPtr = LoadSP
2272+
v20:BasicObject = LoadField v19, :...@0x1000
22712273
CheckInterrupts
22722274
Return v16
22732275
");

0 commit comments

Comments
 (0)