Skip to content

Commit dccfab0

Browse files
authored
YJIT: Always abandon the block when gen_branch() or defer_compilation() fails
In [1], we started checking for gen_branch failures, but I made two crucial mistakes. One, defer_compilation() had the same issue as gen_branch() but wasn't checked. Two, returning None from a codegen function does not throw away the block. Checking how gen_single_block() handles codegen functions, you can see that None terminates the block with an exit, but does not overall return an Err. This handling is fine for unimplemented instructions, for example, but incorrect in case gen_branch() fails. The missing branch essentially corrupts the block; adding more code after a missing branch doesn't correct the code. Always abandon the block when defer_compilation() or gen_branch() fails. [1]: cb661d7 Fixup: [1]
1 parent 6767117 commit dccfab0

File tree

2 files changed

+64
-72
lines changed

2 files changed

+64
-72
lines changed

yjit/src/codegen.rs

Lines changed: 59 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,34 @@ impl<'a> JITState<'a> {
195195
self.outlined_code_block
196196
}
197197

198+
/// Leave a code stub to re-enter the compiler at runtime when the compiling program point is
199+
/// reached. Should always be used in tail position like `return jit.defer_compilation(asm);`.
200+
#[must_use]
201+
fn defer_compilation(&mut self, asm: &mut Assembler) -> Option<CodegenStatus> {
202+
if crate::core::defer_compilation(self, asm).is_err() {
203+
// If we can't leave a stub, the block isn't usable and we have to bail.
204+
self.block_abandoned = true;
205+
}
206+
Some(EndBlock)
207+
}
208+
209+
/// Generate a branch with either end possibly stubbed out
210+
fn gen_branch(
211+
&mut self,
212+
asm: &mut Assembler,
213+
target0: BlockId,
214+
ctx0: &Context,
215+
target1: Option<BlockId>,
216+
ctx1: Option<&Context>,
217+
gen_fn: BranchGenFn,
218+
) {
219+
if crate::core::gen_branch(self, asm, target0, ctx0, target1, ctx1, gen_fn).is_none() {
220+
// If we can't meet the request for a branch, the code is
221+
// essentially corrupt and we have to discard the block.
222+
self.block_abandoned = true;
223+
}
224+
}
225+
198226
/// Return true if the current ISEQ could escape an environment.
199227
///
200228
/// As of vm_push_frame(), EP is always equal to BP. However, after pushing
@@ -1538,8 +1566,7 @@ fn fuse_putobject_opt_ltlt(
15381566
return None;
15391567
}
15401568
if !jit.at_compile_target() {
1541-
defer_compilation(jit, asm);
1542-
return Some(EndBlock);
1569+
return jit.defer_compilation(asm);
15431570
}
15441571

15451572
let lhs = jit.peek_at_stack(&asm.ctx, 0);
@@ -1661,8 +1688,7 @@ fn gen_opt_plus(
16611688
let two_fixnums = match asm.ctx.two_fixnums_on_stack(jit) {
16621689
Some(two_fixnums) => two_fixnums,
16631690
None => {
1664-
defer_compilation(jit, asm);
1665-
return Some(EndBlock);
1691+
return jit.defer_compilation(asm);
16661692
}
16671693
};
16681694

@@ -1802,8 +1828,7 @@ fn gen_splatkw(
18021828
) -> Option<CodegenStatus> {
18031829
// Defer compilation so we can specialize on a runtime hash operand
18041830
if !jit.at_compile_target() {
1805-
defer_compilation(jit, asm);
1806-
return Some(EndBlock);
1831+
return jit.defer_compilation(asm);
18071832
}
18081833

18091834
let comptime_hash = jit.peek_at_stack(&asm.ctx, 1);
@@ -2176,8 +2201,7 @@ fn gen_expandarray(
21762201

21772202
// Defer compilation so we can specialize on a runtime `self`
21782203
if !jit.at_compile_target() {
2179-
defer_compilation(jit, asm);
2180-
return Some(EndBlock);
2204+
return jit.defer_compilation(asm);
21812205
}
21822206

21832207
let comptime_recv = jit.peek_at_stack(&asm.ctx, 0);
@@ -2718,10 +2742,7 @@ fn jit_chain_guard(
27182742
idx: jit.insn_idx,
27192743
};
27202744

2721-
// Bail if we can't generate the branch
2722-
if gen_branch(jit, asm, bid, &deeper, None, None, target0_gen_fn).is_none() {
2723-
jit.block_abandoned = true;
2724-
}
2745+
jit.gen_branch(asm, bid, &deeper, None, None, target0_gen_fn);
27252746
} else {
27262747
target0_gen_fn.call(asm, Target::side_exit(counter), None);
27272748
}
@@ -2895,8 +2916,7 @@ fn gen_getinstancevariable(
28952916
) -> Option<CodegenStatus> {
28962917
// Defer compilation so we can specialize on a runtime `self`
28972918
if !jit.at_compile_target() {
2898-
defer_compilation(jit, asm);
2899-
return Some(EndBlock);
2919+
return jit.defer_compilation(asm);
29002920
}
29012921

29022922
let ivar_name = jit.get_arg(0).as_u64();
@@ -2959,8 +2979,7 @@ fn gen_setinstancevariable(
29592979
) -> Option<CodegenStatus> {
29602980
// Defer compilation so we can specialize on a runtime `self`
29612981
if !jit.at_compile_target() {
2962-
defer_compilation(jit, asm);
2963-
return Some(EndBlock);
2982+
return jit.defer_compilation(asm);
29642983
}
29652984

29662985
let ivar_name = jit.get_arg(0).as_u64();
@@ -3270,8 +3289,7 @@ fn gen_definedivar(
32703289
) -> Option<CodegenStatus> {
32713290
// Defer compilation so we can specialize base on a runtime receiver
32723291
if !jit.at_compile_target() {
3273-
defer_compilation(jit, asm);
3274-
return Some(EndBlock);
3292+
return jit.defer_compilation(asm);
32753293
}
32763294

32773295
let ivar_name = jit.get_arg(0).as_u64();
@@ -3500,8 +3518,7 @@ fn gen_fixnum_cmp(
35003518
Some(two_fixnums) => two_fixnums,
35013519
None => {
35023520
// Defer compilation so we can specialize based on a runtime receiver
3503-
defer_compilation(jit, asm);
3504-
return Some(EndBlock);
3521+
return jit.defer_compilation(asm);
35053522
}
35063523
};
35073524

@@ -3680,8 +3697,7 @@ fn gen_opt_eq(
36803697
Some(specialized) => specialized,
36813698
None => {
36823699
// Defer compilation so we can specialize base on a runtime receiver
3683-
defer_compilation(jit, asm);
3684-
return Some(EndBlock);
3700+
return jit.defer_compilation(asm);
36853701
}
36863702
};
36873703

@@ -3718,8 +3734,7 @@ fn gen_opt_aref(
37183734

37193735
// Defer compilation so we can specialize base on a runtime receiver
37203736
if !jit.at_compile_target() {
3721-
defer_compilation(jit, asm);
3722-
return Some(EndBlock);
3737+
return jit.defer_compilation(asm);
37233738
}
37243739

37253740
// Specialize base on compile time values
@@ -3819,8 +3834,7 @@ fn gen_opt_aset(
38193834
) -> Option<CodegenStatus> {
38203835
// Defer compilation so we can specialize on a runtime `self`
38213836
if !jit.at_compile_target() {
3822-
defer_compilation(jit, asm);
3823-
return Some(EndBlock);
3837+
return jit.defer_compilation(asm);
38243838
}
38253839

38263840
let comptime_recv = jit.peek_at_stack(&asm.ctx, 2);
@@ -3951,8 +3965,7 @@ fn gen_opt_and(
39513965
Some(two_fixnums) => two_fixnums,
39523966
None => {
39533967
// Defer compilation so we can specialize on a runtime `self`
3954-
defer_compilation(jit, asm);
3955-
return Some(EndBlock);
3968+
return jit.defer_compilation(asm);
39563969
}
39573970
};
39583971

@@ -3990,8 +4003,7 @@ fn gen_opt_or(
39904003
Some(two_fixnums) => two_fixnums,
39914004
None => {
39924005
// Defer compilation so we can specialize on a runtime `self`
3993-
defer_compilation(jit, asm);
3994-
return Some(EndBlock);
4006+
return jit.defer_compilation(asm);
39954007
}
39964008
};
39974009

@@ -4029,8 +4041,7 @@ fn gen_opt_minus(
40294041
Some(two_fixnums) => two_fixnums,
40304042
None => {
40314043
// Defer compilation so we can specialize on a runtime `self`
4032-
defer_compilation(jit, asm);
4033-
return Some(EndBlock);
4044+
return jit.defer_compilation(asm);
40344045
}
40354046
};
40364047

@@ -4069,8 +4080,7 @@ fn gen_opt_mult(
40694080
let two_fixnums = match asm.ctx.two_fixnums_on_stack(jit) {
40704081
Some(two_fixnums) => two_fixnums,
40714082
None => {
4072-
defer_compilation(jit, asm);
4073-
return Some(EndBlock);
4083+
return jit.defer_compilation(asm);
40744084
}
40754085
};
40764086

@@ -4121,8 +4131,7 @@ fn gen_opt_mod(
41214131
Some(two_fixnums) => two_fixnums,
41224132
None => {
41234133
// Defer compilation so we can specialize on a runtime `self`
4124-
defer_compilation(jit, asm);
4125-
return Some(EndBlock);
4134+
return jit.defer_compilation(asm);
41264135
}
41274136
};
41284137

@@ -4459,8 +4468,7 @@ fn gen_opt_case_dispatch(
44594468
// hash lookup, at least for small hashes, but it's worth revisiting this
44604469
// assumption in the future.
44614470
if !jit.at_compile_target() {
4462-
defer_compilation(jit, asm);
4463-
return Some(EndBlock);
4471+
return jit.defer_compilation(asm);
44644472
}
44654473

44664474
let case_hash = jit.get_arg(0);
@@ -4572,15 +4580,14 @@ fn gen_branchif(
45724580

45734581
// Generate the branch instructions
45744582
let ctx = asm.ctx;
4575-
gen_branch(
4576-
jit,
4583+
jit.gen_branch(
45774584
asm,
45784585
jump_block,
45794586
&ctx,
45804587
Some(next_block),
45814588
Some(&ctx),
45824589
BranchGenFn::BranchIf(Cell::new(BranchShape::Default)),
4583-
)?;
4590+
);
45844591
}
45854592

45864593
Some(EndBlock)
@@ -4626,15 +4633,14 @@ fn gen_branchunless(
46264633

46274634
// Generate the branch instructions
46284635
let ctx = asm.ctx;
4629-
gen_branch(
4630-
jit,
4636+
jit.gen_branch(
46314637
asm,
46324638
jump_block,
46334639
&ctx,
46344640
Some(next_block),
46354641
Some(&ctx),
46364642
BranchGenFn::BranchUnless(Cell::new(BranchShape::Default)),
4637-
)?;
4643+
);
46384644
}
46394645

46404646
Some(EndBlock)
@@ -4677,15 +4683,14 @@ fn gen_branchnil(
46774683
asm.cmp(val_opnd, Opnd::UImm(Qnil.into()));
46784684
// Generate the branch instructions
46794685
let ctx = asm.ctx;
4680-
gen_branch(
4681-
jit,
4686+
jit.gen_branch(
46824687
asm,
46834688
jump_block,
46844689
&ctx,
46854690
Some(next_block),
46864691
Some(&ctx),
46874692
BranchGenFn::BranchNil(Cell::new(BranchShape::Default)),
4688-
)?;
4693+
);
46894694
}
46904695

46914696
Some(EndBlock)
@@ -8004,19 +8009,14 @@ fn gen_send_iseq(
80048009
return_asm.ctx.set_as_return_landing();
80058010

80068011
// Write the JIT return address on the callee frame
8007-
if gen_branch(
8008-
jit,
8012+
jit.gen_branch(
80098013
asm,
80108014
return_block,
80118015
&return_asm.ctx,
80128016
None,
80138017
None,
80148018
BranchGenFn::JITReturn,
8015-
).is_none() {
8016-
// Returning None here would have send_dynamic() code following incomplete
8017-
// send code. Abandon the block instead.
8018-
jit.block_abandoned = true;
8019-
}
8019+
);
80208020

80218021
// ec->cfp is updated after cfp->jit_return for rb_profile_frames() safety
80228022
asm_comment!(asm, "switch to new CFP");
@@ -8711,8 +8711,7 @@ fn gen_send_general(
87118711

87128712
// Defer compilation so we can specialize on class of receiver
87138713
if !jit.at_compile_target() {
8714-
defer_compilation(jit, asm);
8715-
return Some(EndBlock);
8714+
return jit.defer_compilation(asm);
87168715
}
87178716

87188717
let ci_flags = unsafe { vm_ci_flag(ci) };
@@ -9275,8 +9274,7 @@ fn gen_invokeblock_specialized(
92759274
cd: *const rb_call_data,
92769275
) -> Option<CodegenStatus> {
92779276
if !jit.at_compile_target() {
9278-
defer_compilation(jit, asm);
9279-
return Some(EndBlock);
9277+
return jit.defer_compilation(asm);
92809278
}
92819279

92829280
// Fallback to dynamic dispatch if this callsite is megamorphic
@@ -9438,8 +9436,7 @@ fn gen_invokesuper_specialized(
94389436
) -> Option<CodegenStatus> {
94399437
// Defer compilation so we can specialize on class of receiver
94409438
if !jit.at_compile_target() {
9441-
defer_compilation(jit, asm);
9442-
return Some(EndBlock);
9439+
return jit.defer_compilation(asm);
94439440
}
94449441

94459442
// Handle the last two branches of vm_caller_setup_arg_block
@@ -9672,8 +9669,7 @@ fn gen_objtostring(
96729669
asm: &mut Assembler,
96739670
) -> Option<CodegenStatus> {
96749671
if !jit.at_compile_target() {
9675-
defer_compilation(jit, asm);
9676-
return Some(EndBlock);
9672+
return jit.defer_compilation(asm);
96779673
}
96789674

96799675
let recv = asm.stack_opnd(0);
@@ -10014,8 +10010,7 @@ fn gen_getblockparamproxy(
1001410010
asm: &mut Assembler,
1001510011
) -> Option<CodegenStatus> {
1001610012
if !jit.at_compile_target() {
10017-
defer_compilation(jit, asm);
10018-
return Some(EndBlock);
10013+
return jit.defer_compilation(asm);
1001910014
}
1002010015

1002110016
// EP level

yjit/src/core.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3916,10 +3916,7 @@ pub fn gen_direct_jump(jit: &mut JITState, ctx: &Context, target0: BlockId, asm:
39163916
}
39173917

39183918
/// Create a stub to force the code up to this point to be executed
3919-
pub fn defer_compilation(
3920-
jit: &mut JITState,
3921-
asm: &mut Assembler,
3922-
) {
3919+
pub fn defer_compilation(jit: &mut JITState, asm: &mut Assembler) -> Result<(), ()> {
39233920
if asm.ctx.is_deferred() {
39243921
panic!("Double defer!");
39253922
}
@@ -3936,7 +3933,7 @@ pub fn defer_compilation(
39363933
};
39373934

39383935
// Likely a stub since the context is marked as deferred().
3939-
let target0_address = branch.set_target(0, blockid, &next_ctx, jit);
3936+
let dst_addr = branch.set_target(0, blockid, &next_ctx, jit).ok_or(())?;
39403937

39413938
// Pad the block if it has the potential to be invalidated. This must be
39423939
// done before gen_fn() in case the jump is overwritten by a fallthrough.
@@ -3947,9 +3944,7 @@ pub fn defer_compilation(
39473944
// Call the branch generation function
39483945
asm_comment!(asm, "defer_compilation");
39493946
asm.mark_branch_start(&branch);
3950-
if let Some(dst_addr) = target0_address {
3951-
branch.gen_fn.call(asm, Target::CodePtr(dst_addr), None);
3952-
}
3947+
branch.gen_fn.call(asm, Target::CodePtr(dst_addr), None);
39533948
asm.mark_branch_end(&branch);
39543949

39553950
// If the block we're deferring from is empty
@@ -3958,6 +3953,8 @@ pub fn defer_compilation(
39583953
}
39593954

39603955
incr_counter!(defer_count);
3956+
3957+
Ok(())
39613958
}
39623959

39633960
/// Remove a block from the live control flow graph.

0 commit comments

Comments
 (0)