From 3624031bb594d856c9449130cb162aad612435e7 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 24 Oct 2025 18:19:40 -0700 Subject: [PATCH 01/18] ZJIT: Inline Kernel#nil? and NilClass#nil? We can fully remove the CCall now. --- zjit/src/cruby_methods.rs | 14 ++++++++++++-- zjit/src/hir.rs | 36 ++++++++++++++++++------------------ 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index dd33bb206a5bb3..5530b555bd64e1 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -210,8 +210,8 @@ pub fn init() -> Annotations { annotate!(rb_cHash, "[]", inline_hash_aref); annotate!(rb_cHash, "size", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cHash, "empty?", types::BoolExact, no_gc, leaf, elidable); - annotate!(rb_cNilClass, "nil?", types::TrueClass, no_gc, leaf, elidable); - annotate!(rb_mKernel, "nil?", types::FalseClass, no_gc, leaf, elidable); + annotate!(rb_cNilClass, "nil?", inline_nilclass_nil_p); + annotate!(rb_mKernel, "nil?", inline_kernel_nil_p); annotate!(rb_mKernel, "respond_to?", inline_kernel_respond_to_p); annotate!(rb_cBasicObject, "==", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "!", types::BoolExact, no_gc, leaf, elidable); @@ -363,6 +363,16 @@ fn inline_basic_object_initialize(fun: &mut hir::Function, block: hir::BlockId, Some(result) } +fn inline_nilclass_nil_p(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { + if !args.is_empty() { return None; } + Some(fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(Qtrue) })) +} + +fn inline_kernel_nil_p(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { + if !args.is_empty() { return None; } + Some(fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(Qfalse) })) +} + fn inline_kernel_respond_to_p( fun: &mut hir::Function, block: hir::BlockId, diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index e0e682ccb2fc71..0f415a4a21cf1b 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -12724,10 +12724,10 @@ mod opt_tests { bb2(v6:BasicObject): v10:NilClass = Const Value(nil) PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008, cme:0x1010) + v22:TrueClass = Const Value(true) IncrCounter inline_cfunc_optimized_send_count - v23:TrueClass = CCall nil?@0x1038, v10 CheckInterrupts - Return v23 + Return v22 "); } @@ -12775,10 +12775,10 @@ mod opt_tests { bb2(v6:BasicObject): v10:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(Integer@0x1000, nil?@0x1008, cme:0x1010) + v22:FalseClass = Const Value(false) IncrCounter inline_cfunc_optimized_send_count - v23:FalseClass = CCall nil?@0x1038, v10 CheckInterrupts - Return v23 + Return v22 "); } @@ -12829,10 +12829,10 @@ mod opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008, cme:0x1010) v24:NilClass = GuardType v9, NilClass + v25:TrueClass = Const Value(true) IncrCounter inline_cfunc_optimized_send_count - v26:TrueClass = CCall nil?@0x1038, v24 CheckInterrupts - Return v26 + Return v25 "); } @@ -12856,10 +12856,10 @@ mod opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(FalseClass@0x1000, nil?@0x1008, cme:0x1010) v24:FalseClass = GuardType v9, FalseClass + v25:FalseClass = Const Value(false) IncrCounter inline_cfunc_optimized_send_count - v26:FalseClass = CCall nil?@0x1038, v24 CheckInterrupts - Return v26 + Return v25 "); } @@ -12883,10 +12883,10 @@ mod opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(TrueClass@0x1000, nil?@0x1008, cme:0x1010) v24:TrueClass = GuardType v9, TrueClass + v25:FalseClass = Const Value(false) IncrCounter inline_cfunc_optimized_send_count - v26:FalseClass = CCall nil?@0x1038, v24 CheckInterrupts - Return v26 + Return v25 "); } @@ -12910,10 +12910,10 @@ mod opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(Symbol@0x1000, nil?@0x1008, cme:0x1010) v24:StaticSymbol = GuardType v9, StaticSymbol + v25:FalseClass = Const Value(false) IncrCounter inline_cfunc_optimized_send_count - v26:FalseClass = CCall nil?@0x1038, v24 CheckInterrupts - Return v26 + Return v25 "); } @@ -12937,10 +12937,10 @@ mod opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(Integer@0x1000, nil?@0x1008, cme:0x1010) v24:Fixnum = GuardType v9, Fixnum + v25:FalseClass = Const Value(false) IncrCounter inline_cfunc_optimized_send_count - v26:FalseClass = CCall nil?@0x1038, v24 CheckInterrupts - Return v26 + Return v25 "); } @@ -12964,10 +12964,10 @@ mod opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(Float@0x1000, nil?@0x1008, cme:0x1010) v24:Flonum = GuardType v9, Flonum + v25:FalseClass = Const Value(false) IncrCounter inline_cfunc_optimized_send_count - v26:FalseClass = CCall nil?@0x1038, v24 CheckInterrupts - Return v26 + Return v25 "); } @@ -12992,10 +12992,10 @@ mod opt_tests { PatchPoint MethodRedefined(String@0x1000, nil?@0x1008, cme:0x1010) PatchPoint NoSingletonClass(String@0x1000) v25:StringExact = GuardType v9, StringExact + v26:FalseClass = Const Value(false) IncrCounter inline_cfunc_optimized_send_count - v27:FalseClass = CCall nil?@0x1038, v25 CheckInterrupts - Return v27 + Return v26 "); } From 7425520415991f38e048ff6acbee3af0731baead Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 24 Oct 2025 18:22:53 -0700 Subject: [PATCH 02/18] ZJIT: Remove redundant annotation --- zjit/src/cruby_methods.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 5530b555bd64e1..4ded84c4fb2ee7 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -192,7 +192,6 @@ pub fn init() -> Annotations { annotate!(rb_cString, "bytesize", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cString, "size", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cString, "length", types::Fixnum, no_gc, leaf, elidable); - annotate!(rb_cString, "to_s", types::StringExact); annotate!(rb_cString, "getbyte", inline_string_getbyte); annotate!(rb_cString, "empty?", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cString, "<<", inline_string_append); From 2a6e5d7d94103f73d56c8d6f1393eb5b4c7297d1 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 24 Oct 2025 18:29:15 -0700 Subject: [PATCH 03/18] ZJIT: Allow both inlining and annotating properties --- zjit/src/cruby_methods.rs | 10 +++++++++- zjit/src/hir.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 4ded84c4fb2ee7..51841d6f781102 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -159,6 +159,14 @@ pub fn init() -> Annotations { let props = FnProperties { no_gc: false, leaf: false, elidable: false, return_type: types::BasicObject, inline: $inline }; annotate_c_method(cfuncs, unsafe { $module }, $method_name, props); }; + ($module:ident, $method_name:literal, $inline:ident, $return_type:expr $(, $properties:ident)*) => { + #[allow(unused_mut)] + let mut props = FnProperties { no_gc: false, leaf: false, elidable: false, return_type: $return_type, inline: $inline }; + $( + props.$properties = true; + )* + annotate_c_method(cfuncs, unsafe { $module }, $method_name, props); + }; ($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, inline: no_inline }; @@ -217,7 +225,7 @@ pub fn init() -> Annotations { annotate!(rb_cBasicObject, "initialize", inline_basic_object_initialize); annotate!(rb_cInteger, "succ", inline_integer_succ); annotate!(rb_cInteger, "^", inline_integer_xor); - annotate!(rb_cString, "to_s", inline_string_to_s); + annotate!(rb_cString, "to_s", inline_string_to_s, types::StringExact); let thread_singleton = unsafe { rb_singleton_class(rb_cThread) }; annotate!(thread_singleton, "current", types::BasicObject, no_gc, leaf); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 0f415a4a21cf1b..be8f060e6753cd 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -13711,6 +13711,33 @@ mod opt_tests { "); } + #[test] + fn test_string_subclass_to_s_returns_string_exact() { + eval(r#" + class C < String; end + def test(o) = o.to_s + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(C@0x1000, to_s@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v23:StringSubclass[class_exact:C] = GuardType v9, StringSubclass[class_exact:C] + v24:StringExact = CCallWithFrame to_s@0x1038, v23 + CheckInterrupts + Return v24 + "); + } + #[test] fn test_inline_string_literal_to_s() { eval(r#" From a4f8afcec835401d356350ad4a20a78b9aaa30f2 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 24 Oct 2025 18:34:32 -0700 Subject: [PATCH 04/18] ZJIT: Use FnProperties::default() --- zjit/src/cruby_methods.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 51841d6f781102..b434cfe25a8940 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -156,20 +156,22 @@ pub fn init() -> Annotations { macro_rules! annotate { ($module:ident, $method_name:literal, $inline:ident) => { - let props = FnProperties { no_gc: false, leaf: false, elidable: false, return_type: types::BasicObject, inline: $inline }; + let mut props = FnProperties::default(); + props.inline = $inline; annotate_c_method(cfuncs, unsafe { $module }, $method_name, props); }; ($module:ident, $method_name:literal, $inline:ident, $return_type:expr $(, $properties:ident)*) => { - #[allow(unused_mut)] - let mut props = FnProperties { no_gc: false, leaf: false, elidable: false, return_type: $return_type, inline: $inline }; + let mut props = FnProperties::default(); + props.return_type = $return_type; + props.inline = $inline; $( props.$properties = true; )* annotate_c_method(cfuncs, unsafe { $module }, $method_name, props); }; ($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, inline: no_inline }; + let mut props = FnProperties::default(); + props.return_type = $return_type; $( props.$properties = true; )* @@ -183,13 +185,8 @@ pub fn init() -> Annotations { annotate_builtin!($module, $method_name, $return_type, no_gc, leaf, elidable) }; ($module:ident, $method_name:literal, $return_type:expr, $($properties:ident),+) => { - let mut props = FnProperties { - no_gc: false, - leaf: false, - elidable: false, - return_type: $return_type, - inline: no_inline, - }; + let mut props = FnProperties::default(); + props.return_type = $return_type; $(props.$properties = true;)+ annotate_builtin_method(builtin_funcs, unsafe { $module }, $method_name, props); } From e973baa837a9cc17189ed4e32e43e047f622766b Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 24 Oct 2025 19:06:20 -0700 Subject: [PATCH 05/18] ZJIT: Add BoxBool and remove CCall from BasicObject#== --- zjit/src/codegen.rs | 6 ++++ zjit/src/cruby_methods.rs | 9 ++++- zjit/src/hir.rs | 72 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 84 insertions(+), 3 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 1179b9bf16924b..7f93d226019c15 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -405,6 +405,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::IsNil { val } => gen_isnil(asm, opnd!(val)), &Insn::IsMethodCfunc { val, cd, cfunc, state: _ } => gen_is_method_cfunc(jit, asm, opnd!(val), cd, cfunc), &Insn::IsBitEqual { left, right } => gen_is_bit_equal(asm, opnd!(left), opnd!(right)), + &Insn::BoxBool { val } => gen_box_bool(asm, opnd!(val)), Insn::Test { val } => gen_test(asm, opnd!(val)), Insn::GuardType { val, guard_type, state } => gen_guard_type(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)), Insn::GuardTypeNot { val, guard_type, state } => gen_guard_type_not(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)), @@ -1553,6 +1554,11 @@ fn gen_is_bit_equal(asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd) -> l asm.csel_e(Opnd::Imm(1), Opnd::Imm(0)) } +fn gen_box_bool(asm: &mut Assembler, val: lir::Opnd) -> lir::Opnd { + asm.test(val, val); + asm.csel_nz(Opnd::Value(Qtrue), Opnd::Value(Qfalse)) +} + fn gen_anytostring(asm: &mut Assembler, val: lir::Opnd, str: lir::Opnd, state: &FrameState) -> lir::Opnd { gen_prepare_leaf_call_with_gc(asm, state); diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index b434cfe25a8940..e7be5ab4454bb3 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -217,7 +217,7 @@ pub fn init() -> Annotations { annotate!(rb_cNilClass, "nil?", inline_nilclass_nil_p); annotate!(rb_mKernel, "nil?", inline_kernel_nil_p); annotate!(rb_mKernel, "respond_to?", inline_kernel_respond_to_p); - annotate!(rb_cBasicObject, "==", types::BoolExact, no_gc, leaf, elidable); + annotate!(rb_cBasicObject, "==", inline_basic_object_eq, types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "!", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "initialize", inline_basic_object_initialize); annotate!(rb_cInteger, "succ", inline_integer_succ); @@ -361,6 +361,13 @@ fn inline_integer_xor(fun: &mut hir::Function, block: hir::BlockId, recv: hir::I None } +fn inline_basic_object_eq(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { + let &[other] = args else { return None; }; + let c_result = fun.push_insn(block, hir::Insn::IsBitEqual { left: recv, right: other }); + let result = fun.push_insn(block, hir::Insn::BoxBool { val: c_result }); + Some(result) +} + fn inline_basic_object_initialize(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { if !args.is_empty() { return None; } let result = fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(Qnil) }); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index be8f060e6753cd..6d0b120a3702c2 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -610,6 +610,7 @@ pub enum Insn { IsMethodCfunc { val: InsnId, cd: *const rb_call_data, cfunc: *const u8, state: InsnId }, /// Return C `true` if left == right IsBitEqual { left: InsnId, right: InsnId }, + BoxBool { val: InsnId }, // TODO(max): In iseq body types that are not ISEQ_TYPE_METHOD, rewrite to Constant false. Defined { op_type: usize, obj: VALUE, pushval: VALUE, v: InsnId, state: InsnId }, GetConstantPath { ic: *const iseq_inline_constant_cache, state: InsnId }, @@ -997,6 +998,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::IsNil { val } => { write!(f, "IsNil {val}") } Insn::IsMethodCfunc { val, cd, .. } => { write!(f, "IsMethodCFunc {val}, :{}", ruby_call_method_name(*cd)) } Insn::IsBitEqual { left, right } => write!(f, "IsBitEqual {left}, {right}"), + Insn::BoxBool { val } => write!(f, "BoxBool {val}"), 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}") } @@ -1623,6 +1625,7 @@ impl Function { &IsNil { val } => IsNil { val: find!(val) }, &IsMethodCfunc { val, cd, cfunc, state } => IsMethodCfunc { val: find!(val), cd, cfunc, state }, &IsBitEqual { left, right } => IsBitEqual { left: find!(left), right: find!(right) }, + &BoxBool { val } => BoxBool { val: find!(val) }, Jump(target) => Jump(find_branch_edge!(target)), &IfTrue { val, ref target } => IfTrue { val: find!(val), target: find_branch_edge!(target) }, &IfFalse { val, ref target } => IfFalse { val: find!(val), target: find_branch_edge!(target) }, @@ -1810,6 +1813,7 @@ impl Function { Insn::IsNil { .. } => types::CBool, Insn::IsMethodCfunc { .. } => types::CBool, Insn::IsBitEqual { .. } => types::CBool, + Insn::BoxBool { .. } => types::BoolExact, Insn::StringCopy { .. } => types::StringExact, Insn::StringIntern { .. } => types::Symbol, Insn::StringConcat { .. } => types::StringExact, @@ -3094,6 +3098,7 @@ impl Function { | &Insn::Return { val } | &Insn::Test { val } | &Insn::SetLocal { val, .. } + | &Insn::BoxBool { val } | &Insn::IsNil { val } => worklist.push_back(val), &Insn::SetGlobal { val, state, .. } @@ -13084,7 +13089,7 @@ mod opt_tests { } #[test] - fn test_specialize_basic_object_eq_to_ccall() { + fn test_specialize_basic_object_eq() { eval(" class C; end def test(a, b) = a == b @@ -13106,13 +13111,76 @@ mod opt_tests { PatchPoint MethodRedefined(C@0x1000, ==@0x1008, cme:0x1010) PatchPoint NoSingletonClass(C@0x1000) v28:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] + v29:CBool = IsBitEqual v28, v12 + v30:BoolExact = BoxBool v29 IncrCounter inline_cfunc_optimized_send_count - v30:BoolExact = CCall ==@0x1038, v28, v12 CheckInterrupts Return v30 "); } + #[test] + fn test_specialize_basic_object_eqq() { + eval(" + class C; end + def test(a, b) = a === b + + test(C.new, C.new) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(C@0x1000, ===@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v26:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1000, ==@0x1038, cme:0x1040) + PatchPoint NoSingletonClass(C@0x1000) + v30:CBool = IsBitEqual v26, v12 + v31:BoolExact = BoxBool v30 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v31 + "); + } + + #[test] + fn test_specialize_nil_eq() { + eval(" + def test(a, b) = a == b + + test(nil, 5) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(NilClass@0x1000, ==@0x1008, cme:0x1010) + v27:NilClass = GuardType v11, NilClass + v28:CBool = IsBitEqual v27, v12 + v29:BoolExact = BoxBool v28 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + #[test] fn test_guard_fixnum_and_fixnum() { eval(" From c2bef01b668174936c0a25358d9d50b38bcf341c Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 24 Oct 2025 19:07:04 -0700 Subject: [PATCH 06/18] ZJIT: Optimize Kernel#=== --- zjit/src/cruby_methods.rs | 12 ++++ zjit/src/hir.rs | 118 +++++++++++++++++++++++++++++++++++--- 2 files changed, 122 insertions(+), 8 deletions(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index e7be5ab4454bb3..199b5a64ac5310 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -194,6 +194,7 @@ pub fn init() -> Annotations { annotate!(rb_mKernel, "itself", inline_kernel_itself); annotate!(rb_mKernel, "block_given?", inline_kernel_block_given_p); + annotate!(rb_mKernel, "===", inline_eqq); annotate!(rb_cString, "bytesize", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cString, "size", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cString, "length", types::Fixnum, no_gc, leaf, elidable); @@ -379,6 +380,17 @@ fn inline_nilclass_nil_p(fun: &mut hir::Function, block: hir::BlockId, _recv: hi Some(fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(Qtrue) })) } +fn inline_eqq(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + let &[other] = args else { return None; }; + let recv_class = fun.type_of(recv).runtime_exact_ruby_class()?; + if !fun.assume_expected_cfunc(block, recv_class, ID!(eq), rb_obj_equal as _, state) { + return None; + } + let c_result = fun.push_insn(block, hir::Insn::IsBitEqual { left: recv, right: other }); + let result = fun.push_insn(block, hir::Insn::BoxBool { val: c_result }); + Some(result) +} + fn inline_kernel_nil_p(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { if !args.is_empty() { return None; } Some(fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(Qfalse) })) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 6d0b120a3702c2..c184c2d4b204b7 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2037,6 +2037,21 @@ impl Function { None } + pub fn assume_expected_cfunc(&mut self, block: BlockId, class: VALUE, method_id: ID, cfunc: *mut c_void, state: InsnId) -> bool { + let cme = unsafe { rb_callable_method_entry(class, method_id) }; + if cme.is_null() { return false; } + let def_type = unsafe { get_cme_def_type(cme) }; + if def_type != VM_METHOD_TYPE_CFUNC { return false; } + if unsafe { get_mct_func(get_cme_def_body_cfunc(cme)) } != cfunc { + return false; + } + self.gen_patch_points_for_optimized_ccall(block, class, method_id, cme, state); + if class.instance_can_have_singleton_class() { + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: class }, state }); + } + true + } + pub fn likely_a(&self, val: InsnId, ty: Type, state: InsnId) -> bool { if self.type_of(val).is_subtype(ty) { return true; @@ -2545,6 +2560,11 @@ impl Function { self.infer_types(); } + fn gen_patch_points_for_optimized_ccall(&mut self, block: BlockId, recv_class: VALUE, method_id: ID, method: *const rb_callable_method_entry_struct, state: InsnId) { + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoTracePoint, state }); + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass: recv_class, method: method_id, cme: method }, state }); + } + /// Optimize SendWithoutBlock that land in a C method to a direct CCall without /// runtime lookup. fn optimize_c_calls(&mut self) { @@ -2552,11 +2572,6 @@ impl Function { return; } - fn gen_patch_points_for_optimized_ccall(fun: &mut Function, block: BlockId, recv_class: VALUE, method_id: ID, method: *const rb_callable_method_entry_struct, state: InsnId) { - fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoTracePoint, state }); - fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass: recv_class, method: method_id, cme: method }, state }); - } - // Try to reduce a Send insn to a CCallWithFrame fn reduce_send_to_ccall( fun: &mut Function, @@ -2615,7 +2630,7 @@ impl Function { } // Commit to the replacement. Put PatchPoint. - gen_patch_points_for_optimized_ccall(fun, block, recv_class, method_id, method, state); + fun.gen_patch_points_for_optimized_ccall(block, recv_class, method_id, method, state); if recv_class.instance_can_have_singleton_class() { fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: recv_class }, state }); } @@ -2717,7 +2732,7 @@ impl Function { } // Commit to the replacement. Put PatchPoint. - gen_patch_points_for_optimized_ccall(fun, block, recv_class, method_id, method, state); + fun.gen_patch_points_for_optimized_ccall(block, recv_class, method_id, method, state); if recv_class.instance_can_have_singleton_class() { fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: recv_class }, state }); } @@ -2784,7 +2799,7 @@ impl Function { // func(int argc, VALUE *argv, VALUE recv) let ci_flags = unsafe { vm_ci_flag(call_info) }; if ci_flags & VM_CALL_ARGS_SIMPLE != 0 { - gen_patch_points_for_optimized_ccall(fun, block, recv_class, method_id, method, state); + fun.gen_patch_points_for_optimized_ccall(block, recv_class, method_id, method, state); if recv_class.instance_can_have_singleton_class() { fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: recv_class }, state }); @@ -13181,6 +13196,93 @@ mod opt_tests { "); } + #[test] + fn test_specialize_nil_eqq() { + eval(" + def test(a, b) = a === b + test(nil, 5) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(NilClass@0x1000, ===@0x1008, cme:0x1010) + v25:NilClass = GuardType v11, NilClass + PatchPoint MethodRedefined(NilClass@0x1000, ==@0x1038, cme:0x1040) + v28:CBool = IsBitEqual v25, v12 + v29:BoolExact = BoxBool v28 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_specialize_true_eqq() { + eval(" + def test(a, b) = a === b + test(true, 5) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(TrueClass@0x1000, ===@0x1008, cme:0x1010) + v25:TrueClass = GuardType v11, TrueClass + PatchPoint MethodRedefined(TrueClass@0x1000, ==@0x1038, cme:0x1040) + v28:CBool = IsBitEqual v25, v12 + v29:BoolExact = BoxBool v28 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_specialize_false_eqq() { + eval(" + def test(a, b) = a === b + test(true, 5) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(TrueClass@0x1000, ===@0x1008, cme:0x1010) + v25:TrueClass = GuardType v11, TrueClass + PatchPoint MethodRedefined(TrueClass@0x1000, ==@0x1038, cme:0x1040) + v28:CBool = IsBitEqual v25, v12 + v29:BoolExact = BoxBool v28 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + #[test] fn test_guard_fixnum_and_fixnum() { eval(" From 2e2f31c836c21b87e846d75372b06893f4226521 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 24 Oct 2025 19:33:57 -0700 Subject: [PATCH 07/18] ZJIT: Add IsBitNotEqual and inline BasicObject#!= --- zjit/src/codegen.rs | 6 ++++++ zjit/src/cruby_methods.rs | 12 ++++++++++++ zjit/src/hir.rs | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 7f93d226019c15..a1f318cab7781d 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -405,6 +405,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::IsNil { val } => gen_isnil(asm, opnd!(val)), &Insn::IsMethodCfunc { val, cd, cfunc, state: _ } => gen_is_method_cfunc(jit, asm, opnd!(val), cd, cfunc), &Insn::IsBitEqual { left, right } => gen_is_bit_equal(asm, opnd!(left), opnd!(right)), + &Insn::IsBitNotEqual { left, right } => gen_is_bit_not_equal(asm, opnd!(left), opnd!(right)), &Insn::BoxBool { val } => gen_box_bool(asm, opnd!(val)), Insn::Test { val } => gen_test(asm, opnd!(val)), Insn::GuardType { val, guard_type, state } => gen_guard_type(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)), @@ -1554,6 +1555,11 @@ fn gen_is_bit_equal(asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd) -> l asm.csel_e(Opnd::Imm(1), Opnd::Imm(0)) } +fn gen_is_bit_not_equal(asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd) -> lir::Opnd { + asm.cmp(left, right); + asm.csel_ne(Opnd::Imm(1), Opnd::Imm(0)) +} + fn gen_box_bool(asm: &mut Assembler, val: lir::Opnd) -> lir::Opnd { asm.test(val, val); asm.csel_nz(Opnd::Value(Qtrue), Opnd::Value(Qfalse)) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 199b5a64ac5310..78a79b0faee7fc 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -220,6 +220,7 @@ pub fn init() -> Annotations { annotate!(rb_mKernel, "respond_to?", inline_kernel_respond_to_p); annotate!(rb_cBasicObject, "==", inline_basic_object_eq, types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "!", types::BoolExact, no_gc, leaf, elidable); + annotate!(rb_cBasicObject, "!=", inline_basic_object_neq, types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "initialize", inline_basic_object_initialize); annotate!(rb_cInteger, "succ", inline_integer_succ); annotate!(rb_cInteger, "^", inline_integer_xor); @@ -369,6 +370,17 @@ fn inline_basic_object_eq(fun: &mut hir::Function, block: hir::BlockId, recv: hi Some(result) } +fn inline_basic_object_neq(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + let &[other] = args else { return None; }; + let recv_class = fun.type_of(recv).runtime_exact_ruby_class()?; + if !fun.assume_expected_cfunc(block, recv_class, ID!(eq), rb_obj_equal as _, state) { + return None; + } + let c_result = fun.push_insn(block, hir::Insn::IsBitNotEqual { left: recv, right: other }); + let result = fun.push_insn(block, hir::Insn::BoxBool { val: c_result }); + Some(result) +} + fn inline_basic_object_initialize(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { if !args.is_empty() { return None; } let result = fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(Qnil) }); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index c184c2d4b204b7..8c4f9eb4fe43dc 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -610,6 +610,9 @@ pub enum Insn { IsMethodCfunc { val: InsnId, cd: *const rb_call_data, cfunc: *const u8, state: InsnId }, /// Return C `true` if left == right IsBitEqual { left: InsnId, right: InsnId }, + /// Return C `true` if left != right + IsBitNotEqual { left: InsnId, right: InsnId }, + /// Convert a C `bool` to a Ruby `Qtrue`/`Qfalse`. Same as `RBOOL` macro. BoxBool { val: InsnId }, // TODO(max): In iseq body types that are not ISEQ_TYPE_METHOD, rewrite to Constant false. Defined { op_type: usize, obj: VALUE, pushval: VALUE, v: InsnId, state: InsnId }, @@ -998,6 +1001,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::IsNil { val } => { write!(f, "IsNil {val}") } Insn::IsMethodCfunc { val, cd, .. } => { write!(f, "IsMethodCFunc {val}, :{}", ruby_call_method_name(*cd)) } Insn::IsBitEqual { left, right } => write!(f, "IsBitEqual {left}, {right}"), + Insn::IsBitNotEqual { left, right } => write!(f, "IsBitNotEqual {left}, {right}"), Insn::BoxBool { val } => write!(f, "BoxBool {val}"), Insn::Jump(target) => { write!(f, "Jump {target}") } Insn::IfTrue { val, target } => { write!(f, "IfTrue {val}, {target}") } @@ -1625,6 +1629,7 @@ impl Function { &IsNil { val } => IsNil { val: find!(val) }, &IsMethodCfunc { val, cd, cfunc, state } => IsMethodCfunc { val: find!(val), cd, cfunc, state }, &IsBitEqual { left, right } => IsBitEqual { left: find!(left), right: find!(right) }, + &IsBitNotEqual { left, right } => IsBitNotEqual { left: find!(left), right: find!(right) }, &BoxBool { val } => BoxBool { val: find!(val) }, Jump(target) => Jump(find_branch_edge!(target)), &IfTrue { val, ref target } => IfTrue { val: find!(val), target: find_branch_edge!(target) }, @@ -1813,6 +1818,7 @@ impl Function { Insn::IsNil { .. } => types::CBool, Insn::IsMethodCfunc { .. } => types::CBool, Insn::IsBitEqual { .. } => types::CBool, + Insn::IsBitNotEqual { .. } => types::CBool, Insn::BoxBool { .. } => types::BoolExact, Insn::StringCopy { .. } => types::StringExact, Insn::StringIntern { .. } => types::Symbol, @@ -3156,6 +3162,7 @@ impl Function { | &Insn::FixnumOr { left, right } | &Insn::FixnumXor { left, right } | &Insn::IsBitEqual { left, right } + | &Insn::IsBitNotEqual { left, right } => { worklist.push_back(left); worklist.push_back(right); @@ -10556,6 +10563,38 @@ mod opt_tests { "); } + #[test] + fn test_specialize_basic_object_neq() { + eval(" + class C; end + def test(a, b) = a != b + test(C.new, 5) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(C@0x1000, !=@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v28:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1000, ==@0x1038, cme:0x1040) + PatchPoint NoSingletonClass(C@0x1000) + v32:CBool = IsBitNotEqual v28, v12 + v33:BoolExact = BoxBool v32 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v33 + "); + } + #[test] fn test_do_not_eliminate_get_constant_path() { eval(" From 7a736545e948ec481ba2b2c46d78470194b624ba Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Tue, 28 Oct 2025 11:44:25 -0400 Subject: [PATCH 08/18] ZJIT: Specialize Array#pop for no argument case (#14933) Fixes https://github.com/Shopify/ruby/issues/814 This change specializes the case of calling `Array#pop` on a non frozen array with no arguments. `Array#pop` exists in the non-inlined C function list in the ZJIT SFR performance burndown list. If in the future it is helpful, this patch could be extended to support the case where an argument is provided, but this initial work seeks to elide the ruby frame normally pushed in the case of `Array#pop` without an argument. --- test/ruby/test_zjit.rb | 23 ++++++++ zjit/bindgen/src/main.rs | 2 + zjit/src/codegen.rs | 15 +++++ zjit/src/cruby_bindings.inc.rs | 1 + zjit/src/cruby_methods.rs | 8 +++ zjit/src/hir.rs | 105 +++++++++++++++++++++++++++++++++ zjit/src/stats.rs | 2 + 7 files changed, 156 insertions(+) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index e76e140ba13f2b..802370aa599405 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -1191,6 +1191,29 @@ def test(x) = [1,2,3][x] }, call_threshold: 2, insns: [:opt_aref] end + def test_empty_array_pop + assert_compiles 'nil', %q{ + def test(arr) = arr.pop + test([]) + test([]) + }, call_threshold: 2 + end + + def test_array_pop_no_arg + assert_compiles '42', %q{ + def test(arr) = arr.pop + test([32, 33, 42]) + test([32, 33, 42]) + }, call_threshold: 2 + end + + def test_array_pop_arg + assert_compiles '[33, 42]', %q{ + def test(arr) = arr.pop(2) + test([32, 33, 42]) + }, call_threshold: 2 + end + def test_new_range_inclusive assert_compiles '1..5', %q{ def test(a, b) = a..b diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 75dbd46794abe4..76f04f4369e3da 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -124,6 +124,7 @@ fn main() { .allowlist_function("rb_ary_clear") .allowlist_function("rb_ary_dup") .allowlist_function("rb_ary_push") + .allowlist_function("rb_ary_pop") .allowlist_function("rb_ary_unshift_m") .allowlist_function("rb_ec_ary_new_from_values") .allowlist_function("rb_ary_tmp_new_from_values") @@ -331,6 +332,7 @@ fn main() { .allowlist_function("rb_class_new_instance_pass_kw") .allowlist_function("rb_obj_alloc") .allowlist_function("rb_obj_info") + .allowlist_function("rb_obj_frozen_p") // From include/ruby/debug.h .allowlist_function("rb_profile_frames") .allowlist_function("ruby_xfree") diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index a1f318cab7781d..8ff1d400c32953 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -357,6 +357,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::NewRangeFixnum { low, high, flag, state } => gen_new_range_fixnum(asm, opnd!(low), opnd!(high), *flag, &function.frame_state(*state)), Insn::ArrayDup { val, state } => gen_array_dup(asm, opnd!(val), &function.frame_state(*state)), Insn::ArrayArefFixnum { array, index, .. } => gen_aref_fixnum(asm, opnd!(array), opnd!(index)), + Insn::ArrayPop { array, state } => gen_array_pop(asm, opnd!(array), &function.frame_state(*state)), Insn::ArrayLength { array } => gen_array_length(asm, opnd!(array)), Insn::ObjectAlloc { val, state } => gen_object_alloc(jit, asm, opnd!(val), &function.frame_state(*state)), &Insn::ObjectAllocClass { class, state } => gen_object_alloc_class(asm, class, &function.frame_state(state)), @@ -412,6 +413,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::GuardTypeNot { val, guard_type, state } => gen_guard_type_not(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)), Insn::GuardBitEquals { val, expected, state } => gen_guard_bit_equals(jit, asm, opnd!(val), *expected, &function.frame_state(*state)), &Insn::GuardBlockParamProxy { level, state } => no_output!(gen_guard_block_param_proxy(jit, asm, level, &function.frame_state(state))), + Insn::GuardNotFrozen { val, state } => gen_guard_not_frozen(jit, asm, opnd!(val), &function.frame_state(*state)), Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, invariant, &function.frame_state(*state))), Insn::CCall { cfunc, args, name: _, return_type: _, elidable: _ } => gen_ccall(asm, *cfunc, opnds!(args)), // Give up CCallWithFrame for 7+ args since asm.ccall() doesn't support it. @@ -630,6 +632,14 @@ fn gen_guard_block_param_proxy(jit: &JITState, asm: &mut Assembler, level: u32, asm.jz(side_exit(jit, state, SideExitReason::BlockParamProxyNotIseqOrIfunc)); } +fn gen_guard_not_frozen(jit: &JITState, asm: &mut Assembler, val: Opnd, state: &FrameState) -> Opnd { + let ret = asm_ccall!(asm, rb_obj_frozen_p, val); + asm_comment!(asm, "side-exit if rb_obj_frozen_p returns Qtrue"); + asm.cmp(ret, Qtrue.into()); + asm.je(side_exit(jit, state, GuardNotFrozen)); + val +} + fn gen_get_constant_path(jit: &JITState, asm: &mut Assembler, ic: *const iseq_inline_constant_cache, state: &FrameState) -> Opnd { unsafe extern "C" { fn rb_vm_opt_getconstant_path(ec: EcPtr, cfp: CfpPtr, ic: *const iseq_inline_constant_cache) -> VALUE; @@ -1340,6 +1350,11 @@ fn gen_aref_fixnum( asm_ccall!(asm, rb_ary_entry, array, unboxed_idx) } +fn gen_array_pop(asm: &mut Assembler, array: Opnd, state: &FrameState) -> lir::Opnd { + gen_prepare_leaf_call_with_gc(asm, state); + asm_ccall!(asm, rb_ary_pop, array) +} + fn gen_array_length(asm: &mut Assembler, array: Opnd) -> lir::Opnd { asm_ccall!(asm, rb_jit_array_len, array) } diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index f7e6cdde9419b3..eb8ffcd1580e8c 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -1158,6 +1158,7 @@ unsafe extern "C" { pub fn rb_ary_resurrect(ary: VALUE) -> VALUE; pub fn rb_ary_cat(ary: VALUE, train: *const VALUE, len: ::std::os::raw::c_long) -> VALUE; pub fn rb_ary_push(ary: VALUE, elem: VALUE) -> VALUE; + pub fn rb_ary_pop(ary: VALUE) -> VALUE; pub fn rb_ary_entry(ary: VALUE, off: ::std::os::raw::c_long) -> VALUE; pub fn rb_ary_clear(ary: VALUE) -> VALUE; pub fn rb_ary_concat(lhs: VALUE, rhs: VALUE) -> VALUE; diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 78a79b0faee7fc..12d226ce51bea2 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -212,6 +212,7 @@ pub fn init() -> Annotations { annotate!(rb_cArray, "[]", inline_array_aref); annotate!(rb_cArray, "<<", inline_array_push); annotate!(rb_cArray, "push", inline_array_push); + annotate!(rb_cArray, "pop", inline_array_pop); annotate!(rb_cHash, "[]", inline_hash_aref); annotate!(rb_cHash, "size", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cHash, "empty?", types::BoolExact, no_gc, leaf, elidable); @@ -287,6 +288,13 @@ fn inline_array_push(fun: &mut hir::Function, block: hir::BlockId, recv: hir::In None } +fn inline_array_pop(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + // Only inline the case of no arguments. + let &[] = args else { return None; }; + let arr = fun.push_insn(block, hir::Insn::GuardNotFrozen { val: recv, state }); + Some(fun.push_insn(block, hir::Insn::ArrayPop { array: arr, state })) +} + fn inline_hash_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { if let &[key] = args { let result = fun.push_insn(block, hir::Insn::HashAref { hash: recv, key, state }); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 8c4f9eb4fe43dc..0f532cb459334f 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -461,6 +461,7 @@ pub enum SideExitReason { GuardTypeNot(Type), GuardShape(ShapeId), GuardBitEquals(Const), + GuardNotFrozen, PatchPoint(Invariant), CalleeSideExit, ObjToStringFallback, @@ -585,6 +586,7 @@ pub enum Insn { /// Push `val` onto `array`, where `array` is already `Array`. ArrayPush { array: InsnId, val: InsnId, state: InsnId }, ArrayArefFixnum { array: InsnId, index: InsnId }, + ArrayPop { array: InsnId, state: InsnId }, /// Return the length of the array as a C `long` ([`types::CInt64`]) ArrayLength { array: InsnId }, @@ -795,6 +797,8 @@ pub enum Insn { /// Side-exit if the block param has been modified or the block handler for the frame /// is neither ISEQ nor ifunc, which makes it incompatible with rb_block_param_proxy. GuardBlockParamProxy { level: u32, state: InsnId }, + /// Side-exit if val is frozen. + GuardNotFrozen { val: InsnId, state: InsnId }, /// Generate no code (or padding if necessary) and insert a patch point /// that can be rewritten to a side exit when the Invariant is broken. @@ -921,6 +925,9 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::ArrayArefFixnum { array, index, .. } => { write!(f, "ArrayArefFixnum {array}, {index}") } + Insn::ArrayPop { array, .. } => { + write!(f, "ArrayPop {array}") + } Insn::ArrayLength { array } => { write!(f, "ArrayLength {array}") } @@ -1082,6 +1089,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::GuardBitEquals { val, expected, .. } => { write!(f, "GuardBitEquals {val}, {}", expected.print(self.ptr_map)) }, &Insn::GuardShape { val, shape, .. } => { write!(f, "GuardShape {val}, {:p}", self.ptr_map.map_shape(shape)) }, Insn::GuardBlockParamProxy { level, .. } => write!(f, "GuardBlockParamProxy l{level}"), + Insn::GuardNotFrozen { val, .. } => write!(f, "GuardNotFrozen {val}"), Insn::PatchPoint { invariant, .. } => { write!(f, "PatchPoint {}", invariant.print(self.ptr_map)) }, Insn::GetConstantPath { ic, .. } => { write!(f, "GetConstantPath {:p}", self.ptr_map.map_ptr(ic)) }, Insn::IsBlockGiven => { write!(f, "IsBlockGiven") }, @@ -1639,6 +1647,7 @@ impl Function { &GuardBitEquals { val, expected, state } => GuardBitEquals { val: find!(val), expected, state }, &GuardShape { val, shape, state } => GuardShape { val: find!(val), shape, state }, &GuardBlockParamProxy { level, state } => GuardBlockParamProxy { level, state: find!(state) }, + &GuardNotFrozen { val, state } => GuardNotFrozen { val: find!(val), state }, &FixnumAdd { left, right, state } => FixnumAdd { left: find!(left), right: find!(right), state }, &FixnumSub { left, right, state } => FixnumSub { left: find!(left), right: find!(right), state }, &FixnumMult { left, right, state } => FixnumMult { left: find!(left), right: find!(right), state }, @@ -1736,6 +1745,7 @@ impl Function { &NewRange { low, high, flag, state } => NewRange { low: find!(low), high: find!(high), flag, state: find!(state) }, &NewRangeFixnum { low, high, flag, state } => NewRangeFixnum { low: find!(low), high: find!(high), flag, state: find!(state) }, &ArrayArefFixnum { array, index } => ArrayArefFixnum { array: find!(array), index: find!(index) }, + &ArrayPop { array, state } => ArrayPop { array: find!(array), state: find!(state) }, &ArrayLength { array } => ArrayLength { array: find!(array) }, &ArrayMax { ref elements, state } => ArrayMax { elements: find_vec!(elements), state: find!(state) }, &SetGlobal { id, val, state } => SetGlobal { id, val: find!(val), state }, @@ -1829,6 +1839,7 @@ impl Function { Insn::NewArray { .. } => types::ArrayExact, Insn::ArrayDup { .. } => types::ArrayExact, Insn::ArrayArefFixnum { .. } => types::BasicObject, + Insn::ArrayPop { .. } => types::BasicObject, Insn::ArrayLength { .. } => types::CInt64, Insn::HashAref { .. } => types::BasicObject, Insn::NewHash { .. } => types::HashExact, @@ -1844,6 +1855,7 @@ impl Function { Insn::GuardTypeNot { .. } => types::BasicObject, Insn::GuardBitEquals { val, expected, .. } => self.type_of(*val).intersection(Type::from_const(*expected)), Insn::GuardShape { val, .. } => self.type_of(*val), + Insn::GuardNotFrozen { val, .. } => self.type_of(*val), Insn::FixnumAdd { .. } => types::Fixnum, Insn::FixnumSub { .. } => types::Fixnum, Insn::FixnumMult { .. } => types::Fixnum, @@ -3131,6 +3143,7 @@ impl Function { | &Insn::GuardTypeNot { val, state, .. } | &Insn::GuardBitEquals { val, state, .. } | &Insn::GuardShape { val, state, .. } + | &Insn::GuardNotFrozen { val, state } | &Insn::ToArray { val, state } | &Insn::IsMethodCfunc { val, state, .. } | &Insn::ToNewArray { val, state } => { @@ -3182,6 +3195,10 @@ impl Function { worklist.push_back(array); worklist.push_back(index); } + &Insn::ArrayPop { array, state } => { + worklist.push_back(array); + worklist.push_back(state); + } &Insn::ArrayLength { array } => { worklist.push_back(array); } @@ -14421,6 +14438,94 @@ mod opt_tests { "); } + #[test] + fn test_optimize_array_pop_no_arg() { + eval(" + def test(arr) = arr.pop + test([1]) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Array@0x1000, pop@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v23:ArrayExact = GuardType v9, ArrayExact + v24:ArrayExact = GuardNotFrozen v23 + v25:BasicObject = ArrayPop v24 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_do_not_optimize_array_pop_arg() { + eval(" + def test(arr) = arr.pop(4) + test([1]) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[4] = Const Value(4) + PatchPoint MethodRedefined(Array@0x1000, pop@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v24:ArrayExact = GuardType v9, ArrayExact + v25:BasicObject = CCallVariadic pop@0x1038, v24, v13 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_guard_array_pop_frozen() { + eval(" + def test(arr) + arr.pop + rescue FrozenError + nil + end + arr = [1].freeze + test(arr) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Array@0x1000, pop@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v23:ArrayExact = GuardType v9, ArrayExact + v24:ArrayExact = GuardNotFrozen v23 + v25:BasicObject = ArrayPop v24 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v25 + "); + } + #[test] fn test_optimize_regexpmatch2() { eval(r#" diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 4dd87d269ad4e7..5ab81f9ac6d71f 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -143,6 +143,7 @@ make_counters! { exit_guard_bit_equals_failure, exit_guard_int_equals_failure, exit_guard_shape_failure, + exit_guard_not_frozen_failure, exit_patchpoint_bop_redefined, exit_patchpoint_method_redefined, exit_patchpoint_stable_constant_names, @@ -342,6 +343,7 @@ pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter { GuardTypeNot(_) => exit_guard_type_not_failure, GuardBitEquals(_) => exit_guard_bit_equals_failure, GuardShape(_) => exit_guard_shape_failure, + GuardNotFrozen => exit_guard_not_frozen_failure, CalleeSideExit => exit_callee_side_exit, ObjToStringFallback => exit_obj_to_string_fallback, Interrupt => exit_interrupt, From ec1b9bbd583f34d3d947f25e0bfc4b64a51f7e05 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 28 Oct 2025 09:24:04 -0700 Subject: [PATCH 09/18] ZJIT: Call test again after profiling it It's a call_threshold: 2 test https://github.com/ruby/ruby/pull/14933#discussion_r2469731499 --- test/ruby/test_zjit.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 802370aa599405..6805d91406be5c 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -1211,6 +1211,7 @@ def test_array_pop_arg assert_compiles '[33, 42]', %q{ def test(arr) = arr.pop(2) test([32, 33, 42]) + test([32, 33, 42]) }, call_threshold: 2 end From b66c57be1888a40cf6329c501275051e9cbfc1f7 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 28 Oct 2025 01:51:56 -0700 Subject: [PATCH 10/18] ZJIT: Print unexpected operands on x86_64 --- zjit/src/asm/x86_64/mod.rs | 4 ++-- zjit/src/backend/x86_64/mod.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/zjit/src/asm/x86_64/mod.rs b/zjit/src/asm/x86_64/mod.rs index 854438ad40d231..8077218c4ac720 100644 --- a/zjit/src/asm/x86_64/mod.rs +++ b/zjit/src/asm/x86_64/mod.rs @@ -509,7 +509,7 @@ fn write_rm_unary(cb: &mut CodeBlock, op_mem_reg_8: u8, op_mem_reg_pref: u8, op_ // Encode an add-like RM instruction with multiple possible encodings fn write_rm_multi(cb: &mut CodeBlock, op_mem_reg8: u8, op_mem_reg_pref: u8, op_reg_mem8: u8, op_reg_mem_pref: u8, op_mem_imm8: u8, op_mem_imm_sml: u8, op_mem_imm_lrg: u8, op_ext_imm: Option, opnd0: X86Opnd, opnd1: X86Opnd) { - assert!(matches!(opnd0, X86Opnd::Reg(_) | X86Opnd::Mem(_))); + assert!(matches!(opnd0, X86Opnd::Reg(_) | X86Opnd::Mem(_)), "unexpected opnd0: {opnd0:?}, {opnd1:?}"); // Check the size of opnd0 let opnd_size = opnd0.num_bits(); @@ -1334,7 +1334,7 @@ pub fn test(cb: &mut CodeBlock, rm_opnd: X86Opnd, test_opnd: X86Opnd) { write_rm(cb, rm_num_bits == 16, rm_num_bits == 64, test_opnd, rm_opnd, None, &[0x85]); } }, - _ => unreachable!() + _ => unreachable!("unexpected operands for test: {rm_opnd:?}, {test_opnd:?}") }; } diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index b6c4658463048a..4fdbfe3f97c933 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -69,7 +69,7 @@ impl From for X86Opnd { "Attempted to lower an Opnd::None. This often happens when an out operand was not allocated for an instruction because the output of the instruction was not used. Please ensure you are using the output." ), - _ => panic!("unsupported x86 operand type") + _ => panic!("unsupported x86 operand type: {opnd:?}") } } } From 9b3df50d9015bc0295266c85712ba32c5b5c157d Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 28 Oct 2025 01:53:32 -0700 Subject: [PATCH 11/18] ZJIT: Allow ALLOC_REGS to have an odd number of regs --- zjit/src/codegen.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 8ff1d400c32953..5360fd20469992 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -2127,13 +2127,18 @@ pub fn gen_function_stub_hit_trampoline(cb: &mut CodeBlock) -> Result Date: Tue, 28 Oct 2025 02:00:33 -0700 Subject: [PATCH 12/18] ZJIT: Stop computing offset on gen_push_opnds Once we add register spill, the C stack will have not only spilled basic block params but also spilled VRegs. We won't know how many stack slots are used for spilled VRegs until alloc_regs, so you can't compute an offset as of writing LIR. --- zjit/src/codegen.rs | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 5360fd20469992..d2ba14b1721e96 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -814,7 +814,7 @@ fn gen_ccall_variadic( asm.mov(CFP, new_cfp); asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); - let argv_ptr = gen_push_opnds(jit, asm, &args); + let argv_ptr = gen_push_opnds(asm, &args); let result = asm.ccall(cfunc, vec![args.len().into(), argv_ptr, recv]); gen_pop_opnds(asm, &args); @@ -1372,7 +1372,7 @@ fn gen_new_hash( let new_hash = asm_ccall!(asm, rb_hash_new_with_size, lir::Opnd::Imm(cap)); if !elements.is_empty() { - let argv = gen_push_opnds(jit, asm, &elements); + let argv = gen_push_opnds(asm, &elements); asm_ccall!(asm, rb_hash_bulk_insert, elements.len().into(), argv, new_hash); gen_pop_opnds(asm, &elements); @@ -2183,15 +2183,11 @@ pub fn gen_exit_trampoline_with_counter(cb: &mut CodeBlock, exit_trampoline: Cod }) } -fn gen_push_opnds(jit: &mut JITState, asm: &mut Assembler, opnds: &[Opnd]) -> lir::Opnd { +fn gen_push_opnds(asm: &mut Assembler, opnds: &[Opnd]) -> lir::Opnd { let n = opnds.len(); - - // Calculate the compile-time NATIVE_STACK_PTR offset from NATIVE_BASE_PTR - // At this point, frame_setup(&[], jit.c_stack_slots) has been called, - // which allocated aligned_stack_bytes(jit.c_stack_slots) on the stack - let frame_size = aligned_stack_bytes(jit.c_stack_slots); let allocation_size = aligned_stack_bytes(n); + // Bump the stack pointer to reserve the space for opnds if n != 0 { asm_comment!(asm, "allocate {} bytes on C stack for {} values", allocation_size, n); asm.sub_into(NATIVE_STACK_PTR, allocation_size.into()); @@ -2199,18 +2195,14 @@ fn gen_push_opnds(jit: &mut JITState, asm: &mut Assembler, opnds: &[Opnd]) -> li asm_comment!(asm, "no opnds to allocate"); } - // Calculate the total offset from NATIVE_BASE_PTR to our buffer - let total_offset_from_base = (frame_size + allocation_size) as i32; - + // Load NATIVE_STACK_PTR to get the address of a returned array + // to allow the backend to move it for its own use. + let argv = asm.load(NATIVE_STACK_PTR); for (idx, &opnd) in opnds.iter().enumerate() { - let slot_offset = -total_offset_from_base + (idx as i32 * SIZEOF_VALUE_I32); - asm.mov( - Opnd::mem(VALUE_BITS, NATIVE_BASE_PTR, slot_offset), - opnd - ); + asm.mov(Opnd::mem(VALUE_BITS, argv, idx as i32 * SIZEOF_VALUE_I32), opnd); } - asm.lea(Opnd::mem(64, NATIVE_BASE_PTR, -total_offset_from_base)) + argv } fn gen_pop_opnds(asm: &mut Assembler, opnds: &[Opnd]) { @@ -2227,7 +2219,7 @@ fn gen_pop_opnds(asm: &mut Assembler, opnds: &[Opnd]) { fn gen_toregexp(jit: &mut JITState, asm: &mut Assembler, opt: usize, values: Vec, state: &FrameState) -> Opnd { gen_prepare_non_leaf_call(jit, asm, state); - let first_opnd_ptr = gen_push_opnds(jit, asm, &values); + let first_opnd_ptr = gen_push_opnds(asm, &values); let tmp_ary = asm_ccall!(asm, rb_ary_tmp_new_from_values, Opnd::Imm(0), values.len().into(), first_opnd_ptr); let result = asm_ccall!(asm, rb_reg_new_ary, tmp_ary, opt.into()); @@ -2241,7 +2233,7 @@ fn gen_toregexp(jit: &mut JITState, asm: &mut Assembler, opt: usize, values: Vec fn gen_string_concat(jit: &mut JITState, asm: &mut Assembler, strings: Vec, state: &FrameState) -> Opnd { gen_prepare_non_leaf_call(jit, asm, state); - let first_string_ptr = gen_push_opnds(jit, asm, &strings); + let first_string_ptr = gen_push_opnds(asm, &strings); let result = asm_ccall!(asm, rb_str_concat_literals, strings.len().into(), first_string_ptr); gen_pop_opnds(asm, &strings); From cc051ef0e56b37c9bd29cabd4e930a170a832bcf Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 28 Oct 2025 02:32:33 -0700 Subject: [PATCH 13/18] ZJIT: Simplify Assembler constructors --- zjit/src/backend/arm64/mod.rs | 9 ++++---- zjit/src/backend/lir.rs | 38 +++++++++++++++++++++------------- zjit/src/backend/x86_64/mod.rs | 9 ++++---- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index a6a5fc59582610..5760cadfc30490 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -207,7 +207,7 @@ impl Assembler { /// Return an Assembler with scratch registers disabled in the backend, and a scratch register. pub fn new_with_scratch_reg() -> (Self, Opnd) { - (Self::new_with_label_names(Vec::default(), 0, true), SCRATCH_OPND) + (Self::new_with_accept_scratch_reg(true), SCRATCH_OPND) } /// Return true if opnd contains a scratch reg @@ -386,9 +386,9 @@ impl Assembler { } } + let mut asm_local = Assembler::new_with_asm(&self); let live_ranges: Vec = take(&mut self.live_ranges); let mut iterator = self.insns.into_iter().enumerate().peekable(); - let mut asm_local = Assembler::new_with_label_names(take(&mut self.label_names), live_ranges.len(), self.accept_scratch_reg); let asm = &mut asm_local; while let Some((index, mut insn)) = iterator.next() { @@ -691,9 +691,10 @@ impl Assembler { /// VRegs, most splits should happen in [`Self::arm64_split`]. However, some instructions /// need to be split with registers after `alloc_regs`, e.g. for `compile_side_exits`, so this /// splits them and uses scratch registers for it. - fn arm64_split_with_scratch_reg(mut self) -> Assembler { + fn arm64_split_with_scratch_reg(self) -> Assembler { + let mut asm = Assembler::new_with_asm(&self); + asm.accept_scratch_reg = true; let iterator = self.insns.into_iter().enumerate().peekable(); - let mut asm = Assembler::new_with_label_names(take(&mut self.label_names), self.live_ranges.len(), true); for (_, mut insn) in iterator { match &mut insn { diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index 6efb3e1259e4e6..55151d0605ade7 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -1182,26 +1182,36 @@ pub struct Assembler { impl Assembler { - /// Create an Assembler + /// Create an Assembler with defaults pub fn new() -> Self { - Self::new_with_label_names(Vec::default(), 0, false) - } - - /// Create an Assembler with parameters that are populated by another Assembler instance. - /// This API is used for copying an Assembler for the next compiler pass. - pub fn new_with_label_names(label_names: Vec, num_vregs: usize, accept_scratch_reg: bool) -> Self { - let mut live_ranges = Vec::with_capacity(ASSEMBLER_INSNS_CAPACITY); - live_ranges.resize(num_vregs, LiveRange { start: None, end: None }); - Self { insns: Vec::with_capacity(ASSEMBLER_INSNS_CAPACITY), - live_ranges, - label_names, - accept_scratch_reg, + live_ranges: Vec::with_capacity(ASSEMBLER_INSNS_CAPACITY), + label_names: Vec::default(), + accept_scratch_reg: false, leaf_ccall_stack_size: None, } } + /// Create an Assembler that allows the use of scratch registers. + /// This should be called only through [`Self::new_with_scratch_reg`]. + pub(super) fn new_with_accept_scratch_reg(accept_scratch_reg: bool) -> Self { + Self { accept_scratch_reg, ..Self::new() } + } + + /// Create an Assembler with parameters of another Assembler and empty instructions. + /// Compiler passes build a next Assembler with this API and insert new instructions to it. + pub(super) fn new_with_asm(old_asm: &Assembler) -> Self { + let mut asm = Self { + label_names: old_asm.label_names.clone(), + accept_scratch_reg: old_asm.accept_scratch_reg, + ..Self::new() + }; + // Bump the initial VReg index to allow the use of the VRegs for the old Assembler + asm.live_ranges.resize(old_asm.live_ranges.len(), LiveRange { start: None, end: None }); + asm + } + pub fn expect_leaf_ccall(&mut self, stack_size: usize) { self.leaf_ccall_stack_size = Some(stack_size); } @@ -1357,9 +1367,9 @@ impl Assembler let mut saved_regs: Vec<(Reg, usize)> = vec![]; // live_ranges is indexed by original `index` given by the iterator. + let mut asm = Assembler::new_with_asm(&self); let live_ranges: Vec = take(&mut self.live_ranges); let mut iterator = self.insns.into_iter().enumerate().peekable(); - let mut asm = Assembler::new_with_label_names(take(&mut self.label_names), live_ranges.len(), self.accept_scratch_reg); while let Some((index, mut insn)) = iterator.next() { let before_ccall = match (&insn, iterator.peek().map(|(_, insn)| insn)) { diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index 4fdbfe3f97c933..e7e2f796f190e2 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -102,7 +102,7 @@ const SCRATCH_OPND: Opnd = Opnd::Reg(R11_REG); impl Assembler { /// Return an Assembler with scratch registers disabled in the backend, and a scratch register. pub fn new_with_scratch_reg() -> (Self, Opnd) { - (Self::new_with_label_names(Vec::default(), 0, true), SCRATCH_OPND) + (Self::new_with_accept_scratch_reg(true), SCRATCH_OPND) } /// Return true if opnd contains a scratch reg @@ -137,9 +137,9 @@ impl Assembler { /// Split IR instructions for the x86 platform fn x86_split(mut self) -> Assembler { + let mut asm = Assembler::new_with_asm(&self); let live_ranges: Vec = take(&mut self.live_ranges); let mut iterator = self.insns.into_iter().enumerate().peekable(); - let mut asm = Assembler::new_with_label_names(take(&mut self.label_names), live_ranges.len(), self.accept_scratch_reg); while let Some((index, mut insn)) = iterator.next() { let is_load = matches!(insn, Insn::Load { .. } | Insn::LoadInto { .. }); @@ -390,7 +390,7 @@ impl Assembler { /// for VRegs, most splits should happen in [`Self::x86_split`]. However, some instructions /// need to be split with registers after `alloc_regs`, e.g. for `compile_side_exits`, so /// this splits them and uses scratch registers for it. - pub fn x86_split_with_scratch_reg(mut self) -> Assembler { + pub fn x86_split_with_scratch_reg(self) -> Assembler { /// For some instructions, we want to be able to lower a 64-bit operand /// without requiring more registers to be available in the register /// allocator. So we just use the SCRATCH_OPND register temporarily to hold @@ -419,8 +419,9 @@ impl Assembler { } } + let mut asm = Assembler::new_with_asm(&self); + asm.accept_scratch_reg = true; let mut iterator = self.insns.into_iter().enumerate().peekable(); - let mut asm = Assembler::new_with_label_names(take(&mut self.label_names), self.live_ranges.len(), true); while let Some((_, mut insn)) = iterator.next() { match &mut insn { From 0e1d99ce69135bc875d23903eca3f75ad8ab6405 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 28 Oct 2025 02:43:21 -0700 Subject: [PATCH 14/18] ZJIT: Move c_stack_slots to Assembler --- zjit/src/backend/arm64/mod.rs | 11 +++++++---- zjit/src/backend/lir.rs | 18 ++++++++++++++++- zjit/src/backend/x86_64/mod.rs | 35 ++++++++++++++++++++-------------- zjit/src/codegen.rs | 20 +++++++++---------- 4 files changed, 54 insertions(+), 30 deletions(-) diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index 5760cadfc30490..867ad493ec2821 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -1665,7 +1665,7 @@ mod tests { fn test_emit_frame() { let (mut asm, mut cb) = setup_asm(); - asm.frame_setup(&[], 0); + asm.frame_setup(&[]); asm.frame_teardown(&[]); asm.compile_with_num_regs(&mut cb, 0); @@ -1684,7 +1684,8 @@ mod tests { // Test 3 preserved regs (odd), odd slot_count let cb1 = { let (mut asm, mut cb) = setup_asm(); - asm.frame_setup(THREE_REGS, 3); + asm.stack_base_idx = 3; + asm.frame_setup(THREE_REGS); asm.frame_teardown(THREE_REGS); asm.compile_with_num_regs(&mut cb, 0); cb @@ -1693,7 +1694,8 @@ mod tests { // Test 3 preserved regs (odd), even slot_count let cb2 = { let (mut asm, mut cb) = setup_asm(); - asm.frame_setup(THREE_REGS, 4); + asm.stack_base_idx = 4; + asm.frame_setup(THREE_REGS); asm.frame_teardown(THREE_REGS); asm.compile_with_num_regs(&mut cb, 0); cb @@ -1703,7 +1705,8 @@ mod tests { let cb3 = { static FOUR_REGS: &[Opnd] = &[Opnd::Reg(X19_REG), Opnd::Reg(X20_REG), Opnd::Reg(X21_REG), Opnd::Reg(X22_REG)]; let (mut asm, mut cb) = setup_asm(); - asm.frame_setup(FOUR_REGS, 3); + asm.stack_base_idx = 3; + asm.frame_setup(FOUR_REGS); asm.frame_teardown(FOUR_REGS); asm.compile_with_num_regs(&mut cb, 0); cb diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index 55151d0605ade7..eb49b419d62046 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -1034,6 +1034,9 @@ impl fmt::Debug for Insn { // Print list of operands let mut opnd_iter = self.opnd_iter(); + if let Insn::FrameSetup { slot_count, .. } = self { + write!(fmt, "{slot_count}")?; + } if let Some(first_opnd) = opnd_iter.next() { write!(fmt, "{first_opnd:?}")?; } @@ -1176,6 +1179,11 @@ pub struct Assembler { /// On `compile`, it also disables the backend's use of them. pub(super) accept_scratch_reg: bool, + /// The Assembler can use NATIVE_BASE_PTR + stack_base_idx as the + /// first stack slot in case it needs to allocate memory. This is + /// equal to the number of spilled basic block arguments. + pub(super) stack_base_idx: usize, + /// If Some, the next ccall should verify its leafness leaf_ccall_stack_size: Option } @@ -1189,10 +1197,16 @@ impl Assembler live_ranges: Vec::with_capacity(ASSEMBLER_INSNS_CAPACITY), label_names: Vec::default(), accept_scratch_reg: false, + stack_base_idx: 0, leaf_ccall_stack_size: None, } } + /// Create an Assembler, reserving a specified number of stack slots + pub fn new_with_stack_slots(stack_base_idx: usize) -> Self { + Self { stack_base_idx, ..Self::new() } + } + /// Create an Assembler that allows the use of scratch registers. /// This should be called only through [`Self::new_with_scratch_reg`]. pub(super) fn new_with_accept_scratch_reg(accept_scratch_reg: bool) -> Self { @@ -1205,6 +1219,7 @@ impl Assembler let mut asm = Self { label_names: old_asm.label_names.clone(), accept_scratch_reg: old_asm.accept_scratch_reg, + stack_base_idx: old_asm.stack_base_idx, ..Self::new() }; // Bump the initial VReg index to allow the use of the VRegs for the old Assembler @@ -1841,7 +1856,8 @@ impl Assembler { out } - pub fn frame_setup(&mut self, preserved_regs: &'static [Opnd], slot_count: usize) { + pub fn frame_setup(&mut self, preserved_regs: &'static [Opnd]) { + let slot_count = self.stack_base_idx; self.push_insn(Insn::FrameSetup { preserved: preserved_regs, slot_count }); } diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index e7e2f796f190e2..81f44f2881976d 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -986,7 +986,9 @@ impl Assembler { #[cfg(test)] mod tests { use insta::assert_snapshot; - use crate::assert_disasm_snapshot; + #[cfg(feature = "disasm")] + use crate::disasms_with; + use crate::{assert_disasm_snapshot, hexdumps}; use super::*; fn setup_asm() -> (Assembler, CodeBlock) { @@ -1553,18 +1555,19 @@ mod tests { #[test] fn frame_setup_teardown() { - let (mut asm, mut cb) = setup_asm(); - asm.frame_setup(JIT_PRESERVED_REGS, 0); + let (mut asm, mut cb1) = setup_asm(); + asm.frame_setup(JIT_PRESERVED_REGS); asm.frame_teardown(JIT_PRESERVED_REGS); - asm.cret(C_RET_OPND); + asm.compile_with_num_regs(&mut cb1, 0); - asm.frame_setup(&[], 5); + let (mut asm, mut cb2) = setup_asm(); + asm.stack_base_idx = 5; + asm.frame_setup(&[]); asm.frame_teardown(&[]); + asm.compile_with_num_regs(&mut cb2, 0); - asm.compile_with_num_regs(&mut cb, 0); - - assert_disasm_snapshot!(cb.disasm(), @" + assert_disasm_snapshot!(disasms_with!("\n", cb1, cb2), @r" 0x0: push rbp 0x1: mov rbp, rsp 0x4: push r13 @@ -1577,13 +1580,17 @@ mod tests { 0x19: mov rsp, rbp 0x1c: pop rbp 0x1d: ret - 0x1e: push rbp - 0x1f: mov rbp, rsp - 0x22: sub rsp, 0x30 - 0x26: mov rsp, rbp - 0x29: pop rbp + + 0x0: push rbp + 0x1: mov rbp, rsp + 0x4: sub rsp, 0x30 + 0x8: mov rsp, rbp + 0xb: pop rbp + "); + assert_snapshot!(hexdumps!(cb1, cb2), @r" + 554889e541555341544883ec084c8b6df8488b5df04c8b65e84889ec5dc3 + 554889e54883ec304889ec5d "); - assert_snapshot!(cb.hexdump(), @"554889e541555341544883ec084c8b6df8488b5df04c8b65e84889ec5dc3554889e54883ec304889ec5d"); } #[test] diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index d2ba14b1721e96..bfbc8e88b962a1 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -41,21 +41,17 @@ struct JITState { /// ISEQ calls that need to be compiled later iseq_calls: Vec, - - /// The number of bytes allocated for basic block arguments spilled onto the C stack - c_stack_slots: usize, } impl JITState { /// Create a new JITState instance - fn new(iseq: IseqPtr, num_insns: usize, num_blocks: usize, c_stack_slots: usize) -> Self { + fn new(iseq: IseqPtr, num_insns: usize, num_blocks: usize) -> Self { JITState { iseq, opnds: vec![None; num_insns], labels: vec![None; num_blocks], jit_entries: Vec::default(), iseq_calls: Vec::default(), - c_stack_slots, } } @@ -246,9 +242,9 @@ fn gen_iseq_body(cb: &mut CodeBlock, iseq: IseqPtr, function: Option<&Function>, /// Compile a function fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Result<(IseqCodePtrs, Vec, Vec), CompileError> { - let c_stack_slots = max_num_params(function).saturating_sub(ALLOC_REGS.len()); - let mut jit = JITState::new(iseq, function.num_insns(), function.num_blocks(), c_stack_slots); - let mut asm = Assembler::new(); + let num_spilled_params = max_num_params(function).saturating_sub(ALLOC_REGS.len()); + let mut jit = JITState::new(iseq, function.num_insns(), function.num_blocks()); + let mut asm = Assembler::new_with_stack_slots(num_spilled_params); // Compile each basic block let reverse_post_order = function.rpo(); @@ -1011,7 +1007,7 @@ fn gen_load_ivar_extended(asm: &mut Assembler, self_val: Opnd, id: ID, index: u1 fn gen_entry_prologue(asm: &mut Assembler, iseq: IseqPtr) { asm_comment!(asm, "ZJIT entry point: {}", iseq_get_location(iseq, 0)); // Save the registers we'll use for CFP, EP, SP - asm.frame_setup(lir::JIT_PRESERVED_REGS, 0); + asm.frame_setup(lir::JIT_PRESERVED_REGS); // EC and CFP are passed as arguments asm.mov(EC, C_ARG_OPNDS[0]); @@ -1439,7 +1435,7 @@ fn gen_entry_point(jit: &mut JITState, asm: &mut Assembler, jit_entry_idx: Optio jit_entry.borrow_mut().start_addr.set(Some(code_ptr)); }); } - asm.frame_setup(&[], jit.c_stack_slots); + asm.frame_setup(&[]); } /// Compile code that exits from JIT code with a return value @@ -1910,6 +1906,8 @@ fn param_opnd(idx: usize) -> Opnd { if idx < ALLOC_REGS.len() { Opnd::Reg(ALLOC_REGS[idx]) } else { + // With FrameSetup, the address that NATIVE_BASE_PTR points to stores an old value in the register. + // To avoid clobbering it, we need to start from the next slot, hence `+ 1` for the index. Opnd::mem(64, NATIVE_BASE_PTR, (idx - ALLOC_REGS.len() + 1) as i32 * -SIZEOF_VALUE_I32) } } @@ -2121,7 +2119,7 @@ pub fn gen_function_stub_hit_trampoline(cb: &mut CodeBlock) -> Result Date: Tue, 28 Oct 2025 09:00:00 -0700 Subject: [PATCH 15/18] ZJIT: Split frame_setup_teardown tests --- zjit/src/backend/x86_64/mod.rs | 65 +++++++++++++++++----------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index 81f44f2881976d..bd2421823c9f3c 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -986,9 +986,7 @@ impl Assembler { #[cfg(test)] mod tests { use insta::assert_snapshot; - #[cfg(feature = "disasm")] - use crate::disasms_with; - use crate::{assert_disasm_snapshot, hexdumps}; + use crate::assert_disasm_snapshot; use super::*; fn setup_asm() -> (Assembler, CodeBlock) { @@ -1554,43 +1552,46 @@ mod tests { } #[test] - fn frame_setup_teardown() { - let (mut asm, mut cb1) = setup_asm(); + fn frame_setup_teardown_preserved_regs() { + let (mut asm, mut cb) = setup_asm(); asm.frame_setup(JIT_PRESERVED_REGS); asm.frame_teardown(JIT_PRESERVED_REGS); asm.cret(C_RET_OPND); - asm.compile_with_num_regs(&mut cb1, 0); + asm.compile_with_num_regs(&mut cb, 0); + + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: push rbp + 0x1: mov rbp, rsp + 0x4: push r13 + 0x6: push rbx + 0x7: push r12 + 0x9: sub rsp, 8 + 0xd: mov r13, qword ptr [rbp - 8] + 0x11: mov rbx, qword ptr [rbp - 0x10] + 0x15: mov r12, qword ptr [rbp - 0x18] + 0x19: mov rsp, rbp + 0x1c: pop rbp + 0x1d: ret + "); + assert_snapshot!(cb.hexdump(), @"554889e541555341544883ec084c8b6df8488b5df04c8b65e84889ec5dc3"); + } - let (mut asm, mut cb2) = setup_asm(); + #[test] + fn frame_setup_teardown_stack_base_idx() { + let (mut asm, mut cb) = setup_asm(); asm.stack_base_idx = 5; asm.frame_setup(&[]); asm.frame_teardown(&[]); - asm.compile_with_num_regs(&mut cb2, 0); - - assert_disasm_snapshot!(disasms_with!("\n", cb1, cb2), @r" - 0x0: push rbp - 0x1: mov rbp, rsp - 0x4: push r13 - 0x6: push rbx - 0x7: push r12 - 0x9: sub rsp, 8 - 0xd: mov r13, qword ptr [rbp - 8] - 0x11: mov rbx, qword ptr [rbp - 0x10] - 0x15: mov r12, qword ptr [rbp - 0x18] - 0x19: mov rsp, rbp - 0x1c: pop rbp - 0x1d: ret - - 0x0: push rbp - 0x1: mov rbp, rsp - 0x4: sub rsp, 0x30 - 0x8: mov rsp, rbp - 0xb: pop rbp - "); - assert_snapshot!(hexdumps!(cb1, cb2), @r" - 554889e541555341544883ec084c8b6df8488b5df04c8b65e84889ec5dc3 - 554889e54883ec304889ec5d + asm.compile_with_num_regs(&mut cb, 0); + + assert_disasm_snapshot!(cb.disasm(), @r" + 0x0: push rbp + 0x1: mov rbp, rsp + 0x4: sub rsp, 0x30 + 0x8: mov rsp, rbp + 0xb: pop rbp "); + assert_snapshot!(cb.hexdump(), @"554889e54883ec304889ec5d"); } #[test] From 2c90da465a7ff601007b324f5ab4959ef6277a89 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Tue, 28 Oct 2025 10:36:37 -0400 Subject: [PATCH 16/18] ZJIT: Count GuardType instructions We can measure how many we can remove by adding type information to C functions, etc. --- zjit.rb | 2 ++ zjit/src/codegen.rs | 1 + zjit/src/stats.rs | 3 +++ 3 files changed, 6 insertions(+) diff --git a/zjit.rb b/zjit.rb index 9ea83fe10f716f..b62d8e2b52808f 100644 --- a/zjit.rb +++ b/zjit.rb @@ -197,6 +197,8 @@ def stats_string :vm_write_to_parent_iseq_local_count, :vm_read_from_parent_iseq_local_count, + :guard_type_count, + :code_region_bytes, :side_exit_count, :total_insn_count, diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index bfbc8e88b962a1..2b71be0e15ba71 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -1595,6 +1595,7 @@ fn gen_test(asm: &mut Assembler, val: lir::Opnd) -> lir::Opnd { /// Compile a type check with a side exit fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, guard_type: Type, state: &FrameState) -> lir::Opnd { + gen_incr_counter(asm, Counter::guard_type_count); if guard_type.is_subtype(types::Fixnum) { asm.test(val, Opnd::UImm(RUBY_FIXNUM_FLAG as u64)); asm.jz(side_exit(jit, state, GuardType(guard_type))); diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 5ab81f9ac6d71f..4874d0fe64146b 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -249,6 +249,9 @@ make_counters! { vm_read_from_parent_iseq_local_count, // TODO(max): Implement // vm_reify_stack_count, + + // The number of times we ran a dynamic check + guard_type_count, } /// Increase a counter by a specified amount From a9d42f7c4bff5cb0c14eec8944329b0b1fd0ad9b Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Tue, 28 Oct 2025 13:04:37 -0400 Subject: [PATCH 17/18] ZJIT: Print percentage of GuardType failure --- zjit.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/zjit.rb b/zjit.rb index b62d8e2b52808f..39e353f32741ca 100644 --- a/zjit.rb +++ b/zjit.rb @@ -151,6 +151,8 @@ def stats_string buf = +"***ZJIT: Printing ZJIT statistics on exit***\n" stats = self.stats + stats[:guard_type_exit_ratio] = stats[:exit_guard_type_failure].to_f / stats[:guard_type_count] * 100 + # Show counters independent from exit_* or dynamic_send_* print_counters_with_prefix(prefix: 'not_inlined_cfuncs_', prompt: 'not inlined C methods', buf:, stats:, limit: 20) # Don't show not_annotated_cfuncs right now because it mostly duplicates not_inlined_cfuncs @@ -198,6 +200,7 @@ def stats_string :vm_read_from_parent_iseq_local_count, :guard_type_count, + :guard_type_exit_ratio, :code_region_bytes, :side_exit_count, @@ -234,6 +237,8 @@ def print_counters(keys, buf:, stats:, right_align: false, base: nil) case key when :ratio_in_zjit value = '%0.1f%%' % value + when :guard_type_exit_ratio + value = '%0.1f%%' % value when /_time_ns\z/ key = key.to_s.sub(/_time_ns\z/, '_time') value = "#{number_with_delimiter(value / 10**6)}ms" From 8a765975354d38bd8845a990fa7e70e7126a2a22 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Tue, 28 Oct 2025 14:36:04 -0400 Subject: [PATCH 18/18] ZJIT: Split HIR tests (#14967) `hir.rs` was getting rather large, so I've opted to move the inline tests into their own files. This should also help when looking for where to put your tests, as the optimization tests have a dedicated file. Future follow up work could make the layout of test modules more idiomatic to Rust. --- zjit/src/hir.rs | 10831 +----------------------------------- zjit/src/hir/opt_tests.rs | 7050 +++++++++++++++++++++++ zjit/src/hir/tests.rs | 3207 +++++++++++ 3 files changed, 10348 insertions(+), 10740 deletions(-) create mode 100644 zjit/src/hir/opt_tests.rs create mode 100644 zjit/src/hir/tests.rs diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 0f532cb459334f..3f68764722b448 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -17,6 +17,9 @@ use crate::profile::{TypeDistributionSummary, ProfiledType}; use crate::stats::Counter; use SendFallbackReason::*; +mod tests; +mod opt_tests; + /// An index of an [`Insn`] in a [`Function`]. This is a popular /// type since this effectively acts as a pointer to an [`Insn`]. /// See also: [`Function::find`]. @@ -5384,7 +5387,7 @@ mod infer_tests { } #[cfg(test)] -mod snapshot_tests { +mod graphviz_tests { use super::*; use insta::assert_snapshot; @@ -5392,10760 +5395,108 @@ mod snapshot_tests { fn hir_string(method: &str) -> String { let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method)); unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; - let function = iseq_to_hir(iseq).unwrap(); - format!("{}", FunctionPrinter::with_snapshot(&function)) + let mut function = iseq_to_hir(iseq).unwrap(); + function.optimize(); + function.validate().unwrap(); + format!("{}", FunctionGraphvizPrinter::new(&function)) } #[test] - fn test_new_array_with_elements() { - eval("def test(a, b) = [a, b]"); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v13:Any = Snapshot FrameState { pc: 0x1000, stack: [], locals: [a=v11, b=v12] } - v14:Any = Snapshot FrameState { pc: 0x1008, stack: [], locals: [a=v11, b=v12] } - PatchPoint NoTracePoint - v16:Any = Snapshot FrameState { pc: 0x1010, stack: [v11, v12], locals: [a=v11, b=v12] } - v17:ArrayExact = NewArray v11, v12 - v18:Any = Snapshot FrameState { pc: 0x1018, stack: [v17], locals: [a=v11, b=v12] } - PatchPoint NoTracePoint - v20:Any = Snapshot FrameState { pc: 0x1018, stack: [v17], locals: [a=v11, b=v12] } - CheckInterrupts - Return v17 - "); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use insta::assert_snapshot; - - fn iseq_contains_opcode(iseq: IseqPtr, expected_opcode: u32) -> bool { - let iseq_size = unsafe { get_iseq_encoded_size(iseq) }; - let mut insn_idx = 0; - while insn_idx < iseq_size { - // Get the current pc and opcode - let pc = unsafe { rb_iseq_pc_at_idx(iseq, insn_idx) }; - - // try_into() call below is unfortunate. Maybe pick i32 instead of usize for opcodes. - let opcode: u32 = unsafe { rb_iseq_opcode_at_pc(iseq, pc) } - .try_into() - .unwrap(); - if opcode == expected_opcode { - return true; - } - insn_idx += insn_len(opcode as usize); - } - false - } - - #[track_caller] - pub fn assert_contains_opcode(method: &str, opcode: u32) { - let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method)); - unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; - assert!(iseq_contains_opcode(iseq, opcode), "iseq {method} does not contain {}", insn_name(opcode as usize)); - } + fn test_guard_fixnum_or_fixnum() { + eval(r#" + def test(x, y) = x | y - #[track_caller] - fn assert_contains_opcodes(method: &str, opcodes: &[u32]) { - let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method)); - unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; - for &opcode in opcodes { - assert!(iseq_contains_opcode(iseq, opcode), "iseq {method} does not contain {}", insn_name(opcode as usize)); + test(1, 2) + "#); + assert_snapshot!(hir_string("test"), @r#" + digraph G { # test@<compiled>:2 + node [shape=plaintext]; + mode=hier; overlap=false; splines=true; + bb0 [label=< + + + + + + +
bb0() 
EntryPoint interpreter 
v1:BasicObject = LoadSelf 
v2:BasicObject = GetLocal l0, SP@5 
v3:BasicObject = GetLocal l0, SP@4 
Jump bb2(v1, v2, v3) 
>]; + bb0:v4 -> bb2:params:n; + bb1 [label=< + + + +
bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject) 
EntryPoint JIT(0) 
Jump bb2(v6, v7, v8) 
>]; + bb1:v9 -> bb2:params:n; + bb2 [label=< + + + + + + + + + + +
bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject) 
PatchPoint NoTracePoint 
PatchPoint NoTracePoint 
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, 29) 
v26:Fixnum = GuardType v11, Fixnum 
v27:Fixnum = GuardType v12, Fixnum 
v28:Fixnum = FixnumOr v26, v27 
PatchPoint NoTracePoint 
CheckInterrupts 
Return v28 
>]; } - } - - /// Combine multiple hir_string() results to match all of them at once, which allows - /// us to avoid running the set of zjit-test -> zjit-test-update multiple times. - #[macro_export] - macro_rules! hir_strings { - ($( $s:expr ),+ $(,)?) => {{ - vec![$( hir_string($s) ),+].join("\n") - }}; - } - - #[track_caller] - fn hir_string(method: &str) -> String { - hir_string_proc(&format!("{}.method(:{})", "self", method)) - } - - #[track_caller] - fn hir_string_proc(proc: &str) -> String { - let iseq = crate::cruby::with_rubyvm(|| get_proc_iseq(proc)); - unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; - let function = iseq_to_hir(iseq).unwrap(); - hir_string_function(&function) - } - - #[track_caller] - fn hir_string_function(function: &Function) -> String { - format!("{}", FunctionPrinter::without_snapshot(function)) - } - - #[track_caller] - fn assert_compile_fails(method: &str, reason: ParseError) { - let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method)); - unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; - let result = iseq_to_hir(iseq); - assert!(result.is_err(), "Expected an error but successfully compiled to HIR: {}", FunctionPrinter::without_snapshot(&result.unwrap())); - assert_eq!(result.unwrap_err(), reason); - } - - #[test] - fn test_compile_optional() { - eval("def test(x=1) = 123"); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - v3:CPtr = LoadPC - v4:CPtr[CPtr(0x1000)] = Const CPtr(0x1008) - v5:CBool = IsBitEqual v3, v4 - IfTrue v5, bb2(v1, v2) - Jump bb4(v1, v2) - bb1(v9:BasicObject, v10:BasicObject): - EntryPoint JIT(0) - Jump bb2(v9, v10) - bb2(v12:BasicObject, v13:BasicObject): - v15:Fixnum[1] = Const Value(1) - Jump bb4(v12, v15) - bb3(v18:BasicObject, v19:BasicObject): - EntryPoint JIT(1) - Jump bb4(v18, v19) - bb4(v21:BasicObject, v22:BasicObject): - v26:Fixnum[123] = Const Value(123) - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_putobject() { - eval("def test = 123"); - assert_contains_opcode("test", YARVINSN_putobject); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[123] = Const Value(123) - CheckInterrupts - Return v10 - "); - } - - #[test] - fn test_new_array() { - eval("def test = []"); - assert_contains_opcode("test", YARVINSN_newarray); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:ArrayExact = NewArray - CheckInterrupts - Return v11 - "); - } - - #[test] - fn test_new_array_with_element() { - eval("def test(a) = [a]"); - assert_contains_opcode("test", YARVINSN_newarray); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:ArrayExact = NewArray v9 - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_new_array_with_elements() { - eval("def test(a, b) = [a, b]"); - assert_contains_opcode("test", YARVINSN_newarray); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v17:ArrayExact = NewArray v11, v12 - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_new_range_inclusive_with_one_element() { - eval("def test(a) = (a..10)"); - assert_contains_opcode("test", YARVINSN_newrange); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[10] = Const Value(10) - v15:RangeExact = NewRange v9 NewRangeInclusive v13 - CheckInterrupts - Return v15 - "); - } - - #[test] - fn test_new_range_inclusive_with_two_elements() { - eval("def test(a, b) = (a..b)"); - assert_contains_opcode("test", YARVINSN_newrange); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v17:RangeExact = NewRange v11 NewRangeInclusive v12 - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_new_range_exclusive_with_one_element() { - eval("def test(a) = (a...10)"); - assert_contains_opcode("test", YARVINSN_newrange); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[10] = Const Value(10) - v15:RangeExact = NewRange v9 NewRangeExclusive v13 - CheckInterrupts - Return v15 - "); - } - - #[test] - fn test_new_range_exclusive_with_two_elements() { - eval("def test(a, b) = (a...b)"); - assert_contains_opcode("test", YARVINSN_newrange); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v17:RangeExact = NewRange v11 NewRangeExclusive v12 - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_array_dup() { - eval("def test = [1, 2, 3]"); - assert_contains_opcode("test", YARVINSN_duparray); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:ArrayExact = ArrayDup v10 - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_hash_dup() { - eval("def test = {a: 1, b: 2}"); - assert_contains_opcode("test", YARVINSN_duphash); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:HashExact = HashDup v10 - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_new_hash_empty() { - eval("def test = {}"); - assert_contains_opcode("test", YARVINSN_newhash); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:HashExact = NewHash - CheckInterrupts - Return v11 - "); - } - - #[test] - fn test_new_hash_with_elements() { - eval("def test(aval, bval) = {a: aval, b: bval}"); - assert_contains_opcode("test", YARVINSN_newhash); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v16:StaticSymbol[:a] = Const Value(VALUE(0x1000)) - v17:StaticSymbol[:b] = Const Value(VALUE(0x1008)) - v19:HashExact = NewHash v16: v11, v17: v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_string_copy() { - eval("def test = \"hello\""); - assert_contains_opcode("test", YARVINSN_putchilledstring); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:StringExact = StringCopy v10 - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_bignum() { - eval("def test = 999999999999999999999999999999999999"); - assert_contains_opcode("test", YARVINSN_putobject); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Bignum[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - CheckInterrupts - Return v10 - "); - } - - #[test] - fn test_flonum() { - eval("def test = 1.5"); - assert_contains_opcode("test", YARVINSN_putobject); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Flonum[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - CheckInterrupts - Return v10 - "); - } - - #[test] - fn test_heap_float() { - eval("def test = 1.7976931348623157e+308"); - assert_contains_opcode("test", YARVINSN_putobject); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:HeapFloat[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - CheckInterrupts - Return v10 - "); - } - - #[test] - fn test_static_sym() { - eval("def test = :foo"); - assert_contains_opcode("test", YARVINSN_putobject); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - CheckInterrupts - Return v10 - "); - } - - #[test] - fn test_opt_plus() { - eval("def test = 1+2"); - assert_contains_opcode("test", YARVINSN_opt_plus); - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v11:Fixnum[2] = Const Value(2) - v15:BasicObject = SendWithoutBlock v10, :+, v11 - CheckInterrupts - Return v15 - "); - } - - #[test] - fn test_opt_hash_freeze() { - eval(" - def test = {}.freeze - "); - assert_contains_opcode("test", YARVINSN_opt_hash_freeze); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_opt_hash_freeze_rewritten() { - eval(" - class Hash - def freeze; 5; end - end - def test = {}.freeze - "); - assert_contains_opcode("test", YARVINSN_opt_hash_freeze); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - SideExit PatchPoint(BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE)) - "); - } - - #[test] - fn test_opt_ary_freeze() { - eval(" - def test = [].freeze - "); - assert_contains_opcode("test", YARVINSN_opt_ary_freeze); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_opt_ary_freeze_rewritten() { - eval(" - class Array - def freeze; 5; end - end - def test = [].freeze - "); - assert_contains_opcode("test", YARVINSN_opt_ary_freeze); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - SideExit PatchPoint(BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE)) - "); - } - - #[test] - fn test_opt_str_freeze() { - eval(" - def test = ''.freeze - "); - assert_contains_opcode("test", YARVINSN_opt_str_freeze); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_opt_str_freeze_rewritten() { - eval(" - class String - def freeze; 5; end - end - def test = ''.freeze - "); - assert_contains_opcode("test", YARVINSN_opt_str_freeze); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - SideExit PatchPoint(BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE)) - "); - } - - #[test] - fn test_opt_str_uminus() { - eval(" - def test = -'' - "); - assert_contains_opcode("test", YARVINSN_opt_str_uminus); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_UMINUS) - v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_opt_str_uminus_rewritten() { - eval(" - class String - def -@; 5; end - end - def test = -'' - "); - assert_contains_opcode("test", YARVINSN_opt_str_uminus); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - SideExit PatchPoint(BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_UMINUS)) - "); - } - - #[test] - fn test_setlocal_getlocal() { - eval(" - def test - a = 1 - a - end - "); - assert_contains_opcodes("test", &[YARVINSN_getlocal_WC_0, YARVINSN_setlocal_WC_0]); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:Fixnum[1] = Const Value(1) - CheckInterrupts - Return v13 - "); - } - - #[test] - fn test_nested_setlocal_getlocal() { - eval(" - l3 = 3 - _unused = _unused1 = nil - 1.times do |l2| - _ = nil - l2 = 2 - 1.times do |l1| - l1 = 1 - define_method(:test) do - l1 = l2 - l2 = l1 + l2 - l3 = l2 + l3 - end - end - end - "); - assert_contains_opcodes( - "test", - &[YARVINSN_getlocal_WC_1, YARVINSN_setlocal_WC_1, - YARVINSN_getlocal, YARVINSN_setlocal]); - assert_snapshot!(hir_string("test"), @r" - fn block (3 levels) in @:10: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:BasicObject = GetLocal l2, EP@4 - SetLocal l1, EP@3, v10 - v14:BasicObject = GetLocal l1, EP@3 - v15:BasicObject = GetLocal l2, EP@4 - v19:BasicObject = SendWithoutBlock v14, :+, v15 - SetLocal l2, EP@4, v19 - v23:BasicObject = GetLocal l2, EP@4 - v24:BasicObject = GetLocal l3, EP@5 - v28:BasicObject = SendWithoutBlock v23, :+, v24 - SetLocal l3, EP@5, v28 - CheckInterrupts - Return v28 - " - ); - } - - #[test] - fn test_setlocal_in_default_args() { - eval(" - def test(a = (b = 1)) = [a, b] - "); - assert_contains_opcode("test", YARVINSN_setlocal_WC_0); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:NilClass = Const Value(nil) - v4:CPtr = LoadPC - v5:CPtr[CPtr(0x1000)] = Const CPtr(0x1008) - v6:CBool = IsBitEqual v4, v5 - IfTrue v6, bb2(v1, v2, v3) - Jump bb4(v1, v2, v3) - bb1(v10:BasicObject, v11:BasicObject): - EntryPoint JIT(0) - v12:NilClass = Const Value(nil) - Jump bb2(v10, v11, v12) - bb2(v14:BasicObject, v15:BasicObject, v16:NilClass): - v20:Fixnum[1] = Const Value(1) - Jump bb4(v14, v20, v20) - bb3(v23:BasicObject, v24:BasicObject): - EntryPoint JIT(1) - v25:NilClass = Const Value(nil) - Jump bb4(v23, v24, v25) - bb4(v27:BasicObject, v28:BasicObject, v29:NilClass|Fixnum): - v34:ArrayExact = NewArray v28, v29 - CheckInterrupts - Return v34 - "); - } - - #[test] - fn test_setlocal_in_default_args_with_tracepoint() { - eval(" - def test(a = (b = 1)) = [a, b] - TracePoint.new(:line) {}.enable - test - "); - assert_compile_fails("test", ParseError::FailedOptionalArguments); - } - - #[test] - fn test_setlocal_in_default_args_with_side_exit() { - eval(" - def test(a = (def foo = nil)) = a - "); - assert_compile_fails("test", ParseError::FailedOptionalArguments); - } - - #[test] - fn test_setlocal_cyclic_default_args() { - eval(" - def test = proc { |a=a| a } - "); - assert_snapshot!(hir_string_proc("test"), @r" - fn block in test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - CheckInterrupts - Return v9 - "); - } - - #[test] - fn defined_ivar() { - eval(" - def test = defined?(@foo) - "); - assert_contains_opcode("test", YARVINSN_definedivar); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:StringExact|NilClass = DefinedIvar v6, :@foo - CheckInterrupts - Return v11 - "); - } - - #[test] - fn if_defined_ivar() { - eval(" - def test - if defined?(@foo) - 3 - else - 4 - end - end - "); - assert_contains_opcode("test", YARVINSN_definedivar); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:TrueClass|NilClass = DefinedIvar v6, :@foo - CheckInterrupts - v14:CBool = Test v11 - IfFalse v14, bb3(v6) - v18:Fixnum[3] = Const Value(3) - CheckInterrupts - Return v18 - bb3(v24:BasicObject): - v28:Fixnum[4] = Const Value(4) - CheckInterrupts - Return v28 - "); - } - - #[test] - fn defined() { - eval(" - def test = return defined?(SeaChange), defined?(favourite), defined?($ruby) - "); - assert_contains_opcode("test", YARVINSN_defined); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:NilClass = Const Value(nil) - v12:StringExact|NilClass = Defined constant, v10 - v14:StringExact|NilClass = Defined func, v6 - v15:NilClass = Const Value(nil) - v17:StringExact|NilClass = Defined global-variable, v15 - v19:ArrayExact = NewArray v12, v14, v17 - CheckInterrupts - Return v19 - "); + "#); } #[test] - fn test_return_const() { - eval(" - def test(cond) - if cond + fn test_multiple_blocks() { + eval(r#" + def test(c) + if c 3 else 4 end end - "); - assert_contains_opcode("test", YARVINSN_leave); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - CheckInterrupts - v15:CBool = Test v9 - IfFalse v15, bb3(v8, v9) - v19:Fixnum[3] = Const Value(3) - CheckInterrupts - Return v19 - bb3(v25:BasicObject, v26:BasicObject): - v30:Fixnum[4] = Const Value(4) - CheckInterrupts - Return v30 - "); - } - - #[test] - fn test_merge_const() { - eval(" - def test(cond) - if cond - result = 3 - else - result = 4 - end - result - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject): - EntryPoint JIT(0) - v8:NilClass = Const Value(nil) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:NilClass): - CheckInterrupts - v18:CBool = Test v11 - IfFalse v18, bb3(v10, v11, v12) - v22:Fixnum[3] = Const Value(3) - PatchPoint NoEPEscape(test) - CheckInterrupts - Jump bb4(v10, v11, v22) - bb3(v28:BasicObject, v29:BasicObject, v30:NilClass): - v34:Fixnum[4] = Const Value(4) - PatchPoint NoEPEscape(test) - Jump bb4(v28, v29, v34) - bb4(v38:BasicObject, v39:BasicObject, v40:Fixnum): - PatchPoint NoEPEscape(test) - CheckInterrupts - Return v40 - "); - } - - #[test] - fn test_opt_plus_fixnum() { - eval(" - def test(a, b) = a + b - test(1, 2); test(1, 2) - "); - assert_contains_opcode("test", YARVINSN_opt_plus); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :+, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_opt_minus_fixnum() { - eval(" - def test(a, b) = a - b - test(1, 2); test(1, 2) - "); - assert_contains_opcode("test", YARVINSN_opt_minus); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :-, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_opt_mult_fixnum() { - eval(" - def test(a, b) = a * b - test(1, 2); test(1, 2) - "); - assert_contains_opcode("test", YARVINSN_opt_mult); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :*, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_opt_div_fixnum() { - eval(" - def test(a, b) = a / b - test(1, 2); test(1, 2) - "); - assert_contains_opcode("test", YARVINSN_opt_div); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :/, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_opt_mod_fixnum() { - eval(" - def test(a, b) = a % b - test(1, 2); test(1, 2) - "); - assert_contains_opcode("test", YARVINSN_opt_mod); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :%, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_opt_eq_fixnum() { - eval(" - def test(a, b) = a == b - test(1, 2); test(1, 2) - "); - assert_contains_opcode("test", YARVINSN_opt_eq); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :==, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_opt_neq_fixnum() { - eval(" - def test(a, b) = a != b - test(1, 2); test(1, 2) - "); - assert_contains_opcode("test", YARVINSN_opt_neq); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :!=, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_opt_lt_fixnum() { - eval(" - def test(a, b) = a < b - test(1, 2); test(1, 2) - "); - assert_contains_opcode("test", YARVINSN_opt_lt); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :<, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_opt_le_fixnum() { - eval(" - def test(a, b) = a <= b - test(1, 2); test(1, 2) - "); - assert_contains_opcode("test", YARVINSN_opt_le); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :<=, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_opt_gt_fixnum() { - eval(" - def test(a, b) = a > b - test(1, 2); test(1, 2) - "); - assert_contains_opcode("test", YARVINSN_opt_gt); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :>, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_loop() { - eval(" - def test - result = 0 - times = 10 - while times > 0 - result = result + 1 - times = times - 1 - end - result - end - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - v3:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject): - EntryPoint JIT(0) - v7:NilClass = Const Value(nil) - v8:NilClass = Const Value(nil) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:NilClass, v12:NilClass): - v16:Fixnum[0] = Const Value(0) - v19:Fixnum[10] = Const Value(10) - CheckInterrupts - Jump bb4(v10, v16, v19) - bb4(v25:BasicObject, v26:BasicObject, v27:BasicObject): - PatchPoint NoEPEscape(test) - v31:Fixnum[0] = Const Value(0) - v35:BasicObject = SendWithoutBlock v27, :>, v31 - CheckInterrupts - v38:CBool = Test v35 - IfTrue v38, bb3(v25, v26, v27) - v40:NilClass = Const Value(nil) - PatchPoint NoEPEscape(test) - CheckInterrupts - Return v26 - bb3(v50:BasicObject, v51:BasicObject, v52:BasicObject): - PatchPoint NoEPEscape(test) - v58:Fixnum[1] = Const Value(1) - v62:BasicObject = SendWithoutBlock v51, :+, v58 - v65:Fixnum[1] = Const Value(1) - v69:BasicObject = SendWithoutBlock v52, :-, v65 - Jump bb4(v50, v62, v69) - "); - } - #[test] - fn test_opt_ge_fixnum() { - eval(" - def test(a, b) = a >= b - test(1, 2); test(1, 2) - "); - assert_contains_opcode("test", YARVINSN_opt_ge); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :>=, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_display_types() { - eval(" - def test - cond = true - if cond - 3 - else - 4 - end - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:TrueClass = Const Value(true) - CheckInterrupts - v18:CBool[true] = Test v13 - IfFalse v18, bb3(v8, v13) - v22:Fixnum[3] = Const Value(3) - CheckInterrupts - Return v22 - bb3(v28, v29): - v33 = Const Value(4) - CheckInterrupts - Return v33 - "); - } - - #[test] - fn test_send_without_block() { - eval(" - def bar(a, b) - a+b - end - def test - bar(2, 3) - end - "); - assert_contains_opcode("test", YARVINSN_opt_send_without_block); - assert_snapshot!(hir_string("test"), @r" - fn test@:6: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[2] = Const Value(2) - v11:Fixnum[3] = Const Value(3) - v13:BasicObject = SendWithoutBlock v6, :bar, v10, v11 - CheckInterrupts - Return v13 - "); - } - - #[test] - fn test_send_with_block() { - eval(" - def test(a) - a.each {|item| - item - } - end - test([1,2,3]) - "); - assert_contains_opcode("test", YARVINSN_send); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:BasicObject = GetLocal l0, EP@3 - v15:BasicObject = Send v13, 0x1000, :each - v16:BasicObject = GetLocal l0, EP@3 - CheckInterrupts - Return v15 - "); - } - - #[test] - fn test_intern_interpolated_symbol() { - eval(r#" - def test - :"foo#{123}" - end - "#); - assert_contains_opcode("test", YARVINSN_intern); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v11:Fixnum[123] = Const Value(123) - v13:BasicObject = ObjToString v11 - v15:String = AnyToString v11, str: v13 - v17:StringExact = StringConcat v10, v15 - v19:Symbol = StringIntern v17 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn different_objects_get_addresses() { - eval("def test = unknown_method([0], [1], '2', '2')"); - - // The 2 string literals have the same address because they're deduped. - assert_snapshot!(hir_string("test"), @r" - fn test@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:ArrayExact = ArrayDup v10 - v13:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v15:ArrayExact = ArrayDup v13 - v16:StringExact[VALUE(0x1010)] = Const Value(VALUE(0x1010)) - v18:StringExact = StringCopy v16 - v19:StringExact[VALUE(0x1010)] = Const Value(VALUE(0x1010)) - v21:StringExact = StringCopy v19 - v23:BasicObject = SendWithoutBlock v6, :unknown_method, v12, v15, v18, v21 - CheckInterrupts - Return v23 - "); - } - - #[test] - fn test_cant_compile_splat() { - eval(" - def test(a) = foo(*a) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:ArrayExact = ToArray v9 - SideExit UnhandledCallType(Splat) - "); - } - - #[test] - fn test_compile_block_arg() { - eval(" - def test(a) = foo(&a) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:BasicObject = Send v8, 0x1000, :foo, v9 - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_cant_compile_kwarg() { - eval(" - def test(a) = foo(a: 1) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[1] = Const Value(1) - SideExit UnhandledCallType(Kwarg) - "); - } - - #[test] - fn test_cant_compile_kw_splat() { - eval(" - def test(a) = foo(**a) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:BasicObject = SendWithoutBlock v8, :foo, v9 - CheckInterrupts - Return v14 - "); - } - - // TODO(max): Figure out how to generate a call with TAILCALL flag - - #[test] - fn test_compile_super() { - eval(" - def test = super() - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = InvokeSuper v6, 0x1000 - CheckInterrupts - Return v11 - "); - } - - #[test] - fn test_compile_zsuper() { - eval(" - def test = super - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = InvokeSuper v6, 0x1000 - CheckInterrupts - Return v11 - "); - } - - #[test] - fn test_cant_compile_super_nil_blockarg() { - eval(" - def test = super(&nil) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:NilClass = Const Value(nil) - v12:BasicObject = InvokeSuper v6, 0x1000, v10 - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_cant_compile_super_forward() { - eval(" - def test(...) = super(...) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - SideExit UnhandledYARVInsn(invokesuperforward) - "); - } - - #[test] - fn test_compile_forwardable() { - eval("def forwardable(...) = nil"); - assert_snapshot!(hir_string("forwardable"), @r" - fn forwardable@:1: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:NilClass = Const Value(nil) - CheckInterrupts - Return v13 - "); - } - - // TODO(max): Figure out how to generate a call with OPT_SEND flag - - #[test] - fn test_cant_compile_kw_splat_mut() { - eval(" - def test(a) = foo **a, b: 1 - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Class[VMFrozenCore] = Const Value(VALUE(0x1000)) - v15:HashExact = NewHash - PatchPoint NoEPEscape(test) - v19:BasicObject = SendWithoutBlock v13, :core#hash_merge_kwd, v15, v9 - v20:Class[VMFrozenCore] = Const Value(VALUE(0x1000)) - v21:StaticSymbol[:b] = Const Value(VALUE(0x1008)) - v22:Fixnum[1] = Const Value(1) - v24:BasicObject = SendWithoutBlock v20, :core#hash_merge_ptr, v19, v21, v22 - v26:BasicObject = SendWithoutBlock v8, :foo, v24 - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_cant_compile_splat_mut() { - eval(" - def test(*) = foo *, 1 - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:ArrayExact = GetLocal l0, SP@4, * - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:ArrayExact): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:ArrayExact): - v14:ArrayExact = ToNewArray v9 - v15:Fixnum[1] = Const Value(1) - ArrayPush v14, v15 - SideExit UnhandledCallType(Splat) - "); - } - - #[test] - fn test_compile_forwarding() { - eval(" - def test(...) = foo(...) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:BasicObject = SendForward 0x1000, :foo, v9 - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_compile_triple_dots_with_positional_args() { - eval(" - def test(a, ...) = foo(a, ...) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@8 - v3:ArrayExact = GetLocal l0, SP@7, * - v4:BasicObject = GetLocal l0, SP@6 - v5:BasicObject = GetLocal l0, SP@5 - v6:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3, v4, v5, v6) - bb1(v9:BasicObject, v10:BasicObject, v11:ArrayExact, v12:BasicObject, v13:BasicObject): - EntryPoint JIT(0) - v14:NilClass = Const Value(nil) - Jump bb2(v9, v10, v11, v12, v13, v14) - bb2(v16:BasicObject, v17:BasicObject, v18:ArrayExact, v19:BasicObject, v20:BasicObject, v21:NilClass): - v26:ArrayExact = ToArray v18 - PatchPoint NoEPEscape(test) - GuardBlockParamProxy l0 - v31:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1000)) - SideExit UnhandledYARVInsn(splatkw) - "); - } - - #[test] - fn test_opt_new() { - eval(" - class C; end - def test = C.new - "); - assert_contains_opcode("test", YARVINSN_opt_new); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = GetConstantPath 0x1000 - v12:NilClass = Const Value(nil) - v14:CBool = IsMethodCFunc v11, :new - IfFalse v14, bb3(v6, v12, v11) - v16:HeapBasicObject = ObjectAlloc v11 - v18:BasicObject = SendWithoutBlock v16, :initialize - CheckInterrupts - Jump bb4(v6, v16, v18) - bb3(v22:BasicObject, v23:NilClass, v24:BasicObject): - v27:BasicObject = SendWithoutBlock v24, :new - Jump bb4(v22, v27, v23) - bb4(v29:BasicObject, v30:BasicObject, v31:BasicObject): - CheckInterrupts - Return v30 - "); - } - - #[test] - fn test_opt_newarray_send_max_no_elements() { - eval(" - def test = [].max - "); - // TODO(max): Rewrite to nil - assert_contains_opcode("test", YARVINSN_opt_newarray_send); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_MAX) - v12:BasicObject = ArrayMax - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_opt_newarray_send_max() { - eval(" - def test(a,b) = [a,b].max - "); - assert_contains_opcode("test", YARVINSN_opt_newarray_send); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_MAX) - v18:BasicObject = ArrayMax v11, v12 - CheckInterrupts - Return v18 - "); - } - - #[test] - fn test_opt_newarray_send_min() { - eval(" - def test(a,b) - sum = a+b - result = [a,b].min - puts [1,2,3] - result - end - "); - assert_contains_opcode("test", YARVINSN_opt_newarray_send); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@7 - v3:BasicObject = GetLocal l0, SP@6 - v4:NilClass = Const Value(nil) - v5:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3, v4, v5) - bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject): - EntryPoint JIT(0) - v11:NilClass = Const Value(nil) - v12:NilClass = Const Value(nil) - Jump bb2(v8, v9, v10, v11, v12) - bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): - v25:BasicObject = SendWithoutBlock v15, :+, v16 - SideExit UnknownNewarraySend(MIN) - "); - } - - #[test] - fn test_opt_newarray_send_hash() { - eval(" - def test(a,b) - sum = a+b - result = [a,b].hash - puts [1,2,3] - result - end - "); - assert_contains_opcode("test", YARVINSN_opt_newarray_send); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@7 - v3:BasicObject = GetLocal l0, SP@6 - v4:NilClass = Const Value(nil) - v5:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3, v4, v5) - bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject): - EntryPoint JIT(0) - v11:NilClass = Const Value(nil) - v12:NilClass = Const Value(nil) - Jump bb2(v8, v9, v10, v11, v12) - bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): - v25:BasicObject = SendWithoutBlock v15, :+, v16 - SideExit UnknownNewarraySend(HASH) - "); - } - - #[test] - fn test_opt_newarray_send_pack() { - eval(" - def test(a,b) - sum = a+b - result = [a,b].pack 'C' - puts [1,2,3] - result - end - "); - assert_contains_opcode("test", YARVINSN_opt_newarray_send); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@7 - v3:BasicObject = GetLocal l0, SP@6 - v4:NilClass = Const Value(nil) - v5:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3, v4, v5) - bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject): - EntryPoint JIT(0) - v11:NilClass = Const Value(nil) - v12:NilClass = Const Value(nil) - Jump bb2(v8, v9, v10, v11, v12) - bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): - v25:BasicObject = SendWithoutBlock v15, :+, v16 - v28:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v30:StringExact = StringCopy v28 - SideExit UnknownNewarraySend(PACK) - "); - } - - // TODO(max): Add a test for VM_OPT_NEWARRAY_SEND_PACK_BUFFER - - #[test] - fn test_opt_newarray_send_include_p() { - eval(" - def test(a,b) - sum = a+b - result = [a,b].include? b - puts [1,2,3] - result - end - "); - assert_contains_opcode("test", YARVINSN_opt_newarray_send); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@7 - v3:BasicObject = GetLocal l0, SP@6 - v4:NilClass = Const Value(nil) - v5:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3, v4, v5) - bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject): - EntryPoint JIT(0) - v11:NilClass = Const Value(nil) - v12:NilClass = Const Value(nil) - Jump bb2(v8, v9, v10, v11, v12) - bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): - v25:BasicObject = SendWithoutBlock v15, :+, v16 - SideExit UnknownNewarraySend(INCLUDE_P) - "); - } - - #[test] - fn test_opt_length() { - eval(" - def test(a,b) = [a,b].length - "); - assert_contains_opcode("test", YARVINSN_opt_length); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v17:ArrayExact = NewArray v11, v12 - v21:BasicObject = SendWithoutBlock v17, :length - CheckInterrupts - Return v21 - "); - } - - #[test] - fn test_opt_size() { - eval(" - def test(a,b) = [a,b].size - "); - assert_contains_opcode("test", YARVINSN_opt_size); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v17:ArrayExact = NewArray v11, v12 - v21:BasicObject = SendWithoutBlock v17, :size - CheckInterrupts - Return v21 - "); - } - - #[test] - fn test_getinstancevariable() { - eval(" - def test = @foo - test - "); - assert_contains_opcode("test", YARVINSN_getinstancevariable); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - v12:BasicObject = GetIvar v6, :@foo - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_trace_getinstancevariable() { - eval(" - def test = @foo - test - trace = TracePoint.trace(:call) { |tp| } - trace.enable { test } - "); - assert_contains_opcode("test", YARVINSN_trace_getinstancevariable); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - SideExit UnhandledYARVInsn(trace_getinstancevariable) - "); - } - - #[test] - fn test_setinstancevariable() { - eval(" - def test = @foo = 1 - test - "); - assert_contains_opcode("test", YARVINSN_setinstancevariable); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - PatchPoint SingleRactorMode - SetIvar v6, :@foo, v10 - CheckInterrupts - Return v10 - "); - } - - #[test] - fn test_set_ivar_rescue_frozen() { - let result = eval(" - class Foo - attr_accessor :bar - def initialize - @bar = 1 - freeze - end - end - - def test(foo) - begin - foo.bar = 2 - rescue FrozenError - end - end - - foo = Foo.new - test(foo) - test(foo) - - foo.bar - "); - assert_eq!(VALUE::fixnum_from_usize(1), result); - } - - #[test] - fn test_getclassvariable() { - eval(" - class Foo - def self.test = @@foo - end - "); - let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("Foo", "test")); - assert!(iseq_contains_opcode(iseq, YARVINSN_getclassvariable), "iseq Foo.test does not contain getclassvariable"); - let function = iseq_to_hir(iseq).unwrap(); - assert_snapshot!(hir_string_function(&function), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = GetClassVar :@@foo - CheckInterrupts - Return v11 - "); - } - - #[test] - fn test_setclassvariable() { - eval(" - class Foo - def self.test = @@foo = 42 - end - "); - let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("Foo", "test")); - assert!(iseq_contains_opcode(iseq, YARVINSN_setclassvariable), "iseq Foo.test does not contain setclassvariable"); - let function = iseq_to_hir(iseq).unwrap(); - assert_snapshot!(hir_string_function(&function), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[42] = Const Value(42) - SetClassVar :@@foo, v10 - CheckInterrupts - Return v10 - "); - } - - #[test] - fn test_setglobal() { - eval(" - def test = $foo = 1 - test - "); - assert_contains_opcode("test", YARVINSN_setglobal); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - SetGlobal :$foo, v10 - CheckInterrupts - Return v10 - "); - } - - #[test] - fn test_getglobal() { - eval(" - def test = $foo - test - "); - assert_contains_opcode("test", YARVINSN_getglobal); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = GetGlobal :$foo - CheckInterrupts - Return v11 - "); - } - - #[test] - fn test_splatarray_mut() { - eval(" - def test(a) = [*a] - "); - assert_contains_opcode("test", YARVINSN_splatarray); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:ArrayExact = ToNewArray v9 - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_concattoarray() { - eval(" - def test(a) = [1, *a] - "); - assert_contains_opcode("test", YARVINSN_concattoarray); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[1] = Const Value(1) - v15:ArrayExact = NewArray v13 - v17:ArrayExact = ToArray v9 - ArrayExtend v15, v17 - CheckInterrupts - Return v15 - "); - } - - #[test] - fn test_pushtoarray_one_element() { - eval(" - def test(a) = [*a, 1] - "); - assert_contains_opcode("test", YARVINSN_pushtoarray); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:ArrayExact = ToNewArray v9 - v15:Fixnum[1] = Const Value(1) - ArrayPush v14, v15 - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_pushtoarray_multiple_elements() { - eval(" - def test(a) = [*a, 1, 2, 3] - "); - assert_contains_opcode("test", YARVINSN_pushtoarray); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:ArrayExact = ToNewArray v9 - v15:Fixnum[1] = Const Value(1) - v16:Fixnum[2] = Const Value(2) - v17:Fixnum[3] = Const Value(3) - ArrayPush v14, v15 - ArrayPush v14, v16 - ArrayPush v14, v17 - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_aset() { - eval(" - def test(a, b) = a[b] = 1 - "); - assert_contains_opcode("test", YARVINSN_opt_aset); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v16:NilClass = Const Value(nil) - v17:Fixnum[1] = Const Value(1) - v21:BasicObject = SendWithoutBlock v11, :[]=, v12, v17 - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_aref() { - eval(" - def test(a, b) = a[b] - "); - assert_contains_opcode("test", YARVINSN_opt_aref); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :[], v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn opt_empty_p() { - eval(" - def test(x) = x.empty? - "); - assert_contains_opcode("test", YARVINSN_opt_empty_p); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v16:BasicObject = SendWithoutBlock v9, :empty? - CheckInterrupts - Return v16 - "); - } - - #[test] - fn opt_succ() { - eval(" - def test(x) = x.succ - "); - assert_contains_opcode("test", YARVINSN_opt_succ); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v16:BasicObject = SendWithoutBlock v9, :succ - CheckInterrupts - Return v16 - "); - } - - #[test] - fn opt_and() { - eval(" - def test(x, y) = x & y - "); - assert_contains_opcode("test", YARVINSN_opt_and); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :&, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn opt_or() { - eval(" - def test(x, y) = x | y - "); - assert_contains_opcode("test", YARVINSN_opt_or); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :|, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn opt_not() { - eval(" - def test(x) = !x - "); - assert_contains_opcode("test", YARVINSN_opt_not); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v16:BasicObject = SendWithoutBlock v9, :! - CheckInterrupts - Return v16 - "); - } - - #[test] - fn opt_regexpmatch2() { - eval(" - def test(regexp, matchee) = regexp =~ matchee - "); - assert_contains_opcode("test", YARVINSN_opt_regexpmatch2); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :=~, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - // Tests for ConstBase requires either constant or class definition, both - // of which can't be performed inside a method. - fn test_putspecialobject_vm_core_and_cbase() { - eval(" - def test - alias aliased __callee__ - end - "); - assert_contains_opcode("test", YARVINSN_putspecialobject); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Class[VMFrozenCore] = Const Value(VALUE(0x1000)) - v11:BasicObject = PutSpecialObject CBase - v12:StaticSymbol[:aliased] = Const Value(VALUE(0x1008)) - v13:StaticSymbol[:__callee__] = Const Value(VALUE(0x1010)) - v15:BasicObject = SendWithoutBlock v10, :core#set_method_alias, v11, v12, v13 - CheckInterrupts - Return v15 - "); - } - - #[test] - fn opt_reverse() { - eval(" - def reverse_odd - a, b, c = @a, @b, @c - [a, b, c] - end - - def reverse_even - a, b, c, d = @a, @b, @c, @d - [a, b, c, d] - end - "); - assert_contains_opcode("reverse_odd", YARVINSN_opt_reverse); - assert_contains_opcode("reverse_even", YARVINSN_opt_reverse); - assert_snapshot!(hir_strings!("reverse_odd", "reverse_even"), @r" - fn reverse_odd@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - v3:NilClass = Const Value(nil) - v4:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3, v4) - bb1(v7:BasicObject): - EntryPoint JIT(0) - v8:NilClass = Const Value(nil) - v9:NilClass = Const Value(nil) - v10:NilClass = Const Value(nil) - Jump bb2(v7, v8, v9, v10) - bb2(v12:BasicObject, v13:NilClass, v14:NilClass, v15:NilClass): - PatchPoint SingleRactorMode - v21:BasicObject = GetIvar v12, :@a - PatchPoint SingleRactorMode - v24:BasicObject = GetIvar v12, :@b - PatchPoint SingleRactorMode - v27:BasicObject = GetIvar v12, :@c - PatchPoint NoEPEscape(reverse_odd) - v33:ArrayExact = NewArray v21, v24, v27 - CheckInterrupts - Return v33 - - fn reverse_even@:8: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - v3:NilClass = Const Value(nil) - v4:NilClass = Const Value(nil) - v5:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3, v4, v5) - bb1(v8:BasicObject): - EntryPoint JIT(0) - v9:NilClass = Const Value(nil) - v10:NilClass = Const Value(nil) - v11:NilClass = Const Value(nil) - v12:NilClass = Const Value(nil) - Jump bb2(v8, v9, v10, v11, v12) - bb2(v14:BasicObject, v15:NilClass, v16:NilClass, v17:NilClass, v18:NilClass): - PatchPoint SingleRactorMode - v24:BasicObject = GetIvar v14, :@a - PatchPoint SingleRactorMode - v27:BasicObject = GetIvar v14, :@b - PatchPoint SingleRactorMode - v30:BasicObject = GetIvar v14, :@c - PatchPoint SingleRactorMode - v33:BasicObject = GetIvar v14, :@d - PatchPoint NoEPEscape(reverse_even) - v39:ArrayExact = NewArray v24, v27, v30, v33 - CheckInterrupts - Return v39 - "); - } - - #[test] - fn test_branchnil() { - eval(" - def test(x) = x&.itself - "); - assert_contains_opcode("test", YARVINSN_branchnil); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - CheckInterrupts - v15:CBool = IsNil v9 - IfTrue v15, bb3(v8, v9, v9) - v18:BasicObject = SendWithoutBlock v9, :itself - Jump bb3(v8, v9, v18) - bb3(v20:BasicObject, v21:BasicObject, v22:BasicObject): - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_invokebuiltin_delegate_annotated() { - assert_contains_opcode("Float", YARVINSN_opt_invokebuiltin_delegate_leave); - assert_snapshot!(hir_string("Float"), @r" - fn Float@: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@6 - v3:BasicObject = GetLocal l0, SP@5 - v4:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3, v4) - bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject, v10:BasicObject): - EntryPoint JIT(0) - Jump bb2(v7, v8, v9, v10) - bb2(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:BasicObject): - v20:Float = InvokeBuiltin rb_f_float, v12, v13, v14 - Jump bb3(v12, v13, v14, v15, v20) - bb3(v22:BasicObject, v23:BasicObject, v24:BasicObject, v25:BasicObject, v26:Float): - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_invokebuiltin_cexpr_annotated() { - assert_contains_opcode("class", YARVINSN_opt_invokebuiltin_delegate_leave); - assert_snapshot!(hir_string("class"), @r" - fn class@: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:Class = InvokeBuiltin leaf _bi20, v6 - Jump bb3(v6, v11) - bb3(v13:BasicObject, v14:Class): - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_invokebuiltin_delegate_with_args() { - // Using an unannotated builtin to test InvokeBuiltin generation - let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("Dir", "open")); - assert!(iseq_contains_opcode(iseq, YARVINSN_opt_invokebuiltin_delegate), "iseq Dir.open does not contain invokebuiltin"); - let function = iseq_to_hir(iseq).unwrap(); - assert_snapshot!(hir_string_function(&function), @r" - fn open@: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@8 - v3:BasicObject = GetLocal l0, SP@7 - v4:BasicObject = GetLocal l0, SP@6 - v5:BasicObject = GetLocal l0, SP@5 - v6:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3, v4, v5, v6) - bb1(v9:BasicObject, v10:BasicObject, v11:BasicObject, v12:BasicObject, v13:BasicObject): - EntryPoint JIT(0) - v14:NilClass = Const Value(nil) - Jump bb2(v9, v10, v11, v12, v13, v14) - bb2(v16:BasicObject, v17:BasicObject, v18:BasicObject, v19:BasicObject, v20:BasicObject, v21:NilClass): - v26:BasicObject = InvokeBuiltin dir_s_open, v16, v17, v18 - PatchPoint NoEPEscape(open) - GuardBlockParamProxy l0 - v33:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1000)) - CheckInterrupts - v36:CBool[true] = Test v33 - IfFalse v36, bb3(v16, v17, v18, v19, v20, v26) - PatchPoint NoEPEscape(open) - v43:BasicObject = InvokeBlock, v26 - v47:BasicObject = InvokeBuiltin dir_s_close, v16, v26 - CheckInterrupts - Return v43 - bb3(v53, v54, v55, v56, v57, v58): - PatchPoint NoEPEscape(open) - CheckInterrupts - Return v58 - "); - } - - #[test] - fn test_invokebuiltin_delegate_without_args() { - let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("GC", "enable")); - assert!(iseq_contains_opcode(iseq, YARVINSN_opt_invokebuiltin_delegate_leave), "iseq GC.enable does not contain invokebuiltin"); - let function = iseq_to_hir(iseq).unwrap(); - assert_snapshot!(hir_string_function(&function), @r" - fn enable@: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = InvokeBuiltin gc_enable, v6 - Jump bb3(v6, v11) - bb3(v13:BasicObject, v14:BasicObject): - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_invokebuiltin_with_args() { - let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("GC", "start")); - assert!(iseq_contains_opcode(iseq, YARVINSN_invokebuiltin), "iseq GC.start does not contain invokebuiltin"); - let function = iseq_to_hir(iseq).unwrap(); - assert_snapshot!(hir_string_function(&function), @r" - fn start@: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@7 - v3:BasicObject = GetLocal l0, SP@6 - v4:BasicObject = GetLocal l0, SP@5 - v5:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3, v4, v5) - bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject, v11:BasicObject, v12:BasicObject): - EntryPoint JIT(0) - Jump bb2(v8, v9, v10, v11, v12) - bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:BasicObject, v18:BasicObject): - v22:FalseClass = Const Value(false) - v24:BasicObject = InvokeBuiltin gc_start_internal, v14, v15, v16, v17, v22 - CheckInterrupts - Return v24 - "); - } - - #[test] - fn test_invoke_leaf_builtin_symbol_name() { - let iseq = crate::cruby::with_rubyvm(|| get_instance_method_iseq("Symbol", "name")); - let function = iseq_to_hir(iseq).unwrap(); - assert_snapshot!(hir_string_function(&function), @r" - fn name@: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:StringExact = InvokeBuiltin leaf _bi28, v6 - Jump bb3(v6, v11) - bb3(v13:BasicObject, v14:StringExact): - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_invoke_leaf_builtin_symbol_to_s() { - let iseq = crate::cruby::with_rubyvm(|| get_instance_method_iseq("Symbol", "to_s")); - let function = iseq_to_hir(iseq).unwrap(); - assert_snapshot!(hir_string_function(&function), @r" - fn to_s@: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:StringExact = InvokeBuiltin leaf _bi12, v6 - Jump bb3(v6, v11) - bb3(v13:BasicObject, v14:StringExact): - CheckInterrupts - Return v14 - "); - } - - #[test] - fn dupn() { - eval(" - def test(x) = (x[0, 1] ||= 2) - "); - assert_contains_opcode("test", YARVINSN_dupn); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:NilClass = Const Value(nil) - v14:Fixnum[0] = Const Value(0) - v15:Fixnum[1] = Const Value(1) - v17:BasicObject = SendWithoutBlock v9, :[], v14, v15 - CheckInterrupts - v20:CBool = Test v17 - IfTrue v20, bb3(v8, v9, v13, v9, v14, v15, v17) - v22:Fixnum[2] = Const Value(2) - v24:BasicObject = SendWithoutBlock v9, :[]=, v14, v15, v22 - CheckInterrupts - Return v22 - bb3(v30:BasicObject, v31:BasicObject, v32:NilClass, v33:BasicObject, v34:Fixnum[0], v35:Fixnum[1], v36:BasicObject): - CheckInterrupts - Return v36 - "); - } - - #[test] - fn test_objtostring_anytostring() { - eval(" - def test = \"#{1}\" - "); - assert_contains_opcode("test", YARVINSN_objtostring); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v11:Fixnum[1] = Const Value(1) - v13:BasicObject = ObjToString v11 - v15:String = AnyToString v11, str: v13 - v17:StringExact = StringConcat v10, v15 - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_string_concat() { - eval(r##" - def test = "#{1}#{2}#{3}" - "##); - assert_contains_opcode("test", YARVINSN_concatstrings); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v12:BasicObject = ObjToString v10 - v14:String = AnyToString v10, str: v12 - v15:Fixnum[2] = Const Value(2) - v17:BasicObject = ObjToString v15 - v19:String = AnyToString v15, str: v17 - v20:Fixnum[3] = Const Value(3) - v22:BasicObject = ObjToString v20 - v24:String = AnyToString v20, str: v22 - v26:StringExact = StringConcat v14, v19, v24 - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_string_concat_empty() { - eval(r##" - def test = "#{}" - "##); - assert_contains_opcode("test", YARVINSN_concatstrings); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v11:NilClass = Const Value(nil) - v13:BasicObject = ObjToString v11 - v15:String = AnyToString v11, str: v13 - v17:StringExact = StringConcat v10, v15 - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_toregexp() { - eval(r##" - def test = /#{1}#{2}#{3}/ - "##); - assert_contains_opcode("test", YARVINSN_toregexp); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v12:BasicObject = ObjToString v10 - v14:String = AnyToString v10, str: v12 - v15:Fixnum[2] = Const Value(2) - v17:BasicObject = ObjToString v15 - v19:String = AnyToString v15, str: v17 - v20:Fixnum[3] = Const Value(3) - v22:BasicObject = ObjToString v20 - v24:String = AnyToString v20, str: v22 - v26:RegexpExact = ToRegexp v14, v19, v24 - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_toregexp_with_options() { - eval(r##" - def test = /#{1}#{2}/mixn - "##); - assert_contains_opcode("test", YARVINSN_toregexp); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v12:BasicObject = ObjToString v10 - v14:String = AnyToString v10, str: v12 - v15:Fixnum[2] = Const Value(2) - v17:BasicObject = ObjToString v15 - v19:String = AnyToString v15, str: v17 - v21:RegexpExact = ToRegexp v14, v19, MULTILINE|IGNORECASE|EXTENDED|NOENCODING - CheckInterrupts - Return v21 - "); - } - - #[test] - fn throw() { - eval(" - define_method(:throw_return) { return 1 } - define_method(:throw_break) { break 2 } - "); - assert_contains_opcode("throw_return", YARVINSN_throw); - assert_contains_opcode("throw_break", YARVINSN_throw); - assert_snapshot!(hir_strings!("throw_return", "throw_break"), @r" - fn block in @:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v12:Fixnum[1] = Const Value(1) - Throw TAG_RETURN, v12 - - fn block in @:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v12:Fixnum[2] = Const Value(2) - Throw TAG_BREAK, v12 - "); - } - - #[test] - fn test_invokeblock() { - eval(r#" - def test - yield - end - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = InvokeBlock - CheckInterrupts - Return v11 - "); - } - - #[test] - fn test_invokeblock_with_args() { - eval(r#" - def test(x, y) - yield x, y - end - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v17:BasicObject = InvokeBlock, v11, v12 - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_expandarray_no_splat() { - eval(r#" - def test(o) - a, b = o - end - "#); - assert_contains_opcode("test", YARVINSN_expandarray); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@6 - v3:NilClass = Const Value(nil) - v4:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3, v4) - bb1(v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - v9:NilClass = Const Value(nil) - v10:NilClass = Const Value(nil) - Jump bb2(v7, v8, v9, v10) - bb2(v12:BasicObject, v13:BasicObject, v14:NilClass, v15:NilClass): - v20:ArrayExact = GuardType v13, ArrayExact - v21:CInt64 = ArrayLength v20 - v22:CInt64[2] = GuardBitEquals v21, CInt64(2) - v23:Fixnum[1] = Const Value(1) - v24:BasicObject = ArrayArefFixnum v20, v23 - v25:Fixnum[0] = Const Value(0) - v26:BasicObject = ArrayArefFixnum v20, v25 - PatchPoint NoEPEscape(test) - CheckInterrupts - Return v13 - "); - } - - #[test] - fn test_expandarray_splat() { - eval(r#" - def test(o) - a, *b = o - end - "#); - assert_contains_opcode("test", YARVINSN_expandarray); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@6 - v3:NilClass = Const Value(nil) - v4:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3, v4) - bb1(v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - v9:NilClass = Const Value(nil) - v10:NilClass = Const Value(nil) - Jump bb2(v7, v8, v9, v10) - bb2(v12:BasicObject, v13:BasicObject, v14:NilClass, v15:NilClass): - SideExit UnhandledYARVInsn(expandarray) - "); - } - - #[test] - fn test_expandarray_splat_post() { - eval(r#" - def test(o) - a, *b, c = o - end - "#); - assert_contains_opcode("test", YARVINSN_expandarray); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@7 - v3:NilClass = Const Value(nil) - v4:NilClass = Const Value(nil) - v5:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3, v4, v5) - bb1(v8:BasicObject, v9:BasicObject): - EntryPoint JIT(0) - v10:NilClass = Const Value(nil) - v11:NilClass = Const Value(nil) - v12:NilClass = Const Value(nil) - Jump bb2(v8, v9, v10, v11, v12) - bb2(v14:BasicObject, v15:BasicObject, v16:NilClass, v17:NilClass, v18:NilClass): - SideExit UnhandledYARVInsn(expandarray) - "); - } -} - -#[cfg(test)] -mod graphviz_tests { - use super::*; - use insta::assert_snapshot; - - #[track_caller] - fn hir_string(method: &str) -> String { - let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method)); - unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; - let mut function = iseq_to_hir(iseq).unwrap(); - function.optimize(); - function.validate().unwrap(); - format!("{}", FunctionGraphvizPrinter::new(&function)) - } - - #[test] - fn test_guard_fixnum_or_fixnum() { - eval(r#" - def test(x, y) = x | y - - test(1, 2) - "#); - assert_snapshot!(hir_string("test"), @r#" - digraph G { # test@<compiled>:2 - node [shape=plaintext]; - mode=hier; overlap=false; splines=true; - bb0 [label=< - - - - - - -
bb0() 
EntryPoint interpreter 
v1:BasicObject = LoadSelf 
v2:BasicObject = GetLocal l0, SP@5 
v3:BasicObject = GetLocal l0, SP@4 
Jump bb2(v1, v2, v3) 
>]; - bb0:v4 -> bb2:params:n; - bb1 [label=< - - - -
bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject) 
EntryPoint JIT(0) 
Jump bb2(v6, v7, v8) 
>]; - bb1:v9 -> bb2:params:n; - bb2 [label=< - - - - - - - - - - -
bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject) 
PatchPoint NoTracePoint 
PatchPoint NoTracePoint 
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, 29) 
v26:Fixnum = GuardType v11, Fixnum 
v27:Fixnum = GuardType v12, Fixnum 
v28:Fixnum = FixnumOr v26, v27 
PatchPoint NoTracePoint 
CheckInterrupts 
Return v28 
>]; - } - "#); - } - - #[test] - fn test_multiple_blocks() { - eval(r#" - def test(c) - if c - 3 - else - 4 - end - end - - test(1) - test("x") - "#); - assert_snapshot!(hir_string("test"), @r#" - digraph G { # test@<compiled>:3 - node [shape=plaintext]; - mode=hier; overlap=false; splines=true; - bb0 [label=< - - - - - -
bb0() 
EntryPoint interpreter 
v1:BasicObject = LoadSelf 
v2:BasicObject = GetLocal l0, SP@4 
Jump bb2(v1, v2) 
>]; - bb0:v3 -> bb2:params:n; - bb1 [label=< - - - -
bb1(v5:BasicObject, v6:BasicObject) 
EntryPoint JIT(0) 
Jump bb2(v5, v6) 
>]; - bb1:v7 -> bb2:params:n; - bb2 [label=< - - - - - - - - - - -
bb2(v8:BasicObject, v9:BasicObject) 
PatchPoint NoTracePoint 
CheckInterrupts 
v15:CBool = Test v9 
IfFalse v15, bb3(v8, v9) 
PatchPoint NoTracePoint 
v19:Fixnum[3] = Const Value(3) 
PatchPoint NoTracePoint 
CheckInterrupts 
Return v19 
>]; - bb2:v16 -> bb3:params:n; - bb3 [label=< - - - - - - -
bb3(v25:BasicObject, v26:BasicObject) 
PatchPoint NoTracePoint 
v30:Fixnum[4] = Const Value(4) 
PatchPoint NoTracePoint 
CheckInterrupts 
Return v30 
>]; - } - "#); - } -} - -#[cfg(test)] -mod opt_tests { - use super::*; - use crate::{hir_strings, options::*}; - use insta::assert_snapshot; - use super::tests::assert_contains_opcode; - - #[track_caller] - fn hir_string_function(function: &Function) -> String { - format!("{}", FunctionPrinter::without_snapshot(function)) - } - - #[track_caller] - fn hir_string_proc(proc: &str) -> String { - let iseq = crate::cruby::with_rubyvm(|| get_proc_iseq(proc)); - unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; - let mut function = iseq_to_hir(iseq).unwrap(); - function.optimize(); - function.validate().unwrap(); - hir_string_function(&function) - } - - #[track_caller] - fn hir_string(method: &str) -> String { - hir_string_proc(&format!("{}.method(:{})", "self", method)) - } - - #[test] - fn test_fold_iftrue_away() { - eval(" - def test - cond = true - if cond - 3 - else - 4 - end - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:TrueClass = Const Value(true) - CheckInterrupts - v22:Fixnum[3] = Const Value(3) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_fold_iftrue_into_jump() { - eval(" - def test - cond = false - if cond - 3 - else - 4 - end - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:FalseClass = Const Value(false) - CheckInterrupts - v33:Fixnum[4] = Const Value(4) - CheckInterrupts - Return v33 - "); - } - - #[test] - fn test_fold_fixnum_add() { - eval(" - def test - 1 + 2 + 3 - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v11:Fixnum[2] = Const Value(2) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) - v30:Fixnum[3] = Const Value(3) - v16:Fixnum[3] = Const Value(3) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) - v31:Fixnum[6] = Const Value(6) - CheckInterrupts - Return v31 - "); - } - - #[test] - fn test_fold_fixnum_sub() { - eval(" - def test - 5 - 3 - 1 - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[5] = Const Value(5) - v11:Fixnum[3] = Const Value(3) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MINUS) - v30:Fixnum[2] = Const Value(2) - v16:Fixnum[1] = Const Value(1) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MINUS) - v31:Fixnum[1] = Const Value(1) - CheckInterrupts - Return v31 - "); - } - - #[test] - fn test_fold_fixnum_sub_large_negative_result() { - eval(" - def test - 0 - 1073741825 - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[0] = Const Value(0) - v11:Fixnum[1073741825] = Const Value(1073741825) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MINUS) - v23:Fixnum[-1073741825] = Const Value(-1073741825) - CheckInterrupts - Return v23 - "); - } - - #[test] - fn test_fold_fixnum_mult() { - eval(" - def test - 6 * 7 - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[6] = Const Value(6) - v11:Fixnum[7] = Const Value(7) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MULT) - v23:Fixnum[42] = Const Value(42) - CheckInterrupts - Return v23 - "); - } - - #[test] - fn test_fold_fixnum_mult_zero() { - eval(" - def test(n) - 0 * n + n * 0 - end - test 1; test 2 - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[0] = Const Value(0) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MULT) - v33:Fixnum = GuardType v9, Fixnum - v40:Fixnum[0] = Const Value(0) - v18:Fixnum[0] = Const Value(0) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MULT) - v36:Fixnum = GuardType v9, Fixnum - v41:Fixnum[0] = Const Value(0) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) - v42:Fixnum[0] = Const Value(0) - CheckInterrupts - Return v42 - "); - } - - #[test] - fn test_fold_fixnum_less() { - eval(" - def test - if 1 < 2 - 3 - else - 4 - end - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v11:Fixnum[2] = Const Value(2) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT) - v40:TrueClass = Const Value(true) - CheckInterrupts - v22:Fixnum[3] = Const Value(3) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_fold_fixnum_less_equal() { - eval(" - def test - if 1 <= 2 && 2 <= 2 - 3 - else - 4 - end - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v11:Fixnum[2] = Const Value(2) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LE) - v52:TrueClass = Const Value(true) - CheckInterrupts - v20:Fixnum[2] = Const Value(2) - v21:Fixnum[2] = Const Value(2) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LE) - v54:TrueClass = Const Value(true) - CheckInterrupts - v32:Fixnum[3] = Const Value(3) - CheckInterrupts - Return v32 - "); - } - - #[test] - fn test_fold_fixnum_greater() { - eval(" - def test - if 2 > 1 - 3 - else - 4 - end - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[2] = Const Value(2) - v11:Fixnum[1] = Const Value(1) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GT) - v40:TrueClass = Const Value(true) - CheckInterrupts - v22:Fixnum[3] = Const Value(3) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_fold_fixnum_greater_equal() { - eval(" - def test - if 2 >= 1 && 2 >= 2 - 3 - else - 4 - end - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[2] = Const Value(2) - v11:Fixnum[1] = Const Value(1) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GE) - v52:TrueClass = Const Value(true) - CheckInterrupts - v20:Fixnum[2] = Const Value(2) - v21:Fixnum[2] = Const Value(2) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GE) - v54:TrueClass = Const Value(true) - CheckInterrupts - v32:Fixnum[3] = Const Value(3) - CheckInterrupts - Return v32 - "); - } - - #[test] - fn test_fold_fixnum_eq_false() { - eval(" - def test - if 1 == 2 - 3 - else - 4 - end - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v11:Fixnum[2] = Const Value(2) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) - v40:FalseClass = Const Value(false) - CheckInterrupts - v32:Fixnum[4] = Const Value(4) - CheckInterrupts - Return v32 - "); - } - - #[test] - fn test_fold_fixnum_eq_true() { - eval(" - def test - if 2 == 2 - 3 - else - 4 - end - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[2] = Const Value(2) - v11:Fixnum[2] = Const Value(2) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) - v40:TrueClass = Const Value(true) - CheckInterrupts - v22:Fixnum[3] = Const Value(3) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_fold_fixnum_neq_true() { - eval(" - def test - if 1 != 2 - 3 - else - 4 - end - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v11:Fixnum[2] = Const Value(2) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_NEQ) - v41:TrueClass = Const Value(true) - CheckInterrupts - v22:Fixnum[3] = Const Value(3) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_fold_fixnum_neq_false() { - eval(" - def test - if 2 != 2 - 3 - else - 4 - end - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[2] = Const Value(2) - v11:Fixnum[2] = Const Value(2) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_NEQ) - v41:FalseClass = Const Value(false) - CheckInterrupts - v32:Fixnum[4] = Const Value(4) - CheckInterrupts - Return v32 - "); - } - - #[test] - fn test_replace_guard_if_known_fixnum() { - eval(" - def test(a) - a + 1 - end - test(2); test(3) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[1] = Const Value(1) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) - v24:Fixnum = GuardType v9, Fixnum - v25:Fixnum = FixnumAdd v24, v13 - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_param_forms_get_bb_param() { - eval(" - def rest(*array) = array - def kw(k:) = k - def kw_rest(**k) = k - def post(*rest, post) = post - def block(&b) = nil - "); - assert_snapshot!(hir_strings!("rest", "kw", "kw_rest", "block", "post"), @r" - fn rest@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:ArrayExact = GetLocal l0, SP@4, * - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:ArrayExact): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:ArrayExact): - CheckInterrupts - Return v9 - - fn kw@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - CheckInterrupts - Return v11 - - fn kw_rest@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - CheckInterrupts - Return v9 - - fn block@:6: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:NilClass = Const Value(nil) - CheckInterrupts - Return v13 - - fn post@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:ArrayExact = GetLocal l0, SP@5, * - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:ArrayExact, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:ArrayExact, v12:BasicObject): - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_optimize_top_level_call_into_send_direct() { - eval(" - def foo = [] - def test - foo - end - test; test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v20:BasicObject = SendWithoutBlockDirect v19, :foo (0x1038) - CheckInterrupts - Return v20 - "); - } - - #[test] - fn test_optimize_nonexistent_top_level_call() { - eval(" - def foo - end - def test - foo - end - test; test - undef :foo - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = SendWithoutBlock v6, :foo - CheckInterrupts - Return v11 - "); - } - - #[test] - fn test_optimize_private_top_level_call() { - eval(" - def foo = [] - private :foo - def test - foo - end - test; test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v20:BasicObject = SendWithoutBlockDirect v19, :foo (0x1038) - CheckInterrupts - Return v20 - "); - } - - #[test] - fn test_optimize_top_level_call_with_overloaded_cme() { - eval(" - def test - Integer(3) - end - test; test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[3] = Const Value(3) - PatchPoint MethodRedefined(Object@0x1000, Integer@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v21:BasicObject = SendWithoutBlockDirect v20, :Integer (0x1038), v10 - CheckInterrupts - Return v21 - "); - } - - #[test] - fn test_optimize_top_level_call_with_args_into_send_direct() { - eval(" - def foo(a, b) = [] - def test - foo 1, 2 - end - test; test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v11:Fixnum[2] = Const Value(2) - PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v21:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v22:BasicObject = SendWithoutBlockDirect v21, :foo (0x1038), v10, v11 - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_optimize_top_level_sends_into_send_direct() { - eval(" - def foo = [] - def bar = [] - def test - foo - bar - end - test; test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v23:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v24:BasicObject = SendWithoutBlockDirect v23, :foo (0x1038) - PatchPoint MethodRedefined(Object@0x1000, bar@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(Object@0x1000) - v27:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v28:BasicObject = SendWithoutBlockDirect v27, :bar (0x1038) - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_optimize_variadic_ccall() { - eval(" - def test - puts 'Hello' - end - test; test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:StringExact = StringCopy v10 - PatchPoint MethodRedefined(Object@0x1008, puts@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Object@0x1008) - v23:HeapObject[class_exact*:Object@VALUE(0x1008)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1008)] - v24:BasicObject = CCallVariadic puts@0x1040, v23, v12 - CheckInterrupts - Return v24 - "); - } - - #[test] - fn test_dont_optimize_fixnum_add_if_redefined() { - eval(" - class Integer - def +(other) - 100 - end - end - def test(a, b) = a + b - test(1,2); test(3,4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:7: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v19:BasicObject = SendWithoutBlock v11, :+, v12 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_optimize_send_into_fixnum_add_both_profiled() { - eval(" - def test(a, b) = a + b - test(1,2); test(3,4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) - v26:Fixnum = GuardType v11, Fixnum - v27:Fixnum = GuardType v12, Fixnum - v28:Fixnum = FixnumAdd v26, v27 - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_optimize_send_into_fixnum_add_left_profiled() { - eval(" - def test(a) = a + 1 - test(1); test(3) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[1] = Const Value(1) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) - v24:Fixnum = GuardType v9, Fixnum - v25:Fixnum = FixnumAdd v24, v13 - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_optimize_send_into_fixnum_add_right_profiled() { - eval(" - def test(a) = 1 + a - test(1); test(3) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[1] = Const Value(1) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) - v24:Fixnum = GuardType v9, Fixnum - v25:Fixnum = FixnumAdd v13, v24 - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_optimize_send_into_fixnum_lt_both_profiled() { - eval(" - def test(a, b) = a < b - test(1,2); test(3,4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT) - v26:Fixnum = GuardType v11, Fixnum - v27:Fixnum = GuardType v12, Fixnum - v28:BoolExact = FixnumLt v26, v27 - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_optimize_send_into_fixnum_lt_left_profiled() { - eval(" - def test(a) = a < 1 - test(1); test(3) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[1] = Const Value(1) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT) - v24:Fixnum = GuardType v9, Fixnum - v25:BoolExact = FixnumLt v24, v13 - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_optimize_send_into_fixnum_lt_right_profiled() { - eval(" - def test(a) = 1 < a - test(1); test(3) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[1] = Const Value(1) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT) - v24:Fixnum = GuardType v9, Fixnum - v25:BoolExact = FixnumLt v13, v24 - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_optimize_new_range_fixnum_inclusive_literals() { - eval(" - def test() - a = 2 - (1..a) - end - test; test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:Fixnum[2] = Const Value(2) - v16:Fixnum[1] = Const Value(1) - v24:RangeExact = NewRangeFixnum v16 NewRangeInclusive v13 - CheckInterrupts - Return v24 - "); - } - - - #[test] - fn test_optimize_new_range_fixnum_exclusive_literals() { - eval(" - def test() - a = 2 - (1...a) - end - test; test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:Fixnum[2] = Const Value(2) - v16:Fixnum[1] = Const Value(1) - v24:RangeExact = NewRangeFixnum v16 NewRangeExclusive v13 - CheckInterrupts - Return v24 - "); - } - - #[test] - fn test_optimize_new_range_fixnum_inclusive_high_guarded() { - eval(" - def test(a) - (1..a) - end - test(2); test(3) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[1] = Const Value(1) - v21:Fixnum = GuardType v9, Fixnum - v22:RangeExact = NewRangeFixnum v13 NewRangeInclusive v21 - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_optimize_new_range_fixnum_exclusive_high_guarded() { - eval(" - def test(a) - (1...a) - end - test(2); test(3) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[1] = Const Value(1) - v21:Fixnum = GuardType v9, Fixnum - v22:RangeExact = NewRangeFixnum v13 NewRangeExclusive v21 - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_optimize_new_range_fixnum_inclusive_low_guarded() { - eval(" - def test(a) - (a..10) - end - test(2); test(3) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[10] = Const Value(10) - v21:Fixnum = GuardType v9, Fixnum - v22:RangeExact = NewRangeFixnum v21 NewRangeInclusive v13 - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_optimize_new_range_fixnum_exclusive_low_guarded() { - eval(" - def test(a) - (a...10) - end - test(2); test(3) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[10] = Const Value(10) - v21:Fixnum = GuardType v9, Fixnum - v22:RangeExact = NewRangeFixnum v21 NewRangeExclusive v13 - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_eliminate_new_array() { - eval(" - def test() - c = [] - 5 - end - test; test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v14:ArrayExact = NewArray - v17:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_opt_aref_array() { - eval(" - arr = [1,2,3] - def test(arr) = arr[0] - test(arr) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[0] = Const Value(0) - PatchPoint MethodRedefined(Array@0x1000, []@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v26:ArrayExact = GuardType v9, ArrayExact - v27:BasicObject = ArrayArefFixnum v26, v13 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v27 - "); - assert_snapshot!(inspect("test [1,2,3]"), @"1"); - } - - #[test] - fn test_opt_aref_hash() { - eval(" - arr = {0 => 4} - def test(arr) = arr[0] - test(arr) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[0] = Const Value(0) - PatchPoint MethodRedefined(Hash@0x1000, []@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Hash@0x1000) - v26:HashExact = GuardType v9, HashExact - v27:BasicObject = HashAref v26, v13 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v27 - "); - assert_snapshot!(inspect("test({0 => 4})"), @"4"); - } - - #[test] - fn test_eliminate_new_range() { - eval(" - def test() - c = (1..2) - 5 - end - test; test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:RangeExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v16:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v16 - "); - } - - #[test] - fn test_do_not_eliminate_new_range_non_fixnum() { - eval(" - def test() - _ = (-'a'..'b') - 0 - end - test; test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_UMINUS) - v15:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v16:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v18:StringExact = StringCopy v16 - v20:RangeExact = NewRange v15 NewRangeInclusive v18 - PatchPoint NoEPEscape(test) - v25:Fixnum[0] = Const Value(0) - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_eliminate_new_array_with_elements() { - eval(" - def test(a) - c = [a] - 5 - end - test(1); test(2) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject): - EntryPoint JIT(0) - v8:NilClass = Const Value(nil) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:NilClass): - v17:ArrayExact = NewArray v11 - v20:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v20 - "); - } - - #[test] - fn test_eliminate_new_hash() { - eval(" - def test() - c = {} - 5 - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v14:HashExact = NewHash - PatchPoint NoEPEscape(test) - v19:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_no_eliminate_new_hash_with_elements() { - eval(" - def test(aval, bval) - c = {a: aval, b: bval} - 5 - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@6 - v3:BasicObject = GetLocal l0, SP@5 - v4:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3, v4) - bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject): - EntryPoint JIT(0) - v10:NilClass = Const Value(nil) - Jump bb2(v7, v8, v9, v10) - bb2(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:NilClass): - v19:StaticSymbol[:a] = Const Value(VALUE(0x1000)) - v20:StaticSymbol[:b] = Const Value(VALUE(0x1008)) - v22:HashExact = NewHash v19: v13, v20: v14 - PatchPoint NoEPEscape(test) - v27:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_eliminate_array_dup() { - eval(" - def test - c = [1, 2] - 5 - end - test; test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v15:ArrayExact = ArrayDup v13 - v18:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v18 - "); - } - - #[test] - fn test_eliminate_hash_dup() { - eval(" - def test - c = {a: 1, b: 2} - 5 - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v15:HashExact = HashDup v13 - v18:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v18 - "); - } - - #[test] - fn test_eliminate_putself() { - eval(" - def test() - c = self - 5 - end - test; test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v15:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v15 - "); - } - - #[test] - fn test_eliminate_string_copy() { - eval(r#" - def test() - c = "abc" - 5 - end - test; test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v15:StringExact = StringCopy v13 - v18:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v18 - "); - } - - #[test] - fn test_eliminate_fixnum_add() { - eval(" - def test(a, b) - a + b - 5 - end - test(1, 2); test(3, 4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) - v29:Fixnum = GuardType v11, Fixnum - v30:Fixnum = GuardType v12, Fixnum - v22:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_eliminate_fixnum_sub() { - eval(" - def test(a, b) - a - b - 5 - end - test(1, 2); test(3, 4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MINUS) - v29:Fixnum = GuardType v11, Fixnum - v30:Fixnum = GuardType v12, Fixnum - v22:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_eliminate_fixnum_mul() { - eval(" - def test(a, b) - a * b - 5 - end - test(1, 2); test(3, 4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MULT) - v29:Fixnum = GuardType v11, Fixnum - v30:Fixnum = GuardType v12, Fixnum - v22:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_do_not_eliminate_fixnum_div() { - eval(" - def test(a, b) - a / b - 5 - end - test(1, 2); test(3, 4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_DIV) - v29:Fixnum = GuardType v11, Fixnum - v30:Fixnum = GuardType v12, Fixnum - v31:Fixnum = FixnumDiv v29, v30 - v22:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_do_not_eliminate_fixnum_mod() { - eval(" - def test(a, b) - a % b - 5 - end - test(1, 2); test(3, 4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MOD) - v29:Fixnum = GuardType v11, Fixnum - v30:Fixnum = GuardType v12, Fixnum - v31:Fixnum = FixnumMod v29, v30 - v22:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_eliminate_fixnum_lt() { - eval(" - def test(a, b) - a < b - 5 - end - test(1, 2); test(3, 4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT) - v29:Fixnum = GuardType v11, Fixnum - v30:Fixnum = GuardType v12, Fixnum - v22:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_eliminate_fixnum_le() { - eval(" - def test(a, b) - a <= b - 5 - end - test(1, 2); test(3, 4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LE) - v29:Fixnum = GuardType v11, Fixnum - v30:Fixnum = GuardType v12, Fixnum - v22:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_eliminate_fixnum_gt() { - eval(" - def test(a, b) - a > b - 5 - end - test(1, 2); test(3, 4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GT) - v29:Fixnum = GuardType v11, Fixnum - v30:Fixnum = GuardType v12, Fixnum - v22:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_eliminate_fixnum_ge() { - eval(" - def test(a, b) - a >= b - 5 - end - test(1, 2); test(3, 4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GE) - v29:Fixnum = GuardType v11, Fixnum - v30:Fixnum = GuardType v12, Fixnum - v22:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_eliminate_fixnum_eq() { - eval(" - def test(a, b) - a == b - 5 - end - test(1, 2); test(3, 4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) - v29:Fixnum = GuardType v11, Fixnum - v30:Fixnum = GuardType v12, Fixnum - v22:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_eliminate_fixnum_neq() { - eval(" - def test(a, b) - a != b - 5 - end - test(1, 2); test(3, 4) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_NEQ) - v30:Fixnum = GuardType v11, Fixnum - v31:Fixnum = GuardType v12, Fixnum - v22:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_specialize_basic_object_neq() { - eval(" - class C; end - def test(a, b) = a != b - test(C.new, 5) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(C@0x1000, !=@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(C@0x1000) - v28:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] - PatchPoint MethodRedefined(C@0x1000, ==@0x1038, cme:0x1040) - PatchPoint NoSingletonClass(C@0x1000) - v32:CBool = IsBitNotEqual v28, v12 - v33:BoolExact = BoxBool v32 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v33 - "); - } - - #[test] - fn test_do_not_eliminate_get_constant_path() { - eval(" - def test() - C - 5 - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = GetConstantPath 0x1000 - v14:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v14 - "); - } - - #[test] - fn kernel_itself_const() { - eval(" - def test(x) = x.itself - test(0) # profile - test(1) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Integer@0x1000, itself@0x1008, cme:0x1010) - v22:Fixnum = GuardType v9, Fixnum - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v22 - "); - } - - #[test] - fn kernel_itself_known_type() { - eval(" - def test = [].itself - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:ArrayExact = NewArray - PatchPoint MethodRedefined(Array@0x1000, itself@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v11 - "); - } - - #[test] - fn eliminate_kernel_itself() { - eval(" - def test - x = [].itself - 1 - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v14:ArrayExact = NewArray - PatchPoint MethodRedefined(Array@0x1000, itself@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - IncrCounter inline_cfunc_optimized_send_count - PatchPoint NoEPEscape(test) - v21:Fixnum[1] = Const Value(1) - CheckInterrupts - Return v21 - "); - } - - #[test] - fn eliminate_module_name() { - eval(" - module M; end - def test - x = M.name - 1 - end - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, M) - v29:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint MethodRedefined(Module@0x1010, name@0x1018, cme:0x1020) - PatchPoint NoSingletonClass(Module@0x1010) - IncrCounter inline_cfunc_optimized_send_count - v34:StringExact|NilClass = CCall name@0x1048, v29 - PatchPoint NoEPEscape(test) - v21:Fixnum[1] = Const Value(1) - CheckInterrupts - Return v21 - "); - } - - #[test] - fn eliminate_array_length() { - eval(" - def test - x = [].length - 5 - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v14:ArrayExact = NewArray - PatchPoint MethodRedefined(Array@0x1000, length@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - IncrCounter inline_cfunc_optimized_send_count - v31:Fixnum = CCall length@0x1038, v14 - v21:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v21 - "); - } - - #[test] - fn normal_class_type_inference() { - eval(" - class C; end - def test = C - test # Warm the constant cache - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, C) - v19:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - CheckInterrupts - Return v19 - "); - } - - #[test] - fn core_classes_type_inference() { - eval(" - def test = [String, Class, Module, BasicObject] - test # Warm the constant cache - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, String) - v27:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1010, Class) - v30:Class[VALUE(0x1018)] = Const Value(VALUE(0x1018)) - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1020, Module) - v33:Class[VALUE(0x1028)] = Const Value(VALUE(0x1028)) - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1030, BasicObject) - v36:Class[VALUE(0x1038)] = Const Value(VALUE(0x1038)) - v19:ArrayExact = NewArray v27, v30, v33, v36 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn module_instances_are_module_exact() { - eval(" - def test = [Enumerable, Kernel] - test # Warm the constant cache - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, Enumerable) - v23:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1010, Kernel) - v26:ModuleExact[VALUE(0x1018)] = Const Value(VALUE(0x1018)) - v15:ArrayExact = NewArray v23, v26 - CheckInterrupts - Return v15 - "); - } - - #[test] - fn module_subclasses_are_not_module_exact() { - eval(" - class ModuleSubclass < Module; end - MY_MODULE = ModuleSubclass.new - def test = MY_MODULE - test # Warm the constant cache - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, MY_MODULE) - v19:ModuleSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - CheckInterrupts - Return v19 - "); - } - - #[test] - fn eliminate_array_size() { - eval(" - def test - x = [].size - 5 - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v14:ArrayExact = NewArray - PatchPoint MethodRedefined(Array@0x1000, size@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - IncrCounter inline_cfunc_optimized_send_count - v31:Fixnum = CCall size@0x1038, v14 - v21:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v21 - "); - } - - #[test] - fn kernel_itself_argc_mismatch() { - eval(" - def test = 1.itself(0) - test rescue 0 - test rescue 0 - "); - // Not specialized - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v11:Fixnum[0] = Const Value(0) - v13:BasicObject = SendWithoutBlock v10, :itself, v11 - CheckInterrupts - Return v13 - "); - } - - #[test] - fn test_inline_kernel_block_given_p() { - eval(" - def test = block_given? - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, block_given?@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v21:BoolExact = IsBlockGiven - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v21 - "); - } - - #[test] - fn test_inline_kernel_block_given_p_in_block() { - eval(" - TEST = proc { block_given? } - TEST.call - "); - assert_snapshot!(hir_string_proc("TEST"), @r" - fn block in @:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, block_given?@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - v21:BoolExact = IsBlockGiven - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v21 - "); - } - - #[test] - fn test_elide_kernel_block_given_p() { - eval(" - def test - block_given? - 5 - end - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, block_given?@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v23:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - IncrCounter inline_cfunc_optimized_send_count - v14:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v14 - "); - } - - #[test] - fn const_send_direct_integer() { - eval(" - def test(x) = 1.zero? - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[1] = Const Value(1) - PatchPoint MethodRedefined(Integer@0x1000, zero?@0x1008, cme:0x1010) - IncrCounter inline_iseq_optimized_send_count - v24:BasicObject = InvokeBuiltin leaf _bi285, v13 - CheckInterrupts - Return v24 - "); - } - - #[test] - fn class_known_send_direct_array() { - eval(" - def test(x) - a = [1,2,3] - a.first - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:NilClass = Const Value(nil) - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject): - EntryPoint JIT(0) - v8:NilClass = Const Value(nil) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:NilClass): - v16:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v18:ArrayExact = ArrayDup v16 - PatchPoint MethodRedefined(Array@0x1008, first@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Array@0x1008) - IncrCounter inline_iseq_optimized_send_count - v32:BasicObject = InvokeBuiltin leaf _bi132, v18 - CheckInterrupts - Return v32 - "); - } - - #[test] - fn send_direct_to_module() { - eval(" - module M; end - def test = M.class - test - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, M) - v21:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint MethodRedefined(Module@0x1010, class@0x1018, cme:0x1020) - PatchPoint NoSingletonClass(Module@0x1010) - IncrCounter inline_iseq_optimized_send_count - v26:Class = InvokeBuiltin leaf _bi20, v21 - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_send_direct_to_instance_method() { - eval(" - class C - def foo = [] - end - - def test(c) = c.foo - c = C.new - test c - test c - "); - - assert_snapshot!(hir_string("test"), @r" - fn test@:6: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(C@0x1000) - v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - v23:BasicObject = SendWithoutBlockDirect v22, :foo (0x1038) - CheckInterrupts - Return v23 - "); - } - - #[test] - fn dont_specialize_call_to_iseq_with_opt() { - eval(" - def foo(arg=1) = 1 - def test = foo 1 - test - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v12:BasicObject = SendWithoutBlock v6, :foo, v10 - CheckInterrupts - Return v12 - "); - } - - #[test] - fn dont_specialize_call_to_iseq_with_block() { - eval(" - def foo(&block) = 1 - def test = foo {|| } - test - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = Send v6, 0x1000, :foo - CheckInterrupts - Return v11 - "); - } - - #[test] - fn reload_local_across_send() { - eval(" - def foo(&block) = 1 - def test - a = 1 - foo {|| } - a - end - test - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:Fixnum[1] = Const Value(1) - SetLocal l0, EP@3, v13 - v18:BasicObject = Send v8, 0x1000, :foo - v19:BasicObject = GetLocal l0, EP@3 - v22:BasicObject = GetLocal l0, EP@3 - CheckInterrupts - Return v22 - "); - } - - #[test] - fn dont_specialize_call_to_iseq_with_rest() { - eval(" - def foo(*args) = 1 - def test = foo 1 - test - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v12:BasicObject = SendWithoutBlock v6, :foo, v10 - CheckInterrupts - Return v12 - "); - } - - #[test] - fn dont_specialize_call_to_iseq_with_kw() { - eval(" - def foo(a:) = 1 - def test = foo(a: 1) - test - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - SideExit UnhandledCallType(Kwarg) - "); - } - - #[test] - fn dont_specialize_call_to_iseq_with_kwrest() { - eval(" - def foo(**args) = 1 - def test = foo(a: 1) - test - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - SideExit UnhandledCallType(Kwarg) - "); - } - - #[test] - fn string_bytesize_simple() { - eval(" - def test = 'abc'.bytesize - test - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:StringExact = StringCopy v10 - PatchPoint MethodRedefined(String@0x1008, bytesize@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(String@0x1008) - IncrCounter inline_cfunc_optimized_send_count - v24:Fixnum = CCall bytesize@0x1040, v12 - CheckInterrupts - Return v24 - "); - } - - #[test] - fn dont_replace_get_constant_path_with_empty_ic() { - eval(" - def test = Kernel - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = GetConstantPath 0x1000 - CheckInterrupts - Return v11 - "); - } - - #[test] - fn dont_replace_get_constant_path_with_invalidated_ic() { - eval(" - def test = Kernel - test - Kernel = 5 - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = GetConstantPath 0x1000 - CheckInterrupts - Return v11 - "); - } - - #[test] - fn replace_get_constant_path_with_const() { - eval(" - def test = Kernel - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, Kernel) - v19:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - CheckInterrupts - Return v19 - "); - } - - #[test] - fn replace_nested_get_constant_path_with_const() { - eval(" - module Foo - module Bar - class C - end - end - end - def test = Foo::Bar::C - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:8: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, Foo::Bar::C) - v19:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_opt_new_no_initialize() { - eval(" - class C; end - def test = C.new - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, C) - v40:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v12:NilClass = Const Value(nil) - PatchPoint MethodRedefined(C@0x1008, new@0x1010, cme:0x1018) - v43:HeapObject[class_exact:C] = ObjectAllocClass C:VALUE(0x1008) - PatchPoint MethodRedefined(C@0x1008, initialize@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(C@0x1008) - v47:NilClass = Const Value(nil) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - CheckInterrupts - Return v43 - "); - } - - #[test] - fn test_opt_new_initialize() { - eval(" - class C - def initialize x - @x = x - end - end - def test = C.new 1 - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:7: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, C) - v42:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v12:NilClass = Const Value(nil) - v13:Fixnum[1] = Const Value(1) - PatchPoint MethodRedefined(C@0x1008, new@0x1010, cme:0x1018) - v45:HeapObject[class_exact:C] = ObjectAllocClass C:VALUE(0x1008) - PatchPoint MethodRedefined(C@0x1008, initialize@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(C@0x1008) - v48:BasicObject = SendWithoutBlockDirect v45, :initialize (0x1070), v13 - CheckInterrupts - CheckInterrupts - Return v45 - "); - } - - #[test] - fn test_opt_new_object() { - eval(" - def test = Object.new - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, Object) - v40:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v12:NilClass = Const Value(nil) - PatchPoint MethodRedefined(Object@0x1008, new@0x1010, cme:0x1018) - v43:ObjectExact = ObjectAllocClass Object:VALUE(0x1008) - PatchPoint MethodRedefined(Object@0x1008, initialize@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(Object@0x1008) - v47:NilClass = Const Value(nil) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - CheckInterrupts - Return v43 - "); - } - - #[test] - fn test_opt_new_basic_object() { - eval(" - def test = BasicObject.new - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, BasicObject) - v40:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v12:NilClass = Const Value(nil) - PatchPoint MethodRedefined(BasicObject@0x1008, new@0x1010, cme:0x1018) - v43:BasicObjectExact = ObjectAllocClass BasicObject:VALUE(0x1008) - PatchPoint MethodRedefined(BasicObject@0x1008, initialize@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(BasicObject@0x1008) - v47:NilClass = Const Value(nil) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - CheckInterrupts - Return v43 - "); - } - - #[test] - fn test_opt_new_hash() { - eval(" - def test = Hash.new - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, Hash) - v40:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v12:NilClass = Const Value(nil) - PatchPoint MethodRedefined(Hash@0x1008, new@0x1010, cme:0x1018) - v43:HashExact = ObjectAllocClass Hash:VALUE(0x1008) - v18:BasicObject = SendWithoutBlock v43, :initialize - CheckInterrupts - CheckInterrupts - Return v43 - "); - assert_snapshot!(inspect("test"), @"{}"); - } - - #[test] - fn test_opt_new_array() { - eval(" - def test = Array.new 1 - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, Array) - v42:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v12:NilClass = Const Value(nil) - v13:Fixnum[1] = Const Value(1) - PatchPoint MethodRedefined(Array@0x1008, new@0x1010, cme:0x1018) - PatchPoint MethodRedefined(Class@0x1040, new@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Class@0x1040) - v53:BasicObject = CCallVariadic new@0x1048, v42, v13 - CheckInterrupts - Return v53 - "); - } - - #[test] - fn test_opt_new_set() { - eval(" - def test = Set.new - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, Set) - v40:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v12:NilClass = Const Value(nil) - PatchPoint MethodRedefined(Set@0x1008, new@0x1010, cme:0x1018) - v16:HeapBasicObject = ObjectAlloc v40 - PatchPoint MethodRedefined(Set@0x1008, initialize@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(Set@0x1008) - v46:SetExact = GuardType v16, SetExact - v47:BasicObject = CCallVariadic initialize@0x1070, v46 - CheckInterrupts - CheckInterrupts - Return v16 - "); - } - - #[test] - fn test_opt_new_string() { - eval(" - def test = String.new - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, String) - v40:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v12:NilClass = Const Value(nil) - PatchPoint MethodRedefined(String@0x1008, new@0x1010, cme:0x1018) - PatchPoint MethodRedefined(Class@0x1040, new@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Class@0x1040) - v51:BasicObject = CCallVariadic new@0x1048, v40 - CheckInterrupts - Return v51 - "); - } - - #[test] - fn test_opt_new_regexp() { - eval(" - def test = Regexp.new '' - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, Regexp) - v44:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v12:NilClass = Const Value(nil) - v13:StringExact[VALUE(0x1010)] = Const Value(VALUE(0x1010)) - v15:StringExact = StringCopy v13 - PatchPoint MethodRedefined(Regexp@0x1008, new@0x1018, cme:0x1020) - v47:RegexpExact = ObjectAllocClass Regexp:VALUE(0x1008) - PatchPoint MethodRedefined(Regexp@0x1008, initialize@0x1048, cme:0x1050) - PatchPoint NoSingletonClass(Regexp@0x1008) - v51:BasicObject = CCallVariadic initialize@0x1078, v47, v15 - CheckInterrupts - CheckInterrupts - Return v47 - "); - } - - #[test] - fn test_opt_length() { - eval(" - def test(a,b) = [a,b].length - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v17:ArrayExact = NewArray v11, v12 - PatchPoint MethodRedefined(Array@0x1000, length@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - IncrCounter inline_cfunc_optimized_send_count - v31:Fixnum = CCall length@0x1038, v17 - CheckInterrupts - Return v31 - "); - } - - #[test] - fn test_opt_size() { - eval(" - def test(a,b) = [a,b].size - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v17:ArrayExact = NewArray v11, v12 - PatchPoint MethodRedefined(Array@0x1000, size@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - IncrCounter inline_cfunc_optimized_send_count - v31:Fixnum = CCall size@0x1038, v17 - CheckInterrupts - Return v31 - "); - } - - #[test] - fn test_getblockparamproxy() { - eval(" - def test(&block) = tap(&block) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - GuardBlockParamProxy l0 - v15:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1000)) - v17:BasicObject = Send v8, 0x1008, :tap, v15 - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_getinstancevariable() { - eval(" - def test = @foo - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - v12:BasicObject = GetIvar v6, :@foo - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_setinstancevariable() { - eval(" - def test = @foo = 1 - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - PatchPoint SingleRactorMode - SetIvar v6, :@foo, v10 - CheckInterrupts - Return v10 - "); - } - - #[test] - fn test_elide_freeze_with_frozen_hash() { - eval(" - def test = {}.freeze - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_dont_optimize_hash_freeze_if_redefined() { - eval(" - class Hash - def freeze; end - end - def test = {}.freeze - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - SideExit PatchPoint(BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE)) - "); - } - - #[test] - fn test_elide_freeze_with_refrozen_hash() { - eval(" - def test = {}.freeze.freeze - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - PatchPoint BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE) - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_no_elide_freeze_with_unfrozen_hash() { - eval(" - def test = {}.dup.freeze - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:HashExact = NewHash - PatchPoint MethodRedefined(Hash@0x1000, dup@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Hash@0x1000) - v24:BasicObject = CCallWithFrame dup@0x1038, v11 - v15:BasicObject = SendWithoutBlock v24, :freeze - CheckInterrupts - Return v15 - "); - } - - #[test] - fn test_no_elide_freeze_hash_with_args() { - eval(" - def test = {}.freeze(nil) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:HashExact = NewHash - v12:NilClass = Const Value(nil) - v14:BasicObject = SendWithoutBlock v11, :freeze, v12 - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_elide_freeze_with_frozen_ary() { - eval(" - def test = [].freeze - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_elide_freeze_with_refrozen_ary() { - eval(" - def test = [].freeze.freeze - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_no_elide_freeze_with_unfrozen_ary() { - eval(" - def test = [].dup.freeze - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:ArrayExact = NewArray - PatchPoint MethodRedefined(Array@0x1000, dup@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v24:BasicObject = CCallWithFrame dup@0x1038, v11 - v15:BasicObject = SendWithoutBlock v24, :freeze - CheckInterrupts - Return v15 - "); - } - - #[test] - fn test_no_elide_freeze_ary_with_args() { - eval(" - def test = [].freeze(nil) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:ArrayExact = NewArray - v12:NilClass = Const Value(nil) - v14:BasicObject = SendWithoutBlock v11, :freeze, v12 - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_elide_freeze_with_frozen_str() { - eval(" - def test = ''.freeze - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_elide_freeze_with_refrozen_str() { - eval(" - def test = ''.freeze.freeze - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE) - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_no_elide_freeze_with_unfrozen_str() { - eval(" - def test = ''.dup.freeze - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:StringExact = StringCopy v10 - PatchPoint MethodRedefined(String@0x1008, dup@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(String@0x1008) - v25:BasicObject = CCallWithFrame dup@0x1040, v12 - v16:BasicObject = SendWithoutBlock v25, :freeze - CheckInterrupts - Return v16 - "); - } - - #[test] - fn test_no_elide_freeze_str_with_args() { - eval(" - def test = ''.freeze(nil) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:StringExact = StringCopy v10 - v13:NilClass = Const Value(nil) - v15:BasicObject = SendWithoutBlock v12, :freeze, v13 - CheckInterrupts - Return v15 - "); - } - - #[test] - fn test_elide_uminus_with_frozen_str() { - eval(" - def test = -'' - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_UMINUS) - v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_elide_uminus_with_refrozen_str() { - eval(" - def test = -''.freeze - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_UMINUS) - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_no_elide_uminus_with_unfrozen_str() { - eval(" - def test = -''.dup - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:StringExact = StringCopy v10 - PatchPoint MethodRedefined(String@0x1008, dup@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(String@0x1008) - v25:BasicObject = CCallWithFrame dup@0x1040, v12 - v16:BasicObject = SendWithoutBlock v25, :-@ - CheckInterrupts - Return v16 - "); - } - - #[test] - fn test_objtostring_anytostring_string() { - eval(r##" - def test = "#{('foo')}" - "##); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v13:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v15:StringExact = StringCopy v13 - v21:StringExact = StringConcat v10, v15 - CheckInterrupts - Return v21 - "); - } - - #[test] - fn test_objtostring_anytostring_with_non_string() { - eval(r##" - def test = "#{1}" - "##); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v11:Fixnum[1] = Const Value(1) - v13:BasicObject = ObjToString v11 - v15:String = AnyToString v11, str: v13 - v17:StringExact = StringConcat v10, v15 - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_optimize_objtostring_anytostring_recv_profiled() { - eval(" - def test(a) - \"#{a}\" - end - test('foo'); test('foo') - "); - - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - PatchPoint NoSingletonClass(String@0x1008) - v26:String = GuardType v9, String - v19:StringExact = StringConcat v13, v26 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_optimize_objtostring_anytostring_recv_profiled_string_subclass() { - eval(" - class MyString < String; end - - def test(a) - \"#{a}\" - end - foo = MyString.new('foo') - test(MyString.new(foo)); test(MyString.new(foo)) - "); - - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - PatchPoint NoSingletonClass(MyString@0x1008) - v26:String = GuardType v9, String - v19:StringExact = StringConcat v13, v26 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_optimize_objtostring_profiled_nonstring_falls_back_to_send() { - eval(" - def test(a) - \"#{a}\" - end - test([1,2,3]); test([1,2,3]) # No fast path for array - "); - - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v25:BasicObject = GuardTypeNot v9, String - PatchPoint MethodRedefined(Array@0x1008, to_s@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Array@0x1008) - v30:ArrayExact = GuardType v9, ArrayExact - v31:BasicObject = CCallWithFrame to_s@0x1040, v30 - v17:String = AnyToString v9, str: v31 - v19:StringExact = StringConcat v13, v17 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_branchnil_nil() { - eval(" - def test - x = nil - x&.itself - end - "); - - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:NilClass = Const Value(nil) - CheckInterrupts - CheckInterrupts - Return v13 - "); - } - - #[test] - fn test_branchnil_truthy() { - eval(" - def test - x = 1 - x&.itself - end - "); - - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:Fixnum[1] = Const Value(1) - CheckInterrupts - PatchPoint MethodRedefined(Integer@0x1000, itself@0x1008, cme:0x1010) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v13 - "); - } - - #[test] - fn test_dont_eliminate_load_from_non_frozen_array() { - eval(r##" - S = [4,5,6] - def test = S[0] - test - "##); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, S) - v24:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v12:Fixnum[0] = Const Value(0) - PatchPoint MethodRedefined(Array@0x1010, []@0x1018, cme:0x1020) - PatchPoint NoSingletonClass(Array@0x1010) - v28:BasicObject = ArrayArefFixnum v24, v12 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - // TODO(max): Check the result of `S[0] = 5; test` using `inspect` to make sure that we - // actually do the load at run-time. - } - - #[test] - fn test_eliminate_load_from_frozen_array_in_bounds() { - eval(r##" - def test = [4,5,6].freeze[1] - "##); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v13:Fixnum[1] = Const Value(1) - PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Array@0x1008) - v28:Fixnum[5] = Const Value(5) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_eliminate_load_from_frozen_array_negative() { - eval(r##" - def test = [4,5,6].freeze[-3] - "##); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v13:Fixnum[-3] = Const Value(-3) - PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Array@0x1008) - v28:Fixnum[4] = Const Value(4) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_eliminate_load_from_frozen_array_negative_out_of_bounds() { - eval(r##" - def test = [4,5,6].freeze[-10] - "##); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v13:Fixnum[-10] = Const Value(-10) - PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Array@0x1008) - v28:NilClass = Const Value(nil) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_eliminate_load_from_frozen_array_out_of_bounds() { - eval(r##" - def test = [4,5,6].freeze[10] - "##); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v13:Fixnum[10] = Const Value(10) - PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Array@0x1008) - v28:NilClass = Const Value(nil) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_dont_optimize_array_aref_if_redefined() { - eval(r##" - class Array - def [](index) = [] - end - def test = [4,5,6].freeze[10] - "##); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) - v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v13:Fixnum[10] = Const Value(10) - PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Array@0x1008) - v25:BasicObject = SendWithoutBlockDirect v12, :[] (0x1040), v13 - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_dont_optimize_array_max_if_redefined() { - eval(r##" - class Array - def max = [] - end - def test = [4,5,6].max - "##); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:ArrayExact = ArrayDup v10 - PatchPoint MethodRedefined(Array@0x1008, max@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Array@0x1008) - v22:BasicObject = SendWithoutBlockDirect v12, :max (0x1040) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_set_type_from_constant() { - eval(" - MY_SET = Set.new - - def test = MY_SET - - test - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, MY_SET) - v19:SetExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_regexp_type() { - eval(" - def test = /a/ - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:RegexpExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - CheckInterrupts - Return v10 - "); - } - - #[test] - fn test_bmethod_send_direct() { - eval(" - define_method(:zero) { :b } - define_method(:one) { |arg| arg } - - def test = one(zero) - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint MethodRedefined(Object@0x1000, zero@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v22:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - IncrCounter inline_iseq_optimized_send_count - v30:StaticSymbol[:b] = Const Value(VALUE(0x1038)) - PatchPoint SingleRactorMode - PatchPoint MethodRedefined(Object@0x1000, one@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(Object@0x1000) - v27:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - IncrCounter inline_iseq_optimized_send_count - CheckInterrupts - Return v30 - "); - } - - #[test] - fn test_symbol_block_bmethod() { - eval(" - define_method(:identity, &:itself) - def test = identity(100) - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[100] = Const Value(100) - v12:BasicObject = SendWithoutBlock v6, :identity, v10 - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_call_bmethod_with_block() { - eval(" - define_method(:bmethod) { :b } - def test = (bmethod {}) - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = Send v6, 0x1000, :bmethod - CheckInterrupts - Return v11 - "); - } - - #[test] - fn test_call_shareable_bmethod() { - eval(" - class Foo - class << self - define_method(:identity, &(Ractor.make_shareable ->(val){val})) - end - end - def test = Foo.identity(100) - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:7: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, Foo) - v22:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v12:Fixnum[100] = Const Value(100) - PatchPoint MethodRedefined(Class@0x1010, identity@0x1018, cme:0x1020) - PatchPoint NoSingletonClass(Class@0x1010) - IncrCounter inline_iseq_optimized_send_count - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_nil_nil_specialized_to_ccall() { - eval(" - def test = nil.nil? - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:NilClass = Const Value(nil) - PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008, cme:0x1010) - v22:TrueClass = Const Value(true) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_eliminate_nil_nil_specialized_to_ccall() { - eval(" - def test - nil.nil? - 1 - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:NilClass = Const Value(nil) - PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008, cme:0x1010) - IncrCounter inline_cfunc_optimized_send_count - v17:Fixnum[1] = Const Value(1) - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_non_nil_nil_specialized_to_ccall() { - eval(" - def test = 1.nil? - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - PatchPoint MethodRedefined(Integer@0x1000, nil?@0x1008, cme:0x1010) - v22:FalseClass = Const Value(false) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_eliminate_non_nil_nil_specialized_to_ccall() { - eval(" - def test - 1.nil? - 2 - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - PatchPoint MethodRedefined(Integer@0x1000, nil?@0x1008, cme:0x1010) - IncrCounter inline_cfunc_optimized_send_count - v17:Fixnum[2] = Const Value(2) - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_guard_nil_for_nil_opt() { - eval(" - def test(val) = val.nil? - - test(nil) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008, cme:0x1010) - v24:NilClass = GuardType v9, NilClass - v25:TrueClass = Const Value(true) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_guard_false_for_nil_opt() { - eval(" - def test(val) = val.nil? - - test(false) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(FalseClass@0x1000, nil?@0x1008, cme:0x1010) - v24:FalseClass = GuardType v9, FalseClass - v25:FalseClass = Const Value(false) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_guard_true_for_nil_opt() { - eval(" - def test(val) = val.nil? - - test(true) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(TrueClass@0x1000, nil?@0x1008, cme:0x1010) - v24:TrueClass = GuardType v9, TrueClass - v25:FalseClass = Const Value(false) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_guard_symbol_for_nil_opt() { - eval(" - def test(val) = val.nil? - - test(:foo) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Symbol@0x1000, nil?@0x1008, cme:0x1010) - v24:StaticSymbol = GuardType v9, StaticSymbol - v25:FalseClass = Const Value(false) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_guard_fixnum_for_nil_opt() { - eval(" - def test(val) = val.nil? - - test(1) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Integer@0x1000, nil?@0x1008, cme:0x1010) - v24:Fixnum = GuardType v9, Fixnum - v25:FalseClass = Const Value(false) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_guard_float_for_nil_opt() { - eval(" - def test(val) = val.nil? - - test(1.0) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Float@0x1000, nil?@0x1008, cme:0x1010) - v24:Flonum = GuardType v9, Flonum - v25:FalseClass = Const Value(false) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_guard_string_for_nil_opt() { - eval(" - def test(val) = val.nil? - - test('foo') - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(String@0x1000, nil?@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v25:StringExact = GuardType v9, StringExact - v26:FalseClass = Const Value(false) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_specialize_basicobject_not_to_ccall() { - eval(" - def test(a) = !a - - test([]) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Array@0x1000, !@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v25:ArrayExact = GuardType v9, ArrayExact - IncrCounter inline_cfunc_optimized_send_count - v27:BoolExact = CCall !@0x1038, v25 - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_specialize_array_empty_p_to_ccall() { - eval(" - def test(a) = a.empty? - - test([]) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Array@0x1000, empty?@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v25:ArrayExact = GuardType v9, ArrayExact - IncrCounter inline_cfunc_optimized_send_count - v27:BoolExact = CCall empty?@0x1038, v25 - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_specialize_hash_empty_p_to_ccall() { - eval(" - def test(a) = a.empty? - - test({}) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Hash@0x1000, empty?@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Hash@0x1000) - v25:HashExact = GuardType v9, HashExact - IncrCounter inline_cfunc_optimized_send_count - v27:BoolExact = CCall empty?@0x1038, v25 - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_specialize_basic_object_eq() { - eval(" - class C; end - def test(a, b) = a == b - - test(C.new, C.new) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(C@0x1000, ==@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(C@0x1000) - v28:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] - v29:CBool = IsBitEqual v28, v12 - v30:BoolExact = BoxBool v29 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v30 - "); - } - - #[test] - fn test_specialize_basic_object_eqq() { - eval(" - class C; end - def test(a, b) = a === b - - test(C.new, C.new) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(C@0x1000, ===@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(C@0x1000) - v26:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] - PatchPoint MethodRedefined(C@0x1000, ==@0x1038, cme:0x1040) - PatchPoint NoSingletonClass(C@0x1000) - v30:CBool = IsBitEqual v26, v12 - v31:BoolExact = BoxBool v30 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v31 - "); - } - - #[test] - fn test_specialize_nil_eq() { - eval(" - def test(a, b) = a == b - - test(nil, 5) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(NilClass@0x1000, ==@0x1008, cme:0x1010) - v27:NilClass = GuardType v11, NilClass - v28:CBool = IsBitEqual v27, v12 - v29:BoolExact = BoxBool v28 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v29 - "); - } - - #[test] - fn test_specialize_nil_eqq() { - eval(" - def test(a, b) = a === b - test(nil, 5) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(NilClass@0x1000, ===@0x1008, cme:0x1010) - v25:NilClass = GuardType v11, NilClass - PatchPoint MethodRedefined(NilClass@0x1000, ==@0x1038, cme:0x1040) - v28:CBool = IsBitEqual v25, v12 - v29:BoolExact = BoxBool v28 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v29 - "); - } - - #[test] - fn test_specialize_true_eqq() { - eval(" - def test(a, b) = a === b - test(true, 5) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(TrueClass@0x1000, ===@0x1008, cme:0x1010) - v25:TrueClass = GuardType v11, TrueClass - PatchPoint MethodRedefined(TrueClass@0x1000, ==@0x1038, cme:0x1040) - v28:CBool = IsBitEqual v25, v12 - v29:BoolExact = BoxBool v28 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v29 - "); - } - - #[test] - fn test_specialize_false_eqq() { - eval(" - def test(a, b) = a === b - test(true, 5) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(TrueClass@0x1000, ===@0x1008, cme:0x1010) - v25:TrueClass = GuardType v11, TrueClass - PatchPoint MethodRedefined(TrueClass@0x1000, ==@0x1038, cme:0x1040) - v28:CBool = IsBitEqual v25, v12 - v29:BoolExact = BoxBool v28 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v29 - "); - } - - #[test] - fn test_guard_fixnum_and_fixnum() { - eval(" - def test(x, y) = x & y - - test(1, 2) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, 28) - v26:Fixnum = GuardType v11, Fixnum - v27:Fixnum = GuardType v12, Fixnum - v28:Fixnum = FixnumAnd v26, v27 - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_guard_fixnum_or_fixnum() { - eval(" - def test(x, y) = x | y - - test(1, 2) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, 29) - v26:Fixnum = GuardType v11, Fixnum - v27:Fixnum = GuardType v12, Fixnum - v28:Fixnum = FixnumOr v26, v27 - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_method_redefinition_patch_point_on_top_level_method() { - eval(" - def foo; end - def test = foo - - test; test - "); - - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - IncrCounter inline_iseq_optimized_send_count - v22:NilClass = Const Value(nil) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_optimize_getivar_embedded() { - eval(" - class C - attr_reader :foo - def initialize - @foo = 42 - end - end - - O = C.new - def test(o) = o.foo - test O - test O - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:10: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(C@0x1000) - v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - v25:HeapObject[class_exact:C] = GuardShape v22, 0x1038 - v26:BasicObject = LoadIvarEmbedded v25, :@foo@0x1039 - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_optimize_getivar_extended() { - eval(" - class C - attr_reader :foo - def initialize - @foo = 42 - end - end - - O = C.new - def test(o) = o.foo - test O - test O - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:10: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(C@0x1000) - v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - v25:HeapObject[class_exact:C] = GuardShape v22, 0x1038 - v26:BasicObject = LoadIvarEmbedded v25, :@foo@0x1039 - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_dont_optimize_getivar_polymorphic() { - set_call_threshold(3); - eval(" - class C - attr_reader :foo, :bar - - def foo_then_bar - @foo = 1 - @bar = 2 - end - - def bar_then_foo - @bar = 3 - @foo = 4 - end - end - - O1 = C.new - O1.foo_then_bar - O2 = C.new - O2.bar_then_foo - def test(o) = o.foo - test O1 - test O2 - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:20: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:BasicObject = SendWithoutBlock v9, :foo - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_optimize_send_with_block() { - eval(r#" - def test = [1, 2, 3].map { |x| x * 2 } - test; test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:ArrayExact = ArrayDup v10 - PatchPoint MethodRedefined(Array@0x1008, map@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Array@0x1008) - v23:BasicObject = CCallWithFrame map@0x1040, v12, block=0x1048 - CheckInterrupts - Return v23 - "); - } - - #[test] - fn test_do_not_optimize_send_variadic_with_block() { - eval(r#" - def test = [1, 2, 3].index { |x| x == 2 } - test; test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:ArrayExact = ArrayDup v10 - v14:BasicObject = Send v12, 0x1008, :index - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_do_not_optimize_send_with_block_forwarding() { - eval(r#" - def test(&block) = [].map(&block) - test; test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:ArrayExact = NewArray - GuardBlockParamProxy l0 - v17:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1000)) - v19:BasicObject = Send v14, 0x1008, :map, v17 - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_do_not_optimize_send_to_iseq_method_with_block() { - eval(r#" - def foo - yield 1 - end - - def test = foo {} - test; test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:6: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:BasicObject = Send v6, 0x1000, :foo - CheckInterrupts - Return v11 - "); - } - - #[test] - fn test_inline_attr_reader_constant() { - eval(" - class C - attr_reader :foo - end - - O = C.new - def test = O.foo - test - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:7: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, O) - v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint MethodRedefined(C@0x1010, foo@0x1018, cme:0x1020) - PatchPoint NoSingletonClass(C@0x1010) - v26:HeapObject[VALUE(0x1008)] = GuardShape v21, 0x1048 - v27:NilClass = Const Value(nil) - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_inline_attr_accessor_constant() { - eval(" - class C - attr_accessor :foo - end - - O = C.new - def test = O.foo - test - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:7: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, O) - v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint MethodRedefined(C@0x1010, foo@0x1018, cme:0x1020) - PatchPoint NoSingletonClass(C@0x1010) - v26:HeapObject[VALUE(0x1008)] = GuardShape v21, 0x1048 - v27:NilClass = Const Value(nil) - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_inline_attr_reader() { - eval(" - class C - attr_reader :foo - end - - def test(o) = o.foo - test C.new - test C.new - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:6: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(C@0x1000) - v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - v25:HeapObject[class_exact:C] = GuardShape v22, 0x1038 - v26:NilClass = Const Value(nil) - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_inline_attr_accessor() { - eval(" - class C - attr_accessor :foo - end - - def test(o) = o.foo - test C.new - test C.new - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:6: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(C@0x1000) - v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - v25:HeapObject[class_exact:C] = GuardShape v22, 0x1038 - v26:NilClass = Const Value(nil) - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_inline_attr_accessor_set() { - eval(" - class C - attr_accessor :foo - end - - def test(o) = o.foo = 5 - test C.new - test C.new - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:6: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:Fixnum[5] = Const Value(5) - PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) - v23:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - SetIvar v23, :@foo, v14 - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_inline_attr_writer_set() { - eval(" - class C - attr_writer :foo - end - - def test(o) = o.foo = 5 - test C.new - test C.new - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:6: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:Fixnum[5] = Const Value(5) - PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) - v23:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - SetIvar v23, :@foo, v14 - CheckInterrupts - Return v14 - "); - } - - #[test] - fn test_array_reverse_returns_array() { - eval(r#" - def test = [].reverse - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:ArrayExact = NewArray - PatchPoint MethodRedefined(Array@0x1000, reverse@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v22:ArrayExact = CCallWithFrame reverse@0x1038, v11 - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_array_reverse_is_elidable() { - eval(r#" - def test - [].reverse - 5 - end - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:ArrayExact = NewArray - PatchPoint MethodRedefined(Array@0x1000, reverse@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v16:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v16 - "); - } - - #[test] - fn test_array_join_returns_string() { - eval(r#" - def test = [].join "," - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v11:ArrayExact = NewArray - v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v14:StringExact = StringCopy v12 - PatchPoint MethodRedefined(Array@0x1008, join@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Array@0x1008) - v25:StringExact = CCallVariadic join@0x1040, v11, v14 - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_string_to_s_returns_string() { - eval(r#" - def test = "".to_s - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:StringExact = StringCopy v10 - PatchPoint MethodRedefined(String@0x1008, to_s@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(String@0x1008) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_string_subclass_to_s_returns_string_exact() { - eval(r#" - class C < String; end - def test(o) = o.to_s - test(C.new) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(C@0x1000, to_s@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(C@0x1000) - v23:StringSubclass[class_exact:C] = GuardType v9, StringSubclass[class_exact:C] - v24:StringExact = CCallWithFrame to_s@0x1038, v23 - CheckInterrupts - Return v24 - "); - } - - #[test] - fn test_inline_string_literal_to_s() { - eval(r#" - def test = "foo".to_s - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v12:StringExact = StringCopy v10 - PatchPoint MethodRedefined(String@0x1008, to_s@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(String@0x1008) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_inline_profiled_string_to_s() { - eval(r#" - def test(o) = o.to_s - test "foo" - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(String@0x1000, to_s@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v23:StringExact = GuardType v9, StringExact - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v23 - "); - } - - #[test] - fn test_array_aref_fixnum_literal() { - eval(" - def test - arr = [1, 2, 3] - arr[0] - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v15:ArrayExact = ArrayDup v13 - v18:Fixnum[0] = Const Value(0) - PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Array@0x1008) - v31:BasicObject = ArrayArefFixnum v15, v18 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v31 - "); - } - - #[test] - fn test_array_aref_fixnum_profiled() { - eval(" - def test(arr, idx) - arr[idx] - end - test([1, 2, 3], 0) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(Array@0x1000, []@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v28:ArrayExact = GuardType v11, ArrayExact - v29:Fixnum = GuardType v12, Fixnum - v30:BasicObject = ArrayArefFixnum v28, v29 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v30 - "); - } - - #[test] - fn test_array_aref_fixnum_array_subclass() { - eval(" - class C < Array; end - def test(arr, idx) - arr[idx] - end - test(C.new([1, 2, 3]), 0) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(C@0x1000, []@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(C@0x1000) - v28:ArraySubclass[class_exact:C] = GuardType v11, ArraySubclass[class_exact:C] - v29:Fixnum = GuardType v12, Fixnum - v30:BasicObject = ArrayArefFixnum v28, v29 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v30 - "); - } - - #[test] - fn test_hash_aref_literal() { - eval(" - def test - arr = {1 => 3} - arr[1] - end - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:NilClass = Const Value(nil) - Jump bb2(v1, v2) - bb1(v5:BasicObject): - EntryPoint JIT(0) - v6:NilClass = Const Value(nil) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:NilClass): - v13:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v15:HashExact = HashDup v13 - v18:Fixnum[1] = Const Value(1) - PatchPoint MethodRedefined(Hash@0x1008, []@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(Hash@0x1008) - v31:BasicObject = HashAref v15, v18 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v31 - "); - } - - #[test] - fn test_hash_aref_profiled() { - eval(" - def test(hash, key) - hash[key] - end - test({1 => 3}, 1) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(Hash@0x1000, []@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Hash@0x1000) - v28:HashExact = GuardType v11, HashExact - v29:BasicObject = HashAref v28, v12 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v29 - "); - } - - #[test] - fn test_hash_aref_subclass() { - eval(" - class C < Hash; end - def test(hash, key) - hash[key] - end - test(C.new({0 => 3}), 0) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(C@0x1000, []@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(C@0x1000) - v28:HashSubclass[class_exact:C] = GuardType v11, HashSubclass[class_exact:C] - v29:BasicObject = HashAref v28, v12 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v29 - "); - } - - #[test] - fn test_does_not_fold_hash_aref_with_frozen_hash() { - eval(" - H = {a: 0}.freeze - def test = H[:a] - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, H) - v24:HashExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v12:StaticSymbol[:a] = Const Value(VALUE(0x1010)) - PatchPoint MethodRedefined(Hash@0x1018, []@0x1020, cme:0x1028) - PatchPoint NoSingletonClass(Hash@0x1018) - v28:BasicObject = HashAref v24, v12 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_optimize_thread_current() { - eval(" - def test = Thread.current - test - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint SingleRactorMode - PatchPoint StableConstantNames(0x1000, Thread) - v21:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint MethodRedefined(Class@0x1010, current@0x1018, cme:0x1020) - PatchPoint NoSingletonClass(Class@0x1010) - IncrCounter inline_cfunc_optimized_send_count - v26:BasicObject = CCall current@0x1048, v21 - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_optimize_array_aset() { - eval(" - def test(arr) - arr[1] = 10 - end - test([]) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v14:Fixnum[1] = Const Value(1) - v15:Fixnum[10] = Const Value(10) - PatchPoint MethodRedefined(Array@0x1000, []=@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v28:ArrayExact = GuardType v9, ArrayExact - v29:BasicObject = CCallVariadic []=@0x1038, v28, v14, v15 - CheckInterrupts - Return v15 - "); - } - - #[test] - fn test_optimize_array_ltlt() { - eval(" - def test(arr) - arr << 1 - end - test([]) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[1] = Const Value(1) - PatchPoint MethodRedefined(Array@0x1000, <<@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v26:ArrayExact = GuardType v9, ArrayExact - ArrayPush v26, v13 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_optimize_array_push_single_arg() { - eval(" - def test(arr) - arr.push(1) - end - test([]) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[1] = Const Value(1) - PatchPoint MethodRedefined(Array@0x1000, push@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v24:ArrayExact = GuardType v9, ArrayExact - ArrayPush v24, v13 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v24 - "); - } - - #[test] - fn test_do_not_optimize_array_push_multi_arg() { - eval(" - def test(arr) - arr.push(1,2,3) - end - test([]) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[1] = Const Value(1) - v14:Fixnum[2] = Const Value(2) - v15:Fixnum[3] = Const Value(3) - PatchPoint MethodRedefined(Array@0x1000, push@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v26:ArrayExact = GuardType v9, ArrayExact - v27:BasicObject = CCallVariadic push@0x1038, v26, v13, v14, v15 - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_optimize_array_length() { - eval(" - def test(arr) = arr.length - test([]) - "); - assert_contains_opcode("test", YARVINSN_opt_length); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Array@0x1000, length@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v25:ArrayExact = GuardType v9, ArrayExact - IncrCounter inline_cfunc_optimized_send_count - v27:Fixnum = CCall length@0x1038, v25 - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_optimize_array_size() { - eval(" - def test(arr) = arr.size - test([]) - "); - assert_contains_opcode("test", YARVINSN_opt_size); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Array@0x1000, size@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v25:ArrayExact = GuardType v9, ArrayExact - IncrCounter inline_cfunc_optimized_send_count - v27:Fixnum = CCall size@0x1038, v25 - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_optimize_array_pop_no_arg() { - eval(" - def test(arr) = arr.pop - test([1]) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Array@0x1000, pop@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v23:ArrayExact = GuardType v9, ArrayExact - v24:ArrayExact = GuardNotFrozen v23 - v25:BasicObject = ArrayPop v24 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_do_not_optimize_array_pop_arg() { - eval(" - def test(arr) = arr.pop(4) - test([1]) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:Fixnum[4] = Const Value(4) - PatchPoint MethodRedefined(Array@0x1000, pop@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v24:ArrayExact = GuardType v9, ArrayExact - v25:BasicObject = CCallVariadic pop@0x1038, v24, v13 - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_guard_array_pop_frozen() { - eval(" - def test(arr) - arr.pop - rescue FrozenError - nil - end - arr = [1].freeze - test(arr) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Array@0x1000, pop@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Array@0x1000) - v23:ArrayExact = GuardType v9, ArrayExact - v24:ArrayExact = GuardNotFrozen v23 - v25:BasicObject = ArrayPop v24 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_optimize_regexpmatch2() { - eval(r#" - def test(s) = s =~ /a/ - test("foo") - "#); - assert_contains_opcode("test", YARVINSN_opt_regexpmatch2); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:RegexpExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - PatchPoint MethodRedefined(String@0x1008, =~@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(String@0x1008) - v26:StringExact = GuardType v9, StringExact - v27:BasicObject = CCallWithFrame =~@0x1040, v26, v13 - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_optimize_string_getbyte_fixnum() { - eval(r#" - def test(s, i) = s.getbyte(i) - test("foo", 0) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(String@0x1000, getbyte@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v26:StringExact = GuardType v11, StringExact - v27:Fixnum = GuardType v12, Fixnum - v28:NilClass|Fixnum = StringGetbyteFixnum v26, v27 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_elide_string_getbyte_fixnum() { - eval(r#" - def test(s, i) - s.getbyte(i) - 5 - end - test("foo", 0) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(String@0x1000, getbyte@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v29:StringExact = GuardType v11, StringExact - v30:Fixnum = GuardType v12, Fixnum - IncrCounter inline_cfunc_optimized_send_count - v20:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v20 - "); - } - - #[test] - fn test_specialize_string_empty() { - eval(r#" - def test(s) - s.empty? - end - test("asdf") - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(String@0x1000, empty?@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v25:StringExact = GuardType v9, StringExact - IncrCounter inline_cfunc_optimized_send_count - v27:BoolExact = CCall empty?@0x1038, v25 - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_eliminate_string_empty() { - eval(r#" - def test(s) - s.empty? - 4 - end - test("this should get removed") - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(String@0x1000, empty?@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v28:StringExact = GuardType v9, StringExact - IncrCounter inline_cfunc_optimized_send_count - v19:Fixnum[4] = Const Value(4) - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_inline_integer_succ_with_fixnum() { - eval(" - def test(x) = x.succ - test(4) - "); - assert_contains_opcode("test", YARVINSN_opt_succ); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Integer@0x1000, succ@0x1008, cme:0x1010) - v24:Fixnum = GuardType v9, Fixnum - v25:Fixnum[1] = Const Value(1) - v26:Fixnum = FixnumAdd v24, v25 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_dont_inline_integer_succ_with_bignum() { - eval(" - def test(x) = x.succ - test(4 << 70) - "); - assert_contains_opcode("test", YARVINSN_opt_succ); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Integer@0x1000, succ@0x1008, cme:0x1010) - v24:Integer = GuardType v9, Integer - v25:BasicObject = CCallWithFrame succ@0x1038, v24 - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_optimize_string_append() { - eval(r#" - def test(x, y) = x << y - test("iron", "fish") - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(String@0x1000, <<@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v28:StringExact = GuardType v11, StringExact - v29:String = GuardType v12, String - v30:StringExact = StringAppend v28, v29 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - } - - // TODO: This should be inlined just as in the interpreter - #[test] - fn test_optimize_string_append_non_string() { - eval(r#" - def test(x, y) = x << y - test("iron", 4) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(String@0x1000, <<@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v28:StringExact = GuardType v11, StringExact - v29:BasicObject = CCallWithFrame <<@0x1038, v28, v12 - CheckInterrupts - Return v29 - "); - } - - #[test] - fn test_optimize_string_append_string_subclass() { - eval(r#" - class MyString < String - end - def test(x, y) = x << y - test("iron", MyString.new) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(String@0x1000, <<@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v28:StringExact = GuardType v11, StringExact - v29:String = GuardType v12, String - v30:StringExact = StringAppend v28, v29 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_do_not_optimize_string_subclass_append_string() { - eval(r#" - class MyString < String - end - def test(x, y) = x << y - test(MyString.new, "iron") - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(MyString@0x1000, <<@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(MyString@0x1000) - v28:StringSubclass[class_exact:MyString] = GuardType v11, StringSubclass[class_exact:MyString] - v29:BasicObject = CCallWithFrame <<@0x1038, v28, v12 - CheckInterrupts - Return v29 - "); - } - - #[test] - fn test_dont_inline_integer_succ_with_args() { - eval(" - def test = 4.succ 1 - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[4] = Const Value(4) - v11:Fixnum[1] = Const Value(1) - v13:BasicObject = SendWithoutBlock v10, :succ, v11 - CheckInterrupts - Return v13 - "); - } - - #[test] - fn test_inline_integer_xor_with_fixnum() { - eval(" - def test(x, y) = x ^ y - test(1, 2) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(Integer@0x1000, ^@0x1008, cme:0x1010) - v25:Fixnum = GuardType v11, Fixnum - v26:Fixnum = GuardType v12, Fixnum - v27:Fixnum = FixnumXor v25, v26 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_eliminate_integer_xor() { - eval(r#" - def test(x, y) - x ^ y - 42 - end - test(1, 2) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(Integer@0x1000, ^@0x1008, cme:0x1010) - v28:Fixnum = GuardType v11, Fixnum - v29:Fixnum = GuardType v12, Fixnum - IncrCounter inline_cfunc_optimized_send_count - v20:Fixnum[42] = Const Value(42) - CheckInterrupts - Return v20 - "); - } - - #[test] - fn test_dont_inline_integer_xor_with_bignum_or_boolean() { - eval(" - def test(x, y) = x ^ y - test(4 << 70, 1) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(Integer@0x1000, ^@0x1008, cme:0x1010) - v25:Integer = GuardType v11, Integer - v26:BasicObject = CCallWithFrame ^@0x1038, v25, v12 - CheckInterrupts - Return v26 - "); - - eval(" - def test(x, y) = x ^ y - test(1, 4 << 70) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(Integer@0x1000, ^@0x1008, cme:0x1010) - v25:Fixnum = GuardType v11, Fixnum - v26:BasicObject = CCallWithFrame ^@0x1038, v25, v12 - CheckInterrupts - Return v26 - "); - - eval(" - def test(x, y) = x ^ y - test(true, 0) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(TrueClass@0x1000, ^@0x1008, cme:0x1010) - v25:TrueClass = GuardType v11, TrueClass - v26:BasicObject = CCallWithFrame ^@0x1038, v25, v12 - CheckInterrupts - Return v26 - "); - } - - #[test] - fn test_dont_inline_integer_xor_with_args() { - eval(" - def test(x, y) = x.^() - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - v17:BasicObject = SendWithoutBlock v11, :^ - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_specialize_hash_size() { - eval(" - def test(hash) = hash.size - test({foo: 3, bar: 1, baz: 4}) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Hash@0x1000, size@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Hash@0x1000) - v25:HashExact = GuardType v9, HashExact - IncrCounter inline_cfunc_optimized_send_count - v27:Fixnum = CCall size@0x1038, v25 - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_eliminate_hash_size() { - eval(" - def test(hash) - hash.size - 5 - end - test({foo: 3, bar: 1, baz: 4}) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Hash@0x1000, size@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Hash@0x1000) - v28:HashExact = GuardType v9, HashExact - IncrCounter inline_cfunc_optimized_send_count - v19:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_optimize_respond_to_p_true() { - eval(r#" - class C - def foo; end - end - def test(o) = o.respond_to?(:foo) - test(C.new) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(C@0x1008) - v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(C@0x1008) - v28:TrueClass = Const Value(true) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_optimize_respond_to_p_false_no_method() { - eval(r#" - class C - end - def test(o) = o.respond_to?(:foo) - test(C.new) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(C@0x1008) - v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - PatchPoint MethodRedefined(C@0x1008, respond_to_missing?@0x1040, cme:0x1048) - PatchPoint MethodRedefined(C@0x1008, foo@0x1070, cme:0x1078) - PatchPoint NoSingletonClass(C@0x1008) - v30:FalseClass = Const Value(false) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v30 - "); - } - - #[test] - fn test_optimize_respond_to_p_false_default_private() { - eval(r#" - class C - private - def foo; end - end - def test(o) = o.respond_to?(:foo) - test(C.new) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:6: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(C@0x1008) - v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(C@0x1008) - v28:FalseClass = Const Value(false) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_optimize_respond_to_p_false_private() { - eval(r#" - class C - private - def foo; end - end - def test(o) = o.respond_to?(:foo, false) - test(C.new) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:6: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - v14:FalseClass = Const Value(false) - PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(C@0x1008) - v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(C@0x1008) - v29:FalseClass = Const Value(false) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v29 - "); - } - - #[test] - fn test_optimize_respond_to_p_falsy_private() { - eval(r#" - class C - private - def foo; end - end - def test(o) = o.respond_to?(:foo, nil) - test(C.new) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:6: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - v14:NilClass = Const Value(nil) - PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(C@0x1008) - v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(C@0x1008) - v29:FalseClass = Const Value(false) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v29 - "); - } - - #[test] - fn test_optimize_respond_to_p_true_private() { - eval(r#" - class C - private - def foo; end - end - def test(o) = o.respond_to?(:foo, true) - test(C.new) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:6: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - v14:TrueClass = Const Value(true) - PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(C@0x1008) - v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(C@0x1008) - v29:TrueClass = Const Value(true) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v29 - "); - } - - #[test] - fn test_optimize_respond_to_p_truthy() { - eval(r#" - class C - def foo; end - end - def test(o) = o.respond_to?(:foo, 4) - test(C.new) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - v14:Fixnum[4] = Const Value(4) - PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(C@0x1008) - v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(C@0x1008) - v29:TrueClass = Const Value(true) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v29 - "); - } - - #[test] - fn test_optimize_respond_to_p_falsy() { - eval(r#" - class C - def foo; end - end - def test(o) = o.respond_to?(:foo, nil) - test(C.new) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:5: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - v14:NilClass = Const Value(nil) - PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(C@0x1008) - v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(C@0x1008) - v29:TrueClass = Const Value(true) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v29 - "); - } - - #[test] - fn test_optimize_respond_to_missing() { - eval(r#" - class C - end - def test(o) = o.respond_to?(:foo) - test(C.new) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(C@0x1008) - v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - PatchPoint MethodRedefined(C@0x1008, respond_to_missing?@0x1040, cme:0x1048) - PatchPoint MethodRedefined(C@0x1008, foo@0x1070, cme:0x1078) - PatchPoint NoSingletonClass(C@0x1008) - v30:FalseClass = Const Value(false) - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v30 - "); - } - - #[test] - fn test_do_not_optimize_redefined_respond_to_missing() { - eval(r#" - class C - def respond_to_missing?(method, include_private = false) - true - end - end - def test(o) = o.respond_to?(:foo) - test(C.new) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:7: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) - PatchPoint NoSingletonClass(C@0x1008) - v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - v25:BasicObject = CCallVariadic respond_to?@0x1040, v24, v13 - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_inline_send_without_block_direct_putself() { - eval(r#" - def callee = self - def test = callee - test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - IncrCounter inline_iseq_optimized_send_count - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_inline_send_without_block_direct_putobject_string() { - eval(r#" - # frozen_string_literal: true - def callee = "abc" - def test = callee - test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - IncrCounter inline_iseq_optimized_send_count - v22:StringExact[VALUE(0x1038)] = Const Value(VALUE(0x1038)) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_inline_send_without_block_direct_putnil() { - eval(r#" - def callee = nil - def test = callee - test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - IncrCounter inline_iseq_optimized_send_count - v22:NilClass = Const Value(nil) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_inline_send_without_block_direct_putobject_true() { - eval(r#" - def callee = true - def test = callee - test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - IncrCounter inline_iseq_optimized_send_count - v22:TrueClass = Const Value(true) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_inline_send_without_block_direct_putobject_false() { - eval(r#" - def callee = false - def test = callee - test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - IncrCounter inline_iseq_optimized_send_count - v22:FalseClass = Const Value(false) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_inline_send_without_block_direct_putobject_zero() { - eval(r#" - def callee = 0 - def test = callee - test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - IncrCounter inline_iseq_optimized_send_count - v22:Fixnum[0] = Const Value(0) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_inline_send_without_block_direct_putobject_one() { - eval(r#" - def callee = 1 - def test = callee - test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - IncrCounter inline_iseq_optimized_send_count - v22:Fixnum[1] = Const Value(1) - CheckInterrupts - Return v22 - "); - } - - #[test] - fn test_inline_send_without_block_direct_parameter() { - eval(r#" - def callee(x) = x - def test = callee 3 - test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[3] = Const Value(3) - PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - IncrCounter inline_iseq_optimized_send_count - CheckInterrupts - Return v10 - "); - } - - #[test] - fn test_inline_send_without_block_direct_last_parameter() { - eval(r#" - def callee(x, y, z) = z - def test = callee 1, 2, 3 - test - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb2(v1) - bb1(v4:BasicObject): - EntryPoint JIT(0) - Jump bb2(v4) - bb2(v6:BasicObject): - v10:Fixnum[1] = Const Value(1) - v11:Fixnum[2] = Const Value(2) - v12:Fixnum[3] = Const Value(3) - PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(Object@0x1000) - v22:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] - IncrCounter inline_iseq_optimized_send_count - CheckInterrupts - Return v12 - "); - } - - #[test] - fn test_inline_symbol_to_sym() { - eval(r#" - def test(o) = o.to_sym - test :foo - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Symbol@0x1000, to_sym@0x1008, cme:0x1010) - v21:StaticSymbol = GuardType v9, StaticSymbol - IncrCounter inline_iseq_optimized_send_count - CheckInterrupts - Return v21 - "); - } - - #[test] - fn test_inline_kernel_frozen_p() { - eval(r#" - def test(o) = o.frozen? - test :foo - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Symbol@0x1000, frozen?@0x1008, cme:0x1010) - v21:StaticSymbol = GuardType v9, StaticSymbol - IncrCounter inline_iseq_optimized_send_count - v24:BoolExact = InvokeBuiltin leaf _bi69, v21 - CheckInterrupts - Return v24 - "); - } - - #[test] - fn test_inline_integer_to_i() { - eval(r#" - def test(o) = o.to_i - test 5 - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Integer@0x1000, to_i@0x1008, cme:0x1010) - v21:Fixnum = GuardType v9, Fixnum - IncrCounter inline_iseq_optimized_send_count - CheckInterrupts - Return v21 - "); - } - - #[test] - fn test_inline_symbol_name() { - eval(" - def test(x) = x.to_s - test(:foo) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Symbol@0x1000, to_s@0x1008, cme:0x1010) - v21:StaticSymbol = GuardType v9, StaticSymbol - IncrCounter inline_iseq_optimized_send_count - v24:StringExact = InvokeBuiltin leaf _bi12, v21 - CheckInterrupts - Return v24 - "); - } - - #[test] - fn test_inline_symbol_to_s() { - eval(" - def test(x) = x.to_s - test(:foo) - "); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(Symbol@0x1000, to_s@0x1008, cme:0x1010) - v21:StaticSymbol = GuardType v9, StaticSymbol - IncrCounter inline_iseq_optimized_send_count - v24:StringExact = InvokeBuiltin leaf _bi12, v21 - CheckInterrupts - Return v24 - "); - } - - #[test] - fn test_optimize_stringexact_eq_stringexact() { - eval(r#" - def test(l, r) = l == r - test("a", "b") - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(String@0x1000, ==@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v28:StringExact = GuardType v11, StringExact - v29:String = GuardType v12, String - v30:BoolExact = CCall String#==@0x1038, v28, v29 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v30 - "); - } - - #[test] - fn test_optimize_string_eq_string() { - eval(r#" - class C < String - end - def test(l, r) = l == r - test(C.new("a"), C.new("b")) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(C@0x1000, ==@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(C@0x1000) - v28:StringSubclass[class_exact:C] = GuardType v11, StringSubclass[class_exact:C] - v29:String = GuardType v12, String - v30:BoolExact = CCall String#==@0x1038, v28, v29 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v30 - "); - } - - #[test] - fn test_optimize_stringexact_eq_string() { - eval(r#" - class C < String - end - def test(l, r) = l == r - test("a", C.new("b")) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(String@0x1000, ==@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v28:StringExact = GuardType v11, StringExact - v29:String = GuardType v12, String - v30:BoolExact = CCall String#==@0x1038, v28, v29 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v30 - "); - } - - #[test] - fn test_optimize_stringexact_eqq_stringexact() { - eval(r#" - def test(l, r) = l === r - test("a", "b") - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:2: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(String@0x1000, ===@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v26:StringExact = GuardType v11, StringExact - v27:String = GuardType v12, String - v28:BoolExact = CCall String#==@0x1038, v26, v27 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_optimize_string_eqq_string() { - eval(r#" - class C < String - end - def test(l, r) = l === r - test(C.new("a"), C.new("b")) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(C@0x1000, ===@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(C@0x1000) - v26:StringSubclass[class_exact:C] = GuardType v11, StringSubclass[class_exact:C] - v27:String = GuardType v12, String - v28:BoolExact = CCall String#==@0x1038, v26, v27 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_optimize_stringexact_eqq_string() { - eval(r#" - class C < String - end - def test(l, r) = l === r - test("a", C.new("b")) - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:4: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@5 - v3:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2, v3) - bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): - EntryPoint JIT(0) - Jump bb2(v6, v7, v8) - bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint MethodRedefined(String@0x1000, ===@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v26:StringExact = GuardType v11, StringExact - v27:String = GuardType v12, String - v28:BoolExact = CCall String#==@0x1038, v26, v27 - IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts - Return v28 - "); - } - - #[test] - fn test_specialize_string_size() { - eval(r#" - def test(s) - s.size - end - test("asdf") - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(String@0x1000, size@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v25:StringExact = GuardType v9, StringExact - IncrCounter inline_cfunc_optimized_send_count - v27:Fixnum = CCall size@0x1038, v25 - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_elide_string_size() { - eval(r#" - def test(s) - s.size - 5 - end - test("asdf") - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(String@0x1000, size@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v28:StringExact = GuardType v9, StringExact - IncrCounter inline_cfunc_optimized_send_count - v19:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v19 - "); - } - - #[test] - fn test_specialize_string_bytesize() { - eval(r#" - def test(s) - s.bytesize - end - test("asdf") - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(String@0x1000, bytesize@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v23:StringExact = GuardType v9, StringExact - IncrCounter inline_cfunc_optimized_send_count - v25:Fixnum = CCall bytesize@0x1038, v23 - CheckInterrupts - Return v25 - "); - } - - #[test] - fn test_elide_string_bytesize() { - eval(r#" - def test(s) - s.bytesize - 5 - end - test("asdf") - "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(String@0x1000, bytesize@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v26:StringExact = GuardType v9, StringExact - IncrCounter inline_cfunc_optimized_send_count - v17:Fixnum[5] = Const Value(5) - CheckInterrupts - Return v17 - "); - } - - #[test] - fn test_specialize_string_length() { - eval(r#" - def test(s) - s.length - end - test("asdf") + test(1) + test("x") "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(String@0x1000, length@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v25:StringExact = GuardType v9, StringExact - IncrCounter inline_cfunc_optimized_send_count - v27:Fixnum = CCall length@0x1038, v25 - CheckInterrupts - Return v27 - "); - } - - #[test] - fn test_elide_string_length() { - eval(r#" - def test(s) - s.length - 4 - end - test("this should get removed") + assert_snapshot!(hir_string("test"), @r#" + digraph G { # test@<compiled>:3 + node [shape=plaintext]; + mode=hier; overlap=false; splines=true; + bb0 [label=< + + + + + +
bb0() 
EntryPoint interpreter 
v1:BasicObject = LoadSelf 
v2:BasicObject = GetLocal l0, SP@4 
Jump bb2(v1, v2) 
>]; + bb0:v3 -> bb2:params:n; + bb1 [label=< + + + +
bb1(v5:BasicObject, v6:BasicObject) 
EntryPoint JIT(0) 
Jump bb2(v5, v6) 
>]; + bb1:v7 -> bb2:params:n; + bb2 [label=< + + + + + + + + + + +
bb2(v8:BasicObject, v9:BasicObject) 
PatchPoint NoTracePoint 
CheckInterrupts 
v15:CBool = Test v9 
IfFalse v15, bb3(v8, v9) 
PatchPoint NoTracePoint 
v19:Fixnum[3] = Const Value(3) 
PatchPoint NoTracePoint 
CheckInterrupts 
Return v19 
>]; + bb2:v16 -> bb3:params:n; + bb3 [label=< + + + + + + +
bb3(v25:BasicObject, v26:BasicObject) 
PatchPoint NoTracePoint 
v30:Fixnum[4] = Const Value(4) 
PatchPoint NoTracePoint 
CheckInterrupts 
Return v30 
>]; + } "#); - assert_snapshot!(hir_string("test"), @r" - fn test@:3: - bb0(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - v2:BasicObject = GetLocal l0, SP@4 - Jump bb2(v1, v2) - bb1(v5:BasicObject, v6:BasicObject): - EntryPoint JIT(0) - Jump bb2(v5, v6) - bb2(v8:BasicObject, v9:BasicObject): - PatchPoint MethodRedefined(String@0x1000, length@0x1008, cme:0x1010) - PatchPoint NoSingletonClass(String@0x1000) - v28:StringExact = GuardType v9, StringExact - IncrCounter inline_cfunc_optimized_send_count - v19:Fixnum[4] = Const Value(4) - CheckInterrupts - Return v19 - "); } } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs new file mode 100644 index 00000000000000..897cd6e9566966 --- /dev/null +++ b/zjit/src/hir/opt_tests.rs @@ -0,0 +1,7050 @@ +#[cfg(test)] +mod hir_opt_tests { + use crate::hir::*; + + use crate::{hir_strings, options::*}; + use insta::assert_snapshot; + use crate::hir::tests::hir_build_tests::assert_contains_opcode; + + #[track_caller] + fn hir_string_function(function: &Function) -> String { + format!("{}", FunctionPrinter::without_snapshot(function)) + } + + #[track_caller] + fn hir_string_proc(proc: &str) -> String { + let iseq = crate::cruby::with_rubyvm(|| get_proc_iseq(proc)); + unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; + let mut function = iseq_to_hir(iseq).unwrap(); + function.optimize(); + function.validate().unwrap(); + hir_string_function(&function) + } + + #[track_caller] + fn hir_string(method: &str) -> String { + hir_string_proc(&format!("{}.method(:{})", "self", method)) + } + + #[test] + fn test_fold_iftrue_away() { + eval(" + def test + cond = true + if cond + 3 + else + 4 + end + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:TrueClass = Const Value(true) + CheckInterrupts + v22:Fixnum[3] = Const Value(3) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_fold_iftrue_into_jump() { + eval(" + def test + cond = false + if cond + 3 + else + 4 + end + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:FalseClass = Const Value(false) + CheckInterrupts + v33:Fixnum[4] = Const Value(4) + CheckInterrupts + Return v33 + "); + } + + #[test] + fn test_fold_fixnum_add() { + eval(" + def test + 1 + 2 + 3 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v11:Fixnum[2] = Const Value(2) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) + v30:Fixnum[3] = Const Value(3) + v16:Fixnum[3] = Const Value(3) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) + v31:Fixnum[6] = Const Value(6) + CheckInterrupts + Return v31 + "); + } + + #[test] + fn test_fold_fixnum_sub() { + eval(" + def test + 5 - 3 - 1 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[5] = Const Value(5) + v11:Fixnum[3] = Const Value(3) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MINUS) + v30:Fixnum[2] = Const Value(2) + v16:Fixnum[1] = Const Value(1) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MINUS) + v31:Fixnum[1] = Const Value(1) + CheckInterrupts + Return v31 + "); + } + + #[test] + fn test_fold_fixnum_sub_large_negative_result() { + eval(" + def test + 0 - 1073741825 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[0] = Const Value(0) + v11:Fixnum[1073741825] = Const Value(1073741825) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MINUS) + v23:Fixnum[-1073741825] = Const Value(-1073741825) + CheckInterrupts + Return v23 + "); + } + + #[test] + fn test_fold_fixnum_mult() { + eval(" + def test + 6 * 7 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[6] = Const Value(6) + v11:Fixnum[7] = Const Value(7) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MULT) + v23:Fixnum[42] = Const Value(42) + CheckInterrupts + Return v23 + "); + } + + #[test] + fn test_fold_fixnum_mult_zero() { + eval(" + def test(n) + 0 * n + n * 0 + end + test 1; test 2 + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[0] = Const Value(0) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MULT) + v33:Fixnum = GuardType v9, Fixnum + v40:Fixnum[0] = Const Value(0) + v18:Fixnum[0] = Const Value(0) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MULT) + v36:Fixnum = GuardType v9, Fixnum + v41:Fixnum[0] = Const Value(0) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) + v42:Fixnum[0] = Const Value(0) + CheckInterrupts + Return v42 + "); + } + + #[test] + fn test_fold_fixnum_less() { + eval(" + def test + if 1 < 2 + 3 + else + 4 + end + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v11:Fixnum[2] = Const Value(2) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT) + v40:TrueClass = Const Value(true) + CheckInterrupts + v22:Fixnum[3] = Const Value(3) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_fold_fixnum_less_equal() { + eval(" + def test + if 1 <= 2 && 2 <= 2 + 3 + else + 4 + end + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v11:Fixnum[2] = Const Value(2) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LE) + v52:TrueClass = Const Value(true) + CheckInterrupts + v20:Fixnum[2] = Const Value(2) + v21:Fixnum[2] = Const Value(2) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LE) + v54:TrueClass = Const Value(true) + CheckInterrupts + v32:Fixnum[3] = Const Value(3) + CheckInterrupts + Return v32 + "); + } + + #[test] + fn test_fold_fixnum_greater() { + eval(" + def test + if 2 > 1 + 3 + else + 4 + end + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[2] = Const Value(2) + v11:Fixnum[1] = Const Value(1) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GT) + v40:TrueClass = Const Value(true) + CheckInterrupts + v22:Fixnum[3] = Const Value(3) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_fold_fixnum_greater_equal() { + eval(" + def test + if 2 >= 1 && 2 >= 2 + 3 + else + 4 + end + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[2] = Const Value(2) + v11:Fixnum[1] = Const Value(1) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GE) + v52:TrueClass = Const Value(true) + CheckInterrupts + v20:Fixnum[2] = Const Value(2) + v21:Fixnum[2] = Const Value(2) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GE) + v54:TrueClass = Const Value(true) + CheckInterrupts + v32:Fixnum[3] = Const Value(3) + CheckInterrupts + Return v32 + "); + } + + #[test] + fn test_fold_fixnum_eq_false() { + eval(" + def test + if 1 == 2 + 3 + else + 4 + end + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v11:Fixnum[2] = Const Value(2) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) + v40:FalseClass = Const Value(false) + CheckInterrupts + v32:Fixnum[4] = Const Value(4) + CheckInterrupts + Return v32 + "); + } + + #[test] + fn test_fold_fixnum_eq_true() { + eval(" + def test + if 2 == 2 + 3 + else + 4 + end + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[2] = Const Value(2) + v11:Fixnum[2] = Const Value(2) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) + v40:TrueClass = Const Value(true) + CheckInterrupts + v22:Fixnum[3] = Const Value(3) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_fold_fixnum_neq_true() { + eval(" + def test + if 1 != 2 + 3 + else + 4 + end + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v11:Fixnum[2] = Const Value(2) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_NEQ) + v41:TrueClass = Const Value(true) + CheckInterrupts + v22:Fixnum[3] = Const Value(3) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_fold_fixnum_neq_false() { + eval(" + def test + if 2 != 2 + 3 + else + 4 + end + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[2] = Const Value(2) + v11:Fixnum[2] = Const Value(2) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_NEQ) + v41:FalseClass = Const Value(false) + CheckInterrupts + v32:Fixnum[4] = Const Value(4) + CheckInterrupts + Return v32 + "); + } + + #[test] + fn test_replace_guard_if_known_fixnum() { + eval(" + def test(a) + a + 1 + end + test(2); test(3) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) + v24:Fixnum = GuardType v9, Fixnum + v25:Fixnum = FixnumAdd v24, v13 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_param_forms_get_bb_param() { + eval(" + def rest(*array) = array + def kw(k:) = k + def kw_rest(**k) = k + def post(*rest, post) = post + def block(&b) = nil + "); + assert_snapshot!(hir_strings!("rest", "kw", "kw_rest", "block", "post"), @r" + fn rest@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:ArrayExact = GetLocal l0, SP@4, * + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:ArrayExact): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:ArrayExact): + CheckInterrupts + Return v9 + + fn kw@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + CheckInterrupts + Return v11 + + fn kw_rest@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + CheckInterrupts + Return v9 + + fn block@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:NilClass = Const Value(nil) + CheckInterrupts + Return v13 + + fn post@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:ArrayExact = GetLocal l0, SP@5, * + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:ArrayExact, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:ArrayExact, v12:BasicObject): + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_optimize_top_level_call_into_send_direct() { + eval(" + def foo = [] + def test + foo + end + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v20:BasicObject = SendWithoutBlockDirect v19, :foo (0x1038) + CheckInterrupts + Return v20 + "); + } + + #[test] + fn test_optimize_nonexistent_top_level_call() { + eval(" + def foo + end + def test + foo + end + test; test + undef :foo + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = SendWithoutBlock v6, :foo + CheckInterrupts + Return v11 + "); + } + + #[test] + fn test_optimize_private_top_level_call() { + eval(" + def foo = [] + private :foo + def test + foo + end + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v20:BasicObject = SendWithoutBlockDirect v19, :foo (0x1038) + CheckInterrupts + Return v20 + "); + } + + #[test] + fn test_optimize_top_level_call_with_overloaded_cme() { + eval(" + def test + Integer(3) + end + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[3] = Const Value(3) + PatchPoint MethodRedefined(Object@0x1000, Integer@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v21:BasicObject = SendWithoutBlockDirect v20, :Integer (0x1038), v10 + CheckInterrupts + Return v21 + "); + } + + #[test] + fn test_optimize_top_level_call_with_args_into_send_direct() { + eval(" + def foo(a, b) = [] + def test + foo 1, 2 + end + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v11:Fixnum[2] = Const Value(2) + PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v21:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v22:BasicObject = SendWithoutBlockDirect v21, :foo (0x1038), v10, v11 + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_optimize_top_level_sends_into_send_direct() { + eval(" + def foo = [] + def bar = [] + def test + foo + bar + end + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v23:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v24:BasicObject = SendWithoutBlockDirect v23, :foo (0x1038) + PatchPoint MethodRedefined(Object@0x1000, bar@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(Object@0x1000) + v27:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v28:BasicObject = SendWithoutBlockDirect v27, :bar (0x1038) + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_optimize_variadic_ccall() { + eval(" + def test + puts 'Hello' + end + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:StringExact = StringCopy v10 + PatchPoint MethodRedefined(Object@0x1008, puts@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Object@0x1008) + v23:HeapObject[class_exact*:Object@VALUE(0x1008)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1008)] + v24:BasicObject = CCallVariadic puts@0x1040, v23, v12 + CheckInterrupts + Return v24 + "); + } + + #[test] + fn test_dont_optimize_fixnum_add_if_redefined() { + eval(" + class Integer + def +(other) + 100 + end + end + def test(a, b) = a + b + test(1,2); test(3,4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:7: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :+, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_optimize_send_into_fixnum_add_both_profiled() { + eval(" + def test(a, b) = a + b + test(1,2); test(3,4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) + v26:Fixnum = GuardType v11, Fixnum + v27:Fixnum = GuardType v12, Fixnum + v28:Fixnum = FixnumAdd v26, v27 + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_optimize_send_into_fixnum_add_left_profiled() { + eval(" + def test(a) = a + 1 + test(1); test(3) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) + v24:Fixnum = GuardType v9, Fixnum + v25:Fixnum = FixnumAdd v24, v13 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_optimize_send_into_fixnum_add_right_profiled() { + eval(" + def test(a) = 1 + a + test(1); test(3) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) + v24:Fixnum = GuardType v9, Fixnum + v25:Fixnum = FixnumAdd v13, v24 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_optimize_send_into_fixnum_lt_both_profiled() { + eval(" + def test(a, b) = a < b + test(1,2); test(3,4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT) + v26:Fixnum = GuardType v11, Fixnum + v27:Fixnum = GuardType v12, Fixnum + v28:BoolExact = FixnumLt v26, v27 + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_optimize_send_into_fixnum_lt_left_profiled() { + eval(" + def test(a) = a < 1 + test(1); test(3) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT) + v24:Fixnum = GuardType v9, Fixnum + v25:BoolExact = FixnumLt v24, v13 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_optimize_send_into_fixnum_lt_right_profiled() { + eval(" + def test(a) = 1 < a + test(1); test(3) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT) + v24:Fixnum = GuardType v9, Fixnum + v25:BoolExact = FixnumLt v13, v24 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_optimize_new_range_fixnum_inclusive_literals() { + eval(" + def test() + a = 2 + (1..a) + end + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:Fixnum[2] = Const Value(2) + v16:Fixnum[1] = Const Value(1) + v24:RangeExact = NewRangeFixnum v16 NewRangeInclusive v13 + CheckInterrupts + Return v24 + "); + } + + + #[test] + fn test_optimize_new_range_fixnum_exclusive_literals() { + eval(" + def test() + a = 2 + (1...a) + end + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:Fixnum[2] = Const Value(2) + v16:Fixnum[1] = Const Value(1) + v24:RangeExact = NewRangeFixnum v16 NewRangeExclusive v13 + CheckInterrupts + Return v24 + "); + } + + #[test] + fn test_optimize_new_range_fixnum_inclusive_high_guarded() { + eval(" + def test(a) + (1..a) + end + test(2); test(3) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + v21:Fixnum = GuardType v9, Fixnum + v22:RangeExact = NewRangeFixnum v13 NewRangeInclusive v21 + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_optimize_new_range_fixnum_exclusive_high_guarded() { + eval(" + def test(a) + (1...a) + end + test(2); test(3) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + v21:Fixnum = GuardType v9, Fixnum + v22:RangeExact = NewRangeFixnum v13 NewRangeExclusive v21 + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_optimize_new_range_fixnum_inclusive_low_guarded() { + eval(" + def test(a) + (a..10) + end + test(2); test(3) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[10] = Const Value(10) + v21:Fixnum = GuardType v9, Fixnum + v22:RangeExact = NewRangeFixnum v21 NewRangeInclusive v13 + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_optimize_new_range_fixnum_exclusive_low_guarded() { + eval(" + def test(a) + (a...10) + end + test(2); test(3) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[10] = Const Value(10) + v21:Fixnum = GuardType v9, Fixnum + v22:RangeExact = NewRangeFixnum v21 NewRangeExclusive v13 + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_eliminate_new_array() { + eval(" + def test() + c = [] + 5 + end + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v14:ArrayExact = NewArray + v17:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_opt_aref_array() { + eval(" + arr = [1,2,3] + def test(arr) = arr[0] + test(arr) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[0] = Const Value(0) + PatchPoint MethodRedefined(Array@0x1000, []@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v26:ArrayExact = GuardType v9, ArrayExact + v27:BasicObject = ArrayArefFixnum v26, v13 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v27 + "); + assert_snapshot!(inspect("test [1,2,3]"), @"1"); + } + + #[test] + fn test_opt_aref_hash() { + eval(" + arr = {0 => 4} + def test(arr) = arr[0] + test(arr) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[0] = Const Value(0) + PatchPoint MethodRedefined(Hash@0x1000, []@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Hash@0x1000) + v26:HashExact = GuardType v9, HashExact + v27:BasicObject = HashAref v26, v13 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v27 + "); + assert_snapshot!(inspect("test({0 => 4})"), @"4"); + } + + #[test] + fn test_eliminate_new_range() { + eval(" + def test() + c = (1..2) + 5 + end + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:RangeExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v16:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v16 + "); + } + + #[test] + fn test_do_not_eliminate_new_range_non_fixnum() { + eval(" + def test() + _ = (-'a'..'b') + 0 + end + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_UMINUS) + v15:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v16:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v18:StringExact = StringCopy v16 + v20:RangeExact = NewRange v15 NewRangeInclusive v18 + PatchPoint NoEPEscape(test) + v25:Fixnum[0] = Const Value(0) + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_eliminate_new_array_with_elements() { + eval(" + def test(a) + c = [a] + 5 + end + test(1); test(2) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject): + EntryPoint JIT(0) + v8:NilClass = Const Value(nil) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:NilClass): + v17:ArrayExact = NewArray v11 + v20:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v20 + "); + } + + #[test] + fn test_eliminate_new_hash() { + eval(" + def test() + c = {} + 5 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v14:HashExact = NewHash + PatchPoint NoEPEscape(test) + v19:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_no_eliminate_new_hash_with_elements() { + eval(" + def test(aval, bval) + c = {a: aval, b: bval} + 5 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@6 + v3:BasicObject = GetLocal l0, SP@5 + v4:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4) + bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject): + EntryPoint JIT(0) + v10:NilClass = Const Value(nil) + Jump bb2(v7, v8, v9, v10) + bb2(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:NilClass): + v19:StaticSymbol[:a] = Const Value(VALUE(0x1000)) + v20:StaticSymbol[:b] = Const Value(VALUE(0x1008)) + v22:HashExact = NewHash v19: v13, v20: v14 + PatchPoint NoEPEscape(test) + v27:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_eliminate_array_dup() { + eval(" + def test + c = [1, 2] + 5 + end + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v15:ArrayExact = ArrayDup v13 + v18:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v18 + "); + } + + #[test] + fn test_eliminate_hash_dup() { + eval(" + def test + c = {a: 1, b: 2} + 5 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v15:HashExact = HashDup v13 + v18:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v18 + "); + } + + #[test] + fn test_eliminate_putself() { + eval(" + def test() + c = self + 5 + end + test; test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v15:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v15 + "); + } + + #[test] + fn test_eliminate_string_copy() { + eval(r#" + def test() + c = "abc" + 5 + end + test; test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v15:StringExact = StringCopy v13 + v18:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v18 + "); + } + + #[test] + fn test_eliminate_fixnum_add() { + eval(" + def test(a, b) + a + b + 5 + end + test(1, 2); test(3, 4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_PLUS) + v29:Fixnum = GuardType v11, Fixnum + v30:Fixnum = GuardType v12, Fixnum + v22:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_eliminate_fixnum_sub() { + eval(" + def test(a, b) + a - b + 5 + end + test(1, 2); test(3, 4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MINUS) + v29:Fixnum = GuardType v11, Fixnum + v30:Fixnum = GuardType v12, Fixnum + v22:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_eliminate_fixnum_mul() { + eval(" + def test(a, b) + a * b + 5 + end + test(1, 2); test(3, 4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MULT) + v29:Fixnum = GuardType v11, Fixnum + v30:Fixnum = GuardType v12, Fixnum + v22:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_do_not_eliminate_fixnum_div() { + eval(" + def test(a, b) + a / b + 5 + end + test(1, 2); test(3, 4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_DIV) + v29:Fixnum = GuardType v11, Fixnum + v30:Fixnum = GuardType v12, Fixnum + v31:Fixnum = FixnumDiv v29, v30 + v22:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_do_not_eliminate_fixnum_mod() { + eval(" + def test(a, b) + a % b + 5 + end + test(1, 2); test(3, 4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_MOD) + v29:Fixnum = GuardType v11, Fixnum + v30:Fixnum = GuardType v12, Fixnum + v31:Fixnum = FixnumMod v29, v30 + v22:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_eliminate_fixnum_lt() { + eval(" + def test(a, b) + a < b + 5 + end + test(1, 2); test(3, 4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LT) + v29:Fixnum = GuardType v11, Fixnum + v30:Fixnum = GuardType v12, Fixnum + v22:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_eliminate_fixnum_le() { + eval(" + def test(a, b) + a <= b + 5 + end + test(1, 2); test(3, 4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_LE) + v29:Fixnum = GuardType v11, Fixnum + v30:Fixnum = GuardType v12, Fixnum + v22:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_eliminate_fixnum_gt() { + eval(" + def test(a, b) + a > b + 5 + end + test(1, 2); test(3, 4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GT) + v29:Fixnum = GuardType v11, Fixnum + v30:Fixnum = GuardType v12, Fixnum + v22:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_eliminate_fixnum_ge() { + eval(" + def test(a, b) + a >= b + 5 + end + test(1, 2); test(3, 4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_GE) + v29:Fixnum = GuardType v11, Fixnum + v30:Fixnum = GuardType v12, Fixnum + v22:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_eliminate_fixnum_eq() { + eval(" + def test(a, b) + a == b + 5 + end + test(1, 2); test(3, 4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) + v29:Fixnum = GuardType v11, Fixnum + v30:Fixnum = GuardType v12, Fixnum + v22:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_eliminate_fixnum_neq() { + eval(" + def test(a, b) + a != b + 5 + end + test(1, 2); test(3, 4) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ) + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_NEQ) + v30:Fixnum = GuardType v11, Fixnum + v31:Fixnum = GuardType v12, Fixnum + v22:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_do_not_eliminate_get_constant_path() { + eval(" + def test() + C + 5 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = GetConstantPath 0x1000 + v14:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v14 + "); + } + + #[test] + fn kernel_itself_const() { + eval(" + def test(x) = x.itself + test(0) # profile + test(1) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, itself@0x1008, cme:0x1010) + v22:Fixnum = GuardType v9, Fixnum + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v22 + "); + } + + #[test] + fn kernel_itself_known_type() { + eval(" + def test = [].itself + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:ArrayExact = NewArray + PatchPoint MethodRedefined(Array@0x1000, itself@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v11 + "); + } + + #[test] + fn eliminate_kernel_itself() { + eval(" + def test + x = [].itself + 1 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v14:ArrayExact = NewArray + PatchPoint MethodRedefined(Array@0x1000, itself@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + IncrCounter inline_cfunc_optimized_send_count + PatchPoint NoEPEscape(test) + v21:Fixnum[1] = Const Value(1) + CheckInterrupts + Return v21 + "); + } + + #[test] + fn eliminate_module_name() { + eval(" + module M; end + def test + x = M.name + 1 + end + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, M) + v29:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(Module@0x1010, name@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(Module@0x1010) + IncrCounter inline_cfunc_optimized_send_count + v34:StringExact|NilClass = CCall name@0x1048, v29 + PatchPoint NoEPEscape(test) + v21:Fixnum[1] = Const Value(1) + CheckInterrupts + Return v21 + "); + } + + #[test] + fn eliminate_array_length() { + eval(" + def test + x = [].length + 5 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v14:ArrayExact = NewArray + PatchPoint MethodRedefined(Array@0x1000, length@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + IncrCounter inline_cfunc_optimized_send_count + v31:Fixnum = CCall length@0x1038, v14 + v21:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v21 + "); + } + + #[test] + fn normal_class_type_inference() { + eval(" + class C; end + def test = C + test # Warm the constant cache + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, C) + v19:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + CheckInterrupts + Return v19 + "); + } + + #[test] + fn core_classes_type_inference() { + eval(" + def test = [String, Class, Module, BasicObject] + test # Warm the constant cache + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, String) + v27:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1010, Class) + v30:Class[VALUE(0x1018)] = Const Value(VALUE(0x1018)) + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1020, Module) + v33:Class[VALUE(0x1028)] = Const Value(VALUE(0x1028)) + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1030, BasicObject) + v36:Class[VALUE(0x1038)] = Const Value(VALUE(0x1038)) + v19:ArrayExact = NewArray v27, v30, v33, v36 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn module_instances_are_module_exact() { + eval(" + def test = [Enumerable, Kernel] + test # Warm the constant cache + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Enumerable) + v23:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1010, Kernel) + v26:ModuleExact[VALUE(0x1018)] = Const Value(VALUE(0x1018)) + v15:ArrayExact = NewArray v23, v26 + CheckInterrupts + Return v15 + "); + } + + #[test] + fn module_subclasses_are_not_module_exact() { + eval(" + class ModuleSubclass < Module; end + MY_MODULE = ModuleSubclass.new + def test = MY_MODULE + test # Warm the constant cache + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, MY_MODULE) + v19:ModuleSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + CheckInterrupts + Return v19 + "); + } + + #[test] + fn eliminate_array_size() { + eval(" + def test + x = [].size + 5 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v14:ArrayExact = NewArray + PatchPoint MethodRedefined(Array@0x1000, size@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + IncrCounter inline_cfunc_optimized_send_count + v31:Fixnum = CCall size@0x1038, v14 + v21:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v21 + "); + } + + #[test] + fn kernel_itself_argc_mismatch() { + eval(" + def test = 1.itself(0) + test rescue 0 + test rescue 0 + "); + // Not specialized + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v11:Fixnum[0] = Const Value(0) + v13:BasicObject = SendWithoutBlock v10, :itself, v11 + CheckInterrupts + Return v13 + "); + } + + #[test] + fn test_inline_kernel_block_given_p() { + eval(" + def test = block_given? + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, block_given?@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v21:BoolExact = IsBlockGiven + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v21 + "); + } + + #[test] + fn test_inline_kernel_block_given_p_in_block() { + eval(" + TEST = proc { block_given? } + TEST.call + "); + assert_snapshot!(hir_string_proc("TEST"), @r" + fn block in @:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, block_given?@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v21:BoolExact = IsBlockGiven + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v21 + "); + } + + #[test] + fn test_elide_kernel_block_given_p() { + eval(" + def test + block_given? + 5 + end + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, block_given?@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v23:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_cfunc_optimized_send_count + v14:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v14 + "); + } + + #[test] + fn const_send_direct_integer() { + eval(" + def test(x) = 1.zero? + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Integer@0x1000, zero?@0x1008, cme:0x1010) + IncrCounter inline_iseq_optimized_send_count + v24:BasicObject = InvokeBuiltin leaf _bi285, v13 + CheckInterrupts + Return v24 + "); + } + + #[test] + fn class_known_send_direct_array() { + eval(" + def test(x) + a = [1,2,3] + a.first + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject): + EntryPoint JIT(0) + v8:NilClass = Const Value(nil) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:NilClass): + v16:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v18:ArrayExact = ArrayDup v16 + PatchPoint MethodRedefined(Array@0x1008, first@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + IncrCounter inline_iseq_optimized_send_count + v32:BasicObject = InvokeBuiltin leaf _bi132, v18 + CheckInterrupts + Return v32 + "); + } + + #[test] + fn send_direct_to_module() { + eval(" + module M; end + def test = M.class + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, M) + v21:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(Module@0x1010, class@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(Module@0x1010) + IncrCounter inline_iseq_optimized_send_count + v26:Class = InvokeBuiltin leaf _bi20, v21 + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_send_direct_to_instance_method() { + eval(" + class C + def foo = [] + end + + def test(c) = c.foo + c = C.new + test c + test c + "); + + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + v23:BasicObject = SendWithoutBlockDirect v22, :foo (0x1038) + CheckInterrupts + Return v23 + "); + } + + #[test] + fn dont_specialize_call_to_iseq_with_opt() { + eval(" + def foo(arg=1) = 1 + def test = foo 1 + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v12:BasicObject = SendWithoutBlock v6, :foo, v10 + CheckInterrupts + Return v12 + "); + } + + #[test] + fn dont_specialize_call_to_iseq_with_block() { + eval(" + def foo(&block) = 1 + def test = foo {|| } + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = Send v6, 0x1000, :foo + CheckInterrupts + Return v11 + "); + } + + #[test] + fn reload_local_across_send() { + eval(" + def foo(&block) = 1 + def test + a = 1 + foo {|| } + a + end + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:Fixnum[1] = Const Value(1) + SetLocal l0, EP@3, v13 + v18:BasicObject = Send v8, 0x1000, :foo + v19:BasicObject = GetLocal l0, EP@3 + v22:BasicObject = GetLocal l0, EP@3 + CheckInterrupts + Return v22 + "); + } + + #[test] + fn dont_specialize_call_to_iseq_with_rest() { + eval(" + def foo(*args) = 1 + def test = foo 1 + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v12:BasicObject = SendWithoutBlock v6, :foo, v10 + CheckInterrupts + Return v12 + "); + } + + #[test] + fn dont_specialize_call_to_iseq_with_kw() { + eval(" + def foo(a:) = 1 + def test = foo(a: 1) + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + SideExit UnhandledCallType(Kwarg) + "); + } + + #[test] + fn dont_specialize_call_to_iseq_with_kwrest() { + eval(" + def foo(**args) = 1 + def test = foo(a: 1) + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + SideExit UnhandledCallType(Kwarg) + "); + } + + #[test] + fn string_bytesize_simple() { + eval(" + def test = 'abc'.bytesize + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:StringExact = StringCopy v10 + PatchPoint MethodRedefined(String@0x1008, bytesize@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(String@0x1008) + IncrCounter inline_cfunc_optimized_send_count + v24:Fixnum = CCall bytesize@0x1040, v12 + CheckInterrupts + Return v24 + "); + } + + #[test] + fn dont_replace_get_constant_path_with_empty_ic() { + eval(" + def test = Kernel + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = GetConstantPath 0x1000 + CheckInterrupts + Return v11 + "); + } + + #[test] + fn dont_replace_get_constant_path_with_invalidated_ic() { + eval(" + def test = Kernel + test + Kernel = 5 + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = GetConstantPath 0x1000 + CheckInterrupts + Return v11 + "); + } + + #[test] + fn replace_get_constant_path_with_const() { + eval(" + def test = Kernel + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Kernel) + v19:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + CheckInterrupts + Return v19 + "); + } + + #[test] + fn replace_nested_get_constant_path_with_const() { + eval(" + module Foo + module Bar + class C + end + end + end + def test = Foo::Bar::C + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:8: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Foo::Bar::C) + v19:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_opt_new_no_initialize() { + eval(" + class C; end + def test = C.new + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, C) + v40:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) + PatchPoint MethodRedefined(C@0x1008, new@0x1010, cme:0x1018) + v43:HeapObject[class_exact:C] = ObjectAllocClass C:VALUE(0x1008) + PatchPoint MethodRedefined(C@0x1008, initialize@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v47:NilClass = Const Value(nil) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + CheckInterrupts + Return v43 + "); + } + + #[test] + fn test_opt_new_initialize() { + eval(" + class C + def initialize x + @x = x + end + end + def test = C.new 1 + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:7: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, C) + v42:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) + v13:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(C@0x1008, new@0x1010, cme:0x1018) + v45:HeapObject[class_exact:C] = ObjectAllocClass C:VALUE(0x1008) + PatchPoint MethodRedefined(C@0x1008, initialize@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v48:BasicObject = SendWithoutBlockDirect v45, :initialize (0x1070), v13 + CheckInterrupts + CheckInterrupts + Return v45 + "); + } + + #[test] + fn test_opt_new_object() { + eval(" + def test = Object.new + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Object) + v40:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) + PatchPoint MethodRedefined(Object@0x1008, new@0x1010, cme:0x1018) + v43:ObjectExact = ObjectAllocClass Object:VALUE(0x1008) + PatchPoint MethodRedefined(Object@0x1008, initialize@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(Object@0x1008) + v47:NilClass = Const Value(nil) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + CheckInterrupts + Return v43 + "); + } + + #[test] + fn test_opt_new_basic_object() { + eval(" + def test = BasicObject.new + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, BasicObject) + v40:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) + PatchPoint MethodRedefined(BasicObject@0x1008, new@0x1010, cme:0x1018) + v43:BasicObjectExact = ObjectAllocClass BasicObject:VALUE(0x1008) + PatchPoint MethodRedefined(BasicObject@0x1008, initialize@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(BasicObject@0x1008) + v47:NilClass = Const Value(nil) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + CheckInterrupts + Return v43 + "); + } + + #[test] + fn test_opt_new_hash() { + eval(" + def test = Hash.new + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Hash) + v40:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) + PatchPoint MethodRedefined(Hash@0x1008, new@0x1010, cme:0x1018) + v43:HashExact = ObjectAllocClass Hash:VALUE(0x1008) + v18:BasicObject = SendWithoutBlock v43, :initialize + CheckInterrupts + CheckInterrupts + Return v43 + "); + assert_snapshot!(inspect("test"), @"{}"); + } + + #[test] + fn test_opt_new_array() { + eval(" + def test = Array.new 1 + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Array) + v42:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) + v13:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Array@0x1008, new@0x1010, cme:0x1018) + PatchPoint MethodRedefined(Class@0x1040, new@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Class@0x1040) + v53:BasicObject = CCallVariadic new@0x1048, v42, v13 + CheckInterrupts + Return v53 + "); + } + + #[test] + fn test_opt_new_set() { + eval(" + def test = Set.new + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Set) + v40:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) + PatchPoint MethodRedefined(Set@0x1008, new@0x1010, cme:0x1018) + v16:HeapBasicObject = ObjectAlloc v40 + PatchPoint MethodRedefined(Set@0x1008, initialize@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(Set@0x1008) + v46:SetExact = GuardType v16, SetExact + v47:BasicObject = CCallVariadic initialize@0x1070, v46 + CheckInterrupts + CheckInterrupts + Return v16 + "); + } + + #[test] + fn test_opt_new_string() { + eval(" + def test = String.new + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, String) + v40:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) + PatchPoint MethodRedefined(String@0x1008, new@0x1010, cme:0x1018) + PatchPoint MethodRedefined(Class@0x1040, new@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Class@0x1040) + v51:BasicObject = CCallVariadic new@0x1048, v40 + CheckInterrupts + Return v51 + "); + } + + #[test] + fn test_opt_new_regexp() { + eval(" + def test = Regexp.new '' + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Regexp) + v44:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) + v13:StringExact[VALUE(0x1010)] = Const Value(VALUE(0x1010)) + v15:StringExact = StringCopy v13 + PatchPoint MethodRedefined(Regexp@0x1008, new@0x1018, cme:0x1020) + v47:RegexpExact = ObjectAllocClass Regexp:VALUE(0x1008) + PatchPoint MethodRedefined(Regexp@0x1008, initialize@0x1048, cme:0x1050) + PatchPoint NoSingletonClass(Regexp@0x1008) + v51:BasicObject = CCallVariadic initialize@0x1078, v47, v15 + CheckInterrupts + CheckInterrupts + Return v47 + "); + } + + #[test] + fn test_opt_length() { + eval(" + def test(a,b) = [a,b].length + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v17:ArrayExact = NewArray v11, v12 + PatchPoint MethodRedefined(Array@0x1000, length@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + IncrCounter inline_cfunc_optimized_send_count + v31:Fixnum = CCall length@0x1038, v17 + CheckInterrupts + Return v31 + "); + } + + #[test] + fn test_opt_size() { + eval(" + def test(a,b) = [a,b].size + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v17:ArrayExact = NewArray v11, v12 + PatchPoint MethodRedefined(Array@0x1000, size@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + IncrCounter inline_cfunc_optimized_send_count + v31:Fixnum = CCall size@0x1038, v17 + CheckInterrupts + Return v31 + "); + } + + #[test] + fn test_getblockparamproxy() { + eval(" + def test(&block) = tap(&block) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + GuardBlockParamProxy l0 + v15:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1000)) + v17:BasicObject = Send v8, 0x1008, :tap, v15 + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_getinstancevariable() { + eval(" + def test = @foo + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + v12:BasicObject = GetIvar v6, :@foo + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_setinstancevariable() { + eval(" + def test = @foo = 1 + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + PatchPoint SingleRactorMode + SetIvar v6, :@foo, v10 + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_elide_freeze_with_frozen_hash() { + eval(" + def test = {}.freeze + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_dont_optimize_hash_freeze_if_redefined() { + eval(" + class Hash + def freeze; end + end + def test = {}.freeze + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + SideExit PatchPoint(BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE)) + "); + } + + #[test] + fn test_elide_freeze_with_refrozen_hash() { + eval(" + def test = {}.freeze.freeze + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + PatchPoint BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE) + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_no_elide_freeze_with_unfrozen_hash() { + eval(" + def test = {}.dup.freeze + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:HashExact = NewHash + PatchPoint MethodRedefined(Hash@0x1000, dup@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Hash@0x1000) + v24:BasicObject = CCallWithFrame dup@0x1038, v11 + v15:BasicObject = SendWithoutBlock v24, :freeze + CheckInterrupts + Return v15 + "); + } + + #[test] + fn test_no_elide_freeze_hash_with_args() { + eval(" + def test = {}.freeze(nil) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:HashExact = NewHash + v12:NilClass = Const Value(nil) + v14:BasicObject = SendWithoutBlock v11, :freeze, v12 + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_elide_freeze_with_frozen_ary() { + eval(" + def test = [].freeze + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_elide_freeze_with_refrozen_ary() { + eval(" + def test = [].freeze.freeze + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_no_elide_freeze_with_unfrozen_ary() { + eval(" + def test = [].dup.freeze + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:ArrayExact = NewArray + PatchPoint MethodRedefined(Array@0x1000, dup@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v24:BasicObject = CCallWithFrame dup@0x1038, v11 + v15:BasicObject = SendWithoutBlock v24, :freeze + CheckInterrupts + Return v15 + "); + } + + #[test] + fn test_no_elide_freeze_ary_with_args() { + eval(" + def test = [].freeze(nil) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:ArrayExact = NewArray + v12:NilClass = Const Value(nil) + v14:BasicObject = SendWithoutBlock v11, :freeze, v12 + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_elide_freeze_with_frozen_str() { + eval(" + def test = ''.freeze + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_elide_freeze_with_refrozen_str() { + eval(" + def test = ''.freeze.freeze + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE) + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_no_elide_freeze_with_unfrozen_str() { + eval(" + def test = ''.dup.freeze + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:StringExact = StringCopy v10 + PatchPoint MethodRedefined(String@0x1008, dup@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(String@0x1008) + v25:BasicObject = CCallWithFrame dup@0x1040, v12 + v16:BasicObject = SendWithoutBlock v25, :freeze + CheckInterrupts + Return v16 + "); + } + + #[test] + fn test_no_elide_freeze_str_with_args() { + eval(" + def test = ''.freeze(nil) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:StringExact = StringCopy v10 + v13:NilClass = Const Value(nil) + v15:BasicObject = SendWithoutBlock v12, :freeze, v13 + CheckInterrupts + Return v15 + "); + } + + #[test] + fn test_elide_uminus_with_frozen_str() { + eval(" + def test = -'' + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_UMINUS) + v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_elide_uminus_with_refrozen_str() { + eval(" + def test = -''.freeze + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_UMINUS) + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_no_elide_uminus_with_unfrozen_str() { + eval(" + def test = -''.dup + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:StringExact = StringCopy v10 + PatchPoint MethodRedefined(String@0x1008, dup@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(String@0x1008) + v25:BasicObject = CCallWithFrame dup@0x1040, v12 + v16:BasicObject = SendWithoutBlock v25, :-@ + CheckInterrupts + Return v16 + "); + } + + #[test] + fn test_objtostring_anytostring_string() { + eval(r##" + def test = "#{('foo')}" + "##); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v13:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v15:StringExact = StringCopy v13 + v21:StringExact = StringConcat v10, v15 + CheckInterrupts + Return v21 + "); + } + + #[test] + fn test_objtostring_anytostring_with_non_string() { + eval(r##" + def test = "#{1}" + "##); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v11:Fixnum[1] = Const Value(1) + v13:BasicObject = ObjToString v11 + v15:String = AnyToString v11, str: v13 + v17:StringExact = StringConcat v10, v15 + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_optimize_objtostring_anytostring_recv_profiled() { + eval(" + def test(a) + \"#{a}\" + end + test('foo'); test('foo') + "); + + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + PatchPoint NoSingletonClass(String@0x1008) + v26:String = GuardType v9, String + v19:StringExact = StringConcat v13, v26 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_optimize_objtostring_anytostring_recv_profiled_string_subclass() { + eval(" + class MyString < String; end + + def test(a) + \"#{a}\" + end + foo = MyString.new('foo') + test(MyString.new(foo)); test(MyString.new(foo)) + "); + + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + PatchPoint NoSingletonClass(MyString@0x1008) + v26:String = GuardType v9, String + v19:StringExact = StringConcat v13, v26 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_optimize_objtostring_profiled_nonstring_falls_back_to_send() { + eval(" + def test(a) + \"#{a}\" + end + test([1,2,3]); test([1,2,3]) # No fast path for array + "); + + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v25:BasicObject = GuardTypeNot v9, String + PatchPoint MethodRedefined(Array@0x1008, to_s@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v30:ArrayExact = GuardType v9, ArrayExact + v31:BasicObject = CCallWithFrame to_s@0x1040, v30 + v17:String = AnyToString v9, str: v31 + v19:StringExact = StringConcat v13, v17 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_branchnil_nil() { + eval(" + def test + x = nil + x&.itself + end + "); + + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:NilClass = Const Value(nil) + CheckInterrupts + CheckInterrupts + Return v13 + "); + } + + #[test] + fn test_branchnil_truthy() { + eval(" + def test + x = 1 + x&.itself + end + "); + + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:Fixnum[1] = Const Value(1) + CheckInterrupts + PatchPoint MethodRedefined(Integer@0x1000, itself@0x1008, cme:0x1010) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v13 + "); + } + + #[test] + fn test_dont_eliminate_load_from_non_frozen_array() { + eval(r##" + S = [4,5,6] + def test = S[0] + test + "##); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, S) + v24:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:Fixnum[0] = Const Value(0) + PatchPoint MethodRedefined(Array@0x1010, []@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(Array@0x1010) + v28:BasicObject = ArrayArefFixnum v24, v12 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + // TODO(max): Check the result of `S[0] = 5; test` using `inspect` to make sure that we + // actually do the load at run-time. + } + + #[test] + fn test_eliminate_load_from_frozen_array_in_bounds() { + eval(r##" + def test = [4,5,6].freeze[1] + "##); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v13:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v28:Fixnum[5] = Const Value(5) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_eliminate_load_from_frozen_array_negative() { + eval(r##" + def test = [4,5,6].freeze[-3] + "##); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v13:Fixnum[-3] = Const Value(-3) + PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v28:Fixnum[4] = Const Value(4) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_eliminate_load_from_frozen_array_negative_out_of_bounds() { + eval(r##" + def test = [4,5,6].freeze[-10] + "##); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v13:Fixnum[-10] = Const Value(-10) + PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v28:NilClass = Const Value(nil) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_eliminate_load_from_frozen_array_out_of_bounds() { + eval(r##" + def test = [4,5,6].freeze[10] + "##); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v13:Fixnum[10] = Const Value(10) + PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v28:NilClass = Const Value(nil) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_dont_optimize_array_aref_if_redefined() { + eval(r##" + class Array + def [](index) = [] + end + def test = [4,5,6].freeze[10] + "##); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v13:Fixnum[10] = Const Value(10) + PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v25:BasicObject = SendWithoutBlockDirect v12, :[] (0x1040), v13 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_dont_optimize_array_max_if_redefined() { + eval(r##" + class Array + def max = [] + end + def test = [4,5,6].max + "##); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:ArrayExact = ArrayDup v10 + PatchPoint MethodRedefined(Array@0x1008, max@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v22:BasicObject = SendWithoutBlockDirect v12, :max (0x1040) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_set_type_from_constant() { + eval(" + MY_SET = Set.new + + def test = MY_SET + + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, MY_SET) + v19:SetExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_regexp_type() { + eval(" + def test = /a/ + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:RegexpExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_bmethod_send_direct() { + eval(" + define_method(:zero) { :b } + define_method(:one) { |arg| arg } + + def test = one(zero) + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint MethodRedefined(Object@0x1000, zero@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v22:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + v30:StaticSymbol[:b] = Const Value(VALUE(0x1038)) + PatchPoint SingleRactorMode + PatchPoint MethodRedefined(Object@0x1000, one@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(Object@0x1000) + v27:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_symbol_block_bmethod() { + eval(" + define_method(:identity, &:itself) + def test = identity(100) + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[100] = Const Value(100) + v12:BasicObject = SendWithoutBlock v6, :identity, v10 + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_call_bmethod_with_block() { + eval(" + define_method(:bmethod) { :b } + def test = (bmethod {}) + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = Send v6, 0x1000, :bmethod + CheckInterrupts + Return v11 + "); + } + + #[test] + fn test_call_shareable_bmethod() { + eval(" + class Foo + class << self + define_method(:identity, &(Ractor.make_shareable ->(val){val})) + end + end + def test = Foo.identity(100) + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:7: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Foo) + v22:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:Fixnum[100] = Const Value(100) + PatchPoint MethodRedefined(Class@0x1010, identity@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(Class@0x1010) + IncrCounter inline_iseq_optimized_send_count + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_nil_nil_specialized_to_ccall() { + eval(" + def test = nil.nil? + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:NilClass = Const Value(nil) + PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008, cme:0x1010) + v22:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_eliminate_nil_nil_specialized_to_ccall() { + eval(" + def test + nil.nil? + 1 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:NilClass = Const Value(nil) + PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008, cme:0x1010) + IncrCounter inline_cfunc_optimized_send_count + v17:Fixnum[1] = Const Value(1) + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_non_nil_nil_specialized_to_ccall() { + eval(" + def test = 1.nil? + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Integer@0x1000, nil?@0x1008, cme:0x1010) + v22:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_eliminate_non_nil_nil_specialized_to_ccall() { + eval(" + def test + 1.nil? + 2 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Integer@0x1000, nil?@0x1008, cme:0x1010) + IncrCounter inline_cfunc_optimized_send_count + v17:Fixnum[2] = Const Value(2) + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_guard_nil_for_nil_opt() { + eval(" + def test(val) = val.nil? + + test(nil) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008, cme:0x1010) + v24:NilClass = GuardType v9, NilClass + v25:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_guard_false_for_nil_opt() { + eval(" + def test(val) = val.nil? + + test(false) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(FalseClass@0x1000, nil?@0x1008, cme:0x1010) + v24:FalseClass = GuardType v9, FalseClass + v25:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_guard_true_for_nil_opt() { + eval(" + def test(val) = val.nil? + + test(true) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(TrueClass@0x1000, nil?@0x1008, cme:0x1010) + v24:TrueClass = GuardType v9, TrueClass + v25:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_guard_symbol_for_nil_opt() { + eval(" + def test(val) = val.nil? + + test(:foo) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Symbol@0x1000, nil?@0x1008, cme:0x1010) + v24:StaticSymbol = GuardType v9, StaticSymbol + v25:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_guard_fixnum_for_nil_opt() { + eval(" + def test(val) = val.nil? + + test(1) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, nil?@0x1008, cme:0x1010) + v24:Fixnum = GuardType v9, Fixnum + v25:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_guard_float_for_nil_opt() { + eval(" + def test(val) = val.nil? + + test(1.0) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Float@0x1000, nil?@0x1008, cme:0x1010) + v24:Flonum = GuardType v9, Flonum + v25:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_guard_string_for_nil_opt() { + eval(" + def test(val) = val.nil? + + test('foo') + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, nil?@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v25:StringExact = GuardType v9, StringExact + v26:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_specialize_basicobject_not_to_ccall() { + eval(" + def test(a) = !a + + test([]) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Array@0x1000, !@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v25:ArrayExact = GuardType v9, ArrayExact + IncrCounter inline_cfunc_optimized_send_count + v27:BoolExact = CCall !@0x1038, v25 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_specialize_array_empty_p_to_ccall() { + eval(" + def test(a) = a.empty? + + test([]) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Array@0x1000, empty?@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v25:ArrayExact = GuardType v9, ArrayExact + IncrCounter inline_cfunc_optimized_send_count + v27:BoolExact = CCall empty?@0x1038, v25 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_specialize_hash_empty_p_to_ccall() { + eval(" + def test(a) = a.empty? + + test({}) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Hash@0x1000, empty?@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Hash@0x1000) + v25:HashExact = GuardType v9, HashExact + IncrCounter inline_cfunc_optimized_send_count + v27:BoolExact = CCall empty?@0x1038, v25 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_specialize_basic_object_eq_to_ccall() { + eval(" + class C; end + def test(a, b) = a == b + + test(C.new, C.new) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(C@0x1000, ==@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v28:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] + v29:CBool = IsBitEqual v28, v12 + v30:BoolExact = BoxBool v29 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_guard_fixnum_and_fixnum() { + eval(" + def test(x, y) = x & y + + test(1, 2) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, 28) + v26:Fixnum = GuardType v11, Fixnum + v27:Fixnum = GuardType v12, Fixnum + v28:Fixnum = FixnumAnd v26, v27 + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_guard_fixnum_or_fixnum() { + eval(" + def test(x, y) = x | y + + test(1, 2) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, 29) + v26:Fixnum = GuardType v11, Fixnum + v27:Fixnum = GuardType v12, Fixnum + v28:Fixnum = FixnumOr v26, v27 + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_method_redefinition_patch_point_on_top_level_method() { + eval(" + def foo; end + def test = foo + + test; test + "); + + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + v22:NilClass = Const Value(nil) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_optimize_getivar_embedded() { + eval(" + class C + attr_reader :foo + def initialize + @foo = 42 + end + end + + O = C.new + def test(o) = o.foo + test O + test O + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:10: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + v25:HeapObject[class_exact:C] = GuardShape v22, 0x1038 + v26:BasicObject = LoadIvarEmbedded v25, :@foo@0x1039 + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_optimize_getivar_extended() { + eval(" + class C + attr_reader :foo + def initialize + @foo = 42 + end + end + + O = C.new + def test(o) = o.foo + test O + test O + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:10: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + v25:HeapObject[class_exact:C] = GuardShape v22, 0x1038 + v26:BasicObject = LoadIvarEmbedded v25, :@foo@0x1039 + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_dont_optimize_getivar_polymorphic() { + set_call_threshold(3); + eval(" + class C + attr_reader :foo, :bar + + def foo_then_bar + @foo = 1 + @bar = 2 + end + + def bar_then_foo + @bar = 3 + @foo = 4 + end + end + + O1 = C.new + O1.foo_then_bar + O2 = C.new + O2.bar_then_foo + def test(o) = o.foo + test O1 + test O2 + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:20: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:BasicObject = SendWithoutBlock v9, :foo + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_optimize_send_with_block() { + eval(r#" + def test = [1, 2, 3].map { |x| x * 2 } + test; test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:ArrayExact = ArrayDup v10 + PatchPoint MethodRedefined(Array@0x1008, map@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v23:BasicObject = CCallWithFrame map@0x1040, v12, block=0x1048 + CheckInterrupts + Return v23 + "); + } + + #[test] + fn test_do_not_optimize_send_variadic_with_block() { + eval(r#" + def test = [1, 2, 3].index { |x| x == 2 } + test; test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:ArrayExact = ArrayDup v10 + v14:BasicObject = Send v12, 0x1008, :index + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_do_not_optimize_send_with_block_forwarding() { + eval(r#" + def test(&block) = [].map(&block) + test; test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:ArrayExact = NewArray + GuardBlockParamProxy l0 + v17:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1000)) + v19:BasicObject = Send v14, 0x1008, :map, v17 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_do_not_optimize_send_to_iseq_method_with_block() { + eval(r#" + def foo + yield 1 + end + + def test = foo {} + test; test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = Send v6, 0x1000, :foo + CheckInterrupts + Return v11 + "); + } + + #[test] + fn test_inline_attr_reader_constant() { + eval(" + class C + attr_reader :foo + end + + O = C.new + def test = O.foo + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:7: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, O) + v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(C@0x1010, foo@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(C@0x1010) + v26:HeapObject[VALUE(0x1008)] = GuardShape v21, 0x1048 + v27:NilClass = Const Value(nil) + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_inline_attr_accessor_constant() { + eval(" + class C + attr_accessor :foo + end + + O = C.new + def test = O.foo + test + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:7: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, O) + v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(C@0x1010, foo@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(C@0x1010) + v26:HeapObject[VALUE(0x1008)] = GuardShape v21, 0x1048 + v27:NilClass = Const Value(nil) + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_inline_attr_reader() { + eval(" + class C + attr_reader :foo + end + + def test(o) = o.foo + test C.new + test C.new + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + v25:HeapObject[class_exact:C] = GuardShape v22, 0x1038 + v26:NilClass = Const Value(nil) + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_inline_attr_accessor() { + eval(" + class C + attr_accessor :foo + end + + def test(o) = o.foo + test C.new + test C.new + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + v25:HeapObject[class_exact:C] = GuardShape v22, 0x1038 + v26:NilClass = Const Value(nil) + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_inline_attr_accessor_set() { + eval(" + class C + attr_accessor :foo + end + + def test(o) = o.foo = 5 + test C.new + test C.new + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:Fixnum[5] = Const Value(5) + PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) + v23:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + SetIvar v23, :@foo, v14 + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_inline_attr_writer_set() { + eval(" + class C + attr_writer :foo + end + + def test(o) = o.foo = 5 + test C.new + test C.new + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:Fixnum[5] = Const Value(5) + PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) + v23:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + SetIvar v23, :@foo, v14 + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_array_reverse_returns_array() { + eval(r#" + def test = [].reverse + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:ArrayExact = NewArray + PatchPoint MethodRedefined(Array@0x1000, reverse@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v22:ArrayExact = CCallWithFrame reverse@0x1038, v11 + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_array_reverse_is_elidable() { + eval(r#" + def test + [].reverse + 5 + end + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:ArrayExact = NewArray + PatchPoint MethodRedefined(Array@0x1000, reverse@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v16:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v16 + "); + } + + #[test] + fn test_array_join_returns_string() { + eval(r#" + def test = [].join "," + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:ArrayExact = NewArray + v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v14:StringExact = StringCopy v12 + PatchPoint MethodRedefined(Array@0x1008, join@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v25:StringExact = CCallVariadic join@0x1040, v11, v14 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_string_to_s_returns_string() { + eval(r#" + def test = "".to_s + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:StringExact = StringCopy v10 + PatchPoint MethodRedefined(String@0x1008, to_s@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(String@0x1008) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_inline_string_literal_to_s() { + eval(r#" + def test = "foo".to_s + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:StringExact = StringCopy v10 + PatchPoint MethodRedefined(String@0x1008, to_s@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(String@0x1008) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_inline_profiled_string_to_s() { + eval(r#" + def test(o) = o.to_s + test "foo" + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, to_s@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v23:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v23 + "); + } + + #[test] + fn test_array_aref_fixnum_literal() { + eval(" + def test + arr = [1, 2, 3] + arr[0] + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v15:ArrayExact = ArrayDup v13 + v18:Fixnum[0] = Const Value(0) + PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v31:BasicObject = ArrayArefFixnum v15, v18 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v31 + "); + } + + #[test] + fn test_array_aref_fixnum_profiled() { + eval(" + def test(arr, idx) + arr[idx] + end + test([1, 2, 3], 0) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(Array@0x1000, []@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v28:ArrayExact = GuardType v11, ArrayExact + v29:Fixnum = GuardType v12, Fixnum + v30:BasicObject = ArrayArefFixnum v28, v29 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_array_aref_fixnum_array_subclass() { + eval(" + class C < Array; end + def test(arr, idx) + arr[idx] + end + test(C.new([1, 2, 3]), 0) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(C@0x1000, []@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v28:ArraySubclass[class_exact:C] = GuardType v11, ArraySubclass[class_exact:C] + v29:Fixnum = GuardType v12, Fixnum + v30:BasicObject = ArrayArefFixnum v28, v29 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_hash_aref_literal() { + eval(" + def test + arr = {1 => 3} + arr[1] + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v15:HashExact = HashDup v13 + v18:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Hash@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Hash@0x1008) + v31:BasicObject = HashAref v15, v18 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v31 + "); + } + + #[test] + fn test_hash_aref_profiled() { + eval(" + def test(hash, key) + hash[key] + end + test({1 => 3}, 1) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(Hash@0x1000, []@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Hash@0x1000) + v28:HashExact = GuardType v11, HashExact + v29:BasicObject = HashAref v28, v12 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_hash_aref_subclass() { + eval(" + class C < Hash; end + def test(hash, key) + hash[key] + end + test(C.new({0 => 3}), 0) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(C@0x1000, []@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v28:HashSubclass[class_exact:C] = GuardType v11, HashSubclass[class_exact:C] + v29:BasicObject = HashAref v28, v12 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_does_not_fold_hash_aref_with_frozen_hash() { + eval(" + H = {a: 0}.freeze + def test = H[:a] + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, H) + v24:HashExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:StaticSymbol[:a] = Const Value(VALUE(0x1010)) + PatchPoint MethodRedefined(Hash@0x1018, []@0x1020, cme:0x1028) + PatchPoint NoSingletonClass(Hash@0x1018) + v28:BasicObject = HashAref v24, v12 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_optimize_thread_current() { + eval(" + def test = Thread.current + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Thread) + v21:Class[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(Class@0x1010, current@0x1018, cme:0x1020) + PatchPoint NoSingletonClass(Class@0x1010) + IncrCounter inline_cfunc_optimized_send_count + v26:BasicObject = CCall current@0x1048, v21 + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_optimize_array_aset() { + eval(" + def test(arr) + arr[1] = 10 + end + test([]) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:Fixnum[1] = Const Value(1) + v15:Fixnum[10] = Const Value(10) + PatchPoint MethodRedefined(Array@0x1000, []=@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v28:ArrayExact = GuardType v9, ArrayExact + v29:BasicObject = CCallVariadic []=@0x1038, v28, v14, v15 + CheckInterrupts + Return v15 + "); + } + + #[test] + fn test_optimize_array_ltlt() { + eval(" + def test(arr) + arr << 1 + end + test([]) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Array@0x1000, <<@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v26:ArrayExact = GuardType v9, ArrayExact + ArrayPush v26, v13 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_optimize_array_push_single_arg() { + eval(" + def test(arr) + arr.push(1) + end + test([]) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Array@0x1000, push@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v24:ArrayExact = GuardType v9, ArrayExact + ArrayPush v24, v13 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v24 + "); + } + + #[test] + fn test_do_not_optimize_array_push_multi_arg() { + eval(" + def test(arr) + arr.push(1,2,3) + end + test([]) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + v14:Fixnum[2] = Const Value(2) + v15:Fixnum[3] = Const Value(3) + PatchPoint MethodRedefined(Array@0x1000, push@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v26:ArrayExact = GuardType v9, ArrayExact + v27:BasicObject = CCallVariadic push@0x1038, v26, v13, v14, v15 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_optimize_array_length() { + eval(" + def test(arr) = arr.length + test([]) + "); + assert_contains_opcode("test", YARVINSN_opt_length); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Array@0x1000, length@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v25:ArrayExact = GuardType v9, ArrayExact + IncrCounter inline_cfunc_optimized_send_count + v27:Fixnum = CCall length@0x1038, v25 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_optimize_array_size() { + eval(" + def test(arr) = arr.size + test([]) + "); + assert_contains_opcode("test", YARVINSN_opt_size); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Array@0x1000, size@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v25:ArrayExact = GuardType v9, ArrayExact + IncrCounter inline_cfunc_optimized_send_count + v27:Fixnum = CCall size@0x1038, v25 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_optimize_regexpmatch2() { + eval(r#" + def test(s) = s =~ /a/ + test("foo") + "#); + assert_contains_opcode("test", YARVINSN_opt_regexpmatch2); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:RegexpExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + PatchPoint MethodRedefined(String@0x1008, =~@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(String@0x1008) + v26:StringExact = GuardType v9, StringExact + v27:BasicObject = CCallWithFrame =~@0x1040, v26, v13 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_optimize_string_getbyte_fixnum() { + eval(r#" + def test(s, i) = s.getbyte(i) + test("foo", 0) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, getbyte@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v26:StringExact = GuardType v11, StringExact + v27:Fixnum = GuardType v12, Fixnum + v28:NilClass|Fixnum = StringGetbyteFixnum v26, v27 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_elide_string_getbyte_fixnum() { + eval(r#" + def test(s, i) + s.getbyte(i) + 5 + end + test("foo", 0) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, getbyte@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v29:StringExact = GuardType v11, StringExact + v30:Fixnum = GuardType v12, Fixnum + IncrCounter inline_cfunc_optimized_send_count + v20:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v20 + "); + } + + #[test] + fn test_specialize_string_empty() { + eval(r#" + def test(s) + s.empty? + end + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, empty?@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v25:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v27:BoolExact = CCall empty?@0x1038, v25 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_eliminate_string_empty() { + eval(r#" + def test(s) + s.empty? + 4 + end + test("this should get removed") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, empty?@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v28:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v19:Fixnum[4] = Const Value(4) + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_inline_integer_succ_with_fixnum() { + eval(" + def test(x) = x.succ + test(4) + "); + assert_contains_opcode("test", YARVINSN_opt_succ); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, succ@0x1008, cme:0x1010) + v24:Fixnum = GuardType v9, Fixnum + v25:Fixnum[1] = Const Value(1) + v26:Fixnum = FixnumAdd v24, v25 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_dont_inline_integer_succ_with_bignum() { + eval(" + def test(x) = x.succ + test(4 << 70) + "); + assert_contains_opcode("test", YARVINSN_opt_succ); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, succ@0x1008, cme:0x1010) + v24:Integer = GuardType v9, Integer + v25:BasicObject = CCallWithFrame succ@0x1038, v24 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_optimize_string_append() { + eval(r#" + def test(x, y) = x << y + test("iron", "fish") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, <<@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v28:StringExact = GuardType v11, StringExact + v29:String = GuardType v12, String + v30:StringExact = StringAppend v28, v29 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + // TODO: This should be inlined just as in the interpreter + #[test] + fn test_optimize_string_append_non_string() { + eval(r#" + def test(x, y) = x << y + test("iron", 4) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, <<@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v28:StringExact = GuardType v11, StringExact + v29:BasicObject = CCallWithFrame <<@0x1038, v28, v12 + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_optimize_string_append_string_subclass() { + eval(r#" + class MyString < String + end + def test(x, y) = x << y + test("iron", MyString.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, <<@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v28:StringExact = GuardType v11, StringExact + v29:String = GuardType v12, String + v30:StringExact = StringAppend v28, v29 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_do_not_optimize_string_subclass_append_string() { + eval(r#" + class MyString < String + end + def test(x, y) = x << y + test(MyString.new, "iron") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(MyString@0x1000, <<@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(MyString@0x1000) + v28:StringSubclass[class_exact:MyString] = GuardType v11, StringSubclass[class_exact:MyString] + v29:BasicObject = CCallWithFrame <<@0x1038, v28, v12 + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_dont_inline_integer_succ_with_args() { + eval(" + def test = 4.succ 1 + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[4] = Const Value(4) + v11:Fixnum[1] = Const Value(1) + v13:BasicObject = SendWithoutBlock v10, :succ, v11 + CheckInterrupts + Return v13 + "); + } + + #[test] + fn test_inline_integer_xor_with_fixnum() { + eval(" + def test(x, y) = x ^ y + test(1, 2) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, ^@0x1008, cme:0x1010) + v25:Fixnum = GuardType v11, Fixnum + v26:Fixnum = GuardType v12, Fixnum + v27:Fixnum = FixnumXor v25, v26 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_eliminate_integer_xor() { + eval(r#" + def test(x, y) + x ^ y + 42 + end + test(1, 2) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, ^@0x1008, cme:0x1010) + v28:Fixnum = GuardType v11, Fixnum + v29:Fixnum = GuardType v12, Fixnum + IncrCounter inline_cfunc_optimized_send_count + v20:Fixnum[42] = Const Value(42) + CheckInterrupts + Return v20 + "); + } + + #[test] + fn test_dont_inline_integer_xor_with_bignum_or_boolean() { + eval(" + def test(x, y) = x ^ y + test(4 << 70, 1) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, ^@0x1008, cme:0x1010) + v25:Integer = GuardType v11, Integer + v26:BasicObject = CCallWithFrame ^@0x1038, v25, v12 + CheckInterrupts + Return v26 + "); + + eval(" + def test(x, y) = x ^ y + test(1, 4 << 70) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, ^@0x1008, cme:0x1010) + v25:Fixnum = GuardType v11, Fixnum + v26:BasicObject = CCallWithFrame ^@0x1038, v25, v12 + CheckInterrupts + Return v26 + "); + + eval(" + def test(x, y) = x ^ y + test(true, 0) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(TrueClass@0x1000, ^@0x1008, cme:0x1010) + v25:TrueClass = GuardType v11, TrueClass + v26:BasicObject = CCallWithFrame ^@0x1038, v25, v12 + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_dont_inline_integer_xor_with_args() { + eval(" + def test(x, y) = x.^() + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v17:BasicObject = SendWithoutBlock v11, :^ + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_specialize_hash_size() { + eval(" + def test(hash) = hash.size + test({foo: 3, bar: 1, baz: 4}) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Hash@0x1000, size@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Hash@0x1000) + v25:HashExact = GuardType v9, HashExact + IncrCounter inline_cfunc_optimized_send_count + v27:Fixnum = CCall size@0x1038, v25 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_eliminate_hash_size() { + eval(" + def test(hash) + hash.size + 5 + end + test({foo: 3, bar: 1, baz: 4}) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Hash@0x1000, size@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Hash@0x1000) + v28:HashExact = GuardType v9, HashExact + IncrCounter inline_cfunc_optimized_send_count + v19:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_optimize_respond_to_p_true() { + eval(r#" + class C + def foo; end + end + def test(o) = o.respond_to?(:foo) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v28:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_optimize_respond_to_p_false_no_method() { + eval(r#" + class C + end + def test(o) = o.respond_to?(:foo) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, respond_to_missing?@0x1040, cme:0x1048) + PatchPoint MethodRedefined(C@0x1008, foo@0x1070, cme:0x1078) + PatchPoint NoSingletonClass(C@0x1008) + v30:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_optimize_respond_to_p_false_default_private() { + eval(r#" + class C + private + def foo; end + end + def test(o) = o.respond_to?(:foo) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v28:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_optimize_respond_to_p_false_private() { + eval(r#" + class C + private + def foo; end + end + def test(o) = o.respond_to?(:foo, false) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + v14:FalseClass = Const Value(false) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v29:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_optimize_respond_to_p_falsy_private() { + eval(r#" + class C + private + def foo; end + end + def test(o) = o.respond_to?(:foo, nil) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + v14:NilClass = Const Value(nil) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v29:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_optimize_respond_to_p_true_private() { + eval(r#" + class C + private + def foo; end + end + def test(o) = o.respond_to?(:foo, true) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + v14:TrueClass = Const Value(true) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v29:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_optimize_respond_to_p_truthy() { + eval(r#" + class C + def foo; end + end + def test(o) = o.respond_to?(:foo, 4) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + v14:Fixnum[4] = Const Value(4) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v29:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_optimize_respond_to_p_falsy() { + eval(r#" + class C + def foo; end + end + def test(o) = o.respond_to?(:foo, nil) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + v14:NilClass = Const Value(nil) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(C@0x1008) + v29:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v29 + "); + } + + #[test] + fn test_optimize_respond_to_missing() { + eval(r#" + class C + end + def test(o) = o.respond_to?(:foo) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + PatchPoint MethodRedefined(C@0x1008, respond_to_missing?@0x1040, cme:0x1048) + PatchPoint MethodRedefined(C@0x1008, foo@0x1070, cme:0x1078) + PatchPoint NoSingletonClass(C@0x1008) + v30:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_do_not_optimize_redefined_respond_to_missing() { + eval(r#" + class C + def respond_to_missing?(method, include_private = false) + true + end + end + def test(o) = o.respond_to?(:foo) + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:7: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(C@0x1008) + v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + v25:BasicObject = CCallVariadic respond_to?@0x1040, v24, v13 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_inline_send_without_block_direct_putself() { + eval(r#" + def callee = self + def test = callee + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_inline_send_without_block_direct_putobject_string() { + eval(r#" + # frozen_string_literal: true + def callee = "abc" + def test = callee + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + v22:StringExact[VALUE(0x1038)] = Const Value(VALUE(0x1038)) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_inline_send_without_block_direct_putnil() { + eval(r#" + def callee = nil + def test = callee + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + v22:NilClass = Const Value(nil) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_inline_send_without_block_direct_putobject_true() { + eval(r#" + def callee = true + def test = callee + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + v22:TrueClass = Const Value(true) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_inline_send_without_block_direct_putobject_false() { + eval(r#" + def callee = false + def test = callee + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + v22:FalseClass = Const Value(false) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_inline_send_without_block_direct_putobject_zero() { + eval(r#" + def callee = 0 + def test = callee + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + v22:Fixnum[0] = Const Value(0) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_inline_send_without_block_direct_putobject_one() { + eval(r#" + def callee = 1 + def test = callee + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + v22:Fixnum[1] = Const Value(1) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_inline_send_without_block_direct_parameter() { + eval(r#" + def callee(x) = x + def test = callee 3 + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[3] = Const Value(3) + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_inline_send_without_block_direct_last_parameter() { + eval(r#" + def callee(x, y, z) = z + def test = callee 1, 2, 3 + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v11:Fixnum[2] = Const Value(2) + v12:Fixnum[3] = Const Value(3) + PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Object@0x1000) + v22:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + IncrCounter inline_iseq_optimized_send_count + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_inline_symbol_to_sym() { + eval(r#" + def test(o) = o.to_sym + test :foo + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Symbol@0x1000, to_sym@0x1008, cme:0x1010) + v21:StaticSymbol = GuardType v9, StaticSymbol + IncrCounter inline_iseq_optimized_send_count + CheckInterrupts + Return v21 + "); + } + + #[test] + fn test_inline_integer_to_i() { + eval(r#" + def test(o) = o.to_i + test 5 + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, to_i@0x1008, cme:0x1010) + v21:Fixnum = GuardType v9, Fixnum + IncrCounter inline_iseq_optimized_send_count + CheckInterrupts + Return v21 + "); + } + + #[test] + fn test_optimize_stringexact_eq_stringexact() { + eval(r#" + def test(l, r) = l == r + test("a", "b") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, ==@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v28:StringExact = GuardType v11, StringExact + v29:String = GuardType v12, String + v30:BoolExact = CCall String#==@0x1038, v28, v29 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_optimize_string_eq_string() { + eval(r#" + class C < String + end + def test(l, r) = l == r + test(C.new("a"), C.new("b")) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(C@0x1000, ==@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v28:StringSubclass[class_exact:C] = GuardType v11, StringSubclass[class_exact:C] + v29:String = GuardType v12, String + v30:BoolExact = CCall String#==@0x1038, v28, v29 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_optimize_stringexact_eq_string() { + eval(r#" + class C < String + end + def test(l, r) = l == r + test("a", C.new("b")) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, ==@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v28:StringExact = GuardType v11, StringExact + v29:String = GuardType v12, String + v30:BoolExact = CCall String#==@0x1038, v28, v29 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_optimize_stringexact_eqq_stringexact() { + eval(r#" + def test(l, r) = l === r + test("a", "b") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, ===@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v26:StringExact = GuardType v11, StringExact + v27:String = GuardType v12, String + v28:BoolExact = CCall String#==@0x1038, v26, v27 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_optimize_string_eqq_string() { + eval(r#" + class C < String + end + def test(l, r) = l === r + test(C.new("a"), C.new("b")) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(C@0x1000, ===@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v26:StringSubclass[class_exact:C] = GuardType v11, StringSubclass[class_exact:C] + v27:String = GuardType v12, String + v28:BoolExact = CCall String#==@0x1038, v26, v27 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_optimize_stringexact_eqq_string() { + eval(r#" + class C < String + end + def test(l, r) = l === r + test("a", C.new("b")) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint MethodRedefined(String@0x1000, ===@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v26:StringExact = GuardType v11, StringExact + v27:String = GuardType v12, String + v28:BoolExact = CCall String#==@0x1038, v26, v27 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_specialize_string_size() { + eval(r#" + def test(s) + s.size + end + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, size@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v25:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v27:Fixnum = CCall size@0x1038, v25 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_elide_string_size() { + eval(r#" + def test(s) + s.size + 5 + end + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, size@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v28:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v19:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_specialize_string_bytesize() { + eval(r#" + def test(s) + s.bytesize + end + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, bytesize@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v23:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v25:Fixnum = CCall bytesize@0x1038, v23 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_elide_string_bytesize() { + eval(r#" + def test(s) + s.bytesize + 5 + end + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, bytesize@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v26:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v17:Fixnum[5] = Const Value(5) + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_specialize_string_length() { + eval(r#" + def test(s) + s.length + end + test("asdf") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, length@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v25:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v27:Fixnum = CCall length@0x1038, v25 + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_elide_string_length() { + eval(r#" + def test(s) + s.length + 4 + end + test("this should get removed") + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(String@0x1000, length@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(String@0x1000) + v28:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v19:Fixnum[4] = Const Value(4) + CheckInterrupts + Return v19 + "); + } +} diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs new file mode 100644 index 00000000000000..df14ba5a7c2026 --- /dev/null +++ b/zjit/src/hir/tests.rs @@ -0,0 +1,3207 @@ +#[cfg(test)] +use super::*; + +#[cfg(test)] +mod snapshot_tests { + use super::*; + use insta::assert_snapshot; + + #[track_caller] + fn hir_string(method: &str) -> String { + let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method)); + unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; + let function = iseq_to_hir(iseq).unwrap(); + format!("{}", FunctionPrinter::with_snapshot(&function)) + } + + #[test] + fn test_new_array_with_elements() { + eval("def test(a, b) = [a, b]"); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v13:Any = Snapshot FrameState { pc: 0x1000, stack: [], locals: [a=v11, b=v12] } + v14:Any = Snapshot FrameState { pc: 0x1008, stack: [], locals: [a=v11, b=v12] } + PatchPoint NoTracePoint + v16:Any = Snapshot FrameState { pc: 0x1010, stack: [v11, v12], locals: [a=v11, b=v12] } + v17:ArrayExact = NewArray v11, v12 + v18:Any = Snapshot FrameState { pc: 0x1018, stack: [v17], locals: [a=v11, b=v12] } + PatchPoint NoTracePoint + v20:Any = Snapshot FrameState { pc: 0x1018, stack: [v17], locals: [a=v11, b=v12] } + CheckInterrupts + Return v17 + "); + } +} + +#[cfg(test)] +pub mod hir_build_tests { + use super::*; + use insta::assert_snapshot; + + fn iseq_contains_opcode(iseq: IseqPtr, expected_opcode: u32) -> bool { + let iseq_size = unsafe { get_iseq_encoded_size(iseq) }; + let mut insn_idx = 0; + while insn_idx < iseq_size { + // Get the current pc and opcode + let pc = unsafe { rb_iseq_pc_at_idx(iseq, insn_idx) }; + + // try_into() call below is unfortunate. Maybe pick i32 instead of usize for opcodes. + let opcode: u32 = unsafe { rb_iseq_opcode_at_pc(iseq, pc) } + .try_into() + .unwrap(); + if opcode == expected_opcode { + return true; + } + insn_idx += insn_len(opcode as usize); + } + false + } + + #[track_caller] + pub fn assert_contains_opcode(method: &str, opcode: u32) { + let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method)); + unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; + assert!(iseq_contains_opcode(iseq, opcode), "iseq {method} does not contain {}", insn_name(opcode as usize)); + } + + #[track_caller] + fn assert_contains_opcodes(method: &str, opcodes: &[u32]) { + let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method)); + unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; + for &opcode in opcodes { + assert!(iseq_contains_opcode(iseq, opcode), "iseq {method} does not contain {}", insn_name(opcode as usize)); + } + } + + /// Combine multiple hir_string() results to match all of them at once, which allows + /// us to avoid running the set of zjit-test -> zjit-test-update multiple times. + #[macro_export] + macro_rules! hir_strings { + ($( $s:expr ),+ $(,)?) => {{ + vec![$( hir_string($s) ),+].join("\n") + }}; + } + + #[track_caller] + fn hir_string(method: &str) -> String { + hir_string_proc(&format!("{}.method(:{})", "self", method)) + } + + #[track_caller] + fn hir_string_proc(proc: &str) -> String { + let iseq = crate::cruby::with_rubyvm(|| get_proc_iseq(proc)); + unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; + let function = iseq_to_hir(iseq).unwrap(); + hir_string_function(&function) + } + + #[track_caller] + fn hir_string_function(function: &Function) -> String { + format!("{}", FunctionPrinter::without_snapshot(function)) + } + + #[track_caller] + fn assert_compile_fails(method: &str, reason: ParseError) { + let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method)); + unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; + let result = iseq_to_hir(iseq); + assert!(result.is_err(), "Expected an error but successfully compiled to HIR: {}", FunctionPrinter::without_snapshot(&result.unwrap())); + assert_eq!(result.unwrap_err(), reason); + } + + #[test] + fn test_compile_optional() { + eval("def test(x=1) = 123"); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + v3:CPtr = LoadPC + v4:CPtr[CPtr(0x1000)] = Const CPtr(0x1008) + v5:CBool = IsBitEqual v3, v4 + IfTrue v5, bb2(v1, v2) + Jump bb4(v1, v2) + bb1(v9:BasicObject, v10:BasicObject): + EntryPoint JIT(0) + Jump bb2(v9, v10) + bb2(v12:BasicObject, v13:BasicObject): + v15:Fixnum[1] = Const Value(1) + Jump bb4(v12, v15) + bb3(v18:BasicObject, v19:BasicObject): + EntryPoint JIT(1) + Jump bb4(v18, v19) + bb4(v21:BasicObject, v22:BasicObject): + v26:Fixnum[123] = Const Value(123) + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_putobject() { + eval("def test = 123"); + assert_contains_opcode("test", YARVINSN_putobject); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[123] = Const Value(123) + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_new_array() { + eval("def test = []"); + assert_contains_opcode("test", YARVINSN_newarray); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:ArrayExact = NewArray + CheckInterrupts + Return v11 + "); + } + + #[test] + fn test_new_array_with_element() { + eval("def test(a) = [a]"); + assert_contains_opcode("test", YARVINSN_newarray); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:ArrayExact = NewArray v9 + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_new_array_with_elements() { + eval("def test(a, b) = [a, b]"); + assert_contains_opcode("test", YARVINSN_newarray); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v17:ArrayExact = NewArray v11, v12 + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_new_range_inclusive_with_one_element() { + eval("def test(a) = (a..10)"); + assert_contains_opcode("test", YARVINSN_newrange); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[10] = Const Value(10) + v15:RangeExact = NewRange v9 NewRangeInclusive v13 + CheckInterrupts + Return v15 + "); + } + + #[test] + fn test_new_range_inclusive_with_two_elements() { + eval("def test(a, b) = (a..b)"); + assert_contains_opcode("test", YARVINSN_newrange); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v17:RangeExact = NewRange v11 NewRangeInclusive v12 + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_new_range_exclusive_with_one_element() { + eval("def test(a) = (a...10)"); + assert_contains_opcode("test", YARVINSN_newrange); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[10] = Const Value(10) + v15:RangeExact = NewRange v9 NewRangeExclusive v13 + CheckInterrupts + Return v15 + "); + } + + #[test] + fn test_new_range_exclusive_with_two_elements() { + eval("def test(a, b) = (a...b)"); + assert_contains_opcode("test", YARVINSN_newrange); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v17:RangeExact = NewRange v11 NewRangeExclusive v12 + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_array_dup() { + eval("def test = [1, 2, 3]"); + assert_contains_opcode("test", YARVINSN_duparray); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:ArrayExact = ArrayDup v10 + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_hash_dup() { + eval("def test = {a: 1, b: 2}"); + assert_contains_opcode("test", YARVINSN_duphash); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:HashExact = HashDup v10 + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_new_hash_empty() { + eval("def test = {}"); + assert_contains_opcode("test", YARVINSN_newhash); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:HashExact = NewHash + CheckInterrupts + Return v11 + "); + } + + #[test] + fn test_new_hash_with_elements() { + eval("def test(aval, bval) = {a: aval, b: bval}"); + assert_contains_opcode("test", YARVINSN_newhash); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v16:StaticSymbol[:a] = Const Value(VALUE(0x1000)) + v17:StaticSymbol[:b] = Const Value(VALUE(0x1008)) + v19:HashExact = NewHash v16: v11, v17: v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_string_copy() { + eval("def test = \"hello\""); + assert_contains_opcode("test", YARVINSN_putchilledstring); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:StringExact = StringCopy v10 + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_bignum() { + eval("def test = 999999999999999999999999999999999999"); + assert_contains_opcode("test", YARVINSN_putobject); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Bignum[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_flonum() { + eval("def test = 1.5"); + assert_contains_opcode("test", YARVINSN_putobject); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Flonum[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_heap_float() { + eval("def test = 1.7976931348623157e+308"); + assert_contains_opcode("test", YARVINSN_putobject); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:HeapFloat[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_static_sym() { + eval("def test = :foo"); + assert_contains_opcode("test", YARVINSN_putobject); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_opt_plus() { + eval("def test = 1+2"); + assert_contains_opcode("test", YARVINSN_opt_plus); + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v11:Fixnum[2] = Const Value(2) + v15:BasicObject = SendWithoutBlock v10, :+, v11 + CheckInterrupts + Return v15 + "); + } + + #[test] + fn test_opt_hash_freeze() { + eval(" + def test = {}.freeze + "); + assert_contains_opcode("test", YARVINSN_opt_hash_freeze); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_opt_hash_freeze_rewritten() { + eval(" + class Hash + def freeze; 5; end + end + def test = {}.freeze + "); + assert_contains_opcode("test", YARVINSN_opt_hash_freeze); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + SideExit PatchPoint(BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE)) + "); + } + + #[test] + fn test_opt_ary_freeze() { + eval(" + def test = [].freeze + "); + assert_contains_opcode("test", YARVINSN_opt_ary_freeze); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_opt_ary_freeze_rewritten() { + eval(" + class Array + def freeze; 5; end + end + def test = [].freeze + "); + assert_contains_opcode("test", YARVINSN_opt_ary_freeze); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + SideExit PatchPoint(BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE)) + "); + } + + #[test] + fn test_opt_str_freeze() { + eval(" + def test = ''.freeze + "); + assert_contains_opcode("test", YARVINSN_opt_str_freeze); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE) + v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_opt_str_freeze_rewritten() { + eval(" + class String + def freeze; 5; end + end + def test = ''.freeze + "); + assert_contains_opcode("test", YARVINSN_opt_str_freeze); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + SideExit PatchPoint(BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE)) + "); + } + + #[test] + fn test_opt_str_uminus() { + eval(" + def test = -'' + "); + assert_contains_opcode("test", YARVINSN_opt_str_uminus); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_UMINUS) + v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_opt_str_uminus_rewritten() { + eval(" + class String + def -@; 5; end + end + def test = -'' + "); + assert_contains_opcode("test", YARVINSN_opt_str_uminus); + assert_snapshot!(hir_string("test"), @r" + fn test@:5: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + SideExit PatchPoint(BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_UMINUS)) + "); + } + + #[test] + fn test_setlocal_getlocal() { + eval(" + def test + a = 1 + a + end + "); + assert_contains_opcodes("test", &[YARVINSN_getlocal_WC_0, YARVINSN_setlocal_WC_0]); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:Fixnum[1] = Const Value(1) + CheckInterrupts + Return v13 + "); + } + + #[test] + fn test_nested_setlocal_getlocal() { + eval(" + l3 = 3 + _unused = _unused1 = nil + 1.times do |l2| + _ = nil + l2 = 2 + 1.times do |l1| + l1 = 1 + define_method(:test) do + l1 = l2 + l2 = l1 + l2 + l3 = l2 + l3 + end + end + end + "); + assert_contains_opcodes( + "test", + &[YARVINSN_getlocal_WC_1, YARVINSN_setlocal_WC_1, + YARVINSN_getlocal, YARVINSN_setlocal]); + assert_snapshot!(hir_string("test"), @r" + fn block (3 levels) in @:10: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:BasicObject = GetLocal l2, EP@4 + SetLocal l1, EP@3, v10 + v14:BasicObject = GetLocal l1, EP@3 + v15:BasicObject = GetLocal l2, EP@4 + v19:BasicObject = SendWithoutBlock v14, :+, v15 + SetLocal l2, EP@4, v19 + v23:BasicObject = GetLocal l2, EP@4 + v24:BasicObject = GetLocal l3, EP@5 + v28:BasicObject = SendWithoutBlock v23, :+, v24 + SetLocal l3, EP@5, v28 + CheckInterrupts + Return v28 + " + ); + } + + #[test] + fn test_setlocal_in_default_args() { + eval(" + def test(a = (b = 1)) = [a, b] + "); + assert_contains_opcode("test", YARVINSN_setlocal_WC_0); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:NilClass = Const Value(nil) + v4:CPtr = LoadPC + v5:CPtr[CPtr(0x1000)] = Const CPtr(0x1008) + v6:CBool = IsBitEqual v4, v5 + IfTrue v6, bb2(v1, v2, v3) + Jump bb4(v1, v2, v3) + bb1(v10:BasicObject, v11:BasicObject): + EntryPoint JIT(0) + v12:NilClass = Const Value(nil) + Jump bb2(v10, v11, v12) + bb2(v14:BasicObject, v15:BasicObject, v16:NilClass): + v20:Fixnum[1] = Const Value(1) + Jump bb4(v14, v20, v20) + bb3(v23:BasicObject, v24:BasicObject): + EntryPoint JIT(1) + v25:NilClass = Const Value(nil) + Jump bb4(v23, v24, v25) + bb4(v27:BasicObject, v28:BasicObject, v29:NilClass|Fixnum): + v34:ArrayExact = NewArray v28, v29 + CheckInterrupts + Return v34 + "); + } + + #[test] + fn test_setlocal_in_default_args_with_tracepoint() { + eval(" + def test(a = (b = 1)) = [a, b] + TracePoint.new(:line) {}.enable + test + "); + assert_compile_fails("test", ParseError::FailedOptionalArguments); + } + + #[test] + fn test_setlocal_in_default_args_with_side_exit() { + eval(" + def test(a = (def foo = nil)) = a + "); + assert_compile_fails("test", ParseError::FailedOptionalArguments); + } + + #[test] + fn test_setlocal_cyclic_default_args() { + eval(" + def test = proc { |a=a| a } + "); + assert_snapshot!(hir_string_proc("test"), @r" + fn block in test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + CheckInterrupts + Return v9 + "); + } + + #[test] + fn defined_ivar() { + eval(" + def test = defined?(@foo) + "); + assert_contains_opcode("test", YARVINSN_definedivar); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:StringExact|NilClass = DefinedIvar v6, :@foo + CheckInterrupts + Return v11 + "); + } + + #[test] + fn if_defined_ivar() { + eval(" + def test + if defined?(@foo) + 3 + else + 4 + end + end + "); + assert_contains_opcode("test", YARVINSN_definedivar); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:TrueClass|NilClass = DefinedIvar v6, :@foo + CheckInterrupts + v14:CBool = Test v11 + IfFalse v14, bb3(v6) + v18:Fixnum[3] = Const Value(3) + CheckInterrupts + Return v18 + bb3(v24:BasicObject): + v28:Fixnum[4] = Const Value(4) + CheckInterrupts + Return v28 + "); + } + + #[test] + fn defined() { + eval(" + def test = return defined?(SeaChange), defined?(favourite), defined?($ruby) + "); + assert_contains_opcode("test", YARVINSN_defined); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:NilClass = Const Value(nil) + v12:StringExact|NilClass = Defined constant, v10 + v14:StringExact|NilClass = Defined func, v6 + v15:NilClass = Const Value(nil) + v17:StringExact|NilClass = Defined global-variable, v15 + v19:ArrayExact = NewArray v12, v14, v17 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_return_const() { + eval(" + def test(cond) + if cond + 3 + else + 4 + end + end + "); + assert_contains_opcode("test", YARVINSN_leave); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + CheckInterrupts + v15:CBool = Test v9 + IfFalse v15, bb3(v8, v9) + v19:Fixnum[3] = Const Value(3) + CheckInterrupts + Return v19 + bb3(v25:BasicObject, v26:BasicObject): + v30:Fixnum[4] = Const Value(4) + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_merge_const() { + eval(" + def test(cond) + if cond + result = 3 + else + result = 4 + end + result + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject): + EntryPoint JIT(0) + v8:NilClass = Const Value(nil) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:NilClass): + CheckInterrupts + v18:CBool = Test v11 + IfFalse v18, bb3(v10, v11, v12) + v22:Fixnum[3] = Const Value(3) + PatchPoint NoEPEscape(test) + CheckInterrupts + Jump bb4(v10, v11, v22) + bb3(v28:BasicObject, v29:BasicObject, v30:NilClass): + v34:Fixnum[4] = Const Value(4) + PatchPoint NoEPEscape(test) + Jump bb4(v28, v29, v34) + bb4(v38:BasicObject, v39:BasicObject, v40:Fixnum): + PatchPoint NoEPEscape(test) + CheckInterrupts + Return v40 + "); + } + + #[test] + fn test_opt_plus_fixnum() { + eval(" + def test(a, b) = a + b + test(1, 2); test(1, 2) + "); + assert_contains_opcode("test", YARVINSN_opt_plus); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :+, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_opt_minus_fixnum() { + eval(" + def test(a, b) = a - b + test(1, 2); test(1, 2) + "); + assert_contains_opcode("test", YARVINSN_opt_minus); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :-, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_opt_mult_fixnum() { + eval(" + def test(a, b) = a * b + test(1, 2); test(1, 2) + "); + assert_contains_opcode("test", YARVINSN_opt_mult); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :*, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_opt_div_fixnum() { + eval(" + def test(a, b) = a / b + test(1, 2); test(1, 2) + "); + assert_contains_opcode("test", YARVINSN_opt_div); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :/, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_opt_mod_fixnum() { + eval(" + def test(a, b) = a % b + test(1, 2); test(1, 2) + "); + assert_contains_opcode("test", YARVINSN_opt_mod); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :%, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_opt_eq_fixnum() { + eval(" + def test(a, b) = a == b + test(1, 2); test(1, 2) + "); + assert_contains_opcode("test", YARVINSN_opt_eq); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :==, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_opt_neq_fixnum() { + eval(" + def test(a, b) = a != b + test(1, 2); test(1, 2) + "); + assert_contains_opcode("test", YARVINSN_opt_neq); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :!=, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_opt_lt_fixnum() { + eval(" + def test(a, b) = a < b + test(1, 2); test(1, 2) + "); + assert_contains_opcode("test", YARVINSN_opt_lt); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :<, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_opt_le_fixnum() { + eval(" + def test(a, b) = a <= b + test(1, 2); test(1, 2) + "); + assert_contains_opcode("test", YARVINSN_opt_le); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :<=, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_opt_gt_fixnum() { + eval(" + def test(a, b) = a > b + test(1, 2); test(1, 2) + "); + assert_contains_opcode("test", YARVINSN_opt_gt); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :>, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_loop() { + eval(" + def test + result = 0 + times = 10 + while times > 0 + result = result + 1 + times = times - 1 + end + result + end + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + v3:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject): + EntryPoint JIT(0) + v7:NilClass = Const Value(nil) + v8:NilClass = Const Value(nil) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:NilClass, v12:NilClass): + v16:Fixnum[0] = Const Value(0) + v19:Fixnum[10] = Const Value(10) + CheckInterrupts + Jump bb4(v10, v16, v19) + bb4(v25:BasicObject, v26:BasicObject, v27:BasicObject): + PatchPoint NoEPEscape(test) + v31:Fixnum[0] = Const Value(0) + v35:BasicObject = SendWithoutBlock v27, :>, v31 + CheckInterrupts + v38:CBool = Test v35 + IfTrue v38, bb3(v25, v26, v27) + v40:NilClass = Const Value(nil) + PatchPoint NoEPEscape(test) + CheckInterrupts + Return v26 + bb3(v50:BasicObject, v51:BasicObject, v52:BasicObject): + PatchPoint NoEPEscape(test) + v58:Fixnum[1] = Const Value(1) + v62:BasicObject = SendWithoutBlock v51, :+, v58 + v65:Fixnum[1] = Const Value(1) + v69:BasicObject = SendWithoutBlock v52, :-, v65 + Jump bb4(v50, v62, v69) + "); + } + + #[test] + fn test_opt_ge_fixnum() { + eval(" + def test(a, b) = a >= b + test(1, 2); test(1, 2) + "); + assert_contains_opcode("test", YARVINSN_opt_ge); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :>=, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn test_display_types() { + eval(" + def test + cond = true + if cond + 3 + else + 4 + end + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb2(v1, v2) + bb1(v5:BasicObject): + EntryPoint JIT(0) + v6:NilClass = Const Value(nil) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:NilClass): + v13:TrueClass = Const Value(true) + CheckInterrupts + v18:CBool[true] = Test v13 + IfFalse v18, bb3(v8, v13) + v22:Fixnum[3] = Const Value(3) + CheckInterrupts + Return v22 + bb3(v28, v29): + v33 = Const Value(4) + CheckInterrupts + Return v33 + "); + } + + #[test] + fn test_send_without_block() { + eval(" + def bar(a, b) + a+b + end + def test + bar(2, 3) + end + "); + assert_contains_opcode("test", YARVINSN_opt_send_without_block); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[2] = Const Value(2) + v11:Fixnum[3] = Const Value(3) + v13:BasicObject = SendWithoutBlock v6, :bar, v10, v11 + CheckInterrupts + Return v13 + "); + } + + #[test] + fn test_send_with_block() { + eval(" + def test(a) + a.each {|item| + item + } + end + test([1,2,3]) + "); + assert_contains_opcode("test", YARVINSN_send); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:BasicObject = GetLocal l0, EP@3 + v15:BasicObject = Send v13, 0x1000, :each + v16:BasicObject = GetLocal l0, EP@3 + CheckInterrupts + Return v15 + "); + } + + #[test] + fn test_intern_interpolated_symbol() { + eval(r#" + def test + :"foo#{123}" + end + "#); + assert_contains_opcode("test", YARVINSN_intern); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v11:Fixnum[123] = Const Value(123) + v13:BasicObject = ObjToString v11 + v15:String = AnyToString v11, str: v13 + v17:StringExact = StringConcat v10, v15 + v19:Symbol = StringIntern v17 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn different_objects_get_addresses() { + eval("def test = unknown_method([0], [1], '2', '2')"); + + // The 2 string literals have the same address because they're deduped. + assert_snapshot!(hir_string("test"), @r" + fn test@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v12:ArrayExact = ArrayDup v10 + v13:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v15:ArrayExact = ArrayDup v13 + v16:StringExact[VALUE(0x1010)] = Const Value(VALUE(0x1010)) + v18:StringExact = StringCopy v16 + v19:StringExact[VALUE(0x1010)] = Const Value(VALUE(0x1010)) + v21:StringExact = StringCopy v19 + v23:BasicObject = SendWithoutBlock v6, :unknown_method, v12, v15, v18, v21 + CheckInterrupts + Return v23 + "); + } + + #[test] + fn test_cant_compile_splat() { + eval(" + def test(a) = foo(*a) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:ArrayExact = ToArray v9 + SideExit UnhandledCallType(Splat) + "); + } + + #[test] + fn test_compile_block_arg() { + eval(" + def test(a) = foo(&a) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:BasicObject = Send v8, 0x1000, :foo, v9 + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_cant_compile_kwarg() { + eval(" + def test(a) = foo(a: 1) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + SideExit UnhandledCallType(Kwarg) + "); + } + + #[test] + fn test_cant_compile_kw_splat() { + eval(" + def test(a) = foo(**a) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:BasicObject = SendWithoutBlock v8, :foo, v9 + CheckInterrupts + Return v14 + "); + } + + // TODO(max): Figure out how to generate a call with TAILCALL flag + + #[test] + fn test_compile_super() { + eval(" + def test = super() + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = InvokeSuper v6, 0x1000 + CheckInterrupts + Return v11 + "); + } + + #[test] + fn test_compile_zsuper() { + eval(" + def test = super + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = InvokeSuper v6, 0x1000 + CheckInterrupts + Return v11 + "); + } + + #[test] + fn test_cant_compile_super_nil_blockarg() { + eval(" + def test = super(&nil) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:NilClass = Const Value(nil) + v12:BasicObject = InvokeSuper v6, 0x1000, v10 + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_cant_compile_super_forward() { + eval(" + def test(...) = super(...) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + SideExit UnhandledYARVInsn(invokesuperforward) + "); + } + + #[test] + fn test_compile_forwardable() { + eval("def forwardable(...) = nil"); + assert_snapshot!(hir_string("forwardable"), @r" + fn forwardable@:1: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:NilClass = Const Value(nil) + CheckInterrupts + Return v13 + "); + } + + // TODO(max): Figure out how to generate a call with OPT_SEND flag + + #[test] + fn test_cant_compile_kw_splat_mut() { + eval(" + def test(a) = foo **a, b: 1 + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Class[VMFrozenCore] = Const Value(VALUE(0x1000)) + v15:HashExact = NewHash + PatchPoint NoEPEscape(test) + v19:BasicObject = SendWithoutBlock v13, :core#hash_merge_kwd, v15, v9 + v20:Class[VMFrozenCore] = Const Value(VALUE(0x1000)) + v21:StaticSymbol[:b] = Const Value(VALUE(0x1008)) + v22:Fixnum[1] = Const Value(1) + v24:BasicObject = SendWithoutBlock v20, :core#hash_merge_ptr, v19, v21, v22 + v26:BasicObject = SendWithoutBlock v8, :foo, v24 + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_cant_compile_splat_mut() { + eval(" + def test(*) = foo *, 1 + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:ArrayExact = GetLocal l0, SP@4, * + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:ArrayExact): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:ArrayExact): + v14:ArrayExact = ToNewArray v9 + v15:Fixnum[1] = Const Value(1) + ArrayPush v14, v15 + SideExit UnhandledCallType(Splat) + "); + } + + #[test] + fn test_compile_forwarding() { + eval(" + def test(...) = foo(...) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:BasicObject = SendForward 0x1000, :foo, v9 + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_compile_triple_dots_with_positional_args() { + eval(" + def test(a, ...) = foo(a, ...) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@8 + v3:ArrayExact = GetLocal l0, SP@7, * + v4:BasicObject = GetLocal l0, SP@6 + v5:BasicObject = GetLocal l0, SP@5 + v6:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4, v5, v6) + bb1(v9:BasicObject, v10:BasicObject, v11:ArrayExact, v12:BasicObject, v13:BasicObject): + EntryPoint JIT(0) + v14:NilClass = Const Value(nil) + Jump bb2(v9, v10, v11, v12, v13, v14) + bb2(v16:BasicObject, v17:BasicObject, v18:ArrayExact, v19:BasicObject, v20:BasicObject, v21:NilClass): + v26:ArrayExact = ToArray v18 + PatchPoint NoEPEscape(test) + GuardBlockParamProxy l0 + v31:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1000)) + SideExit UnhandledYARVInsn(splatkw) + "); + } + + #[test] + fn test_opt_new() { + eval(" + class C; end + def test = C.new + "); + assert_contains_opcode("test", YARVINSN_opt_new); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = GetConstantPath 0x1000 + v12:NilClass = Const Value(nil) + v14:CBool = IsMethodCFunc v11, :new + IfFalse v14, bb3(v6, v12, v11) + v16:HeapBasicObject = ObjectAlloc v11 + v18:BasicObject = SendWithoutBlock v16, :initialize + CheckInterrupts + Jump bb4(v6, v16, v18) + bb3(v22:BasicObject, v23:NilClass, v24:BasicObject): + v27:BasicObject = SendWithoutBlock v24, :new + Jump bb4(v22, v27, v23) + bb4(v29:BasicObject, v30:BasicObject, v31:BasicObject): + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_opt_newarray_send_max_no_elements() { + eval(" + def test = [].max + "); + // TODO(max): Rewrite to nil + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_MAX) + v12:BasicObject = ArrayMax + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_opt_newarray_send_max() { + eval(" + def test(a,b) = [a,b].max + "); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_MAX) + v18:BasicObject = ArrayMax v11, v12 + CheckInterrupts + Return v18 + "); + } + + #[test] + fn test_opt_newarray_send_min() { + eval(" + def test(a,b) + sum = a+b + result = [a,b].min + puts [1,2,3] + result + end + "); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@7 + v3:BasicObject = GetLocal l0, SP@6 + v4:NilClass = Const Value(nil) + v5:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4, v5) + bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject): + EntryPoint JIT(0) + v11:NilClass = Const Value(nil) + v12:NilClass = Const Value(nil) + Jump bb2(v8, v9, v10, v11, v12) + bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): + v25:BasicObject = SendWithoutBlock v15, :+, v16 + SideExit UnknownNewarraySend(MIN) + "); + } + + #[test] + fn test_opt_newarray_send_hash() { + eval(" + def test(a,b) + sum = a+b + result = [a,b].hash + puts [1,2,3] + result + end + "); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@7 + v3:BasicObject = GetLocal l0, SP@6 + v4:NilClass = Const Value(nil) + v5:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4, v5) + bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject): + EntryPoint JIT(0) + v11:NilClass = Const Value(nil) + v12:NilClass = Const Value(nil) + Jump bb2(v8, v9, v10, v11, v12) + bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): + v25:BasicObject = SendWithoutBlock v15, :+, v16 + SideExit UnknownNewarraySend(HASH) + "); + } + + #[test] + fn test_opt_newarray_send_pack() { + eval(" + def test(a,b) + sum = a+b + result = [a,b].pack 'C' + puts [1,2,3] + result + end + "); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@7 + v3:BasicObject = GetLocal l0, SP@6 + v4:NilClass = Const Value(nil) + v5:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4, v5) + bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject): + EntryPoint JIT(0) + v11:NilClass = Const Value(nil) + v12:NilClass = Const Value(nil) + Jump bb2(v8, v9, v10, v11, v12) + bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): + v25:BasicObject = SendWithoutBlock v15, :+, v16 + v28:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v30:StringExact = StringCopy v28 + SideExit UnknownNewarraySend(PACK) + "); + } + + // TODO(max): Add a test for VM_OPT_NEWARRAY_SEND_PACK_BUFFER + + #[test] + fn test_opt_newarray_send_include_p() { + eval(" + def test(a,b) + sum = a+b + result = [a,b].include? b + puts [1,2,3] + result + end + "); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@7 + v3:BasicObject = GetLocal l0, SP@6 + v4:NilClass = Const Value(nil) + v5:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4, v5) + bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject): + EntryPoint JIT(0) + v11:NilClass = Const Value(nil) + v12:NilClass = Const Value(nil) + Jump bb2(v8, v9, v10, v11, v12) + bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:NilClass, v18:NilClass): + v25:BasicObject = SendWithoutBlock v15, :+, v16 + SideExit UnknownNewarraySend(INCLUDE_P) + "); + } + + #[test] + fn test_opt_length() { + eval(" + def test(a,b) = [a,b].length + "); + assert_contains_opcode("test", YARVINSN_opt_length); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v17:ArrayExact = NewArray v11, v12 + v21:BasicObject = SendWithoutBlock v17, :length + CheckInterrupts + Return v21 + "); + } + + #[test] + fn test_opt_size() { + eval(" + def test(a,b) = [a,b].size + "); + assert_contains_opcode("test", YARVINSN_opt_size); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v17:ArrayExact = NewArray v11, v12 + v21:BasicObject = SendWithoutBlock v17, :size + CheckInterrupts + Return v21 + "); + } + + #[test] + fn test_getinstancevariable() { + eval(" + def test = @foo + test + "); + assert_contains_opcode("test", YARVINSN_getinstancevariable); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + v12:BasicObject = GetIvar v6, :@foo + CheckInterrupts + Return v12 + "); + } + + #[test] + fn test_setinstancevariable() { + eval(" + def test = @foo = 1 + test + "); + assert_contains_opcode("test", YARVINSN_setinstancevariable); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + PatchPoint SingleRactorMode + SetIvar v6, :@foo, v10 + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_set_ivar_rescue_frozen() { + let result = eval(" + class Foo + attr_accessor :bar + def initialize + @bar = 1 + freeze + end + end + + def test(foo) + begin + foo.bar = 2 + rescue FrozenError + end + end + + foo = Foo.new + test(foo) + test(foo) + + foo.bar + "); + assert_eq!(VALUE::fixnum_from_usize(1), result); + } + + #[test] + fn test_getclassvariable() { + eval(" + class Foo + def self.test = @@foo + end + "); + let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("Foo", "test")); + assert!(iseq_contains_opcode(iseq, YARVINSN_getclassvariable), "iseq Foo.test does not contain getclassvariable"); + let function = iseq_to_hir(iseq).unwrap(); + assert_snapshot!(hir_string_function(&function), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = GetClassVar :@@foo + CheckInterrupts + Return v11 + "); + } + + #[test] + fn test_setclassvariable() { + eval(" + class Foo + def self.test = @@foo = 42 + end + "); + let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("Foo", "test")); + assert!(iseq_contains_opcode(iseq, YARVINSN_setclassvariable), "iseq Foo.test does not contain setclassvariable"); + let function = iseq_to_hir(iseq).unwrap(); + assert_snapshot!(hir_string_function(&function), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[42] = Const Value(42) + SetClassVar :@@foo, v10 + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_setglobal() { + eval(" + def test = $foo = 1 + test + "); + assert_contains_opcode("test", YARVINSN_setglobal); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + SetGlobal :$foo, v10 + CheckInterrupts + Return v10 + "); + } + + #[test] + fn test_getglobal() { + eval(" + def test = $foo + test + "); + assert_contains_opcode("test", YARVINSN_getglobal); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = GetGlobal :$foo + CheckInterrupts + Return v11 + "); + } + + #[test] + fn test_splatarray_mut() { + eval(" + def test(a) = [*a] + "); + assert_contains_opcode("test", YARVINSN_splatarray); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:ArrayExact = ToNewArray v9 + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_concattoarray() { + eval(" + def test(a) = [1, *a] + "); + assert_contains_opcode("test", YARVINSN_concattoarray); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:Fixnum[1] = Const Value(1) + v15:ArrayExact = NewArray v13 + v17:ArrayExact = ToArray v9 + ArrayExtend v15, v17 + CheckInterrupts + Return v15 + "); + } + + #[test] + fn test_pushtoarray_one_element() { + eval(" + def test(a) = [*a, 1] + "); + assert_contains_opcode("test", YARVINSN_pushtoarray); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:ArrayExact = ToNewArray v9 + v15:Fixnum[1] = Const Value(1) + ArrayPush v14, v15 + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_pushtoarray_multiple_elements() { + eval(" + def test(a) = [*a, 1, 2, 3] + "); + assert_contains_opcode("test", YARVINSN_pushtoarray); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v14:ArrayExact = ToNewArray v9 + v15:Fixnum[1] = Const Value(1) + v16:Fixnum[2] = Const Value(2) + v17:Fixnum[3] = Const Value(3) + ArrayPush v14, v15 + ArrayPush v14, v16 + ArrayPush v14, v17 + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_aset() { + eval(" + def test(a, b) = a[b] = 1 + "); + assert_contains_opcode("test", YARVINSN_opt_aset); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v16:NilClass = Const Value(nil) + v17:Fixnum[1] = Const Value(1) + v21:BasicObject = SendWithoutBlock v11, :[]=, v12, v17 + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_aref() { + eval(" + def test(a, b) = a[b] + "); + assert_contains_opcode("test", YARVINSN_opt_aref); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :[], v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn opt_empty_p() { + eval(" + def test(x) = x.empty? + "); + assert_contains_opcode("test", YARVINSN_opt_empty_p); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v16:BasicObject = SendWithoutBlock v9, :empty? + CheckInterrupts + Return v16 + "); + } + + #[test] + fn opt_succ() { + eval(" + def test(x) = x.succ + "); + assert_contains_opcode("test", YARVINSN_opt_succ); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v16:BasicObject = SendWithoutBlock v9, :succ + CheckInterrupts + Return v16 + "); + } + + #[test] + fn opt_and() { + eval(" + def test(x, y) = x & y + "); + assert_contains_opcode("test", YARVINSN_opt_and); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :&, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn opt_or() { + eval(" + def test(x, y) = x | y + "); + assert_contains_opcode("test", YARVINSN_opt_or); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :|, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + fn opt_not() { + eval(" + def test(x) = !x + "); + assert_contains_opcode("test", YARVINSN_opt_not); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v16:BasicObject = SendWithoutBlock v9, :! + CheckInterrupts + Return v16 + "); + } + + #[test] + fn opt_regexpmatch2() { + eval(" + def test(regexp, matchee) = regexp =~ matchee + "); + assert_contains_opcode("test", YARVINSN_opt_regexpmatch2); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v19:BasicObject = SendWithoutBlock v11, :=~, v12 + CheckInterrupts + Return v19 + "); + } + + #[test] + // Tests for ConstBase requires either constant or class definition, both + // of which can't be performed inside a method. + fn test_putspecialobject_vm_core_and_cbase() { + eval(" + def test + alias aliased __callee__ + end + "); + assert_contains_opcode("test", YARVINSN_putspecialobject); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Class[VMFrozenCore] = Const Value(VALUE(0x1000)) + v11:BasicObject = PutSpecialObject CBase + v12:StaticSymbol[:aliased] = Const Value(VALUE(0x1008)) + v13:StaticSymbol[:__callee__] = Const Value(VALUE(0x1010)) + v15:BasicObject = SendWithoutBlock v10, :core#set_method_alias, v11, v12, v13 + CheckInterrupts + Return v15 + "); + } + + #[test] + fn opt_reverse() { + eval(" + def reverse_odd + a, b, c = @a, @b, @c + [a, b, c] + end + + def reverse_even + a, b, c, d = @a, @b, @c, @d + [a, b, c, d] + end + "); + assert_contains_opcode("reverse_odd", YARVINSN_opt_reverse); + assert_contains_opcode("reverse_even", YARVINSN_opt_reverse); + assert_snapshot!(hir_strings!("reverse_odd", "reverse_even"), @r" + fn reverse_odd@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + v3:NilClass = Const Value(nil) + v4:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4) + bb1(v7:BasicObject): + EntryPoint JIT(0) + v8:NilClass = Const Value(nil) + v9:NilClass = Const Value(nil) + v10:NilClass = Const Value(nil) + Jump bb2(v7, v8, v9, v10) + bb2(v12:BasicObject, v13:NilClass, v14:NilClass, v15:NilClass): + PatchPoint SingleRactorMode + v21:BasicObject = GetIvar v12, :@a + PatchPoint SingleRactorMode + v24:BasicObject = GetIvar v12, :@b + PatchPoint SingleRactorMode + v27:BasicObject = GetIvar v12, :@c + PatchPoint NoEPEscape(reverse_odd) + v33:ArrayExact = NewArray v21, v24, v27 + CheckInterrupts + Return v33 + + fn reverse_even@:8: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + v3:NilClass = Const Value(nil) + v4:NilClass = Const Value(nil) + v5:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4, v5) + bb1(v8:BasicObject): + EntryPoint JIT(0) + v9:NilClass = Const Value(nil) + v10:NilClass = Const Value(nil) + v11:NilClass = Const Value(nil) + v12:NilClass = Const Value(nil) + Jump bb2(v8, v9, v10, v11, v12) + bb2(v14:BasicObject, v15:NilClass, v16:NilClass, v17:NilClass, v18:NilClass): + PatchPoint SingleRactorMode + v24:BasicObject = GetIvar v14, :@a + PatchPoint SingleRactorMode + v27:BasicObject = GetIvar v14, :@b + PatchPoint SingleRactorMode + v30:BasicObject = GetIvar v14, :@c + PatchPoint SingleRactorMode + v33:BasicObject = GetIvar v14, :@d + PatchPoint NoEPEscape(reverse_even) + v39:ArrayExact = NewArray v24, v27, v30, v33 + CheckInterrupts + Return v39 + "); + } + + #[test] + fn test_branchnil() { + eval(" + def test(x) = x&.itself + "); + assert_contains_opcode("test", YARVINSN_branchnil); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + CheckInterrupts + v15:CBool = IsNil v9 + IfTrue v15, bb3(v8, v9, v9) + v18:BasicObject = SendWithoutBlock v9, :itself + Jump bb3(v8, v9, v18) + bb3(v20:BasicObject, v21:BasicObject, v22:BasicObject): + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_invokebuiltin_delegate_annotated() { + assert_contains_opcode("Float", YARVINSN_opt_invokebuiltin_delegate_leave); + assert_snapshot!(hir_string("Float"), @r" + fn Float@: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@6 + v3:BasicObject = GetLocal l0, SP@5 + v4:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3, v4) + bb1(v7:BasicObject, v8:BasicObject, v9:BasicObject, v10:BasicObject): + EntryPoint JIT(0) + Jump bb2(v7, v8, v9, v10) + bb2(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:BasicObject): + v20:Float = InvokeBuiltin rb_f_float, v12, v13, v14 + Jump bb3(v12, v13, v14, v15, v20) + bb3(v22:BasicObject, v23:BasicObject, v24:BasicObject, v25:BasicObject, v26:Float): + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_invokebuiltin_cexpr_annotated() { + assert_contains_opcode("class", YARVINSN_opt_invokebuiltin_delegate_leave); + assert_snapshot!(hir_string("class"), @r" + fn class@: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:Class = InvokeBuiltin leaf _bi20, v6 + Jump bb3(v6, v11) + bb3(v13:BasicObject, v14:Class): + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_invokebuiltin_delegate_with_args() { + // Using an unannotated builtin to test InvokeBuiltin generation + let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("Dir", "open")); + assert!(iseq_contains_opcode(iseq, YARVINSN_opt_invokebuiltin_delegate), "iseq Dir.open does not contain invokebuiltin"); + let function = iseq_to_hir(iseq).unwrap(); + assert_snapshot!(hir_string_function(&function), @r" + fn open@: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@8 + v3:BasicObject = GetLocal l0, SP@7 + v4:BasicObject = GetLocal l0, SP@6 + v5:BasicObject = GetLocal l0, SP@5 + v6:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4, v5, v6) + bb1(v9:BasicObject, v10:BasicObject, v11:BasicObject, v12:BasicObject, v13:BasicObject): + EntryPoint JIT(0) + v14:NilClass = Const Value(nil) + Jump bb2(v9, v10, v11, v12, v13, v14) + bb2(v16:BasicObject, v17:BasicObject, v18:BasicObject, v19:BasicObject, v20:BasicObject, v21:NilClass): + v26:BasicObject = InvokeBuiltin dir_s_open, v16, v17, v18 + PatchPoint NoEPEscape(open) + GuardBlockParamProxy l0 + v33:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1000)) + CheckInterrupts + v36:CBool[true] = Test v33 + IfFalse v36, bb3(v16, v17, v18, v19, v20, v26) + PatchPoint NoEPEscape(open) + v43:BasicObject = InvokeBlock, v26 + v47:BasicObject = InvokeBuiltin dir_s_close, v16, v26 + CheckInterrupts + Return v43 + bb3(v53, v54, v55, v56, v57, v58): + PatchPoint NoEPEscape(open) + CheckInterrupts + Return v58 + "); + } + + #[test] + fn test_invokebuiltin_delegate_without_args() { + let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("GC", "enable")); + assert!(iseq_contains_opcode(iseq, YARVINSN_opt_invokebuiltin_delegate_leave), "iseq GC.enable does not contain invokebuiltin"); + let function = iseq_to_hir(iseq).unwrap(); + assert_snapshot!(hir_string_function(&function), @r" + fn enable@: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = InvokeBuiltin gc_enable, v6 + Jump bb3(v6, v11) + bb3(v13:BasicObject, v14:BasicObject): + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_invokebuiltin_with_args() { + let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("GC", "start")); + assert!(iseq_contains_opcode(iseq, YARVINSN_invokebuiltin), "iseq GC.start does not contain invokebuiltin"); + let function = iseq_to_hir(iseq).unwrap(); + assert_snapshot!(hir_string_function(&function), @r" + fn start@: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@7 + v3:BasicObject = GetLocal l0, SP@6 + v4:BasicObject = GetLocal l0, SP@5 + v5:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3, v4, v5) + bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject, v11:BasicObject, v12:BasicObject): + EntryPoint JIT(0) + Jump bb2(v8, v9, v10, v11, v12) + bb2(v14:BasicObject, v15:BasicObject, v16:BasicObject, v17:BasicObject, v18:BasicObject): + v22:FalseClass = Const Value(false) + v24:BasicObject = InvokeBuiltin gc_start_internal, v14, v15, v16, v17, v22 + CheckInterrupts + Return v24 + "); + } + + #[test] + fn test_invoke_leaf_builtin_symbol_name() { + let iseq = crate::cruby::with_rubyvm(|| get_instance_method_iseq("Symbol", "name")); + let function = iseq_to_hir(iseq).unwrap(); + assert_snapshot!(hir_string_function(&function), @r" + fn name@: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:StringExact = InvokeBuiltin leaf _bi28, v6 + Jump bb3(v6, v11) + bb3(v13:BasicObject, v14:StringExact): + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_invoke_leaf_builtin_symbol_to_s() { + let iseq = crate::cruby::with_rubyvm(|| get_instance_method_iseq("Symbol", "to_s")); + let function = iseq_to_hir(iseq).unwrap(); + assert_snapshot!(hir_string_function(&function), @r" + fn to_s@: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:StringExact = InvokeBuiltin leaf _bi12, v6 + Jump bb3(v6, v11) + bb3(v13:BasicObject, v14:StringExact): + CheckInterrupts + Return v14 + "); + } + + #[test] + fn dupn() { + eval(" + def test(x) = (x[0, 1] ||= 2) + "); + assert_contains_opcode("test", YARVINSN_dupn); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + v13:NilClass = Const Value(nil) + v14:Fixnum[0] = Const Value(0) + v15:Fixnum[1] = Const Value(1) + v17:BasicObject = SendWithoutBlock v9, :[], v14, v15 + CheckInterrupts + v20:CBool = Test v17 + IfTrue v20, bb3(v8, v9, v13, v9, v14, v15, v17) + v22:Fixnum[2] = Const Value(2) + v24:BasicObject = SendWithoutBlock v9, :[]=, v14, v15, v22 + CheckInterrupts + Return v22 + bb3(v30:BasicObject, v31:BasicObject, v32:NilClass, v33:BasicObject, v34:Fixnum[0], v35:Fixnum[1], v36:BasicObject): + CheckInterrupts + Return v36 + "); + } + + #[test] + fn test_objtostring_anytostring() { + eval(" + def test = \"#{1}\" + "); + assert_contains_opcode("test", YARVINSN_objtostring); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v11:Fixnum[1] = Const Value(1) + v13:BasicObject = ObjToString v11 + v15:String = AnyToString v11, str: v13 + v17:StringExact = StringConcat v10, v15 + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_string_concat() { + eval(r##" + def test = "#{1}#{2}#{3}" + "##); + assert_contains_opcode("test", YARVINSN_concatstrings); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v12:BasicObject = ObjToString v10 + v14:String = AnyToString v10, str: v12 + v15:Fixnum[2] = Const Value(2) + v17:BasicObject = ObjToString v15 + v19:String = AnyToString v15, str: v17 + v20:Fixnum[3] = Const Value(3) + v22:BasicObject = ObjToString v20 + v24:String = AnyToString v20, str: v22 + v26:StringExact = StringConcat v14, v19, v24 + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_string_concat_empty() { + eval(r##" + def test = "#{}" + "##); + assert_contains_opcode("test", YARVINSN_concatstrings); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v11:NilClass = Const Value(nil) + v13:BasicObject = ObjToString v11 + v15:String = AnyToString v11, str: v13 + v17:StringExact = StringConcat v10, v15 + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_toregexp() { + eval(r##" + def test = /#{1}#{2}#{3}/ + "##); + assert_contains_opcode("test", YARVINSN_toregexp); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v12:BasicObject = ObjToString v10 + v14:String = AnyToString v10, str: v12 + v15:Fixnum[2] = Const Value(2) + v17:BasicObject = ObjToString v15 + v19:String = AnyToString v15, str: v17 + v20:Fixnum[3] = Const Value(3) + v22:BasicObject = ObjToString v20 + v24:String = AnyToString v20, str: v22 + v26:RegexpExact = ToRegexp v14, v19, v24 + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_toregexp_with_options() { + eval(r##" + def test = /#{1}#{2}/mixn + "##); + assert_contains_opcode("test", YARVINSN_toregexp); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + v12:BasicObject = ObjToString v10 + v14:String = AnyToString v10, str: v12 + v15:Fixnum[2] = Const Value(2) + v17:BasicObject = ObjToString v15 + v19:String = AnyToString v15, str: v17 + v21:RegexpExact = ToRegexp v14, v19, MULTILINE|IGNORECASE|EXTENDED|NOENCODING + CheckInterrupts + Return v21 + "); + } + + #[test] + fn throw() { + eval(" + define_method(:throw_return) { return 1 } + define_method(:throw_break) { break 2 } + "); + assert_contains_opcode("throw_return", YARVINSN_throw); + assert_contains_opcode("throw_break", YARVINSN_throw); + assert_snapshot!(hir_strings!("throw_return", "throw_break"), @r" + fn block in @:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v12:Fixnum[1] = Const Value(1) + Throw TAG_RETURN, v12 + + fn block in @:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v12:Fixnum[2] = Const Value(2) + Throw TAG_BREAK, v12 + "); + } + + #[test] + fn test_invokeblock() { + eval(r#" + def test + yield + end + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v11:BasicObject = InvokeBlock + CheckInterrupts + Return v11 + "); + } + + #[test] + fn test_invokeblock_with_args() { + eval(r#" + def test(x, y) + yield x, y + end + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@5 + v3:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2, v3) + bb1(v6:BasicObject, v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + Jump bb2(v6, v7, v8) + bb2(v10:BasicObject, v11:BasicObject, v12:BasicObject): + v17:BasicObject = InvokeBlock, v11, v12 + CheckInterrupts + Return v17 + "); + } + + #[test] + fn test_expandarray_no_splat() { + eval(r#" + def test(o) + a, b = o + end + "#); + assert_contains_opcode("test", YARVINSN_expandarray); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@6 + v3:NilClass = Const Value(nil) + v4:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4) + bb1(v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + v9:NilClass = Const Value(nil) + v10:NilClass = Const Value(nil) + Jump bb2(v7, v8, v9, v10) + bb2(v12:BasicObject, v13:BasicObject, v14:NilClass, v15:NilClass): + v20:ArrayExact = GuardType v13, ArrayExact + v21:CInt64 = ArrayLength v20 + v22:CInt64[2] = GuardBitEquals v21, CInt64(2) + v23:Fixnum[1] = Const Value(1) + v24:BasicObject = ArrayArefFixnum v20, v23 + v25:Fixnum[0] = Const Value(0) + v26:BasicObject = ArrayArefFixnum v20, v25 + PatchPoint NoEPEscape(test) + CheckInterrupts + Return v13 + "); + } + + #[test] + fn test_expandarray_splat() { + eval(r#" + def test(o) + a, *b = o + end + "#); + assert_contains_opcode("test", YARVINSN_expandarray); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@6 + v3:NilClass = Const Value(nil) + v4:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4) + bb1(v7:BasicObject, v8:BasicObject): + EntryPoint JIT(0) + v9:NilClass = Const Value(nil) + v10:NilClass = Const Value(nil) + Jump bb2(v7, v8, v9, v10) + bb2(v12:BasicObject, v13:BasicObject, v14:NilClass, v15:NilClass): + SideExit UnhandledYARVInsn(expandarray) + "); + } + + #[test] + fn test_expandarray_splat_post() { + eval(r#" + def test(o) + a, *b, c = o + end + "#); + assert_contains_opcode("test", YARVINSN_expandarray); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@7 + v3:NilClass = Const Value(nil) + v4:NilClass = Const Value(nil) + v5:NilClass = Const Value(nil) + Jump bb2(v1, v2, v3, v4, v5) + bb1(v8:BasicObject, v9:BasicObject): + EntryPoint JIT(0) + v10:NilClass = Const Value(nil) + v11:NilClass = Const Value(nil) + v12:NilClass = Const Value(nil) + Jump bb2(v8, v9, v10, v11, v12) + bb2(v14:BasicObject, v15:BasicObject, v16:NilClass, v17:NilClass, v18:NilClass): + SideExit UnhandledYARVInsn(expandarray) + "); + } +}