diff --git a/NEWS.md b/NEWS.md index 0d725703b9c9f9..13839ed6076907 100644 --- a/NEWS.md +++ b/NEWS.md @@ -150,6 +150,10 @@ Ruby 4.0 bundled RubyGems and Bundler version 4. see the following links for det ## Compatibility issues +* `Kernel#at_exit` and `END {}` now raise `Ractor::IsolationError` when called + in a non-main Ractor. Previously the registered handler ran in the main + Ractor at process exit, which was confusing. [[Feature #22139]] + ## Stdlib compatibility issues ## C API updates @@ -207,6 +211,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #21861]: https://bugs.ruby-lang.org/issues/21861 [Feature #21932]: https://bugs.ruby-lang.org/issues/21932 [Feature #21981]: https://bugs.ruby-lang.org/issues/21981 +[Feature #22139]: https://bugs.ruby-lang.org/issues/22139 [PR #17201]: https://github.com/ruby/ruby/pull/17201 [RubyGems-v4.0.4]: https://github.com/rubygems/rubygems/releases/tag/v4.0.4 [RubyGems-v4.0.5]: https://github.com/rubygems/rubygems/releases/tag/v4.0.5 diff --git a/depend b/depend index bfde95272b0d95..96d0485d527d51 100644 --- a/depend +++ b/depend @@ -5452,6 +5452,7 @@ eval.$(OBJEXT): $(top_srcdir)/internal/imemo.h eval.$(OBJEXT): $(top_srcdir)/internal/inits.h eval.$(OBJEXT): $(top_srcdir)/internal/io.h eval.$(OBJEXT): $(top_srcdir)/internal/object.h +eval.$(OBJEXT): $(top_srcdir)/internal/ractor.h eval.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h eval.$(OBJEXT): $(top_srcdir)/internal/serial.h eval.$(OBJEXT): $(top_srcdir)/internal/set_table.h @@ -19742,6 +19743,7 @@ vm.$(OBJEXT): $(top_srcdir)/internal/numeric.h vm.$(OBJEXT): $(top_srcdir)/internal/object.h vm.$(OBJEXT): $(top_srcdir)/internal/parse.h vm.$(OBJEXT): $(top_srcdir)/internal/proc.h +vm.$(OBJEXT): $(top_srcdir)/internal/ractor.h vm.$(OBJEXT): $(top_srcdir)/internal/random.h vm.$(OBJEXT): $(top_srcdir)/internal/rational.h vm.$(OBJEXT): $(top_srcdir)/internal/re.h diff --git a/eval_jump.c b/eval_jump.c index 6ee8ff4a6f5f9a..7737148644ce37 100644 --- a/eval_jump.c +++ b/eval_jump.c @@ -4,6 +4,7 @@ */ #include "eval_intern.h" +#include "internal/ractor.h" /* exit */ @@ -42,6 +43,7 @@ rb_f_at_exit(VALUE _) if (!rb_block_given_p()) { rb_raise(rb_eArgError, "called without a block"); } + rb_ractor_ensure_main_ractor("can not call at_exit from non-main Ractors"); proc = rb_block_proc(); rb_set_end_proc(rb_call_end_proc, proc); return proc; diff --git a/test/ruby/test_ractor.rb b/test/ruby/test_ractor.rb index 65c4756dc0bc02..9c4bcad7f00742 100644 --- a/test/ruby/test_ractor.rb +++ b/test/ruby/test_ractor.rb @@ -240,6 +240,28 @@ def test_fork_raise_isolation_error RUBY end if Process.respond_to?(:fork) + def test_at_exit_raise_isolation_error + assert_ractor(<<~'RUBY') + ractor = Ractor.new do + at_exit { } + rescue Ractor::IsolationError => e + e + end + assert_equal Ractor::IsolationError, ractor.value.class + RUBY + end + + def test_END_raise_isolation_error + assert_ractor(<<~'RUBY', ignore_stderr: true) + ractor = Ractor.new do + END { nil } + rescue Ractor::IsolationError => e + e + end + assert_equal Ractor::IsolationError, ractor.value.class + RUBY + end + def test_require_raises_and_no_ractor_belonging_issue assert_ractor(<<~'RUBY') require "tempfile" diff --git a/thread_pthread.c b/thread_pthread.c index 4912711506b277..1b664afbb1456a 100644 --- a/thread_pthread.c +++ b/thread_pthread.c @@ -346,9 +346,9 @@ struct rb_thread_context { bool dead; }; -static bool thread_sched_reclaim_from(struct coroutine_context *returning_from); +static bool thread_sched_reclaim(struct coroutine_context *dead_co); #endif -static struct coroutine_context *coroutine_transfer0(struct coroutine_context *transfer_from, +static void coroutine_transfer0(struct coroutine_context *transfer_from, struct coroutine_context *transfer_to, bool to_dead); #define thread_sched_dump(s) thread_sched_dump_(__FILE__, __LINE__, s) @@ -1264,7 +1264,7 @@ rb_thread_sched_init(struct rb_thread_sched *sched, bool atfork) #endif } -static struct coroutine_context * +static void coroutine_transfer0(struct coroutine_context *transfer_from, struct coroutine_context *transfer_to, bool to_dead) { #ifdef RUBY_ASAN_ENABLED @@ -1279,6 +1279,7 @@ coroutine_transfer0(struct coroutine_context *transfer_from, struct coroutine_co __tsan_switch_to_fiber(transfer_to->tsan_fiber, 0); #endif + RBIMPL_ATTR_MAYBE_UNUSED() struct coroutine_context *returning_from = coroutine_transfer(transfer_from, transfer_to); /* if to_dead was passed, the caller is promising that this coroutine is finished and it should @@ -1288,11 +1289,9 @@ coroutine_transfer0(struct coroutine_context *transfer_from, struct coroutine_co __sanitizer_finish_switch_fiber(transfer_from->fake_stack, (const void**)&returning_from->stack_base, &returning_from->stack_size); #endif - - return returning_from; } -static struct coroutine_context * +static void thread_sched_switch0(struct coroutine_context *current_cont, rb_thread_t *next_th, struct rb_native_thread *nt, bool to_dead) { VM_ASSERT(!nt->dedicated); @@ -1307,7 +1306,7 @@ thread_sched_switch0(struct coroutine_context *current_cont, rb_thread_t *next_t ruby_thread_set_native(next_th); native_thread_assign(nt, next_th); - return coroutine_transfer0(current_cont, next_th->sched.context, to_dead); + coroutine_transfer0(current_cont, next_th->sched.context, to_dead); } static void @@ -2436,12 +2435,16 @@ nt_start(void *ptr) if (next_th && next_th->nt == NULL) { RUBY_DEBUG_LOG("nt:%d next_th:%d", (int)nt->serial, (int)next_th->serial); #if USE_MN_THREADS - struct coroutine_context *from = thread_sched_switch0(nt->nt_context, next_th, nt, false); - if (thread_sched_reclaim_from(from)) { - // A terminal transfer: the dying thread released - // the sched lock before transferring (its Ractor - // may be gone by now); we resumed with NO lock - // held and must not touch the sched again. + thread_sched_switch0(nt->nt_context, next_th, nt, false); + + // If a coroutine terminated during the transfer, co_start + // recorded it in nt->dead_co (switch0's return value is + // backend-dependent, unusable; see thread_pthread.h). + struct coroutine_context *dead_co = nt->dead_co; + nt->dead_co = NULL; + if (thread_sched_reclaim(dead_co)) { + // it already released the sched lock before its + // transfer (its Ractor may be gone): leave sched be. locked = false; } #else @@ -2477,16 +2480,15 @@ static int native_thread_create_shared(rb_thread_t *th); static void nt_free_stack(void *mstack); -// Reclaim the coroutine context the nt scheduling loop just resumed from, -// if its thread terminated. A dying thread always returns to its native -// thread's own context (co_start's epilogue), so this is the only place -// terminal transfers arrive -- and our running here proves the transfer's -// register save into the block has completed. Returns true for a terminal -// transfer, whose dying thread RELEASED the sched lock before transferring. +// Reclaim the context a coroutine thread recorded in nt->dead_co before its +// final transfer (co_start's epilogue). Our running here proves that transfer's +// register save into the block completed. Returns true when a thread did +// terminate -- it RELEASED the sched lock before transferring; NULL/false means +// a live yield, where the loop still owns the lock. static bool -thread_sched_reclaim_from(struct coroutine_context *returning_from) +thread_sched_reclaim(struct coroutine_context *dead_co) { - struct rb_thread_context *tctx = (struct rb_thread_context *)returning_from; + struct rb_thread_context *tctx = (struct rb_thread_context *)dead_co; if (tctx != NULL && tctx->dead) { nt_free_stack(tctx->stack); @@ -2512,7 +2514,7 @@ rb_threadptr_sched_free(rb_thread_t *th) else if (th->sched.context != NULL) { // a coroutine thread that never reached its epilogue (never started); // a terminated one is reclaimed by whoever resumed from its final - // transfer (thread_sched_reclaim_from), and cleared this pointer. + // transfer (thread_sched_reclaim), and cleared this pointer. struct rb_thread_context *tctx = (struct rb_thread_context *)th->sched.context; nt_free_stack(tctx->stack); SIZED_FREE(tctx); diff --git a/thread_pthread.h b/thread_pthread.h index cbc2d5ee4abf0a..cf717aef2f75f5 100644 --- a/thread_pthread.h +++ b/thread_pthread.h @@ -112,6 +112,11 @@ struct rb_native_thread { struct coroutine_context *nt_context; int dedicated; + + // A terminating coroutine records its context here before its final + // transfer; this nt's loop reclaims it. (Not via coroutine_transfer()'s + // return value: its meaning differs between the amd64 asm and ucontext.) + struct coroutine_context *dead_co; }; #undef except diff --git a/thread_pthread_mn.c b/thread_pthread_mn.c index 2efde13702e420..422aae7b00034e 100644 --- a/thread_pthread_mn.c +++ b/thread_pthread_mn.c @@ -549,6 +549,9 @@ co_start(struct coroutine_context *from, struct coroutine_context *self) // this native thread's own context, where the nt loop reclaims tctx. struct rb_thread_context *tctx = (struct rb_thread_context *)self; tctx->dead = true; + // Hand this context to the nt's loop, which reclaims it right after the + // final transfer returns from switch0. + tctx->nt->dead_co = &tctx->co; coroutine_transfer0(&tctx->co, tctx->nt->nt_context, true); rb_bug("unreachable"); diff --git a/vm.c b/vm.c index 86c0e3391553a6..d0f944592ae55a 100644 --- a/vm.c +++ b/vm.c @@ -24,6 +24,7 @@ #include "internal/missing.h" #include "internal/object.h" #include "internal/proc.h" +#include "internal/ractor.h" #include "internal/re.h" #include "internal/ruby_parser.h" #include "internal/st.h" @@ -4115,6 +4116,7 @@ m_core_undef_method(VALUE self, VALUE cbase, VALUE sym) static VALUE m_core_set_postexe(VALUE self) { + rb_ractor_ensure_main_ractor("can not use END{} in non-main Ractors"); rb_set_end_proc(rb_call_end_proc, rb_block_proc()); return Qnil; } diff --git a/zjit.rb b/zjit.rb index bff9cfe1538621..22eba6e1475260 100644 --- a/zjit.rb +++ b/zjit.rb @@ -152,9 +152,12 @@ def stats_string :compile_hir_build_time_ns, :compile_hir_strength_reduce_time_ns, :compile_hir_inline_methods_time_ns, + :compile_hir_optimize_load_store_time_ns, :compile_hir_canonicalize_time_ns, :compile_hir_fold_constants_time_ns, :compile_hir_clean_cfg_time_ns, + :compile_hir_remove_redundant_patch_points_time_ns, + :compile_hir_remove_duplicate_check_interrupts_time_ns, :compile_hir_eliminate_dead_code_time_ns, :compile_lir_time_ns, :profile_time_ns, diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 95f7ee87cc5e9c..cc28a6e05e8ceb 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -222,7 +222,7 @@ fn main() { .allowlist_type("ruby_rstring_private_flags") .allowlist_function("rb_ec_str_resurrect") .allowlist_function("rb_str_concat_literals") - .allowlist_function("rb_obj_as_string_result") + .allowlist_function("rb_any_to_s") .allowlist_function("rb_str_byte_substr") .allowlist_function("rb_str_substr_two_fixnums") .allowlist_function("rb_backref_get") diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 0ad066c5e2bc24..7f62d83ae09b21 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -745,7 +745,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::FixnumBitCheck { val, index } => gen_fixnum_bit_check(asm, opnd!(val), *index), Insn::SideExit { state, reason, recompile } => no_output!(gen_side_exit(jit, asm, function, reason, *recompile, &function.frame_state(*state))), Insn::PutSpecialObject { value_type, state } => gen_putspecialobject(jit, asm, function, *value_type, &function.frame_state(*state)), - Insn::AnyToString { val, str, state } => gen_anytostring(asm, opnd!(val), opnd!(str), &function.frame_state(*state)), + Insn::AnyToString { val, state } => gen_anytostring(asm, opnd!(val), &function.frame_state(*state)), Insn::Defined { op_type, obj, pushval, v, lep_level, state } => gen_defined(jit, asm, function, *op_type, *obj, *pushval, opnd!(v), *lep_level, &function.frame_state(*state)), Insn::CheckMatch { target, pattern, flag, state } => gen_checkmatch(jit, asm, function, opnd!(target), opnd!(pattern), *flag, &function.frame_state(*state)), Insn::GetSpecialSymbol { symbol_type, state } => gen_getspecial_symbol(asm, *symbol_type, &function.frame_state(*state)), @@ -768,8 +768,8 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::LoadSP => gen_load_sp(), &Insn::GetEP { level } => gen_get_ep(asm, level), Insn::LoadSelf => gen_load_self(asm), - &Insn::LoadField { recv, id, offset, return_type } => gen_load_field(asm, opnd!(recv), id, offset, return_type), - &Insn::StoreField { recv, id, offset, val } => no_output!(gen_store_field(asm, opnd!(recv), id, offset, opnd!(val), function.type_of(val))), + &Insn::LoadField { recv, id, offset, return_type: _, num_bits } => gen_load_field(asm, opnd!(recv), id, offset, num_bits), + &Insn::StoreField { recv, id, offset, val, num_bits } => no_output!(gen_store_field(asm, opnd!(recv), id, offset, opnd!(val), num_bits)), &Insn::WriteBarrier { recv, val } => no_output!(gen_write_barrier(jit, asm, opnd!(recv), opnd!(val), function.type_of(val))), &Insn::IsBlockGiven { lep } => gen_is_block_given(asm, opnd!(lep)), Insn::ArrayInclude { elements, target, state } => gen_array_include(jit, asm, function, opnds!(elements), opnd!(target), &function.frame_state(*state)), @@ -1370,18 +1370,18 @@ fn gen_load_self(asm: &mut Assembler) -> Opnd { asm.load(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SELF)) } -fn gen_load_field(asm: &mut Assembler, recv: Opnd, id: FieldName, offset: i32, return_type: Type) -> Opnd { +fn gen_load_field(asm: &mut Assembler, recv: Opnd, id: FieldName, offset: i32, num_bits: u8) -> Opnd { gen_incr_counter(asm, Counter::load_field_count); asm_comment!(asm, "Load field id={id} offset={offset}"); let recv = asm.load_mem(recv); - asm.load(Opnd::mem(return_type.num_bits(), recv, offset)) + asm.load(Opnd::mem(num_bits, recv, offset)) } -fn gen_store_field(asm: &mut Assembler, recv: Opnd, id: FieldName, offset: i32, val: Opnd, val_type: Type) { +fn gen_store_field(asm: &mut Assembler, recv: Opnd, id: FieldName, offset: i32, val: Opnd, num_bits: u8) { gen_incr_counter(asm, Counter::store_field_count); asm_comment!(asm, "Store field id={id} offset={offset}"); let recv = asm.load_mem(recv); - asm.store(Opnd::mem(val_type.num_bits(), recv, offset), val); + asm.store(Opnd::mem(num_bits, recv, offset), val); } fn gen_write_barrier(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, val: Opnd, val_type: Type) { @@ -2604,10 +2604,10 @@ fn gen_box_fixnum(jit: &mut JITState, asm: &mut Assembler, function: &Function, asm.or(shifted, Opnd::UImm(RUBY_FIXNUM_FLAG as u64)) } -fn gen_anytostring(asm: &mut Assembler, val: lir::Opnd, str: lir::Opnd, state: &FrameState) -> lir::Opnd { +fn gen_anytostring(asm: &mut Assembler, val: lir::Opnd, state: &FrameState) -> lir::Opnd { gen_prepare_leaf_call_with_gc(asm, state); - asm_ccall!(asm, rb_obj_as_string_result, str, val) + asm_ccall!(asm, rb_any_to_s, val) } /// Evaluate if a value is truthy diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 3a76b2a16f10e7..7b000daa7393cb 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -2104,6 +2104,7 @@ unsafe extern "C" { argv: *const VALUE, klass: VALUE, ) -> VALUE; + pub fn rb_any_to_s(obj: VALUE) -> VALUE; pub fn rb_obj_is_kind_of(obj: VALUE, klass: VALUE) -> VALUE; pub fn rb_obj_alloc(klass: VALUE) -> VALUE; pub fn rb_obj_frozen_p(obj: VALUE) -> VALUE; @@ -2199,7 +2200,6 @@ unsafe extern "C" { len: VALUE, empty: ::std::os::raw::c_int, ) -> VALUE; - pub fn rb_obj_as_string_result(str_: VALUE, obj: VALUE) -> VALUE; pub fn rb_str_concat_literals(num: usize, strary: *const VALUE) -> VALUE; pub fn rb_ec_str_resurrect( ec: *mut rb_execution_context_struct, diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 3e4e9ed3768fed..29c32fe9aab5e9 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -322,20 +322,10 @@ fn inline_string_to_s(fun: &mut hir::Function, block: hir::BlockId, recv: hir::I fn inline_thread_current(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { let &[] = args else { return None; }; let ec = fun.push_insn(block, hir::Insn::LoadEC); - let thread_ptr = fun.push_insn(block, hir::Insn::LoadField { - recv: ec, - id: FieldName::thread_ptr, - offset: RUBY_OFFSET_EC_THREAD_PTR, - return_type: types::CPtr, - }); - let thread_self = fun.push_insn(block, hir::Insn::LoadField { - recv: thread_ptr, - id: FieldName::SelfParam, - offset: RUBY_OFFSET_THREAD_SELF, - // TODO(max): Add Thread type. But Thread.current is not guaranteed to be an exact Thread. - // You can make subclasses... - return_type: types::BasicObject, - }); + let thread_ptr = fun.load_field(block, ec, FieldName::thread_ptr, RUBY_OFFSET_EC_THREAD_PTR, types::CPtr); + // TODO(max): Add Thread type. But Thread.current is not guaranteed to be an exact Thread. + // You can make subclasses... + let thread_self = fun.load_field(block, thread_ptr, FieldName::SelfParam, RUBY_OFFSET_THREAD_SELF, types::BasicObject); Some(thread_self) } @@ -468,12 +458,7 @@ fn inline_hash_aset(fun: &mut hir::Function, block: hir::BlockId, recv: hir::Ins fn inline_string_bytesize(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { if args.is_empty() && fun.likely_a(recv, types::String, state) { let recv = fun.coerce_to(block, recv, types::String, state); - let len = fun.push_insn(block, hir::Insn::LoadField { - recv, - id: FieldName::len, - offset: RUBY_OFFSET_RSTRING_LEN, - return_type: types::CInt64, - }); + let len = fun.load_string_length(block, recv); let result = fun.push_insn(block, hir::Insn::BoxFixnum { val: len, @@ -492,12 +477,7 @@ fn inline_string_getbyte(fun: &mut hir::Function, block: hir::BlockId, recv: hir // when converting the index to a C integer. let index = fun.coerce_to(block, index, types::Fixnum, state); let unboxed_index = fun.push_insn(block, hir::Insn::UnboxFixnum { val: index }); - let len = fun.push_insn(block, hir::Insn::LoadField { - recv, - id: FieldName::len, - offset: RUBY_OFFSET_RSTRING_LEN, - return_type: types::CInt64, - }); + let len = fun.load_string_length(block, recv); // TODO(max): Find a way to mark these guards as not needed for correctness... as in, once // the data dependency is gone (say, the StringGetbyte is elided), they can also be elided. // @@ -520,12 +500,7 @@ fn inline_string_setbyte(fun: &mut hir::Function, block: hir::BlockId, recv: hir let value = fun.coerce_to(block, value, types::Fixnum, state); let unboxed_index = fun.push_insn(block, hir::Insn::UnboxFixnum { val: index }); - let len = fun.push_insn(block, hir::Insn::LoadField { - recv, - id: FieldName::len, - offset: RUBY_OFFSET_RSTRING_LEN, - return_type: types::CInt64, - }); + let len = fun.load_string_length(block, recv); let unboxed_index = fun.push_insn(block, hir::Insn::GuardLess { left: unboxed_index, right: len, reason: Box::new(SideExitReason::GuardLess), state }); let unboxed_index = fun.push_insn(block, hir::Insn::AdjustBounds { index: unboxed_index, length: len }); let zero = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(0) }); @@ -543,12 +518,7 @@ fn inline_string_setbyte(fun: &mut hir::Function, block: hir::BlockId, recv: hir fn inline_string_empty_p(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { let &[] = args else { return None; }; - let len = fun.push_insn(block, hir::Insn::LoadField { - recv, - id: FieldName::len, - offset: RUBY_OFFSET_RSTRING_LEN, - return_type: types::CInt64, - }); + let len = fun.load_string_length(block, recv); let zero = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(0) }); let is_zero = fun.push_insn(block, hir::Insn::IsBitEqual { left: len, right: zero }); let result = fun.push_insn(block, hir::Insn::BoxBool { val: is_zero }); diff --git a/zjit/src/distribution.rs b/zjit/src/distribution.rs index aa4667b9399c09..1d8149dbd35fe2 100644 --- a/zjit/src/distribution.rs +++ b/zjit/src/distribution.rs @@ -81,6 +81,10 @@ pub struct DistributionSummary DistributionSummary { + pub fn empty() -> Self { + Self { kind: DistributionKind::Empty, buckets: [Default::default(); N] } + } + pub fn new(dist: &Distribution) -> Self { #[cfg(debug_assertions)] { diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 028196d7d4ff59..4f1b8750fa00e8 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -570,6 +570,7 @@ pub enum SideExitReason { DirectiveInduced, SendWhileTracing, NoProfileSend, + NoProfileGetIvar, InvokeBlockNotIfunc, } @@ -1022,10 +1023,10 @@ pub enum Insn { LoadSP, /// Load cfp->self LoadSelf, - LoadField { recv: InsnId, id: FieldName, offset: i32, return_type: Type }, + LoadField { recv: InsnId, id: FieldName, offset: i32, return_type: Type, num_bits: u8 }, /// Write `val` at an offset of `recv`. /// When writing a Ruby object to a Ruby object, one must use GuardNotFrozen (or equivalent) before and WriteBarrier after. - StoreField { recv: InsnId, id: FieldName, offset: i32, val: InsnId }, + StoreField { recv: InsnId, id: FieldName, offset: i32, val: InsnId, num_bits: u8 }, WriteBarrier { recv: InsnId, val: InsnId }, /// Check whether VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM is set in the (already loaded) environment flags. @@ -1189,7 +1190,7 @@ pub enum Insn { /// Float#to_i: truncate float to integer via rb_jit_flo_to_i FloatToInt { recv: InsnId, state: InsnId }, - AnyToString { val: InsnId, str: InsnId, state: InsnId }, + AnyToString { val: InsnId, state: InsnId }, /// Refine the known type information of with additional type information. /// Computes the intersection of the existing type and the new type. @@ -1526,9 +1527,8 @@ macro_rules! for_each_operand_impl { $visit_one!(*val); $visit_one!(*state); } - Insn::AnyToString { val, str, state, .. } => { + Insn::AnyToString { val, state, .. } => { $visit_one!(*val); - $visit_one!(*str); $visit_one!(*state); } Insn::LoadField { recv, .. } => { @@ -2269,10 +2269,10 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::LoadSP => write!(f, "LoadSP"), &Insn::GetEP { level } => write!(f, "GetEP {level}"), Insn::LoadSelf => write!(f, "LoadSelf"), - &Insn::LoadField { recv, id, offset, return_type: _ } => { + &Insn::LoadField { recv, id, offset, return_type: _, num_bits: _ } => { write!(f, "LoadField {recv}, :{id}@{:#x}", self.ptr_map.map_offset(offset)) } - &Insn::StoreField { recv, id, offset, val } => write!(f, "StoreField {recv}, :{id}@{:#x}, {val}", self.ptr_map.map_offset(offset)), + &Insn::StoreField { recv, id, offset, val, num_bits: _ } => write!(f, "StoreField {recv}, :{id}@{:#x}, {val}", self.ptr_map.map_offset(offset)), &Insn::WriteBarrier { recv, val } => write!(f, "WriteBarrier {recv}, {val}"), Insn::SetIvar { self_val, id, val, .. } => write!(f, "SetIvar {self_val}, :{}, {val}", id.contents_lossy()), Insn::GetGlobal { id, .. } => write!(f, "GetGlobal :{}", id.contents_lossy()), @@ -2294,7 +2294,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::ArrayExtend { left, right, .. } => write!(f, "ArrayExtend {left}, {right}"), Insn::ArrayPush { array, val, .. } => write!(f, "ArrayPush {array}, {val}"), Insn::StringIntern { val, .. } => { write!(f, "StringIntern {val}") }, - Insn::AnyToString { val, str, .. } => { write!(f, "AnyToString {val}, str: {str}") }, + Insn::AnyToString { val, .. } => { write!(f, "AnyToString {val}") }, Insn::SideExit { reason, recompile, .. } => { if recompile.is_some() { write!(f, "SideExit {reason} recompile") @@ -2790,7 +2790,12 @@ impl Function { } pub fn load_field(&mut self, block: BlockId, recv: InsnId, id: FieldName, offset: i32, return_type: Type) -> InsnId { - self.push_insn(block, Insn::LoadField { recv, id, offset, return_type }) + let num_bits = return_type.num_bits(); + self.push_insn(block, Insn::LoadField { recv, id, offset, return_type, num_bits }) + } + + pub fn load_string_length(&mut self, block: BlockId, str: InsnId) -> InsnId { + self.load_field(block, str, FieldName::len, RUBY_OFFSET_RSTRING_LEN, types::CInt64) } // Add an instruction to an SSA block @@ -3158,7 +3163,7 @@ impl Function { Insn::GetClassVar { .. } => types::BasicObject, Insn::ToNewArray { .. } => types::ArrayExact, Insn::ToArray { .. } => types::ArrayExact, - Insn::AnyToString { .. } => types::String, + Insn::AnyToString { .. } => types::StringExact, Insn::IsBlockParamModified { .. } => types::CBool, Insn::GetBlockParam { .. } => types::BasicObject, // The type of Snapshot doesn't really matter; it's never materialized. It's used only @@ -3491,6 +3496,19 @@ impl Function { } } + fn profile_summary(&self, profiles: &ProfileOracle, recv: InsnId, state: InsnId) -> TypeDistributionSummary { + let Some(entries) = profiles.get(state) else { + return TypeDistributionSummary::empty(); + }; + let recv = self.chase_insn(recv); + for (entry_insn, entry_type_summary) in entries { + if self.chase_insn(*entry_insn) == recv { + return entry_type_summary.clone(); + } + } + TypeDistributionSummary::empty() + } + fn polymorphic_summary(&self, profiles: &ProfileOracle, recv: InsnId, state: InsnId) -> Option { let Some(entries) = profiles.get(state) else { return None; @@ -4096,7 +4114,7 @@ impl Function { }; let replacement = if let (OptimizedMethodType::StructAset, &[val]) = (opt_type, args.as_slice()) { - self.push_insn(block, Insn::StoreField { recv: target, id: mid.into(), offset, val }); + self.push_insn(block, Insn::StoreField { recv: target, id: mid.into(), offset, val, num_bits: types::BasicObject.num_bits() }); self.push_insn(block, Insn::WriteBarrier { recv, val }); val } else { // StructAref @@ -4331,13 +4349,6 @@ impl Function { self.push_insn_id(block, insn_id); continue; } } - Insn::AnyToString { str, .. } => { - if self.is_a(str, types::String) { - self.make_equal_to(insn_id, str); - } else { - self.push_insn_id(block, insn_id); - } - } Insn::IsMethodCfunc { val, cd, cfunc, state } if self.type_of(val).ruby_object_known() => { let class = self.type_of(val).ruby_object().unwrap(); let cme = unsafe { rb_zjit_vm_search_method(self.iseq.into(), cd as *mut rb_call_data, class) }; @@ -5225,13 +5236,13 @@ impl Function { let offset = SIZEOF_VALUE_I32 * ivar_index as i32; (as_heap, offset) }; - self.push_insn(block, Insn::StoreField { recv: ivar_storage, id: id.into(), offset, val }); + self.push_insn(block, Insn::StoreField { recv: ivar_storage, id: id.into(), offset, val, num_bits: types::BasicObject.num_bits() }); self.push_insn(block, Insn::WriteBarrier { recv: self_val, val }); if next_shape_id != profiled_type.shape() { // Write the new shape ID let shape_id = self.push_insn(block, Insn::Const { val: Const::CShape(next_shape_id) }); let shape_id_offset = unsafe { rb_shape_id_offset() }; - self.push_insn(block, Insn::StoreField { recv: self_val, id: FieldName::shape_id, offset: shape_id_offset, val: shape_id }); + self.push_insn(block, Insn::StoreField { recv: self_val, id: FieldName::shape_id, offset: shape_id_offset, val: shape_id, num_bits: types::CShape.num_bits() }); } Ok(()) } @@ -5525,19 +5536,6 @@ impl Function { let mut new_insns = vec![]; for insn_id in old_insns { let replacement_id = match self.find(insn_id) { - // TODO (nirvdrum 2026-06-26): Folding the guard to a SideExit is a workaround, - // not a proper fix. It relies on constant folding to keep an Empty-typed value - // (see below) from reaching codegen; disabling this pass would let that value - // through and the program would fail to compile on x86-64. Compilation correctness - // should not depend on an optimization pass, so this should be replaced by a - // comprehensive fix. - Insn::GuardType { val, guard_type, state, recompile } if !self.type_of(val).could_be(guard_type) => { - // The value's type is disjoint from the guard type, so the guard can never - // pass. Every execution would side-exit here, so we replace the guard with an - // unconditional exit. The terminator handling below then drops the rest of - // the block, which is now unreachable. - self.new_insn(Insn::SideExit { state, reason: Box::new(SideExitReason::GuardType(guard_type)), recompile }) - } Insn::GuardType { val, guard_type, .. } if self.is_a(val, guard_type) => { self.make_equal_to(insn_id, val); // Don't bother re-inferring the type of val; we already know it. @@ -5623,11 +5621,6 @@ impl Function { insn_id } } - Insn::AnyToString { str, .. } if self.is_a(str, types::String) => { - self.make_equal_to(insn_id, str); - // Don't bother re-inferring the type of str; we already know it. - continue; - } Insn::IsA { val, class } => 'is_a: { let class_type = self.type_of(class); if !class_type.is_subtype(types::Class) { @@ -6489,7 +6482,6 @@ impl Function { // Instructions with 2 Ruby object operands Insn::SetIvar { self_val: left, val: right, .. } | Insn::NewRange { low: left, high: right, .. } - | Insn::AnyToString { val: left, str: right, .. } | Insn::CheckMatch { target: left, pattern: right, .. } | Insn::WriteBarrier { recv: left, val: right } => { self.assert_subtype(insn_id, left, types::BasicObject)?; @@ -6499,6 +6491,9 @@ impl Function { self.assert_subtype(insn_id, klass, types::BasicObject)?; self.assert_subtype(insn_id, allow_nil, types::BoolExact) } + Insn::AnyToString { val, .. } => { + self.assert_subtype(insn_id, val, types::BasicObject) + } // Instructions with recv and a Vec of Ruby objects Insn::PushInlineFrame { recv, ref args, .. } | Insn::Send { recv, ref args, .. } @@ -6784,6 +6779,78 @@ impl Function { self.validate_types()?; Ok(()) } + + /// Return Some(InsnId) if we generated any code to load an ivar and None if we only generated + /// an unconditional SideExit (in which case we should end the block). + fn dispatch_getivar( + &mut self, + profiled_types: &Vec, + mut block: BlockId, + insn_idx: u32, + self_param: InsnId, + id: ID, + ic: *const iseq_inline_iv_cache_entry, + exit_id: InsnId, + ) -> Option<(BlockId, InsnId)> { + // 0 profiled_types: Generate a recompile exit or a fallback. No need for new HIR blocks. + if profiled_types.is_empty() { + if self.policy.no_side_exits { + self.count(block, Counter::getivar_fallback_no_side_exits); + let result = self.push_insn(block, Insn::GetIvar { self_val: self_param, id, ic, state: exit_id }); + return Some((block, result)); + } else { + self.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::NoProfileGetIvar), recompile: Some(Recompile) }); + return None; + } + } + // 1 profiled_types: Generate a monomorphic getivar with a guard if allowed by policy. No need for new HIR blocks. + if profiled_types.len() == 1 && !self.policy.no_side_exits { + // 0 profiled_types: Generate a recompile exit or a fallback. + let actual = self.load_shape(block, self_param); + self.guard_shape(block, actual, profiled_types[0].shape(), exit_id, Some(Recompile)); + let result = self.load_ivar(block, self_param, profiled_types[0], id); + return Some((block, result)); + } + + // Otherwise, make HIR blocks to handle different shapes or a fallback, and let them jump to join_block. + let edge = |target: BlockId| BranchEdge { target, args: vec![] }; + let branch = |cond: InsnId, if_true: BlockId, if_false: BlockId| Insn::CondBranch { val: cond, if_true: edge(if_true), if_false: edge(if_false) }; + let actual = self.load_shape(block, self_param); + let last_shape_index = profiled_types.len() - 1; + let join_block = self.new_block(insn_idx); + let result = self.push_insn(join_block, Insn::Param); + for (i, &profiled_type) in profiled_types.iter().enumerate() { + let load_optimized_block = self.new_block(insn_idx); + if i == last_shape_index { + if self.policy.no_side_exits { + // If the policy doesn't allow exits, make a fallback block and jump to it if the shape doesn't match. + let expected = self.push_insn(block, Insn::Const { val: Const::CShape(profiled_type.shape()) }); + let val = self.push_insn(block, Insn::IsBitEqual { left: actual, right: expected }); + let load_fallback_block = self.new_block(insn_idx); + self.push_insn(block, branch(val, load_optimized_block, load_fallback_block)); + self.count(load_fallback_block, Counter::getivar_fallback_no_side_exits); + let result = self.push_insn(load_fallback_block, Insn::GetIvar { self_val: self_param, id, ic, state: exit_id }); + self.push_insn(load_fallback_block, Insn::Jump(BranchEdge { target: join_block, args: vec![result] })); + } else { + // If the policy allows exits, exit to the interpreter if the shape doesn't match. + self.guard_shape(block, actual, profiled_type.shape(), exit_id, Some(Recompile)); + // TODO(max): Don't make a new block in this case + self.push_insn(block, Insn::Jump(BranchEdge { target: load_optimized_block, args: vec![] })); + } + } else { + // If this is not the last profiled shape, let the guard jump to the next block. + let expected = self.push_insn(block, Insn::Const { val: Const::CShape(profiled_type.shape()) }); + let val = self.push_insn(block, Insn::IsBitEqual { left: actual, right: expected}); + let next_block = self.new_block(insn_idx); + self.push_insn(block, branch(val, load_optimized_block, next_block)); + block = next_block; + } + // Generate the optimized getivar for the profiled_type and jump to join_block + let result = self.load_ivar(load_optimized_block, self_param, profiled_type, id); + self.push_insn(load_optimized_block, Insn::Jump(BranchEdge { target: join_block, args: vec![result] })); + } + Some((join_block, result)) + } } impl<'a> std::fmt::Display for FunctionPrinter<'a> { @@ -8177,6 +8244,7 @@ fn add_iseq_to_hir( id: FieldName::VM_ENV_DATA_INDEX_FLAGS, offset: SIZEOF_VALUE_I32 * (VM_ENV_DATA_INDEX_FLAGS as i32), val: modified, + num_bits: types::CInt64.num_bits(), }); } YARVINSN_getblockparamproxy => { @@ -8925,68 +8993,42 @@ fn add_iseq_to_hir( fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledYARVInsn(opcode)), recompile: None }); break; // End the block } - if let Some(summary) = fun.polymorphic_summary(&profiles, self_param, exit_id) { - self_param = fun.push_insn(block, Insn::GuardType { val: self_param, guard_type: types::HeapBasicObject, state: exit_id, recompile: None }); - let join_block = fun.new_block(insn_idx); - let join_param = fun.push_insn(join_block, Insn::Param); - // Dedup by expected shape so objects with different classes but the same shape can share code - // TODO(max): De-duplicate further by checking ivar offsets to allow - // different shapes with the same ivar layout to share code - let mut seen_shape = Vec::with_capacity(summary.buckets().len()); - for &profiled_type in summary.buckets() { - // End of the buckets - if profiled_type.is_empty() { break; } - // Instance variable lookups on immediate values are always nil; don't bother - if profiled_type.flags().is_immediate() { continue; } - let profiled_shape = profiled_type.shape(); - assert!(profiled_shape.is_valid()); - // Too-complex shapes use hash tables for ivars; - // rb_shape_get_iv_index doesn't work for them. - // Let the fallthrough GetIvar handle these. - if profiled_shape.is_complex() { continue; } - if seen_shape.contains(&profiled_shape) { continue; } - seen_shape.push(profiled_shape); - let actual_shape = fun.load_shape(block, self_param); - // Load the expected shape to a variable - let expected_shape = fun.push_insn(block, Insn::Const { val: Const::CShape(profiled_shape) }); - let has_shape = fun.push_insn(block, Insn::IsBitEqual { left: actual_shape, right: expected_shape }); - let iftrue_block = fun.new_block(insn_idx); - let target = BranchEdge { target: iftrue_block, args: vec![] }; - let fall_through = fun.new_block(insn_idx); - - fun.push_insn(block, Insn::CondBranch { val: has_shape, - if_true: target, - if_false: BranchEdge { target: fall_through, args: vec![] } - }); - - block = fall_through; - let result = fun.load_ivar(iftrue_block, self_param, profiled_type, id); - fun.push_insn(iftrue_block, Insn::Jump(BranchEdge { target: join_block, args: vec![result] })); - } - // In the fallthrough case, do a generic interpreter getivar and then join. - let result = fun.push_insn(block, Insn::GetIvar { self_val: self_param, id, ic, state: exit_id }); - fun.push_insn(block, Insn::Jump(BranchEdge { target: join_block, args: vec![result] })); - state.stack_push(join_param); - // Continue compilation from the join block at the next instruction. - // Make a copy of the current state without the args (pop the receiver - // and push the result) because we just use the locals/stack sizes to - // make the right number of Params - block = join_block; - } else { - if let Some(profiled_type) = fun.monomorphic_summary(&profiles, self_param, exit_id) { - let result = fun.try_emit_optimized_getivar(block, self_param, id, profiled_type, exit_id).unwrap_or_else(|counter| { - fun.count(block, counter); - fun.push_insn(block, Insn::GetIvar { self_val: self_param, id, ic, state: exit_id }) - }); - state.stack_push(result); - } else { - let resolution = fun.resolve_receiver_type_from_profile(self_param, exit_id); - let counter = Function::getivar_fallback_reason(resolution, ic); - fun.count(block, counter); - let result = fun.push_insn(block, Insn::GetIvar { self_val: self_param, id, ic, state: exit_id }); - state.stack_push(result); + let summary = fun.profile_summary(&profiles, self_param, exit_id); + let self_param = fun.guard_heap(block, self_param, exit_id); + // Filter out profiled types we don't care to optimize + let profiled_types = summary.buckets().iter().filter(|profiled_type| { + // Don't read past the end of the profiled types + !profiled_type.is_empty() + // Instance variable lookups on immediate values are always nil; don't bother + && !profiled_type.flags().is_immediate() + // Too-complex shapes use hash tables for ivars; + // rb_shape_get_iv_index doesn't work for them. + // Let the fallthrough GetIvar handle these. + && !profiled_type.shape().is_complex() + }).collect::>(); + // We might have two objects of class A and B with the same shape; de-duplicate + // profiled types by shape. This is just an optimization to reduce code size. + let mut profiled_types_unique_shapes = Vec::with_capacity(profiled_types.len()); + for &profiled_type in profiled_types { + if profiled_types_unique_shapes.iter().any(|t: &ProfiledType| t.shape() == profiled_type.shape()) { + continue; } - } + profiled_types_unique_shapes.push(profiled_type); + } + let Some((new_block, result)) = fun.dispatch_getivar( + &profiled_types_unique_shapes, + block, + insn_idx, + self_param, + id, + ic, + exit_id, + ) else { + // Side-exiting unconditionally; end the block + break; + }; + block = new_block; + state.stack_push(result); } YARVINSN_setinstancevariable => { let id = ID(get_arg(pc, 0).as_u64()); @@ -9152,8 +9194,26 @@ fn add_iseq_to_hir( let str = state.stack_pop()?; let val = state.stack_pop()?; - let anytostring = fun.push_insn(block, Insn::AnyToString { val, str, state: exit_id }); - state.stack_push(anytostring); + // Mirror logic of rb_obj_as_string_result() (`anytostring` in insns.def) + let has_type = fun.push_insn(block, Insn::HasType { val: str, expected: types::String }); + let iftrue_block = fun.new_block(insn_idx); + let iffalse_block = fun.new_block(insn_idx); + let join_block = fun.new_block(insn_idx); + fun.push_insn(block, Insn::CondBranch { + val: has_type, + if_true: BranchEdge { target: iftrue_block, args: vec![] }, + if_false: BranchEdge { target: iffalse_block, args: vec![] } + }); + // true block + let refined = fun.push_insn(iftrue_block, Insn::RefineType { val: str, new_type: types::String }); + fun.push_insn(iftrue_block, Insn::Jump(BranchEdge { target: join_block, args: vec![refined] })); + // false block + let anytostring = fun.push_insn(iffalse_block, Insn::AnyToString { val, state: exit_id }); + fun.push_insn(iffalse_block, Insn::Jump(BranchEdge { target: join_block, args: vec![anytostring] })); + // join block + block = join_block; + let result = fun.push_insn(join_block, Insn::Param); + state.stack_push(result); } YARVINSN_getspecial => { let key = get_arg(pc, 0).as_u64(); @@ -9410,6 +9470,7 @@ fn compile_jit_entry_state(fun: &mut Function, jit_entry_block: BlockId, jit_ent id: local_id.into(), offset: -(SIZEOF_VALUE_I32 * ep_offset), val: local, + num_bits: types::BasicObject.num_bits(), }); } } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 595efc4e66b2a8..6db2f1d74e0b99 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -2440,41 +2440,6 @@ mod hir_opt_tests { assert!(!insns.contains(&dead_const)); } - // A GuardType whose value type is disjoint from the guard type can never pass, so every - // execution side-exits there. fold_constants should replace the guard with an unconditional - // SideExit and drop the now-unreachable instructions that follow. - #[test] - fn test_fold_guard_type_that_can_never_pass_into_side_exit() { - let mut function = Function::new(std::ptr::null()); - let entry = function.entry_block; - - let state = function.push_insn(entry, Insn::Snapshot { state: Box::new(FrameState::new(std::ptr::null())) }); - // A nil constant is a NilClass, which is disjoint from Fixnum, so the guard below can - // never pass and the optimizer infers its result as Empty. - let nil = function.push_insn(entry, Insn::Const { val: Const::Value(Qnil) }); - let guard = function.push_insn(entry, Insn::GuardType { val: nil, guard_type: types::Fixnum, state, recompile: None }); - function.push_insn(entry, Insn::StoreField { recv: nil, id: FieldName::len, offset: 0, val: guard }); - function.push_insn(entry, Insn::Return { val: guard }); - function.seal_entries(); - - function.infer_types(); - function.fold_constants(); - - let insns: Vec = function.blocks[entry.0].insns.iter().map(|&id| function.find(id)).collect(); - assert!( - insns.iter().any(|insn| matches!(insn, Insn::SideExit { .. })), - "expected the always-failing guard to be folded into a SideExit, got {insns:?}", - ); - assert!( - !insns.iter().any(|insn| matches!(insn, Insn::GuardType { .. })), - "the always-failing GuardType should have been removed, got {insns:?}", - ); - assert!( - !insns.iter().any(|insn| matches!(insn, Insn::StoreField { .. } | Insn::Return { .. })), - "instructions after the unconditional SideExit are unreachable and should have been dropped, got {insns:?}", - ); - } - #[test] fn test_eliminate_new_array() { eval(" @@ -5651,9 +5616,8 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): PatchPoint SingleRactorMode - v11:BasicObject = GetIvar v6, :@foo - CheckInterrupts - Return v11 + v11:HeapBasicObject = GuardType v6, HeapBasicObject + SideExit NoProfileGetIvar recompile "); } @@ -6966,9 +6930,9 @@ mod hir_opt_tests { v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v13:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) v14:StringExact = StringCopy v13 - v28:StringExact = StringConcat v10, v14 + v34:StringExact = StringConcat v10, v14 CheckInterrupts - Return v28 + Return v34 "); } @@ -6991,10 +6955,10 @@ mod hir_opt_tests { v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v12:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(Integer@0x1008, to_s@0x1010, cme:0x1018) - v34:StringExact = CCallVariadic v12, :Integer#to_s@0x1040 - v26:StringExact = StringConcat v10, v34 + v40:StringExact = CCallVariadic v12, :Integer#to_s@0x1040 + v32:StringExact = StringConcat v10, v40 CheckInterrupts - Return v26 + Return v32 "); } @@ -7024,9 +6988,9 @@ mod hir_opt_tests { v14:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(String@0x1010) v19:String = GuardType v10, String - v23:StringExact = StringConcat v14, v19 + v29:StringExact = StringConcat v14, v19 CheckInterrupts - Return v23 + Return v29 "); } @@ -7059,9 +7023,9 @@ mod hir_opt_tests { v14:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(MyString@0x1010) v19:String = GuardType v10, String - v23:StringExact = StringConcat v14, v19 + v29:StringExact = StringConcat v14, v19 CheckInterrupts - Return v23 + Return v29 "); } @@ -7092,11 +7056,19 @@ mod hir_opt_tests { v18:ArrayExact = GuardType v10, ArrayExact PatchPoint NoSingletonClass(Array@0x1010) PatchPoint MethodRedefined(Array@0x1010, to_s@0x1018, cme:0x1020) - v33:BasicObject = CCallWithFrame v18, :Array#to_s@0x1048 - v21:String = AnyToString v18, str: v33 - v23:StringExact = StringConcat v14, v21 + v39:BasicObject = CCallWithFrame v18, :Array#to_s@0x1048 + v21:CBool = HasType v39, String + CondBranch v21, bb4(), bb5() + bb4(): + v23:String = RefineType v39, String + Jump bb6(v23) + bb5(): + v25:StringExact = AnyToString v10 + Jump bb6(v25) + bb6(v27:String): + v29:StringExact = StringConcat v14, v27 CheckInterrupts - Return v23 + Return v29 "); } @@ -8325,27 +8297,20 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:HeapBasicObject): PatchPoint SingleRactorMode - v13:CShape = LoadField v6, :shape_id@0x1000 + v12:CShape = LoadField v6, :shape_id@0x1000 v14:CShape[0x1001] = Const CShape(0x1001) - v15:CBool = IsBitEqual v13, v14 + v15:CBool = IsBitEqual v12, v14 CondBranch v15, bb5(), bb6() bb5(): v17:BasicObject = LoadField v6, :@foo@0x1002 Jump bb4(v17) bb6(): - v19:CShape = LoadField v6, :shape_id@0x1000 - v20:CShape[0x1003] = Const CShape(0x1003) - v21:CBool = IsBitEqual v19, v20 - CondBranch v21, bb7(), bb8() - bb7(): - v23:BasicObject = LoadField v6, :@foo@0x1004 - Jump bb4(v23) - bb8(): - v25:BasicObject = GetIvar v6, :@foo - Jump bb4(v25) - bb4(v12:BasicObject): + v19:CShape[0x1003] = GuardBitEquals v12, CShape(0x1003) recompile + v21:BasicObject = LoadField v6, :@foo@0x1004 + Jump bb4(v21) + bb4(v13:BasicObject): CheckInterrupts - Return v12 + Return v13 "); } @@ -8378,9 +8343,8 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): PatchPoint SingleRactorMode - v11:BasicObject = GetIvar v6, :@foo - CheckInterrupts - Return v11 + v11:HeapBasicObject = GuardType v6, HeapBasicObject + SideExit NoProfileGetIvar recompile "); } @@ -8404,9 +8368,8 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): PatchPoint SingleRactorMode - v11:BasicObject = GetIvar v6, :@foo - CheckInterrupts - Return v11 + v11:HeapBasicObject = GuardType v6, HeapBasicObject + SideExit NoProfileGetIvar recompile "); } @@ -8430,9 +8393,8 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): PatchPoint SingleRactorMode - v11:BasicObject = GetIvar v6, :@foo - CheckInterrupts - Return v11 + v11:HeapBasicObject = GuardType v6, HeapBasicObject + SideExit NoProfileGetIvar recompile "); } @@ -8456,9 +8418,8 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): PatchPoint SingleRactorMode - v11:BasicObject = GetIvar v6, :@foo - CheckInterrupts - Return v11 + v11:HeapBasicObject = GuardType v6, HeapBasicObject + SideExit NoProfileGetIvar recompile "); } @@ -8482,9 +8443,8 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): PatchPoint SingleRactorMode - v11:BasicObject = GetIvar v6, :@foo - CheckInterrupts - Return v11 + v11:HeapBasicObject = GuardType v6, HeapBasicObject + SideExit NoProfileGetIvar recompile "); } @@ -8508,9 +8468,8 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): PatchPoint SingleRactorMode - v11:BasicObject = GetIvar v6, :@foo - CheckInterrupts - Return v11 + v11:HeapBasicObject = GuardType v6, HeapBasicObject + SideExit NoProfileGetIvar recompile "); } @@ -8600,9 +8559,8 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): PatchPoint SingleRactorMode - v11:BasicObject = GetIvar v6, :@foo - CheckInterrupts - Return v11 + v11:HeapBasicObject = GuardType v6, HeapBasicObject + SideExit NoProfileGetIvar recompile "); } @@ -8809,9 +8767,8 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): PatchPoint SingleRactorMode - v11:BasicObject = GetIvar v6, :@foo - CheckInterrupts - Return v11 + v11:HeapBasicObject = GuardType v6, HeapBasicObject + SideExit NoProfileGetIvar recompile "); } @@ -8880,9 +8837,8 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): PatchPoint SingleRactorMode - v11:BasicObject = GetIvar v6, :@foo - CheckInterrupts - Return v11 + v11:HeapBasicObject = GuardType v6, HeapBasicObject + SideExit NoProfileGetIvar recompile "); } @@ -8981,9 +8937,8 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): PatchPoint SingleRactorMode - v11:BasicObject = GetIvar v6, :@var1000 - CheckInterrupts - Return v11 + v11:HeapBasicObject = GuardType v6, HeapBasicObject + SideExit NoProfileGetIvar recompile "); } @@ -9082,32 +9037,25 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode v11:HeapBasicObject = GuardType v6, HeapBasicObject - v13:CShape = LoadField v11, :shape_id@0x1000 + v12:CShape = LoadField v11, :shape_id@0x1000 v14:CShape[0x1001] = Const CShape(0x1001) - v15:CBool = IsBitEqual v13, v14 + v15:CBool = IsBitEqual v12, v14 CondBranch v15, bb5(), bb6() bb5(): v17:BasicObject = LoadField v11, :@foo@0x1002 Jump bb4(v17) bb6(): - v19:CShape = LoadField v11, :shape_id@0x1000 - v20:CShape[0x1003] = Const CShape(0x1003) - v21:CBool = IsBitEqual v19, v20 - CondBranch v21, bb7(), bb8() - bb7(): - v23:CPtr = LoadField v11, :as_heap@0x1004 - v24:BasicObject = LoadField v23, :@foo@0x1005 - Jump bb4(v24) - bb8(): - v26:BasicObject = GetIvar v11, :@foo - Jump bb4(v26) - bb4(v12:BasicObject): - v29:Fixnum[1] = Const Value(1) + v19:CShape[0x1003] = GuardBitEquals v12, CShape(0x1003) recompile + v21:CPtr = LoadField v11, :as_heap@0x1004 + v22:BasicObject = LoadField v21, :@foo@0x1005 + Jump bb4(v22) + bb4(v13:BasicObject): + v25:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(Integer@0x1008, +@0x1010, cme:0x1018) - v40:Fixnum = GuardType v12, Fixnum recompile - v41:Fixnum = FixnumAdd v40, v29 + v36:Fixnum = GuardType v13, Fixnum recompile + v37:Fixnum = FixnumAdd v36, v25 CheckInterrupts - Return v41 + Return v37 "); } @@ -9156,32 +9104,25 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode v11:HeapBasicObject = GuardType v6, HeapBasicObject - v13:CShape = LoadField v11, :shape_id@0x1000 + v12:CShape = LoadField v11, :shape_id@0x1000 v14:CShape[0x1001] = Const CShape(0x1001) - v15:CBool = IsBitEqual v13, v14 + v15:CBool = IsBitEqual v12, v14 CondBranch v15, bb5(), bb6() bb5(): v17:CPtr = LoadField v11, :as_heap@0x1002 v18:BasicObject = LoadField v17, :@foo@0x1003 Jump bb4(v18) bb6(): - v20:CShape = LoadField v11, :shape_id@0x1000 - v21:CShape[0x1004] = Const CShape(0x1004) - v22:CBool = IsBitEqual v20, v21 - CondBranch v22, bb7(), bb8() - bb7(): - v24:BasicObject = LoadField v11, :@foo@0x1005 - Jump bb4(v24) - bb8(): - v26:BasicObject = GetIvar v11, :@foo - Jump bb4(v26) - bb4(v12:BasicObject): - v29:Fixnum[1] = Const Value(1) + v20:CShape[0x1004] = GuardBitEquals v12, CShape(0x1004) recompile + v22:BasicObject = LoadField v11, :@foo@0x1005 + Jump bb4(v22) + bb4(v13:BasicObject): + v25:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(Integer@0x1008, +@0x1010, cme:0x1018) - v40:Fixnum = GuardType v12, Fixnum recompile - v41:Fixnum = FixnumAdd v40, v29 + v36:Fixnum = GuardType v13, Fixnum recompile + v37:Fixnum = FixnumAdd v36, v25 CheckInterrupts - Return v41 + Return v37 "); } @@ -9222,31 +9163,24 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode v11:HeapBasicObject = GuardType v6, HeapBasicObject - v13:CShape = LoadField v11, :shape_id@0x1000 + v12:CShape = LoadField v11, :shape_id@0x1000 v14:CShape[0x1001] = Const CShape(0x1001) - v15:CBool = IsBitEqual v13, v14 + v15:CBool = IsBitEqual v12, v14 CondBranch v15, bb5(), bb6() bb5(): v17:BasicObject = LoadField v11, :@foo@0x1002 Jump bb4(v17) bb6(): - v19:CShape = LoadField v11, :shape_id@0x1000 - v20:CShape[0x1003] = Const CShape(0x1003) - v21:CBool = IsBitEqual v19, v20 - CondBranch v21, bb7(), bb8() - bb7(): - v23:BasicObject = LoadField v11, :@foo@0x1002 - Jump bb4(v23) - bb8(): - v25:BasicObject = GetIvar v11, :@foo - Jump bb4(v25) - bb4(v12:BasicObject): - v28:Fixnum[1] = Const Value(1) + v19:CShape[0x1003] = GuardBitEquals v12, CShape(0x1003) recompile + v21:BasicObject = LoadField v11, :@foo@0x1002 + Jump bb4(v21) + bb4(v13:BasicObject): + v24:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(Integer@0x1008, +@0x1010, cme:0x1018) - v39:Fixnum = GuardType v12, Fixnum recompile - v40:Fixnum = FixnumAdd v39, v28 + v35:Fixnum = GuardType v13, Fixnum recompile + v36:Fixnum = FixnumAdd v35, v24 CheckInterrupts - Return v40 + Return v36 "); } @@ -9285,29 +9219,22 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode v11:HeapBasicObject = GuardType v6, HeapBasicObject - v13:CShape = LoadField v11, :shape_id@0x1000 + v12:CShape = LoadField v11, :shape_id@0x1000 v14:CShape[0x1001] = Const CShape(0x1001) - v15:CBool = IsBitEqual v13, v14 + v15:CBool = IsBitEqual v12, v14 CondBranch v15, bb5(), bb6() bb5(): v17:RubyValue = LoadField v11, :fields_obj@0x1002 v18:BasicObject = LoadField v17, :@a@0x1002 Jump bb4(v18) bb6(): - v20:CShape = LoadField v11, :shape_id@0x1000 - v21:CShape[0x1003] = Const CShape(0x1003) - v22:CBool = IsBitEqual v20, v21 - CondBranch v22, bb7(), bb8() - bb7(): - v24:RubyValue = LoadField v11, :fields_obj@0x1004 - v25:BasicObject = LoadField v24, :@a@0x1002 - Jump bb4(v25) - bb8(): - v27:BasicObject = GetIvar v11, :@a - Jump bb4(v27) - bb4(v12:BasicObject): + v20:CShape[0x1003] = GuardBitEquals v12, CShape(0x1003) recompile + v22:RubyValue = LoadField v11, :fields_obj@0x1004 + v23:BasicObject = LoadField v22, :@a@0x1002 + Jump bb4(v23) + bb4(v13:BasicObject): CheckInterrupts - Return v12 + Return v13 "); } @@ -10366,10 +10293,10 @@ mod hir_opt_tests { v14:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) v18:Fixnum = GuardType v10, Fixnum PatchPoint MethodRedefined(Integer@0x1010, to_s@0x1018, cme:0x1020) - v32:StringExact = CCallVariadic v18, :Integer#to_s@0x1048 - v23:StringExact = StringConcat v14, v32 + v38:StringExact = CCallVariadic v18, :Integer#to_s@0x1048 + v29:StringExact = StringConcat v14, v38 CheckInterrupts - Return v23 + Return v29 "); } @@ -17274,50 +17201,43 @@ mod hir_opt_tests { bb6(v19:HeapBasicObject, v20:Fixnum): v24:Fixnum[10] = Const Value(10) PatchPoint MethodRedefined(Integer@0x1000, <@0x1008, cme:0x1010) - v100:BoolExact = FixnumLt v20, v24 + v96:BoolExact = FixnumLt v20, v24 CheckInterrupts - v30:CBool = Test v100 + v30:CBool = Test v96 CondBranch v30, bb4(v19, v20), bb7() bb4(v40:HeapBasicObject, v41:Fixnum): PatchPoint SingleRactorMode - v48:CShape = LoadField v40, :shape_id@0x1038 + v47:CShape = LoadField v40, :shape_id@0x1038 v49:CShape[0x1039] = Const CShape(0x1039) - v50:CBool = IsBitEqual v48, v49 + v50:CBool = IsBitEqual v47, v49 CondBranch v50, bb9(), bb10() bb9(): v52:BasicObject = LoadField v40, :@levar@0x103a Jump bb8(v52) bb10(): - v54:CShape = LoadField v40, :shape_id@0x1038 - v55:CShape[0x103b] = Const CShape(0x103b) - v56:CBool = IsBitEqual v54, v55 - CondBranch v56, bb11(), bb12() - bb11(): - v58:NilClass = Const Value(nil) - Jump bb8(v58) - bb12(): - v60:BasicObject = GetIvar v40, :@levar - Jump bb8(v60) - bb8(v47:BasicObject): + v54:CShape[0x103b] = GuardBitEquals v47, CShape(0x103b) recompile + v56:NilClass = Const Value(nil) + Jump bb8(v56) + bb8(v48:BasicObject): CheckInterrupts - v64:CBool = Test v47 - CondBranch v64, bb5(v40, v41), bb13() - bb13(): + v60:CBool = Test v48 + CondBranch v60, bb5(v40, v41), bb12() + bb12(): PatchPoint NoEPEscape(set_value_loop) PatchPoint SingleRactorMode - v74:CShape = LoadField v40, :shape_id@0x1038 - v75:CShape[0x103b] = GuardBitEquals v74, CShape(0x103b) recompile + v70:CShape = LoadField v40, :shape_id@0x1038 + v71:CShape[0x103b] = GuardBitEquals v70, CShape(0x103b) recompile StoreField v40, :@levar@0x103a, v41 WriteBarrier v40, v41 - v78:CShape[0x1039] = Const CShape(0x1039) - StoreField v40, :shape_id@0x1038, v78 + v74:CShape[0x1039] = Const CShape(0x1039) + StoreField v40, :shape_id@0x1038, v74 Jump bb5(v40, v41) - bb5(v82:HeapBasicObject, v83:Fixnum): + bb5(v78:HeapBasicObject, v79:Fixnum): PatchPoint NoEPEscape(set_value_loop) - v90:Fixnum[1] = Const Value(1) + v86:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(Integer@0x1000, +@0x103c, cme:0x1040) - v104:Fixnum = FixnumAdd v83, v90 - Jump bb6(v82, v104) + v100:Fixnum = FixnumAdd v79, v86 + Jump bb6(v78, v100) bb7(): v35:NilClass = Const Value(nil) CheckInterrupts diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index 02de128ef4ee32..e3271833326450 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -2319,11 +2319,19 @@ pub(crate) mod hir_build_tests { v20:BasicObject = Send v19, :to_s # SendFallbackReason: ObjToString: result is not a string Jump bb6(v20) bb6(v22:BasicObject): - v24:String = AnyToString v12, str: v22 - v26:StringExact = StringConcat v10, v24 - v28:Symbol = StringIntern v26 + v24:CBool = HasType v22, String + CondBranch v24, bb7(), bb8() + bb7(): + v26:String = RefineType v22, String + Jump bb9(v26) + bb8(): + v28:StringExact = AnyToString v12 + Jump bb9(v28) + bb9(v30:String): + v32:StringExact = StringConcat v10, v30 + v34:Symbol = StringIntern v32 CheckInterrupts - Return v28 + Return v34 "); } @@ -4818,15 +4826,8 @@ pub(crate) mod hir_build_tests { Jump bb3(v7, v8, v9, v10) bb3(v12:BasicObject, v13:NilClass, v14:NilClass, v15:NilClass): PatchPoint SingleRactorMode - v20:BasicObject = GetIvar v12, :@a - PatchPoint SingleRactorMode - v23:BasicObject = GetIvar v12, :@b - PatchPoint SingleRactorMode - v26:BasicObject = GetIvar v12, :@c - PatchPoint NoEPEscape(reverse_odd) - v38:ArrayExact = NewArray v20, v23, v26 - CheckInterrupts - Return v38 + v20:HeapBasicObject = GuardType v12, HeapBasicObject + SideExit NoProfileGetIvar recompile fn reverse_even@:8: bb1(): @@ -4847,17 +4848,8 @@ pub(crate) mod hir_build_tests { Jump bb3(v8, v9, v10, v11, v12) bb3(v14:BasicObject, v15:NilClass, v16:NilClass, v17:NilClass, v18:NilClass): PatchPoint SingleRactorMode - v23:BasicObject = GetIvar v14, :@a - PatchPoint SingleRactorMode - v26:BasicObject = GetIvar v14, :@b - PatchPoint SingleRactorMode - v29:BasicObject = GetIvar v14, :@c - PatchPoint SingleRactorMode - v32:BasicObject = GetIvar v14, :@d - PatchPoint NoEPEscape(reverse_even) - v46:ArrayExact = NewArray v23, v26, v29, v32 - CheckInterrupts - Return v46 + v23:HeapBasicObject = GuardType v14, HeapBasicObject + SideExit NoProfileGetIvar recompile "); } @@ -5348,10 +5340,18 @@ pub(crate) mod hir_build_tests { v20:BasicObject = Send v19, :to_s # SendFallbackReason: ObjToString: result is not a string Jump bb6(v20) bb6(v22:BasicObject): - v24:String = AnyToString v12, str: v22 - v26:StringExact = StringConcat v10, v24 + v24:CBool = HasType v22, String + CondBranch v24, bb7(), bb8() + bb7(): + v26:String = RefineType v22, String + Jump bb9(v26) + bb8(): + v28:StringExact = AnyToString v12 + Jump bb9(v28) + bb9(v30:String): + v32:StringExact = StringConcat v10, v30 CheckInterrupts - Return v26 + Return v32 "); } @@ -5383,34 +5383,58 @@ pub(crate) mod hir_build_tests { v18:BasicObject = Send v17, :to_s # SendFallbackReason: ObjToString: result is not a string Jump bb6(v18) bb6(v20:BasicObject): - v22:String = AnyToString v10, str: v20 - v24:Fixnum[2] = Const Value(2) - v27:CBool[false] = HasType v24, String - CondBranch v27, bb7(), bb8() + v22:CBool = HasType v20, String + CondBranch v22, bb7(), bb8() bb7(): - v29 = RefineType v24, String - Jump bb9(v29) + v24:String = RefineType v20, String + Jump bb9(v24) bb8(): - v31:Fixnum[2] = RefineType v24, NotString - v32:BasicObject = Send v31, :to_s # SendFallbackReason: ObjToString: result is not a string - Jump bb9(v32) - bb9(v34:BasicObject): - v36:String = AnyToString v24, str: v34 - v38:Fixnum[3] = Const Value(3) - v41:CBool[false] = HasType v38, String - CondBranch v41, bb10(), bb11() + v26:StringExact = AnyToString v10 + Jump bb9(v26) + bb9(v28:String): + v30:Fixnum[2] = Const Value(2) + v33:CBool[false] = HasType v30, String + CondBranch v33, bb10(), bb11() bb10(): - v43 = RefineType v38, String - Jump bb12(v43) + v35 = RefineType v30, String + Jump bb12(v35) bb11(): - v45:Fixnum[3] = RefineType v38, NotString - v46:BasicObject = Send v45, :to_s # SendFallbackReason: ObjToString: result is not a string - Jump bb12(v46) - bb12(v48:BasicObject): - v50:String = AnyToString v38, str: v48 - v52:StringExact = StringConcat v22, v36, v50 - CheckInterrupts - Return v52 + v37:Fixnum[2] = RefineType v30, NotString + v38:BasicObject = Send v37, :to_s # SendFallbackReason: ObjToString: result is not a string + Jump bb12(v38) + bb12(v40:BasicObject): + v42:CBool = HasType v40, String + CondBranch v42, bb13(), bb14() + bb13(): + v44:String = RefineType v40, String + Jump bb15(v44) + bb14(): + v46:StringExact = AnyToString v30 + Jump bb15(v46) + bb15(v48:String): + v50:Fixnum[3] = Const Value(3) + v53:CBool[false] = HasType v50, String + CondBranch v53, bb16(), bb17() + bb16(): + v55 = RefineType v50, String + Jump bb18(v55) + bb17(): + v57:Fixnum[3] = RefineType v50, NotString + v58:BasicObject = Send v57, :to_s # SendFallbackReason: ObjToString: result is not a string + Jump bb18(v58) + bb18(v60:BasicObject): + v62:CBool = HasType v60, String + CondBranch v62, bb19(), bb20() + bb19(): + v64:String = RefineType v60, String + Jump bb21(v64) + bb20(): + v66:StringExact = AnyToString v50 + Jump bb21(v66) + bb21(v68:String): + v70:StringExact = StringConcat v28, v48, v68 + CheckInterrupts + Return v70 "); } @@ -5443,10 +5467,18 @@ pub(crate) mod hir_build_tests { v20:BasicObject = Send v19, :to_s # SendFallbackReason: ObjToString: result is not a string Jump bb6(v20) bb6(v22:BasicObject): - v24:String = AnyToString v12, str: v22 - v26:StringExact = StringConcat v10, v24 + v24:CBool = HasType v22, String + CondBranch v24, bb7(), bb8() + bb7(): + v26:String = RefineType v22, String + Jump bb9(v26) + bb8(): + v28:StringExact = AnyToString v12 + Jump bb9(v28) + bb9(v30:String): + v32:StringExact = StringConcat v10, v30 CheckInterrupts - Return v26 + Return v32 "); } @@ -5478,34 +5510,58 @@ pub(crate) mod hir_build_tests { v18:BasicObject = Send v17, :to_s # SendFallbackReason: ObjToString: result is not a string Jump bb6(v18) bb6(v20:BasicObject): - v22:String = AnyToString v10, str: v20 - v24:Fixnum[2] = Const Value(2) - v27:CBool[false] = HasType v24, String - CondBranch v27, bb7(), bb8() + v22:CBool = HasType v20, String + CondBranch v22, bb7(), bb8() bb7(): - v29 = RefineType v24, String - Jump bb9(v29) + v24:String = RefineType v20, String + Jump bb9(v24) bb8(): - v31:Fixnum[2] = RefineType v24, NotString - v32:BasicObject = Send v31, :to_s # SendFallbackReason: ObjToString: result is not a string - Jump bb9(v32) - bb9(v34:BasicObject): - v36:String = AnyToString v24, str: v34 - v38:Fixnum[3] = Const Value(3) - v41:CBool[false] = HasType v38, String - CondBranch v41, bb10(), bb11() + v26:StringExact = AnyToString v10 + Jump bb9(v26) + bb9(v28:String): + v30:Fixnum[2] = Const Value(2) + v33:CBool[false] = HasType v30, String + CondBranch v33, bb10(), bb11() bb10(): - v43 = RefineType v38, String - Jump bb12(v43) + v35 = RefineType v30, String + Jump bb12(v35) bb11(): - v45:Fixnum[3] = RefineType v38, NotString - v46:BasicObject = Send v45, :to_s # SendFallbackReason: ObjToString: result is not a string - Jump bb12(v46) - bb12(v48:BasicObject): - v50:String = AnyToString v38, str: v48 - v52:RegexpExact = ToRegexp v22, v36, v50 - CheckInterrupts - Return v52 + v37:Fixnum[2] = RefineType v30, NotString + v38:BasicObject = Send v37, :to_s # SendFallbackReason: ObjToString: result is not a string + Jump bb12(v38) + bb12(v40:BasicObject): + v42:CBool = HasType v40, String + CondBranch v42, bb13(), bb14() + bb13(): + v44:String = RefineType v40, String + Jump bb15(v44) + bb14(): + v46:StringExact = AnyToString v30 + Jump bb15(v46) + bb15(v48:String): + v50:Fixnum[3] = Const Value(3) + v53:CBool[false] = HasType v50, String + CondBranch v53, bb16(), bb17() + bb16(): + v55 = RefineType v50, String + Jump bb18(v55) + bb17(): + v57:Fixnum[3] = RefineType v50, NotString + v58:BasicObject = Send v57, :to_s # SendFallbackReason: ObjToString: result is not a string + Jump bb18(v58) + bb18(v60:BasicObject): + v62:CBool = HasType v60, String + CondBranch v62, bb19(), bb20() + bb19(): + v64:String = RefineType v60, String + Jump bb21(v64) + bb20(): + v66:StringExact = AnyToString v50 + Jump bb21(v66) + bb21(v68:String): + v70:RegexpExact = ToRegexp v28, v48, v68 + CheckInterrupts + Return v70 "); } @@ -5537,22 +5593,38 @@ pub(crate) mod hir_build_tests { v18:BasicObject = Send v17, :to_s # SendFallbackReason: ObjToString: result is not a string Jump bb6(v18) bb6(v20:BasicObject): - v22:String = AnyToString v10, str: v20 - v24:Fixnum[2] = Const Value(2) - v27:CBool[false] = HasType v24, String - CondBranch v27, bb7(), bb8() + v22:CBool = HasType v20, String + CondBranch v22, bb7(), bb8() bb7(): - v29 = RefineType v24, String - Jump bb9(v29) + v24:String = RefineType v20, String + Jump bb9(v24) bb8(): - v31:Fixnum[2] = RefineType v24, NotString - v32:BasicObject = Send v31, :to_s # SendFallbackReason: ObjToString: result is not a string - Jump bb9(v32) - bb9(v34:BasicObject): - v36:String = AnyToString v24, str: v34 - v38:RegexpExact = ToRegexp v22, v36, MULTILINE|IGNORECASE|EXTENDED|NOENCODING - CheckInterrupts - Return v38 + v26:StringExact = AnyToString v10 + Jump bb9(v26) + bb9(v28:String): + v30:Fixnum[2] = Const Value(2) + v33:CBool[false] = HasType v30, String + CondBranch v33, bb10(), bb11() + bb10(): + v35 = RefineType v30, String + Jump bb12(v35) + bb11(): + v37:Fixnum[2] = RefineType v30, NotString + v38:BasicObject = Send v37, :to_s # SendFallbackReason: ObjToString: result is not a string + Jump bb12(v38) + bb12(v40:BasicObject): + v42:CBool = HasType v40, String + CondBranch v42, bb13(), bb14() + bb13(): + v44:String = RefineType v40, String + Jump bb15(v44) + bb14(): + v46:StringExact = AnyToString v30 + Jump bb15(v46) + bb15(v48:String): + v50:RegexpExact = ToRegexp v28, v48, MULTILINE|IGNORECASE|EXTENDED|NOENCODING + CheckInterrupts + Return v50 "); } diff --git a/zjit/src/hir_type/mod.rs b/zjit/src/hir_type/mod.rs index d005562d9ef4ab..9a44575042a44d 100644 --- a/zjit/src/hir_type/mod.rs +++ b/zjit/src/hir_type/mod.rs @@ -641,9 +641,6 @@ impl Type { } pub fn num_bytes(&self) -> u8 { - assert!(!self.bit_equal(types::Empty), - "a value of type Empty is unreachable and should have been eliminated before codegen"); - if self.is_subtype(types::CUInt8) || self.is_subtype(types::CInt8) { return 1; } if self.is_subtype(types::CUInt16) || self.is_subtype(types::CInt16) { return 2; } if self.is_subtype(types::CUInt32) || self.is_subtype(types::CInt32) { return 4; } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index ae8fd26c47b7b1..bd88b97eeed124 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -235,6 +235,7 @@ make_counters! { exit_too_many_keyword_parameters, exit_too_many_args_for_lir, exit_no_profile_send, + exit_no_profile_getivar, exit_splatkw_not_nil_or_hash, exit_splatkw_polymorphic, exit_splatkw_not_profiled, @@ -659,6 +660,7 @@ pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter { => exit_patchpoint_root_box_only, SendWhileTracing => exit_send_while_tracing, NoProfileSend => exit_no_profile_send, + NoProfileGetIvar => exit_no_profile_getivar, InvokeBlockNotIfunc => exit_invokeblock_not_ifunc, } }