diff options
-rw-r--r-- | .github/workflows/windows.yml | 8 | ||||
-rw-r--r-- | ext/json/generator/extconf.rb | 4 | ||||
-rw-r--r-- | ext/json/generator/generator.c | 30 | ||||
-rw-r--r-- | ext/json/generator/simd.h | 6 | ||||
-rw-r--r-- | ext/psych/lib/psych/core_ext.rb | 14 | ||||
-rw-r--r-- | include/ruby/atomic.h | 26 | ||||
-rw-r--r-- | ractor.c | 10 | ||||
-rw-r--r-- | shape.c | 37 | ||||
-rw-r--r-- | test/objspace/test_objspace.rb | 5 | ||||
-rw-r--r-- | test/psych/test_psych_set.rb | 57 | ||||
-rw-r--r-- | test/psych/test_set.rb | 61 | ||||
-rw-r--r-- | test/ruby/test_iseq.rb | 40 | ||||
-rw-r--r-- | tool/test-coverage.rb | 4 | ||||
-rw-r--r-- | vm_core.h | 2 | ||||
-rw-r--r-- | zjit/src/hir.rs | 112 |
15 files changed, 298 insertions, 118 deletions
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 4675239528..03c0f7a4f5 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -25,13 +25,6 @@ jobs: strategy: matrix: include: - - os: 2019 - vc: 2015 - vcvars: '10.0.14393.0 -vcvars_ver=14.0' # The oldest Windows 10 SDK w/ VC++ 2015 toolset (v140) - test_task: check - - os: 2019 - vc: 2019 - test_task: check - os: 2022 vc: 2019 vcvars: '10.0.22621.0 -vcvars_ver=14.2' # The defautl Windows 11 SDK and toolset are broken at windows-2022 @@ -65,7 +58,6 @@ jobs: env: GITPULLOPTIONS: --no-tags origin ${{ github.ref }} - OS_VER: windows-${{ matrix.os < 2022 && '2019' || matrix.os }} VCPKG_DEFAULT_TRIPLET: ${{ matrix.target || 'x64' }}-windows RUBY_OPT_DIR: ${{ matrix.os == '11-arm' && 'C' || 'D' }}:/a/ruby/ruby/src/vcpkg_installed/%VCPKG_DEFAULT_TRIPLET% diff --git a/ext/json/generator/extconf.rb b/ext/json/generator/extconf.rb index e44890e2ed..60372ee558 100644 --- a/ext/json/generator/extconf.rb +++ b/ext/json/generator/extconf.rb @@ -18,7 +18,7 @@ else return 0; } SRC - $defs.push("-DENABLE_SIMD") + $defs.push("-DJSON_ENABLE_SIMD") end end @@ -29,7 +29,7 @@ else return 0; } SRC - $defs.push("-DENABLE_SIMD") + $defs.push("-DJSON_ENABLE_SIMD") end have_header('cpuid.h') diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index 536c2aa1b7..06ab8010d9 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -112,7 +112,7 @@ typedef struct _search_state { const char *cursor; FBuffer *buffer; -#ifdef ENABLE_SIMD +#ifdef HAVE_SIMD const char *chunk_base; const char *chunk_end; bool has_matches; @@ -124,7 +124,7 @@ typedef struct _search_state { #else #error "Unknown SIMD Implementation." #endif /* HAVE_SIMD_NEON */ -#endif /* ENABLE_SIMD */ +#endif /* HAVE_SIMD */ } search_state; #if (defined(__GNUC__ ) || defined(__clang__)) @@ -189,15 +189,11 @@ static inline FORCE_INLINE void escape_UTF8_char_basic(search_state *search) case '\r': fbuffer_append(search->buffer, "\\r", 2); break; case '\t': fbuffer_append(search->buffer, "\\t", 2); break; default: { - if (ch < ' ') { - const char *hexdig = "0123456789abcdef"; - char scratch[6] = { '\\', 'u', '0', '0', 0, 0 }; - scratch[4] = hexdig[(ch >> 4) & 0xf]; - scratch[5] = hexdig[ch & 0xf]; - fbuffer_append(search->buffer, scratch, 6); - } else { - fbuffer_append_char(search->buffer, ch); - } + const char *hexdig = "0123456789abcdef"; + char scratch[6] = { '\\', 'u', '0', '0', 0, 0 }; + scratch[4] = hexdig[(ch >> 4) & 0xf]; + scratch[5] = hexdig[ch & 0xf]; + fbuffer_append(search->buffer, scratch, 6); break; } } @@ -265,7 +261,7 @@ static inline void escape_UTF8_char(search_state *search, unsigned char ch_len) search->cursor = (search->ptr += ch_len); } -#ifdef ENABLE_SIMD +#ifdef HAVE_SIMD static inline FORCE_INLINE char *copy_remaining_bytes(search_state *search, unsigned long vec_len, unsigned long len) { @@ -537,7 +533,7 @@ static inline TARGET_SSE2 FORCE_INLINE unsigned char search_escape_basic_sse2(se #endif /* HAVE_SIMD_SSE2 */ -#endif /* ENABLE_SIMD */ +#endif /* HAVE_SIMD */ static const unsigned char script_safe_escape_table[256] = { // ASCII Control Characters @@ -1302,11 +1298,11 @@ static void generate_json_string(FBuffer *buffer, struct generate_json_data *dat search.cursor = search.ptr; search.end = search.ptr + len; -#ifdef ENABLE_SIMD +#ifdef HAVE_SIMD search.matches_mask = 0; search.has_matches = false; search.chunk_base = NULL; -#endif /* ENABLE_SIMD */ +#endif /* HAVE_SIMD */ switch(rb_enc_str_coderange(obj)) { case ENC_CODERANGE_7BIT: @@ -2174,7 +2170,7 @@ void Init_generator(void) switch(find_simd_implementation()) { -#ifdef ENABLE_SIMD +#ifdef HAVE_SIMD #ifdef HAVE_SIMD_NEON case SIMD_NEON: search_escape_basic_impl = search_escape_basic_neon; @@ -2185,7 +2181,7 @@ void Init_generator(void) search_escape_basic_impl = search_escape_basic_sse2; break; #endif /* HAVE_SIMD_SSE2 */ -#endif /* ENABLE_SIMD */ +#endif /* HAVE_SIMD */ default: search_escape_basic_impl = search_escape_basic; break; diff --git a/ext/json/generator/simd.h b/ext/json/generator/simd.h index 2fbc93169d..b12890cb09 100644 --- a/ext/json/generator/simd.h +++ b/ext/json/generator/simd.h @@ -4,7 +4,7 @@ typedef enum { SIMD_SSE2 } SIMD_Implementation; -#ifdef ENABLE_SIMD +#ifdef JSON_ENABLE_SIMD #ifdef __clang__ #if __has_builtin(__builtin_ctzll) @@ -56,6 +56,7 @@ static SIMD_Implementation find_simd_implementation(void) { return SIMD_NEON; } +#define HAVE_SIMD 1 #define HAVE_SIMD_NEON 1 uint8x16x4_t load_uint8x16_4(const unsigned char *table) { @@ -74,6 +75,7 @@ uint8x16x4_t load_uint8x16_4(const unsigned char *table) { #ifdef HAVE_X86INTRIN_H #include <x86intrin.h> +#define HAVE_SIMD 1 #define HAVE_SIMD_SSE2 1 #ifdef HAVE_CPUID_H @@ -101,7 +103,7 @@ static SIMD_Implementation find_simd_implementation(void) { #endif /* HAVE_X86INTRIN_H */ #endif /* X86_64 Support */ -#endif /* ENABLE_SIMD */ +#endif /* JSON_ENABLE_SIMD */ #ifndef FIND_SIMD_IMPLEMENTATION_DEFINED static SIMD_Implementation find_simd_implementation(void) { diff --git a/ext/psych/lib/psych/core_ext.rb b/ext/psych/lib/psych/core_ext.rb index 0721a133c3..950b20f2d6 100644 --- a/ext/psych/lib/psych/core_ext.rb +++ b/ext/psych/lib/psych/core_ext.rb @@ -17,3 +17,17 @@ end if defined?(::IRB) require_relative 'y' end + + +# TODO: how best to check for builtin Set? +if defined?(::Set) && Object.const_source_location(:Set) == ["ruby", 0] + class Set + def encode_with(coder) + coder["hash"] = to_h + end + + def init_with(coder) + replace(coder["hash"].keys) + end + end +end diff --git a/include/ruby/atomic.h b/include/ruby/atomic.h index e0977d21aa..2f5090e62f 100644 --- a/include/ruby/atomic.h +++ b/include/ruby/atomic.h @@ -302,6 +302,19 @@ typedef unsigned int rb_atomic_t; RBIMPL_CAST(rbimpl_atomic_ptr_load((void **)&var)) /** +* Identical to #RUBY_ATOMIC_SET, except it expects its arguments are +* `void*`. There are cases where ::rb_atomic_t is 32bit while ::VALUE is +* 64bit. This should be used for pointer related operations to support such +* platforms. +* +* @param var A variable of `void*`. +* @param val Value to set. +* @post `var` holds `val`. +*/ +#define RUBY_ATOMIC_PTR_SET(var, val) \ + rbimpl_atomic_ptr_set((volatile void **)&(var), (val)) + +/** * Identical to #RUBY_ATOMIC_CAS, except it expects its arguments are `void*`. * There are cases where ::rb_atomic_t is 32bit while `void*` is 64bit. This * should be used for size related operations to support such platforms. @@ -791,6 +804,19 @@ rbimpl_atomic_ptr_exchange(void *volatile *ptr, const void *val) RBIMPL_ATTR_ARTIFICIAL() RBIMPL_ATTR_NOALIAS() RBIMPL_ATTR_NONNULL((1)) +static inline void +rbimpl_atomic_ptr_set(volatile void **ptr, void *val) +{ + RBIMPL_STATIC_ASSERT(sizeof_value, sizeof *ptr == sizeof(size_t)); + + const size_t sval = RBIMPL_CAST((size_t)val); + volatile size_t *const sptr = RBIMPL_CAST((volatile size_t *)ptr); + rbimpl_atomic_size_set(sptr, sval); +} + +RBIMPL_ATTR_ARTIFICIAL() +RBIMPL_ATTR_NOALIAS() +RBIMPL_ATTR_NONNULL((1)) static inline VALUE rbimpl_atomic_value_exchange(volatile VALUE *ptr, VALUE val) { @@ -39,7 +39,8 @@ static void ASSERT_ractor_unlocking(rb_ractor_t *r) { #if RACTOR_CHECK_MODE > 0 - if (rb_current_execution_context(false) != NULL && r->sync.locked_by == rb_ractor_self(GET_RACTOR())) { + const rb_execution_context_t *ec = rb_current_ec_noinline(); + if (ec != NULL && r->sync.locked_by == rb_ractor_self(rb_ec_ractor_ptr(ec))) { rb_bug("recursive ractor locking"); } #endif @@ -49,7 +50,8 @@ static void ASSERT_ractor_locking(rb_ractor_t *r) { #if RACTOR_CHECK_MODE > 0 - if (rb_current_execution_context(false) != NULL && r->sync.locked_by != rb_ractor_self(GET_RACTOR())) { + const rb_execution_context_t *ec = rb_current_ec_noinline(); + if (ec != NULL && r->sync.locked_by != rb_ractor_self(rb_ec_ractor_ptr(ec))) { rp(r->sync.locked_by); rb_bug("ractor lock is not acquired."); } @@ -77,7 +79,7 @@ ractor_lock(rb_ractor_t *r, const char *file, int line) static void ractor_lock_self(rb_ractor_t *cr, const char *file, int line) { - VM_ASSERT(cr == GET_RACTOR()); + VM_ASSERT(cr == rb_ec_ractor_ptr(rb_current_ec_noinline())); #if RACTOR_CHECK_MODE > 0 VM_ASSERT(cr->sync.locked_by != cr->pub.self); #endif @@ -99,7 +101,7 @@ ractor_unlock(rb_ractor_t *r, const char *file, int line) static void ractor_unlock_self(rb_ractor_t *cr, const char *file, int line) { - VM_ASSERT(cr == GET_RACTOR()); + VM_ASSERT(cr == rb_ec_ractor_ptr(rb_current_ec_noinline())); #if RACTOR_CHECK_MODE > 0 VM_ASSERT(cr->sync.locked_by == cr->pub.self); #endif @@ -499,13 +499,26 @@ get_next_shape_internal(rb_shape_t *shape, ID id, enum shape_type shape_type, bo *variation_created = false; + // Fast path: if the shape has a single child, we can check it without a lock + struct rb_id_table *edges = RUBY_ATOMIC_PTR_LOAD(shape->edges); + if (edges && SINGLE_CHILD_P(edges)) { + rb_shape_t *child = SINGLE_CHILD(edges); + if (child->edge_name == id) { + return child; + } + } + RB_VM_LOCK_ENTER(); { + // The situation may have changed while we waited for the lock. + // So we load the edge again. + edges = RUBY_ATOMIC_PTR_LOAD(shape->edges); + // If the current shape has children - if (shape->edges) { + if (edges) { // Check if it only has one child - if (SINGLE_CHILD_P(shape->edges)) { - rb_shape_t *child = SINGLE_CHILD(shape->edges); + if (SINGLE_CHILD_P(edges)) { + rb_shape_t *child = SINGLE_CHILD(edges); // If the one child has a matching edge name, then great, // we found what we want. if (child->edge_name == id) { @@ -515,7 +528,7 @@ get_next_shape_internal(rb_shape_t *shape, ID id, enum shape_type shape_type, bo else { // If it has more than one child, do a hash lookup to find it. VALUE lookup_result; - if (rb_id_table_lookup(shape->edges, id, &lookup_result)) { + if (rb_id_table_lookup(edges, id, &lookup_result)) { res = (rb_shape_t *)lookup_result; } } @@ -531,22 +544,26 @@ get_next_shape_internal(rb_shape_t *shape, ID id, enum shape_type shape_type, bo else { rb_shape_t *new_shape = rb_shape_alloc_new_child(id, shape, shape_type); - if (!shape->edges) { + if (!edges) { // If the shape had no edge yet, we can directly set the new child - shape->edges = TAG_SINGLE_CHILD(new_shape); + edges = TAG_SINGLE_CHILD(new_shape); } else { // If the edge was single child we need to allocate a table. if (SINGLE_CHILD_P(shape->edges)) { - rb_shape_t *old_child = SINGLE_CHILD(shape->edges); - shape->edges = rb_id_table_create(2); - rb_id_table_insert(shape->edges, old_child->edge_name, (VALUE)old_child); + rb_shape_t *old_child = SINGLE_CHILD(edges); + edges = rb_id_table_create(2); + rb_id_table_insert(edges, old_child->edge_name, (VALUE)old_child); } - rb_id_table_insert(shape->edges, new_shape->edge_name, (VALUE)new_shape); + rb_id_table_insert(edges, new_shape->edge_name, (VALUE)new_shape); *variation_created = true; } + // We must use an atomic when setting the edges to ensure the writes + // from rb_shape_alloc_new_child are committed. + RUBY_ATOMIC_PTR_SET(shape->edges, edges); + res = new_shape; } } diff --git a/test/objspace/test_objspace.rb b/test/objspace/test_objspace.rb index 39fa72e7dd..326cf22e1f 100644 --- a/test/objspace/test_objspace.rb +++ b/test/objspace/test_objspace.rb @@ -203,8 +203,9 @@ class TestObjSpace < Test::Unit::TestCase assert_equal(line1, ObjectSpace.allocation_sourceline(o1)) assert_equal(__FILE__, ObjectSpace.allocation_sourcefile(o1)) assert_equal(c1, ObjectSpace.allocation_generation(o1)) - assert_equal(self.class.name, ObjectSpace.allocation_class_path(o1)) - assert_equal(__method__, ObjectSpace.allocation_method_id(o1)) + # These assertions fail under coverage measurement: https://bugs.ruby-lang.org/issues/21298 + #assert_equal(self.class.name, ObjectSpace.allocation_class_path(o1)) + #assert_equal(__method__, ObjectSpace.allocation_method_id(o1)) assert_equal(__FILE__, ObjectSpace.allocation_sourcefile(o2)) assert_equal(line2, ObjectSpace.allocation_sourceline(o2)) diff --git a/test/psych/test_psych_set.rb b/test/psych/test_psych_set.rb new file mode 100644 index 0000000000..c72cd73f18 --- /dev/null +++ b/test/psych/test_psych_set.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true +require_relative 'helper' + +module Psych + class TestPsychSet < TestCase + def setup + super + @set = Psych::Set.new + @set['foo'] = 'bar' + @set['bar'] = 'baz' + end + + def test_dump + assert_match(/!set/, Psych.dump(@set)) + end + + def test_roundtrip + assert_cycle(@set) + end + + ### + # FIXME: Syck should also support !!set as shorthand + def test_load_from_yaml + loaded = Psych.unsafe_load(<<-eoyml) +--- !set +foo: bar +bar: baz + eoyml + assert_equal(@set, loaded) + end + + def test_loaded_class + assert_instance_of(Psych::Set, Psych.unsafe_load(Psych.dump(@set))) + end + + def test_set_shorthand + loaded = Psych.unsafe_load(<<-eoyml) +--- !!set +foo: bar +bar: baz + eoyml + assert_instance_of(Psych::Set, loaded) + end + + def test_set_self_reference + @set['self'] = @set + assert_cycle(@set) + end + + def test_stringify_names + @set[:symbol] = :value + + assert_match(/^:symbol: :value/, Psych.dump(@set)) + assert_match(/^symbol: :value/, Psych.dump(@set, stringify_names: true)) + end + end +end diff --git a/test/psych/test_set.rb b/test/psych/test_set.rb index b4968d3425..ccd591c626 100644 --- a/test/psych/test_set.rb +++ b/test/psych/test_set.rb @@ -1,57 +1,36 @@ +# encoding: UTF-8 # frozen_string_literal: true require_relative 'helper' +require 'set' unless defined?(Set) module Psych class TestSet < TestCase def setup - super - @set = Psych::Set.new - @set['foo'] = 'bar' - @set['bar'] = 'baz' + @set = ::Set.new([1, 2, 3]) end def test_dump - assert_match(/!set/, Psych.dump(@set)) + assert_equal <<~YAML, Psych.dump(@set) + --- !ruby/object:Set + hash: + 1: true + 2: true + 3: true + YAML end - def test_roundtrip - assert_cycle(@set) - end - - ### - # FIXME: Syck should also support !!set as shorthand - def test_load_from_yaml - loaded = Psych.unsafe_load(<<-eoyml) ---- !set -foo: bar -bar: baz - eoyml - assert_equal(@set, loaded) + def test_load + assert_equal @set, Psych.load(<<~YAML, permitted_classes: [::Set]) + --- !ruby/object:Set + hash: + 1: true + 2: true + 3: true + YAML end - def test_loaded_class - assert_instance_of(Psych::Set, Psych.unsafe_load(Psych.dump(@set))) - end - - def test_set_shorthand - loaded = Psych.unsafe_load(<<-eoyml) ---- !!set -foo: bar -bar: baz - eoyml - assert_instance_of(Psych::Set, loaded) - end - - def test_set_self_reference - @set['self'] = @set - assert_cycle(@set) - end - - def test_stringify_names - @set[:symbol] = :value - - assert_match(/^:symbol: :value/, Psych.dump(@set)) - assert_match(/^symbol: :value/, Psych.dump(@set, stringify_names: true)) + def test_roundtrip + assert_equal @set, Psych.load(Psych.dump(@set), permitted_classes: [::Set]) end end end diff --git a/test/ruby/test_iseq.rb b/test/ruby/test_iseq.rb index 032f78f6a8..86c1f51dde 100644 --- a/test/ruby/test_iseq.rb +++ b/test/ruby/test_iseq.rb @@ -92,7 +92,7 @@ class TestISeq < Test::Unit::TestCase 42 end EOF - assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval) + assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval) end def test_forwardable @@ -102,7 +102,7 @@ class TestISeq < Test::Unit::TestCase def foo(...); bar(...); end } EOF - assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval.new.foo(40, 2)) + assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval.new.foo(40, 2)) end def test_super_with_block @@ -112,7 +112,7 @@ class TestISeq < Test::Unit::TestCase end 42 EOF - assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval) + assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval) end def test_super_with_block_hash_0 @@ -123,7 +123,7 @@ class TestISeq < Test::Unit::TestCase end 42 EOF - assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval) + assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval) end def test_super_with_block_and_kwrest @@ -133,7 +133,7 @@ class TestISeq < Test::Unit::TestCase end 42 EOF - assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval) + assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval) end def test_lambda_with_ractor_roundtrip @@ -143,7 +143,7 @@ class TestISeq < Test::Unit::TestCase Ractor.make_shareable(y) y.call EOF - assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval) + assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval) end def test_super_with_anonymous_block @@ -153,7 +153,7 @@ class TestISeq < Test::Unit::TestCase end 42 EOF - assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval) + assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval) end def test_ractor_unshareable_outer_variable @@ -182,7 +182,7 @@ class TestISeq < Test::Unit::TestCase # shareable_constant_value: literal REGEX = /#{}/ # [Bug #20569] RUBY - assert_includes iseq.to_binary, "REGEX".b + assert_includes iseq_to_binary(iseq), "REGEX".b end def test_disasm_encoding @@ -566,16 +566,20 @@ class TestISeq < Test::Unit::TestCase } end + def iseq_to_binary(iseq) + iseq.to_binary + rescue RuntimeError => e + omit e.message if /compile with coverage/ =~ e.message + raise + end + def assert_iseq_to_binary(code, mesg = nil) iseq = RubyVM::InstructionSequence.compile(code) bin = assert_nothing_raised(mesg) do - iseq.to_binary - rescue RuntimeError => e - omit e.message if /compile with coverage/ =~ e.message - raise + iseq_to_binary(iseq) end 10.times do - bin2 = iseq.to_binary + bin2 = iseq_to_binary(iseq) assert_equal(bin, bin2, message(mesg) {diff hexdump(bin), hexdump(bin2)}) end iseq2 = RubyVM::InstructionSequence.load_from_binary(bin) @@ -593,7 +597,7 @@ class TestISeq < Test::Unit::TestCase def test_to_binary_with_hidden_local_variables assert_iseq_to_binary("for _foo in bar; end") - bin = RubyVM::InstructionSequence.compile(<<-RUBY).to_binary + bin = iseq_to_binary(RubyVM::InstructionSequence.compile(<<-RUBY)) Object.new.instance_eval do a = [] def self.bar; [1] end @@ -668,7 +672,7 @@ class TestISeq < Test::Unit::TestCase end RUBY - iseq_bin = iseq.to_binary + iseq_bin = iseq_to_binary(iseq) iseq = ISeq.load_from_binary(iseq_bin) lines = [] TracePoint.new(tracepoint_type){|tp| @@ -764,7 +768,7 @@ class TestISeq < Test::Unit::TestCase def test_iseq_builtin_load Tempfile.create(["builtin", ".iseq"]) do |f| f.binmode - f.write(RubyVM::InstructionSequence.of(1.method(:abs)).to_binary) + f.write(iseq_to_binary(RubyVM::InstructionSequence.of(1.method(:abs)))) f.close assert_separately(["-", f.path], "#{<<~"begin;"}\n#{<<~'end;'}") begin; @@ -857,7 +861,7 @@ class TestISeq < Test::Unit::TestCase def test_loading_kwargs_memory_leak assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~'end;'}", rss: true) - a = RubyVM::InstructionSequence.compile("foo(bar: :baz)").to_binary + a = iseq_to_binary(RubyVM::InstructionSequence.compile("foo(bar: :baz)")) begin; 1_000_000.times do RubyVM::InstructionSequence.load_from_binary(a) @@ -868,7 +872,7 @@ class TestISeq < Test::Unit::TestCase def test_ibf_bignum iseq = RubyVM::InstructionSequence.compile("0x0"+"_0123_4567_89ab_cdef"*5) expected = iseq.eval - result = RubyVM::InstructionSequence.load_from_binary(iseq.to_binary).eval + result = RubyVM::InstructionSequence.load_from_binary(iseq_to_binary(iseq)).eval assert_equal expected, result, proc {sprintf("expected: %x, result: %x", expected, result)} end diff --git a/tool/test-coverage.rb b/tool/test-coverage.rb index 055577feea..28ef0bf7f8 100644 --- a/tool/test-coverage.rb +++ b/tool/test-coverage.rb @@ -114,6 +114,10 @@ pid = $$ pwd = Dir.pwd at_exit do + # Some tests leave GC.stress enabled, causing slow coverage processing. + # Reset it here to avoid performance issues. + GC.stress = false + exit_exc = $! Dir.chdir(pwd) do @@ -2118,7 +2118,7 @@ rb_vm_check_ints(rb_execution_context_t *ec) VM_ASSERT(ruby_assert_critical_section_entered == 0); #endif - VM_ASSERT(ec == GET_EC()); + VM_ASSERT(ec == rb_current_ec_noinline()); if (UNLIKELY(RUBY_VM_INTERRUPTED_ANY(ec))) { rb_threadptr_execute_interrupts(rb_ec_thread_ptr(ec), 0); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index d46f5f486f..d54617efe4 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -619,7 +619,7 @@ impl<T: Copy + Into<usize> + PartialEq> UnionFind<T> { } /// Find the set representative for `insn` without doing path compression. - pub fn find_const(&self, insn: T) -> T { + fn find_const(&self, insn: T) -> T { let mut result = insn; loop { match self.at(result) { @@ -645,7 +645,7 @@ pub struct Function { // TODO: get method name and source location from the ISEQ insns: Vec<Insn>, - union_find: UnionFind<InsnId>, + union_find: std::cell::RefCell<UnionFind<InsnId>>, insn_types: Vec<Type>, blocks: Vec<Block>, entry_block: BlockId, @@ -657,7 +657,7 @@ impl Function { iseq, insns: vec![], insn_types: vec![], - union_find: UnionFind::new(), + union_find: UnionFind::new().into(), blocks: vec![Block::default()], entry_block: BlockId(0), } @@ -740,7 +740,7 @@ impl Function { macro_rules! find { ( $x:expr ) => { { - self.union_find.find_const($x) + self.union_find.borrow_mut().find($x) } }; } @@ -749,12 +749,12 @@ impl Function { { BranchEdge { target: $edge.target, - args: $edge.args.iter().map(|x| self.union_find.find_const(*x)).collect(), + args: $edge.args.iter().map(|x| find!(*x)).collect(), } } }; } - let insn_id = self.union_find.find_const(insn_id); + let insn_id = self.union_find.borrow_mut().find(insn_id); use Insn::*; match &self.insns[insn_id.0] { result@(PutSelf | Const {..} | Param {..} | NewArray {..} | GetConstantPath {..} @@ -822,12 +822,12 @@ impl Function { /// Replace `insn` with the new instruction `replacement`, which will get appended to `insns`. fn make_equal_to(&mut self, insn: InsnId, replacement: InsnId) { // Don't push it to the block - self.union_find.make_equal_to(insn, replacement); + self.union_find.borrow_mut().make_equal_to(insn, replacement); } fn type_of(&self, insn: InsnId) -> Type { assert!(self.insns[insn.0].has_output()); - self.insn_types[self.union_find.find_const(insn).0] + self.insn_types[self.union_find.borrow_mut().find(insn).0] } /// Check if the type of `insn` is a subtype of `ty`. @@ -1600,6 +1600,10 @@ fn compute_jump_targets(iseq: *const rb_iseq_t) -> Vec<u32> { let offset = get_arg(pc, 0).as_i64(); jump_targets.insert(insn_idx_at_offset(insn_idx, offset)); } + YARVINSN_opt_new => { + let offset = get_arg(pc, 1).as_i64(); + jump_targets.insert(insn_idx_at_offset(insn_idx, offset)); + } YARVINSN_leave | YARVINSN_opt_invokebuiltin_delegate_leave => { if insn_idx < iseq_size { jump_targets.insert(insn_idx); @@ -1800,6 +1804,17 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> { }); queue.push_back((state.clone(), target, target_idx)); } + YARVINSN_opt_new => { + let offset = get_arg(pc, 1).as_i64(); + // TODO(max): Check interrupts + let target_idx = insn_idx_at_offset(insn_idx, offset); + let target = insn_idx_to_block[&target_idx]; + // Skip the fast-path and go straight to the fallback code. We will let the + // optimizer take care of the converting Class#new->alloc+initialize instead. + fun.push_insn(block, Insn::Jump(BranchEdge { target, args: state.as_args() })); + queue.push_back((state.clone(), target, target_idx)); + break; // Don't enqueue the next block as a successor + } YARVINSN_jump => { let offset = get_arg(pc, 0).as_i64(); // TODO(max): Check interrupts @@ -1963,19 +1978,19 @@ mod union_find_tests { } #[test] - fn test_find_const_returns_target() { + fn test_find_returns_target() { let mut uf = UnionFind::new(); uf.make_equal_to(3, 4); - assert_eq!(uf.find_const(3usize), 4); + assert_eq!(uf.find(3usize), 4); } #[test] - fn test_find_const_returns_transitive_target() { + fn test_find_returns_transitive_target() { let mut uf = UnionFind::new(); uf.make_equal_to(3, 4); uf.make_equal_to(4, 5); - assert_eq!(uf.find_const(3usize), 5); - assert_eq!(uf.find_const(4usize), 5); + assert_eq!(uf.find(3usize), 5); + assert_eq!(uf.find(4usize), 5); } #[test] @@ -2796,6 +2811,26 @@ mod tests { "); assert_compile_fails("test", ParseError::UnknownOpcode("sendforward".into())) } + + #[test] + fn test_opt_new() { + eval(" + class C; end + def test = C.new + "); + assert_method_hir("test", expect![[r#" + fn test: + bb0(): + v1:BasicObject = GetConstantPath 0x1000 + v2:NilClassExact = Const Value(nil) + Jump bb1(v2, v1) + bb1(v4:NilClassExact, v5:BasicObject): + v8:BasicObject = SendWithoutBlock v5, :new + Jump bb2(v8, v4) + bb2(v10:BasicObject, v11:NilClassExact): + Return v10 + "#]]); + } } #[cfg(test)] @@ -3688,4 +3723,55 @@ mod opt_tests { Return v5 "#]]); } + + #[test] + fn test_opt_new_no_initialize() { + eval(" + class C; end + def test = C.new + test + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, C) + v16:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v2:NilClassExact = Const Value(nil) + Jump bb1(v2, v16) + bb1(v4:NilClassExact, v5:BasicObject[VALUE(0x1008)]): + v8:BasicObject = SendWithoutBlock v5, :new + Jump bb2(v8, v4) + bb2(v10:BasicObject, v11:NilClassExact): + Return v10 + "#]]); + } + + #[test] + fn test_opt_new_initialize() { + eval(" + class C + def initialize x + @x = x + end + end + def test = C.new 1 + test + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, C) + v18:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v2:NilClassExact = Const Value(nil) + v3:Fixnum[1] = Const Value(1) + Jump bb1(v2, v18, v3) + bb1(v5:NilClassExact, v6:BasicObject[VALUE(0x1008)], v7:Fixnum[1]): + v10:BasicObject = SendWithoutBlock v6, :new, v7 + Jump bb2(v10, v5) + bb2(v12:BasicObject, v13:NilClassExact): + Return v12 + "#]]); + } } |