diff --git a/NEWS.md b/NEWS.md index 12a8cb540b8092..02db0c2c33f3a0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -84,7 +84,9 @@ Note: We're only listing outstanding class updates. * `Module#descendants` is added. It returns an array of classes and modules that have the receiver in their ancestors. [[Feature #9779]] * `Module#ruby2_keywords` and top-level `ruby2_keywords` are - deprecated and will be removed in Ruby 4.4. [[Feature #22205]] + deprecated and will be removed in Ruby 4.4. [[Feature #22205]] + * `Module#method_defined?` now accepts a third optional argument to also + match private methods. [[Feature #22297]] * ObjectSpace @@ -445,6 +447,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #22205]: https://bugs.ruby-lang.org/issues/22205 [Feature #22226]: https://bugs.ruby-lang.org/issues/22226 [Feature #22238]: https://bugs.ruby-lang.org/issues/22238 +[Feature #22297]: https://bugs.ruby-lang.org/issues/22297 [PR #17201]: https://github.com/ruby/ruby/pull/17201 [GH-psych #805]: https://github.com/ruby/psych/pull/805 [RubyGems-v4.0.4]: https://github.com/rubygems/rubygems/releases/tag/v4.0.4 diff --git a/benchmark/struct_accessor.yml b/benchmark/struct_accessor.yml index d95240e2dd8e45..4be731f5ab7556 100644 --- a/benchmark/struct_accessor.yml +++ b/benchmark/struct_accessor.yml @@ -27,11 +27,21 @@ prelude: | end END end + # 200 members do not fit in a GC slot, so this struct is not embedded + H = Struct.new(*(1..200).map { |i| :"m#{i}" }) do + class_eval <<-END + def w + #{'self.m200 = nil;'*256} + end + END + end C.new(nil) # ensure common shape is known obj = C.new(nil) + heap_obj = H.new benchmark: member_reader: "obj.r" member_writer: "obj.w" + member_writer_heap: "heap_obj.w" member_reader_method: "obj.rm" member_writer_method: "obj.wm" ivar_reader: "obj.r_ivar" diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index e9ce905e2c7073..181a02bbd52d5e 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -3783,6 +3783,128 @@ def foo(s) foo(s) rescue :ok } +# struct aset returns the assigned value +assert_equal '42', %q{ + def foo(s) + x = (s.foo = 42) + x + end + + S = Struct.new(:foo) + foo(S.new) + foo(S.new) +} + +# struct aset on a frozen struct raises FrozenError (embedded) +assert_equal 'ok', %q{ + def foo(s) + s.foo = 123 + end + + S = Struct.new(:foo) + foo(S.new(1)) # compile the inline store on a non-frozen receiver + foo(S.new(2)) + frozen = S.new(3).freeze + begin + foo(frozen) + :bad + rescue FrozenError + :ok + end +} + +# struct aset on a frozen struct raises FrozenError (non-embedded) +assert_equal 'ok', %q{ + def foo(s) + s.m1 = 1 + end + + max_alloc_size, rbasic_size, rvalue_overhead = GC::INTERNAL_CONSTANTS.fetch_values( + :RVARGC_MAX_ALLOCATE_SIZE, + :RBASIC_SIZE, + :RVALUE_OVERHEAD + ) { skip(:ok) } + max_embedded_members = (max_alloc_size - rbasic_size - rvalue_overhead) / 8 + S = Struct.new(*(1..(max_embedded_members + 1)).map { |i| :"m#{i}" }) + foo(S.new) # compile the inline store on a non-frozen receiver + foo(S.new) + frozen = S.new.freeze + begin + foo(frozen) + :bad + rescue FrozenError + :ok + end +} + +# struct aset writing nil and false skips the write barrier +assert_equal '[nil, false, true]', %q{ + def foo(s) + s.a = nil + s.b = false + s.c = true + end + + S = Struct.new(:a, :b, :c) + s = S.new(1, 2, 3) + foo(s) + s = S.new(1, 2, 3) + foo(s) + [s.a, s.b, s.c] +} + +# struct aset writing heap objects exercises the write barrier and survives GC (embedded) +assert_equal '["foo", [1, 2], {k: :v}, "bar", "baz"]', %q{ + def foo(s, a, b, c, d, e) + s.m1 = a + s.m2 = b + s.m3 = c + s.m4 = d + s.m5 = e + end + + S = Struct.new(*(1..5).map { |i| :"m#{i}" }) + s = S.new + foo(s, "f", [], {}, "b", "z") # compile + GC.start + GC.start # promote s to the old generation + foo(s, "fo" + "o", [1, 2], { k: :v }, "ba" + "r", "ba" + "z") + GC.start # a missing write barrier would lose the young objects here + [s.m1, s.m2, s.m3, s.m4, s.m5] +} + +# struct aset via .send (VM_CALL_OPT_SEND) +assert_equal '7', %q{ + def foo(s) + s.send(:foo=, 7) + end + + S = Struct.new(:foo) + s = S.new + foo(s) + s = S.new + foo(s) + s.foo +} + +# struct aset via .send on a frozen struct still raises FrozenError +assert_equal 'ok', %q{ + def foo(s) + s.send(:foo=, 7) + end + + S = Struct.new(:foo) + foo(S.new) # compile the .send path on a non-frozen receiver + foo(S.new) + frozen = S.new(1).freeze + begin + foo(frozen) + :bad + rescue FrozenError + :ok + end +} + # File.join is a cfunc accepting variable arguments as a Ruby array (argc = -2) assert_equal 'foo/bar', %q{ def foo diff --git a/gc/default/default.c b/gc/default/default.c index ff2b353616ac11..0eae80af6f403f 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -4094,6 +4094,47 @@ gc_abort(void *objspace_ptr) gc_mode_set(objspace, gc_mode_none); } +#if VERIFY_FREE_SIZE +# ifdef RB_THREAD_LOCAL_SPECIFIER +# define GC_FREEING_OBJ_TLS RB_THREAD_LOCAL_SPECIFIER +# else +# define GC_FREEING_OBJ_TLS +# endif + +static GC_FREEING_OBJ_TLS VALUE gc_freeing_obj; + +/* Remember what we are tearing down so that a bad xfree() underneath can name + * the object and not just the buffer. Saved and restored because a dfree + * callback can free another object. */ +static bool +gc_obj_free(void *objspace, VALUE obj) +{ + VALUE prev = gc_freeing_obj; + gc_freeing_obj = obj; + + bool freed = rb_gc_obj_free(objspace, obj); + + gc_freeing_obj = prev; + return freed; +} + +static const char * +gc_freeing_obj_info(void) +{ + /* Not thread-local: only reachable from a rb_bug() path, where a second + * thread racing us is already unrecoverable. */ + static char buf[128]; + + if (!gc_freeing_obj) return NULL; + + snprintf(buf, sizeof(buf), "%p %s", (void *)gc_freeing_obj, rb_obj_info(gc_freeing_obj)); + return buf; +} +#else +# define gc_obj_free(objspace, obj) rb_gc_obj_free((objspace), (obj)) +# define gc_freeing_obj_info() NULL +#endif + void rb_gc_impl_shutdown_free_objects(void *objspace_ptr) { @@ -4110,7 +4151,7 @@ rb_gc_impl_shutdown_free_objects(void *objspace_ptr) asan_unpoisoning_object(vp) { if (RB_BUILTIN_TYPE(vp) != T_NONE) { rb_gc_obj_free_vm_weak_references(vp); - if (rb_gc_obj_free(objspace, vp)) { + if (gc_obj_free(objspace, vp)) { RBASIC(vp)->flags = 0; } } @@ -4184,7 +4225,7 @@ rb_gc_impl_shutdown_call_finalizer(void *objspace_ptr) asan_unpoisoning_object(vp) { if (rb_gc_shutdown_call_finalizer_p(vp)) { rb_gc_obj_free_vm_weak_references(vp); - if (rb_gc_obj_free(objspace, vp)) { + if (gc_obj_free(objspace, vp)) { RBASIC(vp)->flags = 0; } } @@ -4723,7 +4764,7 @@ gc_sweep_plane(rb_objspace_t *objspace, rb_heap_t *heap, uintptr_t p, bits_t bit gc_report(2, objspace, "page_sweep: free %p\n", (void *)p); rb_gc_obj_free_vm_weak_references(vp); - if (rb_gc_obj_free(objspace, vp)) { + if (gc_obj_free(objspace, vp)) { (void)VALGRIND_MAKE_MEM_UNDEFINED((void*)p, slot_size); gc_sweep_register_free_slot(objspace, sweep_page, ctx, p, slot_size); gc_report(3, objspace, "page_sweep: %s is freed\n", rb_obj_info(vp)); @@ -11125,11 +11166,15 @@ rb_gc_impl_free(void *objspace_ptr, void *ptr, size_t old_size) struct malloc_obj_info *info = (struct malloc_obj_info *)ptr - 1; #if VERIFY_FREE_SIZE if (!info->size) { - rb_bug("buffer %p has no recorded size. Was it allocated with ruby_mimalloc? If so it should be freed with ruby_mimfree", ptr); + const char *freeing = gc_freeing_obj_info(); + rb_bug("buffer %p has no recorded size%s%s. Was it allocated with ruby_mimalloc? If so it should be freed with ruby_mimfree", ptr, + freeing ? ", while freeing " : "", freeing ? freeing : ""); } if (old_size && (old_size + sizeof(struct malloc_obj_info)) != info->size) { - rb_bug("buffer %p freed with old_size=%zu, but was allocated with size=%zu", ptr, old_size, info->size - sizeof(struct malloc_obj_info)); + const char *freeing = gc_freeing_obj_info(); + rb_bug("buffer %p freed with old_size=%zu, but was allocated with size=%zu%s%s", ptr, old_size, info->size - sizeof(struct malloc_obj_info), + freeing ? ", while freeing " : "", freeing ? freeing : ""); } #endif ptr = info; @@ -11240,7 +11285,9 @@ rb_gc_impl_realloc(void *objspace_ptr, void *ptr, size_t new_size, size_t old_si ptr = info; #if VERIFY_FREE_SIZE if (old_size && (old_size + sizeof(struct malloc_obj_info)) != info->size) { - rb_bug("buffer %p realloced with old_size=%zu, but was allocated with size=%zu", ptr, old_size, info->size - sizeof(struct malloc_obj_info)); + const char *freeing = gc_freeing_obj_info(); + rb_bug("buffer %p realloced with old_size=%zu, but was allocated with size=%zu%s%s", ptr, old_size, info->size - sizeof(struct malloc_obj_info), + freeing ? ", while freeing " : "", freeing ? freeing : ""); } #endif old_size = info->size; diff --git a/lib/erb/util.rb b/lib/erb/util.rb index d7d69eb4f159a3..42202ea0734f3c 100644 --- a/lib/erb/util.rb +++ b/lib/erb/util.rb @@ -21,7 +21,8 @@ module ERB::Escape # :stopdoc: def html_escape(s) - CGI.escapeHTML(s.to_s) + s = s.to_s unless String === s + CGI.escapeHTML(s) end module_function :html_escape end diff --git a/spec/ruby/core/module/method_defined_spec.rb b/spec/ruby/core/module/method_defined_spec.rb index e6b4c7b8176fcf..d4ec9f27b80fe1 100644 --- a/spec/ruby/core/module/method_defined_spec.rb +++ b/spec/ruby/core/module/method_defined_spec.rb @@ -95,4 +95,54 @@ ModuleSpecs::Child.method_defined?(:private_super_module, false).should == false end end + + ruby_version_is "4.1" do + describe "when passed true as a third optional argument" do + it "returns true for private methods as well" do + # Include super + ModuleSpecs::Child.method_defined?(:public_child, true, true).should == true + ModuleSpecs::Child.method_defined?(:protected_child, true, true).should == true + ModuleSpecs::Child.method_defined?(:accessor_method, true, true).should == true + ModuleSpecs::Child.method_defined?(:private_child, true, true).should == true + ModuleSpecs::Child.method_defined?(:undefined, true, true).should == false + + # Defined in Parent + ModuleSpecs::Child.method_defined?(:public_parent, true, true).should == true + ModuleSpecs::Child.method_defined?(:protected_parent, true, true).should == true + ModuleSpecs::Child.method_defined?(:private_parent, true, true).should == true + + # Defined in Module + ModuleSpecs::Child.method_defined?(:public_module, true, true).should == true + ModuleSpecs::Child.method_defined?(:protected_module, true, true).should == true + ModuleSpecs::Child.method_defined?(:private_module, true, true).should == true + + # Defined in SuperModule + ModuleSpecs::Child.method_defined?(:public_super_module, true, true).should == true + ModuleSpecs::Child.method_defined?(:protected_super_module, true, true).should == true + ModuleSpecs::Child.method_defined?(:private_super_module, true, true).should == true + + # Ignore super + ModuleSpecs::Child.method_defined?(:public_child, false, true).should == true + ModuleSpecs::Child.method_defined?(:protected_child, false, true).should == true + ModuleSpecs::Child.method_defined?(:accessor_method, false, true).should == true + ModuleSpecs::Child.method_defined?(:private_child, false, true).should == true + ModuleSpecs::Child.method_defined?(:undefined, false, true).should == false + + # Defined in Parent + ModuleSpecs::Child.method_defined?(:public_parent, false, true).should == false + ModuleSpecs::Child.method_defined?(:protected_parent, false, true).should == false + ModuleSpecs::Child.method_defined?(:private_parent, false, true).should == false + + # Defined in Module + ModuleSpecs::Child.method_defined?(:public_module, false, true).should == false + ModuleSpecs::Child.method_defined?(:protected_module, false, true).should == false + ModuleSpecs::Child.method_defined?(:private_module, false, true).should == false + + # Defined in SuperModule + ModuleSpecs::Child.method_defined?(:public_super_module, false, true).should == false + ModuleSpecs::Child.method_defined?(:protected_super_module, false, true).should == false + ModuleSpecs::Child.method_defined?(:private_super_module, false, true).should == false + end + end + end end diff --git a/test/erb/test_erb_escape.rb b/test/erb/test_erb_escape.rb index fea2988819503a..c351feb862cbbc 100644 --- a/test/erb/test_erb_escape.rb +++ b/test/erb/test_erb_escape.rb @@ -18,6 +18,15 @@ def test_html_escape assert_equal(65536+5, ERB::Util.html_escape("&" + "x"*65536).size) end + def test_html_escape_string_subclass + klass = Class.new(String) do + def to_s + "" + end + end + assert_equal("<b>", ERB::Util.html_escape(klass.new(""))) + end + def test_html_escape_to_s object = Object.new def object.to_s diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index 1f4b5d9495256e..1b2197735dec4c 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -539,18 +539,32 @@ def test_configure_keeps_the_layout_of_a_pretty_state end def test_configure_only_writes_the_other_options_it_is_given - omit 'JRuby resets the non-string options' if RUBY_ENGINE == 'jruby' - state = JSON.state.new(max_nesting: 3, allow_nan: true, ascii_only: true, script_safe: true) + state = JSON.state.new(max_nesting: 3, allow_nan: true, ascii_only: true, script_safe: true, + strict: true, buffer_initial_length: 32) state.configure(indent: '1') assert_equal '1', state.indent assert_equal 3, state.max_nesting assert_equal true, state.allow_nan? assert_equal true, state.ascii_only? assert_equal true, state.script_safe? + assert_equal true, state.strict? + assert_equal 32, state.buffer_initial_length + end + + def test_configure_keeps_sort_keys + state = JSON.state.new(sort_keys: true) + state.configure(depth: 0) + assert_equal '{"a":2,"b":1}', state.generate({ 'b' => 1, 'a' => 2 }) + end + + def test_configure_keeps_as_json + as_json = ->(object, _is_key) { object.to_s } + state = JSON.state.new(strict: true, as_json: as_json) + state.configure(depth: 0) + assert_equal as_json, state.as_json end def test_configure_writes_a_string_option_given_as_nil - omit 'JRuby keeps the previous value for an explicit nil' if RUBY_ENGINE == 'jruby' state = JSON.state.new(indent: '1', space: '2') state.configure(indent: nil) assert_equal '', state.indent diff --git a/vm_method.c b/vm_method.c index e248f03163b758..c8536566a99a66 100644 --- a/vm_method.c +++ b/vm_method.c @@ -2589,28 +2589,14 @@ rb_mod_undef_method(int argc, VALUE *argv, VALUE mod) } static rb_method_visibility_t -check_definition_visibility(VALUE mod, int argc, VALUE *argv) +check_definition_visibility(VALUE mod, VALUE mid, bool inc_super) { - const rb_method_entry_t *me; - VALUE mid, include_super, lookup_mod = mod; - int inc_super; - ID id; - - rb_scan_args(argc, argv, "11", &mid, &include_super); - id = rb_check_id(&mid); + ID id = rb_check_id(&mid); if (!id) return METHOD_VISI_UNDEF; - if (argc == 1) { - inc_super = 1; - } - else { - inc_super = RTEST(include_super); - if (!inc_super) { - lookup_mod = RCLASS_ORIGIN(mod); - } - } + VALUE lookup_mod = inc_super ? mod : RCLASS_ORIGIN(mod); - me = rb_method_entry_without_refinements(lookup_mod, id, NULL); + const rb_method_entry_t *me = rb_method_entry_without_refinements(lookup_mod, id, NULL); if (me) { if (me->def->type == VM_METHOD_TYPE_NOTIMPLEMENTED) return METHOD_VISI_UNDEF; if (!inc_super && me->owner != mod) return METHOD_VISI_UNDEF; @@ -2621,12 +2607,14 @@ check_definition_visibility(VALUE mod, int argc, VALUE *argv) /* * call-seq: - * mod.method_defined?(symbol, inherit=true) -> true or false - * mod.method_defined?(string, inherit=true) -> true or false + * mod.method_defined?(symbol, inherit=true, include_all = false) -> true or false + * mod.method_defined?(string, inherit=true, include_all = false) -> true or false * * Returns +true+ if the named method is defined by * _mod_. If _inherit_ is set, the lookup will also search _mod_'s - * ancestors. Public and protected methods are matched. + * ancestors. + * By default only public and protected methods are matched, but if _include_all_ + * is set the lookup will also consider private methods. * String arguments are converted to symbols. * * module A @@ -2644,28 +2632,55 @@ check_definition_visibility(VALUE mod, int argc, VALUE *argv) * def method3() end * end * - * A.method_defined? :method1 #=> true - * C.method_defined? "method1" #=> true - * C.method_defined? "method2" #=> true - * C.method_defined? "method2", true #=> true - * C.method_defined? "method2", false #=> false - * C.method_defined? "method3" #=> true - * C.method_defined? "protected_method1" #=> true - * C.method_defined? "method4" #=> false - * C.method_defined? "private_method2" #=> false + * A.method_defined? :method1 #=> true + * C.method_defined? "method1" #=> true + * C.method_defined? "method2" #=> true + * C.method_defined? "method2", true #=> true + * C.method_defined? "method2", false #=> false + * C.method_defined? "method3" #=> true + * C.method_defined? "protected_method1" #=> true + * C.method_defined? "method4" #=> false + * C.method_defined? "private_method2" #=> false + * C.method_defined? "private_method2", true, true #=> true + * C.method_defined? "private_method2", false, true #=> false */ static VALUE rb_mod_method_defined(int argc, VALUE *argv, VALUE mod) { - rb_method_visibility_t visi = check_definition_visibility(mod, argc, argv); - return RBOOL(visi == METHOD_VISI_PUBLIC || visi == METHOD_VISI_PROTECTED); + VALUE mid, include_super, include_private; + + rb_scan_args(argc, argv, "12", &mid, &include_super, &include_private); + if (argc < 3) { + include_private = Qfalse; + if (argc < 2) { + include_super = Qtrue; + } + } + + rb_method_visibility_t visi = check_definition_visibility(mod, mid, RTEST(include_super)); + switch (visi) { + case METHOD_VISI_UNDEF: + return Qfalse; + case METHOD_VISI_PUBLIC: + case METHOD_VISI_PROTECTED: + return Qtrue; + case METHOD_VISI_PRIVATE: + return RBOOL(RTEST(include_private)); + default: + UNREACHABLE_RETURN(Qundef); + } } static VALUE check_definition(VALUE mod, int argc, VALUE *argv, rb_method_visibility_t visi) { - return RBOOL(check_definition_visibility(mod, argc, argv) == visi); + VALUE mid, include_super; + rb_scan_args(argc, argv, "11", &mid, &include_super); + if (argc < 2) { + include_super = Qtrue; + } + return RBOOL(check_definition_visibility(mod, mid, RTEST(include_super)) == visi); } /* diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 44b3dd390d4533..ec8ede54dbdde0 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -9049,6 +9049,16 @@ fn gen_struct_aset( assert!(unsafe { RB_TYPE_P(comptime_recv, RUBY_T_STRUCT) }); assert!((off as i64) < unsafe { RSTRUCT_LEN(comptime_recv) }); + // We are going to use an encoding that takes a 4-byte immediate which + // limits the offset to INT32_MAX (mirrors struct aref). + { + let native_off = (off as i64) * (SIZEOF_VALUE as i64); + if native_off > (i32::MAX as i64) { + gen_counter_incr(jit, asm, Counter::send_struct_aset_offset_too_large); + return None; + } + } + // Even if the comptime recv was not frozen, future recv may be. So we need to emit a guard // that the recv is not frozen. // We know all structs are heap objects, so we can check the flag directly. @@ -9060,15 +9070,49 @@ fn gen_struct_aset( // Not frozen, so we can proceed. + // All structs from the same Struct class and shape_id have the same length, so + // embedded-ness is fixed per class. + let embedded = unsafe { FL_TEST_RAW(comptime_recv, VALUE(RSTRUCT_EMBED_LEN_MASK)) }; + + // Whether the written value is a known immediate, so we can skip the write barrier. + let val_is_imm = asm.ctx.get_opnd_type(StackOpnd(0)).is_imm(); + asm_comment!(asm, "struct aset"); - let val = asm.stack_pop(1); - let recv = asm.stack_pop(1); + // Keep the value on the VM stack across the (possible) write barrier ccall so the GC + // can see it. + let val = asm.stack_opnd(0); + + let slot = if embedded != VALUE(0) { + Opnd::mem(64, recv, RUBY_OFFSET_RSTRUCT_AS_ARY + (SIZEOF_VALUE_I32 * off)) + } else { + let rstruct_ptr = asm.load(Opnd::mem(64, recv, RUBY_OFFSET_RSTRUCT_AS_HEAP_PTR)); + Opnd::mem(64, rstruct_ptr, SIZEOF_VALUE_I32 * off) + }; + asm.mov(slot, val); + + // Conditional write barrier: skipped entirely when the value is a known immediate, + // otherwise skipped at runtime for immediate/nil/false. + if !val_is_imm { + asm.spill_regs(); // unconditional for RegMappings consistency across the ccall + let skip_wb = asm.new_label("skip_wb"); + asm.test(val, (RUBY_IMMEDIATE_MASK as u64).into()); + asm.jnz(skip_wb); + asm.cmp(val, Qnil.into()); + asm.jbe(skip_wb); + + asm_comment!(asm, "write barrier"); + asm.ccall(rb_gc_writebarrier as *const u8, vec![recv, val]); - let val = asm.ccall(RSTRUCT_SET as *const u8, vec![recv, (off as i64).into(), val]); + asm.write_label(skip_wb); + } + + let write_val = asm.stack_pop(1); // pop the value (kept on stack for GC until now) + asm.stack_pop(1); // pop the receiver + // Struct member assignment returns the assigned value. let ret = asm.stack_push(Type::Unknown); - asm.mov(ret, val); + asm.mov(ret, write_val); jump_to_next_insn(jit, asm) } diff --git a/yjit/src/cruby.rs b/yjit/src/cruby.rs index 7f1826fa0cbf8a..bff3ec02071c93 100644 --- a/yjit/src/cruby.rs +++ b/yjit/src/cruby.rs @@ -207,7 +207,6 @@ pub use rb_FL_TEST_RAW as FL_TEST_RAW; pub use rb_RB_TYPE_P as RB_TYPE_P; pub use rb_BASIC_OP_UNREDEFINED_P as BASIC_OP_UNREDEFINED_P; pub use rb_RSTRUCT_LEN as RSTRUCT_LEN; -pub use rb_RSTRUCT_SET as RSTRUCT_SET; pub use rb_vm_ci_argc as vm_ci_argc; pub use rb_vm_ci_mid as vm_ci_mid; pub use rb_vm_ci_flag as vm_ci_flag; diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index 82381b4a5a4c1b..7fee4b785a7796 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -380,6 +380,7 @@ make_counters! { send_args_splat_bmethod, send_args_splat_aref, send_args_splat_aset, + send_struct_aset_offset_too_large, send_args_splat_opt_call, send_iseq_splat_arity_error, send_splat_too_long, diff --git a/zjit.rb b/zjit.rb index 995d1485cd911a..3f6d18b205c731 100644 --- a/zjit.rb +++ b/zjit.rb @@ -87,7 +87,7 @@ def stats_string stats[:guard_shape_exit_ratio] = stats[:exit_guard_shape_failure].to_f / stats[:guard_shape_count] * 100 end if stats[:code_region_bytes]&.nonzero? - stats[:side_exit_size_ratio] = stats[:side_exit_size].to_f / stats[:code_region_bytes] * 100 + stats[:side_exit_size_ratio] = stats[:side_exit_size_bytes].to_f / stats[:code_region_bytes] * 100 end if stats[:compile_time_ns]&.nonzero? stats[:compile_side_exit_time_ratio] = stats[:compile_side_exit_time_ns].to_f / stats[:compile_time_ns] * 100 @@ -184,7 +184,7 @@ def stats_string :throw_count, - :side_exit_size, + :side_exit_size_bytes, :side_exit_size_ratio, :jit_frame_heap_bytes, :jit_frame_region_bytes, diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index c6f2bd0d025a44..82fdeec96176ea 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -3244,7 +3244,7 @@ impl Assembler self.pos_marker(move |start_pos, cb| { let end_pos = cb.get_write_ptr(); let size = end_pos.as_offset() - start_pos.as_offset(); - crate::stats::incr_counter_by(crate::stats::Counter::side_exit_size, size as u64); + crate::stats::incr_counter_by(crate::stats::Counter::side_exit_size_bytes, size as u64); }); } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 23eb2acd4650f5..f64f232145675e 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -165,7 +165,7 @@ make_counters! { invalidation_time_ns, compiled_side_exit_count, - side_exit_size, + side_exit_size_bytes, compile_side_exit_time_ns, compile_hir_time_ns,