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]: cb661d7d82984cdb54485ea3f4af01ac21960882
Fixup: [1]
This commit is contained in:
Alan Wu 2024-11-08 14:09:55 -05:00 committed by GitHub
parent 6767117b07
commit dccfab0c53
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
Notes: git 2024-11-08 19:10:14 +00:00
Merged: https://github.com/ruby/ruby/pull/12035

Merged-By: XrXr
2 changed files with 64 additions and 72 deletions

View File

@ -195,6 +195,34 @@ impl<'a> JITState<'a> {
self.outlined_code_block self.outlined_code_block
} }
/// Leave a code stub to re-enter the compiler at runtime when the compiling program point is
/// reached. Should always be used in tail position like `return jit.defer_compilation(asm);`.
#[must_use]
fn defer_compilation(&mut self, asm: &mut Assembler) -> Option<CodegenStatus> {
if crate::core::defer_compilation(self, asm).is_err() {
// If we can't leave a stub, the block isn't usable and we have to bail.
self.block_abandoned = true;
}
Some(EndBlock)
}
/// Generate a branch with either end possibly stubbed out
fn gen_branch(
&mut self,
asm: &mut Assembler,
target0: BlockId,
ctx0: &Context,
target1: Option<BlockId>,
ctx1: Option<&Context>,
gen_fn: BranchGenFn,
) {
if crate::core::gen_branch(self, asm, target0, ctx0, target1, ctx1, gen_fn).is_none() {
// If we can't meet the request for a branch, the code is
// essentially corrupt and we have to discard the block.
self.block_abandoned = true;
}
}
/// Return true if the current ISEQ could escape an environment. /// Return true if the current ISEQ could escape an environment.
/// ///
/// As of vm_push_frame(), EP is always equal to BP. However, after pushing /// As of vm_push_frame(), EP is always equal to BP. However, after pushing
@ -1538,8 +1566,7 @@ fn fuse_putobject_opt_ltlt(
return None; return None;
} }
if !jit.at_compile_target() { if !jit.at_compile_target() {
defer_compilation(jit, asm); return jit.defer_compilation(asm);
return Some(EndBlock);
} }
let lhs = jit.peek_at_stack(&asm.ctx, 0); let lhs = jit.peek_at_stack(&asm.ctx, 0);
@ -1661,8 +1688,7 @@ fn gen_opt_plus(
let two_fixnums = match asm.ctx.two_fixnums_on_stack(jit) { let two_fixnums = match asm.ctx.two_fixnums_on_stack(jit) {
Some(two_fixnums) => two_fixnums, Some(two_fixnums) => two_fixnums,
None => { None => {
defer_compilation(jit, asm); return jit.defer_compilation(asm);
return Some(EndBlock);
} }
}; };
@ -1802,8 +1828,7 @@ fn gen_splatkw(
) -> Option<CodegenStatus> { ) -> Option<CodegenStatus> {
// Defer compilation so we can specialize on a runtime hash operand // Defer compilation so we can specialize on a runtime hash operand
if !jit.at_compile_target() { if !jit.at_compile_target() {
defer_compilation(jit, asm); return jit.defer_compilation(asm);
return Some(EndBlock);
} }
let comptime_hash = jit.peek_at_stack(&asm.ctx, 1); let comptime_hash = jit.peek_at_stack(&asm.ctx, 1);
@ -2176,8 +2201,7 @@ fn gen_expandarray(
// Defer compilation so we can specialize on a runtime `self` // Defer compilation so we can specialize on a runtime `self`
if !jit.at_compile_target() { if !jit.at_compile_target() {
defer_compilation(jit, asm); return jit.defer_compilation(asm);
return Some(EndBlock);
} }
let comptime_recv = jit.peek_at_stack(&asm.ctx, 0); let comptime_recv = jit.peek_at_stack(&asm.ctx, 0);
@ -2718,10 +2742,7 @@ fn jit_chain_guard(
idx: jit.insn_idx, idx: jit.insn_idx,
}; };
// Bail if we can't generate the branch jit.gen_branch(asm, bid, &deeper, None, None, target0_gen_fn);
if gen_branch(jit, asm, bid, &deeper, None, None, target0_gen_fn).is_none() {
jit.block_abandoned = true;
}
} else { } else {
target0_gen_fn.call(asm, Target::side_exit(counter), None); target0_gen_fn.call(asm, Target::side_exit(counter), None);
} }
@ -2895,8 +2916,7 @@ fn gen_getinstancevariable(
) -> Option<CodegenStatus> { ) -> Option<CodegenStatus> {
// Defer compilation so we can specialize on a runtime `self` // Defer compilation so we can specialize on a runtime `self`
if !jit.at_compile_target() { if !jit.at_compile_target() {
defer_compilation(jit, asm); return jit.defer_compilation(asm);
return Some(EndBlock);
} }
let ivar_name = jit.get_arg(0).as_u64(); let ivar_name = jit.get_arg(0).as_u64();
@ -2959,8 +2979,7 @@ fn gen_setinstancevariable(
) -> Option<CodegenStatus> { ) -> Option<CodegenStatus> {
// Defer compilation so we can specialize on a runtime `self` // Defer compilation so we can specialize on a runtime `self`
if !jit.at_compile_target() { if !jit.at_compile_target() {
defer_compilation(jit, asm); return jit.defer_compilation(asm);
return Some(EndBlock);
} }
let ivar_name = jit.get_arg(0).as_u64(); let ivar_name = jit.get_arg(0).as_u64();
@ -3270,8 +3289,7 @@ fn gen_definedivar(
) -> Option<CodegenStatus> { ) -> Option<CodegenStatus> {
// Defer compilation so we can specialize base on a runtime receiver // Defer compilation so we can specialize base on a runtime receiver
if !jit.at_compile_target() { if !jit.at_compile_target() {
defer_compilation(jit, asm); return jit.defer_compilation(asm);
return Some(EndBlock);
} }
let ivar_name = jit.get_arg(0).as_u64(); let ivar_name = jit.get_arg(0).as_u64();
@ -3500,8 +3518,7 @@ fn gen_fixnum_cmp(
Some(two_fixnums) => two_fixnums, Some(two_fixnums) => two_fixnums,
None => { None => {
// Defer compilation so we can specialize based on a runtime receiver // Defer compilation so we can specialize based on a runtime receiver
defer_compilation(jit, asm); return jit.defer_compilation(asm);
return Some(EndBlock);
} }
}; };
@ -3680,8 +3697,7 @@ fn gen_opt_eq(
Some(specialized) => specialized, Some(specialized) => specialized,
None => { None => {
// Defer compilation so we can specialize base on a runtime receiver // Defer compilation so we can specialize base on a runtime receiver
defer_compilation(jit, asm); return jit.defer_compilation(asm);
return Some(EndBlock);
} }
}; };
@ -3718,8 +3734,7 @@ fn gen_opt_aref(
// Defer compilation so we can specialize base on a runtime receiver // Defer compilation so we can specialize base on a runtime receiver
if !jit.at_compile_target() { if !jit.at_compile_target() {
defer_compilation(jit, asm); return jit.defer_compilation(asm);
return Some(EndBlock);
} }
// Specialize base on compile time values // Specialize base on compile time values
@ -3819,8 +3834,7 @@ fn gen_opt_aset(
) -> Option<CodegenStatus> { ) -> Option<CodegenStatus> {
// Defer compilation so we can specialize on a runtime `self` // Defer compilation so we can specialize on a runtime `self`
if !jit.at_compile_target() { if !jit.at_compile_target() {
defer_compilation(jit, asm); return jit.defer_compilation(asm);
return Some(EndBlock);
} }
let comptime_recv = jit.peek_at_stack(&asm.ctx, 2); let comptime_recv = jit.peek_at_stack(&asm.ctx, 2);
@ -3951,8 +3965,7 @@ fn gen_opt_and(
Some(two_fixnums) => two_fixnums, Some(two_fixnums) => two_fixnums,
None => { None => {
// Defer compilation so we can specialize on a runtime `self` // Defer compilation so we can specialize on a runtime `self`
defer_compilation(jit, asm); return jit.defer_compilation(asm);
return Some(EndBlock);
} }
}; };
@ -3990,8 +4003,7 @@ fn gen_opt_or(
Some(two_fixnums) => two_fixnums, Some(two_fixnums) => two_fixnums,
None => { None => {
// Defer compilation so we can specialize on a runtime `self` // Defer compilation so we can specialize on a runtime `self`
defer_compilation(jit, asm); return jit.defer_compilation(asm);
return Some(EndBlock);
} }
}; };
@ -4029,8 +4041,7 @@ fn gen_opt_minus(
Some(two_fixnums) => two_fixnums, Some(two_fixnums) => two_fixnums,
None => { None => {
// Defer compilation so we can specialize on a runtime `self` // Defer compilation so we can specialize on a runtime `self`
defer_compilation(jit, asm); return jit.defer_compilation(asm);
return Some(EndBlock);
} }
}; };
@ -4069,8 +4080,7 @@ fn gen_opt_mult(
let two_fixnums = match asm.ctx.two_fixnums_on_stack(jit) { let two_fixnums = match asm.ctx.two_fixnums_on_stack(jit) {
Some(two_fixnums) => two_fixnums, Some(two_fixnums) => two_fixnums,
None => { None => {
defer_compilation(jit, asm); return jit.defer_compilation(asm);
return Some(EndBlock);
} }
}; };
@ -4121,8 +4131,7 @@ fn gen_opt_mod(
Some(two_fixnums) => two_fixnums, Some(two_fixnums) => two_fixnums,
None => { None => {
// Defer compilation so we can specialize on a runtime `self` // Defer compilation so we can specialize on a runtime `self`
defer_compilation(jit, asm); return jit.defer_compilation(asm);
return Some(EndBlock);
} }
}; };
@ -4459,8 +4468,7 @@ fn gen_opt_case_dispatch(
// hash lookup, at least for small hashes, but it's worth revisiting this // hash lookup, at least for small hashes, but it's worth revisiting this
// assumption in the future. // assumption in the future.
if !jit.at_compile_target() { if !jit.at_compile_target() {
defer_compilation(jit, asm); return jit.defer_compilation(asm);
return Some(EndBlock);
} }
let case_hash = jit.get_arg(0); let case_hash = jit.get_arg(0);
@ -4572,15 +4580,14 @@ fn gen_branchif(
// Generate the branch instructions // Generate the branch instructions
let ctx = asm.ctx; let ctx = asm.ctx;
gen_branch( jit.gen_branch(
jit,
asm, asm,
jump_block, jump_block,
&ctx, &ctx,
Some(next_block), Some(next_block),
Some(&ctx), Some(&ctx),
BranchGenFn::BranchIf(Cell::new(BranchShape::Default)), BranchGenFn::BranchIf(Cell::new(BranchShape::Default)),
)?; );
} }
Some(EndBlock) Some(EndBlock)
@ -4626,15 +4633,14 @@ fn gen_branchunless(
// Generate the branch instructions // Generate the branch instructions
let ctx = asm.ctx; let ctx = asm.ctx;
gen_branch( jit.gen_branch(
jit,
asm, asm,
jump_block, jump_block,
&ctx, &ctx,
Some(next_block), Some(next_block),
Some(&ctx), Some(&ctx),
BranchGenFn::BranchUnless(Cell::new(BranchShape::Default)), BranchGenFn::BranchUnless(Cell::new(BranchShape::Default)),
)?; );
} }
Some(EndBlock) Some(EndBlock)
@ -4677,15 +4683,14 @@ fn gen_branchnil(
asm.cmp(val_opnd, Opnd::UImm(Qnil.into())); asm.cmp(val_opnd, Opnd::UImm(Qnil.into()));
// Generate the branch instructions // Generate the branch instructions
let ctx = asm.ctx; let ctx = asm.ctx;
gen_branch( jit.gen_branch(
jit,
asm, asm,
jump_block, jump_block,
&ctx, &ctx,
Some(next_block), Some(next_block),
Some(&ctx), Some(&ctx),
BranchGenFn::BranchNil(Cell::new(BranchShape::Default)), BranchGenFn::BranchNil(Cell::new(BranchShape::Default)),
)?; );
} }
Some(EndBlock) Some(EndBlock)
@ -8004,19 +8009,14 @@ fn gen_send_iseq(
return_asm.ctx.set_as_return_landing(); return_asm.ctx.set_as_return_landing();
// Write the JIT return address on the callee frame // Write the JIT return address on the callee frame
if gen_branch( jit.gen_branch(
jit,
asm, asm,
return_block, return_block,
&return_asm.ctx, &return_asm.ctx,
None, None,
None, None,
BranchGenFn::JITReturn, BranchGenFn::JITReturn,
).is_none() { );
// Returning None here would have send_dynamic() code following incomplete
// send code. Abandon the block instead.
jit.block_abandoned = true;
}
// ec->cfp is updated after cfp->jit_return for rb_profile_frames() safety // ec->cfp is updated after cfp->jit_return for rb_profile_frames() safety
asm_comment!(asm, "switch to new CFP"); asm_comment!(asm, "switch to new CFP");
@ -8711,8 +8711,7 @@ fn gen_send_general(
// Defer compilation so we can specialize on class of receiver // Defer compilation so we can specialize on class of receiver
if !jit.at_compile_target() { if !jit.at_compile_target() {
defer_compilation(jit, asm); return jit.defer_compilation(asm);
return Some(EndBlock);
} }
let ci_flags = unsafe { vm_ci_flag(ci) }; let ci_flags = unsafe { vm_ci_flag(ci) };
@ -9275,8 +9274,7 @@ fn gen_invokeblock_specialized(
cd: *const rb_call_data, cd: *const rb_call_data,
) -> Option<CodegenStatus> { ) -> Option<CodegenStatus> {
if !jit.at_compile_target() { if !jit.at_compile_target() {
defer_compilation(jit, asm); return jit.defer_compilation(asm);
return Some(EndBlock);
} }
// Fallback to dynamic dispatch if this callsite is megamorphic // Fallback to dynamic dispatch if this callsite is megamorphic
@ -9438,8 +9436,7 @@ fn gen_invokesuper_specialized(
) -> Option<CodegenStatus> { ) -> Option<CodegenStatus> {
// Defer compilation so we can specialize on class of receiver // Defer compilation so we can specialize on class of receiver
if !jit.at_compile_target() { if !jit.at_compile_target() {
defer_compilation(jit, asm); return jit.defer_compilation(asm);
return Some(EndBlock);
} }
// Handle the last two branches of vm_caller_setup_arg_block // Handle the last two branches of vm_caller_setup_arg_block
@ -9672,8 +9669,7 @@ fn gen_objtostring(
asm: &mut Assembler, asm: &mut Assembler,
) -> Option<CodegenStatus> { ) -> Option<CodegenStatus> {
if !jit.at_compile_target() { if !jit.at_compile_target() {
defer_compilation(jit, asm); return jit.defer_compilation(asm);
return Some(EndBlock);
} }
let recv = asm.stack_opnd(0); let recv = asm.stack_opnd(0);
@ -10014,8 +10010,7 @@ fn gen_getblockparamproxy(
asm: &mut Assembler, asm: &mut Assembler,
) -> Option<CodegenStatus> { ) -> Option<CodegenStatus> {
if !jit.at_compile_target() { if !jit.at_compile_target() {
defer_compilation(jit, asm); return jit.defer_compilation(asm);
return Some(EndBlock);
} }
// EP level // EP level

View File

@ -3916,10 +3916,7 @@ pub fn gen_direct_jump(jit: &mut JITState, ctx: &Context, target0: BlockId, asm:
} }
/// Create a stub to force the code up to this point to be executed /// Create a stub to force the code up to this point to be executed
pub fn defer_compilation( pub fn defer_compilation(jit: &mut JITState, asm: &mut Assembler) -> Result<(), ()> {
jit: &mut JITState,
asm: &mut Assembler,
) {
if asm.ctx.is_deferred() { if asm.ctx.is_deferred() {
panic!("Double defer!"); panic!("Double defer!");
} }
@ -3936,7 +3933,7 @@ pub fn defer_compilation(
}; };
// Likely a stub since the context is marked as deferred(). // Likely a stub since the context is marked as deferred().
let target0_address = branch.set_target(0, blockid, &next_ctx, jit); let dst_addr = branch.set_target(0, blockid, &next_ctx, jit).ok_or(())?;
// Pad the block if it has the potential to be invalidated. This must be // Pad the block if it has the potential to be invalidated. This must be
// done before gen_fn() in case the jump is overwritten by a fallthrough. // done before gen_fn() in case the jump is overwritten by a fallthrough.
@ -3947,9 +3944,7 @@ pub fn defer_compilation(
// Call the branch generation function // Call the branch generation function
asm_comment!(asm, "defer_compilation"); asm_comment!(asm, "defer_compilation");
asm.mark_branch_start(&branch); asm.mark_branch_start(&branch);
if let Some(dst_addr) = target0_address {
branch.gen_fn.call(asm, Target::CodePtr(dst_addr), None); branch.gen_fn.call(asm, Target::CodePtr(dst_addr), None);
}
asm.mark_branch_end(&branch); asm.mark_branch_end(&branch);
// If the block we're deferring from is empty // If the block we're deferring from is empty
@ -3958,6 +3953,8 @@ pub fn defer_compilation(
} }
incr_counter!(defer_count); incr_counter!(defer_count);
Ok(())
} }
/// Remove a block from the live control flow graph. /// Remove a block from the live control flow graph.