diff options
Diffstat (limited to 'test/ruby/test_yjit.rb')
| -rw-r--r-- | test/ruby/test_yjit.rb | 192 |
1 files changed, 179 insertions, 13 deletions
diff --git a/test/ruby/test_yjit.rb b/test/ruby/test_yjit.rb index 0e476588f4..0d7fe66e1c 100644 --- a/test/ruby/test_yjit.rb +++ b/test/ruby/test_yjit.rb @@ -133,7 +133,7 @@ class TestYJIT < Test::Unit::TestCase end def test_yjit_enable_with_monkey_patch - assert_separately(%w[--yjit-disable], <<~RUBY) + assert_ruby_status(%w[--yjit-disable], <<~RUBY) # This lets rb_method_entry_at(rb_mKernel, ...) return NULL Kernel.prepend(Module.new) @@ -142,6 +142,36 @@ class TestYJIT < Test::Unit::TestCase RUBY end + def test_yjit_enable_with_valid_runtime_call_threshold_option + assert_in_out_err(['--yjit-disable', '-e', + 'RubyVM::YJIT.enable(call_threshold: 1); puts RubyVM::YJIT.enabled?']) do |stdout, stderr, _status| + assert_empty stderr + assert_include stdout.join, "true" + end + end + + def test_yjit_enable_with_invalid_runtime_call_threshold_option + assert_in_out_err(['--yjit-disable', '-e', 'RubyVM::YJIT.enable(mem_size: 0)']) do |stdout, stderr, status| + assert_not_empty stderr + assert_match(/ArgumentError/, stderr.join) + assert_equal 1, status.exitstatus + end + end + + def test_yjit_enable_with_invalid_runtime_mem_size_option + assert_in_out_err(['--yjit-disable', '-e', 'RubyVM::YJIT.enable(mem_size: 0)']) do |stdout, stderr, status| + assert_not_empty stderr + assert_match(/ArgumentError/, stderr.join) + assert_equal 1, status.exitstatus + end + end + + if JITSupport.zjit_supported? + def test_yjit_enable_with_zjit_enabled + assert_in_out_err(['--zjit'], 'puts RubyVM::YJIT.enable', ['false'], ['Only one JIT can be enabled at the same time.']) + end + end + def test_yjit_stats_and_v_no_error _stdout, stderr, _status = invoke_ruby(%w(-v --yjit-stats), '', true, true) refute_includes(stderr, "NoMethodError") @@ -517,7 +547,7 @@ class TestYJIT < Test::Unit::TestCase end def test_opt_getconstant_path_slowpath - assert_compiles(<<~RUBY, exits: { opt_getconstant_path: 1 }, result: [42, 42, 1, 1], call_threshold: 2) + assert_compiles(<<~RUBY, result: [42, 42, 1, 1], call_threshold: 2) class A FOO = 42 class << self @@ -614,6 +644,40 @@ class TestYJIT < Test::Unit::TestCase RUBY end + STRUCT_MAX_EMBEDDED_MEMBERS = ( + GC::INTERNAL_CONSTANTS[:RVARGC_MAX_ALLOCATE_SIZE] - + GC::INTERNAL_CONSTANTS[:RBASIC_SIZE] - + GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD] + ) / RbConfig::SIZEOF["void*"] + + def test_spilled_struct_aref + omit("FIXME: https://github.com/Shopify/ruby/issues/977") + assert_compiles(<<~RUBY) + LargeStruct = Struct.new(:foo, :bar, *(#{STRUCT_MAX_EMBEDDED_MEMBERS} - 2).times.map { :"m_\#{it}" }) + + def foo(obj) + foo = obj.foo + raise "Expected 1, got: \#{foo}" unless foo == 1 + bar = obj.bar + raise "Expected 2, got: \#{bar}" unless bar == 2 + end + + embedded_struct = LargeStruct.new(1, 2) + # Bump RCLASS_MAX_IV_COUNT for LargeStruct + embedded_struct.instance_variable_set(:@test, 1) + + # Next allocation reserves space for the imemo/fields reference. + heap_struct = LargeStruct.new(1, 2) + + RubyVM::YJIT.reset_stats! + + foo(embedded_struct) + foo(embedded_struct) + foo(heap_struct) + foo(heap_struct) + RUBY + end + def test_struct_aset assert_compiles(<<~RUBY) def foo(obj) @@ -627,6 +691,26 @@ class TestYJIT < Test::Unit::TestCase RUBY end + def test_struct_aset_guards_recv_is_not_frozen + assert_compiles(<<~RUBY, result: :ok, exits: { opt_send_without_block: 1 }) + def foo(obj) + obj.foo = 123 + end + + Foo = Struct.new(:foo) + obj = Foo.new(123) + 100.times do + foo(obj) + end + obj.freeze + begin + foo(obj) + rescue FrozenError + :ok + end + RUBY + end + def test_getblockparam assert_compiles(<<~'RUBY', insns: [:getblockparam]) def foo &blk @@ -923,6 +1007,40 @@ class TestYJIT < Test::Unit::TestCase RUBY end + def test_super_bmethod + # Bmethod defined at class scope + assert_compiles(<<~'RUBY', insns: %i[invokesuper], result: true, exits: {}) + class SuperItself + define_method(:itself) { super() } + end + + obj = SuperItself.new + obj.itself + obj.itself == obj + RUBY + + # Bmethod defined inside a method (the block's local_iseq is ISEQ_TYPE_METHOD + # but the CME is at the bmethod frame, not the enclosing method's frame) + assert_compiles(<<~'RUBY', insns: %i[invokesuper], result: "Base#foo via bmethod", exits: {}) + class Base + def foo = "Base#foo" + end + + class SetupHelper + def add_bmethod_to(klass) + klass.define_method(:foo) { super() + " via bmethod" } + end + end + + class Target < Base; end + + SetupHelper.new.add_bmethod_to(Target) + obj = Target.new + obj.foo + obj.foo + RUBY + end + # Tests calling a variadic cfunc with many args def test_build_large_struct assert_compiles(<<~RUBY, insns: %i[opt_send_without_block], call_threshold: 2) @@ -1316,7 +1434,7 @@ class TestYJIT < Test::Unit::TestCase end def test_tracing_str_uplus - assert_compiles(<<~RUBY, frozen_string_literal: true, result: :ok, exits: { putspecialobject: 1, definemethod: 1 }) + assert_compiles(<<~RUBY, frozen_string_literal: true, result: :ok, exits: { putspecialobject: 1 }) def str_uplus _ = 1 _ = 2 @@ -1504,14 +1622,6 @@ class TestYJIT < Test::Unit::TestCase RUBY end - def test_opt_aref_with - assert_compiles(<<~RUBY, insns: %i[opt_aref_with], result: "bar", frozen_string_literal: false) - h = {"foo" => "bar"} - - h["foo"] - RUBY - end - def test_proc_block_arg assert_compiles(<<~RUBY, result: [:proc, :no_block]) def yield_if_given = block_given? ? yield : :no_block @@ -1637,7 +1747,7 @@ class TestYJIT < Test::Unit::TestCase [ stats[:object_shape_count].is_a?(Integer), - stats[:ratio_in_yjit].is_a?(Float), + stats[:ratio_in_yjit].nil? || stats[:ratio_in_yjit].is_a?(Float), ].all? RUBY end @@ -1648,7 +1758,7 @@ class TestYJIT < Test::Unit::TestCase 3.times { test } # Collect single stat. - stat = RubyVM::YJIT.runtime_stats(:ratio_in_yjit) + stat = RubyVM::YJIT.runtime_stats(:yjit_alloc_size) # Ensure this invocation had stats. return true unless RubyVM::YJIT.runtime_stats[:all_stats] @@ -1750,6 +1860,62 @@ class TestYJIT < Test::Unit::TestCase RUBY end + def test_proc_block_with_kwrest + # When the bug was present this required --yjit-stats to trigger. + assert_compiles(<<~RUBY, result: {extra: 5}) + def foo = bar(w: 1, x: 2, y: 3, z: 4, extra: 5, &proc { _1 }) + def bar(w:, x:, y:, z:, **kwrest) = yield kwrest + + GC.stress = true + foo + foo + RUBY + end + + def test_yjit_dump_insns + # Testing that this undocumented debugging feature doesn't crash + args = [ + '--yjit-call-threshold=1', + '--yjit-dump-insns', + '-e def foo(case:) = {case:}[:case]', + '-e foo(case:0)', + ] + _out, _err, status = invoke_ruby(args, '', true, true) + assert_not_predicate(status, :signaled?) + end + + def test_yjit_prelude_kernel_prepend + # Simulate what bundler/setup can do: prepend a module to Kernel during + # the prelude via the BUNDLER_SETUP mechanism in rubygems.rb: + # require ENV["BUNDLER_SETUP"] if ENV["BUNDLER_SETUP"] && !defined?(Bundler) + Tempfile.create(["kernel_prepend", ".rb"]) do |f| + f.write("Kernel.prepend(Module.new)\n") + f.flush + assert_separately([{ "BUNDLER_SETUP" => f.path }, "--enable=gems", "--yjit"], "", ignore_stderr: true) + end + end + + def test_exceptional_entry_into_env_escaped_before_yjit_enablement + threshold = 2 + assert_separately(["--disable-all", "--yjit-disable", "--yjit-call-threshold=#{threshold}"], <<~RUBY) + def run + @captured_env = ->{} + RubyVM::YJIT.enable + + i = 0 + while i < #{threshold} + next_i = i + 1 + from_break = tap { break i + 1 } # break from the block generates an exceptional entry + assert_equal(from_break, next_i, '[Bug #21941]') + i = next_i + end + end + + run + assert_equal(#{threshold}, @captured_env.binding.local_variable_get(:i)) + RUBY + end + private def code_gc_helpers |
