Skip to content

Commit 1c41803

Browse files
committed
ZJIT: Add NoEPEscape guard after send-with-block
After reload_modified_locals, locals not written by the block retain stale FrameState values. If eval ran inside the block, these stale values would be spilled to the frame by gen_spill_locals on the next non-leaf call, overwriting the correct values set by eval. Add a NoEPEscape PatchPoint immediately after reload_modified_locals. If the EP escaped during the block (e.g. via eval), this side-exits before any stale locals can be spilled. The snapshot uses the post-send state without locals so the interpreter reads them from the frame, which has the correct values at that point.
1 parent f5ec572 commit 1c41803

File tree

3 files changed

+125
-79
lines changed

3 files changed

+125
-79
lines changed

zjit/src/hir.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7282,6 +7282,17 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
72827282

72837283
if !blockiseq.is_null() {
72847284
reload_modified_locals(&mut fun, block, &mut state, iseq, blockiseq);
7285+
// Guard against EP escape (e.g. eval) during the block. This must
7286+
// be placed before any subsequent gen_spill_locals overwrites the
7287+
// frame with stale local values. The snapshot uses the post-send
7288+
// state without locals, pointing to the next instruction, so a
7289+
// side-exit continues from the right place with locals from the frame.
7290+
let len = insn_len(opcode as usize) as usize;
7291+
let mut pp_state = state.clone();
7292+
pp_state.pc = unsafe { pc.add(len) };
7293+
pp_state.insn_idx = insn_idx as usize + len;
7294+
let pp_exit_id = fun.push_insn(block, Insn::Snapshot { state: pp_state.without_locals() });
7295+
fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoEPEscape(iseq), state: pp_exit_id });
72857296
}
72867297
}
72877298
YARVINSN_sendforward => {
@@ -7304,6 +7315,14 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
73047315

73057316
if !blockiseq.is_null() {
73067317
reload_modified_locals(&mut fun, block, &mut state, iseq, blockiseq);
7318+
let next_insn_idx = insn_idx + insn_len(opcode as usize) as u32;
7319+
let next_pc = unsafe { rb_iseq_pc_at_idx(iseq, next_insn_idx) };
7320+
let mut pp_state = state.clone();
7321+
pp_state.pc = next_pc;
7322+
pp_state.insn_idx = next_insn_idx as usize;
7323+
let pp_exit = pp_state.without_locals();
7324+
let pp_exit_id = fun.push_insn(block, Insn::Snapshot { state: pp_exit });
7325+
fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoEPEscape(iseq), state: pp_exit_id });
73077326
}
73087327
}
73097328
YARVINSN_invokesuper => {
@@ -7325,6 +7344,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
73257344

73267345
if !blockiseq.is_null() {
73277346
reload_modified_locals(&mut fun, block, &mut state, iseq, blockiseq);
7347+
let pp_exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state.without_locals() });
7348+
fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoEPEscape(iseq), state: pp_exit_id });
73287349
}
73297350
}
73307351
YARVINSN_invokesuperforward => {
@@ -7346,6 +7367,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
73467367

73477368
if !blockiseq.is_null() {
73487369
reload_modified_locals(&mut fun, block, &mut state, iseq, blockiseq);
7370+
let pp_exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state.without_locals() });
7371+
fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoEPEscape(iseq), state: pp_exit_id });
73497372
}
73507373
}
73517374
YARVINSN_invokeblock => {

zjit/src/hir/opt_tests.rs

Lines changed: 70 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -823,10 +823,11 @@ mod hir_opt_tests {
823823
bb2(v8:BasicObject, v9:BasicObject):
824824
PatchPoint NoSingletonClass(C@0x1000)
825825
PatchPoint MethodRedefined(C@0x1000, fun_new_map@0x1008, cme:0x1010)
826-
v21:ArraySubclass[class_exact:C] = GuardType v9, ArraySubclass[class_exact:C]
827-
v22:BasicObject = SendDirect v21, 0x1038, :fun_new_map (0x1048)
826+
v23:ArraySubclass[class_exact:C] = GuardType v9, ArraySubclass[class_exact:C]
827+
v24:BasicObject = SendDirect v23, 0x1038, :fun_new_map (0x1048)
828+
PatchPoint NoEPEscape(test)
828829
CheckInterrupts
829-
Return v22
830+
Return v24
830831
");
831832
}
832833

@@ -854,10 +855,11 @@ mod hir_opt_tests {
854855
bb2(v8:BasicObject, v9:BasicObject):
855856
PatchPoint NoSingletonClass(C@0x1000)
856857
PatchPoint MethodRedefined(C@0x1000, bar@0x1008, cme:0x1010)
857-
v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C]
858-
v23:BasicObject = CCallWithFrame v22, :Enumerable#bar@0x1038, block=0x1040
858+
v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C]
859+
v25:BasicObject = CCallWithFrame v24, :Enumerable#bar@0x1038, block=0x1040
860+
PatchPoint NoEPEscape(test)
859861
CheckInterrupts
860-
Return v23
862+
Return v25
861863
");
862864
}
863865

@@ -2919,10 +2921,11 @@ mod hir_opt_tests {
29192921
v13:Fixnum[2] = Const Value(2)
29202922
PatchPoint NoSingletonClass(Object@0x1000)
29212923
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
2922-
v22:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)]
2923-
v23:BasicObject = SendDirect v22, 0x1038, :foo (0x1048), v11, v13
2924+
v24:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)]
2925+
v25:BasicObject = SendDirect v24, 0x1038, :foo (0x1048), v11, v13
2926+
PatchPoint NoEPEscape(test)
29242927
CheckInterrupts
2925-
Return v23
2928+
Return v25
29262929
");
29272930
}
29282931

@@ -2955,6 +2958,7 @@ mod hir_opt_tests {
29552958
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
29562959
v30:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v8, HeapObject[class_exact*:Object@VALUE(0x1000)]
29572960
IncrCounter inline_iseq_optimized_send_count
2961+
v33:Fixnum[1] = Const Value(1)
29582962
PatchPoint NoEPEscape(test)
29592963
CheckInterrupts
29602964
Return v13
@@ -3038,21 +3042,21 @@ mod hir_opt_tests {
30383042
SetLocal :a, l0, EP@3, v13
30393043
PatchPoint SingleRactorMode
30403044
PatchPoint StableConstantNames(0x1000, EvalLocal)
3041-
v59:Class[EvalLocal@0x1008] = Const Value(VALUE(0x1008))
3045+
v60:Class[EvalLocal@0x1008] = Const Value(VALUE(0x1008))
30423046
v21:NilClass = Const Value(nil)
30433047
PatchPoint MethodRedefined(EvalLocal@0x1008, new@0x1009, cme:0x1010)
3044-
v62:HeapObject[class_exact:EvalLocal] = ObjectAllocClass EvalLocal:VALUE(0x1008)
3048+
v63:HeapObject[class_exact:EvalLocal] = ObjectAllocClass EvalLocal:VALUE(0x1008)
30453049
PatchPoint NoSingletonClass(EvalLocal@0x1008)
30463050
PatchPoint MethodRedefined(EvalLocal@0x1008, initialize@0x1038, cme:0x1040)
3047-
v70:NilClass = Const Value(nil)
3051+
v71:NilClass = Const Value(nil)
30483052
IncrCounter inline_cfunc_optimized_send_count
30493053
CheckInterrupts
30503054
PatchPoint NoSingletonClass(EvalLocal@0x1008)
30513055
PatchPoint MethodRedefined(EvalLocal@0x1008, f@0x1068, cme:0x1070)
3052-
v66:BasicObject = SendDirect v62, 0x1098, :f (0x10a8)
3053-
v52:BasicObject = GetLocal :a, l0, EP@3
3056+
v67:BasicObject = SendDirect v63, 0x1098, :f (0x10a8)
3057+
PatchPoint NoEPEscape(test)
30543058
CheckInterrupts
3055-
Return v52
3059+
Return v13
30563060
");
30573061
}
30583062

@@ -5631,6 +5635,7 @@ mod hir_opt_tests {
56315635
Jump bb2(v4)
56325636
bb2(v6:BasicObject):
56335637
v11:BasicObject = Send v6, 0x1000, :bmethod # SendFallbackReason: Send: unsupported method type Bmethod
5638+
PatchPoint NoEPEscape(test)
56345639
CheckInterrupts
56355640
Return v11
56365641
");
@@ -6605,9 +6610,10 @@ mod hir_opt_tests {
66056610
v11:ArrayExact = ArrayDup v10
66066611
PatchPoint NoSingletonClass(Array@0x1008)
66076612
PatchPoint MethodRedefined(Array@0x1008, map@0x1010, cme:0x1018)
6608-
v20:BasicObject = SendDirect v11, 0x1040, :map (0x1050)
6613+
v22:BasicObject = SendDirect v11, 0x1040, :map (0x1050)
6614+
PatchPoint NoEPEscape(test)
66096615
CheckInterrupts
6610-
Return v20
6616+
Return v22
66116617
");
66126618
}
66136619

@@ -6772,10 +6778,11 @@ mod hir_opt_tests {
67726778
bb2(v6:BasicObject):
67736779
PatchPoint NoSingletonClass(Object@0x1000)
67746780
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
6775-
v18:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)]
6776-
v19:BasicObject = SendDirect v18, 0x1038, :foo (0x1048)
6781+
v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)]
6782+
v21:BasicObject = SendDirect v20, 0x1038, :foo (0x1048)
6783+
PatchPoint NoEPEscape(test)
67776784
CheckInterrupts
6778-
Return v19
6785+
Return v21
67796786
");
67806787
}
67816788

@@ -9688,11 +9695,12 @@ mod hir_opt_tests {
96889695
bb2(v6:BasicObject):
96899696
PatchPoint NoSingletonClass(Object@0x1000)
96909697
PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010)
9691-
v18:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)]
9698+
v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)]
96929699
IncrCounter inline_iseq_optimized_send_count
9693-
v21:Fixnum[123] = Const Value(123)
9700+
v23:Fixnum[123] = Const Value(123)
9701+
PatchPoint NoEPEscape(test)
96949702
CheckInterrupts
9695-
Return v21
9703+
Return v23
96969704
");
96979705
}
96989706

@@ -9718,11 +9726,12 @@ mod hir_opt_tests {
97189726
bb2(v6:BasicObject):
97199727
PatchPoint NoSingletonClass(Object@0x1000)
97209728
PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010)
9721-
v18:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)]
9729+
v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)]
97229730
IncrCounter inline_iseq_optimized_send_count
9723-
v21:Fixnum[123] = Const Value(123)
9731+
v23:Fixnum[123] = Const Value(123)
9732+
PatchPoint NoEPEscape(test)
97249733
CheckInterrupts
9725-
Return v21
9734+
Return v23
97269735
");
97279736
}
97289737

@@ -9748,11 +9757,12 @@ mod hir_opt_tests {
97489757
bb2(v6:BasicObject):
97499758
PatchPoint NoSingletonClass(Object@0x1000)
97509759
PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010)
9751-
v18:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)]
9760+
v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)]
97529761
IncrCounter inline_iseq_optimized_send_count
9753-
v21:Fixnum[123] = Const Value(123)
9762+
v23:Fixnum[123] = Const Value(123)
9763+
PatchPoint NoEPEscape(test)
97549764
CheckInterrupts
9755-
Return v21
9765+
Return v23
97569766
");
97579767
}
97589768

@@ -10674,19 +10684,20 @@ mod hir_opt_tests {
1067410684
CheckInterrupts
1067510685
SetLocal :formatted, l0, EP@3, v15
1067610686
PatchPoint SingleRactorMode
10677-
v53:HeapBasicObject = GuardType v14, HeapBasicObject
10678-
v54:CShape = LoadField v53, :_shape_id@0x1000
10679-
v55:CShape[0x1001] = GuardBitEquals v54, CShape(0x1001)
10680-
StoreField v53, :@formatted@0x1002, v15
10681-
WriteBarrier v53, v15
10682-
v58:CShape[0x1003] = Const CShape(0x1003)
10683-
StoreField v53, :_shape_id@0x1000, v58
10687+
v55:HeapBasicObject = GuardType v14, HeapBasicObject
10688+
v56:CShape = LoadField v55, :_shape_id@0x1000
10689+
v57:CShape[0x1001] = GuardBitEquals v56, CShape(0x1001)
10690+
StoreField v55, :@formatted@0x1002, v15
10691+
WriteBarrier v55, v15
10692+
v60:CShape[0x1003] = Const CShape(0x1003)
10693+
StoreField v55, :_shape_id@0x1000, v60
1068410694
v46:Class[VMFrozenCore] = Const Value(VALUE(0x1008))
1068510695
PatchPoint NoSingletonClass(Class@0x1010)
1068610696
PatchPoint MethodRedefined(Class@0x1010, lambda@0x1018, cme:0x1020)
10687-
v63:BasicObject = CCallWithFrame v46, :RubyVM::FrozenCore.lambda@0x1048, block=0x1050
10697+
v65:BasicObject = CCallWithFrame v46, :RubyVM::FrozenCore.lambda@0x1048, block=0x1050
10698+
PatchPoint NoEPEscape(read_nil_local)
1068810699
CheckInterrupts
10689-
Return v63
10700+
Return v65
1069010701
");
1069110702
}
1069210703

@@ -11519,6 +11530,7 @@ mod hir_opt_tests {
1151911530
Jump bb2(v4)
1152011531
bb2(v6:BasicObject):
1152111532
v11:BasicObject = InvokeSuper v6, 0x1000 # SendFallbackReason: super: call made with a block
11533+
PatchPoint NoEPEscape(foo)
1152211534
CheckInterrupts
1152311535
Return v11
1152411536
");
@@ -11685,13 +11697,13 @@ mod hir_opt_tests {
1168511697
bb2(v10:BasicObject, v11:BasicObject, v12:NilClass):
1168611698
PatchPoint NoSingletonClass(B@0x1000)
1168711699
PatchPoint MethodRedefined(B@0x1000, proc@0x1008, cme:0x1010)
11688-
v33:HeapObject[class_exact:B] = GuardType v10, HeapObject[class_exact:B]
11689-
v34:BasicObject = CCallWithFrame v33, :Kernel#proc@0x1038, block=0x1040
11690-
SetLocal :other_block, l0, EP@3, v34
11691-
v23:BasicObject = GetLocal :other_block, l0, EP@3
11692-
v25:BasicObject = InvokeSuper v10, 0x1048, v23 # SendFallbackReason: super: complex argument passing to `super` call
11700+
v34:HeapObject[class_exact:B] = GuardType v10, HeapObject[class_exact:B]
11701+
v35:BasicObject = CCallWithFrame v34, :Kernel#proc@0x1038, block=0x1040
11702+
PatchPoint NoEPEscape(foo)
11703+
SetLocal :other_block, l0, EP@3, v35
11704+
v26:BasicObject = InvokeSuper v10, 0x1048, v35 # SendFallbackReason: super: complex argument passing to `super` call
1169311705
CheckInterrupts
11694-
Return v25
11706+
Return v26
1169511707
");
1169611708
}
1169711709

@@ -12065,20 +12077,22 @@ mod hir_opt_tests {
1206512077
v70:Falsy = RefineType v59, Falsy
1206612078
PatchPoint NoSingletonClass(Object@0x1018)
1206712079
PatchPoint MethodRedefined(Object@0x1018, lambda@0x1020, cme:0x1028)
12068-
v106:HeapObject[class_exact*:Object@VALUE(0x1018)] = GuardType v57, HeapObject[class_exact*:Object@VALUE(0x1018)]
12069-
v107:BasicObject = CCallWithFrame v106, :Kernel#lambda@0x1050, block=0x1058
12070-
SetLocal :sep, l0, EP@5, v107
12071-
Jump bb7(v57, v58, v107, v60, v61)
12072-
bb7(v77:BasicObject, v78:BasicObject, v79:BasicObject, v80:BasicObject, v81:NilClass):
12080+
v110:HeapObject[class_exact*:Object@VALUE(0x1018)] = GuardType v57, HeapObject[class_exact*:Object@VALUE(0x1018)]
12081+
v111:BasicObject = CCallWithFrame v110, :Kernel#lambda@0x1050, block=0x1058
12082+
PatchPoint NoEPEscape(test_ep_escape)
12083+
SetLocal :sep, l0, EP@5, v111
12084+
Jump bb7(v57, v58, v111, v60, v61)
12085+
bb7(v79:BasicObject, v80:BasicObject, v81:BasicObject, v82:BasicObject, v83:NilClass):
1207312086
PatchPoint SingleRactorMode
1207412087
PatchPoint StableConstantNames(0x1060, CONST)
12075-
v102:HashExact[VALUE(0x1068)] = Const Value(VALUE(0x1068))
12076-
SetLocal :kwsplat, l0, EP@3, v102
12077-
v91:BasicObject = GetLocal :list, l0, EP@6
12078-
v93:BasicObject = GetLocal :iter_method, l0, EP@4
12079-
v95:BasicObject = Send v91, 0x1070, :__send__, v93 # SendFallbackReason: Send: unsupported method type Optimized
12080-
CheckInterrupts
12081-
Return v95
12088+
v106:HashExact[VALUE(0x1068)] = Const Value(VALUE(0x1068))
12089+
SetLocal :kwsplat, l0, EP@3, v106
12090+
v93:BasicObject = GetLocal :list, l0, EP@6
12091+
v95:BasicObject = GetLocal :iter_method, l0, EP@4
12092+
v97:BasicObject = Send v93, 0x1070, :__send__, v95 # SendFallbackReason: Uncategorized(send)
12093+
PatchPoint NoEPEscape(test_ep_escape)
12094+
CheckInterrupts
12095+
Return v97
1208212096
");
1208312097
}
1208412098

0 commit comments

Comments
 (0)