Skip to content

Commit fa348b6

Browse files
committed
ZJIT: Side-exit on unknown instructions
Don't abort the entire compilation. Fix Shopify#700
1 parent 6b2b379 commit fa348b6

File tree

2 files changed

+36
-30
lines changed

2 files changed

+36
-30
lines changed

zjit/src/codegen.rs

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -281,11 +281,15 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Optio
281281
// Compile all instructions
282282
for &insn_id in block.insns() {
283283
let insn = function.find(insn_id);
284-
if gen_insn(cb, &mut jit, &mut asm, function, insn_id, &insn).is_none() {
285-
debug!("Failed to compile insn: {insn_id} {insn}");
284+
if let Err(last_snapshot) = gen_insn(cb, &mut jit, &mut asm, function, insn_id, &insn) {
285+
debug!("ZJIT: gen_function: Failed to compile insn: {insn_id} {insn}. Generating side-exit.");
286286
incr_counter!(failed_gen_insn);
287-
return None;
288-
}
287+
gen_side_exit(&mut jit, &mut asm, &SideExitReason::UnhandledInstruction(insn_id), &function.frame_state(last_snapshot));
288+
// Don't bother generating code after a side-exit. We won't run it.
289+
// TODO(max): Generate ud2 or equivalent.
290+
break;
291+
};
292+
// It's fine; we generated the instruction
289293
}
290294
// Make sure the last patch point has enough space to insert a jump
291295
asm.pad_patch_point();
@@ -316,7 +320,7 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Optio
316320
}
317321

318322
/// Compile an instruction
319-
fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, function: &Function, insn_id: InsnId, insn: &Insn) -> Option<()> {
323+
fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, function: &Function, insn_id: InsnId, insn: &Insn) -> Result<(), InsnId> {
320324
// Convert InsnId to lir::Opnd
321325
macro_rules! opnd {
322326
($insn_id:ident) => {
@@ -334,7 +338,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
334338

335339
macro_rules! no_output {
336340
($call:expr) => {
337-
{ let () = $call; return Some(()); }
341+
{ let () = $call; return Ok(()); }
338342
};
339343
}
340344

@@ -344,19 +348,20 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
344348

345349
let out_opnd = match insn {
346350
Insn::Const { val: Const::Value(val) } => gen_const(*val),
351+
Insn::Const { .. } => panic!("Unexpected Const in gen_insn: {insn}"),
347352
Insn::NewArray { elements, state } => gen_new_array(asm, opnds!(elements), &function.frame_state(*state)),
348353
Insn::NewHash { elements, state } => gen_new_hash(jit, asm, elements, &function.frame_state(*state)),
349354
Insn::NewRange { low, high, flag, state } => gen_new_range(jit, asm, opnd!(low), opnd!(high), *flag, &function.frame_state(*state)),
350355
Insn::ArrayDup { val, state } => gen_array_dup(asm, opnd!(val), &function.frame_state(*state)),
351356
Insn::StringCopy { val, chilled, state } => gen_string_copy(asm, opnd!(val), *chilled, &function.frame_state(*state)),
352357
// concatstrings shouldn't have 0 strings
353358
// If it happens we abort the compilation for now
354-
Insn::StringConcat { strings, .. } if strings.is_empty() => return None,
359+
Insn::StringConcat { strings, state, .. } if strings.is_empty() => return Err(*state),
355360
Insn::StringConcat { strings, state } => gen_string_concat(jit, asm, opnds!(strings), &function.frame_state(*state)),
356361
Insn::StringIntern { val, state } => gen_intern(asm, opnd!(val), &function.frame_state(*state)),
357362
Insn::ToRegexp { opt, values, state } => gen_toregexp(jit, asm, *opt, opnds!(values), &function.frame_state(*state)),
358363
Insn::Param { idx } => unreachable!("block.insns should not have Insn::Param({idx})"),
359-
Insn::Snapshot { .. } => return Some(()), // we don't need to do anything for this instruction at the moment
364+
Insn::Snapshot { .. } => return Ok(()), // we don't need to do anything for this instruction at the moment
360365
Insn::Jump(branch) => no_output!(gen_jump(jit, asm, branch)),
361366
Insn::IfTrue { val, target } => no_output!(gen_if_true(jit, asm, opnd!(val), target)),
362367
Insn::IfFalse { val, target } => no_output!(gen_if_false(jit, asm, opnd!(val), target)),
@@ -367,7 +372,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
367372
Insn::SendWithoutBlockDirect { cme, iseq, self_val, args, state, .. } => gen_send_without_block_direct(cb, jit, asm, *cme, *iseq, opnd!(self_val), opnds!(args), &function.frame_state(*state)),
368373
// Ensure we have enough room fit ec, self, and arguments
369374
// TODO remove this check when we have stack args (we can use Time.new to test it)
370-
Insn::InvokeBuiltin { bf, .. } if bf.argc + 2 > (C_ARG_OPNDS.len() as i32) => return None,
375+
Insn::InvokeBuiltin { bf, state, .. } if bf.argc + 2 > (C_ARG_OPNDS.len() as i32) => return Err(*state),
371376
Insn::InvokeBuiltin { bf, args, state, .. } => gen_invokebuiltin(jit, asm, &function.frame_state(*state), bf, opnds!(args)),
372377
Insn::Return { val } => no_output!(gen_return(asm, opnd!(val))),
373378
Insn::FixnumAdd { left, right, state } => gen_fixnum_add(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state)),
@@ -403,22 +408,20 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
403408
&Insn::IncrCounter(counter) => no_output!(gen_incr_counter(asm, counter)),
404409
Insn::ObjToString { val, cd, state, .. } => gen_objtostring(jit, asm, opnd!(val), *cd, &function.frame_state(*state)),
405410
&Insn::CheckInterrupts { state } => no_output!(gen_check_interrupts(jit, asm, &function.frame_state(state))),
406-
Insn::ArrayExtend { .. }
407-
| Insn::ArrayMax { .. }
408-
| Insn::ArrayPush { .. }
409-
| Insn::DefinedIvar { .. }
410-
| Insn::FixnumDiv { .. }
411-
| Insn::FixnumMod { .. }
412-
| Insn::HashDup { .. }
413-
| Insn::Send { .. }
414-
| Insn::Throw { .. }
415-
| Insn::ToArray { .. }
416-
| Insn::ToNewArray { .. }
417-
| Insn::Const { .. }
411+
&Insn::ArrayExtend { state, .. }
412+
| &Insn::ArrayMax { state, .. }
413+
| &Insn::ArrayPush { state, .. }
414+
| &Insn::DefinedIvar { state, .. }
415+
| &Insn::FixnumDiv { state, .. }
416+
| &Insn::FixnumMod { state, .. }
417+
| &Insn::HashDup { state, .. }
418+
| &Insn::Send { state, .. }
419+
| &Insn::Throw { state, .. }
420+
| &Insn::ToArray { state, .. }
421+
| &Insn::ToNewArray { state, .. }
418422
=> {
419-
debug!("ZJIT: gen_function: unexpected insn {insn}");
420423
incr_counter!(failed_gen_insn_unexpected);
421-
return None;
424+
return Err(state);
422425
}
423426
};
424427

@@ -427,7 +430,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
427430
// If the instruction has an output, remember it in jit.opnds
428431
jit.opnds[insn_id.0] = Some(out_opnd);
429432

430-
Some(())
433+
Ok(())
431434
}
432435

433436
/// Gets the EP of the ISeq of the containing method, or "local level".

zjit/src/hir.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,7 @@ pub enum SideExitReason {
431431
UnknownNewarraySend(vm_opt_newarray_send_type),
432432
UnknownCallType,
433433
UnknownOpcode(u32),
434+
UnhandledInstruction(InsnId),
434435
FixnumAddOverflow,
435436
FixnumSubOverflow,
436437
FixnumMultOverflow,
@@ -567,7 +568,7 @@ pub enum Insn {
567568
/// Control flow instructions
568569
Return { val: InsnId },
569570
/// Non-local control flow. See the throw YARV instruction
570-
Throw { throw_state: u32, val: InsnId },
571+
Throw { throw_state: u32, val: InsnId, state: InsnId },
571572

572573
/// Fixnum +, -, *, /, %, ==, !=, <, <=, >, >=, &, |
573574
FixnumAdd { left: InsnId, right: InsnId, state: InsnId },
@@ -854,7 +855,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
854855
Insn::AnyToString { val, str, .. } => { write!(f, "AnyToString {val}, str: {str}") },
855856
Insn::SideExit { reason, .. } => write!(f, "SideExit {reason}"),
856857
Insn::PutSpecialObject { value_type } => write!(f, "PutSpecialObject {value_type}"),
857-
Insn::Throw { throw_state, val } => {
858+
Insn::Throw { throw_state, val, .. } => {
858859
write!(f, "Throw ")?;
859860
match throw_state & VM_THROW_STATE_MASK {
860861
RUBY_TAG_NONE => write!(f, "TAG_NONE"),
@@ -1218,7 +1219,7 @@ impl Function {
12181219
}
12191220
},
12201221
&Return { val } => Return { val: find!(val) },
1221-
&Throw { throw_state, val } => Throw { throw_state, val: find!(val) },
1222+
&Throw { throw_state, val, state } => Throw { throw_state, val: find!(val), state },
12221223
&StringCopy { val, chilled, state } => StringCopy { val: find!(val), chilled, state },
12231224
&StringIntern { val, state } => StringIntern { val: find!(val), state: find!(state) },
12241225
&StringConcat { ref strings, state } => StringConcat { strings: find_vec!(strings), state: find!(state) },
@@ -1992,7 +1993,6 @@ impl Function {
19921993
worklist.push_back(state);
19931994
}
19941995
| &Insn::Return { val }
1995-
| &Insn::Throw { val, .. }
19961996
| &Insn::Test { val }
19971997
| &Insn::SetLocal { val, .. }
19981998
| &Insn::IsNil { val } =>
@@ -2040,7 +2040,9 @@ impl Function {
20402040
worklist.push_back(val);
20412041
worklist.extend(args);
20422042
}
2043-
&Insn::ArrayDup { val, state } | &Insn::HashDup { val, state } => {
2043+
&Insn::ArrayDup { val, state }
2044+
| &Insn::Throw { val, state, .. }
2045+
| &Insn::HashDup { val, state } => {
20442046
worklist.push_back(val);
20452047
worklist.push_back(state);
20462048
}
@@ -3261,7 +3263,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
32613263
break; // Don't enqueue the next block as a successor
32623264
}
32633265
YARVINSN_throw => {
3264-
fun.push_insn(block, Insn::Throw { throw_state: get_arg(pc, 0).as_u32(), val: state.stack_pop()? });
3266+
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
3267+
fun.push_insn(block, Insn::Throw { throw_state: get_arg(pc, 0).as_u32(), val: state.stack_pop()?, state: exit_id });
32653268
break; // Don't enqueue the next block as a successor
32663269
}
32673270

0 commit comments

Comments
 (0)