diff --git a/jit.c b/jit.c index d2147a9d7f22c6..930b11d02c871d 100644 --- a/jit.c +++ b/jit.c @@ -421,3 +421,19 @@ rb_assert_cme_handle(VALUE handle) RUBY_ASSERT_ALWAYS(!rb_objspace_garbage_object_p(handle)); RUBY_ASSERT_ALWAYS(IMEMO_TYPE_P(handle, imemo_ment)); } + +extern +VALUE +rb_vm_call0(rb_execution_context_t *ec, VALUE recv, ID id, int argc, const VALUE *argv, const rb_callable_method_entry_t *cme, int kw_splat); + +VALUE +rb_zjit_vm_call0_no_splat(rb_execution_context_t *ec, VALUE recv, ID id, int argc, const VALUE *argv, const rb_callable_method_entry_t *cme) { + const char *cme_name = rb_id2name(cme->def->original_id); + fprintf(stderr, "call0 ec %p recv %p id %p argc %d, argv %p, cme %p name %s\n", + ec, (void*)recv, (void*)id, argc, argv, cme, cme_name); + return rb_vm_call0(ec, recv, id, argc, argv, cme, /*kw_splat=*/0); +} + +extern +VALUE +rb_vm_send(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, CALL_DATA cd, ISEQ blockiseq); diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 4aff3193f0d3b4..68f31352abfeb4 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -347,6 +347,8 @@ fn main() { .allowlist_function("rb_optimized_call") .allowlist_function("rb_zjit_icache_invalidate") .allowlist_function("rb_zjit_print_exception") + .allowlist_function("rb_zjit_vm_call0_no_splat") + .allowlist_function("rb_vm_send") .allowlist_type("robject_offsets") .allowlist_type("rstring_offsets") diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index f7e871523e1547..57e31e21168edb 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -28,6 +28,7 @@ pub const _C_ARG_OPNDS: [Opnd; 6] = [ // C return value register on this platform pub const C_RET_REG: Reg = X0_REG; pub const _C_RET_OPND: Opnd = Opnd::Reg(X0_REG); +pub const _NATIVE_STACK_PTR: Opnd = Opnd::Reg(XZR_REG); // These constants define the way we work with Arm64's stack pointer. The stack // pointer always needs to be aligned to a 16-byte boundary. @@ -679,6 +680,10 @@ impl Assembler *opnd = opnd0; asm.push_insn(insn); }, + Insn::CPush(opnd) => { + let opnd = split_load_operand(asm, *opnd); + asm.push_insn(Insn::CPush(opnd)); + } Insn::Store { dest, src } => { // The value being stored must be in a register, so if it's // not already one we'll load it first. diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index e9ae8730f6ca70..f76dda909fa4ce 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -16,6 +16,7 @@ pub const SP: Opnd = _SP; pub const C_ARG_OPNDS: [Opnd; 6] = _C_ARG_OPNDS; pub const C_RET_OPND: Opnd = _C_RET_OPND; +pub const NATIVE_STACK_PTR: Opnd = _NATIVE_STACK_PTR; pub use crate::backend::current::{Reg, C_RET_REG}; // Memory operand base @@ -2225,7 +2226,7 @@ impl Assembler { /// when not dumping disassembly. macro_rules! asm_comment { ($asm:expr, $($fmt:tt)*) => { - if $crate::options::get_option!(dump_disasm) { + if $crate::options::get_option!(dump_disasm) || $crate::options::get_option!(dump_lir) { $asm.push_insn(crate::backend::lir::Insn::Comment(format!($($fmt)*))); } }; diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index cf62cdd7f58cbf..c7e12e0b8aa57e 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -28,6 +28,7 @@ pub const _C_ARG_OPNDS: [Opnd; 6] = [ // C return value register on this platform pub const C_RET_REG: Reg = RAX_REG; pub const _C_RET_OPND: Opnd = Opnd::Reg(RAX_REG); +pub const _NATIVE_STACK_PTR: Opnd = Opnd::Reg(RSP_REG); impl CodeBlock { // The number of bytes that are generated by jmp_ptr diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 0dbe815c71ae55..6a2f9f6d73046e 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -6,8 +6,8 @@ use crate::profile::get_or_create_iseq_payload; use crate::state::ZJITState; use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr}; use crate::invariants::{iseq_escapes_ep, track_no_ep_escape_assumption}; -use crate::backend::lir::{self, asm_comment, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, SP}; -use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, CallInfo, RangeType, SELF_PARAM_IDX}; +use crate::backend::lir::{self, asm_comment, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, SP, NATIVE_STACK_PTR}; +use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, RangeType, SELF_PARAM_IDX}; use crate::hir::{Const, FrameState, Function, Insn, InsnId}; use crate::hir_type::{types::Fixnum, Type}; use crate::options::get_option; @@ -257,8 +257,10 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::Jump(branch) => return gen_jump(jit, asm, branch), Insn::IfTrue { val, target } => return gen_if_true(jit, asm, opnd!(val), target), Insn::IfFalse { val, target } => return gen_if_false(jit, asm, opnd!(val), target), - Insn::SendWithoutBlock { call_info, cd, state, self_val, args, .. } => gen_send_without_block(jit, asm, call_info, *cd, &function.frame_state(*state), self_val, args)?, - Insn::SendWithoutBlockDirect { iseq, self_val, args, .. } => gen_send_without_block_direct(cb, jit, asm, *iseq, opnd!(self_val), args)?, + Insn::LookupMethod { self_val, method_id, state } => gen_lookup_method(jit, asm, opnd!(self_val), *method_id, &function.frame_state(*state))?, + Insn::CallMethod { callable, cd, self_val, args, state } => gen_call_method(jit, asm, opnd!(callable), *cd, *self_val, args, &function.frame_state(*state))?, + Insn::CallCFunc { cfunc, self_val, args, .. } => gen_call_cfunc(jit, asm, *cfunc, opnd!(self_val), args)?, + Insn::CallIseq { iseq, self_val, args, .. } => gen_send_without_block_direct(cb, jit, asm, *iseq, opnd!(self_val), args)?, Insn::Return { val } => return Some(gen_return(asm, opnd!(val))?), Insn::FixnumAdd { left, right, state } => gen_fixnum_add(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state))?, Insn::FixnumSub { left, right, state } => gen_fixnum_sub(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state))?, @@ -455,38 +457,126 @@ fn gen_if_false(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, branch: } /// Compile a dynamic dispatch without block -fn gen_send_without_block( +fn gen_lookup_method( jit: &mut JITState, asm: &mut Assembler, - call_info: &CallInfo, - cd: *const rb_call_data, + recv: Opnd, + method_id: ID, state: &FrameState, - self_val: &InsnId, - args: &Vec, ) -> Option { - // Spill the receiver and the arguments onto the stack. They need to be marked by GC and may be caller-saved registers. + asm_comment!(asm, "get the class of the receiver with rb_obj_class"); + let class = asm.ccall(rb_obj_class as *const u8, vec![recv]); + // TODO(max): Figure out if we need to do anything here to save state to CFP + let method_opnd = Opnd::UImm(method_id.0.into()); + asm_comment!(asm, "call rb_callable_method_entry"); + let result = asm.ccall( + rb_callable_method_entry as *const u8, + vec![class, method_opnd], + ); + // rb_callable_method_entry may be NULL, and we don't want to handle method_missing shenanigans + // in JIT code. + asm.test(result, result); + asm.jz(side_exit(jit, state)?); + Some(result) +} + +fn gen_call_method( + jit: &mut JITState, + asm: &mut Assembler, + callable: Opnd, + cd: CallDataPtr, + recv: InsnId, + args: &Vec, + state: &FrameState, +) -> Option { + // Spill the receiver and the arguments onto the stack. + // They need to be on the interpreter stack to let the interpreter access them. // TODO: Avoid spilling operands that have been spilled before. - for (idx, &insn_id) in [*self_val].iter().chain(args.iter()).enumerate() { + asm_comment!(asm, "spill receiver and arguments"); + for (idx, &insn_id) in [recv].iter().chain(args.iter()).enumerate() { // Currently, we don't move the SP register. So it's equal to the base pointer. let stack_opnd = Opnd::mem(64, SP, idx as i32 * SIZEOF_VALUE_I32); asm.mov(stack_opnd, jit.get_opnd(insn_id)?); } - // Save PC and SP + // Don't push recv; it is passed in separately. + asm_comment!(asm, "make stack-allocated array of {} args", args.len()); + let sp_adjust = if args.len() % 2 == 0 { args.len() } else { args.len() + 1 }; + let new_sp = asm.sub(NATIVE_STACK_PTR, (sp_adjust*SIZEOF_VALUE).into()); + asm.mov(NATIVE_STACK_PTR, new_sp); + for (idx, &arg) in args.iter().rev().enumerate() { + asm.mov(Opnd::mem(VALUE_BITS, NATIVE_STACK_PTR, ((idx)*SIZEOF_VALUE).try_into().unwrap()), jit.get_opnd(arg)?); + } + // Save PC for GC gen_save_pc(asm, state); gen_save_sp(asm, 1 + args.len()); // +1 for receiver + // Call rb_zjit_vm_call0_no_splat, which will push a frame + // TODO(max): Figure out if we need to manually handle stack alignment and how to do it + let call_info = unsafe { rb_get_call_data_ci(cd) }; + let method_id = unsafe { rb_vm_ci_mid(call_info) }; + asm_comment!(asm, "get stack pointer"); + let sp = asm.lea(Opnd::mem(VALUE_BITS, NATIVE_STACK_PTR, VALUE_BITS.into())); + asm_comment!(asm, "call rb_zjit_vm_call0_no_splat"); + let recv = jit.get_opnd(recv)?; + let result = asm.ccall( + rb_zjit_vm_call0_no_splat as *const u8, + vec![EC, recv, Opnd::UImm(method_id.0), Opnd::UImm(args.len().try_into().unwrap()), sp, callable], + ); + // Pop all the args off the stack + asm_comment!(asm, "clear stack-allocated array of {} args", args.len()); + let new_sp = asm.add(NATIVE_STACK_PTR, (sp_adjust*SIZEOF_VALUE).into()); + asm.mov(NATIVE_STACK_PTR, new_sp); + Some(result) +} + +/// Compile an interpreter frame +fn gen_push_frame(asm: &mut Assembler, recv: Opnd) { + // Write to a callee CFP + fn cfp_opnd(offset: i32) -> Opnd { + Opnd::mem(64, CFP, offset - (RUBY_SIZEOF_CONTROL_FRAME as i32)) + } - asm_comment!(asm, "call #{} with dynamic dispatch", call_info.method_name); - unsafe extern "C" { - fn rb_vm_opt_send_without_block(ec: EcPtr, cfp: CfpPtr, cd: VALUE) -> VALUE; + asm_comment!(asm, "push callee control frame"); + asm.mov(cfp_opnd(RUBY_OFFSET_CFP_SELF), recv); + // TODO: Write more fields as needed +} + +fn gen_call_cfunc( + jit: &mut JITState, + asm: &mut Assembler, + cfunc: CFuncPtr, + recv: Opnd, + args: &Vec, +) -> Option { + let cfunc_argc = unsafe { get_mct_argc(cfunc) }; + // NB: The presence of self is assumed (no need for +1). + if args.len() != cfunc_argc as usize { + // TODO(max): We should check this at compile-time. If we have an arity mismatch at this + // point, we should side-exit (we're definitely going to raise) and if we don't, we should + // not check anything. + todo!("Arity mismatch"); } - let ret = asm.ccall( - rb_vm_opt_send_without_block as *const u8, - vec![EC, CFP, (cd as usize).into()], - ); + + // Set up the new frame + gen_push_frame(asm, recv); + + asm_comment!(asm, "switch to new CFP"); + let new_cfp = asm.sub(CFP, RUBY_SIZEOF_CONTROL_FRAME.into()); + asm.mov(CFP, new_cfp); + asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); + + // Set up arguments + let mut c_args: Vec = vec![recv]; + for &arg in args.iter() { + c_args.push(jit.get_opnd(arg)?); + } + + // Make a method call. The target address will be rewritten once compiled. + let cfun = unsafe { get_mct_func(cfunc) }.cast(); // TODO(max): Add a PatchPoint here that can side-exit the function if the callee messed with // the frame's locals - + let ret = asm.ccall(cfun, c_args); + gen_pop_frame(asm); Some(ret) } @@ -590,14 +680,18 @@ fn gen_new_range( new_range } -/// Compile code that exits from JIT code with a return value -fn gen_return(asm: &mut Assembler, val: lir::Opnd) -> Option<()> { +fn gen_pop_frame(asm: &mut Assembler) { // Pop the current frame (ec->cfp++) // Note: the return PC is already in the previous CFP asm_comment!(asm, "pop stack frame"); let incr_cfp = asm.add(CFP, RUBY_SIZEOF_CONTROL_FRAME.into()); asm.mov(CFP, incr_cfp); asm.mov(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); +} + +/// Compile code that exits from JIT code with a return value +fn gen_return(asm: &mut Assembler, val: lir::Opnd) -> Option<()> { + gen_pop_frame(asm); asm.frame_teardown(); diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index d5be47e02656d4..ce040ffcb8bbc0 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -120,9 +120,9 @@ pub use autogened::*; #[cfg_attr(test, allow(unused))] // We don't link against C code when testing unsafe extern "C" { pub fn rb_check_overloaded_cme( - me: *const rb_callable_method_entry_t, + me: CmePtr, ci: *const rb_callinfo, - ) -> *const rb_callable_method_entry_t; + ) -> CmePtr; // Floats within range will be encoded without creating objects in the heap. // (Range is 0x3000000000000001 to 0x4fffffffffffffff (1.7272337110188893E-77 to 2.3158417847463237E+77). @@ -144,8 +144,8 @@ unsafe extern "C" { pub fn rb_vm_set_ivar_id(obj: VALUE, idx: u32, val: VALUE) -> VALUE; pub fn rb_vm_setinstancevariable(iseq: IseqPtr, obj: VALUE, id: ID, val: VALUE, ic: IVC); pub fn rb_aliased_callable_method_entry( - me: *const rb_callable_method_entry_t, - ) -> *const rb_callable_method_entry_t; + me: CmePtr, + ) -> CmePtr; pub fn rb_vm_getclassvariable(iseq: IseqPtr, cfp: CfpPtr, id: ID, ic: ICVARC) -> VALUE; pub fn rb_vm_setclassvariable( iseq: IseqPtr, @@ -156,7 +156,7 @@ unsafe extern "C" { ) -> VALUE; pub fn rb_vm_ic_hit_p(ic: IC, reg_ep: *const VALUE) -> bool; pub fn rb_vm_stack_canary() -> VALUE; - pub fn rb_vm_push_cfunc_frame(cme: *const rb_callable_method_entry_t, recv_idx: c_int); + pub fn rb_vm_push_cfunc_frame(cme: CmePtr, recv_idx: c_int); } // Renames @@ -259,6 +259,12 @@ pub struct ID(pub ::std::os::raw::c_ulong); /// Pointer to an ISEQ pub type IseqPtr = *const rb_iseq_t; +pub type CallDataPtr = *const rb_call_data; + +pub type CmePtr = *const rb_callable_method_entry_t; + +pub type CFuncPtr = *const rb_method_cfunc_t; + // Given an ISEQ pointer, convert PC to insn_idx pub fn iseq_pc_to_insn_idx(iseq: IseqPtr, pc: *mut VALUE) -> Option { let pc_zero = unsafe { rb_iseq_pc_at_idx(iseq, 0) }; @@ -579,8 +585,8 @@ impl VALUE { } /// Assert that `self` is a method entry in debug builds - pub fn as_cme(self) -> *const rb_callable_method_entry_t { - let ptr: *const rb_callable_method_entry_t = self.as_ptr(); + pub fn as_cme(self) -> CmePtr { + let ptr: CmePtr = self.as_ptr(); #[cfg(debug_assertions)] if !ptr.is_null() { @@ -611,9 +617,9 @@ impl From for VALUE { } } -impl From<*const rb_callable_method_entry_t> for VALUE { +impl From for VALUE { /// For `.into()` convenience - fn from(cme: *const rb_callable_method_entry_t) -> Self { + fn from(cme: CmePtr) -> Self { VALUE(cme as usize) } } diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 0447f46fd0f26e..c9c7962485474b 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -369,6 +369,7 @@ pub type vm_special_object_type = u32; pub type IC = *mut iseq_inline_constant_cache; pub type IVC = *mut iseq_inline_iv_cache_entry; pub type ICVARC = *mut iseq_inline_cvar_cache_entry; +pub type CALL_DATA = *mut rb_call_data; pub const VM_FRAME_MAGIC_METHOD: vm_frame_env_flags = 286326785; pub const VM_FRAME_MAGIC_BLOCK: vm_frame_env_flags = 572653569; pub const VM_FRAME_MAGIC_CLASS: vm_frame_env_flags = 858980353; @@ -736,6 +737,7 @@ pub const DEFINED_REF: defined_type = 15; pub const DEFINED_FUNC: defined_type = 16; pub const DEFINED_CONST_FROM: defined_type = 17; pub type defined_type = u32; +pub type ISEQ = *mut rb_iseq_t; pub type rb_iseq_param_keyword_struct = rb_iseq_constant_body__bindgen_ty_1_rb_iseq_param_keyword; unsafe extern "C" { pub fn ruby_xfree(ptr: *mut ::std::os::raw::c_void); @@ -1020,4 +1022,18 @@ unsafe extern "C" { pub fn rb_assert_iseq_handle(handle: VALUE); pub fn rb_IMEMO_TYPE_P(imemo: VALUE, imemo_type: imemo_type) -> ::std::os::raw::c_int; pub fn rb_assert_cme_handle(handle: VALUE); + pub fn rb_zjit_vm_call0_no_splat( + ec: *mut rb_execution_context_t, + recv: VALUE, + id: ID, + argc: ::std::os::raw::c_int, + argv: *const VALUE, + cme: *const rb_callable_method_entry_t, + ) -> VALUE; + pub fn rb_vm_send( + ec: *mut rb_execution_context_t, + reg_cfp: *mut rb_control_frame_t, + cd: CALL_DATA, + blockiseq: ISEQ, + ) -> VALUE; } diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index edaaba1516f2a3..c18a7ca8300f79 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -37,7 +37,7 @@ impl Annotations { if VM_METHOD_TYPE_CFUNC != get_cme_def_type(method) { return None; } - get_mct_func(get_cme_def_body_cfunc(method.cast())) + rb_get_mct_func(rb_get_cme_def_body_cfunc(method.cast())) }; self.cfuncs.get(&fn_ptr).copied() } @@ -65,11 +65,12 @@ pub fn init() -> Annotations { let cfuncs = &mut HashMap::new(); macro_rules! annotate { - ($module:ident, $method_name:literal, $return_type:expr, $($properties:ident),+) => { + ($module:ident, $method_name:literal, $return_type:expr, $($properties:ident),*) => { + #[allow(unused_mut)] let mut props = FnProperties { no_gc: false, leaf: false, elidable: false, return_type: $return_type }; $( props.$properties = true; - )+ + )* annotate_c_method(cfuncs, unsafe { $module }, $method_name, props); } } @@ -80,6 +81,7 @@ pub fn init() -> Annotations { annotate!(rb_cModule, "===", types::BoolExact, no_gc, leaf); annotate!(rb_cArray, "length", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cArray, "size", types::Fixnum, no_gc, leaf, elidable); + annotate!(rb_cInteger, "+", types::IntegerExact,); Annotations { cfuncs: std::mem::take(cfuncs) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index c93b603f535b9d..f1127684cc33b0 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -102,11 +102,6 @@ impl std::fmt::Display for BranchEdge { } } -#[derive(Debug, PartialEq, Clone)] -pub struct CallInfo { - pub method_name: String, -} - /// Invalidation reasons #[derive(Debug, Clone, Copy)] pub enum Invariant { @@ -417,11 +412,16 @@ pub enum Insn { /// `name` is for printing purposes only CCall { cfun: *const u8, args: Vec, name: ID, return_type: Type, elidable: bool }, - /// Send without block with dynamic dispatch - /// Ignoring keyword arguments etc for now - SendWithoutBlock { self_val: InsnId, call_info: CallInfo, cd: *const rb_call_data, args: Vec, state: InsnId }, - Send { self_val: InsnId, call_info: CallInfo, cd: *const rb_call_data, blockiseq: IseqPtr, args: Vec, state: InsnId }, - SendWithoutBlockDirect { self_val: InsnId, call_info: CallInfo, cd: *const rb_call_data, iseq: IseqPtr, args: Vec, state: InsnId }, + /// Look up the given method (CME) on the receiver, side-exiting if the method is not found. + LookupMethod { self_val: InsnId, method_id: ID, state: InsnId }, + /// Call a CME. + CallMethod { callable: InsnId, cd: CallDataPtr, self_val: InsnId, args: Vec, state: InsnId }, + /// Call a CME with a block. + CallMethodWithBlock { callable: InsnId, blockiseq: IseqPtr, cd: CallDataPtr, self_val: InsnId, args: Vec, state: InsnId }, + /// Call the given ISEQ. + CallIseq { iseq: IseqPtr, cd: CallDataPtr, self_val: InsnId, args: Vec, return_type: Type, elidable: bool, state: InsnId }, + /// Call the given CFunc. + CallCFunc { cfunc: CFuncPtr, cd: CallDataPtr, self_val: InsnId, args: Vec, return_type: Type, elidable: bool, state: InsnId }, /// Control flow instructions Return { val: InsnId }, @@ -502,7 +502,10 @@ impl Insn { Insn::FixnumLe { .. } => false, Insn::FixnumGt { .. } => false, Insn::FixnumGe { .. } => false, + Insn::LookupMethod { .. } => false, Insn::CCall { elidable, .. } => !elidable, + Insn::CallCFunc { elidable, .. } => !elidable, + Insn::CallIseq { elidable, .. } => !elidable, _ => true, } } @@ -558,26 +561,49 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::Jump(target) => { write!(f, "Jump {target}") } Insn::IfTrue { val, target } => { write!(f, "IfTrue {val}, {target}") } Insn::IfFalse { val, target } => { write!(f, "IfFalse {val}, {target}") } - Insn::SendWithoutBlock { self_val, call_info, args, .. } => { - write!(f, "SendWithoutBlock {self_val}, :{}", call_info.method_name)?; - for arg in args { - write!(f, ", {arg}")?; - } - Ok(()) + Insn::LookupMethod { self_val, method_id, .. } => { + let method_name = method_id.contents_lossy().into_owned(); + write!(f, "LookupMethod {self_val}, :{method_name}") } - Insn::SendWithoutBlockDirect { self_val, call_info, iseq, args, .. } => { - write!(f, "SendWithoutBlockDirect {self_val}, :{} ({:?})", call_info.method_name, self.ptr_map.map_ptr(iseq))?; - for arg in args { + Insn::CallMethod { callable, cd, self_val, args, .. } => { + let call_info = unsafe { (**cd).ci }; + let method_id = unsafe { rb_vm_ci_mid(call_info) }; + let method_name = method_id.contents_lossy().into_owned(); + write!(f, "CallMethod {callable} (:{method_name})")?; + for arg in [*self_val].iter().chain(args) { write!(f, ", {arg}")?; } Ok(()) } - Insn::Send { self_val, call_info, args, blockiseq, .. } => { + Insn::CallMethodWithBlock { self_val, callable, cd, args, blockiseq, .. } => { + let call_info = unsafe { (**cd).ci }; + let method_id = unsafe { rb_vm_ci_mid(call_info) }; + let method_name = method_id.contents_lossy().into_owned(); // For tests, we want to check HIR snippets textually. Addresses change // between runs, making tests fail. Instead, pick an arbitrary hex value to // use as a "pointer" so we can check the rest of the HIR. - write!(f, "Send {self_val}, {:p}, :{}", self.ptr_map.map_ptr(blockiseq), call_info.method_name)?; - for arg in args { + write!(f, "CallMethodWithBlock {callable} (:{method_name}), {:p}", self.ptr_map.map_ptr(blockiseq))?; + for arg in [*self_val].iter().chain(args) { + write!(f, ", {arg}")?; + } + Ok(()) + } + Insn::CallIseq { iseq, cd, self_val, args, .. } => { + let call_info = unsafe { (**cd).ci }; + let method_id = unsafe { rb_vm_ci_mid(call_info) }; + let method_name = method_id.contents_lossy().into_owned(); + write!(f, "CallIseq {:p} (:{method_name})", self.ptr_map.map_ptr(iseq))?; + for arg in [*self_val].iter().chain(args) { + write!(f, ", {arg}")?; + } + Ok(()) + } + Insn::CallCFunc { cfunc, cd, self_val, args, .. } => { + let call_info = unsafe { (**cd).ci }; + let method_id = unsafe { rb_vm_ci_mid(call_info) }; + let method_name = method_id.contents_lossy().into_owned(); + write!(f, "CallCFunc {:p} (:{method_name})", self.ptr_map.map_ptr(cfunc))?; + for arg in [*self_val].iter().chain(args) { write!(f, ", {arg}")?; } Ok(()) @@ -943,28 +969,43 @@ impl Function { FixnumGe { left, right } => FixnumGe { left: find!(*left), right: find!(*right) }, FixnumLt { left, right } => FixnumLt { left: find!(*left), right: find!(*right) }, FixnumLe { left, right } => FixnumLe { left: find!(*left), right: find!(*right) }, - SendWithoutBlock { self_val, call_info, cd, args, state } => SendWithoutBlock { + LookupMethod { self_val, method_id, state } => LookupMethod { self_val: find!(*self_val), - call_info: call_info.clone(), - cd: *cd, - args: args.iter().map(|arg| find!(*arg)).collect(), - state: *state, + method_id: *method_id, + state: find!(*state), }, - SendWithoutBlockDirect { self_val, call_info, cd, iseq, args, state } => SendWithoutBlockDirect { + CallMethod { callable, self_val, cd, args, state } => CallMethod { + callable: find!(*callable), self_val: find!(*self_val), - call_info: call_info.clone(), cd: *cd, - iseq: *iseq, args: args.iter().map(|arg| find!(*arg)).collect(), - state: *state, + state: find!(*state), }, - Send { self_val, call_info, cd, blockiseq, args, state } => Send { + CallMethodWithBlock { callable, blockiseq, self_val, cd, args, state } => CallMethodWithBlock { + callable: find!(*callable), + blockiseq: *blockiseq, self_val: find!(*self_val), - call_info: call_info.clone(), cd: *cd, - blockiseq: *blockiseq, args: args.iter().map(|arg| find!(*arg)).collect(), - state: *state, + state: find!(*state), + }, + &CallIseq { iseq, self_val, cd, ref args, return_type, elidable, state } => CallIseq { + iseq, + self_val: find!(self_val), + cd, + args: args.iter().map(|arg| find!(*arg)).collect(), + return_type, + elidable, + state: find!(state), + }, + &CallCFunc { cfunc, self_val, cd, ref args, return_type, elidable, state } => CallCFunc { + cfunc, + self_val: find!(self_val), + cd, + args: args.iter().map(|arg| find!(*arg)).collect(), + return_type, + elidable, + state: find!(state), }, ArraySet { array, idx, val } => ArraySet { array: find!(*array), idx: *idx, val: find!(*val) }, ArrayDup { val , state } => ArrayDup { val: find!(*val), state: *state }, @@ -1043,6 +1084,8 @@ impl Function { Insn::HashDup { .. } => types::HashExact, Insn::NewRange { .. } => types::RangeExact, Insn::CCall { return_type, .. } => *return_type, + Insn::CallCFunc { return_type, .. } => *return_type, + Insn::CallIseq { return_type, .. } => *return_type, Insn::GuardType { val, guard_type, .. } => self.type_of(*val).intersection(*guard_type), Insn::GuardBitEquals { val, expected, .. } => self.type_of(*val).intersection(Type::from_value(*expected)), Insn::FixnumAdd { .. } => types::Fixnum, @@ -1056,9 +1099,6 @@ impl Function { Insn::FixnumLe { .. } => types::BoolExact, Insn::FixnumGt { .. } => types::BoolExact, Insn::FixnumGe { .. } => types::BoolExact, - Insn::SendWithoutBlock { .. } => types::BasicObject, - Insn::SendWithoutBlockDirect { .. } => types::BasicObject, - Insn::Send { .. } => types::BasicObject, Insn::Defined { .. } => types::BasicObject, Insn::DefinedIvar { .. } => types::BasicObject, Insn::GetConstantPath { .. } => types::BasicObject, @@ -1066,6 +1106,9 @@ impl Function { Insn::GetIvar { .. } => types::BasicObject, Insn::ToNewArray { .. } => types::ArrayExact, Insn::ToArray { .. } => types::ArrayExact, + Insn::LookupMethod { .. } => types::CallableMethodEntry, + Insn::CallMethod { .. } => types::BasicObject, + Insn::CallMethodWithBlock { .. } => types::BasicObject, } } @@ -1189,71 +1232,115 @@ impl Function { } } - /// Rewrite SendWithoutBlock opcodes into SendWithoutBlockDirect opcodes if we know the target - /// ISEQ statically. This removes run-time method lookups and opens the door for inlining. - fn optimize_direct_sends(&mut self) { + fn optimize_lookup_method(&mut self) { for block in self.rpo() { let old_insns = std::mem::take(&mut self.blocks[block.0].insns); assert!(self.blocks[block.0].insns.is_empty()); for insn_id in old_insns { match self.find(insn_id) { - 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::FixnumAdd { left, right, state }, BOP_PLUS, 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::FixnumSub { left, right, state }, BOP_MINUS, 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::FixnumMult { left, right, state }, BOP_MULT, 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::FixnumDiv { left, right, state }, BOP_DIV, 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::FixnumMod { left, right, state }, BOP_MOD, 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::FixnumEq { left, right }, BOP_EQ, 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::FixnumNeq { left, right }, BOP_NEQ, 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::FixnumLt { left, right }, BOP_LT, 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::FixnumLe { left, right }, BOP_LE, 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::FixnumGt { left, right }, BOP_GT, 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::FixnumGe { left, right }, BOP_GE, 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() { - // If we know the class statically, use it to fold the lookup at compile-time. - (klass, None) + Insn::LookupMethod { self_val, method_id, state, .. } => { + let self_type = self.type_of(self_val); + if let Some(self_class) = self_type.runtime_exact_ruby_class() { + let method = unsafe { rb_callable_method_entry(self_class, method_id) }; + if method.is_null() { self.push_insn_id(block, insn_id); continue; } + // TODO(max): Check for overloaded CME? + // TODO(max): Convert MethodRedefined into BOP check for internal methods + self.push_insn(block, Insn::PatchPoint(Invariant::MethodRedefined { klass: self_class, method: method_id })); + let replacement = self.push_insn(block, Insn::Const { val: Const::Value(method.into()) }); + self.make_equal_to(insn_id, replacement); } else { - // If we know that self is top-self from profile information, guard and use it to fold the lookup at compile-time. - match self.profiled_type_of_at(self_val, frame_state.insn_idx) { - Some(self_type) if self_type.is_top_self() => (self_type.exact_ruby_class().unwrap(), self_type.ruby_object()), - _ => { self.push_insn_id(block, insn_id); continue; } - } - }; - let ci = unsafe { get_call_data_ci(cd) }; // info about the call site - let mid = unsafe { vm_ci_mid(ci) }; - // Do method lookup - let mut cme = unsafe { rb_callable_method_entry(klass, mid) }; - if cme.is_null() { - self.push_insn_id(block, insn_id); continue; + // Try checking profiles + let iseq_insn_idx = self.frame_state(state).insn_idx; + let Some(recv_type) = self.profiled_type_of_at(self_val, iseq_insn_idx) else { + self.push_insn_id(block, insn_id); continue; + }; + let Some(recv_class) = recv_type.exact_ruby_class() else { + self.push_insn_id(block, insn_id); continue; + }; + let guard_type = recv_type.unspecialized(); + // Do method lookup + let method = unsafe { rb_callable_method_entry(recv_class, method_id) }; + if method.is_null() { self.push_insn_id(block, insn_id); continue; } + // TODO(max): Check for overloaded CME? + // Commit to the replacement. Put PatchPoint. + // TODO(max): Convert MethodRedefined into BOP check for internal methods + self.push_insn(block, Insn::PatchPoint(Invariant::MethodRedefined { klass: recv_class, method: method_id })); + // Guard receiver class + self.push_insn(block, Insn::GuardType { val: self_val, guard_type, state }); + let replacement = self.push_insn(block, Insn::Const { val: Const::Value(method.into()) }); + self.make_equal_to(insn_id, replacement); } - // Load an overloaded cme if applicable. See vm_search_cc(). - // It allows you to use a faster ISEQ if possible. - cme = unsafe { rb_check_overloaded_cme(cme, ci) }; - let def_type = unsafe { get_cme_def_type(cme) }; - if def_type != VM_METHOD_TYPE_ISEQ { - // TODO(max): Allow non-iseq; cache cme - self.push_insn_id(block, insn_id); continue; + } + _ => { self.push_insn_id(block, insn_id); } + } + } + } + self.infer_types(); + } + + /// Rewrite call opcodes into specialized opcodes if we know the target + /// ISEQ or CFUNC statically. This removes run-time method lookups and opens the door for inlining. + fn optimize_direct_sends(&mut self) { + for block in self.rpo() { + let old_insns = std::mem::take(&mut self.blocks[block.0].insns); + assert!(self.blocks[block.0].insns.is_empty()); + for insn_id in old_insns { + match self.find(insn_id) { + Insn::CallMethod { callable, cd, self_val, args, state } => { + if args.len() == 1 { + let call_info = unsafe { (*cd).ci }; + let method_id = unsafe { rb_vm_ci_mid(call_info) }; + // Short-cut iseq/cfunc call for fixnum binary operations + match method_id.contents_lossy().as_ref() { + "+" => { self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumAdd { left, right, state }, BOP_PLUS, self_val, args[0], state); continue; } + "-" => { self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumSub { left, right, state }, BOP_MINUS, self_val, args[0], state); continue; } + "*" => { self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumMult { left, right, state }, BOP_MULT, self_val, args[0], state); continue; } + "/" => { self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumDiv { left, right, state }, BOP_DIV, self_val, args[0], state); continue; } + "%" => { self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumMod { left, right, state }, BOP_MOD, self_val, args[0], state); continue; } + "==" => { self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumEq { left, right }, BOP_EQ, self_val, args[0], state); continue; } + "!=" => { self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumNeq { left, right }, BOP_NEQ, self_val, args[0], state); continue; } + "<" => { self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumLt { left, right }, BOP_LT, self_val, args[0], state); continue; } + "<=" => { self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumLe { left, right }, BOP_LE, self_val, args[0], state); continue; } + ">" => { self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumGt { left, right }, BOP_GT, self_val, args[0], state); continue; } + ">=" => { self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumGe { left, right }, BOP_GE, self_val, args[0], state); continue; } + _ => {} + } } - self.push_insn(block, Insn::PatchPoint(Invariant::MethodRedefined { klass, method: mid })); - 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 }); + if let Some(value) = self.type_of(callable).ruby_object() { + assert!(self.type_of(callable).is_subtype(types::CallableMethodEntry), "LookupMethod should return CME"); + let cme: CmePtr = value.as_cme(); + let properties = ZJITState::get_method_annotations().get_cfunc_properties(cme); + let (return_type, elidable) = properties.map(|p| (p.return_type, p.elidable)).unwrap_or((types::BasicObject, false)); + let def_type = unsafe { get_cme_def_type(cme) }; + if def_type == VM_METHOD_TYPE_ISEQ { + let iseq = unsafe { get_def_iseq_ptr((*cme).def) }; + let replacement = self.push_insn(block, Insn::CallIseq { iseq, cd, self_val, args, return_type, elidable, state }); + self.make_equal_to(insn_id, replacement); + continue; + } + if def_type == VM_METHOD_TYPE_CFUNC { + let cfunc = unsafe { get_cme_def_body_cfunc(cme) }; + let replacement = self.push_insn(block, Insn::CallCFunc { cfunc, cd, self_val, args, return_type, elidable, state }); + self.make_equal_to(insn_id, replacement); + continue; + } + // Fall through for cases we don't currently optimize } - let send_direct = self.push_insn(block, Insn::SendWithoutBlockDirect { self_val, call_info, cd, iseq, args, state }); - self.make_equal_to(insn_id, send_direct); + self.push_insn_id(block, insn_id); continue; } + _ => { self.push_insn_id(block, insn_id); } + } + } + } + self.infer_types(); + } + + fn optimize_constants(&mut self) { + for block in self.rpo() { + let old_insns = std::mem::take(&mut self.blocks[block.0].insns); + assert!(self.blocks[block.0].insns.is_empty()); + for insn_id in old_insns { + match self.find(insn_id) { Insn::GetConstantPath { ic } => { let idlist: *const ID = unsafe { (*ic).segments }; let ice = unsafe { (*ic).entry }; @@ -1280,118 +1367,6 @@ impl Function { self.infer_types(); } - /// Optimize SendWithoutBlock that land in a C method to a direct CCall without - /// runtime lookup. - fn optimize_c_calls(&mut self) { - // Try to reduce one SendWithoutBlock to a CCall - fn reduce_to_ccall( - fun: &mut Function, - block: BlockId, - self_type: Type, - send: Insn, - send_insn_id: InsnId, - ) -> Result<(), ()> { - let Insn::SendWithoutBlock { mut self_val, cd, mut args, state, .. } = send else { - return Err(()); - }; - - let call_info = unsafe { (*cd).ci }; - let argc = unsafe { vm_ci_argc(call_info) }; - let method_id = unsafe { rb_vm_ci_mid(call_info) }; - - // If we have info about the class of the receiver - // - // TODO(alan): there was a seemingly a miscomp here if you swap with - // `inexact_ruby_class`. Theoretically it can call a method too general - // for the receiver. Confirm and add a test. - let (recv_class, guard_type) = if let Some(klass) = self_type.runtime_exact_ruby_class() { - (klass, None) - } else { - let iseq_insn_idx = fun.frame_state(state).insn_idx; - let Some(recv_type) = fun.profiled_type_of_at(self_val, iseq_insn_idx) else { return Err(()) }; - let Some(recv_class) = recv_type.exact_ruby_class() else { return Err(()) }; - (recv_class, Some(recv_type.unspecialized())) - }; - - // Do method lookup - let method = unsafe { rb_callable_method_entry(recv_class, method_id) }; - if method.is_null() { - return Err(()); - } - - // Filter for C methods - let def_type = unsafe { get_cme_def_type(method) }; - if def_type != VM_METHOD_TYPE_CFUNC { - return Err(()); - } - - // Find the `argc` (arity) of the C method, which describes the parameters it expects - let cfunc = unsafe { get_cme_def_body_cfunc(method) }; - let cfunc_argc = unsafe { get_mct_argc(cfunc) }; - match cfunc_argc { - 0.. => { - // (self, arg0, arg1, ..., argc) form - // - // Bail on argc mismatch - if argc != cfunc_argc as u32 { - return Err(()); - } - - // Filter for a leaf and GC free function - use crate::cruby_methods::FnProperties; - let Some(FnProperties { leaf: true, no_gc: true, return_type, elidable }) = - ZJITState::get_method_annotations().get_cfunc_properties(method) - else { - return Err(()); - }; - - let ci_flags = unsafe { vm_ci_flag(call_info) }; - // 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 })); - if let Some(guard_type) = guard_type { - // Guard receiver class - self_val = fun.push_insn(block, Insn::GuardType { val: self_val, guard_type, state }); - } - let cfun = unsafe { get_mct_func(cfunc) }.cast(); - let mut cfunc_args = vec![self_val]; - cfunc_args.append(&mut args); - let ccall = fun.push_insn(block, Insn::CCall { cfun, args: cfunc_args, name: method_id, return_type, elidable }); - fun.make_equal_to(send_insn_id, ccall); - return Ok(()); - } - } - -1 => { - // (argc, argv, self) parameter form - // Falling through for now - } - -2 => { - // (self, args_ruby_array) parameter form - // Falling through for now - } - _ => unreachable!("unknown cfunc kind: argc={argc}") - } - - Err(()) - } - - for block in self.rpo() { - let old_insns = std::mem::take(&mut self.blocks[block.0].insns); - assert!(self.blocks[block.0].insns.is_empty()); - for insn_id in old_insns { - if let send @ Insn::SendWithoutBlock { self_val, .. } = self.find(insn_id) { - let self_type = self.type_of(self_val); - if reduce_to_ccall(self, block, self_type, send, insn_id).is_ok() { - continue; - } - } - self.push_insn_id(block, insn_id); - } - } - self.infer_types(); - } - /// Fold a binary operator on fixnums. fn fold_fixnum_bop(&mut self, insn_id: InsnId, left: InsnId, right: InsnId, f: impl FnOnce(Option, Option) -> Option) -> InsnId { f(self.type_of(left).fixnum_value(), self.type_of(right).fixnum_value()) @@ -1613,9 +1588,8 @@ impl Function { worklist.push_back(val); worklist.push_back(state); } - Insn::Send { self_val, args, state, .. } - | Insn::SendWithoutBlock { self_val, args, state, .. } - | Insn::SendWithoutBlockDirect { self_val, args, state, .. } => { + Insn::CallIseq { self_val, args, state, .. } + | Insn::CallCFunc { self_val, args, state, .. } => { worklist.push_back(self_val); worklist.extend(args); worklist.push_back(state); @@ -1636,6 +1610,17 @@ impl Function { worklist.push_back(state); } Insn::SideExit { state } => worklist.push_back(state), + Insn::LookupMethod { self_val, state, .. } => { + worklist.push_back(self_val); + worklist.push_back(state); + } + Insn::CallMethod { callable, self_val, args, state, .. } + | Insn::CallMethodWithBlock { callable, self_val, args, state, .. } => { + worklist.push_back(callable); + worklist.push_back(self_val); + worklist.extend(args); + worklist.push_back(state); + } } } // Now remove all unnecessary instructions @@ -1680,8 +1665,9 @@ impl Function { /// Run all the optimization passes we have. pub fn optimize(&mut self) { // Function is assumed to have types inferred already + self.optimize_constants(); + self.optimize_lookup_method(); self.optimize_direct_sends(); - self.optimize_c_calls(); self.fold_constants(); self.eliminate_dead_code(); @@ -2306,24 +2292,20 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; - - let method_name = unsafe { - let mid = rb_vm_ci_mid(call_info); - mid.contents_lossy().into_owned() - }; - assert_eq!(1, argc, "opt_aref_with should only be emitted for argc=1"); + let method_id = unsafe { rb_vm_ci_mid(call_info) }; let aref_arg = fun.push_insn(block, Insn::Const { val: Const::Value(get_arg(pc, 0)) }); let args = vec![aref_arg]; let recv = state.stack_pop()?; let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - let send = fun.push_insn(block, Insn::SendWithoutBlock { self_val: recv, call_info: CallInfo { method_name }, cd, args, state: exit_id }); + let lookup = fun.push_insn(block, Insn::LookupMethod { self_val: recv, method_id, state: exit_id }); + let send = fun.push_insn(block, Insn::CallMethod { callable: lookup, cd, self_val: recv, args, state: exit_id }); state.stack_push(send); } YARVINSN_opt_neq => { // NB: opt_neq has two cd; get_arg(0) is for eq and get_arg(1) is for neq - let cd: *const rb_call_data = get_arg(pc, 1).as_ptr(); + let cd: CallDataPtr = get_arg(pc, 1).as_ptr(); let call_info = unsafe { rb_get_call_data_ci(cd) }; if unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { // Unknown call type; side-exit into the interpreter @@ -2332,12 +2314,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; + let method_id = unsafe { rb_vm_ci_mid(call_info) }; - - let method_name = unsafe { - let mid = rb_vm_ci_mid(call_info); - mid.contents_lossy().into_owned() - }; let mut args = vec![]; for _ in 0..argc { args.push(state.stack_pop()?); @@ -2346,7 +2324,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let recv = state.stack_pop()?; let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - let send = fun.push_insn(block, Insn::SendWithoutBlock { self_val: recv, call_info: CallInfo { method_name }, cd, args, state: exit_id }); + let lookup = fun.push_insn(block, Insn::LookupMethod { self_val: recv, method_id, state: exit_id }); + let send = fun.push_insn(block, Insn::CallMethod { callable: lookup, cd, self_val: recv, args, state: exit_id }); state.stack_push(send); } @@ -2382,7 +2361,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { YARVINSN_opt_not | YARVINSN_opt_regexpmatch2 | YARVINSN_opt_send_without_block => { - let cd: *const rb_call_data = get_arg(pc, 0).as_ptr(); + let cd: CallDataPtr = get_arg(pc, 0).as_ptr(); let call_info = unsafe { rb_get_call_data_ci(cd) }; if unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { // Unknown call type; side-exit into the interpreter @@ -2391,12 +2370,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; + let method_id = unsafe { rb_vm_ci_mid(call_info) }; - - let method_name = unsafe { - let mid = rb_vm_ci_mid(call_info); - mid.contents_lossy().into_owned() - }; let mut args = vec![]; for _ in 0..argc { args.push(state.stack_pop()?); @@ -2405,11 +2380,12 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let recv = state.stack_pop()?; let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - let send = fun.push_insn(block, Insn::SendWithoutBlock { self_val: recv, call_info: CallInfo { method_name }, cd, args, state: exit_id }); + let lookup = fun.push_insn(block, Insn::LookupMethod { self_val: recv, method_id, state: exit_id }); + let send = fun.push_insn(block, Insn::CallMethod { callable: lookup, cd, self_val: recv, args, state: exit_id }); state.stack_push(send); } YARVINSN_send => { - let cd: *const rb_call_data = get_arg(pc, 0).as_ptr(); + let cd: CallDataPtr = get_arg(pc, 0).as_ptr(); let blockiseq: IseqPtr = get_arg(pc, 1).as_iseq(); let call_info = unsafe { rb_get_call_data_ci(cd) }; if unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { @@ -2419,11 +2395,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; + let method_id = unsafe { rb_vm_ci_mid(call_info) }; - let method_name = unsafe { - let mid = rb_vm_ci_mid(call_info); - mid.contents_lossy().into_owned() - }; let mut args = vec![]; for _ in 0..argc { args.push(state.stack_pop()?); @@ -2432,7 +2405,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let recv = state.stack_pop()?; let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - let send = fun.push_insn(block, Insn::Send { self_val: recv, call_info: CallInfo { method_name }, cd, blockiseq, args, state: exit_id }); + let lookup = fun.push_insn(block, Insn::LookupMethod { self_val: recv, method_id, state: exit_id }); + let send = fun.push_insn(block, Insn::CallMethodWithBlock { self_val: recv, callable: lookup, cd, blockiseq, args, state: exit_id }); state.stack_push(send); } YARVINSN_getinstancevariable => { @@ -3009,8 +2983,9 @@ mod tests { bb0(v0:BasicObject): v2:Fixnum[1] = Const Value(1) v3:Fixnum[2] = Const Value(2) - v5:BasicObject = SendWithoutBlock v2, :+, v3 - Return v5 + v5:CallableMethodEntry = LookupMethod v2, :+ + v6:BasicObject = CallMethod v5 (:+), v2, v3 + Return v6 "#]]); } @@ -3123,8 +3098,9 @@ mod tests { assert_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): - v5:BasicObject = SendWithoutBlock v1, :+, v2 - Return v5 + v5:CallableMethodEntry = LookupMethod v1, :+ + v6:BasicObject = CallMethod v5 (:+), v1, v2 + Return v6 "#]]); } @@ -3137,8 +3113,9 @@ mod tests { assert_method_hir_with_opcode("test", YARVINSN_opt_minus, expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): - v5:BasicObject = SendWithoutBlock v1, :-, v2 - Return v5 + v5:CallableMethodEntry = LookupMethod v1, :- + v6:BasicObject = CallMethod v5 (:-), v1, v2 + Return v6 "#]]); } @@ -3151,8 +3128,9 @@ mod tests { assert_method_hir_with_opcode("test", YARVINSN_opt_mult, expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): - v5:BasicObject = SendWithoutBlock v1, :*, v2 - Return v5 + v5:CallableMethodEntry = LookupMethod v1, :* + v6:BasicObject = CallMethod v5 (:*), v1, v2 + Return v6 "#]]); } @@ -3165,8 +3143,9 @@ mod tests { assert_method_hir_with_opcode("test", YARVINSN_opt_div, expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): - v5:BasicObject = SendWithoutBlock v1, :/, v2 - Return v5 + v5:CallableMethodEntry = LookupMethod v1, :/ + v6:BasicObject = CallMethod v5 (:/), v1, v2 + Return v6 "#]]); } @@ -3179,8 +3158,9 @@ mod tests { assert_method_hir_with_opcode("test", YARVINSN_opt_mod, expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): - v5:BasicObject = SendWithoutBlock v1, :%, v2 - Return v5 + v5:CallableMethodEntry = LookupMethod v1, :% + v6:BasicObject = CallMethod v5 (:%), v1, v2 + Return v6 "#]]); } @@ -3193,8 +3173,9 @@ mod tests { assert_method_hir_with_opcode("test", YARVINSN_opt_eq, expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): - v5:BasicObject = SendWithoutBlock v1, :==, v2 - Return v5 + v5:CallableMethodEntry = LookupMethod v1, :== + v6:BasicObject = CallMethod v5 (:==), v1, v2 + Return v6 "#]]); } @@ -3207,8 +3188,9 @@ mod tests { assert_method_hir_with_opcode("test", YARVINSN_opt_neq, expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): - v5:BasicObject = SendWithoutBlock v1, :!=, v2 - Return v5 + v5:CallableMethodEntry = LookupMethod v1, :!= + v6:BasicObject = CallMethod v5 (:!=), v1, v2 + Return v6 "#]]); } @@ -3221,8 +3203,9 @@ mod tests { assert_method_hir_with_opcode("test", YARVINSN_opt_lt, expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): - v5:BasicObject = SendWithoutBlock v1, :<, v2 - Return v5 + v5:CallableMethodEntry = LookupMethod v1, :< + v6:BasicObject = CallMethod v5 (:<), v1, v2 + Return v6 "#]]); } @@ -3235,8 +3218,9 @@ mod tests { assert_method_hir_with_opcode("test", YARVINSN_opt_le, expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): - v5:BasicObject = SendWithoutBlock v1, :<=, v2 - Return v5 + v5:CallableMethodEntry = LookupMethod v1, :<= + v6:BasicObject = CallMethod v5 (:<=), v1, v2 + Return v6 "#]]); } @@ -3249,8 +3233,9 @@ mod tests { assert_method_hir_with_opcode("test", YARVINSN_opt_gt, expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): - v5:BasicObject = SendWithoutBlock v1, :>, v2 - Return v5 + v5:CallableMethodEntry = LookupMethod v1, :> + v6:BasicObject = CallMethod v5 (:>), v1, v2 + Return v6 "#]]); } @@ -3278,17 +3263,20 @@ mod tests { Jump bb2(v0, v4, v5) bb2(v7:BasicObject, v8:BasicObject, v9:BasicObject): v11:Fixnum[0] = Const Value(0) - v13:BasicObject = SendWithoutBlock v9, :>, v11 - v14:CBool = Test v13 - IfTrue v14, bb1(v7, v8, v9) - v16:NilClassExact = Const Value(nil) + v13:CallableMethodEntry = LookupMethod v9, :> + v14:BasicObject = CallMethod v13 (:>), v9, v11 + v15:CBool = Test v14 + IfTrue v15, bb1(v7, v8, v9) + v17:NilClassExact = Const Value(nil) Return v8 - bb1(v18:BasicObject, v19:BasicObject, v20:BasicObject): - v22:Fixnum[1] = Const Value(1) - v24:BasicObject = SendWithoutBlock v19, :+, v22 - v25:Fixnum[1] = Const Value(1) - v27:BasicObject = SendWithoutBlock v20, :-, v25 - Jump bb2(v18, v24, v27) + bb1(v19:BasicObject, v20:BasicObject, v21:BasicObject): + v23:Fixnum[1] = Const Value(1) + v25:CallableMethodEntry = LookupMethod v20, :+ + v26:BasicObject = CallMethod v25 (:+), v20, v23 + v27:Fixnum[1] = Const Value(1) + v29:CallableMethodEntry = LookupMethod v21, :- + v30:BasicObject = CallMethod v29 (:-), v21, v27 + Jump bb2(v19, v26, v30) "#]]); } @@ -3301,8 +3289,9 @@ mod tests { assert_method_hir_with_opcode("test", YARVINSN_opt_ge, expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): - v5:BasicObject = SendWithoutBlock v1, :>=, v2 - Return v5 + v5:CallableMethodEntry = LookupMethod v1, :>= + v6:BasicObject = CallMethod v5 (:>=), v1, v2 + Return v6 "#]]); } @@ -3348,8 +3337,9 @@ mod tests { bb0(v0:BasicObject): v2:Fixnum[2] = Const Value(2) v3:Fixnum[3] = Const Value(3) - v5:BasicObject = SendWithoutBlock v0, :bar, v2, v3 - Return v5 + v5:CallableMethodEntry = LookupMethod v0, :bar + v6:BasicObject = CallMethod v5 (:bar), v0, v2, v3 + Return v6 "#]]); } @@ -3366,8 +3356,9 @@ mod tests { assert_method_hir_with_opcode("test", YARVINSN_send, expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject): - v4:BasicObject = Send v1, 0x1000, :each - Return v4 + v4:CallableMethodEntry = LookupMethod v1, :each + v5:BasicObject = CallMethodWithBlock v4 (:each), 0x1000, v1 + Return v5 "#]]); } @@ -3387,8 +3378,9 @@ mod tests { v9:StringExact = StringCopy v8 v10:StringExact[VALUE(0x1010)] = Const Value(VALUE(0x1010)) v11:StringExact = StringCopy v10 - v13:BasicObject = SendWithoutBlock v0, :unknown_method, v4, v7, v9, v11 - Return v13 + v13:CallableMethodEntry = LookupMethod v0, :unknown_method + v14:BasicObject = CallMethod v13 (:unknown_method), v0, v4, v7, v9, v11 + Return v14 "#]]); } @@ -3534,10 +3526,11 @@ mod tests { v3:NilClassExact = Const Value(nil) Jump bb1(v0, v3, v2) bb1(v5:BasicObject, v6:NilClassExact, v7:BasicObject): - v10:BasicObject = SendWithoutBlock v7, :new - Jump bb2(v5, v10, v6) - bb2(v12:BasicObject, v13:BasicObject, v14:NilClassExact): - Return v13 + v10:CallableMethodEntry = LookupMethod v7, :new + v11:BasicObject = CallMethod v10 (:new), v7 + Jump bb2(v5, v11, v6) + bb2(v13:BasicObject, v14:BasicObject, v15:NilClassExact): + Return v14 "#]]); } @@ -3585,7 +3578,8 @@ mod tests { bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): v3:NilClassExact = Const Value(nil) v4:NilClassExact = Const Value(nil) - v7:BasicObject = SendWithoutBlock v1, :+, v2 + v7:CallableMethodEntry = LookupMethod v1, :+ + v8:BasicObject = CallMethod v7 (:+), v1, v2 SideExit "#]]); } @@ -3605,7 +3599,8 @@ mod tests { bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): v3:NilClassExact = Const Value(nil) v4:NilClassExact = Const Value(nil) - v7:BasicObject = SendWithoutBlock v1, :+, v2 + v7:CallableMethodEntry = LookupMethod v1, :+ + v8:BasicObject = CallMethod v7 (:+), v1, v2 SideExit "#]]); } @@ -3625,9 +3620,10 @@ mod tests { bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): v3:NilClassExact = Const Value(nil) v4:NilClassExact = Const Value(nil) - v7:BasicObject = SendWithoutBlock v1, :+, v2 - v8:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v9:StringExact = StringCopy v8 + v7:CallableMethodEntry = LookupMethod v1, :+ + v8:BasicObject = CallMethod v7 (:+), v1, v2 + v9:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v10:StringExact = StringCopy v9 SideExit "#]]); } @@ -3649,7 +3645,8 @@ mod tests { bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): v3:NilClassExact = Const Value(nil) v4:NilClassExact = Const Value(nil) - v7:BasicObject = SendWithoutBlock v1, :+, v2 + v7:CallableMethodEntry = LookupMethod v1, :+ + v8:BasicObject = CallMethod v7 (:+), v1, v2 SideExit "#]]); } @@ -3663,8 +3660,9 @@ mod tests { fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): v5:ArrayExact = NewArray v1, v2 - v7:BasicObject = SendWithoutBlock v5, :length - Return v7 + v7:CallableMethodEntry = LookupMethod v5, :length + v8:BasicObject = CallMethod v7 (:length), v5 + Return v8 "#]]); } @@ -3677,8 +3675,9 @@ mod tests { fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): v5:ArrayExact = NewArray v1, v2 - v7:BasicObject = SendWithoutBlock v5, :size - Return v7 + v7:CallableMethodEntry = LookupMethod v5, :size + v8:BasicObject = CallMethod v7 (:size), v5 + Return v8 "#]]); } @@ -3784,7 +3783,8 @@ mod tests { bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): v4:NilClassExact = Const Value(nil) v5:Fixnum[1] = Const Value(1) - v7:BasicObject = SendWithoutBlock v1, :[]=, v2, v5 + v7:CallableMethodEntry = LookupMethod v1, :[]= + v8:BasicObject = CallMethod v7 (:[]=), v1, v2, v5 Return v5 "#]]); } @@ -3797,8 +3797,9 @@ mod tests { assert_method_hir_with_opcode("test", YARVINSN_opt_aref, expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): - v5:BasicObject = SendWithoutBlock v1, :[], v2 - Return v5 + v5:CallableMethodEntry = LookupMethod v1, :[] + v6:BasicObject = CallMethod v5 (:[]), v1, v2 + Return v6 "#]]); } @@ -3811,8 +3812,9 @@ mod tests { fn test: bb0(v0:BasicObject, v1:BasicObject): v3:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v5:BasicObject = SendWithoutBlock v1, :[], v3 - Return v5 + v5:CallableMethodEntry = LookupMethod v1, :[] + v6:BasicObject = CallMethod v5 (:[]), v1, v3 + Return v6 "#]]); } @@ -3824,8 +3826,9 @@ mod tests { assert_method_hir_with_opcode("test", YARVINSN_opt_empty_p, expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject): - v4:BasicObject = SendWithoutBlock v1, :empty? - Return v4 + v4:CallableMethodEntry = LookupMethod v1, :empty? + v5:BasicObject = CallMethod v4 (:empty?), v1 + Return v5 "#]]); } @@ -3837,8 +3840,9 @@ mod tests { assert_method_hir_with_opcode("test", YARVINSN_opt_succ, expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject): - v4:BasicObject = SendWithoutBlock v1, :succ - Return v4 + v4:CallableMethodEntry = LookupMethod v1, :succ + v5:BasicObject = CallMethod v4 (:succ), v1 + Return v5 "#]]); } @@ -3850,8 +3854,9 @@ mod tests { assert_method_hir_with_opcode("test", YARVINSN_opt_and, expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): - v5:BasicObject = SendWithoutBlock v1, :&, v2 - Return v5 + v5:CallableMethodEntry = LookupMethod v1, :& + v6:BasicObject = CallMethod v5 (:&), v1, v2 + Return v6 "#]]); } @@ -3863,8 +3868,9 @@ mod tests { assert_method_hir_with_opcode("test", YARVINSN_opt_or, expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): - v5:BasicObject = SendWithoutBlock v1, :|, v2 - Return v5 + v5:CallableMethodEntry = LookupMethod v1, :| + v6:BasicObject = CallMethod v5 (:|), v1, v2 + Return v6 "#]]); } @@ -3876,8 +3882,9 @@ mod tests { assert_method_hir_with_opcode("test", YARVINSN_opt_not, expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject): - v4:BasicObject = SendWithoutBlock v1, :! - Return v4 + v4:CallableMethodEntry = LookupMethod v1, :! + v5:BasicObject = CallMethod v4 (:!), v1 + Return v5 "#]]); } @@ -3889,8 +3896,9 @@ mod tests { assert_method_hir_with_opcode("test", YARVINSN_opt_regexpmatch2, expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): - v5:BasicObject = SendWithoutBlock v1, :=~, v2 - Return v5 + v5:CallableMethodEntry = LookupMethod v1, :=~ + v6:BasicObject = CallMethod v5 (:=~), v1, v2 + Return v6 "#]]); } @@ -3904,10 +3912,11 @@ mod tests { bb0(v0:BasicObject, v1:BasicObject): v3:CBool = IsNil v1 IfTrue v3, bb1(v0, v1, v1) - v6:BasicObject = SendWithoutBlock v1, :itself - Jump bb1(v0, v1, v6) - bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject): - Return v10 + v6:CallableMethodEntry = LookupMethod v1, :itself + v7:BasicObject = CallMethod v6 (:itself), v1 + Jump bb1(v0, v1, v7) + bb1(v9:BasicObject, v10:BasicObject, v11:BasicObject): + Return v11 "#]]); } } @@ -3980,10 +3989,11 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, +@0x1008) PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) - v15:Fixnum[6] = Const Value(6) - Return v15 + v19:Fixnum[6] = Const Value(6) + Return v19 "#]]); } @@ -3997,10 +4007,11 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, -@0x1008) PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MINUS) PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MINUS) - v15:Fixnum[1] = Const Value(1) - Return v15 + v19:Fixnum[1] = Const Value(1) + Return v19 "#]]); } @@ -4014,9 +4025,10 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, *@0x1008) PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MULT) - v9:Fixnum[42] = Const Value(42) - Return v9 + v12:Fixnum[42] = Const Value(42) + Return v12 "#]]); } @@ -4032,15 +4044,19 @@ mod opt_tests { fn test: bb0(v0:BasicObject, v1:BasicObject): v3:Fixnum[0] = Const Value(0) + PatchPoint MethodRedefined(Integer@0x1000, *@0x1008) PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MULT) - v13:Fixnum = GuardType v1, Fixnum - v20:Fixnum[0] = Const Value(0) - v6:Fixnum[0] = Const Value(0) + v24:Fixnum = GuardType v1, Fixnum + v31:Fixnum[0] = Const Value(0) + v7:Fixnum[0] = Const Value(0) + PatchPoint MethodRedefined(Integer@0x1000, *@0x1008) + v18:Fixnum = GuardType v1, Fixnum PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MULT) - v16:Fixnum = GuardType v1, Fixnum + v27:Fixnum = GuardType v1, Fixnum + PatchPoint MethodRedefined(Integer@0x1000, +@0x1009) PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) - v22:Fixnum[0] = Const Value(0) - Return v22 + v33:Fixnum[0] = Const Value(0) + Return v33 "#]]); } @@ -4058,9 +4074,10 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, <@0x1008) PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT) - v8:Fixnum[3] = Const Value(3) - Return v8 + v9:Fixnum[3] = Const Value(3) + Return v9 "#]]); } @@ -4078,10 +4095,12 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, <=@0x1008) PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LE) + PatchPoint MethodRedefined(Integer@0x1000, <=@0x1008) PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LE) - v14:Fixnum[3] = Const Value(3) - Return v14 + v16:Fixnum[3] = Const Value(3) + Return v16 "#]]); } @@ -4099,9 +4118,10 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, >@0x1008) PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GT) - v8:Fixnum[3] = Const Value(3) - Return v8 + v9:Fixnum[3] = Const Value(3) + Return v9 "#]]); } @@ -4119,10 +4139,12 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, >=@0x1008) PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GE) + PatchPoint MethodRedefined(Integer@0x1000, >=@0x1008) PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GE) - v14:Fixnum[3] = Const Value(3) - Return v14 + v16:Fixnum[3] = Const Value(3) + Return v16 "#]]); } @@ -4140,11 +4162,12 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, ==@0x1008) PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) Jump bb1(v0) - bb1(v10:BasicObject): - v12:Fixnum[4] = Const Value(4) - Return v12 + bb1(v11:BasicObject): + v13:Fixnum[4] = Const Value(4) + Return v13 "#]]); } @@ -4162,9 +4185,10 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, ==@0x1008) PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) - v8:Fixnum[3] = Const Value(3) - Return v8 + v9:Fixnum[3] = Const Value(3) + Return v9 "#]]); } @@ -4182,10 +4206,11 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, !=@0x1008) PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_NEQ) - v8:Fixnum[3] = Const Value(3) - Return v8 + v9:Fixnum[3] = Const Value(3) + Return v9 "#]]); } @@ -4203,12 +4228,13 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, !=@0x1008) PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_NEQ) Jump bb1(v0) - bb1(v10:BasicObject): - v12:Fixnum[4] = Const Value(4) - Return v12 + bb1(v11:BasicObject): + v13:Fixnum[4] = Const Value(4) + Return v13 "#]]); } @@ -4224,10 +4250,12 @@ mod opt_tests { fn test: bb0(v0:BasicObject, v1:BasicObject): v3:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Integer@0x1000, +@0x1008) + v9:Fixnum = GuardType v1, Fixnum PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) - v8:Fixnum = GuardType v1, Fixnum - v9:Fixnum = FixnumAdd v8, v3 - Return v9 + v12:Fixnum = GuardType v1, Fixnum + v13:Fixnum = FixnumAdd v12, v3 + Return v13 "#]]); } @@ -4291,9 +4319,8 @@ mod opt_tests { fn test: bb0(v0:BasicObject): PatchPoint MethodRedefined(Object@0x1000, foo@0x1008) - v6:BasicObject[VALUE(0x1010)] = GuardBitEquals v0, VALUE(0x1010) - v7:BasicObject = SendWithoutBlockDirect v6, :foo (0x1018) - Return v7 + v9:BasicObject = CallIseq 0x1010 (:foo), v0 + Return v9 "#]]); } @@ -4311,8 +4338,9 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject): - v3:BasicObject = SendWithoutBlock v0, :foo - Return v3 + v3:CallableMethodEntry = LookupMethod v0, :foo + v4:BasicObject = CallMethod v3 (:foo), v0 + Return v4 "#]]); } @@ -4331,9 +4359,8 @@ mod opt_tests { fn test: bb0(v0:BasicObject): PatchPoint MethodRedefined(Object@0x1000, foo@0x1008) - v6:BasicObject[VALUE(0x1010)] = GuardBitEquals v0, VALUE(0x1010) - v7:BasicObject = SendWithoutBlockDirect v6, :foo (0x1018) - Return v7 + v9:BasicObject = CallIseq 0x1010 (:foo), v0 + Return v9 "#]]); } @@ -4350,9 +4377,8 @@ mod opt_tests { bb0(v0:BasicObject): v2:Fixnum[3] = Const Value(3) PatchPoint MethodRedefined(Object@0x1000, Integer@0x1008) - v7:BasicObject[VALUE(0x1010)] = GuardBitEquals v0, VALUE(0x1010) - v8:BasicObject = SendWithoutBlockDirect v7, :Integer (0x1018), v2 - Return v8 + v10:BasicObject = CallIseq 0x1010 (:Integer), v0, v2 + Return v10 "#]]); } @@ -4372,9 +4398,8 @@ mod opt_tests { v2:Fixnum[1] = Const Value(1) v3:Fixnum[2] = Const Value(2) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008) - v8:BasicObject[VALUE(0x1010)] = GuardBitEquals v0, VALUE(0x1010) - v9:BasicObject = SendWithoutBlockDirect v8, :foo (0x1018), v2, v3 - Return v9 + v11:BasicObject = CallIseq 0x1010 (:foo), v0, v2, v3 + Return v11 "#]]); } @@ -4395,12 +4420,10 @@ mod opt_tests { fn test: bb0(v0:BasicObject): PatchPoint MethodRedefined(Object@0x1000, foo@0x1008) - v8:BasicObject[VALUE(0x1010)] = GuardBitEquals v0, VALUE(0x1010) - v9:BasicObject = SendWithoutBlockDirect v8, :foo (0x1018) - PatchPoint MethodRedefined(Object@0x1000, bar@0x1020) - v11:BasicObject[VALUE(0x1010)] = GuardBitEquals v0, VALUE(0x1010) - v12:BasicObject = SendWithoutBlockDirect v11, :bar (0x1018) - Return v12 + v15:BasicObject = CallIseq 0x1010 (:foo), v0 + PatchPoint MethodRedefined(Object@0x1000, bar@0x1018) + v16:BasicObject = CallIseq 0x1010 (:bar), v0 + Return v16 "#]]); } @@ -4413,11 +4436,13 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, +@0x1008) + v9:Fixnum = GuardType v1, Fixnum PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) - v8:Fixnum = GuardType v1, Fixnum - v9:Fixnum = GuardType v2, Fixnum - v10:Fixnum = FixnumAdd v8, v9 - Return v10 + v12:Fixnum = GuardType v1, Fixnum + v13:Fixnum = GuardType v2, Fixnum + v14:Fixnum = FixnumAdd v12, v13 + Return v14 "#]]); } @@ -4431,10 +4456,12 @@ mod opt_tests { fn test: bb0(v0:BasicObject, v1:BasicObject): v3:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Integer@0x1000, +@0x1008) + v9:Fixnum = GuardType v1, Fixnum PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) - v8:Fixnum = GuardType v1, Fixnum - v9:Fixnum = FixnumAdd v8, v3 - Return v9 + v12:Fixnum = GuardType v1, Fixnum + v13:Fixnum = FixnumAdd v12, v3 + Return v13 "#]]); } @@ -4448,10 +4475,11 @@ mod opt_tests { fn test: bb0(v0:BasicObject, v1:BasicObject): v3:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Integer@0x1000, +@0x1008) PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) - v8:Fixnum = GuardType v1, Fixnum - v9:Fixnum = FixnumAdd v3, v8 - Return v9 + v11:Fixnum = GuardType v1, Fixnum + v12:Fixnum = FixnumAdd v3, v11 + Return v12 "#]]); } @@ -4464,11 +4492,13 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, <@0x1008) + v9:Fixnum = GuardType v1, Fixnum PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT) - v8:Fixnum = GuardType v1, Fixnum - v9:Fixnum = GuardType v2, Fixnum - v10:BoolExact = FixnumLt v8, v9 - Return v10 + v12:Fixnum = GuardType v1, Fixnum + v13:Fixnum = GuardType v2, Fixnum + v14:BoolExact = FixnumLt v12, v13 + Return v14 "#]]); } @@ -4482,10 +4512,12 @@ mod opt_tests { fn test: bb0(v0:BasicObject, v1:BasicObject): v3:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Integer@0x1000, <@0x1008) + v9:Fixnum = GuardType v1, Fixnum PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT) - v8:Fixnum = GuardType v1, Fixnum - v9:BoolExact = FixnumLt v8, v3 - Return v9 + v12:Fixnum = GuardType v1, Fixnum + v13:BoolExact = FixnumLt v12, v3 + Return v13 "#]]); } @@ -4499,10 +4531,11 @@ mod opt_tests { fn test: bb0(v0:BasicObject, v1:BasicObject): v3:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Integer@0x1000, <@0x1008) PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT) - v8:Fixnum = GuardType v1, Fixnum - v9:BoolExact = FixnumLt v3, v8 - Return v9 + v11:Fixnum = GuardType v1, Fixnum + v12:BoolExact = FixnumLt v3, v11 + Return v12 "#]]); } @@ -4656,6 +4689,27 @@ mod opt_tests { "#]]); } + #[test] + fn test_optimize_fixnum_add() { + eval(" + def test(a, b) + a + b + end + test(1, 2); test(3, 4) + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, +@0x1008) + v9:Fixnum = GuardType v1, Fixnum + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) + v12:Fixnum = GuardType v1, Fixnum + v13:Fixnum = GuardType v2, Fixnum + v14:Fixnum = FixnumAdd v12, v13 + Return v14 + "#]]); + } + #[test] fn test_eliminate_fixnum_add() { eval(" @@ -4668,11 +4722,13 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, +@0x1008) + v10:Fixnum = GuardType v1, Fixnum PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) - v9:Fixnum = GuardType v1, Fixnum - v10:Fixnum = GuardType v2, Fixnum - v6:Fixnum[5] = Const Value(5) - Return v6 + v13:Fixnum = GuardType v1, Fixnum + v14:Fixnum = GuardType v2, Fixnum + v7:Fixnum[5] = Const Value(5) + Return v7 "#]]); } @@ -4688,11 +4744,13 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, -@0x1008) + v10:Fixnum = GuardType v1, Fixnum PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MINUS) - v9:Fixnum = GuardType v1, Fixnum - v10:Fixnum = GuardType v2, Fixnum - v6:Fixnum[5] = Const Value(5) - Return v6 + v13:Fixnum = GuardType v1, Fixnum + v14:Fixnum = GuardType v2, Fixnum + v7:Fixnum[5] = Const Value(5) + Return v7 "#]]); } @@ -4708,11 +4766,13 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, *@0x1008) + v10:Fixnum = GuardType v1, Fixnum PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MULT) - v9:Fixnum = GuardType v1, Fixnum - v10:Fixnum = GuardType v2, Fixnum - v6:Fixnum[5] = Const Value(5) - Return v6 + v13:Fixnum = GuardType v1, Fixnum + v14:Fixnum = GuardType v2, Fixnum + v7:Fixnum[5] = Const Value(5) + Return v7 "#]]); } @@ -4728,12 +4788,14 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, /@0x1008) + v10:Fixnum = GuardType v1, Fixnum PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_DIV) - v9:Fixnum = GuardType v1, Fixnum - v10:Fixnum = GuardType v2, Fixnum - v11:Fixnum = FixnumDiv v9, v10 - v6:Fixnum[5] = Const Value(5) - Return v6 + v13:Fixnum = GuardType v1, Fixnum + v14:Fixnum = GuardType v2, Fixnum + v15:Fixnum = FixnumDiv v13, v14 + v7:Fixnum[5] = Const Value(5) + Return v7 "#]]); } @@ -4749,12 +4811,14 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, %@0x1008) + v10:Fixnum = GuardType v1, Fixnum PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MOD) - v9:Fixnum = GuardType v1, Fixnum - v10:Fixnum = GuardType v2, Fixnum - v11:Fixnum = FixnumMod v9, v10 - v6:Fixnum[5] = Const Value(5) - Return v6 + v13:Fixnum = GuardType v1, Fixnum + v14:Fixnum = GuardType v2, Fixnum + v15:Fixnum = FixnumMod v13, v14 + v7:Fixnum[5] = Const Value(5) + Return v7 "#]]); } @@ -4770,11 +4834,13 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, <@0x1008) + v10:Fixnum = GuardType v1, Fixnum PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT) - v9:Fixnum = GuardType v1, Fixnum - v10:Fixnum = GuardType v2, Fixnum - v6:Fixnum[5] = Const Value(5) - Return v6 + v13:Fixnum = GuardType v1, Fixnum + v14:Fixnum = GuardType v2, Fixnum + v7:Fixnum[5] = Const Value(5) + Return v7 "#]]); } @@ -4790,11 +4856,13 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, <=@0x1008) + v10:Fixnum = GuardType v1, Fixnum PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LE) - v9:Fixnum = GuardType v1, Fixnum - v10:Fixnum = GuardType v2, Fixnum - v6:Fixnum[5] = Const Value(5) - Return v6 + v13:Fixnum = GuardType v1, Fixnum + v14:Fixnum = GuardType v2, Fixnum + v7:Fixnum[5] = Const Value(5) + Return v7 "#]]); } @@ -4810,11 +4878,13 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, >@0x1008) + v10:Fixnum = GuardType v1, Fixnum PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GT) - v9:Fixnum = GuardType v1, Fixnum - v10:Fixnum = GuardType v2, Fixnum - v6:Fixnum[5] = Const Value(5) - Return v6 + v13:Fixnum = GuardType v1, Fixnum + v14:Fixnum = GuardType v2, Fixnum + v7:Fixnum[5] = Const Value(5) + Return v7 "#]]); } @@ -4830,11 +4900,13 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, >=@0x1008) + v10:Fixnum = GuardType v1, Fixnum PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GE) - v9:Fixnum = GuardType v1, Fixnum - v10:Fixnum = GuardType v2, Fixnum - v6:Fixnum[5] = Const Value(5) - Return v6 + v13:Fixnum = GuardType v1, Fixnum + v14:Fixnum = GuardType v2, Fixnum + v7:Fixnum[5] = Const Value(5) + Return v7 "#]]); } @@ -4850,11 +4922,13 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, ==@0x1008) + v10:Fixnum = GuardType v1, Fixnum PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) - v9:Fixnum = GuardType v1, Fixnum - v10:Fixnum = GuardType v2, Fixnum - v6:Fixnum[5] = Const Value(5) - Return v6 + v13:Fixnum = GuardType v1, Fixnum + v14:Fixnum = GuardType v2, Fixnum + v7:Fixnum[5] = Const Value(5) + Return v7 "#]]); } @@ -4870,12 +4944,14 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, !=@0x1008) + v10:Fixnum = GuardType v1, Fixnum PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_NEQ) - v10:Fixnum = GuardType v1, Fixnum - v11:Fixnum = GuardType v2, Fixnum - v6:Fixnum[5] = Const Value(5) - Return v6 + v14:Fixnum = GuardType v1, Fixnum + v15:Fixnum = GuardType v2, Fixnum + v7:Fixnum[5] = Const Value(5) + Return v7 "#]]); } @@ -4907,9 +4983,9 @@ mod opt_tests { fn test: bb0(v0:BasicObject, v1:BasicObject): PatchPoint MethodRedefined(Integer@0x1000, itself@0x1008) - v7:Fixnum = GuardType v1, Fixnum - v8:BasicObject = CCall itself@0x1010, v7 - Return v8 + v8:Fixnum = GuardType v1, Fixnum + v10:BasicObject = CallCFunc 0x1010 (:itself), v1 + Return v10 "#]]); } @@ -4923,8 +4999,8 @@ mod opt_tests { bb0(v0:BasicObject): v3:ArrayExact = NewArray PatchPoint MethodRedefined(Array@0x1000, itself@0x1008) - v8:BasicObject = CCall itself@0x1010, v3 - Return v8 + v10:BasicObject = CallCFunc 0x1010 (:itself), v3 + Return v10 "#]]); } @@ -4940,8 +5016,8 @@ mod opt_tests { fn test: bb0(v0:BasicObject): PatchPoint MethodRedefined(Array@0x1000, itself@0x1008) - v7:Fixnum[1] = Const Value(1) - Return v7 + v8:Fixnum[1] = Const Value(1) + Return v8 "#]]); } @@ -4961,8 +5037,8 @@ mod opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, M) PatchPoint MethodRedefined(Module@0x1008, name@0x1010) - v6:Fixnum[1] = Const Value(1) - Return v6 + v7:Fixnum[1] = Const Value(1) + Return v7 "#]]); } @@ -4978,8 +5054,8 @@ mod opt_tests { fn test: bb0(v0:BasicObject): PatchPoint MethodRedefined(Array@0x1000, length@0x1008) - v7:Fixnum[5] = Const Value(5) - Return v7 + v8:Fixnum[5] = Const Value(5) + Return v8 "#]]); } @@ -4995,8 +5071,8 @@ mod opt_tests { fn test: bb0(v0:BasicObject): PatchPoint MethodRedefined(Array@0x1000, size@0x1008) - v7:Fixnum[5] = Const Value(5) - Return v7 + v8:Fixnum[5] = Const Value(5) + Return v8 "#]]); } @@ -5013,8 +5089,9 @@ mod opt_tests { bb0(v0:BasicObject): v2:Fixnum[1] = Const Value(1) v3:Fixnum[0] = Const Value(0) - v5:BasicObject = SendWithoutBlock v2, :itself, v3 - Return v5 + PatchPoint MethodRedefined(Integer@0x1000, itself@0x1008) + v10:BasicObject = CallCFunc 0x1010 (:itself), v2, v3 + Return v10 "#]]); } @@ -5028,8 +5105,8 @@ mod opt_tests { bb0(v0:BasicObject, v1:BasicObject): v3:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(Integer@0x1000, zero?@0x1008) - v8:BasicObject = SendWithoutBlockDirect v3, :zero? (0x1010) - Return v8 + v10:BasicObject = CallIseq 0x1010 (:zero?), v3 + Return v10 "#]]); } @@ -5048,8 +5125,8 @@ mod opt_tests { v4:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v6:ArrayExact = ArrayDup v4 PatchPoint MethodRedefined(Array@0x1008, first@0x1010) - v11:BasicObject = SendWithoutBlockDirect v6, :first (0x1018) - Return v11 + v13:BasicObject = CallIseq 0x1018 (:first), v6 + Return v13 "#]]); } @@ -5066,8 +5143,8 @@ mod opt_tests { v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v3:StringExact = StringCopy v2 PatchPoint MethodRedefined(String@0x1008, bytesize@0x1010) - v8:Fixnum = CCall bytesize@0x1018, v3 - Return v8 + v10:Fixnum = CallCFunc 0x1018 (:bytesize), v3 + Return v10 "#]]); } @@ -5149,14 +5226,15 @@ mod opt_tests { bb0(v0:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, C) - v19:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v20:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) v3:NilClassExact = Const Value(nil) - Jump bb1(v0, v3, v19) + Jump bb1(v0, v3, v20) bb1(v5:BasicObject, v6:NilClassExact, v7:BasicObject[VALUE(0x1008)]): - v10:BasicObject = SendWithoutBlock v7, :new - Jump bb2(v5, v10, v6) - bb2(v12:BasicObject, v13:BasicObject, v14:NilClassExact): - Return v13 + PatchPoint MethodRedefined(Class@0x1010, new@0x1018) + v23:BasicObject = CallCFunc 0x1020 (:new), v7 + Jump bb2(v5, v23, v6) + bb2(v13:BasicObject, v14:BasicObject, v15:NilClassExact): + Return v14 "#]]); } @@ -5176,15 +5254,16 @@ mod opt_tests { bb0(v0:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, C) - v21:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v22:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) v3:NilClassExact = Const Value(nil) v4:Fixnum[1] = Const Value(1) - Jump bb1(v0, v3, v21, v4) + Jump bb1(v0, v3, v22, v4) bb1(v6:BasicObject, v7:NilClassExact, v8:BasicObject[VALUE(0x1008)], v9:Fixnum[1]): - v12:BasicObject = SendWithoutBlock v8, :new, v9 - Jump bb2(v6, v12, v7) - bb2(v14:BasicObject, v15:BasicObject, v16:NilClassExact): - Return v15 + PatchPoint MethodRedefined(Class@0x1010, new@0x1018) + v25:BasicObject = CallCFunc 0x1020 (:new), v8, v9 + Jump bb2(v6, v25, v7) + bb2(v15:BasicObject, v16:BasicObject, v17:NilClassExact): + Return v16 "#]]); } @@ -5198,8 +5277,8 @@ mod opt_tests { bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): v5:ArrayExact = NewArray v1, v2 PatchPoint MethodRedefined(Array@0x1000, length@0x1008) - v10:Fixnum = CCall length@0x1010, v5 - Return v10 + v12:Fixnum = CallCFunc 0x1010 (:length), v5 + Return v12 "#]]); } @@ -5213,8 +5292,8 @@ mod opt_tests { bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): v5:ArrayExact = NewArray v1, v2 PatchPoint MethodRedefined(Array@0x1000, size@0x1008) - v10:Fixnum = CCall size@0x1010, v5 - Return v10 + v12:Fixnum = CallCFunc 0x1010 (:size), v5 + Return v12 "#]]); }