diff --git a/benchmark/marshal_load_partial_objects.yml b/benchmark/marshal_load_partial_objects.yml new file mode 100644 index 00000000000000..f13c338db20aed --- /dev/null +++ b/benchmark/marshal_load_partial_objects.yml @@ -0,0 +1,74 @@ +prelude: | + MarshalLoadPartialObjectsObject = Class.new do + def initialize(a, b, c, d) + @a = a + @b = b + @c = c + @d = d + end + end + + MarshalLoadPartialObjectsStruct = Struct.new(:a, :b, :c, :d) + MarshalLoadPartialObjectsData = Data.define(:a, :b, :c, :d) + + integer_array_10 = 10.times.to_a + integer_array_1000 = 1000.times.to_a + unique_strings = 1000.times.map { |i| "string #{i}" } + shared_string = +"shared string" + shared_strings = Array.new(1000, shared_string) + integer_hash = 1000.times.to_h { |i| [i, i] } + symbol_string_hash = 1000.times.to_h { |i| ["key#{i}".to_sym, "value #{i}"] } + objects = 1000.times.map { |i| MarshalLoadPartialObjectsObject.new(i, i + 1, i + 2, i + 3) } + structs = 1000.times.map { |i| MarshalLoadPartialObjectsStruct.new(i, i + 1, i + 2, i + 3) } + data_objects = 1000.times.map { |i| MarshalLoadPartialObjectsData.new(i, i + 1, i + 2, i + 3) } + + integer_array_10_dump = Marshal.dump(integer_array_10) + integer_array_1000_dump = Marshal.dump(integer_array_1000) + unique_strings_dump = Marshal.dump(unique_strings) + shared_strings_dump = Marshal.dump(shared_strings) + integer_hash_dump = Marshal.dump(integer_hash) + symbol_string_hash_dump = Marshal.dump(symbol_string_hash) + objects_dump = Marshal.dump(objects) + structs_dump = Marshal.dump(structs) + data_objects_dump = Marshal.dump(data_objects) + + load_proc = ->(object) { object } + +benchmark: + marshal_load_integer_array_10: 'Marshal.load(integer_array_10_dump)' + marshal_load_integer_array_10_with_proc: 'Marshal.load(integer_array_10_dump, load_proc)' + marshal_load_integer_array_10_freeze: 'Marshal.load(integer_array_10_dump, freeze: true)' + + marshal_load_integer_array_1000: 'Marshal.load(integer_array_1000_dump)' + marshal_load_integer_array_1000_with_proc: 'Marshal.load(integer_array_1000_dump, load_proc)' + marshal_load_integer_array_1000_freeze: 'Marshal.load(integer_array_1000_dump, freeze: true)' + + marshal_load_unique_strings: 'Marshal.load(unique_strings_dump)' + marshal_load_unique_strings_with_proc: 'Marshal.load(unique_strings_dump, load_proc)' + marshal_load_unique_strings_freeze: 'Marshal.load(unique_strings_dump, freeze: true)' + + marshal_load_shared_strings: 'Marshal.load(shared_strings_dump)' + marshal_load_shared_strings_with_proc: 'Marshal.load(shared_strings_dump, load_proc)' + marshal_load_shared_strings_freeze: 'Marshal.load(shared_strings_dump, freeze: true)' + + marshal_load_integer_hash: 'Marshal.load(integer_hash_dump)' + marshal_load_integer_hash_with_proc: 'Marshal.load(integer_hash_dump, load_proc)' + marshal_load_integer_hash_freeze: 'Marshal.load(integer_hash_dump, freeze: true)' + + marshal_load_symbol_string_hash: 'Marshal.load(symbol_string_hash_dump)' + marshal_load_symbol_string_hash_with_proc: 'Marshal.load(symbol_string_hash_dump, load_proc)' + marshal_load_symbol_string_hash_freeze: 'Marshal.load(symbol_string_hash_dump, freeze: true)' + + marshal_load_objects: 'Marshal.load(objects_dump)' + marshal_load_objects_with_proc: 'Marshal.load(objects_dump, load_proc)' + marshal_load_objects_freeze: 'Marshal.load(objects_dump, freeze: true)' + + marshal_load_structs: 'Marshal.load(structs_dump)' + marshal_load_structs_with_proc: 'Marshal.load(structs_dump, load_proc)' + marshal_load_structs_freeze: 'Marshal.load(structs_dump, freeze: true)' + + marshal_load_data_objects: 'Marshal.load(data_objects_dump)' + marshal_load_data_objects_with_proc: 'Marshal.load(data_objects_dump, load_proc)' + marshal_load_data_objects_freeze: 'Marshal.load(data_objects_dump, freeze: true)' + +loop_count: 1000 diff --git a/marshal.c b/marshal.c index 3ad30efa7e8f2c..a9fd5aeb918d47 100644 --- a/marshal.c +++ b/marshal.c @@ -1275,7 +1275,7 @@ mark_load_arg(void *ptr) return; rb_mark_tbl(p->symbols); rb_mark_tbl(p->data); - rb_mark_tbl(p->partial_objects); + if (p->partial_objects) rb_mark_tbl(p->partial_objects); rb_mark_hash(p->compat_tbl); } @@ -1658,7 +1658,9 @@ r_entry0(VALUE v, st_index_t num, struct load_arg *arg) st_lookup(arg->compat_tbl, v, &real_obj); } st_insert(arg->data, num, real_obj); - st_insert(arg->partial_objects, (st_data_t)real_obj, Qtrue); + if (arg->partial_objects) { + st_insert(arg->partial_objects, (st_data_t)real_obj, Qtrue); + } return v; } @@ -1693,9 +1695,11 @@ r_leave(VALUE v, struct load_arg *arg, bool partial) { v = r_fixup_compat(v, arg); if (!partial) { - st_data_t data; - st_data_t key = (st_data_t)v; - st_delete(arg->partial_objects, &key, &data); + if (arg->partial_objects) { + st_data_t data; + st_data_t key = (st_data_t)v; + st_delete(arg->partial_objects, &key, &data); + } if (arg->freeze) { if (RB_TYPE_P(v, T_MODULE) || RB_TYPE_P(v, T_CLASS)) { // noop @@ -1890,7 +1894,8 @@ r_object_for(struct load_arg *arg, bool partial, int *ivp, VALUE klass, VALUE ex rb_raise(rb_eArgError, "dump format error (unlinked)"); } v = (VALUE)link; - if (!st_lookup(arg->partial_objects, (st_data_t)v, &link)) { + if (arg->partial_objects && + !st_lookup(arg->partial_objects, (st_data_t)v, &link)) { if (arg->freeze && RB_TYPE_P(v, T_STRING)) { v = rb_str_to_interned_str(v); } @@ -2382,8 +2387,10 @@ clear_load_arg(struct load_arg *arg) arg->symbols = 0; st_free_table(arg->data); arg->data = 0; - st_free_table(arg->partial_objects); - arg->partial_objects = 0; + if (arg->partial_objects) { + st_free_table(arg->partial_objects); + arg->partial_objects = 0; + } if (arg->compat_tbl) { st_free_table(arg->compat_tbl); arg->compat_tbl = 0; @@ -2413,7 +2420,7 @@ rb_marshal_load_with_proc(VALUE port, VALUE proc, bool freeze) arg->offset = 0; arg->symbols = st_init_numtable(); arg->data = rb_init_identtable(); - arg->partial_objects = rb_init_identtable(); + arg->partial_objects = (RTEST(proc) || freeze) ? rb_init_identtable() : NULL; arg->compat_tbl = 0; arg->proc = 0; arg->readable = 0; diff --git a/test/ruby/test_marshal.rb b/test/ruby/test_marshal.rb index 84c00a6502c71b..b7e40cb2d3600d 100644 --- a/test/ruby/test_marshal.rb +++ b/test/ruby/test_marshal.rb @@ -769,6 +769,14 @@ def test_marshal_proc_freeze assert_equal object, Marshal.load(Marshal.dump(object), :freeze.to_proc) end + def test_marshal_false_proc + object = [] + object << object + + loaded = Marshal.load(Marshal.dump(object), false) + assert_same loaded, loaded.first + end + def test_marshal_load_extended_class_crash assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") begin; diff --git a/thread_pthread.c b/thread_pthread.c index 275a4f9b97a4de..8f6a7b11667b4b 100644 --- a/thread_pthread.c +++ b/thread_pthread.c @@ -92,7 +92,12 @@ static const void *const condattr_monotonic = NULL; #define USE_MN_THREADS 0 #elif HAVE_SYS_EPOLL_H #include - #define USE_MN_THREADS 1 + #ifdef EPOLLONESHOT + #define USE_MN_THREADS 1 + #else + // the scheduler arms io fds with EPOLLONESHOT (Linux 2.6.2) + #define USE_MN_THREADS 0 + #endif #elif HAVE_SYS_EVENT_H #include #define USE_MN_THREADS 1 diff --git a/thread_sched.h b/thread_sched.h index 0fe5bd951133d3..63ca7d003b6204 100644 --- a/thread_sched.h +++ b/thread_sched.h @@ -77,6 +77,9 @@ struct rb_fd_waiters { // what its waiters asked for. uint32_t armed_flags; + // epoll only: the fd is in the interest set (kept across oneshot disarms). + bool registered; + // Bumped on full disarm. Events carry the generation they were armed with, // so one queued before the fd was disarmed (and reused) is recognised. uint32_t generation; diff --git a/thread_sched_mn.c b/thread_sched_mn.c index ddf35b459b8602..038fe0b5fba1df 100644 --- a/thread_sched_mn.c +++ b/thread_sched_mn.c @@ -1228,11 +1228,14 @@ fd_event_tag(int fd, uint32_t generation) // Make the backend match `want`. Returns false if the fd cannot be registered // at all (closed, or unsupported by the backend), leaving the entry untouched. -// The fd's shard lock must be held. +// The fd's shard lock must be held. `consumed` says an epoll event for the +// current arming was just delivered, so EPOLLONESHOT has already disarmed it. static bool -fd_waiters_arm(int fd, struct rb_fd_waiters *e, uint32_t want) +fd_waiters_arm(int fd, struct rb_fd_waiters *e, uint32_t want, bool consumed) { - if (want == e->armed_flags) return true; + // After a delivery the kernel side is disarmed even when the flags agree, + // so a consumed call must fall through to re-arm. + if (want == e->armed_flags && !consumed) return true; #if HAVE_SYS_EVENT_H struct kevent ke[2]; @@ -1268,15 +1271,22 @@ fd_waiters_arm(int fd, struct rb_fd_waiters *e, uint32_t want) } #elif HAVE_SYS_EPOLL_H if (want == 0) { - if (epoll_ctl(timer_th.event_fd, EPOLL_CTL_DEL, fd, NULL) == -1) { - switch (errno) { - case EBADF: - case ENOENT: - // the fd is already closed or gone from the set - break; - default: - perror("epoll_ctl"); - rb_bug("fd_waiters_arm/epoll_ctl del failed (fd:%d errno:%d)", fd, errno); + // A delivered oneshot event has already disarmed the fd; otherwise + // disarm by MOD to no events. Either way the registration stays, so + // the next wait is one MOD instead of DEL + ADD. + if (!consumed && e->registered) { + struct epoll_event off = { .events = 0, .data = { .u64 = 0 } }; + if (epoll_ctl(timer_th.event_fd, EPOLL_CTL_MOD, fd, &off) == -1) { + switch (errno) { + case EBADF: + case ENOENT: + // the fd is already closed or gone from the set + e->registered = false; + break; + default: + perror("epoll_ctl"); + rb_bug("fd_waiters_arm/epoll_ctl disarm failed (fd:%d errno:%d)", fd, errno); + } } } // Anything epoll_wait already queued for the old arming is stale now. @@ -1285,7 +1295,7 @@ fd_waiters_arm(int fd, struct rb_fd_waiters *e, uint32_t want) return true; } - uint32_t epoll_events = 0; + uint32_t epoll_events = EPOLLONESHOT; if (want & thread_sched_waiting_io_read) epoll_events |= EPOLLIN; if (want & thread_sched_waiting_io_write) epoll_events |= EPOLLOUT; @@ -1294,7 +1304,7 @@ fd_waiters_arm(int fd, struct rb_fd_waiters *e, uint32_t want) .data = { .u64 = fd_event_tag(fd, e->generation) }, }; - int op = e->armed_flags ? EPOLL_CTL_MOD : EPOLL_CTL_ADD; + int op = e->registered ? EPOLL_CTL_MOD : EPOLL_CTL_ADD; if (epoll_ctl(timer_th.event_fd, op, fd, &event) == -1) { switch (errno) { @@ -1304,6 +1314,7 @@ fd_waiters_arm(int fd, struct rb_fd_waiters *e, uint32_t want) epoll_ctl(timer_th.event_fd, EPOLL_CTL_ADD, fd, &event) == 0) { break; } + e->registered = false; return false; case EEXIST: // Likewise in the other direction. @@ -1315,12 +1326,14 @@ fd_waiters_arm(int fd, struct rb_fd_waiters *e, uint32_t want) case EBADF: case EPERM: // closed, or the fd does not support epoll + e->registered = false; return false; default: perror("epoll_ctl"); rb_bug("fd_waiters_arm/epoll_ctl failed (fd:%d op:%d errno:%d)", fd, op, errno); } } + e->registered = true; #else # error "neither kqueue nor epoll" #endif @@ -1493,7 +1506,7 @@ timer_thread_register_waiting(rb_thread_t *th, int fd, enum thread_sched_waiting // Arm the union of what this fd's waiters want, so a second waiter // on the same fd extends the arming instead of colliding with it. - if (!fd_waiters_arm(fd, e, fd_waiters_union(e) | (uint32_t)(flags & FD_WAIT_IO_MASK))) { + if (!fd_waiters_arm(fd, e, fd_waiters_union(e) | (uint32_t)(flags & FD_WAIT_IO_MASK), false)) { fd_shard_unlock(fd); return timer_thread_unavailable; } @@ -1573,7 +1586,7 @@ timer_thread_unregister_waiting(rb_thread_t *th, int fd, enum thread_sched_waiti struct rb_fd_waiters *e = fd_waiters_lookup(fd, false); if (e) { - fd_waiters_arm(fd, e, fd_waiters_union(e)); + fd_waiters_arm(fd, e, fd_waiters_union(e), false); } } @@ -1689,7 +1702,7 @@ timer_thread_wake_fd_waiters(int fd, uint32_t generation, uint32_t wake_flags, i // Re-arm for whoever is still waiting on this fd (nothing, if // they all just woke up). - fd_waiters_arm(fd, e, fd_waiters_union(e)); + fd_waiters_arm(fd, e, fd_waiters_union(e), true); } } fd_shard_unlock(fd); diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 6e860241be08a9..3b30e1bb2563de 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -633,6 +633,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio assert_eq!(SHAPE_ID_NUM_BITS, 32); gen_const_uint32(val.0) } + &Insn::Const { val: Const::CBool(val) } => Opnd::UImm(val.into()), Insn::Const { .. } => panic!("Unexpected Const in gen_insn: {insn}"), Insn::NewArray { elements, state } => gen_new_array(jit, asm, function, opnds!(elements), &function.frame_state(*state)), Insn::NewHash { elements, state } => { diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 43bda091ae2996..7becc9eb5536c4 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -277,6 +277,8 @@ pub fn init() -> Annotations { annotate!(rb_cFloat, "nan?", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cFloat, "finite?", types::BoolExact, no_gc, leaf, elidable); annotate!(rb_cFloat, "infinite?", types::Fixnum.union(types::NilClass), no_gc, leaf, elidable); + annotate!(rb_cFalseClass, "&", inline_falseclass_and); + annotate!(rb_cTrueClass, "&", inline_trueclass_and); let thread_singleton = unsafe { rb_singleton_class(rb_cThread) }; annotate!(thread_singleton, "current", inline_thread_current, types::BasicObject, no_gc, leaf); @@ -319,6 +321,19 @@ fn inline_string_to_s(fun: &mut hir::Function, block: hir::BlockId, recv: hir::I None } +fn inline_falseclass_and(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { + // FalseClass#& just returns Qfalse and ignores its argument. + let &[_] = args else { return None; }; + Some(fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(Qfalse) })) +} + +fn inline_trueclass_and(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { + // TrueClass#& does RBOOL(RTEST(arg)) + let &[val] = args else { return None; }; + let test = fun.push_insn(block, hir::Insn::Test { val }); + Some(fun.push_insn(block, hir::Insn::BoxBool { val: test })) +} + 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); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 9a6461b9301d93..efe30cf05f66ff 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -6447,6 +6447,10 @@ impl Function { _ => insn_id, } } + &Insn::WriteBarrier { val, .. } if self.is_a(val, types::Immediate) => { + // The write barrier does nothing for immediates. + continue; + } &Insn::ArrayLength { array } => { match self.type_of(array).ruby_object() { Some(array_obj) if array_obj.is_frozen() => { @@ -6714,6 +6718,20 @@ impl Function { insn_id } } + &Insn::BoxBool { val: bool_val } => { + if let &Insn::Test { val: test_val } = self.resolve(bool_val).insn(self) { + // If the thing being Test'd is already a BoolExact + // (TrueClass|FalseClass), then we don't need to Test+BoxBool and can + // just return the test_val. + if self.is_a(test_val, types::BoolExact) { + self.make_equal_to(insn_id, test_val); + continue; + } + insn_id + } else { + insn_id + } + } &Insn::CondBranch { val, ref if_true, .. } if self.is_a(val, Type::from_cbool(true)) => { self.new_insn(Insn::Jump(if_true.clone())) } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 0b3665c3182fa5..5112f422c1467a 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -6172,7 +6172,6 @@ mod hir_opt_tests { v63:CShape = LoadField v45, :shape_id@0x1088 v64:CShape[0x1089] = GuardBitEquals v63, CShape(0x1089) recompile StoreField v45, :@x@0x108a, v15 - WriteBarrier v45, v15 v67:CShape[0x108b] = Const CShape(0x108b) StoreField v45, :shape_id@0x1088, v67 CheckInterrupts @@ -7844,7 +7843,6 @@ mod hir_opt_tests { v15:CShape = LoadField v14, :shape_id@0x1000 v16:CShape[0x1001] = GuardBitEquals v15, CShape(0x1001) recompile StoreField v14, :@foo@0x1002, v10 - WriteBarrier v14, v10 CheckInterrupts Return v10 "); @@ -7916,7 +7914,6 @@ mod hir_opt_tests { v15:CShape = LoadField v14, :shape_id@0x1000 v16:CShape[0x1001] = GuardBitEquals v15, CShape(0x1001) recompile StoreField v14, :@foo@0x1002, v10 - WriteBarrier v14, v10 v19:CShape[0x1003] = Const CShape(0x1003) StoreField v14, :shape_id@0x1000, v19 CheckInterrupts @@ -7985,13 +7982,11 @@ mod hir_opt_tests { v14:CShape = LoadField v13, :shape_id@0x1000 v15:CShape[0x1001] = GuardBitEquals v14, CShape(0x1001) recompile StoreField v13, :@foo@0x1002, v10 - WriteBarrier v13, v10 v18:CShape[0x1003] = Const CShape(0x1003) StoreField v13, :shape_id@0x1000, v18 v23:Fixnum[2] = Const Value(2) PatchPoint SingleRactorMode StoreField v13, :@bar@0x1004, v23 - WriteBarrier v13, v23 v32:CShape[0x1005] = Const CShape(0x1005) StoreField v13, :shape_id@0x1000, v32 CheckInterrupts @@ -8064,14 +8059,12 @@ mod hir_opt_tests { CondBranch v17, bb5(), bb6() bb5(): StoreField v14, :@a@0x1002, v10 - WriteBarrier v14, v10 v21:CShape[0x1003] = Const CShape(0x1003) StoreField v14, :shape_id@0x1000, v21 Jump bb4() bb6(): v24:CShape[0x1004] = GuardBitEquals v15, CShape(0x1004) recompile StoreField v14, :@a@0x1005, v10 - WriteBarrier v14, v10 Jump bb4() bb4(): CheckInterrupts @@ -10368,12 +10361,10 @@ mod hir_opt_tests { CondBranch v17, bb5(), bb6() bb5(): StoreField v6, :@foo@0x1002, v10 - WriteBarrier v6, v10 Jump bb4() bb6(): v22:CShape[0x1003] = GuardBitEquals v15, CShape(0x1003) recompile StoreField v6, :@foo@0x1004, v10 - WriteBarrier v6, v10 Jump bb4() bb4(): CheckInterrupts @@ -11677,7 +11668,6 @@ mod hir_opt_tests { v30:CShape = LoadField v28, :shape_id@0x1040 v31:CShape[0x1041] = GuardBitEquals v30, CShape(0x1041) StoreField v28, :@foo@0x1042, v17 - WriteBarrier v28, v17 v34:CShape[0x1043] = Const CShape(0x1043) StoreField v28, :shape_id@0x1040, v34 CheckInterrupts @@ -11716,7 +11706,6 @@ mod hir_opt_tests { v30:CShape = LoadField v28, :shape_id@0x1040 v31:CShape[0x1041] = GuardBitEquals v30, CShape(0x1041) StoreField v28, :@foo@0x1042, v17 - WriteBarrier v28, v17 v34:CShape[0x1043] = Const CShape(0x1043) StoreField v28, :shape_id@0x1040, v34 CheckInterrupts @@ -12547,7 +12536,6 @@ mod hir_opt_tests { v39:CInt64 = ArrayLength v33 v40:CInt64[1] = GuardLess v46, v39 ArrayAset v33, v40, v19 - WriteBarrier v33, v19 CheckInterrupts Return v19 "); @@ -18783,12 +18771,10 @@ mod hir_opt_tests { v20:CShape = LoadField v19, :shape_id@0x1000 v21:CShape[0x1001] = GuardBitEquals v20, CShape(0x1001) recompile StoreField v19, :@a@0x1002, v13 - WriteBarrier v19, v13 v24:CShape[0x1003] = Const CShape(0x1003) StoreField v19, :shape_id@0x1000, v24 PatchPoint NoEPEscape(initialize) PatchPoint SingleRactorMode - WriteBarrier v19, v13 CheckInterrupts Return v13 "); @@ -18828,7 +18814,6 @@ mod hir_opt_tests { v23:CShape = LoadField v22, :shape_id@0x1000 v24:CShape[0x1001] = GuardBitEquals v23, CShape(0x1001) recompile StoreField v22, :@a@0x1002, v16 - WriteBarrier v22, v16 v27:CShape[0x1003] = Const CShape(0x1003) StoreField v22, :shape_id@0x1000, v27 v32:Fixnum[5] = Const Value(5) @@ -18836,7 +18821,6 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Integer@0x1008, +@0x1010, cme:0x1018) v65:Fixnum[6] = Const Value(6) PatchPoint SingleRactorMode - WriteBarrier v22, v16 CheckInterrupts Return v16 "); @@ -18874,13 +18858,10 @@ mod hir_opt_tests { v20:CShape = LoadField v19, :shape_id@0x1000 v21:CShape[0x1001] = GuardBitEquals v20, CShape(0x1001) recompile StoreField v19, :@a@0x1002, v13 - WriteBarrier v19, v13 v24:CShape[0x1003] = Const CShape(0x1003) StoreField v19, :shape_id@0x1000, v24 PatchPoint NoEPEscape(initialize) PatchPoint SingleRactorMode - WriteBarrier v19, v13 - WriteBarrier v19, v13 CheckInterrupts Return v13 "); @@ -19369,7 +19350,6 @@ mod hir_opt_tests { v68:CShape = LoadField v18, :shape_id@0x1038 v69:CShape[0x103b] = GuardBitEquals v68, CShape(0x103b) recompile StoreField v18, :@levar@0x103a, v19 - WriteBarrier v18, v19 v72:CShape[0x1039] = Const CShape(0x1039) StoreField v18, :shape_id@0x1038, v72 Jump bb5(v18) @@ -20160,7 +20140,6 @@ mod hir_opt_tests { CondBranch v23, bb5(), bb6() bb5(): StoreField v11, :@a@0x1003, v17 - WriteBarrier v11, v17 Jump bb4() bb6(): v28:CShape[0x1004] = Const CShape(0x1004) @@ -20168,7 +20147,6 @@ mod hir_opt_tests { CondBranch v29, bb7(), bb8() bb7(): StoreField v11, :@a@0x1003, v17 - WriteBarrier v11, v17 v35:CShape[0x1002] = Const CShape(0x1002) StoreField v11, :shape_id@0x1001, v35 Jump bb4() @@ -20188,7 +20166,6 @@ mod hir_opt_tests { CondBranch v57, bb10(), bb11() bb10(): StoreField v11, :@a@0x1003, v73 - WriteBarrier v11, v73 Jump bb9() bb11(): SetIvar v11, :@a, v73 @@ -20219,7 +20196,6 @@ mod hir_opt_tests { CondBranch v23, bb5(), bb6() bb5(): StoreField v11, :@a@0x1003, v17 - WriteBarrier v11, v17 Jump bb4() bb6(): v28:CShape[0x1004] = Const CShape(0x1004) @@ -20227,7 +20203,6 @@ mod hir_opt_tests { CondBranch v29, bb7(), bb8() bb7(): StoreField v11, :@a@0x1003, v17 - WriteBarrier v11, v17 v35:CShape[0x1002] = Const CShape(0x1002) StoreField v11, :shape_id@0x1001, v35 Jump bb4() @@ -21693,13 +21668,11 @@ mod hir_opt_tests { v117:CShape = LoadField v85, :shape_id@0x1088 v118:CShape[0x1089] = GuardBitEquals v117, CShape(0x1089) recompile StoreField v85, :@x@0x108a, v15 - WriteBarrier v85, v15 v121:CShape[0x108b] = Const CShape(0x108b) StoreField v85, :shape_id@0x1088, v121 PatchPoint NoEPEscape(initialize) PatchPoint SingleRactorMode StoreField v85, :@y@0x108c, v17 - WriteBarrier v85, v17 v136:CShape[0x108d] = Const CShape(0x108d) StoreField v85, :shape_id@0x1088, v136 CheckInterrupts @@ -21718,13 +21691,11 @@ mod hir_opt_tests { v157:CShape = LoadField v95, :shape_id@0x1088 v158:CShape[0x1089] = GuardBitEquals v157, CShape(0x1089) recompile StoreField v95, :@x@0x108a, v47 - WriteBarrier v95, v47 v161:CShape[0x108b] = Const CShape(0x108b) StoreField v95, :shape_id@0x1088, v161 PatchPoint NoEPEscape(initialize) PatchPoint SingleRactorMode StoreField v95, :@y@0x108c, v49 - WriteBarrier v95, v49 v176:CShape[0x108d] = Const CShape(0x108d) StoreField v95, :shape_id@0x1088, v176 CheckInterrupts @@ -21896,7 +21867,6 @@ mod hir_opt_tests { v71:Fixnum = GuardType v15, Fixnum recompile v72:Fixnum = FixnumAdd v71, v17 StoreField v14, :@hclk@0x1003, v72 - WriteBarrier v14, v72 PatchPoint SingleRactorMode v37:BasicObject = LoadField v14, :@hclk_target@0x1040 PatchPoint MethodRedefined(Integer@0x1008, <=@0x1041, cme:0x1048) @@ -22006,4 +21976,109 @@ mod hir_opt_tests { Return v49 "); } + + #[test] + fn test_optimize_falseclass_and_to_false() { + eval(r#" + def test(cond) + cond & "hello" + end + + test(false) + "#); + assert_snapshot!(hir_string("test"), @" + fn test@:3: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:CPtr = LoadSP + v3:BasicObject = LoadField v2, :cond@0x1000 + Jump bb3(v1, v3) + bb2(): + EntryPoint JIT(0) + v6:BasicObject = LoadArg :self@0 + v7:BasicObject = LoadArg :cond@1 + Jump bb3(v6, v7) + bb3(v9:BasicObject, v10:BasicObject): + v15:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v16:StringExact = StringCopy v15 + PatchPoint MethodRedefined(FalseClass@0x1010, &@0x1018, cme:0x1020) + v27:FalseClass = GuardType v10, FalseClass recompile + v28:FalseClass = Const Value(false) + CheckInterrupts + Return v28 + "); + } + + #[test] + fn test_optimize_trueclass_and_to_test() { + eval(r#" + def test(l, r) + l & r + end + + test(true, "hello") + "#); + assert_snapshot!(hir_string("test"), @" + fn test@:3: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:CPtr = LoadSP + v3:BasicObject = LoadField v2, :l@0x1000 + v4:BasicObject = LoadField v2, :r@0x1001 + Jump bb3(v1, v3, v4) + bb2(): + EntryPoint JIT(0) + v7:BasicObject = LoadArg :self@0 + v8:BasicObject = LoadArg :l@1 + v9:BasicObject = LoadArg :r@2 + Jump bb3(v7, v8, v9) + bb3(v11:BasicObject, v12:BasicObject, v13:BasicObject): + PatchPoint MethodRedefined(TrueClass@0x1008, &@0x1010, cme:0x1018) + v28:TrueClass = GuardType v12, TrueClass recompile + v29:CBool = Test v13 + v30:BoolExact = BoxBool v29 + CheckInterrupts + Return v30 + "); + } + + #[test] + fn test_remove_box_of_test_of_bool_exact() { + eval(r#" + def test(l, a, b) + l & (a == b) + end + + test(true, 3, 4) + "#); + assert_snapshot!(hir_string("test"), @" + fn test@:3: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:CPtr = LoadSP + v3:BasicObject = LoadField v2, :l@0x1000 + v4:BasicObject = LoadField v2, :a@0x1001 + v5:BasicObject = LoadField v2, :b@0x1002 + Jump bb3(v1, v3, v4, v5) + bb2(): + EntryPoint JIT(0) + v8:BasicObject = LoadArg :self@0 + v9:BasicObject = LoadArg :l@1 + v10:BasicObject = LoadArg :a@2 + v11:BasicObject = LoadArg :b@3 + Jump bb3(v8, v9, v10, v11) + bb3(v13:BasicObject, v14:BasicObject, v15:BasicObject, v16:BasicObject): + PatchPoint MethodRedefined(Integer@0x1008, ==@0x1010, cme:0x1018) + v35:Fixnum = GuardType v15, Fixnum recompile + v36:Fixnum = GuardType v16, Fixnum + v37:BoolExact = FixnumEq v35, v36 + PatchPoint MethodRedefined(TrueClass@0x1040, &@0x1048, cme:0x1050) + v40:TrueClass = GuardType v14, TrueClass recompile + CheckInterrupts + Return v37 + "); + } } diff --git a/zjit/src/state.rs b/zjit/src/state.rs index 183b3c206ee813..9889997d1e4838 100644 --- a/zjit/src/state.rs +++ b/zjit/src/state.rs @@ -449,7 +449,7 @@ pub extern "C" fn rb_zjit_assert_compiles(_ec: EcPtr, _self: VALUE) -> VALUE { } /// Resolve a profile frame VALUE to a human-readable "label (path)" string. -fn resolve_frame_label(frame: VALUE) -> String { +fn resolve_frame_label(frame: VALUE, line_number: i32) -> String { unsafe { let label_str = ruby_str_to_rust_string_result(rb_profile_frame_full_label(frame)).unwrap_or("".into()); @@ -457,7 +457,7 @@ fn resolve_frame_label(frame: VALUE) -> String { let path = if path.nil_p() { rb_profile_frame_path(frame) } else { path }; let path_str = ruby_str_to_rust_string_result(path).unwrap_or("".into()); - format!("{label_str} ({path_str})") + format!("{label_str} ({path_str}:{line_number})") } } @@ -574,6 +574,6 @@ fn capture_ruby_frames() -> Vec { // Resolve each frame to a human-readable string (top frame first) (0..stack_length as usize) - .map(|i| resolve_frame_label(frames_buffer[i])) + .map(|i| resolve_frame_label(frames_buffer[i], lines_buffer[i])) .collect() }