Skip to content

Commit 4129769

Browse files
committed
ZJIT: Invalidate NoEPEscape for parent ISEQs on eval
eval captures the caller's EP without escaping it to the heap, so vm_make_env_each never fires for parent frames. Walk up parent ISEQs to invalidate NoEPEscape since eval can write to locals in any enclosing block scope through the EP chain.
1 parent 91a29f4 commit 4129769

File tree

2 files changed

+101
-1
lines changed

2 files changed

+101
-1
lines changed

vm_eval.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1991,7 +1991,12 @@ eval_string_with_cref(VALUE self, VALUE src, rb_cref_t *cref, VALUE file, int li
19911991

19921992
// EP is not escaped to the heap here, but captured and reused by another frame.
19931993
// ZJIT's locals are incompatible with it unlike YJIT's, so invalidate the ISEQ for ZJIT.
1994-
rb_zjit_invalidate_no_ep_escape(cfp->iseq);
1994+
// Walk up parent ISEQs since eval can write to locals in any enclosing block scope.
1995+
// Stop at the method boundary since `def` creates a new scope that eval can't cross.
1996+
for (const rb_iseq_t *iseq = cfp->iseq; iseq; iseq = ISEQ_BODY(iseq)->parent_iseq) {
1997+
rb_zjit_invalidate_no_ep_escape(iseq);
1998+
if (ISEQ_BODY(iseq)->type == ISEQ_TYPE_METHOD) break;
1999+
}
19952000

19962001
iseq = eval_make_iseq(src, file, line, &block);
19972002
if (!iseq) {

zjit/src/hir/opt_tests.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2961,6 +2961,101 @@ mod hir_opt_tests {
29612961
");
29622962
}
29632963

2964+
#[test]
2965+
fn reload_local_written_in_rescue_handler() {
2966+
eval("
2967+
def test
2968+
a = 1
2969+
b = 2
2970+
tap do
2971+
begin
2972+
raise
2973+
rescue
2974+
b = 3
2975+
end
2976+
end
2977+
a + b
2978+
end
2979+
test
2980+
test
2981+
");
2982+
assert_snapshot!(hir_string("test"), @r"
2983+
fn test@<compiled>:3:
2984+
bb0():
2985+
EntryPoint interpreter
2986+
v1:BasicObject = LoadSelf
2987+
v2:NilClass = Const Value(nil)
2988+
v3:NilClass = Const Value(nil)
2989+
Jump bb2(v1, v2, v3)
2990+
bb1(v6:BasicObject):
2991+
EntryPoint JIT(0)
2992+
v7:NilClass = Const Value(nil)
2993+
v8:NilClass = Const Value(nil)
2994+
Jump bb2(v6, v7, v8)
2995+
bb2(v10:BasicObject, v11:NilClass, v12:NilClass):
2996+
v16:Fixnum[1] = Const Value(1)
2997+
v20:Fixnum[2] = Const Value(2)
2998+
PatchPoint NoSingletonClass(Object@0x1000)
2999+
PatchPoint MethodRedefined(Object@0x1000, tap@0x1008, cme:0x1010)
3000+
v42:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v10, HeapObject[class_exact*:Object@VALUE(0x1000)]
3001+
v43:BasicObject = SendDirect v42, 0x1038, :tap (0x1048)
3002+
v26:BasicObject = GetLocal :b, l0, EP@3
3003+
PatchPoint NoEPEscape(test)
3004+
PatchPoint MethodRedefined(Integer@0x1050, +@0x1058, cme:0x1060)
3005+
v46:Fixnum = GuardType v26, Fixnum
3006+
v47:Fixnum = FixnumAdd v16, v46
3007+
IncrCounter inline_cfunc_optimized_send_count
3008+
CheckInterrupts
3009+
Return v47
3010+
");
3011+
}
3012+
3013+
#[test]
3014+
fn eval_in_block_escapes_parent_ep() {
3015+
eval("
3016+
class EvalLocal; def f; yield; end; end
3017+
def test
3018+
a = 1
3019+
EvalLocal.new.f { eval('a = 2') }
3020+
a
3021+
end
3022+
test
3023+
test
3024+
");
3025+
assert_snapshot!(hir_string("test"), @r"
3026+
fn test@<compiled>:4:
3027+
bb0():
3028+
EntryPoint interpreter
3029+
v1:BasicObject = LoadSelf
3030+
v2:NilClass = Const Value(nil)
3031+
Jump bb2(v1, v2)
3032+
bb1(v5:BasicObject):
3033+
EntryPoint JIT(0)
3034+
v6:NilClass = Const Value(nil)
3035+
Jump bb2(v5, v6)
3036+
bb2(v8:BasicObject, v9:NilClass):
3037+
v13:Fixnum[1] = Const Value(1)
3038+
SetLocal :a, l0, EP@3, v13
3039+
PatchPoint SingleRactorMode
3040+
PatchPoint StableConstantNames(0x1000, EvalLocal)
3041+
v59:Class[EvalLocal@0x1008] = Const Value(VALUE(0x1008))
3042+
v21:NilClass = Const Value(nil)
3043+
PatchPoint MethodRedefined(EvalLocal@0x1008, new@0x1009, cme:0x1010)
3044+
v62:HeapObject[class_exact:EvalLocal] = ObjectAllocClass EvalLocal:VALUE(0x1008)
3045+
PatchPoint NoSingletonClass(EvalLocal@0x1008)
3046+
PatchPoint MethodRedefined(EvalLocal@0x1008, initialize@0x1038, cme:0x1040)
3047+
v70:NilClass = Const Value(nil)
3048+
IncrCounter inline_cfunc_optimized_send_count
3049+
CheckInterrupts
3050+
PatchPoint NoSingletonClass(EvalLocal@0x1008)
3051+
PatchPoint MethodRedefined(EvalLocal@0x1008, f@0x1068, cme:0x1070)
3052+
v66:BasicObject = SendDirect v62, 0x1098, :f (0x10a8)
3053+
v52:BasicObject = GetLocal :a, l0, EP@3
3054+
CheckInterrupts
3055+
Return v52
3056+
");
3057+
}
3058+
29643059
#[test]
29653060
fn dont_specialize_call_to_iseq_with_rest() {
29663061
eval("

0 commit comments

Comments
 (0)