diff --git a/class.c b/class.c index 7baaa5e715f044..84c8668b6be3a3 100644 --- a/class.c +++ b/class.c @@ -480,7 +480,7 @@ rb_module_add_to_subclasses_list(VALUE module, VALUE iclass) } void -rb_class_remove_subclass_head(VALUE klass) // TODO: check this is still used and required +rb_class_remove_subclass_head(VALUE klass) { rb_classext_t *ext = RCLASS_EXT_WRITABLE(klass); rb_class_classext_free_subclasses(ext, klass); diff --git a/lib/erb/erb.gemspec b/lib/erb/erb.gemspec index 94edc686825419..3793e5d70fac87 100644 --- a/lib/erb/erb.gemspec +++ b/lib/erb/erb.gemspec @@ -18,6 +18,7 @@ Gem::Specification.new do |spec| spec.metadata['homepage_uri'] = spec.homepage spec.metadata['source_code_uri'] = spec.homepage + spec.metadata['changelog_uri'] = "https://github.com/ruby/erb/blob/v#{spec.version}/NEWS.md" spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } diff --git a/namespace.c b/namespace.c index 6a8f4abc9d87bf..c8c947f50cd8ba 100644 --- a/namespace.c +++ b/namespace.c @@ -118,6 +118,12 @@ namespace_generate_id(void) return id; } +static VALUE +namespace_main_to_s(VALUE obj) +{ + return rb_str_new2("main"); +} + static void namespace_entry_initialize(rb_namespace_t *ns) { @@ -128,9 +134,8 @@ namespace_entry_initialize(rb_namespace_t *ns) ns->ns_id = 0; ns->top_self = rb_obj_alloc(rb_cObject); - // TODO: - // rb_define_singleton_method(rb_vm_top_self(), "to_s", main_to_s, 0); - // rb_define_alias(rb_singleton_class(rb_vm_top_self()), "inspect", "to_s"); + rb_define_singleton_method(ns->top_self, "to_s", namespace_main_to_s, 0); + rb_define_alias(rb_singleton_class(ns->top_self), "inspect", "to_s"); ns->load_path = rb_ary_dup(root->load_path); ns->expanded_load_path = rb_ary_dup(root->expanded_load_path); ns->load_path_snapshot = rb_ary_new(); @@ -349,20 +354,6 @@ rb_namespace_s_current(VALUE recv) return ns->ns_object; } -/* - * call-seq: - * Namespace.is_builtin?(klass) -> true or false - * - * Returns +true+ if +klass+ is only in a user namespace. - */ -static VALUE -rb_namespace_s_is_builtin_p(VALUE recv, VALUE klass) -{ - if (RCLASS_PRIME_CLASSEXT_READABLE_P(klass) && !RCLASS_PRIME_CLASSEXT_WRITABLE_P(klass)) - return Qtrue; - return Qfalse; -} - /* * call-seq: * load_path -> array @@ -1056,7 +1047,6 @@ Init_Namespace(void) rb_define_singleton_method(rb_cNamespace, "enabled?", rb_namespace_s_getenabled, 0); rb_define_singleton_method(rb_cNamespace, "current", rb_namespace_s_current, 0); - rb_define_singleton_method(rb_cNamespace, "is_builtin?", rb_namespace_s_is_builtin_p, 1); rb_define_method(rb_cNamespace, "load_path", rb_namespace_load_path, 0); rb_define_method(rb_cNamespace, "load", rb_namespace_load, -1); diff --git a/variable.c b/variable.c index bb3811a81c5425..c8565047a46e4e 100644 --- a/variable.c +++ b/variable.c @@ -999,7 +999,7 @@ rb_gvar_set_entry(struct rb_global_entry *entry, VALUE val) } #define USE_NAMESPACE_GVAR_TBL(ns,entry) \ - (NAMESPACE_OPTIONAL_P(ns) && \ + (NAMESPACE_USER_P(ns) && \ (!entry || !entry->var->namespace_ready || entry->var->setter != rb_gvar_readonly_setter)) VALUE @@ -1012,7 +1012,6 @@ rb_gvar_set(ID id, VALUE val) RB_VM_LOCKING() { entry = rb_global_entry(id); - // TODO: consider root/main namespaces if (USE_NAMESPACE_GVAR_TBL(ns, entry)) { rb_hash_aset(ns->gvar_tbl, rb_id2sym(entry->id), val); retval = val; diff --git a/vm_eval.c b/vm_eval.c index 81b6bed7257abf..71656f5a0f2ab1 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -2000,7 +2000,6 @@ eval_string_with_cref(VALUE self, VALUE src, rb_cref_t *cref, VALUE file, int li cref = vm_cref_dup(orig_cref); } vm_set_eval_stack(ec, iseq, cref, &block); - // TODO: set the namespace frame /* kick */ return vm_exec(ec); @@ -2023,8 +2022,6 @@ eval_string_with_scope(VALUE scope, VALUE src, VALUE file, int line) vm_bind_update_env(scope, bind, vm_make_env_object(ec, ec->cfp)); } - // TODO: set the namespace frame - /* kick */ return vm_exec(ec); } diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 7467cb50c85446..bc8f1d3b847501 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -12,6 +12,7 @@ use crate::cruby::*; use std::collections::HashMap; use std::ffi::c_void; use crate::hir_type::{types, Type}; +use crate::hir; pub struct Annotations { cfuncs: HashMap<*mut c_void, FnProperties>, @@ -29,6 +30,7 @@ pub struct FnProperties { pub return_type: Type, /// Whether it's legal to remove the call if the result is unused pub elidable: bool, + pub inline: fn(&mut hir::Function, hir::BlockId, hir::InsnId, &[hir::InsnId], hir::InsnId) -> Option, } /// A safe default for un-annotated Ruby methods: we can't optimize them or their returned values. @@ -39,6 +41,7 @@ impl Default for FnProperties { leaf: false, return_type: types::BasicObject, elidable: false, + inline: no_inline, } } } @@ -152,12 +155,17 @@ pub fn init() -> Annotations { let builtin_funcs = &mut HashMap::new(); 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 }; + 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 }; + let mut props = FnProperties { no_gc: false, leaf: false, elidable: false, return_type: $return_type, inline: no_inline }; $( props.$properties = true; )* + #[allow(unused_unsafe)] annotate_c_method(cfuncs, unsafe { $module }, $method_name, props); } } @@ -171,14 +179,15 @@ pub fn init() -> Annotations { no_gc: false, leaf: false, elidable: false, - return_type: $return_type + return_type: $return_type, + inline: no_inline, }; $(props.$properties = true;)+ annotate_builtin_method(builtin_funcs, unsafe { $module }, $method_name, props); } } - annotate!(rb_mKernel, "itself", types::BasicObject, no_gc, leaf, elidable); + annotate!(rb_mKernel, "itself", inline_kernel_itself); annotate!(rb_cString, "bytesize", types::Fixnum, no_gc, leaf); annotate!(rb_cString, "to_s", types::StringExact); annotate!(rb_cModule, "name", types::StringExact.union(types::NilClass), no_gc, leaf, elidable); @@ -188,12 +197,16 @@ pub fn init() -> Annotations { annotate!(rb_cArray, "empty?", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cArray, "reverse", types::ArrayExact, leaf, elidable); annotate!(rb_cArray, "join", types::StringExact); + annotate!(rb_cArray, "[]", inline_array_aref); 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_cBasicObject, "==", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "!", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cBasicObject, "initialize", types::NilClass, no_gc, leaf, elidable); + annotate!(rb_cString, "to_s", inline_string_to_s); + let thread_singleton = unsafe { rb_singleton_class(rb_cThread) }; + annotate!(thread_singleton, "current", types::BasicObject, no_gc, leaf); annotate_builtin!(rb_mKernel, "Float", types::Float); annotate_builtin!(rb_mKernel, "Integer", types::Integer); @@ -204,3 +217,34 @@ pub fn init() -> Annotations { builtin_funcs: std::mem::take(builtin_funcs), } } + +fn no_inline(_fun: &mut hir::Function, _block: hir::BlockId, _recv: hir::InsnId, _args: &[hir::InsnId], _state: hir::InsnId) -> Option { + None +} + +fn inline_string_to_s(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + if args.len() == 0 && fun.likely_a(recv, types::StringExact, state) { + let recv = fun.coerce_to(block, recv, types::StringExact, state); + return Some(recv); + } + None +} + +fn inline_kernel_itself(_fun: &mut hir::Function, _block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { + if args.len() == 0 { + // No need to coerce the receiver; that is done by the SendWithoutBlock rewriting. + return Some(recv); + } + None +} + +fn inline_array_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + if let &[index] = args { + if fun.likely_a(index, types::Fixnum, state) { + let index = fun.coerce_to(block, index, types::Fixnum, state); + let result = fun.push_insn(block, hir::Insn::ArrayArefFixnum { array: recv, index }); + return Some(result); + } + } + None +} diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 8b04143f840deb..a032d9ec8a30a7 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1361,7 +1361,7 @@ impl Function { } // Add an instruction to an SSA block - fn push_insn(&mut self, block: BlockId, insn: Insn) -> InsnId { + pub fn push_insn(&mut self, block: BlockId, insn: Insn) -> InsnId { let is_param = matches!(insn, Insn::Param { .. }); let id = self.new_insn(insn); if is_param { @@ -1401,6 +1401,13 @@ impl Function { id } + fn remove_block(&mut self, block_id: BlockId) { + if BlockId(self.blocks.len() - 1) != block_id { + panic!("Can only remove the last block"); + } + self.blocks.pop(); + } + /// Return a reference to the Block at the given index. pub fn block(&self, block_id: BlockId) -> &Block { &self.blocks[block_id.0] @@ -1882,6 +1889,23 @@ impl Function { None } + pub fn likely_a(&self, val: InsnId, ty: Type, state: InsnId) -> bool { + if self.type_of(val).is_subtype(ty) { + return true; + } + let frame_state = self.frame_state(state); + let iseq_insn_idx = frame_state.insn_idx as usize; + let Some(profiled_type) = self.profiled_type_of_at(val, iseq_insn_idx) else { + return false; + }; + Type::from_profiled_type(profiled_type).is_subtype(ty) + } + + pub fn coerce_to(&mut self, block: BlockId, val: InsnId, guard_type: Type, state: InsnId) -> InsnId { + if self.is_a(val, guard_type) { return val; } + self.push_insn(block, Insn::GuardType { val, guard_type, state }) + } + fn likely_is_fixnum(&self, val: InsnId, profiled_type: ProfiledType) -> bool { self.is_a(val, types::Fixnum) || profiled_type.is_fixnum() } @@ -1958,40 +1982,6 @@ impl Function { } } - fn try_rewrite_aref(&mut self, block: BlockId, orig_insn_id: InsnId, self_val: InsnId, idx_val: InsnId, state: InsnId) { - if !unsafe { rb_BASIC_OP_UNREDEFINED_P(BOP_AREF, ARRAY_REDEFINED_OP_FLAG) } { - // If the basic operation is already redefined, we cannot optimize it. - self.push_insn_id(block, orig_insn_id); - return; - } - let self_type = self.type_of(self_val); - let idx_type = self.type_of(idx_val); - if self_type.is_subtype(types::ArrayExact) { - if let Some(array_obj) = self_type.ruby_object() { - if array_obj.is_frozen() { - if let Some(idx) = idx_type.fixnum_value() { - self.push_insn(block, Insn::PatchPoint { invariant: Invariant::BOPRedefined { klass: ARRAY_REDEFINED_OP_FLAG, bop: BOP_AREF }, state }); - let val = unsafe { rb_yarv_ary_entry_internal(array_obj, idx) }; - let const_insn = self.push_insn(block, Insn::Const { val: Const::Value(val) }); - self.make_equal_to(orig_insn_id, const_insn); - return; - } - } - } - if self.type_of(idx_val).is_subtype(types::Fixnum) { - self.push_insn(block, Insn::PatchPoint { invariant: Invariant::BOPRedefined { klass: ARRAY_REDEFINED_OP_FLAG, bop: BOP_AREF }, state }); - let fixnum_idx = self.push_insn(block, Insn::GuardType { val: idx_val, guard_type: types::Fixnum, state }); - let result = self.push_insn(block, Insn::ArrayArefFixnum { - array: self_val, - index: fixnum_idx, - }); - self.make_equal_to(orig_insn_id, result); - return; - } - } - self.push_insn_id(block, orig_insn_id); - } - /// Rewrite SendWithoutBlock opcodes into SendWithoutBlockDirect opcodes if we know the target /// ISEQ statically. This removes run-time method lookups and opens the door for inlining. /// Also try and inline constant caches, specialize object allocations, and more. @@ -2031,8 +2021,6 @@ impl Function { self.try_rewrite_freeze(block, insn_id, recv, state), Insn::SendWithoutBlock { recv, args, state, cd, .. } if ruby_call_method_id(cd) == ID!(minusat) && args.is_empty() => self.try_rewrite_uminus(block, insn_id, recv, state), - Insn::SendWithoutBlock { recv, args, state, cd, .. } if ruby_call_method_id(cd) == ID!(aref) && args.len() == 1 => - self.try_rewrite_aref(block, insn_id, recv, args[0], state), Insn::SendWithoutBlock { mut recv, cd, args, state, .. } => { let frame_state = self.frame_state(state); let (klass, profiled_type) = if let Some(klass) = self.type_of(recv).runtime_exact_ruby_class() { @@ -2392,19 +2380,35 @@ impl Function { if recv_class.instance_can_have_singleton_class() { fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: recv_class }, state }); } + + let props = ZJITState::get_method_annotations().get_cfunc_properties(method); + if props.is_none() && get_option!(stats) { + count_not_annotated_cfunc(fun, block, method); + } + let props = props.unwrap_or_default(); + if let Some(profiled_type) = profiled_type { // Guard receiver class recv = fun.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); + fun.insn_types[recv.0] = fun.infer_type(recv); } + + // Try inlining the cfunc into HIR + let tmp_block = fun.new_block(u32::MAX); + if let Some(replacement) = (props.inline)(fun, tmp_block, recv, &args, state) { + // Copy contents of tmp_block to block + assert_ne!(block, tmp_block); + let insns = fun.blocks[tmp_block.0].insns.drain(..).collect::>(); + fun.blocks[block.0].insns.extend(insns); + fun.make_equal_to(send_insn_id, replacement); + fun.remove_block(tmp_block); + return Ok(()); + } + + // No inlining; emit a call let cfunc = unsafe { get_mct_func(cfunc) }.cast(); let mut cfunc_args = vec![recv]; cfunc_args.append(&mut args); - - let props = ZJITState::get_method_annotations().get_cfunc_properties(method); - if props.is_none() && get_option!(stats) { - count_not_annotated_cfunc(fun, block, method); - } - let props = props.unwrap_or_default(); let return_type = props.return_type; let elidable = props.elidable; // Filter for a leaf and GC free function @@ -2427,9 +2431,6 @@ 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 { - if get_option!(stats) { - count_not_inlined_cfunc(fun, block, method); - } gen_patch_points_for_optimized_ccall(fun, block, recv_class, method_id, method, state); if recv_class.instance_can_have_singleton_class() { @@ -2446,6 +2447,23 @@ impl Function { count_not_annotated_cfunc(fun, block, method); } let props = props.unwrap_or_default(); + + // Try inlining the cfunc into HIR + let tmp_block = fun.new_block(u32::MAX); + if let Some(replacement) = (props.inline)(fun, tmp_block, recv, &args, state) { + // Copy contents of tmp_block to block + assert_ne!(block, tmp_block); + let insns = fun.blocks[tmp_block.0].insns.drain(..).collect::>(); + fun.blocks[block.0].insns.extend(insns); + fun.make_equal_to(send_insn_id, replacement); + fun.remove_block(tmp_block); + return Ok(()); + } + + // No inlining; emit a call + if get_option!(stats) { + count_not_inlined_cfunc(fun, block, method); + } let return_type = props.return_type; let elidable = props.elidable; let ccall = fun.push_insn(block, Insn::CCallVariadic { @@ -2609,6 +2627,13 @@ impl Function { _ => None, }) } + Insn::ArrayArefFixnum { array, index } if self.type_of(array).ruby_object_known() + && self.type_of(index).ruby_object_known() => { + let array_obj = self.type_of(array).ruby_object().unwrap(); + let index = self.type_of(index).fixnum_value().unwrap(); + let val = unsafe { rb_yarv_ary_entry_internal(array_obj, index) }; + self.new_insn(Insn::Const { val: Const::Value(val) }) + } Insn::Test { val } if self.type_of(val).is_known_falsy() => { self.new_insn(Insn::Const { val: Const::CBool(false) }) } @@ -9183,7 +9208,7 @@ mod opt_tests { PatchPoint MethodRedefined(Array@0x1000, []@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) v26:ArrayExact = GuardType v9, ArrayExact - v27:BasicObject = CCallVariadic []@0x1038, v26, v13 + v27:BasicObject = ArrayArefFixnum v26, v13 CheckInterrupts Return v27 "); @@ -9860,9 +9885,8 @@ mod opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(Integer@0x1000, itself@0x1008, cme:0x1010) v22:Fixnum = GuardType v9, Fixnum - v23:BasicObject = CCall itself@0x1038, v22 CheckInterrupts - Return v23 + Return v22 "); } @@ -9884,9 +9908,8 @@ mod opt_tests { v11:ArrayExact = NewArray PatchPoint MethodRedefined(Array@0x1000, itself@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) - v22:BasicObject = CCall itself@0x1038, v11 CheckInterrupts - Return v22 + Return v11 "); } @@ -9913,7 +9936,6 @@ mod opt_tests { v14:ArrayExact = NewArray PatchPoint MethodRedefined(Array@0x1000, itself@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) - v30:BasicObject = CCall itself@0x1038, v14 PatchPoint NoEPEscape(test) v21:Fixnum[1] = Const Value(1) CheckInterrupts @@ -11528,9 +11550,8 @@ mod opt_tests { v13:Fixnum[1] = Const Value(1) CheckInterrupts PatchPoint MethodRedefined(Integer@0x1000, itself@0x1008, cme:0x1010) - v34:BasicObject = CCall itself@0x1038, v13 CheckInterrupts - Return v34 + Return v13 "); } @@ -11552,10 +11573,11 @@ mod opt_tests { PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v13:Fixnum[1] = Const Value(1) - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_AREF) - v24:Fixnum[5] = Const Value(5) + PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v27:Fixnum[5] = Const Value(5) CheckInterrupts - Return v24 + Return v27 "); } @@ -11577,10 +11599,11 @@ mod opt_tests { PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v13:Fixnum[-3] = Const Value(-3) - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_AREF) - v24:Fixnum[4] = Const Value(4) + PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v27:Fixnum[4] = Const Value(4) CheckInterrupts - Return v24 + Return v27 "); } @@ -11602,10 +11625,11 @@ mod opt_tests { PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v13:Fixnum[-10] = Const Value(-10) - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_AREF) - v24:NilClass = Const Value(nil) + PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v27:NilClass = Const Value(nil) CheckInterrupts - Return v24 + Return v27 "); } @@ -11627,10 +11651,11 @@ mod opt_tests { PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v13:Fixnum[10] = Const Value(10) - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_AREF) - v24:NilClass = Const Value(nil) + PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v27:NilClass = Const Value(nil) CheckInterrupts - Return v24 + Return v27 "); } @@ -11655,9 +11680,11 @@ mod opt_tests { PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) v12:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v13:Fixnum[10] = Const Value(10) - v17:BasicObject = SendWithoutBlock v12, :[], v13 + PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v25:BasicObject = SendWithoutBlockDirect v12, :[] (0x1040), v13 CheckInterrupts - Return v17 + Return v25 "); } @@ -12618,14 +12645,62 @@ mod opt_tests { v12:StringExact = StringCopy v10 PatchPoint MethodRedefined(String@0x1008, to_s@0x1010, cme:0x1018) PatchPoint NoSingletonClass(String@0x1008) - v23:StringExact = CCallWithFrame to_s@0x1040, v12 + 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) + 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 CheckInterrupts Return v23 "); } #[test] - fn test_array_aref_fixnum() { + fn test_array_aref_fixnum_literal() { eval(" def test arr = [1, 2, 3] @@ -12647,10 +12722,99 @@ mod opt_tests { v13:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v15:ArrayExact = ArrayDup v13 v18:Fixnum[0] = Const Value(0) - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_AREF) - v30:BasicObject = ArrayArefFixnum v15, v18 + PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) + PatchPoint NoSingletonClass(Array@0x1008) + v31:BasicObject = ArrayArefFixnum v15, v18 + 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 + 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:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] + v29:Fixnum = GuardType v12, Fixnum + v30:BasicObject = ArrayArefFixnum v28, v29 CheckInterrupts Return v30 "); } + + #[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) + v25:BasicObject = CCall current@0x1048, v21 + CheckInterrupts + Return v25 + "); + } }