mirror of
https://github.com/ruby/ruby.git
synced 2025-08-15 13:39:04 +02:00
ZJIT: Implement patch points on BOP redefinition (#13850)
Co-authored-by: Max Bernstein <max@bernsteinbear.com>
This commit is contained in:
parent
214983bd9b
commit
b1828cbbfe
9 changed files with 238 additions and 57 deletions
|
@ -973,6 +973,28 @@ class TestZJIT < Test::Unit::TestCase
|
|||
}, call_threshold: 2
|
||||
end
|
||||
|
||||
def test_bop_redefinition
|
||||
assert_runs '[3, :+, 100]', %q{
|
||||
def test
|
||||
1 + 2
|
||||
end
|
||||
|
||||
test # profile opt_plus
|
||||
[test, Integer.class_eval { def +(_) = 100 }, test]
|
||||
}, call_threshold: 2
|
||||
end
|
||||
|
||||
def test_bop_redefinition_with_adjacent_patch_points
|
||||
assert_runs '[15, :+, 100]', %q{
|
||||
def test
|
||||
1 + 2 + 3 + 4 + 5
|
||||
end
|
||||
|
||||
test # profile opt_plus
|
||||
[test, Integer.class_eval { def +(_) = 100 }, test]
|
||||
}, call_threshold: 2
|
||||
end
|
||||
|
||||
def test_module_name_with_guard_passes
|
||||
assert_compiles '"Integer"', %q{
|
||||
def test(mod)
|
||||
|
|
|
@ -112,7 +112,7 @@ impl CodeBlock {
|
|||
}
|
||||
|
||||
/// Set the current write position from a pointer
|
||||
fn set_write_ptr(&mut self, code_ptr: CodePtr) {
|
||||
pub fn set_write_ptr(&mut self, code_ptr: CodePtr) {
|
||||
let pos = code_ptr.as_offset() - self.mem_block.borrow().start_ptr().as_offset();
|
||||
self.write_pos = pos.try_into().unwrap();
|
||||
}
|
||||
|
@ -248,6 +248,11 @@ impl CodeBlock {
|
|||
assert!(self.label_refs.is_empty());
|
||||
}
|
||||
|
||||
/// Convert a Label to CodePtr
|
||||
pub fn resolve_label(&self, label: Label) -> CodePtr {
|
||||
self.get_ptr(self.label_addrs[label.0])
|
||||
}
|
||||
|
||||
pub fn clear_labels(&mut self) {
|
||||
self.label_addrs.clear();
|
||||
self.label_names.clear();
|
||||
|
|
|
@ -891,6 +891,9 @@ impl Assembler
|
|||
// Buffered list of PosMarker callbacks to fire if codegen is successful
|
||||
let mut pos_markers: Vec<(usize, CodePtr)> = vec![];
|
||||
|
||||
// The write_pos for the last Insn::PatchPoint, if any
|
||||
let mut last_patch_pos: Option<usize> = None;
|
||||
|
||||
// For each instruction
|
||||
let mut insn_idx: usize = 0;
|
||||
while let Some(insn) = self.insns.get(insn_idx) {
|
||||
|
@ -1211,6 +1214,16 @@ impl Assembler
|
|||
Insn::Jonz(opnd, target) => {
|
||||
emit_cmp_zero_jump(cb, opnd.into(), false, target.clone());
|
||||
},
|
||||
Insn::PatchPoint(_) |
|
||||
Insn::PadPatchPoint => {
|
||||
// If patch points are too close to each other or the end of the block, fill nop instructions
|
||||
if let Some(last_patch_pos) = last_patch_pos {
|
||||
while cb.get_write_pos().saturating_sub(last_patch_pos) < cb.jmp_ptr_bytes() && !cb.has_dropped_bytes() {
|
||||
nop(cb);
|
||||
}
|
||||
}
|
||||
last_patch_pos = Some(cb.get_write_pos());
|
||||
},
|
||||
Insn::IncrCounter { mem: _, value: _ } => {
|
||||
/*
|
||||
let label = cb.new_label("incr_counter_loop".to_string());
|
||||
|
|
|
@ -276,10 +276,17 @@ pub enum Target
|
|||
{
|
||||
/// Pointer to a piece of ZJIT-generated code
|
||||
CodePtr(CodePtr),
|
||||
// Side exit with a counter
|
||||
SideExit { pc: *const VALUE, stack: Vec<Opnd>, locals: Vec<Opnd>, c_stack_bytes: usize },
|
||||
/// A label within the generated code
|
||||
Label(Label),
|
||||
/// Side exit to the interpreter
|
||||
SideExit {
|
||||
pc: *const VALUE,
|
||||
stack: Vec<Opnd>,
|
||||
locals: Vec<Opnd>,
|
||||
c_stack_bytes: usize,
|
||||
// Some if the side exit should write this label. We use it for patch points.
|
||||
label: Option<Label>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Target
|
||||
|
@ -484,6 +491,14 @@ pub enum Insn {
|
|||
// binary OR operation.
|
||||
Or { left: Opnd, right: Opnd, out: Opnd },
|
||||
|
||||
/// Patch point that will be rewritten to a jump to a side exit on invalidation.
|
||||
PatchPoint(Target),
|
||||
|
||||
/// Make sure the last PatchPoint has enough space to insert a jump.
|
||||
/// We insert this instruction at the end of each block so that the jump
|
||||
/// will not overwrite the next block or a side exit.
|
||||
PadPatchPoint,
|
||||
|
||||
// Mark a position in the generated code
|
||||
PosMarker(PosMarkerFn),
|
||||
|
||||
|
@ -541,7 +556,8 @@ impl Insn {
|
|||
Insn::Joz(_, target) |
|
||||
Insn::Jonz(_, target) |
|
||||
Insn::Label(target) |
|
||||
Insn::LeaJumpTarget { target, .. } => {
|
||||
Insn::LeaJumpTarget { target, .. } |
|
||||
Insn::PatchPoint(target) => {
|
||||
Some(target)
|
||||
}
|
||||
_ => None,
|
||||
|
@ -603,6 +619,8 @@ impl Insn {
|
|||
Insn::Mov { .. } => "Mov",
|
||||
Insn::Not { .. } => "Not",
|
||||
Insn::Or { .. } => "Or",
|
||||
Insn::PatchPoint(_) => "PatchPoint",
|
||||
Insn::PadPatchPoint => "PadPatchPoint",
|
||||
Insn::PosMarker(_) => "PosMarker",
|
||||
Insn::RShift { .. } => "RShift",
|
||||
Insn::Store { .. } => "Store",
|
||||
|
@ -698,7 +716,8 @@ impl Insn {
|
|||
Insn::Joz(_, target) |
|
||||
Insn::Jonz(_, target) |
|
||||
Insn::Label(target) |
|
||||
Insn::LeaJumpTarget { target, .. } => Some(target),
|
||||
Insn::LeaJumpTarget { target, .. } |
|
||||
Insn::PatchPoint(target) => Some(target),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
@ -744,7 +763,8 @@ impl<'a> Iterator for InsnOpndIterator<'a> {
|
|||
Insn::JoMul(target) |
|
||||
Insn::Jz(target) |
|
||||
Insn::Label(target) |
|
||||
Insn::LeaJumpTarget { target, .. } => {
|
||||
Insn::LeaJumpTarget { target, .. } |
|
||||
Insn::PatchPoint(target) => {
|
||||
if let Target::SideExit { stack, locals, .. } = target {
|
||||
let stack_idx = self.idx;
|
||||
if stack_idx < stack.len() {
|
||||
|
@ -796,6 +816,7 @@ impl<'a> Iterator for InsnOpndIterator<'a> {
|
|||
Insn::CPushAll |
|
||||
Insn::FrameSetup |
|
||||
Insn::FrameTeardown |
|
||||
Insn::PadPatchPoint |
|
||||
Insn::PosMarker(_) => None,
|
||||
|
||||
Insn::CPopInto(opnd) |
|
||||
|
@ -898,7 +919,8 @@ impl<'a> InsnOpndMutIterator<'a> {
|
|||
Insn::JoMul(target) |
|
||||
Insn::Jz(target) |
|
||||
Insn::Label(target) |
|
||||
Insn::LeaJumpTarget { target, .. } => {
|
||||
Insn::LeaJumpTarget { target, .. } |
|
||||
Insn::PatchPoint(target) => {
|
||||
if let Target::SideExit { stack, locals, .. } = target {
|
||||
let stack_idx = self.idx;
|
||||
if stack_idx < stack.len() {
|
||||
|
@ -950,6 +972,7 @@ impl<'a> InsnOpndMutIterator<'a> {
|
|||
Insn::CPushAll |
|
||||
Insn::FrameSetup |
|
||||
Insn::FrameTeardown |
|
||||
Insn::PadPatchPoint |
|
||||
Insn::PosMarker(_) => None,
|
||||
|
||||
Insn::CPopInto(opnd) |
|
||||
|
@ -1780,8 +1803,13 @@ impl Assembler
|
|||
for (idx, target) in targets {
|
||||
// Compile a side exit. Note that this is past the split pass and alloc_regs(),
|
||||
// so you can't use a VReg or an instruction that needs to be split.
|
||||
if let Target::SideExit { pc, stack, locals, c_stack_bytes } = target {
|
||||
let side_exit_label = self.new_label("side_exit".into());
|
||||
if let Target::SideExit { pc, stack, locals, c_stack_bytes, label } = target {
|
||||
asm_comment!(self, "side exit to the interpreter");
|
||||
let side_exit_label = if let Some(label) = label {
|
||||
Target::Label(label)
|
||||
} else {
|
||||
self.new_label("side_exit".into())
|
||||
};
|
||||
self.write_label(side_exit_label.clone());
|
||||
|
||||
// Load an operand that cannot be used as a source of Insn::Store
|
||||
|
@ -2164,7 +2192,14 @@ impl Assembler {
|
|||
out
|
||||
}
|
||||
|
||||
//pub fn pos_marker<F: FnMut(CodePtr)>(&mut self, marker_fn: F)
|
||||
pub fn patch_point(&mut self, target: Target) {
|
||||
self.push_insn(Insn::PatchPoint(target));
|
||||
}
|
||||
|
||||
pub fn pad_patch_point(&mut self) {
|
||||
self.push_insn(Insn::PadPatchPoint);
|
||||
}
|
||||
|
||||
pub fn pos_marker(&mut self, marker_fn: impl Fn(CodePtr, &CodeBlock) + 'static) {
|
||||
self.push_insn(Insn::PosMarker(Box::new(marker_fn)));
|
||||
}
|
||||
|
|
|
@ -443,6 +443,9 @@ impl Assembler
|
|||
// Buffered list of PosMarker callbacks to fire if codegen is successful
|
||||
let mut pos_markers: Vec<(usize, CodePtr)> = vec![];
|
||||
|
||||
// The write_pos for the last Insn::PatchPoint, if any
|
||||
let mut last_patch_pos: Option<usize> = None;
|
||||
|
||||
// For each instruction
|
||||
let mut insn_idx: usize = 0;
|
||||
while let Some(insn) = self.insns.get(insn_idx) {
|
||||
|
@ -759,6 +762,18 @@ impl Assembler
|
|||
|
||||
Insn::Joz(..) | Insn::Jonz(..) => unreachable!("Joz/Jonz should be unused for now"),
|
||||
|
||||
Insn::PatchPoint(_) |
|
||||
Insn::PadPatchPoint => {
|
||||
// If patch points are too close to each other or the end of the block, fill nop instructions
|
||||
if let Some(last_patch_pos) = last_patch_pos {
|
||||
let code_size = cb.get_write_pos().saturating_sub(last_patch_pos);
|
||||
if code_size < cb.jmp_ptr_bytes() {
|
||||
nop(cb, (cb.jmp_ptr_bytes() - code_size) as u32);
|
||||
}
|
||||
}
|
||||
last_patch_pos = Some(cb.get_write_pos());
|
||||
},
|
||||
|
||||
// Atomically increment a counter at a given memory location
|
||||
Insn::IncrCounter { mem, value } => {
|
||||
assert!(matches!(mem, Opnd::Mem(_)));
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::asm::Label;
|
||||
use crate::backend::current::{Reg, ALLOC_REGS};
|
||||
use crate::invariants::track_bop_assumption;
|
||||
use crate::gc::get_or_create_iseq_payload;
|
||||
|
@ -235,6 +236,8 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Optio
|
|||
return None;
|
||||
}
|
||||
}
|
||||
// Make sure the last patch point has enough space to insert a jump
|
||||
asm.pad_patch_point();
|
||||
}
|
||||
|
||||
if get_option!(dump_lir) {
|
||||
|
@ -296,7 +299,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
|
|||
Insn::Test { val } => gen_test(asm, opnd!(val))?,
|
||||
Insn::GuardType { val, guard_type, state } => gen_guard_type(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state))?,
|
||||
Insn::GuardBitEquals { val, expected, state } => gen_guard_bit_equals(jit, asm, opnd!(val), *expected, &function.frame_state(*state))?,
|
||||
Insn::PatchPoint(invariant) => return gen_patch_point(asm, invariant),
|
||||
Insn::PatchPoint { invariant, state } => return gen_patch_point(jit, asm, invariant, &function.frame_state(*state)),
|
||||
Insn::CCall { cfun, args, name: _, return_type: _, elidable: _ } => gen_ccall(asm, *cfun, opnds!(args))?,
|
||||
Insn::GetIvar { self_val, id, state: _ } => gen_getivar(asm, opnd!(self_val), *id),
|
||||
Insn::SetGlobal { id, val, state: _ } => return Some(gen_setglobal(asm, *id, opnd!(val))),
|
||||
|
@ -434,12 +437,18 @@ fn gen_invokebuiltin(asm: &mut Assembler, state: &FrameState, bf: &rb_builtin_fu
|
|||
}
|
||||
|
||||
/// Record a patch point that should be invalidated on a given invariant
|
||||
fn gen_patch_point(asm: &mut Assembler, invariant: &Invariant) -> Option<()> {
|
||||
fn gen_patch_point(jit: &mut JITState, asm: &mut Assembler, invariant: &Invariant, state: &FrameState) -> Option<()> {
|
||||
let label = asm.new_label("patch_point").unwrap_label();
|
||||
let invariant = invariant.clone();
|
||||
asm.pos_marker(move |code_ptr, _cb| {
|
||||
|
||||
// Compile a side exit. Fill nop instructions if the last patch point is too close.
|
||||
asm.patch_point(build_side_exit(jit, state, Some(label))?);
|
||||
// Remember the current address as a patch point
|
||||
asm.pos_marker(move |code_ptr, cb| {
|
||||
match invariant {
|
||||
Invariant::BOPRedefined { klass, bop } => {
|
||||
track_bop_assumption(klass, bop, code_ptr);
|
||||
let side_exit_ptr = cb.resolve_label(label);
|
||||
track_bop_assumption(klass, bop, code_ptr, side_exit_ptr);
|
||||
}
|
||||
_ => {
|
||||
debug!("ZJIT: gen_patch_point: unimplemented invariant {invariant:?}");
|
||||
|
@ -447,7 +456,6 @@ fn gen_patch_point(asm: &mut Assembler, invariant: &Invariant) -> Option<()> {
|
|||
}
|
||||
}
|
||||
});
|
||||
// TODO: Make sure patch points do not overlap with each other.
|
||||
Some(())
|
||||
}
|
||||
|
||||
|
@ -1110,8 +1118,13 @@ fn compile_iseq(iseq: IseqPtr) -> Option<Function> {
|
|||
Some(function)
|
||||
}
|
||||
|
||||
/// Build a Target::SideExit out of a FrameState
|
||||
/// Build a Target::SideExit for non-PatchPoint instructions
|
||||
fn side_exit(jit: &mut JITState, state: &FrameState) -> Option<Target> {
|
||||
build_side_exit(jit, state, None)
|
||||
}
|
||||
|
||||
/// Build a Target::SideExit out of a FrameState
|
||||
fn build_side_exit(jit: &mut JITState, state: &FrameState, label: Option<Label>) -> Option<Target> {
|
||||
let mut stack = Vec::new();
|
||||
for &insn_id in state.stack() {
|
||||
stack.push(jit.get_opnd(insn_id)?);
|
||||
|
@ -1127,6 +1140,7 @@ fn side_exit(jit: &mut JITState, state: &FrameState) -> Option<Target> {
|
|||
stack,
|
||||
locals,
|
||||
c_stack_bytes: jit.c_stack_bytes,
|
||||
label,
|
||||
};
|
||||
Some(target)
|
||||
}
|
||||
|
|
107
zjit/src/hir.rs
107
zjit/src/hir.rs
|
@ -548,7 +548,7 @@ pub enum Insn {
|
|||
|
||||
/// Generate no code (or padding if necessary) and insert a patch point
|
||||
/// that can be rewritten to a side exit when the Invariant is broken.
|
||||
PatchPoint(Invariant),
|
||||
PatchPoint { invariant: Invariant, state: InsnId },
|
||||
|
||||
/// Side-exit into the interpreter.
|
||||
SideExit { state: InsnId, reason: SideExitReason },
|
||||
|
@ -712,7 +712,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
|
|||
Insn::FixnumOr { left, right, .. } => { write!(f, "FixnumOr {left}, {right}") },
|
||||
Insn::GuardType { val, guard_type, .. } => { write!(f, "GuardType {val}, {}", guard_type.print(self.ptr_map)) },
|
||||
Insn::GuardBitEquals { val, expected, .. } => { write!(f, "GuardBitEquals {val}, {}", expected.print(self.ptr_map)) },
|
||||
Insn::PatchPoint(invariant) => { write!(f, "PatchPoint {}", invariant.print(self.ptr_map)) },
|
||||
Insn::PatchPoint { invariant, .. } => { write!(f, "PatchPoint {}", invariant.print(self.ptr_map)) },
|
||||
Insn::GetConstantPath { ic, .. } => { write!(f, "GetConstantPath {:p}", self.ptr_map.map_ptr(ic)) },
|
||||
Insn::CCall { cfun, args, name, return_type: _, elidable: _ } => {
|
||||
write!(f, "CCall {}@{:p}", name.contents_lossy(), self.ptr_map.map_ptr(cfun))?;
|
||||
|
@ -1362,9 +1362,9 @@ impl Function {
|
|||
if self.arguments_likely_fixnums(left, right, state) {
|
||||
if bop == BOP_NEQ {
|
||||
// For opt_neq, the interpreter checks that both neq and eq are unchanged.
|
||||
self.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_EQ }));
|
||||
self.push_insn(block, Insn::PatchPoint { invariant: Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_EQ }, state });
|
||||
}
|
||||
self.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop }));
|
||||
self.push_insn(block, Insn::PatchPoint { invariant: Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop }, state });
|
||||
let left = self.coerce_to_fixnum(block, left, state);
|
||||
let right = self.coerce_to_fixnum(block, right, state);
|
||||
let result = self.push_insn(block, f(left, right));
|
||||
|
@ -1375,11 +1375,11 @@ impl Function {
|
|||
}
|
||||
}
|
||||
|
||||
fn rewrite_if_frozen(&mut self, block: BlockId, orig_insn_id: InsnId, self_val: InsnId, klass: u32, bop: u32) {
|
||||
fn rewrite_if_frozen(&mut self, block: BlockId, orig_insn_id: InsnId, self_val: InsnId, klass: u32, bop: u32, state: InsnId) {
|
||||
let self_type = self.type_of(self_val);
|
||||
if let Some(obj) = self_type.ruby_object() {
|
||||
if obj.is_frozen() {
|
||||
self.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass, bop }));
|
||||
self.push_insn(block, Insn::PatchPoint { invariant: Invariant::BOPRedefined { klass, bop }, state });
|
||||
self.make_equal_to(orig_insn_id, self_val);
|
||||
return;
|
||||
}
|
||||
|
@ -1387,34 +1387,34 @@ impl Function {
|
|||
self.push_insn_id(block, orig_insn_id);
|
||||
}
|
||||
|
||||
fn try_rewrite_freeze(&mut self, block: BlockId, orig_insn_id: InsnId, self_val: InsnId) {
|
||||
fn try_rewrite_freeze(&mut self, block: BlockId, orig_insn_id: InsnId, self_val: InsnId, state: InsnId) {
|
||||
if self.is_a(self_val, types::StringExact) {
|
||||
self.rewrite_if_frozen(block, orig_insn_id, self_val, STRING_REDEFINED_OP_FLAG, BOP_FREEZE);
|
||||
self.rewrite_if_frozen(block, orig_insn_id, self_val, STRING_REDEFINED_OP_FLAG, BOP_FREEZE, state);
|
||||
} else if self.is_a(self_val, types::ArrayExact) {
|
||||
self.rewrite_if_frozen(block, orig_insn_id, self_val, ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE);
|
||||
self.rewrite_if_frozen(block, orig_insn_id, self_val, ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE, state);
|
||||
} else if self.is_a(self_val, types::HashExact) {
|
||||
self.rewrite_if_frozen(block, orig_insn_id, self_val, HASH_REDEFINED_OP_FLAG, BOP_FREEZE);
|
||||
self.rewrite_if_frozen(block, orig_insn_id, self_val, HASH_REDEFINED_OP_FLAG, BOP_FREEZE, state);
|
||||
} else {
|
||||
self.push_insn_id(block, orig_insn_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn try_rewrite_uminus(&mut self, block: BlockId, orig_insn_id: InsnId, self_val: InsnId) {
|
||||
fn try_rewrite_uminus(&mut self, block: BlockId, orig_insn_id: InsnId, self_val: InsnId, state: InsnId) {
|
||||
if self.is_a(self_val, types::StringExact) {
|
||||
self.rewrite_if_frozen(block, orig_insn_id, self_val, STRING_REDEFINED_OP_FLAG, BOP_UMINUS);
|
||||
self.rewrite_if_frozen(block, orig_insn_id, self_val, STRING_REDEFINED_OP_FLAG, BOP_UMINUS, state);
|
||||
} else {
|
||||
self.push_insn_id(block, orig_insn_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn try_rewrite_aref(&mut self, block: BlockId, orig_insn_id: InsnId, self_val: InsnId, idx_val: InsnId) {
|
||||
fn try_rewrite_aref(&mut self, block: BlockId, orig_insn_id: InsnId, self_val: InsnId, idx_val: InsnId, state: InsnId) {
|
||||
let self_type = self.type_of(self_val);
|
||||
let idx_type = self.type_of(idx_val);
|
||||
if self_type.is_subtype(types::ArrayExact) {
|
||||
if let Some(array_obj) = self_type.ruby_object() {
|
||||
if array_obj.is_frozen() {
|
||||
if let Some(idx) = idx_type.fixnum_value() {
|
||||
self.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: ARRAY_REDEFINED_OP_FLAG, bop: BOP_AREF }));
|
||||
self.push_insn(block, Insn::PatchPoint { invariant: Invariant::BOPRedefined { klass: ARRAY_REDEFINED_OP_FLAG, bop: BOP_AREF }, state });
|
||||
let val = unsafe { rb_yarv_ary_entry_internal(array_obj, idx) };
|
||||
let const_insn = self.push_insn(block, Insn::Const { val: Const::Value(val) });
|
||||
self.make_equal_to(orig_insn_id, const_insn);
|
||||
|
@ -1460,12 +1460,12 @@ impl Function {
|
|||
self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumAnd { left, right }, BOP_AND, self_val, args[0], state),
|
||||
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, state, .. } if method_name == "|" && args.len() == 1 =>
|
||||
self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumOr { left, right }, BOP_OR, self_val, args[0], state),
|
||||
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, .. } if method_name == "freeze" && args.len() == 0 =>
|
||||
self.try_rewrite_freeze(block, insn_id, self_val),
|
||||
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, .. } if method_name == "-@" && args.len() == 0 =>
|
||||
self.try_rewrite_uminus(block, insn_id, self_val),
|
||||
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, .. } if method_name == "[]" && args.len() == 1 =>
|
||||
self.try_rewrite_aref(block, insn_id, self_val, args[0]),
|
||||
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, state, .. } if method_name == "freeze" && args.len() == 0 =>
|
||||
self.try_rewrite_freeze(block, insn_id, self_val, state),
|
||||
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, state, .. } if method_name == "-@" && args.len() == 0 =>
|
||||
self.try_rewrite_uminus(block, insn_id, self_val, state),
|
||||
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, state, .. } if method_name == "[]" && args.len() == 1 =>
|
||||
self.try_rewrite_aref(block, insn_id, self_val, args[0], state),
|
||||
Insn::SendWithoutBlock { mut self_val, call_info, cd, args, state } => {
|
||||
let frame_state = self.frame_state(state);
|
||||
let (klass, guard_equal_to) = if let Some(klass) = self.type_of(self_val).runtime_exact_ruby_class() {
|
||||
|
@ -1493,7 +1493,7 @@ impl Function {
|
|||
// TODO(max): Allow non-iseq; cache cme
|
||||
self.push_insn_id(block, insn_id); continue;
|
||||
}
|
||||
self.push_insn(block, Insn::PatchPoint(Invariant::MethodRedefined { klass, method: mid }));
|
||||
self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid }, state });
|
||||
let iseq = unsafe { get_def_iseq_ptr((*cme).def) };
|
||||
if let Some(expected) = guard_equal_to {
|
||||
self_val = self.push_insn(block, Insn::GuardBitEquals { val: self_val, expected, state });
|
||||
|
@ -1501,7 +1501,7 @@ impl Function {
|
|||
let send_direct = self.push_insn(block, Insn::SendWithoutBlockDirect { self_val, call_info, cd, cme, iseq, args, state });
|
||||
self.make_equal_to(insn_id, send_direct);
|
||||
}
|
||||
Insn::GetConstantPath { ic, .. } => {
|
||||
Insn::GetConstantPath { ic, state, .. } => {
|
||||
let idlist: *const ID = unsafe { (*ic).segments };
|
||||
let ice = unsafe { (*ic).entry };
|
||||
if ice.is_null() {
|
||||
|
@ -1513,10 +1513,10 @@ impl Function {
|
|||
self.push_insn_id(block, insn_id); continue;
|
||||
}
|
||||
// Assume single-ractor mode.
|
||||
self.push_insn(block, Insn::PatchPoint(Invariant::SingleRactorMode));
|
||||
self.push_insn(block, Insn::PatchPoint { invariant: Invariant::SingleRactorMode, state });
|
||||
// Invalidate output code on any constant writes associated with constants
|
||||
// referenced after the PatchPoint.
|
||||
self.push_insn(block, Insn::PatchPoint(Invariant::StableConstantNames { idlist }));
|
||||
self.push_insn(block, Insn::PatchPoint { invariant: Invariant::StableConstantNames { idlist }, state });
|
||||
let replacement = self.push_insn(block, Insn::Const { val: Const::Value(unsafe { (*ice).value }) });
|
||||
self.make_equal_to(insn_id, replacement);
|
||||
}
|
||||
|
@ -1612,7 +1612,7 @@ impl Function {
|
|||
// Filter for simple call sites (i.e. no splats etc.)
|
||||
if ci_flags & VM_CALL_ARGS_SIMPLE != 0 {
|
||||
// Commit to the replacement. Put PatchPoint.
|
||||
fun.push_insn(block, Insn::PatchPoint(Invariant::MethodRedefined { klass: recv_class, method: method_id }));
|
||||
fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass: recv_class, method: method_id }, state });
|
||||
if let Some(guard_type) = guard_type {
|
||||
// Guard receiver class
|
||||
self_val = fun.push_insn(block, Insn::GuardType { val: self_val, guard_type, state });
|
||||
|
@ -1787,11 +1787,11 @@ impl Function {
|
|||
match insn {
|
||||
&Insn::Const { .. }
|
||||
| &Insn::Param { .. }
|
||||
| &Insn::PatchPoint(..)
|
||||
| &Insn::GetLocal { .. }
|
||||
| &Insn::PutSpecialObject { .. } =>
|
||||
{}
|
||||
&Insn::GetConstantPath { ic: _, state } => {
|
||||
&Insn::PatchPoint { state, .. }
|
||||
| &Insn::GetConstantPath { ic: _, state } => {
|
||||
worklist.push_back(state);
|
||||
}
|
||||
&Insn::ArrayMax { ref elements, state }
|
||||
|
@ -2611,7 +2611,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
|
|||
break; // End the block
|
||||
},
|
||||
};
|
||||
fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: ARRAY_REDEFINED_OP_FLAG, bop }));
|
||||
fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::BOPRedefined { klass: ARRAY_REDEFINED_OP_FLAG, bop }, state: exit_id });
|
||||
state.stack_push(fun.push_insn(block, insn));
|
||||
}
|
||||
YARVINSN_duparray => {
|
||||
|
@ -5054,7 +5054,11 @@ mod opt_tests {
|
|||
assert_optimized_method_hir("test", expect![[r#"
|
||||
fn test:
|
||||
bb0(v0:BasicObject):
|
||||
v2:Fixnum[1] = Const Value(1)
|
||||
v3:Fixnum[2] = Const Value(2)
|
||||
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS)
|
||||
v14:Fixnum[3] = Const Value(3)
|
||||
v6:Fixnum[3] = Const Value(3)
|
||||
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS)
|
||||
v15:Fixnum[6] = Const Value(6)
|
||||
Return v15
|
||||
|
@ -5071,7 +5075,11 @@ mod opt_tests {
|
|||
assert_optimized_method_hir("test", expect![[r#"
|
||||
fn test:
|
||||
bb0(v0:BasicObject):
|
||||
v2:Fixnum[5] = Const Value(5)
|
||||
v3:Fixnum[3] = Const Value(3)
|
||||
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MINUS)
|
||||
v14:Fixnum[2] = Const Value(2)
|
||||
v6:Fixnum[1] = Const Value(1)
|
||||
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MINUS)
|
||||
v15:Fixnum[1] = Const Value(1)
|
||||
Return v15
|
||||
|
@ -5088,6 +5096,8 @@ mod opt_tests {
|
|||
assert_optimized_method_hir("test", expect![[r#"
|
||||
fn test:
|
||||
bb0(v0:BasicObject):
|
||||
v2:Fixnum[6] = Const Value(6)
|
||||
v3:Fixnum[7] = Const Value(7)
|
||||
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MULT)
|
||||
v9:Fixnum[42] = Const Value(42)
|
||||
Return v9
|
||||
|
@ -5112,6 +5122,7 @@ mod opt_tests {
|
|||
v6:Fixnum[0] = Const Value(0)
|
||||
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MULT)
|
||||
v16:Fixnum = GuardType v1, Fixnum
|
||||
v21:Fixnum[0] = Const Value(0)
|
||||
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS)
|
||||
v22:Fixnum[0] = Const Value(0)
|
||||
Return v22
|
||||
|
@ -5132,6 +5143,8 @@ mod opt_tests {
|
|||
assert_optimized_method_hir("test", expect![[r#"
|
||||
fn test:
|
||||
bb0(v0:BasicObject):
|
||||
v2:Fixnum[1] = Const Value(1)
|
||||
v3:Fixnum[2] = Const Value(2)
|
||||
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT)
|
||||
v8:Fixnum[3] = Const Value(3)
|
||||
Return v8
|
||||
|
@ -5152,7 +5165,11 @@ mod opt_tests {
|
|||
assert_optimized_method_hir("test", expect![[r#"
|
||||
fn test:
|
||||
bb0(v0:BasicObject):
|
||||
v2:Fixnum[1] = Const Value(1)
|
||||
v3:Fixnum[2] = Const Value(2)
|
||||
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LE)
|
||||
v8:Fixnum[2] = Const Value(2)
|
||||
v9:Fixnum[2] = Const Value(2)
|
||||
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LE)
|
||||
v14:Fixnum[3] = Const Value(3)
|
||||
Return v14
|
||||
|
@ -5173,6 +5190,8 @@ mod opt_tests {
|
|||
assert_optimized_method_hir("test", expect![[r#"
|
||||
fn test:
|
||||
bb0(v0:BasicObject):
|
||||
v2:Fixnum[2] = Const Value(2)
|
||||
v3:Fixnum[1] = Const Value(1)
|
||||
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GT)
|
||||
v8:Fixnum[3] = Const Value(3)
|
||||
Return v8
|
||||
|
@ -5193,7 +5212,11 @@ mod opt_tests {
|
|||
assert_optimized_method_hir("test", expect![[r#"
|
||||
fn test:
|
||||
bb0(v0:BasicObject):
|
||||
v2:Fixnum[2] = Const Value(2)
|
||||
v3:Fixnum[1] = Const Value(1)
|
||||
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GE)
|
||||
v8:Fixnum[2] = Const Value(2)
|
||||
v9:Fixnum[2] = Const Value(2)
|
||||
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GE)
|
||||
v14:Fixnum[3] = Const Value(3)
|
||||
Return v14
|
||||
|
@ -5214,6 +5237,8 @@ mod opt_tests {
|
|||
assert_optimized_method_hir("test", expect![[r#"
|
||||
fn test:
|
||||
bb0(v0:BasicObject):
|
||||
v2:Fixnum[1] = Const Value(1)
|
||||
v3:Fixnum[2] = Const Value(2)
|
||||
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ)
|
||||
v12:Fixnum[4] = Const Value(4)
|
||||
Return v12
|
||||
|
@ -5234,6 +5259,8 @@ mod opt_tests {
|
|||
assert_optimized_method_hir("test", expect![[r#"
|
||||
fn test:
|
||||
bb0(v0:BasicObject):
|
||||
v2:Fixnum[2] = Const Value(2)
|
||||
v3:Fixnum[2] = Const Value(2)
|
||||
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ)
|
||||
v8:Fixnum[3] = Const Value(3)
|
||||
Return v8
|
||||
|
@ -5254,6 +5281,8 @@ mod opt_tests {
|
|||
assert_optimized_method_hir("test", expect![[r#"
|
||||
fn test:
|
||||
bb0(v0:BasicObject):
|
||||
v2:Fixnum[1] = Const Value(1)
|
||||
v3:Fixnum[2] = Const Value(2)
|
||||
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ)
|
||||
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_NEQ)
|
||||
v8:Fixnum[3] = Const Value(3)
|
||||
|
@ -5275,6 +5304,8 @@ mod opt_tests {
|
|||
assert_optimized_method_hir("test", expect![[r#"
|
||||
fn test:
|
||||
bb0(v0:BasicObject):
|
||||
v2:Fixnum[2] = Const Value(2)
|
||||
v3:Fixnum[2] = Const Value(2)
|
||||
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ)
|
||||
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_NEQ)
|
||||
v12:Fixnum[4] = Const Value(4)
|
||||
|
@ -6009,6 +6040,8 @@ mod opt_tests {
|
|||
assert_optimized_method_hir("test", expect![[r#"
|
||||
fn test:
|
||||
bb0(v0:BasicObject):
|
||||
v1:NilClassExact = Const Value(nil)
|
||||
v4:ArrayExact = NewArray
|
||||
PatchPoint MethodRedefined(Array@0x1000, itself@0x1008)
|
||||
v7:Fixnum[1] = Const Value(1)
|
||||
Return v7
|
||||
|
@ -6028,9 +6061,11 @@ mod opt_tests {
|
|||
assert_optimized_method_hir("test", expect![[r#"
|
||||
fn test:
|
||||
bb0(v0:BasicObject):
|
||||
v1:NilClassExact = Const Value(nil)
|
||||
PatchPoint SingleRactorMode
|
||||
PatchPoint StableConstantNames(0x1000, M)
|
||||
PatchPoint MethodRedefined(Module@0x1008, name@0x1010)
|
||||
v11:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
|
||||
PatchPoint MethodRedefined(Module@0x1010, name@0x1018)
|
||||
v7:Fixnum[1] = Const Value(1)
|
||||
Return v7
|
||||
"#]]);
|
||||
|
@ -6047,6 +6082,8 @@ mod opt_tests {
|
|||
assert_optimized_method_hir("test", expect![[r#"
|
||||
fn test:
|
||||
bb0(v0:BasicObject):
|
||||
v1:NilClassExact = Const Value(nil)
|
||||
v4:ArrayExact = NewArray
|
||||
PatchPoint MethodRedefined(Array@0x1000, length@0x1008)
|
||||
v7:Fixnum[5] = Const Value(5)
|
||||
Return v7
|
||||
|
@ -6145,6 +6182,8 @@ mod opt_tests {
|
|||
assert_optimized_method_hir("test", expect![[r#"
|
||||
fn test:
|
||||
bb0(v0:BasicObject):
|
||||
v1:NilClassExact = Const Value(nil)
|
||||
v4:ArrayExact = NewArray
|
||||
PatchPoint MethodRedefined(Array@0x1000, size@0x1008)
|
||||
v7:Fixnum[5] = Const Value(5)
|
||||
Return v7
|
||||
|
@ -6687,7 +6726,9 @@ mod opt_tests {
|
|||
assert_optimized_method_hir("test", expect![[r#"
|
||||
fn test:
|
||||
bb0(v0:BasicObject):
|
||||
v3:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
|
||||
PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE)
|
||||
v5:Fixnum[1] = Const Value(1)
|
||||
PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_AREF)
|
||||
v11:Fixnum[5] = Const Value(5)
|
||||
Return v11
|
||||
|
@ -6702,7 +6743,9 @@ mod opt_tests {
|
|||
assert_optimized_method_hir("test", expect![[r#"
|
||||
fn test:
|
||||
bb0(v0:BasicObject):
|
||||
v3:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
|
||||
PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE)
|
||||
v5:Fixnum[-3] = Const Value(-3)
|
||||
PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_AREF)
|
||||
v11:Fixnum[4] = Const Value(4)
|
||||
Return v11
|
||||
|
@ -6717,7 +6760,9 @@ mod opt_tests {
|
|||
assert_optimized_method_hir("test", expect![[r#"
|
||||
fn test:
|
||||
bb0(v0:BasicObject):
|
||||
v3:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
|
||||
PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE)
|
||||
v5:Fixnum[-10] = Const Value(-10)
|
||||
PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_AREF)
|
||||
v11:NilClassExact = Const Value(nil)
|
||||
Return v11
|
||||
|
@ -6732,7 +6777,9 @@ mod opt_tests {
|
|||
assert_optimized_method_hir("test", expect![[r#"
|
||||
fn test:
|
||||
bb0(v0:BasicObject):
|
||||
v3:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
|
||||
PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE)
|
||||
v5:Fixnum[10] = Const Value(10)
|
||||
PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_AREF)
|
||||
v11:NilClassExact = Const Value(nil)
|
||||
Return v11
|
||||
|
@ -6798,6 +6845,7 @@ mod opt_tests {
|
|||
assert_optimized_method_hir("test", expect![[r#"
|
||||
fn test:
|
||||
bb0(v0:BasicObject):
|
||||
v2:NilClassExact = Const Value(nil)
|
||||
PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008)
|
||||
v5:Fixnum[1] = Const Value(1)
|
||||
Return v5
|
||||
|
@ -6830,6 +6878,7 @@ mod opt_tests {
|
|||
assert_optimized_method_hir("test", expect![[r#"
|
||||
fn test:
|
||||
bb0(v0:BasicObject):
|
||||
v2:Fixnum[1] = Const Value(1)
|
||||
PatchPoint MethodRedefined(Integer@0x1000, nil?@0x1008)
|
||||
v5:Fixnum[2] = Const Value(2)
|
||||
Return v5
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
use std::{collections::{HashMap, HashSet}};
|
||||
|
||||
use crate::{cruby::{ruby_basic_operators, IseqPtr, RedefinitionFlag}, state::{zjit_enabled_p, ZJITState}, virtualmem::CodePtr};
|
||||
use crate::{backend::lir::{asm_comment, Assembler}, cruby::{ruby_basic_operators, src_loc, with_vm_lock, IseqPtr, RedefinitionFlag}, hir::{Invariant, PtrPrintMap}, options::debug, state::{zjit_enabled_p, ZJITState}, virtualmem::CodePtr};
|
||||
|
||||
#[derive(Debug, Eq, Hash, PartialEq)]
|
||||
struct Jump {
|
||||
from: CodePtr,
|
||||
to: CodePtr,
|
||||
}
|
||||
|
||||
/// Used to track all of the various block references that contain assumptions
|
||||
/// about the state of the virtual machine.
|
||||
|
@ -13,7 +19,7 @@ pub struct Invariants {
|
|||
no_ep_escape_iseqs: HashSet<IseqPtr>,
|
||||
|
||||
/// Map from a class and its associated basic operator to a set of patch points
|
||||
bop_patch_points: HashMap<(RedefinitionFlag, ruby_basic_operators), HashSet<CodePtr>>,
|
||||
bop_patch_points: HashMap<(RedefinitionFlag, ruby_basic_operators), HashSet<Jump>>,
|
||||
}
|
||||
|
||||
/// Called when a basic operator is redefined. Note that all the blocks assuming
|
||||
|
@ -26,13 +32,26 @@ pub extern "C" fn rb_zjit_bop_redefined(klass: RedefinitionFlag, bop: ruby_basic
|
|||
return;
|
||||
}
|
||||
|
||||
let invariants = ZJITState::get_invariants();
|
||||
if let Some(code_ptrs) = invariants.bop_patch_points.get(&(klass, bop)) {
|
||||
// Invalidate all patch points for this BOP
|
||||
for &ptr in code_ptrs {
|
||||
unimplemented!("Invalidation on BOP redefinition is not implemented yet: {ptr:?}");
|
||||
with_vm_lock(src_loc!(), || {
|
||||
let invariants = ZJITState::get_invariants();
|
||||
if let Some(jumps) = invariants.bop_patch_points.get(&(klass, bop)) {
|
||||
let cb = ZJITState::get_code_block();
|
||||
|
||||
// Invalidate all patch points for this BOP
|
||||
let bop = Invariant::BOPRedefined { klass, bop };
|
||||
debug!("BOP is redefined: {}", bop.print(&PtrPrintMap::identity()));
|
||||
for jump in jumps {
|
||||
cb.with_write_ptr(jump.from, |cb| {
|
||||
let mut asm = Assembler::new();
|
||||
asm_comment!(asm, "BOP redefined: {}", bop.print(&PtrPrintMap::identity()));
|
||||
asm.jmp(jump.to.into());
|
||||
asm.compile(cb).expect("can write existing code");
|
||||
});
|
||||
}
|
||||
|
||||
cb.mark_all_executable();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Invalidate blocks for a given ISEQ that assumes environment pointer is
|
||||
|
@ -68,7 +87,15 @@ pub fn iseq_escapes_ep(iseq: IseqPtr) -> bool {
|
|||
}
|
||||
|
||||
/// Track a patch point for a basic operator in a given class.
|
||||
pub fn track_bop_assumption(klass: RedefinitionFlag, bop: ruby_basic_operators, code_ptr: CodePtr) {
|
||||
pub fn track_bop_assumption(
|
||||
klass: RedefinitionFlag,
|
||||
bop: ruby_basic_operators,
|
||||
patch_point_ptr: CodePtr,
|
||||
side_exit_ptr: CodePtr
|
||||
) {
|
||||
let invariants = ZJITState::get_invariants();
|
||||
invariants.bop_patch_points.entry((klass, bop)).or_default().insert(code_ptr);
|
||||
invariants.bop_patch_points.entry((klass, bop)).or_default().insert(Jump {
|
||||
from: patch_point_ptr,
|
||||
to: side_exit_ptr,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::cruby_methods;
|
|||
use crate::invariants::Invariants;
|
||||
use crate::options::Options;
|
||||
use crate::asm::CodeBlock;
|
||||
use crate::backend::lir::{Assembler, C_RET_OPND};
|
||||
use crate::backend::lir::{asm_comment, Assembler, C_RET_OPND};
|
||||
use crate::virtualmem::CodePtr;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
|
@ -141,6 +141,7 @@ impl ZJITState {
|
|||
/// Generate a trampoline to propagate a callee's side exit to the caller
|
||||
fn gen_exit_trampoline(cb: &mut CodeBlock) -> Option<CodePtr> {
|
||||
let mut asm = Assembler::new();
|
||||
asm_comment!(asm, "ZJIT exit trampoline");
|
||||
asm.frame_teardown();
|
||||
asm.cret(C_RET_OPND);
|
||||
asm.compile(cb).map(|(start_ptr, _)| start_ptr)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue