diff options
Diffstat (limited to 'zjit/src')
-rw-r--r-- | zjit/src/cruby_bindings.inc.rs | 98 | ||||
-rw-r--r-- | zjit/src/hir.rs | 263 |
2 files changed, 296 insertions, 65 deletions
diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 169789ef77..637c356970 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -406,7 +406,7 @@ pub struct rb_shape { pub edges: *mut rb_id_table, pub edge_name: ID, pub next_iv_index: attr_index_t, - pub capacity: u32, + pub capacity: attr_index_t, pub type_: u8, pub heap_index: u8, pub parent_id: shape_id_t, @@ -929,21 +929,46 @@ unsafe extern "C" { pub fn rb_jit_cont_each_iseq(callback: rb_iseq_callback, data: *mut ::std::os::raw::c_void); pub fn rb_zjit_get_page_size() -> u32; pub fn rb_zjit_reserve_addr_space(mem_size: u32) -> *mut u8; - pub fn rb_RSTRING_LEN(str_: VALUE) -> ::std::os::raw::c_ulong; - pub fn rb_RSTRING_PTR(str_: VALUE) -> *mut ::std::os::raw::c_char; pub fn rb_zjit_profile_disable(iseq: *const rb_iseq_t); + pub fn rb_vm_base_ptr(cfp: *mut rb_control_frame_struct) -> *mut VALUE; + pub fn rb_zjit_multi_ractor_p() -> bool; + pub fn rb_zjit_constcache_shareable(ice: *const iseq_inline_constant_cache_entry) -> bool; + pub fn rb_zjit_vm_unlock( + recursive_lock_level: *mut ::std::os::raw::c_uint, + file: *const ::std::os::raw::c_char, + line: ::std::os::raw::c_int, + ); + pub fn rb_zjit_mark_writable(mem_block: *mut ::std::os::raw::c_void, mem_size: u32) -> bool; + pub fn rb_zjit_mark_executable(mem_block: *mut ::std::os::raw::c_void, mem_size: u32); + pub fn rb_zjit_mark_unused(mem_block: *mut ::std::os::raw::c_void, mem_size: u32) -> bool; + pub fn rb_zjit_icache_invalidate( + start: *mut ::std::os::raw::c_void, + end: *mut ::std::os::raw::c_void, + ); + pub fn rb_zjit_vm_lock_then_barrier( + recursive_lock_level: *mut ::std::os::raw::c_uint, + file: *const ::std::os::raw::c_char, + line: ::std::os::raw::c_int, + ); + pub fn rb_iseq_get_zjit_payload(iseq: *const rb_iseq_t) -> *mut ::std::os::raw::c_void; + pub fn rb_iseq_set_zjit_payload(iseq: *const rb_iseq_t, payload: *mut ::std::os::raw::c_void); + pub fn rb_zjit_print_exception(); pub fn rb_iseq_encoded_size(iseq: *const rb_iseq_t) -> ::std::os::raw::c_uint; - pub fn rb_iseq_opcode_at_pc(iseq: *const rb_iseq_t, pc: *const VALUE) -> ::std::os::raw::c_int; pub fn rb_iseq_pc_at_idx(iseq: *const rb_iseq_t, insn_idx: u32) -> *mut VALUE; + pub fn rb_iseq_opcode_at_pc(iseq: *const rb_iseq_t, pc: *const VALUE) -> ::std::os::raw::c_int; + pub fn rb_RSTRING_LEN(str_: VALUE) -> ::std::os::raw::c_ulong; + pub fn rb_RSTRING_PTR(str_: VALUE) -> *mut ::std::os::raw::c_char; pub fn rb_insn_name(insn: VALUE) -> *const ::std::os::raw::c_char; - pub fn rb_get_ec_cfp(ec: *const rb_execution_context_t) -> *mut rb_control_frame_struct; - pub fn rb_get_cfp_iseq(cfp: *mut rb_control_frame_struct) -> *const rb_iseq_t; - pub fn rb_get_cfp_pc(cfp: *mut rb_control_frame_struct) -> *mut VALUE; - pub fn rb_get_cfp_sp(cfp: *mut rb_control_frame_struct) -> *mut VALUE; - pub fn rb_get_cfp_self(cfp: *mut rb_control_frame_struct) -> VALUE; - pub fn rb_get_cfp_ep(cfp: *mut rb_control_frame_struct) -> *mut VALUE; - pub fn rb_get_cfp_ep_level(cfp: *mut rb_control_frame_struct, lv: u32) -> *const VALUE; - pub fn rb_vm_base_ptr(cfp: *mut rb_control_frame_struct) -> *mut VALUE; + pub fn rb_vm_ci_argc(ci: *const rb_callinfo) -> ::std::os::raw::c_uint; + pub fn rb_vm_ci_mid(ci: *const rb_callinfo) -> ID; + pub fn rb_vm_ci_flag(ci: *const rb_callinfo) -> ::std::os::raw::c_uint; + pub fn rb_vm_ci_kwarg(ci: *const rb_callinfo) -> *const rb_callinfo_kwarg; + pub fn rb_get_cikw_keyword_len(cikw: *const rb_callinfo_kwarg) -> ::std::os::raw::c_int; + pub fn rb_get_cikw_keywords_idx( + cikw: *const rb_callinfo_kwarg, + idx: ::std::os::raw::c_int, + ) -> VALUE; + pub fn rb_METHOD_ENTRY_VISI(me: *const rb_callable_method_entry_t) -> rb_method_visibility_t; pub fn rb_get_cme_def_type(cme: *const rb_callable_method_entry_t) -> rb_method_type_t; pub fn rb_get_cme_def_body_attr_id(cme: *const rb_callable_method_entry_t) -> ID; pub fn rb_get_cme_def_body_optimized_type( @@ -961,6 +986,7 @@ unsafe extern "C" { pub fn rb_get_mct_func(mct: *const rb_method_cfunc_t) -> *mut ::std::os::raw::c_void; pub fn rb_get_def_iseq_ptr(def: *mut rb_method_definition_t) -> *const rb_iseq_t; pub fn rb_get_iseq_body_local_iseq(iseq: *const rb_iseq_t) -> *const rb_iseq_t; + pub fn rb_get_iseq_body_local_table_size(iseq: *const rb_iseq_t) -> ::std::os::raw::c_uint; pub fn rb_get_iseq_body_iseq_encoded(iseq: *const rb_iseq_t) -> *mut VALUE; pub fn rb_get_iseq_body_stack_max(iseq: *const rb_iseq_t) -> ::std::os::raw::c_uint; pub fn rb_get_iseq_body_type(iseq: *const rb_iseq_t) -> rb_iseq_type; @@ -983,48 +1009,22 @@ unsafe extern "C" { pub fn rb_get_iseq_body_param_lead_num(iseq: *const rb_iseq_t) -> ::std::os::raw::c_int; pub fn rb_get_iseq_body_param_opt_num(iseq: *const rb_iseq_t) -> ::std::os::raw::c_int; pub fn rb_get_iseq_body_param_opt_table(iseq: *const rb_iseq_t) -> *const VALUE; - pub fn rb_get_iseq_body_local_table_size(iseq: *const rb_iseq_t) -> ::std::os::raw::c_uint; - pub fn rb_get_cikw_keyword_len(cikw: *const rb_callinfo_kwarg) -> ::std::os::raw::c_int; - pub fn rb_get_cikw_keywords_idx( - cikw: *const rb_callinfo_kwarg, - idx: ::std::os::raw::c_int, - ) -> VALUE; - pub fn rb_get_call_data_ci(cd: *const rb_call_data) -> *const rb_callinfo; + pub fn rb_get_ec_cfp(ec: *const rb_execution_context_t) -> *mut rb_control_frame_struct; + pub fn rb_get_cfp_iseq(cfp: *mut rb_control_frame_struct) -> *const rb_iseq_t; + pub fn rb_get_cfp_pc(cfp: *mut rb_control_frame_struct) -> *mut VALUE; + pub fn rb_get_cfp_sp(cfp: *mut rb_control_frame_struct) -> *mut VALUE; + pub fn rb_get_cfp_self(cfp: *mut rb_control_frame_struct) -> VALUE; + pub fn rb_get_cfp_ep(cfp: *mut rb_control_frame_struct) -> *mut VALUE; + pub fn rb_get_cfp_ep_level(cfp: *mut rb_control_frame_struct, lv: u32) -> *const VALUE; + pub fn rb_yarv_class_of(obj: VALUE) -> VALUE; pub fn rb_FL_TEST(obj: VALUE, flags: VALUE) -> VALUE; pub fn rb_FL_TEST_RAW(obj: VALUE, flags: VALUE) -> VALUE; pub fn rb_RB_TYPE_P(obj: VALUE, t: ruby_value_type) -> bool; pub fn rb_RSTRUCT_LEN(st: VALUE) -> ::std::os::raw::c_long; + pub fn rb_get_call_data_ci(cd: *const rb_call_data) -> *const rb_callinfo; pub fn rb_BASIC_OP_UNREDEFINED_P(bop: ruby_basic_operators, klass: u32) -> bool; - pub fn rb_zjit_multi_ractor_p() -> bool; + pub fn rb_RCLASS_ORIGIN(c: VALUE) -> VALUE; pub fn rb_assert_iseq_handle(handle: VALUE); - pub fn rb_zjit_constcache_shareable(ice: *const iseq_inline_constant_cache_entry) -> bool; - pub fn rb_assert_cme_handle(handle: VALUE); pub fn rb_IMEMO_TYPE_P(imemo: VALUE, imemo_type: imemo_type) -> ::std::os::raw::c_int; - pub fn rb_zjit_vm_unlock( - recursive_lock_level: *mut ::std::os::raw::c_uint, - file: *const ::std::os::raw::c_char, - line: ::std::os::raw::c_int, - ); - pub fn rb_zjit_mark_writable(mem_block: *mut ::std::os::raw::c_void, mem_size: u32) -> bool; - pub fn rb_zjit_mark_executable(mem_block: *mut ::std::os::raw::c_void, mem_size: u32); - pub fn rb_zjit_mark_unused(mem_block: *mut ::std::os::raw::c_void, mem_size: u32) -> bool; - pub fn rb_zjit_icache_invalidate( - start: *mut ::std::os::raw::c_void, - end: *mut ::std::os::raw::c_void, - ); - pub fn rb_vm_ci_argc(ci: *const rb_callinfo) -> ::std::os::raw::c_uint; - pub fn rb_vm_ci_mid(ci: *const rb_callinfo) -> ID; - pub fn rb_vm_ci_flag(ci: *const rb_callinfo) -> ::std::os::raw::c_uint; - pub fn rb_vm_ci_kwarg(ci: *const rb_callinfo) -> *const rb_callinfo_kwarg; - pub fn rb_METHOD_ENTRY_VISI(me: *const rb_callable_method_entry_t) -> rb_method_visibility_t; - pub fn rb_yarv_class_of(obj: VALUE) -> VALUE; - pub fn rb_zjit_vm_lock_then_barrier( - recursive_lock_level: *mut ::std::os::raw::c_uint, - file: *const ::std::os::raw::c_char, - line: ::std::os::raw::c_int, - ); - pub fn rb_RCLASS_ORIGIN(c: VALUE) -> VALUE; - pub fn rb_iseq_get_zjit_payload(iseq: *const rb_iseq_t) -> *mut ::std::os::raw::c_void; - pub fn rb_iseq_set_zjit_payload(iseq: *const rb_iseq_t, payload: *mut ::std::os::raw::c_void); - pub fn rb_zjit_print_exception(); + pub fn rb_assert_cme_handle(handle: VALUE); } diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index d46f5f486f..72cd855bba 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -141,6 +141,7 @@ impl<'a> std::fmt::Display for InvariantPrinter<'a> { write!(f, "BOPRedefined(")?; match klass { INTEGER_REDEFINED_OP_FLAG => write!(f, "INTEGER_REDEFINED_OP_FLAG")?, + ARRAY_REDEFINED_OP_FLAG => write!(f, "ARRAY_REDEFINED_OP_FLAG")?, _ => write!(f, "{klass}")?, } write!(f, ", ")?; @@ -156,6 +157,7 @@ impl<'a> std::fmt::Display for InvariantPrinter<'a> { BOP_LE => write!(f, "BOP_LE")?, BOP_GT => write!(f, "BOP_GT")?, BOP_GE => write!(f, "BOP_GE")?, + BOP_MAX => write!(f, "BOP_MAX")?, _ => write!(f, "{bop}")?, } write!(f, ")") @@ -310,6 +312,7 @@ pub enum Insn { NewArray { elements: Vec<InsnId>, state: InsnId }, ArraySet { array: InsnId, idx: usize, val: InsnId }, ArrayDup { val: InsnId, state: InsnId }, + ArrayMax { elements: Vec<InsnId>, state: InsnId }, // Check if the value is truthy and "return" a C boolean. In reality, we will likely fuse this // with IfTrue/IfFalse in the backend to generate jcc. @@ -441,6 +444,15 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { } Ok(()) } + Insn::ArrayMax { elements, .. } => { + write!(f, "ArrayMax")?; + let mut prefix = " "; + for element in elements { + write!(f, "{prefix}{element}")?; + prefix = ", "; + } + Ok(()) + } Insn::ArraySet { array, idx, val } => { write!(f, "ArraySet {array}, {idx}, {val}") } Insn::ArrayDup { val, .. } => { write!(f, "ArrayDup {val}") } Insn::StringCopy { val } => { write!(f, "StringCopy {val}") } @@ -619,7 +631,7 @@ impl<T: Copy + Into<usize> + PartialEq> UnionFind<T> { } /// Find the set representative for `insn` without doing path compression. - pub fn find_const(&self, insn: T) -> T { + fn find_const(&self, insn: T) -> T { let mut result = insn; loop { match self.at(result) { @@ -645,7 +657,7 @@ pub struct Function { // TODO: get method name and source location from the ISEQ insns: Vec<Insn>, - union_find: UnionFind<InsnId>, + union_find: std::cell::RefCell<UnionFind<InsnId>>, insn_types: Vec<Type>, blocks: Vec<Block>, entry_block: BlockId, @@ -657,7 +669,7 @@ impl Function { iseq, insns: vec![], insn_types: vec![], - union_find: UnionFind::new(), + union_find: UnionFind::new().into(), blocks: vec![Block::default()], entry_block: BlockId(0), } @@ -740,7 +752,14 @@ impl Function { macro_rules! find { ( $x:expr ) => { { - self.union_find.find_const($x) + self.union_find.borrow_mut().find($x) + } + }; + } + macro_rules! find_vec { + ( $x:expr ) => { + { + $x.iter().map(|arg| find!(*arg)).collect() } }; } @@ -749,15 +768,15 @@ impl Function { { BranchEdge { target: $edge.target, - args: $edge.args.iter().map(|x| self.union_find.find_const(*x)).collect(), + args: find_vec!($edge.args), } } }; } - let insn_id = self.union_find.find_const(insn_id); + let insn_id = self.union_find.borrow_mut().find(insn_id); use Insn::*; match &self.insns[insn_id.0] { - result@(PutSelf | Const {..} | Param {..} | NewArray {..} | GetConstantPath {..} + result@(PutSelf | Const {..} | Param {..} | GetConstantPath {..} | PatchPoint {..}) => result.clone(), Snapshot { state: FrameState { iseq, insn_idx, pc, stack, locals } } => Snapshot { @@ -816,18 +835,20 @@ impl Function { ArrayDup { val , state } => ArrayDup { val: find!(*val), state: *state }, CCall { cfun, args, name, return_type } => CCall { cfun: *cfun, args: args.iter().map(|arg| find!(*arg)).collect(), name: *name, return_type: *return_type }, Defined { .. } => todo!("find(Defined)"), + NewArray { elements, state } => NewArray { elements: find_vec!(*elements), state: find!(*state) }, + ArrayMax { elements, state } => ArrayMax { elements: find_vec!(*elements), state: find!(*state) }, } } /// Replace `insn` with the new instruction `replacement`, which will get appended to `insns`. fn make_equal_to(&mut self, insn: InsnId, replacement: InsnId) { // Don't push it to the block - self.union_find.make_equal_to(insn, replacement); + self.union_find.borrow_mut().make_equal_to(insn, replacement); } fn type_of(&self, insn: InsnId) -> Type { assert!(self.insns[insn.0].has_output()); - self.insn_types[self.union_find.find_const(insn).0] + self.insn_types[self.union_find.borrow_mut().find(insn).0] } /// Check if the type of `insn` is a subtype of `ty`. @@ -882,6 +903,7 @@ impl Function { Insn::PutSelf => types::BasicObject, Insn::Defined { .. } => types::BasicObject, Insn::GetConstantPath { .. } => types::BasicObject, + Insn::ArrayMax { .. } => types::BasicObject, } } @@ -1309,9 +1331,13 @@ impl Function { necessary[insn_id.0] = true; match self.find(insn_id) { Insn::PutSelf | Insn::Const { .. } | Insn::Param { .. } - | Insn::NewArray { .. } | Insn::PatchPoint(..) - | Insn::GetConstantPath { .. } => + | Insn::PatchPoint(..) | Insn::GetConstantPath { .. } => {} + Insn::ArrayMax { elements, state } + | Insn::NewArray { elements, state } => { + worklist.extend(elements); + worklist.push_back(state); + } Insn::StringCopy { val } | Insn::StringIntern { val } | Insn::Return { val } @@ -1547,6 +1573,12 @@ impl FrameState { self.stack[idx] = opnd; } + /// Get a stack operand at idx + fn stack_topn(&mut self, idx: usize) -> Result<InsnId, ParseError> { + let idx = self.stack.len() - idx - 1; + self.stack.get(idx).ok_or_else(|| ParseError::StackUnderflow(self.clone())).copied() + } + fn setlocal(&mut self, ep_offset: u32, opnd: InsnId) { let idx = ep_offset_to_local_idx(self.iseq, ep_offset); self.locals[idx] = opnd; @@ -1600,6 +1632,10 @@ fn compute_jump_targets(iseq: *const rb_iseq_t) -> Vec<u32> { let offset = get_arg(pc, 0).as_i64(); jump_targets.insert(insn_idx_at_offset(insn_idx, offset)); } + YARVINSN_opt_new => { + let offset = get_arg(pc, 1).as_i64(); + jump_targets.insert(insn_idx_at_offset(insn_idx, offset)); + } YARVINSN_leave | YARVINSN_opt_invokebuiltin_delegate_leave => { if insn_idx < iseq_size { jump_targets.insert(insn_idx); @@ -1632,6 +1668,7 @@ pub enum CallType { pub enum ParseError { StackUnderflow(FrameState), UnknownOpcode(String), + UnknownNewArraySend(String), UnhandledCallType(CallType), } @@ -1751,6 +1788,26 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> { elements.reverse(); state.stack_push(fun.push_insn(block, Insn::NewArray { elements, state: exit_id })); } + YARVINSN_opt_newarray_send => { + let count = get_arg(pc, 0).as_usize(); + let method = get_arg(pc, 1).as_u32(); + let mut elements = vec![]; + for _ in 0..count { + elements.push(state.stack_pop()?); + } + elements.reverse(); + let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state.clone() }); + let (bop, insn) = match method { + VM_OPT_NEWARRAY_SEND_MAX => (BOP_MAX, Insn::ArrayMax { elements, state: exit_id }), + VM_OPT_NEWARRAY_SEND_MIN => return Err(ParseError::UnknownNewArraySend("min".into())), + VM_OPT_NEWARRAY_SEND_HASH => return Err(ParseError::UnknownNewArraySend("hash".into())), + VM_OPT_NEWARRAY_SEND_PACK => return Err(ParseError::UnknownNewArraySend("pack".into())), + VM_OPT_NEWARRAY_SEND_PACK_BUFFER => return Err(ParseError::UnknownNewArraySend("pack_buffer".into())), + _ => return Err(ParseError::UnknownNewArraySend(format!("{method}"))), + }; + fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: ARRAY_REDEFINED_OP_FLAG, bop })); + state.stack_push(fun.push_insn(block, insn)); + } YARVINSN_duparray => { let val = fun.push_insn(block, Insn::Const { val: Const::Value(get_arg(pc, 0)) }); let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state.clone() }); @@ -1800,6 +1857,17 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> { }); queue.push_back((state.clone(), target, target_idx)); } + YARVINSN_opt_new => { + let offset = get_arg(pc, 1).as_i64(); + // TODO(max): Check interrupts + let target_idx = insn_idx_at_offset(insn_idx, offset); + let target = insn_idx_to_block[&target_idx]; + // Skip the fast-path and go straight to the fallback code. We will let the + // optimizer take care of the converting Class#new->alloc+initialize instead. + fun.push_insn(block, Insn::Jump(BranchEdge { target, args: state.as_args() })); + queue.push_back((state.clone(), target, target_idx)); + break; // Don't enqueue the next block as a successor + } YARVINSN_jump => { let offset = get_arg(pc, 0).as_i64(); // TODO(max): Check interrupts @@ -1834,6 +1902,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> { let top = state.stack_top()?; state.stack_setn(n, top); } + YARVINSN_topn => { + let n = get_arg(pc, 0).as_usize(); + let top = state.stack_topn(n)?; + state.stack_push(top); + } YARVINSN_adjuststack => { let mut n = get_arg(pc, 0).as_usize(); while n > 0 { @@ -1884,6 +1957,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> { YARVINSN_opt_ge | YARVINSN_opt_ltlt | YARVINSN_opt_aset | + YARVINSN_opt_length | + YARVINSN_opt_size | YARVINSN_opt_send_without_block => { let cd: *const rb_call_data = get_arg(pc, 0).as_ptr(); let call_info = unsafe { rb_get_call_data_ci(cd) }; @@ -1963,19 +2038,19 @@ mod union_find_tests { } #[test] - fn test_find_const_returns_target() { + fn test_find_returns_target() { let mut uf = UnionFind::new(); uf.make_equal_to(3, 4); - assert_eq!(uf.find_const(3usize), 4); + assert_eq!(uf.find(3usize), 4); } #[test] - fn test_find_const_returns_transitive_target() { + fn test_find_returns_transitive_target() { let mut uf = UnionFind::new(); uf.make_equal_to(3, 4); uf.make_equal_to(4, 5); - assert_eq!(uf.find_const(3usize), 5); - assert_eq!(uf.find_const(4usize), 5); + assert_eq!(uf.find(3usize), 5); + assert_eq!(uf.find(4usize), 5); } #[test] @@ -2796,6 +2871,83 @@ mod tests { "); assert_compile_fails("test", ParseError::UnknownOpcode("sendforward".into())) } + + #[test] + fn test_opt_new() { + eval(" + class C; end + def test = C.new + "); + assert_method_hir("test", expect![[r#" + fn test: + bb0(): + v1:BasicObject = GetConstantPath 0x1000 + v2:NilClassExact = Const Value(nil) + Jump bb1(v2, v1) + bb1(v4:NilClassExact, v5:BasicObject): + v8:BasicObject = SendWithoutBlock v5, :new + Jump bb2(v8, v4) + bb2(v10:BasicObject, v11:NilClassExact): + Return v10 + "#]]); + } + + #[test] + fn test_opt_newarray_send_max_no_elements() { + eval(" + def test = [].max + "); + // TODO(max): Rewrite to nil + assert_method_hir("test", expect![[r#" + fn test: + bb0(): + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_MAX) + v3:BasicObject = ArrayMax + Return v3 + "#]]); + } + + #[test] + fn test_opt_newarray_send_max() { + eval(" + def test(a,b) = [a,b].max + "); + assert_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject, v1:BasicObject): + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_MAX) + v5:BasicObject = ArrayMax v0, v1 + Return v5 + "#]]); + } + + #[test] + fn test_opt_length() { + eval(" + def test(a,b) = [a,b].length + "); + assert_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject, v1:BasicObject): + v4:ArrayExact = NewArray v0, v1 + v6:BasicObject = SendWithoutBlock v4, :length + Return v6 + "#]]); + } + + #[test] + fn test_opt_size() { + eval(" + def test(a,b) = [a,b].size + "); + assert_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject, v1:BasicObject): + v4:ArrayExact = NewArray v0, v1 + v6:BasicObject = SendWithoutBlock v4, :size + Return v6 + "#]]); + } } #[cfg(test)] @@ -3688,4 +3840,83 @@ mod opt_tests { Return v5 "#]]); } + + #[test] + fn test_opt_new_no_initialize() { + eval(" + class C; end + def test = C.new + test + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, C) + v16:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v2:NilClassExact = Const Value(nil) + Jump bb1(v2, v16) + bb1(v4:NilClassExact, v5:BasicObject[VALUE(0x1008)]): + v8:BasicObject = SendWithoutBlock v5, :new + Jump bb2(v8, v4) + bb2(v10:BasicObject, v11:NilClassExact): + Return v10 + "#]]); + } + + #[test] + fn test_opt_new_initialize() { + eval(" + class C + def initialize x + @x = x + end + end + def test = C.new 1 + test + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, C) + v18:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v2:NilClassExact = Const Value(nil) + v3:Fixnum[1] = Const Value(1) + Jump bb1(v2, v18, v3) + bb1(v5:NilClassExact, v6:BasicObject[VALUE(0x1008)], v7:Fixnum[1]): + v10:BasicObject = SendWithoutBlock v6, :new, v7 + Jump bb2(v10, v5) + bb2(v12:BasicObject, v13:NilClassExact): + Return v12 + "#]]); + } + + #[test] + fn test_opt_length() { + eval(" + def test(a,b) = [a,b].length + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject, v1:BasicObject): + v4:ArrayExact = NewArray v0, v1 + v6:BasicObject = SendWithoutBlock v4, :length + Return v6 + "#]]); + } + + #[test] + fn test_opt_size() { + eval(" + def test(a,b) = [a,b].size + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject, v1:BasicObject): + v4:ArrayExact = NewArray v0, v1 + v6:BasicObject = SendWithoutBlock v4, :size + Return v6 + "#]]); + } } |