Skip to content

Commit 89ef3a0

Browse files
committed
Add BlockParamProxyModified side exit, fixes bug
1 parent 1a8cba9 commit 89ef3a0

4 files changed

Lines changed: 127 additions & 84 deletions

File tree

zjit/src/codegen_tests.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,28 @@ fn test_block_param_proxy_iseq_guard_miss_recompiles() {
644644
assert_snapshot!(assert_compiles_allowing_exits("[invoke { |x| x + 1 }, invoke { 1000 }]"), @"[42, 1000]");
645645
}
646646

647+
#[test]
648+
fn test_block_param_proxy_call_reassigned_when_no_block() {
649+
// Regression: `blk ||= proc {...}` reassigns the block param when no block is passed, so
650+
// getblockparamproxy takes its modified path and returns the reassigned proc, not the proxy.
651+
// That path loads the local with no handler guard and feeds it to blk.call, which lowers to
652+
// InvokeBlockParam here (the arity-mismatched `.call(41)` against a no-param block is not
653+
// inlinable). Without the receiver identity guard, InvokeBlockParam yields the (absent) frame
654+
// block and raises LocalJumpError. The guard must miss and recompile as a generic send.
655+
set_call_threshold(2);
656+
eval("
657+
def invoke(&blk)
658+
blk ||= proc { 2000 }
659+
r = nil
660+
[1].each { r = blk.call(41) }
661+
r
662+
end
663+
invoke { 1000 }
664+
invoke { 1000 }
665+
");
666+
assert_snapshot!(assert_compiles_allowing_exits("[invoke { 1000 }, invoke]"), @"[1000, 2000]");
667+
}
668+
647669
#[test]
648670
fn test_yield_iseq_guard_miss_recompiles() {
649671
set_call_threshold(2);

zjit/src/hir.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,7 @@ pub enum SideExitReason {
555555
BlockParamProxyProfileNotCovered,
556556
BlockParamProxyHandlerNotIseq,
557557
BlockParamProxyIseqChanged,
558+
BlockParamProxyModified,
558559
BlockParamWbRequired,
559560
StackOverflow,
560561
FixnumModByZero,
@@ -4203,6 +4204,13 @@ impl Function {
42034204
self.set_dynamic_send_reason(insn_id, SendWithoutBlockNotOptimizedMethodTypeOptimized(OptimizedMethodType::BlockCall));
42044205
self.push_insn_id(block, insn_id); continue;
42054206
}
4207+
self.push_insn(block, Insn::GuardBitEquals {
4208+
val: recv,
4209+
expected: Const::Value(unsafe { rb_block_param_proxy }),
4210+
reason: SideExitReason::BlockParamProxyModified,
4211+
state,
4212+
recompile: Some(Recompile),
4213+
});
42064214
// if the block is a known simple ISEQ block with exact arity and no non-local
42074215
// exit, push its frame inline instead of calling vm_yield.
42084216
let inline_iseq = self.block_param_proxy_iseqs.get(&recv).copied()

zjit/src/hir/opt_tests.rs

Lines changed: 95 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -3844,19 +3844,20 @@ mod hir_opt_tests {
38443844
Jump bb8(v41, v51)
38453845
bb8(v31:BasicObject, v32:BasicObject):
38463846
PatchPoint BOPRedefined(4096, BOP_CALL)
3847-
v57:CPtr = GetEP 0
3848-
v58:CInt64 = LoadField v57, :VM_ENV_DATA_INDEX_SPECVAL@0x1042
3849-
v59:CInt64[3] = Const CInt64(3)
3850-
v60:CInt64 = IntAnd v58, v59
3851-
v61:CInt64[1] = GuardBitEquals v60, CInt64(1) recompile
3852-
v62:CInt64[-4] = Const CInt64(-4)
3853-
v63:CInt64 = IntAnd v58, v62
3854-
v64:CPtr = LoadField v63, :code_iseq@0x1050
3855-
v65:CPtr[CPtr(0x1051)] = GuardBitEquals v64, CPtr(0x1051) recompile
3856-
v66:BasicObject = InvokeBlockIseqDirect (0x1038), v63, v11, v13
3847+
v57:ObjectSubclass[BlockParamProxy] = GuardBitEquals v31, Value(VALUE(0x1048)) recompile
3848+
v58:CPtr = GetEP 0
3849+
v59:CInt64 = LoadField v58, :VM_ENV_DATA_INDEX_SPECVAL@0x1042
3850+
v60:CInt64[3] = Const CInt64(3)
3851+
v61:CInt64 = IntAnd v59, v60
3852+
v62:CInt64[1] = GuardBitEquals v61, CInt64(1) recompile
3853+
v63:CInt64[-4] = Const CInt64(-4)
3854+
v64:CInt64 = IntAnd v59, v63
3855+
v65:CPtr = LoadField v64, :code_iseq@0x1050
3856+
v66:CPtr[CPtr(0x1051)] = GuardBitEquals v65, CPtr(0x1051) recompile
3857+
v67:BasicObject = InvokeBlockIseqDirect (0x1038), v64, v11, v13
38573858
CheckInterrupts
38583859
PopInlineFrame
3859-
Return v66
3860+
Return v67
38603861
");
38613862
}
38623863

@@ -3898,19 +3899,20 @@ mod hir_opt_tests {
38983899
Jump bb8(v35, v43)
38993900
bb8(v25:BasicObject, v26:BasicObject):
39003901
PatchPoint BOPRedefined(4096, BOP_CALL)
3901-
v49:CPtr = GetEP 0
3902-
v50:CInt64 = LoadField v49, :VM_ENV_DATA_INDEX_SPECVAL@0x1042
3903-
v51:CInt64[3] = Const CInt64(3)
3904-
v52:CInt64 = IntAnd v50, v51
3905-
v53:CInt64[1] = GuardBitEquals v52, CInt64(1) recompile
3906-
v54:CInt64[-4] = Const CInt64(-4)
3907-
v55:CInt64 = IntAnd v50, v54
3908-
v56:CPtr = LoadField v55, :code_iseq@0x1050
3909-
v57:CPtr[CPtr(0x1051)] = GuardBitEquals v56, CPtr(0x1051) recompile
3910-
v58:BasicObject = InvokeBlockIseqDirect (0x1038), v55
3902+
v49:ObjectSubclass[BlockParamProxy] = GuardBitEquals v25, Value(VALUE(0x1048)) recompile
3903+
v50:CPtr = GetEP 0
3904+
v51:CInt64 = LoadField v50, :VM_ENV_DATA_INDEX_SPECVAL@0x1042
3905+
v52:CInt64[3] = Const CInt64(3)
3906+
v53:CInt64 = IntAnd v51, v52
3907+
v54:CInt64[1] = GuardBitEquals v53, CInt64(1) recompile
3908+
v55:CInt64[-4] = Const CInt64(-4)
3909+
v56:CInt64 = IntAnd v51, v55
3910+
v57:CPtr = LoadField v56, :code_iseq@0x1050
3911+
v58:CPtr[CPtr(0x1051)] = GuardBitEquals v57, CPtr(0x1051) recompile
3912+
v59:BasicObject = InvokeBlockIseqDirect (0x1038), v56
39113913
CheckInterrupts
39123914
PopInlineFrame
3913-
Return v58
3915+
Return v59
39143916
");
39153917
}
39163918

@@ -3955,19 +3957,20 @@ mod hir_opt_tests {
39553957
v38:ArrayExact[VALUE(0x1050)] = Const Value(VALUE(0x1050))
39563958
v39:ArrayExact = ArrayDup v38
39573959
PatchPoint BOPRedefined(4096, BOP_CALL)
3958-
v52:CPtr = GetEP 0
3959-
v53:CInt64 = LoadField v52, :VM_ENV_DATA_INDEX_SPECVAL@0x1042
3960-
v54:CInt64[3] = Const CInt64(3)
3961-
v55:CInt64 = IntAnd v53, v54
3962-
v56:CInt64[1] = GuardBitEquals v55, CInt64(1) recompile
3963-
v57:CInt64[-4] = Const CInt64(-4)
3964-
v58:CInt64 = IntAnd v53, v57
3965-
v59:CPtr = LoadField v58, :code_iseq@0x1058
3966-
v60:CPtr[CPtr(0x1059)] = GuardBitEquals v59, CPtr(0x1059) recompile
3967-
v61:BasicObject = InvokeBlockIseqDirect (0x1038), v58, v39
3960+
v52:ObjectSubclass[BlockParamProxy] = GuardBitEquals v25, Value(VALUE(0x1048)) recompile
3961+
v53:CPtr = GetEP 0
3962+
v54:CInt64 = LoadField v53, :VM_ENV_DATA_INDEX_SPECVAL@0x1042
3963+
v55:CInt64[3] = Const CInt64(3)
3964+
v56:CInt64 = IntAnd v54, v55
3965+
v57:CInt64[1] = GuardBitEquals v56, CInt64(1) recompile
3966+
v58:CInt64[-4] = Const CInt64(-4)
3967+
v59:CInt64 = IntAnd v54, v58
3968+
v60:CPtr = LoadField v59, :code_iseq@0x1058
3969+
v61:CPtr[CPtr(0x1059)] = GuardBitEquals v60, CPtr(0x1059) recompile
3970+
v62:BasicObject = InvokeBlockIseqDirect (0x1038), v59, v39
39683971
CheckInterrupts
39693972
PopInlineFrame
3970-
Return v61
3973+
Return v62
39713974
");
39723975
}
39733976

@@ -4010,19 +4013,20 @@ mod hir_opt_tests {
40104013
Jump bb8(v35, v43)
40114014
bb8(v25:BasicObject, v26:BasicObject):
40124015
PatchPoint BOPRedefined(4096, BOP_CALL)
4013-
v49:CPtr = GetEP 0
4014-
v50:CInt64 = LoadField v49, :VM_ENV_DATA_INDEX_SPECVAL@0x1042
4015-
v51:CInt64[3] = Const CInt64(3)
4016-
v52:CInt64 = IntAnd v50, v51
4017-
v53:CInt64[1] = GuardBitEquals v52, CInt64(1) recompile
4018-
v54:CInt64[-4] = Const CInt64(-4)
4019-
v55:CInt64 = IntAnd v50, v54
4020-
v56:CPtr = LoadField v55, :code_iseq@0x1050
4021-
v57:CPtr[CPtr(0x1051)] = GuardBitEquals v56, CPtr(0x1051) recompile
4022-
v58:BasicObject = InvokeBlockIseqDirect (0x1038), v55
4016+
v49:ObjectSubclass[BlockParamProxy] = GuardBitEquals v25, Value(VALUE(0x1048)) recompile
4017+
v50:CPtr = GetEP 0
4018+
v51:CInt64 = LoadField v50, :VM_ENV_DATA_INDEX_SPECVAL@0x1042
4019+
v52:CInt64[3] = Const CInt64(3)
4020+
v53:CInt64 = IntAnd v51, v52
4021+
v54:CInt64[1] = GuardBitEquals v53, CInt64(1) recompile
4022+
v55:CInt64[-4] = Const CInt64(-4)
4023+
v56:CInt64 = IntAnd v51, v55
4024+
v57:CPtr = LoadField v56, :code_iseq@0x1050
4025+
v58:CPtr[CPtr(0x1051)] = GuardBitEquals v57, CPtr(0x1051) recompile
4026+
v59:BasicObject = InvokeBlockIseqDirect (0x1038), v56
40234027
CheckInterrupts
40244028
PopInlineFrame
4025-
Return v58
4029+
Return v59
40264030
");
40274031
}
40284032

@@ -4075,20 +4079,21 @@ mod hir_opt_tests {
40754079
Jump bb8(v46, v54)
40764080
bb8(v36:BasicObject, v37:BasicObject):
40774081
PatchPoint BOPRedefined(4096, BOP_CALL)
4078-
v60:CPtr = GetEP 0
4079-
v61:CInt64 = LoadField v60, :VM_ENV_DATA_INDEX_SPECVAL@0x1042
4080-
v62:CInt64[3] = Const CInt64(3)
4081-
v63:CInt64 = IntAnd v61, v62
4082-
v64:CInt64[1] = GuardBitEquals v63, CInt64(1) recompile
4083-
v65:CInt64[-4] = Const CInt64(-4)
4084-
v66:CInt64 = IntAnd v61, v65
4085-
v67:CPtr = LoadField v66, :code_iseq@0x1050
4086-
v68:CPtr[CPtr(0x1051)] = GuardBitEquals v67, CPtr(0x1051) recompile
4087-
v69:BasicObject = InvokeBlockIseqDirect (0x1038), v66
4082+
v60:ObjectSubclass[BlockParamProxy] = GuardBitEquals v36, Value(VALUE(0x1048)) recompile
4083+
v61:CPtr = GetEP 0
4084+
v62:CInt64 = LoadField v61, :VM_ENV_DATA_INDEX_SPECVAL@0x1042
4085+
v63:CInt64[3] = Const CInt64(3)
4086+
v64:CInt64 = IntAnd v62, v63
4087+
v65:CInt64[1] = GuardBitEquals v64, CInt64(1) recompile
4088+
v66:CInt64[-4] = Const CInt64(-4)
4089+
v67:CInt64 = IntAnd v62, v66
4090+
v68:CPtr = LoadField v67, :code_iseq@0x1050
4091+
v69:CPtr[CPtr(0x1051)] = GuardBitEquals v68, CPtr(0x1051) recompile
4092+
v70:BasicObject = InvokeBlockIseqDirect (0x1038), v67
40884093
CheckInterrupts
40894094
PopInlineFrame
40904095
PatchPoint NoEPEscape(test)
4091-
Return v69
4096+
Return v70
40924097
");
40934098
}
40944099

@@ -4131,19 +4136,20 @@ mod hir_opt_tests {
41314136
Jump bb8(v35, v43)
41324137
bb8(v25:BasicObject, v26:BasicObject):
41334138
PatchPoint BOPRedefined(4096, BOP_CALL)
4134-
v49:CPtr = GetEP 0
4135-
v50:CInt64 = LoadField v49, :VM_ENV_DATA_INDEX_SPECVAL@0x1042
4136-
v51:CInt64[3] = Const CInt64(3)
4137-
v52:CInt64 = IntAnd v50, v51
4138-
v53:CInt64[1] = GuardBitEquals v52, CInt64(1) recompile
4139-
v54:CInt64[-4] = Const CInt64(-4)
4140-
v55:CInt64 = IntAnd v50, v54
4141-
v56:CPtr = LoadField v55, :code_iseq@0x1050
4142-
v57:CPtr[CPtr(0x1051)] = GuardBitEquals v56, CPtr(0x1051) recompile
4143-
v58:BasicObject = InvokeBlockIseqDirect (0x1038), v55
4139+
v49:ObjectSubclass[BlockParamProxy] = GuardBitEquals v25, Value(VALUE(0x1048)) recompile
4140+
v50:CPtr = GetEP 0
4141+
v51:CInt64 = LoadField v50, :VM_ENV_DATA_INDEX_SPECVAL@0x1042
4142+
v52:CInt64[3] = Const CInt64(3)
4143+
v53:CInt64 = IntAnd v51, v52
4144+
v54:CInt64[1] = GuardBitEquals v53, CInt64(1) recompile
4145+
v55:CInt64[-4] = Const CInt64(-4)
4146+
v56:CInt64 = IntAnd v51, v55
4147+
v57:CPtr = LoadField v56, :code_iseq@0x1050
4148+
v58:CPtr[CPtr(0x1051)] = GuardBitEquals v57, CPtr(0x1051) recompile
4149+
v59:BasicObject = InvokeBlockIseqDirect (0x1038), v56
41444150
CheckInterrupts
41454151
PopInlineFrame
4146-
Return v58
4152+
Return v59
41474153
");
41484154
}
41494155

@@ -4189,10 +4195,11 @@ mod hir_opt_tests {
41894195
v38:ArrayExact[VALUE(0x1050)] = Const Value(VALUE(0x1050))
41904196
v39:ArrayExact = ArrayDup v38
41914197
PatchPoint BOPRedefined(4096, BOP_CALL)
4192-
v52:BasicObject = InvokeBlockParam v39
4198+
v52:ObjectSubclass[BlockParamProxy] = GuardBitEquals v25, Value(VALUE(0x1048)) recompile
4199+
v53:BasicObject = InvokeBlockParam v39
41934200
CheckInterrupts
41944201
PopInlineFrame
4195-
Return v52
4202+
Return v53
41964203
");
41974204
}
41984205

@@ -4236,10 +4243,11 @@ mod hir_opt_tests {
42364243
bb8(v25:BasicObject, v26:BasicObject):
42374244
v38:Fixnum[9] = Const Value(9)
42384245
PatchPoint BOPRedefined(4096, BOP_CALL)
4239-
v51:BasicObject = InvokeBlockParam v38
4246+
v51:ObjectSubclass[BlockParamProxy] = GuardBitEquals v25, Value(VALUE(0x1048)) recompile
4247+
v52:BasicObject = InvokeBlockParam v38
42404248
CheckInterrupts
42414249
PopInlineFrame
4242-
Return v51
4250+
Return v52
42434251
");
42444252
}
42454253

@@ -4282,10 +4290,11 @@ mod hir_opt_tests {
42824290
Jump bb8(v35, v43)
42834291
bb8(v25:BasicObject, v26:BasicObject):
42844292
PatchPoint BOPRedefined(4096, BOP_CALL)
4285-
v49:BasicObject = InvokeBlockParam
4293+
v49:ObjectSubclass[BlockParamProxy] = GuardBitEquals v25, Value(VALUE(0x1048)) recompile
4294+
v50:BasicObject = InvokeBlockParam
42864295
CheckInterrupts
42874296
PopInlineFrame
4288-
Return v49
4297+
Return v50
42894298
");
42904299
}
42914300

@@ -4331,7 +4340,8 @@ mod hir_opt_tests {
43314340
Jump bb8(v39, v47)
43324341
bb8(v29:BasicObject, v30:BasicObject):
43334342
PatchPoint BOPRedefined(4096, BOP_CALL)
4334-
v53:BasicObject = InvokeBlockParam
4343+
v53:ObjectSubclass[BlockParamProxy] = GuardBitEquals v29, Value(VALUE(0x1048)) recompile
4344+
v54:BasicObject = InvokeBlockParam
43354345
CheckInterrupts
43364346
PopInlineFrame
43374347
v15:Fixnum[99] = Const Value(99)
@@ -19788,20 +19798,21 @@ mod hir_opt_tests {
1978819798
Jump bb8(v43, v52)
1978919799
bb8(v33:BasicObject, v34:BasicObject):
1979019800
PatchPoint BOPRedefined(4096, BOP_CALL)
19791-
v58:CPtr = GetEP 0
19792-
v59:CInt64 = LoadField v58, :VM_ENV_DATA_INDEX_SPECVAL@0x104a
19793-
v60:CInt64[3] = Const CInt64(3)
19794-
v61:CInt64 = IntAnd v59, v60
19795-
v62:CInt64[1] = GuardBitEquals v61, CInt64(1) recompile
19796-
v63:CInt64[-4] = Const CInt64(-4)
19797-
v64:CInt64 = IntAnd v59, v63
19798-
v65:CPtr = LoadField v64, :code_iseq@0x1058
19799-
v66:CPtr[CPtr(0x1059)] = GuardBitEquals v65, CPtr(0x1059) recompile
19800-
v67:BasicObject = InvokeBlockIseqDirect (0x1040), v64, v10
19801+
v58:ObjectSubclass[BlockParamProxy] = GuardBitEquals v33, Value(VALUE(0x1050)) recompile
19802+
v59:CPtr = GetEP 0
19803+
v60:CInt64 = LoadField v59, :VM_ENV_DATA_INDEX_SPECVAL@0x104a
19804+
v61:CInt64[3] = Const CInt64(3)
19805+
v62:CInt64 = IntAnd v60, v61
19806+
v63:CInt64[1] = GuardBitEquals v62, CInt64(1) recompile
19807+
v64:CInt64[-4] = Const CInt64(-4)
19808+
v65:CInt64 = IntAnd v60, v64
19809+
v66:CPtr = LoadField v65, :code_iseq@0x1058
19810+
v67:CPtr[CPtr(0x1059)] = GuardBitEquals v66, CPtr(0x1059) recompile
19811+
v68:BasicObject = InvokeBlockIseqDirect (0x1040), v65, v10
1980119812
CheckInterrupts
1980219813
PopInlineFrame
1980319814
PatchPoint NoEPEscape(test)
19804-
Return v67
19815+
Return v68
1980519816
");
1980619817
}
1980719818

zjit/src/stats.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ make_counters! {
233233
exit_block_param_proxy_profile_not_covered,
234234
exit_block_param_proxy_handler_not_iseq,
235235
exit_block_param_proxy_iseq_changed,
236+
exit_block_param_proxy_modified,
236237
exit_block_param_wb_required,
237238
exit_too_many_keyword_parameters,
238239
exit_too_many_args_for_lir,
@@ -639,6 +640,7 @@ pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter {
639640
BlockParamProxyProfileNotCovered => exit_block_param_proxy_profile_not_covered,
640641
BlockParamProxyHandlerNotIseq => exit_block_param_proxy_handler_not_iseq,
641642
BlockParamProxyIseqChanged => exit_block_param_proxy_iseq_changed,
643+
BlockParamProxyModified => exit_block_param_proxy_modified,
642644
BlockParamWbRequired => exit_block_param_wb_required,
643645
TooManyKeywordParameters => exit_too_many_keyword_parameters,
644646
TooManyArgsForLir => exit_too_many_args_for_lir,

0 commit comments

Comments
 (0)