diff --git a/encoding.c b/encoding.c index e9510fe3c146c5..8bb393b471ed54 100644 --- a/encoding.c +++ b/encoding.c @@ -1367,7 +1367,7 @@ enc_names_i(st_data_t name, st_data_t idx, st_data_t args) VALUE *arg = (VALUE *)args; if ((int)idx == (int)arg[0]) { - VALUE str = rb_enc_interned_str_cstr((char *)name, rb_usascii_encoding()); + VALUE str = rb_interned_str_cstr((char *)name); rb_ary_push(arg[1], str); } return ST_CONTINUE; @@ -1873,7 +1873,7 @@ static int rb_enc_name_list_i(st_data_t name, st_data_t idx, st_data_t arg) { VALUE ary = (VALUE)arg; - VALUE str = rb_enc_interned_str_cstr((char *)name, rb_usascii_encoding()); + VALUE str = rb_interned_str_cstr((char *)name); rb_ary_push(ary, str); return ST_CONTINUE; } @@ -1921,7 +1921,7 @@ rb_enc_aliases_enc_i(st_data_t name, st_data_t orig, st_data_t arg) str = rb_fstring_cstr(rb_enc_name(enc)); rb_ary_store(ary, idx, str); } - key = rb_enc_interned_str_cstr((char *)name, rb_usascii_encoding()); + key = rb_interned_str_cstr((char *)name); rb_hash_aset(aliases, key, str); return ST_CONTINUE; } diff --git a/ext/io/console/console.c b/ext/io/console/console.c index 2b0193bb90b631..7ddaf071a8833a 100644 --- a/ext/io/console/console.c +++ b/ext/io/console/console.c @@ -84,9 +84,9 @@ getattr(int fd, conmode *t) static ID id_getc, id_close; static ID id_gets, id_flush, id_chomp_bang; -#ifndef HAVE_RB_ENC_INTERNED_STR_CSTR +#ifndef HAVE_RB_INTERNED_STR_CSTR # define rb_str_to_interned_str(str) rb_str_freeze(str) -# define rb_enc_interned_str_cstr(str, enc) rb_str_freeze(rb_usascii_str_new_cstr(str)) +# define rb_interned_str_cstr(str) rb_str_freeze(rb_usascii_str_new_cstr(str)) #endif #if defined HAVE_RUBY_FIBER_SCHEDULER_H @@ -1897,7 +1897,7 @@ console_ttyname(VALUE io) size_t size = sizeof(termname); int e; if (ttyname_r(fd, tn, size) == 0) - return rb_enc_interned_str_cstr(tn, rb_usascii_encoding()); + return rb_interned_str_cstr(tn); if ((e = errno) == ERANGE) { VALUE s = rb_str_new(0, size); while (1) { @@ -1921,7 +1921,7 @@ console_ttyname(VALUE io) int e = errno; rb_syserr_fail_str(e, rb_sprintf("ttyname(%d)", fd)); } - return rb_enc_interned_str_cstr(tn, rb_usascii_encoding()); + return rb_interned_str_cstr(tn); } # else # error No ttyname function diff --git a/ext/io/console/extconf.rb b/ext/io/console/extconf.rb index e6254c9e90fe98..dd3d221ae51df3 100644 --- a/ext/io/console/extconf.rb +++ b/ext/io/console/extconf.rb @@ -9,7 +9,7 @@ have_func("rb_syserr_new_str(0, Qnil)") or abort -have_func("rb_enc_interned_str_cstr") +have_func("rb_interned_str_cstr") have_func("rb_io_path", "ruby/io.h") have_func("rb_io_descriptor", "ruby/io.h") have_func("rb_io_get_write_io", "ruby/io.h") diff --git a/gc/default/default.c b/gc/default/default.c index 45c0d3e2552fef..013c0749946e2d 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -9582,7 +9582,7 @@ rb_gc_impl_init(void) VALUE opts; /* \GC build options */ rb_define_const(rb_mGC, "OPTS", opts = rb_ary_new()); -#define OPT(o) if (o) rb_ary_push(opts, rb_enc_interned_str(#o, sizeof(#o) - 1, rb_usascii_encoding())) +#define OPT(o) if (o) rb_ary_push(opts, rb_interned_str(#o, sizeof(#o) - 1)) OPT(GC_DEBUG); OPT(USE_RGENGC); OPT(RGENGC_DEBUG); diff --git a/include/ruby/internal/intern/string.h b/include/ruby/internal/intern/string.h index 75a28143fb8f1e..2ec08fc81f9d13 100644 --- a/include/ruby/internal/intern/string.h +++ b/include/ruby/internal/intern/string.h @@ -444,8 +444,8 @@ VALUE rb_str_to_interned_str(VALUE str); * terminating NUL character. * @exception rb_eArgError `len` is negative. * @return A found or created instance of ::rb_cString, of `len` bytes - * length, of "binary" encoding, whose contents are identical to - * that of `ptr`. + * length, of US-ASCII or "binary" encoding, whose contents are + * identical to that of `ptr`. * @pre At least `len` bytes of continuous memory region shall be * accessible via `ptr`. */ diff --git a/jit.c b/jit.c index fb7a5bd47d4919..9399cb4026c6e8 100644 --- a/jit.c +++ b/jit.c @@ -191,6 +191,14 @@ rb_jit_get_proc_ptr(VALUE procv) return proc; } +VALUE +rb_optimized_call(VALUE *recv, rb_execution_context_t *ec, int argc, VALUE *argv, int kw_splat, VALUE block_handler) +{ + rb_proc_t *proc; + GetProcPtr(recv, proc); + return rb_vm_invoke_proc(ec, proc, argc, argv, kw_splat, block_handler); +} + unsigned int rb_jit_iseq_builtin_attrs(const rb_iseq_t *iseq) { diff --git a/string.c b/string.c index d564c2e2e1bf94..2d74c46a360aa8 100644 --- a/string.c +++ b/string.c @@ -12709,7 +12709,15 @@ VALUE rb_interned_str(const char *ptr, long len) { struct RString fake_str = {RBASIC_INIT}; - return register_fstring(setup_fake_str(&fake_str, ptr, len, ENCINDEX_ASCII_8BIT), true, false); + int encidx = ENCINDEX_US_ASCII; + int coderange = ENC_CODERANGE_7BIT; + if (len > 0 && search_nonascii(ptr, ptr + len)) { + encidx = ENCINDEX_ASCII_8BIT; + coderange = ENC_CODERANGE_VALID; + } + VALUE str = setup_fake_str(&fake_str, ptr, len, encidx); + ENC_CODERANGE_SET(str, coderange); + return register_fstring(str, true, false); } VALUE diff --git a/test/-ext-/string/test_interned_str.rb b/test/-ext-/string/test_interned_str.rb index 340dba41e8c48b..a81cb59aa5fd34 100644 --- a/test/-ext-/string/test_interned_str.rb +++ b/test/-ext-/string/test_interned_str.rb @@ -9,4 +9,9 @@ def test_interned_str src << "b" * 20 assert_equal "a" * 20, interned_str end + + def test_interned_str_encoding + src = :ascii.name + assert_equal Encoding::US_ASCII, Bug::String.rb_interned_str_dup(src).encoding + end end diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 6e46346d5af983..ad2df806d59958 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -470,6 +470,74 @@ def test(&block) }, insns: [:getblockparamproxy] end + def test_optimized_method_call_proc_call + assert_compiles '2', %q{ + p = proc { |x| x * 2 } + def test(p) + p.call(1) + end + test(p) + test(p) + }, call_threshold: 2, insns: [:opt_send_without_block] + end + + def test_optimized_method_call_proc_aref + assert_compiles '4', %q{ + p = proc { |x| x * 2 } + def test(p) + p[2] + end + test(p) + test(p) + }, call_threshold: 2, insns: [:opt_aref] + end + + def test_optimized_method_call_proc_yield + assert_compiles '6', %q{ + p = proc { |x| x * 2 } + def test(p) + p.yield(3) + end + test(p) + test(p) + }, call_threshold: 2, insns: [:opt_send_without_block] + end + + def test_optimized_method_call_proc_kw_splat + assert_compiles '3', %q{ + p = proc { |**kw| kw[:a] + kw[:b] } + def test(p, h) + p.call(**h) + end + h = { a: 1, b: 2 } + test(p, h) + test(p, h) + }, call_threshold: 2, insns: [:opt_send_without_block] + end + + def test_optimized_method_call_proc_call_splat + assert_compiles '43', %q{ + p = proc { |x| x + 1 } + def test(p) + ary = [42] + p.call(*ary) + end + test(p) + test(p) + }, call_threshold: 2 + end + + def test_optimized_method_call_proc_call_kwarg + assert_compiles '1', %q{ + p = proc { |a:| a } + def test(p) + p.call(a: 1) + end + test(p) + test(p) + }, call_threshold: 2 + end + def test_call_a_forwardable_method assert_runs '[]', %q{ def test_root = forwardable diff --git a/yjit.c b/yjit.c index 1cd934c42eee63..2e7216a1915406 100644 --- a/yjit.c +++ b/yjit.c @@ -223,14 +223,6 @@ typedef struct rb_iseq_param_keyword rb_seq_param_keyword_struct; ID rb_get_symbol_id(VALUE namep); -VALUE -rb_optimized_call(VALUE *recv, rb_execution_context_t *ec, int argc, VALUE *argv, int kw_splat, VALUE block_handler) -{ - rb_proc_t *proc; - GetProcPtr(recv, proc); - return rb_vm_invoke_proc(ec, proc, argc, argv, kw_splat, block_handler); -} - // If true, the iseq has only opt_invokebuiltin_delegate(_leave) and leave insns. static bool invokebuiltin_delegate_leave_p(const rb_iseq_t *iseq) diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 9fbcf2169f8779..56994388a3a4a1 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -1164,14 +1164,6 @@ extern "C" { pub fn rb_iseq_get_yjit_payload(iseq: *const rb_iseq_t) -> *mut ::std::os::raw::c_void; pub fn rb_iseq_set_yjit_payload(iseq: *const rb_iseq_t, payload: *mut ::std::os::raw::c_void); pub fn rb_get_symbol_id(namep: VALUE) -> ID; - pub fn rb_optimized_call( - recv: *mut VALUE, - ec: *mut rb_execution_context_t, - argc: ::std::os::raw::c_int, - argv: *mut VALUE, - kw_splat: ::std::os::raw::c_int, - block_handler: VALUE, - ) -> VALUE; pub fn rb_yjit_builtin_function(iseq: *const rb_iseq_t) -> *const rb_builtin_function; pub fn rb_yjit_str_simple_append(str1: VALUE, str2: VALUE) -> VALUE; pub fn rb_vm_base_ptr(cfp: *mut rb_control_frame_struct) -> *mut VALUE; @@ -1240,6 +1232,14 @@ extern "C" { pub fn rb_get_def_original_id(def: *const rb_method_definition_t) -> ID; pub fn rb_get_def_bmethod_proc(def: *mut rb_method_definition_t) -> VALUE; pub fn rb_jit_get_proc_ptr(procv: VALUE) -> *mut rb_proc_t; + pub fn rb_optimized_call( + recv: *mut VALUE, + ec: *mut rb_execution_context_t, + argc: ::std::os::raw::c_int, + argv: *mut VALUE, + kw_splat: ::std::os::raw::c_int, + block_handler: VALUE, + ) -> VALUE; pub fn rb_jit_iseq_builtin_attrs(iseq: *const rb_iseq_t) -> ::std::os::raw::c_uint; pub fn rb_get_mct_argc(mct: *const rb_method_cfunc_t) -> ::std::os::raw::c_int; pub fn rb_get_mct_func(mct: *const rb_method_cfunc_t) -> *mut ::std::os::raw::c_void; diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 1c453aed771055..7afcff5863bc84 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -404,6 +404,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::SendWithoutBlockDirect { cme, iseq, recv, args, state, .. } => gen_send_iseq_direct(cb, jit, asm, *cme, *iseq, opnd!(recv), opnds!(args), &function.frame_state(*state), None), &Insn::InvokeSuper { cd, blockiseq, state, reason, .. } => gen_invokesuper(jit, asm, cd, blockiseq, &function.frame_state(state), reason), &Insn::InvokeBlock { cd, state, reason, .. } => gen_invokeblock(jit, asm, cd, &function.frame_state(state), reason), + Insn::InvokeProc { recv, args, state, kw_splat } => gen_invokeproc(jit, asm, opnd!(recv), opnds!(args), *kw_splat, &function.frame_state(*state)), // Ensure we have enough room fit ec, self, and arguments // TODO remove this check when we have stack args (we can use Time.new to test it) Insn::InvokeBuiltin { bf, state, .. } if bf.argc + 2 > (C_ARG_OPNDS.len() as i32) => return Err(*state), @@ -1497,6 +1498,35 @@ fn gen_invokeblock( ) } +fn gen_invokeproc( + jit: &mut JITState, + asm: &mut Assembler, + recv: Opnd, + args: Vec, + kw_splat: bool, + state: &FrameState, +) -> lir::Opnd { + gen_prepare_non_leaf_call(jit, asm, state); + + asm_comment!(asm, "call invokeproc"); + + let argv_ptr = gen_push_opnds(asm, &args); + let kw_splat_opnd = Opnd::Imm(i64::from(kw_splat)); + let result = asm_ccall!( + asm, + rb_optimized_call, + recv, + EC, + args.len().into(), + argv_ptr, + kw_splat_opnd, + VM_BLOCK_HANDLER_NONE.into() + ); + gen_pop_opnds(asm, &args); + + result +} + /// Compile a dynamic dispatch for `super` fn gen_invokesuper( jit: &mut JITState, diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 2201bdcffee776..15533180dad72f 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -2114,6 +2114,14 @@ unsafe extern "C" { pub fn rb_get_def_original_id(def: *const rb_method_definition_t) -> ID; pub fn rb_get_def_bmethod_proc(def: *mut rb_method_definition_t) -> VALUE; pub fn rb_jit_get_proc_ptr(procv: VALUE) -> *mut rb_proc_t; + pub fn rb_optimized_call( + recv: *mut VALUE, + ec: *mut rb_execution_context_t, + argc: ::std::os::raw::c_int, + argv: *mut VALUE, + kw_splat: ::std::os::raw::c_int, + block_handler: VALUE, + ) -> VALUE; pub fn rb_jit_iseq_builtin_attrs(iseq: *const rb_iseq_t) -> ::std::os::raw::c_uint; pub fn rb_get_mct_argc(mct: *const rb_method_cfunc_t) -> ::std::os::raw::c_int; pub fn rb_get_mct_func(mct: *const rb_method_cfunc_t) -> *mut ::std::os::raw::c_void; diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 29c34694b7e8c1..6c2bd09ad3181b 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -932,6 +932,13 @@ pub enum Insn { state: InsnId, reason: SendFallbackReason, }, + /// Call Proc#call optimized method type. + InvokeProc { + recv: InsnId, + args: Vec, + state: InsnId, + kw_splat: bool, + }, /// Optimized ISEQ call SendWithoutBlockDirect { @@ -1198,6 +1205,7 @@ impl Insn { Insn::IncrCounter(_) => effects::Any, Insn::IncrCounterPtr { .. } => effects::Any, Insn::CheckInterrupts { .. } => effects::Any, + Insn::InvokeProc { .. } => effects::Any, } } @@ -1452,6 +1460,16 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { write!(f, " # SendFallbackReason: {reason}")?; Ok(()) } + Insn::InvokeProc { recv, args, kw_splat, .. } => { + write!(f, "InvokeProc {recv}")?; + for arg in args { + write!(f, ", {arg}")?; + } + if *kw_splat { + write!(f, ", kw_splat")?; + } + Ok(()) + } Insn::InvokeBuiltin { bf, args, leaf, .. } => { let bf_name = unsafe { CStr::from_ptr(bf.name) }.to_str().unwrap(); write!(f, "InvokeBuiltin{} {}", @@ -2228,6 +2246,12 @@ impl Function { state, reason, }, + &InvokeProc { recv, ref args, state, kw_splat } => InvokeProc { + recv: find!(recv), + args: find_vec!(args), + state: find!(state), + kw_splat, + }, &InvokeBuiltin { bf, recv, ref args, state, leaf, return_type } => InvokeBuiltin { bf, recv: find!(recv), args: find_vec!(args), state, leaf, return_type }, &ArrayDup { val, state } => ArrayDup { val: find!(val), state }, &HashDup { val, state } => HashDup { val: find!(val), state }, @@ -2416,6 +2440,7 @@ impl Function { Insn::SendForward { .. } => types::BasicObject, Insn::InvokeSuper { .. } => types::BasicObject, Insn::InvokeBlock { .. } => types::BasicObject, + Insn::InvokeProc { .. } => types::BasicObject, Insn::InvokeBuiltin { return_type, .. } => return_type.unwrap_or(types::BasicObject), Insn::Defined { pushval, .. } => Type::from_value(*pushval).union(types::NilClass), Insn::DefinedIvar { pushval, .. } => Type::from_value(*pushval).union(types::NilClass), @@ -2828,14 +2853,7 @@ impl Function { }; let ci = unsafe { get_call_data_ci(cd) }; // info about the call site - // If the call site info indicates that the `Function` has overly complex arguments, then - // do not optimize into a `SendWithoutBlockDirect`. let flags = unsafe { rb_vm_ci_flag(ci) }; - if unspecializable_call_type(flags) { - self.count_complex_call_features(block, flags); - self.set_dynamic_send_reason(insn_id, ComplexArgPass); - self.push_insn_id(block, insn_id); continue; - } let mid = unsafe { vm_ci_mid(ci) }; // Do method lookup @@ -2863,6 +2881,14 @@ impl Function { def_type = unsafe { get_cme_def_type(cme) }; } + // If the call site info indicates that the `Function` has overly complex arguments, then do not optimize into a `SendWithoutBlockDirect`. + // Optimized methods(`VM_METHOD_TYPE_OPTIMIZED`) handle their own argument constraints (e.g., kw_splat for Proc call). + if def_type != VM_METHOD_TYPE_OPTIMIZED && unspecializable_call_type(flags) { + self.count_complex_call_features(block, flags); + self.set_dynamic_send_reason(insn_id, ComplexArgPass); + self.push_insn_id(block, insn_id); continue; + } + if def_type == VM_METHOD_TYPE_ISEQ { // TODO(max): Allow non-iseq; cache cme // Only specialize positional-positional calls @@ -2993,7 +3019,31 @@ impl Function { } else if def_type == VM_METHOD_TYPE_OPTIMIZED { let opt_type: OptimizedMethodType = unsafe { get_cme_def_body_optimized_type(cme) }.into(); match (opt_type, args.as_slice()) { + (OptimizedMethodType::Call, _) => { + if flags & (VM_CALL_ARGS_SPLAT | VM_CALL_KWARG) != 0 { + self.count_complex_call_features(block, flags); + self.set_dynamic_send_reason(insn_id, ComplexArgPass); + self.push_insn_id(block, insn_id); continue; + } + // Check singleton class assumption first, before emitting other patchpoints + if !self.assume_no_singleton_classes(block, klass, state) { + self.set_dynamic_send_reason(insn_id, SingletonClassSeen); + self.push_insn_id(block, insn_id); continue; + } + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); + if let Some(profiled_type) = profiled_type { + recv = self.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state }); + } + let kw_splat = flags & VM_CALL_KW_SPLAT != 0; + let invoke_proc = self.push_insn(block, Insn::InvokeProc { recv, args: args.clone(), state, kw_splat }); + self.make_equal_to(insn_id, invoke_proc); + } (OptimizedMethodType::StructAref, &[]) | (OptimizedMethodType::StructAset, &[_]) => { + if unspecializable_call_type(flags) { + self.count_complex_call_features(block, flags); + self.set_dynamic_send_reason(insn_id, ComplexArgPass); + self.push_insn_id(block, insn_id); continue; + } let index: i32 = unsafe { get_cme_def_body_optimized_index(cme) } .try_into() .unwrap(); @@ -4416,7 +4466,8 @@ impl Function { | &Insn::CCallWithFrame { recv, ref args, state, .. } | &Insn::SendWithoutBlockDirect { recv, ref args, state, .. } | &Insn::InvokeBuiltin { recv, ref args, state, .. } - | &Insn::InvokeSuper { recv, ref args, state, .. } => { + | &Insn::InvokeSuper { recv, ref args, state, .. } + | &Insn::InvokeProc { recv, ref args, state, .. } => { worklist.push_back(recv); worklist.extend(args); worklist.push_back(state); @@ -5041,6 +5092,7 @@ impl Function { | Insn::CCallWithFrame { recv, ref args, .. } | Insn::CCallVariadic { recv, ref args, .. } | Insn::InvokeBuiltin { recv, ref args, .. } + | Insn::InvokeProc { recv, ref args, .. } | Insn::ArrayInclude { target: recv, elements: ref args, .. } => { self.assert_subtype(insn_id, recv, types::BasicObject)?; for &arg in args { diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 08aa4474219032..60b4c2598629fc 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -3817,6 +3817,186 @@ mod hir_opt_tests { "); } + #[test] + fn test_specialize_proc_call() { + eval(" + p = proc { |x| x + 1 } + def test(p) + p.call(1) + end + test p + "); + assert_snapshot!(hir_string("test"), @" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal :p, 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) + PatchPoint NoSingletonClass(Proc@0x1000) + PatchPoint MethodRedefined(Proc@0x1000, call@0x1008, cme:0x1010) + v23:HeapObject[class_exact:Proc] = GuardType v9, HeapObject[class_exact:Proc] + v24:BasicObject = InvokeProc v23, v14 + CheckInterrupts + Return v24 + "); + } + + #[test] + fn test_specialize_proc_aref() { + eval(" + p = proc { |x| x + 1 } + def test(p) + p[2] + end + test p + "); + assert_snapshot!(hir_string("test"), @" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal :p, 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[2] = Const Value(2) + PatchPoint NoSingletonClass(Proc@0x1000) + PatchPoint MethodRedefined(Proc@0x1000, []@0x1008, cme:0x1010) + v24:HeapObject[class_exact:Proc] = GuardType v9, HeapObject[class_exact:Proc] + v25:BasicObject = InvokeProc v24, v14 + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_specialize_proc_yield() { + eval(" + p = proc { |x| x + 1 } + def test(p) + p.yield(3) + end + test p + "); + assert_snapshot!(hir_string("test"), @" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal :p, 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[3] = Const Value(3) + PatchPoint NoSingletonClass(Proc@0x1000) + PatchPoint MethodRedefined(Proc@0x1000, yield@0x1008, cme:0x1010) + v23:HeapObject[class_exact:Proc] = GuardType v9, HeapObject[class_exact:Proc] + v24:BasicObject = InvokeProc v23, v14 + CheckInterrupts + Return v24 + "); + } + + #[test] + fn test_specialize_proc_eqq() { + eval(" + p = proc { |x| x > 0 } + def test(p) + p === 1 + end + test p + "); + assert_snapshot!(hir_string("test"), @" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal :p, 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) + PatchPoint NoSingletonClass(Proc@0x1000) + PatchPoint MethodRedefined(Proc@0x1000, ===@0x1008, cme:0x1010) + v23:HeapObject[class_exact:Proc] = GuardType v9, HeapObject[class_exact:Proc] + v24:BasicObject = InvokeProc v23, v14 + CheckInterrupts + Return v24 + "); + } + + #[test] + fn test_dont_specialize_proc_call_splat() { + eval(" + p = proc { } + def test(p) + empty = [] + p.call(*empty) + end + test p + "); + assert_snapshot!(hir_string("test"), @" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal :p, 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 = NewArray + v22:ArrayExact = ToArray v16 + IncrCounter complex_arg_pass_caller_splat + v24:BasicObject = SendWithoutBlock v11, :call, v22 # SendFallbackReason: Complex argument passing + CheckInterrupts + Return v24 + "); + } + + #[test] + fn test_dont_specialize_proc_call_kwarg() { + eval(" + p = proc { |a:| a } + def test(p) + p.call(a: 1) + end + test p + "); + assert_snapshot!(hir_string("test"), @" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal :p, 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) + IncrCounter complex_arg_pass_caller_kwarg + v16:BasicObject = SendWithoutBlock v9, :call, v14 # SendFallbackReason: Complex argument passing + CheckInterrupts + Return v16 + "); + } + #[test] fn test_dont_specialize_definedivar_with_t_data() { eval("