summaryrefslogtreecommitdiff
path: root/test/ruby/test_zjit.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/ruby/test_zjit.rb')
-rw-r--r--test/ruby/test_zjit.rb2011
1 files changed, 276 insertions, 1735 deletions
diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb
index 8f83e858a6..a56fea6d51 100644
--- a/test/ruby/test_zjit.rb
+++ b/test/ruby/test_zjit.rb
@@ -27,206 +27,125 @@ class TestZJIT < Test::Unit::TestCase
RUBY
end
- def test_enable_through_env
- child_env = {'RUBY_YJIT_ENABLE' => nil, 'RUBY_ZJIT_ENABLE' => '1'}
- assert_in_out_err([child_env, '-v'], '') do |stdout, stderr|
- assert_includes(stdout.first, '+ZJIT')
- assert_equal([], stderr)
- end
- end
-
- def test_call_itself
- assert_compiles '42', <<~RUBY, call_threshold: 2
- def test = 42.itself
- test
- test
+ def test_stats_string_no_zjit
+ assert_runs 'nil', <<~RUBY, zjit: false
+ RubyVM::ZJIT.stats_string
+ RUBY
+ assert_runs 'true', <<~RUBY, stats: false
+ RubyVM::ZJIT.stats_string.is_a?(String)
+ RUBY
+ assert_runs 'true', <<~RUBY, stats: true
+ RubyVM::ZJIT.stats_string.is_a?(String)
RUBY
end
- def test_nil
- assert_compiles 'nil', %q{
- def test = nil
- test
- }
- end
-
- def test_putobject
- assert_compiles '1', %q{
- def test = 1
- test
- }
- end
-
- def test_putstring
- assert_compiles '""', %q{
- def test = "#{""}"
- test
- }, insns: [:putstring]
- end
-
- def test_putchilldedstring
- assert_compiles '""', %q{
- def test = ""
+ def test_stats_quiet
+ # Test that --zjit-stats-quiet collects stats but doesn't print them
+ script = <<~RUBY
+ def test = 42
test
- }, insns: [:putchilledstring]
- end
-
- def test_leave_param
- assert_compiles '5', %q{
- def test(n) = n
- test(5)
- }
- end
-
- def test_setglobal
- assert_compiles '1', %q{
- def test
- $a = 1
- $a
- end
-
test
- }, insns: [:setglobal]
- end
+ puts RubyVM::ZJIT.stats_enabled?
+ RUBY
- def test_string_intern
- assert_compiles ':foo123', %q{
- def test
- :"foo#{123}"
- end
+ stats_header = "***ZJIT: Printing ZJIT statistics on exit***"
- test
- }, insns: [:intern]
- end
+ # With --zjit-stats, stats should be printed to stderr
+ out, err, status = eval_with_jit(script, stats: true)
+ assert_success(out, err, status)
+ assert_includes(err, stats_header)
+ assert_equal("true\n", out)
- def test_setglobal_with_trace_var_exception
- assert_compiles '"rescued"', %q{
- def test
- $a = 1
- rescue
- "rescued"
- end
-
- trace_var(:$a) { raise }
- test
- }, insns: [:setglobal]
- end
+ # With --zjit-stats-quiet, stats should NOT be printed but still enabled
+ out, err, status = eval_with_jit(script, stats: :quiet)
+ assert_success(out, err, status)
+ refute_includes(err, stats_header)
+ assert_equal("true\n", out)
- def test_setlocal
- assert_compiles '3', %q{
- def test(n)
- m = n
- m
- end
- test(3)
- }
- end
+ # With --zjit-stats=<path>, stats should be printed to the path
+ Tempfile.create("zjit-stats-") {|tmp|
+ stats_file = tmp.path
+ tmp.puts("Lorem ipsum dolor sit amet, consectetur adipiscing elit, ...")
+ tmp.close
- def test_setlocal_on_eval
- assert_compiles '1', %q{
- @b = binding
- eval('a = 1', @b)
- eval('a', @b)
+ out, err, status = eval_with_jit(script, stats: stats_file)
+ assert_success(out, err, status)
+ refute_includes(err, stats_header)
+ assert_equal("true\n", out)
+ assert_equal stats_header, File.open(stats_file) {|f| f.gets(chomp: true)}, "should be overwritten"
}
end
- def test_call_a_forwardable_method
- assert_runs '[]', %q{
- def test_root = forwardable
- def forwardable(...) = Array.[](...)
- test_root
- test_root
- }, call_threshold: 2
+ def test_enable_through_env
+ child_env = {'RUBY_YJIT_ENABLE' => nil, 'RUBY_ZJIT_ENABLE' => '1'}
+ assert_in_out_err([child_env, '-v'], '') do |stdout, stderr|
+ assert_includes(stdout.first, '+ZJIT')
+ assert_equal([], stderr)
+ end
end
- def test_setlocal_on_eval_with_spill
- assert_compiles '1', %q{
- @b = binding
- eval('a = 1; itself', @b)
- eval('a', @b)
- }
- end
+ def test_zjit_enable
+ # --disable-all is important in case the build/environment has YJIT enabled by
+ # default through e.g. -DYJIT_FORCE_ENABLE. Can't enable ZJIT when YJIT is on.
+ assert_separately(["--disable-all"], <<~'RUBY')
+ refute_predicate RubyVM::ZJIT, :enabled?
+ refute_predicate RubyVM::ZJIT, :stats_enabled?
+ refute_includes RUBY_DESCRIPTION, "+ZJIT"
- def test_nested_local_access
- assert_compiles '[1, 2, 3]', %q{
- 1.times do |l2|
- 1.times do |l1|
- define_method(:test) do
- l1 = 1
- l2 = 2
- l3 = 3
- [l1, l2, l3]
- end
- end
- end
+ RubyVM::ZJIT.enable
- test
- test
- test
- }, call_threshold: 3, insns: [:getlocal, :setlocal, :getlocal_WC_0, :setlocal_WC_1]
+ assert_predicate RubyVM::ZJIT, :enabled?
+ refute_predicate RubyVM::ZJIT, :stats_enabled?
+ assert_includes RUBY_DESCRIPTION, "+ZJIT"
+ RUBY
end
- def test_read_local_written_by_children_iseqs
- omit "This test fails right now because Send doesn't compile."
-
- assert_compiles '[1, 2]', %q{
- def test
- l1 = nil
- l2 = nil
- tap do |_|
- l1 = 1
- tap do |_|
- l2 = 2
- end
- end
+ def test_zjit_disable
+ assert_separately(["--zjit", "--zjit-disable"], <<~'RUBY')
+ refute_predicate RubyVM::ZJIT, :enabled?
+ refute_includes RUBY_DESCRIPTION, "+ZJIT"
- [l1, l2]
- end
+ RubyVM::ZJIT.enable
- test
- test
- }, call_threshold: 2
+ assert_predicate RubyVM::ZJIT, :enabled?
+ assert_includes RUBY_DESCRIPTION, "+ZJIT"
+ RUBY
end
- def test_send_without_block
- assert_compiles '[1, 2, 3]', %q{
- def foo = 1
- def bar(a) = a - 1
- def baz(a, b) = a - b
-
- def test1 = foo
- def test2 = bar(3)
- def test3 = baz(4, 1)
-
- [test1, test2, test3]
- }
+ def test_zjit_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", "--zjit"], "", ignore_stderr: true)
+ end
end
- def test_send_with_six_args
- assert_compiles '[1, 2, 3, 4, 5, 6]', %q{
- def foo(a1, a2, a3, a4, a5, a6)
- [a1, a2, a3, a4, a5, a6]
- end
+ def test_zjit_enable_respects_existing_options
+ assert_separately(['--zjit-disable', '--zjit-stats-quiet'], <<~RUBY)
+ refute_predicate RubyVM::ZJIT, :enabled?
+ assert_predicate RubyVM::ZJIT, :stats_enabled?
- def test
- foo(1, 2, 3, 4, 5, 6)
- end
+ RubyVM::ZJIT.enable
- test # profile send
- test
- }, call_threshold: 2
+ assert_predicate RubyVM::ZJIT, :enabled?
+ assert_predicate RubyVM::ZJIT, :stats_enabled?
+ RUBY
end
- def test_send_on_heap_object_in_spilled_arg
- # This leads to a register spill, so not using `assert_compiles`
- assert_runs 'Hash', %q{
- def entry(a1, a2, a3, a4, a5, a6, a7, a8, a9)
- a9.itself.class
- end
-
- entry(1, 2, 3, 4, 5, 6, 7, 8, {}) # profile
- entry(1, 2, 3, 4, 5, 6, 7, 8, {})
- }, call_threshold: 2
+ def test_toplevel_binding
+ # Not using assert_compiles, which doesn't use the toplevel frame for `test_script`.
+ out, err, status = eval_with_jit(%q{
+ a = 1
+ b = 2
+ TOPLEVEL_BINDING.local_variable_set(:b, 3)
+ c = 4
+ print [a, b, c]
+ })
+ assert_success(out, err, status)
+ assert_equal "[1, 3, 4]", out
end
def test_send_exit_with_uninitialized_locals
@@ -245,889 +164,27 @@ class TestZJIT < Test::Unit::TestCase
}, call_threshold: 2, allowed_iseqs: 'entry@-e:2'
end
- def test_invokebuiltin
- omit 'Test fails at the moment due to not handling optional parameters'
- assert_compiles '["."]', %q{
- def test = Dir.glob(".")
- test
- }
- end
-
- def test_invokebuiltin_delegate
- assert_compiles '[[], true]', %q{
- def test = [].clone(freeze: true)
- r = test
- [r, r.frozen?]
- }
- end
-
- def test_opt_plus_const
- assert_compiles '3', %q{
- def test = 1 + 2
- test # profile opt_plus
- test
- }, call_threshold: 2
- end
-
- def test_opt_plus_fixnum
- assert_compiles '3', %q{
- def test(a, b) = a + b
- test(0, 1) # profile opt_plus
- test(1, 2)
- }, call_threshold: 2
- end
-
- def test_opt_plus_chain
- assert_compiles '6', %q{
- def test(a, b, c) = a + b + c
- test(0, 1, 2) # profile opt_plus
- test(1, 2, 3)
- }, call_threshold: 2
- end
-
- def test_opt_plus_left_imm
- assert_compiles '3', %q{
- def test(a) = 1 + a
- test(1) # profile opt_plus
- test(2)
- }, call_threshold: 2
- end
-
- def test_opt_plus_type_guard_exit
- assert_compiles '[3, 3.0]', %q{
- def test(a) = 1 + a
- test(1) # profile opt_plus
- [test(2), test(2.0)]
- }, call_threshold: 2
- end
-
- def test_opt_plus_type_guard_exit_with_locals
- assert_compiles '[6, 6.0]', %q{
- def test(a)
- local = 3
- 1 + a + local
- end
- test(1) # profile opt_plus
- [test(2), test(2.0)]
- }, call_threshold: 2
- end
-
- def test_opt_plus_type_guard_nested_exit
- assert_compiles '[4, 4.0]', %q{
- def side_exit(n) = 1 + n
- def jit_frame(n) = 1 + side_exit(n)
- def entry(n) = jit_frame(n)
- entry(2) # profile send
- [entry(2), entry(2.0)]
- }, call_threshold: 2
- end
-
- def test_opt_plus_type_guard_nested_exit_with_locals
- assert_compiles '[9, 9.0]', %q{
- def side_exit(n)
- local = 2
- 1 + n + local
- end
- def jit_frame(n)
- local = 3
- 1 + side_exit(n) + local
- end
- def entry(n) = jit_frame(n)
- entry(2) # profile send
- [entry(2), entry(2.0)]
- }, call_threshold: 2
- end
-
- # Test argument ordering
- def test_opt_minus
- assert_compiles '2', %q{
- def test(a, b) = a - b
- test(2, 1) # profile opt_minus
- test(6, 4)
- }, call_threshold: 2
- end
-
- def test_opt_mult
- assert_compiles '6', %q{
- def test(a, b) = a * b
- test(1, 2) # profile opt_mult
- test(2, 3)
- }, call_threshold: 2
- end
-
- def test_opt_mult_overflow
- assert_compiles '[6, -6, 9671406556917033397649408, -9671406556917033397649408, 21267647932558653966460912964485513216]', %q{
- def test(a, b)
- a * b
- end
- test(1, 1) # profile opt_mult
-
- r1 = test(2, 3)
- r2 = test(2, -3)
- r3 = test(2 << 40, 2 << 41)
- r4 = test(2 << 40, -2 << 41)
- r5 = test(1 << 62, 1 << 62)
-
- [r1, r2, r3, r4, r5]
- }, call_threshold: 2
- end
-
- def test_opt_eq
- assert_compiles '[true, false]', %q{
- def test(a, b) = a == b
- test(0, 2) # profile opt_eq
- [test(1, 1), test(0, 1)]
- }, insns: [:opt_eq], call_threshold: 2
- end
-
- def test_opt_eq_with_minus_one
- assert_compiles '[false, true]', %q{
- def test(a) = a == -1
- test(1) # profile opt_eq
- [test(0), test(-1)]
- }, insns: [:opt_eq], call_threshold: 2
- end
-
- def test_opt_neq_dynamic
- # TODO(max): Don't split this test; instead, run all tests with and without
- # profiling.
- assert_compiles '[false, true]', %q{
- def test(a, b) = a != b
- test(0, 2) # profile opt_neq
- [test(1, 1), test(0, 1)]
- }, insns: [:opt_neq], call_threshold: 1
- end
-
- def test_opt_neq_fixnum
- assert_compiles '[false, true]', %q{
- def test(a, b) = a != b
- test(0, 2) # profile opt_neq
- [test(1, 1), test(0, 1)]
- }, call_threshold: 2
- end
-
- def test_opt_lt
- assert_compiles '[true, false, false]', %q{
- def test(a, b) = a < b
- test(2, 3) # profile opt_lt
- [test(0, 1), test(0, 0), test(1, 0)]
- }, insns: [:opt_lt], call_threshold: 2
- end
-
- def test_opt_lt_with_literal_lhs
- assert_compiles '[false, false, true]', %q{
- def test(n) = 2 < n
- test(2) # profile opt_lt
- [test(1), test(2), test(3)]
- }, insns: [:opt_lt], call_threshold: 2
- end
-
- def test_opt_le
- assert_compiles '[true, true, false]', %q{
- def test(a, b) = a <= b
- test(2, 3) # profile opt_le
- [test(0, 1), test(0, 0), test(1, 0)]
- }, insns: [:opt_le], call_threshold: 2
- end
-
- def test_opt_gt
- assert_compiles '[false, false, true]', %q{
- def test(a, b) = a > b
- test(2, 3) # profile opt_gt
- [test(0, 1), test(0, 0), test(1, 0)]
- }, insns: [:opt_gt], call_threshold: 2
- end
-
- def test_opt_empty_p
- assert_compiles('[false, false, true]', <<~RUBY, insns: [:opt_empty_p])
- def test(x) = x.empty?
- return test([1]), test("1"), test({})
- RUBY
- end
-
- def test_opt_succ
- assert_compiles('[0, "B"]', <<~RUBY, insns: [:opt_succ])
- def test(obj) = obj.succ
- return test(-1), test("A")
- RUBY
- end
-
- def test_opt_and
- assert_compiles('[1, [3, 2, 1]]', <<~RUBY, insns: [:opt_and])
- def test(x, y) = x & y
- return test(0b1101, 3), test([3, 2, 1, 4], [8, 1, 2, 3])
- RUBY
- end
-
- def test_opt_or
- assert_compiles('[11, [3, 2, 1]]', <<~RUBY, insns: [:opt_or])
- def test(x, y) = x | y
- return test(0b1000, 3), test([3, 2, 1], [1, 2, 3])
- RUBY
- end
-
- def test_fixnum_and
- assert_compiles '1', %q{
- def test(a, b) = a & b
- test(2, 2)
- test(2, 2)
- test(5, 3)
- }, call_threshold: 2, insns: [:opt_and]
- end
-
- def test_fixnum_and_side_exit
- assert_compiles 'false', %q{
- def test(a, b) = a & b
- test(2, 2)
- test(2, 2)
- test(true, false)
- }, call_threshold: 2, insns: [:opt_and]
- end
-
- def test_fixnum_or
- assert_compiles '3', %q{
- def test(a, b) = a | b
- test(5, 3)
- test(5, 3)
- test(1, 2)
- }, call_threshold: 2, insns: [:opt_or]
- end
-
- def test_fixnum_or_side_exit
- assert_compiles 'true', %q{
- def test(a, b) = a | b
- test(2, 2)
- test(2, 2)
- test(true, false)
- }, call_threshold: 2, insns: [:opt_or]
- end
-
- def test_fixnum_mul
- assert_compiles '12', %q{
- C = 3
- def test(n) = C * n
- test(4)
- test(4)
- test(4)
- }, call_threshold: 2, insns: [:opt_mult]
- end
-
- def test_opt_not
- assert_compiles('[true, true, false]', <<~RUBY, insns: [:opt_not])
- def test(obj) = !obj
- return test(nil), test(false), test(0)
- RUBY
- end
-
- def test_opt_regexpmatch2
- assert_compiles('[1, nil]', <<~RUBY, insns: [:opt_regexpmatch2])
- def test(haystack) = /needle/ =~ haystack
- return test("kneedle"), test("")
- RUBY
- end
-
- def test_opt_ge
- assert_compiles '[false, true, true]', %q{
- def test(a, b) = a >= b
- test(2, 3) # profile opt_ge
- [test(0, 1), test(0, 0), test(1, 0)]
- }, insns: [:opt_ge], call_threshold: 2
- end
-
- def test_new_hash_empty
- assert_compiles '{}', %q{
- def test = {}
- test
- }, insns: [:newhash]
- end
-
- def test_new_hash_nonempty
- assert_compiles '{"key" => "value", 42 => 100}', %q{
- def test
- key = "key"
- value = "value"
- num = 42
- result = 100
- {key => value, num => result}
- end
- test
- }, insns: [:newhash]
- end
-
- def test_new_hash_single_key_value
- assert_compiles '{"key" => "value"}', %q{
- def test = {"key" => "value"}
- test
- }, insns: [:newhash]
- end
-
- def test_new_hash_with_computation
- assert_compiles '{"sum" => 5, "product" => 6}', %q{
- def test(a, b)
- {"sum" => a + b, "product" => a * b}
- end
- test(2, 3)
- }, insns: [:newhash]
- end
-
- def test_new_hash_with_user_defined_hash_method
- assert_runs 'true', %q{
- class CustomKey
- attr_reader :val
-
- def initialize(val)
- @val = val
- end
-
- def hash
- @val.hash
- end
-
- def eql?(other)
- other.is_a?(CustomKey) && @val == other.val
- end
- end
-
- def test
- key = CustomKey.new("key")
- hash = {key => "value"}
- hash[key] == "value"
- end
- test
- }
+ def test_opt_new_with_custom_allocator
+ assert_compiles '"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"', %q{
+ require "digest"
+ def test = Digest::SHA256.new.hexdigest
+ test; test
+ }, insns: [:opt_new], call_threshold: 2
end
- def test_new_hash_with_user_hash_method_exception
- assert_runs 'RuntimeError', %q{
- class BadKey
- def hash
- raise "Hash method failed!"
- end
- end
-
+ def test_opt_new_with_custom_allocator_raises
+ assert_compiles '[42, 42]', %q{
+ require "digest"
+ class C < Digest::Base; end
def test
- key = BadKey.new
- {key => "value"}
- end
-
- begin
- test
- rescue => e
- e.class
- end
- }
- end
-
- def test_new_hash_with_user_eql_method_exception
- assert_runs 'RuntimeError', %q{
- class BadKey
- def hash
+ begin
+ Digest::Base.new
+ rescue NotImplementedError
42
end
-
- def eql?(other)
- raise "Eql method failed!"
- end
- end
-
- def test
- key1 = BadKey.new
- key2 = BadKey.new
- {key1 => "value1", key2 => "value2"}
- end
-
- begin
- test
- rescue => e
- e.class
- end
- }
- end
-
- def test_opt_hash_freeze
- assert_compiles '{}', <<~RUBY, insns: [:opt_hash_freeze]
- def test = {}.freeze
- test
- RUBY
- end
-
- def test_opt_ary_freeze
- assert_compiles '[]', <<~RUBY, insns: [:opt_ary_freeze]
- def test = [].freeze
- test
- RUBY
- end
-
- def test_opt_str_freeze
- assert_compiles '""', <<~RUBY, insns: [:opt_str_freeze]
- def test = "".freeze
- test
- RUBY
- end
-
- def test_opt_str_uminus
- assert_compiles '""', <<~RUBY, insns: [:opt_str_uminus]
- def test = -""
- test
- RUBY
- end
-
- def test_new_array_empty
- assert_compiles '[]', %q{
- def test = []
- test
- }, insns: [:newarray]
- end
-
- def test_new_array_nonempty
- assert_compiles '[5]', %q{
- def a = 5
- def test = [a]
- test
- }
- end
-
- def test_new_array_order
- assert_compiles '[3, 2, 1]', %q{
- def a = 3
- def b = 2
- def c = 1
- def test = [a, b, c]
- test
- }
- end
-
- def test_array_dup
- assert_compiles '[1, 2, 3]', %q{
- def test = [1,2,3]
- test
- }
- end
-
- def test_new_range_inclusive
- assert_compiles '1..5', %q{
- def test(a, b) = a..b
- test(1, 5)
- }
- end
-
- def test_new_range_exclusive
- assert_compiles '1...5', %q{
- def test(a, b) = a...b
- test(1, 5)
- }
- end
-
- def test_new_range_with_literal
- assert_compiles '3..10', %q{
- def test(n) = n..10
- test(3)
- }
- end
-
- def test_if
- assert_compiles '[0, nil]', %q{
- def test(n)
- if n < 5
- 0
- end
- end
- [test(3), test(7)]
- }
- end
-
- def test_if_else
- assert_compiles '[0, 1]', %q{
- def test(n)
- if n < 5
- 0
- else
- 1
- end
- end
- [test(3), test(7)]
- }
- end
-
- def test_if_else_params
- assert_compiles '[1, 20]', %q{
- def test(n, a, b)
- if n < 5
- a
- else
- b
- end
- end
- [test(3, 1, 2), test(7, 10, 20)]
- }
- end
-
- def test_if_else_nested
- assert_compiles '[3, 8, 9, 14]', %q{
- def test(a, b, c, d, e)
- if 2 < a
- if a < 4
- b
- else
- c
- end
- else
- if a < 0
- d
- else
- e
- end
- end
- end
- [
- test(-1, 1, 2, 3, 4),
- test( 0, 5, 6, 7, 8),
- test( 3, 9, 10, 11, 12),
- test( 5, 13, 14, 15, 16),
- ]
- }
- end
-
- def test_if_else_chained
- assert_compiles '[12, 11, 21]', %q{
- def test(a)
- (if 2 < a then 1 else 2 end) + (if a < 4 then 10 else 20 end)
- end
- [test(0), test(3), test(5)]
- }
- end
-
- def test_if_elsif_else
- assert_compiles '[0, 2, 1]', %q{
- def test(n)
- if n < 5
- 0
- elsif 8 < n
- 1
- else
- 2
- end
- end
- [test(3), test(7), test(9)]
- }
- end
-
- def test_ternary_operator
- assert_compiles '[1, 20]', %q{
- def test(n, a, b)
- n < 5 ? a : b
- end
- [test(3, 1, 2), test(7, 10, 20)]
- }
- end
-
- def test_ternary_operator_nested
- assert_compiles '[2, 21]', %q{
- def test(n, a, b)
- (n < 5 ? a : b) + 1
- end
- [test(3, 1, 2), test(7, 10, 20)]
- }
- end
-
- def test_while_loop
- assert_compiles '10', %q{
- def test(n)
- i = 0
- while i < n
- i = i + 1
- end
- i
- end
- test(10)
- }
- end
-
- def test_while_loop_chain
- assert_compiles '[135, 270]', %q{
- def test(n)
- i = 0
- while i < n
- i = i + 1
- end
- while i < n * 10
- i = i * 3
- end
- i
- end
- [test(5), test(10)]
- }
- end
-
- def test_while_loop_nested
- assert_compiles '[0, 4, 12]', %q{
- def test(n, m)
- i = 0
- while i < n
- j = 0
- while j < m
- j += 2
- end
- i += j
- end
- i
- end
- [test(0, 0), test(1, 3), test(10, 5)]
- }
- end
-
- def test_while_loop_if_else
- assert_compiles '[9, -1]', %q{
- def test(n)
- i = 0
- while i < n
- if n >= 10
- return -1
- else
- i = i + 1
- end
- end
- i
- end
- [test(9), test(10)]
- }
- end
-
- def test_if_while_loop
- assert_compiles '[9, 12]', %q{
- def test(n)
- i = 0
- if n < 10
- while i < n
- i += 1
- end
- else
- while i < n
- i += 3
- end
- end
- i
- end
- [test(9), test(10)]
- }
- end
-
- def test_live_reg_past_ccall
- assert_compiles '2', %q{
- def callee = 1
- def test = callee + callee
- test
- }
- end
-
- def test_method_call
- assert_compiles '12', %q{
- def callee(a, b)
- a - b
- end
-
- def test
- callee(4, 2) + 10
- end
-
- test # profile test
- test
- }, call_threshold: 2
- end
-
- def test_recursive_fact
- assert_compiles '[1, 6, 720]', %q{
- def fact(n)
- if n == 0
- return 1
- end
- return n * fact(n-1)
- end
- [fact(0), fact(3), fact(6)]
- }
- end
-
- def test_profiled_fact
- assert_compiles '[1, 6, 720]', %q{
- def fact(n)
- if n == 0
- return 1
- end
- return n * fact(n-1)
- end
- fact(1) # profile fact
- [fact(0), fact(3), fact(6)]
- }, call_threshold: 3, num_profiles: 2
- end
-
- def test_recursive_fib
- assert_compiles '[0, 2, 3]', %q{
- def fib(n)
- if n < 2
- return n
- end
- return fib(n-1) + fib(n-2)
- end
- [fib(0), fib(3), fib(4)]
- }
- end
-
- def test_profiled_fib
- assert_compiles '[0, 2, 3]', %q{
- def fib(n)
- if n < 2
- return n
- end
- return fib(n-1) + fib(n-2)
- end
- fib(3) # profile fib
- [fib(0), fib(3), fib(4)]
- }, call_threshold: 5, num_profiles: 3
- end
-
- def test_spilled_basic_block_args
- assert_compiles '55', %q{
- def test(n1, n2)
- n3 = 3
- n4 = 4
- n5 = 5
- n6 = 6
- n7 = 7
- n8 = 8
- n9 = 9
- n10 = 10
- if n1 < n2
- n1 + n2 + n3 + n4 + n5 + n6 + n7 + n8 + n9 + n10
- end
- end
- test(1, 2)
- }
- end
-
- def test_spilled_method_args
- assert_runs '55', %q{
- def foo(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10)
- n1 + n2 + n3 + n4 + n5 + n6 + n7 + n8 + n9 + n10
- end
-
- def test
- foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
- end
-
- test
- }
-
- # TODO(Shopify/ruby#716): Support spills and change to assert_compiles
- assert_runs '1', %q{
- def a(n1,n2,n3,n4,n5,n6,n7,n8,n9) = n1+n9
- a(2,0,0,0,0,0,0,0,-1)
- }
-
- # TODO(Shopify/ruby#716): Support spills and change to assert_compiles
- assert_runs '0', %q{
- def a(n1,n2,n3,n4,n5,n6,n7,n8) = n8
- a(1,1,1,1,1,1,1,0)
- }
-
- # TODO(Shopify/ruby#716): Support spills and change to assert_compiles
- # self param with spilled param
- assert_runs '"main"', %q{
- def a(n1,n2,n3,n4,n5,n6,n7,n8) = self
- a(1,0,0,0,0,0,0,0).to_s
- }
- end
-
- def test_spilled_param_new_arary
- # TODO(Shopify/ruby#716): Support spills and change to assert_compiles
- assert_runs '[:ok]', %q{
- def a(n1,n2,n3,n4,n5,n6,n7,n8) = [n8]
- a(0,0,0,0,0,0,0, :ok)
- }
- end
-
- def test_forty_param_method
- # This used to a trigger a miscomp on A64 due
- # to a memory displacement larger than 9 bits.
- assert_compiles '1', %Q{
- def foo(#{'_,' * 39} n40) = n40
-
- foo(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1)
- }
- end
-
-
- def test_opt_aref_with
- assert_compiles ':ok', %q{
- def aref_with(hash) = hash["key"]
-
- aref_with({ "key" => :ok })
- }
- end
-
- def test_putself
- assert_compiles '3', %q{
- class Integer
- def minus(a)
- self - a
- end
- end
- 5.minus(2)
- }
- end
-
- def test_getinstancevariable
- assert_compiles 'nil', %q{
- def test() = @foo
-
- test()
- }
- assert_compiles '3', %q{
- @foo = 3
- def test() = @foo
-
- test()
- }
- end
-
- def test_setinstancevariable
- assert_compiles '1', %q{
- def test() = @foo = 1
-
- test()
- @foo
- }
- end
-
- def test_attr_reader
- assert_compiles '[4, 4]', %q{
- class C
- attr_reader :foo
-
- def initialize
- @foo = 4
- end
- end
-
- def test(c) = c.foo
- c = C.new
- [test(c), test(c)]
- }, call_threshold: 2, insns: [:opt_send_without_block]
- end
-
- def test_attr_accessor
- assert_compiles '[4, 4]', %q{
- class C
- attr_accessor :foo
-
- def initialize
- @foo = 4
- end
end
-
- def test(c) = c.foo
- c = C.new
- [test(c), test(c)]
- }, call_threshold: 2, insns: [:opt_send_without_block]
+ [test, test]
+ }, insns: [:opt_new], call_threshold: 2
end
def test_uncached_getconstant_path
@@ -1151,78 +208,6 @@ class TestZJIT < Test::Unit::TestCase
end
end
- def test_constant_invalidation
- assert_compiles '123', <<~RUBY, call_threshold: 2, insns: [:opt_getconstant_path]
- class C; end
- def test = C
- test
- test
-
- C = 123
- test
- RUBY
- end
-
- def test_constant_path_invalidation
- assert_compiles '["Foo::C", "Foo::C", "Bar::C"]', <<~RUBY, call_threshold: 2, insns: [:opt_getconstant_path]
- module A
- module B; end
- end
-
- module Foo
- C = "Foo::C"
- end
-
- module Bar
- C = "Bar::C"
- end
-
- A::B = Foo
-
- def test = A::B::C
-
- result = []
-
- result << test
- result << test
-
- A::B = Bar
-
- result << test
- result
- RUBY
- end
-
- def test_single_ractor_mode_invalidation
- # Without invalidating the single-ractor mode, the test would crash
- assert_compiles '"errored but not crashed"', <<~RUBY, call_threshold: 2, insns: [:opt_getconstant_path]
- C = Object.new
-
- def test
- C
- rescue Ractor::IsolationError
- "errored but not crashed"
- end
-
- test
- test
-
- Ractor.new {
- test
- }.value
- RUBY
- end
-
- def test_dupn
- assert_compiles '[[1], [1, 1], :rhs, [nil, :rhs]]', <<~RUBY, insns: [:dupn]
- def test(array) = (array[1, 2] ||= :rhs)
-
- one = [1, 1]
- start_empty = []
- [test(one), one, test(start_empty), start_empty]
- RUBY
- end
-
def test_send_backtrace
backtrace = [
"-e:2:in 'Object#jit_frame1'",
@@ -1239,235 +224,6 @@ class TestZJIT < Test::Unit::TestCase
}, call_threshold: 2
end
- def test_bop_invalidation
- omit 'Invalidation on BOP redefinition is not implemented yet'
- assert_compiles '', %q{
- def test
- eval(<<~RUBY)
- class Integer
- def +(_) = 100
- end
- RUBY
- 1 + 2
- end
- test
- }
- end
-
- def test_defined_with_defined_values
- assert_compiles '["constant", "method", "global-variable"]', %q{
- class Foo; end
- def bar; end
- $ruby = 1
-
- def test = return defined?(Foo), defined?(bar), defined?($ruby)
-
- test
- }, insns: [:defined]
- end
-
- def test_defined_with_undefined_values
- assert_compiles '[nil, nil, nil]', %q{
- def test = return defined?(Foo), defined?(bar), defined?($ruby)
-
- test
- }, insns: [:defined]
- end
-
- def test_defined_with_method_call
- assert_compiles '["method", nil]', %q{
- def test = return defined?("x".reverse(1)), defined?("x".reverse(1).reverse)
-
- test
- }, insns: [:defined]
- end
-
- def test_defined_yield
- assert_compiles "nil", "defined?(yield)"
- assert_compiles '[nil, nil, "yield"]', %q{
- def test = defined?(yield)
- [test, test, test{}]
- }, call_threshold: 2, insns: [:defined]
- end
-
- def test_defined_yield_from_block
- # This will do some EP hopping to find the local EP,
- # so it's slightly different than doing it outside of a block.
-
- omit 'Test fails at the moment due to missing Send codegen'
-
- assert_compiles '[nil, nil, "yield"]', %q{
- def test
- yield_self { yield_self { defined?(yield) } }
- end
-
- [test, test, test{}]
- }, call_threshold: 2, insns: [:defined]
- end
-
- def test_invokeblock_without_block_after_jit_call
- assert_compiles '"no block given (yield)"', %q{
- def test(*arr, &b)
- arr.class
- yield
- end
- begin
- test
- rescue => e
- e.message
- end
- }
- end
-
- def test_putspecialobject_vm_core_and_cbase
- assert_compiles '10', %q{
- def test
- alias bar test
- 10
- end
-
- test
- bar
- }, insns: [:putspecialobject]
- end
-
- def test_putspecialobject_const_base
- assert_compiles '1', %q{
- Foo = 1
-
- def test = Foo
-
- # First call: populates the constant cache
- test
- # Second call: triggers ZJIT compilation with warm cache
- # RubyVM::ZJIT.assert_compiles will panic if this fails to compile
- test
- }, call_threshold: 2
- end
-
- def test_branchnil
- assert_compiles '[2, nil]', %q{
- def test(x)
- x&.succ
- end
- [test(1), test(nil)]
- }, call_threshold: 1, insns: [:branchnil]
- end
-
- def test_nil_nil
- assert_compiles 'true', %q{
- def test = nil.nil?
- test
- }, insns: [:opt_nil_p]
- end
-
- def test_non_nil_nil
- assert_compiles 'false', %q{
- def test = 1.nil?
- test
- }, insns: [:opt_nil_p]
- end
-
- def test_getspecial_last_match
- assert_compiles '"hello"', %q{
- def test(str)
- str =~ /hello/
- $&
- end
- test("hello world")
- }, insns: [:getspecial]
- end
-
- def test_getspecial_match_pre
- assert_compiles '"hello "', %q{
- def test(str)
- str =~ /world/
- $`
- end
- test("hello world")
- }, insns: [:getspecial]
- end
-
- def test_getspecial_match_post
- assert_compiles '" world"', %q{
- def test(str)
- str =~ /hello/
- $'
- end
- test("hello world")
- }, insns: [:getspecial]
- end
-
- def test_getspecial_match_last_group
- assert_compiles '"world"', %q{
- def test(str)
- str =~ /(hello) (world)/
- $+
- end
- test("hello world")
- }, insns: [:getspecial]
- end
-
- def test_getspecial_numbered_match_1
- assert_compiles '"hello"', %q{
- def test(str)
- str =~ /(hello) (world)/
- $1
- end
- test("hello world")
- }, insns: [:getspecial]
- end
-
- def test_getspecial_numbered_match_2
- assert_compiles '"world"', %q{
- def test(str)
- str =~ /(hello) (world)/
- $2
- end
- test("hello world")
- }, insns: [:getspecial]
- end
-
- def test_getspecial_numbered_match_nonexistent
- assert_compiles 'nil', %q{
- def test(str)
- str =~ /(hello)/
- $2
- end
- test("hello world")
- }, insns: [:getspecial]
- end
-
- def test_getspecial_no_match
- assert_compiles 'nil', %q{
- def test(str)
- str =~ /xyz/
- $&
- end
- test("hello world")
- }, insns: [:getspecial]
- end
-
- def test_getspecial_complex_pattern
- assert_compiles '"123"', %q{
- def test(str)
- str =~ /(\d+)/
- $1
- end
- test("abc123def")
- }, insns: [:getspecial]
- end
-
- def test_getspecial_multiple_groups
- assert_compiles '"456"', %q{
- def test(str)
- str =~ /(\d+)-(\d+)/
- $2
- end
- test("123-456")
- }, insns: [:getspecial]
- end
-
# tool/ruby_vm/views/*.erb relies on the zjit instructions a) being contiguous and
# b) being reliably ordered after all the other instructions.
def test_instruction_order
@@ -1488,429 +244,190 @@ class TestZJIT < Test::Unit::TestCase
end
def test_require_rubygems_with_auto_compact
+ omit("GC.auto_compact= support is required for this test") unless GC.respond_to?(:auto_compact=)
assert_runs 'true', %q{
GC.auto_compact = true
require 'rubygems'
}, call_threshold: 2
end
- def test_stats
+ def test_stats_availability
assert_runs '[true, true]', %q{
def test = 1
test
[
- RubyVM::ZJIT.stats[:zjit_insns_count] > 0,
- RubyVM::ZJIT.stats(:zjit_insns_count) > 0,
+ RubyVM::ZJIT.stats[:zjit_insn_count] > 0,
+ RubyVM::ZJIT.stats(:zjit_insn_count) > 0,
]
}, stats: true
end
- def test_zjit_option_uses_array_each_in_ruby
- omit 'ZJIT wrongly compiles Array#each, so it is disabled for now'
- assert_runs '"<internal:array>"', %q{
- Array.instance_method(:each).source_location&.first
- }
- end
-
- def test_profile_under_nested_jit_call
- assert_compiles '[nil, nil, 3]', %q{
- def profile
- 1 + 2
- end
+ def test_stats_consistency
+ assert_runs '[]', %q{
+ def test = 1
+ test # increment some counters
- def jit_call(flag)
- if flag
- profile
+ RubyVM::ZJIT.stats.to_a.filter_map do |key, value|
+ # The value may be incremented, but the class should stay the same
+ other_value = RubyVM::ZJIT.stats(key)
+ if value.class != other_value.class
+ [key, value, other_value]
end
end
-
- def entry(flag)
- jit_call(flag)
- end
-
- [entry(false), entry(false), entry(true)]
- }, call_threshold: 2
- end
-
- def test_bop_redefinition
- assert_runs '[3, :+, 100]', %q{
- def test
- 1 + 2
- end
-
- test # profile opt_plus
- [test, Integer.class_eval { def +(_) = 100 }, test]
- }, call_threshold: 2
- end
-
- def test_bop_redefinition_with_adjacent_patch_points
- assert_runs '[15, :+, 100]', %q{
- def test
- 1 + 2 + 3 + 4 + 5
- end
-
- test # profile opt_plus
- [test, Integer.class_eval { def +(_) = 100 }, test]
- }, call_threshold: 2
+ }, stats: true
end
- # ZJIT currently only generates a MethodRedefined patch point when the method
- # is called on the top-level self.
- def test_method_redefinition_with_top_self
- assert_runs '["original", "redefined"]', %q{
- def foo
- "original"
- end
-
- def test = foo
-
- test; test
-
- result1 = test
-
- # Redefine the method
- def foo
- "redefined"
- end
+ def test_reset_stats
+ assert_runs 'true', %q{
+ def test = 1
+ 100.times { test }
- result2 = test
+ # Get initial stats and verify they're non-zero
+ initial_stats = RubyVM::ZJIT.stats
- [result1, result2]
- }, call_threshold: 2
- end
+ # Reset the stats
+ RubyVM::ZJIT.reset_stats!
- def test_module_name_with_guard_passes
- assert_compiles '"Integer"', %q{
- def test(mod)
- mod.name
- end
+ # Get stats after reset
+ reset_stats = RubyVM::ZJIT.stats
- test(String)
- test(Integer)
- }, call_threshold: 2
+ [
+ # After reset, counters should be zero or at least much smaller
+ # (some instructions might execute between reset and reading stats)
+ :zjit_insn_count.then { |s| initial_stats[s] > 0 && reset_stats[s] < initial_stats[s] },
+ :compiled_iseq_count.then { |s| initial_stats[s] > 0 && reset_stats[s] < initial_stats[s] }
+ ].all?
+ }, stats: true
end
- def test_module_name_with_guard_side_exit
- # This test demonstrates that the guard side exit works correctly
- # In this case, when we call with a non-Class object, it should fall back to interpreter
- assert_compiles '["String", "Integer", "Bar"]', %q{
- class MyClass
- def name = "Bar"
- end
-
- def test(mod)
- mod.name
- end
-
- results = []
- results << test(String)
- results << test(Integer)
- results << test(MyClass.new)
-
- results
- }, call_threshold: 2
+ def test_zjit_option_uses_array_each_in_ruby
+ omit 'ZJIT wrongly compiles Array#each, so it is disabled for now'
+ assert_runs '"<internal:array>"', %q{
+ Array.instance_method(:each).source_location&.first
+ }
end
- def test_objtostring_calls_to_s_on_non_strings
- assert_compiles '["foo", "foo"]', %q{
- results = []
-
- class Foo
- def to_s
- "foo"
- end
+ def test_line_tracepoint_on_c_method
+ assert_compiles '"[[:line, true]]"', %q{
+ events = []
+ events.instance_variable_set(
+ :@tp,
+ TracePoint.new(:line) { |tp| events << [tp.event, tp.lineno] if tp.path == __FILE__ }
+ )
+ def events.to_str
+ @tp.enable; ''
end
- def test(str)
- "#{str}"
+ # Stay in generated code while enabling tracing
+ def events.compiled(obj)
+ String(obj)
+ @tp.disable; __LINE__
end
- results << test(Foo.new)
- results << test(Foo.new)
+ line = events.compiled(events)
+ events[0][-1] = (events[0][-1] == line)
- results
+ events.to_s # can't dump events as it's a singleton object AND it has a TracePoint instance variable, which also can't be dumped
}
end
- def test_objtostring_rewrite_does_not_call_to_s_on_strings
- assert_compiles '["foo", "foo"]', %q{
- results = []
-
- class String
- def to_s
- "bad"
- end
+ def test_targeted_line_tracepoint_in_c_method_call
+ assert_compiles '"[true]"', %q{
+ events = []
+ events.instance_variable_set(:@tp, TracePoint.new(:line) { |tp| events << tp.lineno })
+ def events.to_str
+ @tp.enable(target: method(:compiled))
+ ''
end
- def test(foo)
- "#{foo}"
+ # Stay in generated code while enabling tracing
+ def events.compiled(obj)
+ String(obj)
+ __LINE__
end
- results << test("foo")
- results << test("foo")
+ line = events.compiled(events)
+ events[0] = (events[0] == line)
- results
+ events.to_s # can't dump events as it's a singleton object AND it has a TracePoint instance variable, which also can't be dumped
}
end
- def test_objtostring_rewrite_does_not_call_to_s_on_string_subclasses
- assert_compiles '["foo", "foo"]', %q{
- results = []
-
- class StringSubclass < String
- def to_s
- "bad"
- end
+ def test_regression_cfp_sp_set_correctly_before_leaf_gc_call
+ assert_compiles ':ok', %q{
+ def check(l, r)
+ return 1 unless l
+ 1 + check(*l) + check(*r)
end
- foo = StringSubclass.new("foo")
+ def tree(depth)
+ # This duparray is our leaf-gc target.
+ return [nil, nil] unless depth > 0
- def test(str)
- "#{str}"
+ # Modify the local and pass it to the following calls.
+ depth -= 1
+ [tree(depth), tree(depth)]
end
- results << test(foo)
- results << test(foo)
-
- results
- }
- end
-
- def test_string_bytesize_with_guard
- assert_compiles '5', %q{
- def test(str)
- str.bytesize
+ def test
+ GC.stress = true
+ 2.times do
+ t = tree(11)
+ check(*t)
+ end
+ :ok
end
- test('hello')
- test('world')
- }, call_threshold: 2
- end
-
- def test_nil_value_nil_opt_with_guard
- assert_compiles 'true', %q{
- def test(val) = val.nil?
-
- test(nil)
- test(nil)
- }, call_threshold: 2, insns: [:opt_nil_p]
- end
-
- def test_nil_value_nil_opt_with_guard_side_exit
- assert_compiles 'false', %q{
- def test(val) = val.nil?
-
- test(nil)
- test(nil)
- test(1)
- }, call_threshold: 2, insns: [:opt_nil_p]
- end
-
- def test_true_nil_opt_with_guard
- assert_compiles 'false', %q{
- def test(val) = val.nil?
-
- test(true)
- test(true)
- }, call_threshold: 2, insns: [:opt_nil_p]
- end
-
- def test_true_nil_opt_with_guard_side_exit
- assert_compiles 'true', %q{
- def test(val) = val.nil?
-
- test(true)
- test(true)
- test(nil)
- }, call_threshold: 2, insns: [:opt_nil_p]
- end
-
- def test_false_nil_opt_with_guard
- assert_compiles 'false', %q{
- def test(val) = val.nil?
-
- test(false)
- test(false)
- }, call_threshold: 2, insns: [:opt_nil_p]
- end
-
- def test_false_nil_opt_with_guard_side_exit
- assert_compiles 'true', %q{
- def test(val) = val.nil?
-
- test(false)
- test(false)
- test(nil)
- }, call_threshold: 2, insns: [:opt_nil_p]
- end
-
- def test_integer_nil_opt_with_guard
- assert_compiles 'false', %q{
- def test(val) = val.nil?
-
- test(1)
- test(2)
- }, call_threshold: 2, insns: [:opt_nil_p]
- end
-
- def test_integer_nil_opt_with_guard_side_exit
- assert_compiles 'true', %q{
- def test(val) = val.nil?
-
- test(1)
- test(2)
- test(nil)
- }, call_threshold: 2, insns: [:opt_nil_p]
- end
-
- def test_float_nil_opt_with_guard
- assert_compiles 'false', %q{
- def test(val) = val.nil?
-
- test(1.0)
- test(2.0)
- }, call_threshold: 2, insns: [:opt_nil_p]
- end
-
- def test_float_nil_opt_with_guard_side_exit
- assert_compiles 'true', %q{
- def test(val) = val.nil?
-
- test(1.0)
- test(2.0)
- test(nil)
- }, call_threshold: 2, insns: [:opt_nil_p]
- end
-
- def test_symbol_nil_opt_with_guard
- assert_compiles 'false', %q{
- def test(val) = val.nil?
-
- test(:foo)
- test(:bar)
- }, call_threshold: 2, insns: [:opt_nil_p]
- end
-
- def test_symbol_nil_opt_with_guard_side_exit
- assert_compiles 'true', %q{
- def test(val) = val.nil?
-
- test(:foo)
- test(:bar)
- test(nil)
- }, call_threshold: 2, insns: [:opt_nil_p]
- end
-
- def test_class_nil_opt_with_guard
- assert_compiles 'false', %q{
- def test(val) = val.nil?
-
- test(String)
- test(Integer)
- }, call_threshold: 2, insns: [:opt_nil_p]
- end
-
- def test_class_nil_opt_with_guard_side_exit
- assert_compiles 'true', %q{
- def test(val) = val.nil?
-
- test(String)
- test(Integer)
- test(nil)
- }, call_threshold: 2, insns: [:opt_nil_p]
- end
-
- def test_module_nil_opt_with_guard
- assert_compiles 'false', %q{
- def test(val) = val.nil?
-
- test(Enumerable)
- test(Kernel)
- }, call_threshold: 2, insns: [:opt_nil_p]
- end
-
- def test_module_nil_opt_with_guard_side_exit
- assert_compiles 'true', %q{
- def test(val) = val.nil?
-
- test(Enumerable)
- test(Kernel)
- test(nil)
- }, call_threshold: 2, insns: [:opt_nil_p]
- end
-
- def test_basic_object_guard_works_with_immediate
- assert_compiles 'NilClass', %q{
- class Foo; end
-
- def test(val) = val.class
-
- test(Foo.new)
- test(Foo.new)
- test(nil)
- }, call_threshold: 2
- end
-
- def test_basic_object_guard_works_with_false
- assert_compiles 'FalseClass', %q{
- class Foo; end
-
- def test(val) = val.class
-
- test(Foo.new)
- test(Foo.new)
- test(false)
- }, call_threshold: 2
- end
-
- def test_string_concat
- assert_compiles '"123"', %q{
- def test = "#{1}#{2}#{3}"
-
test
- }, insns: [:concatstrings]
+ }, call_threshold: 14, num_profiles: 5
end
- def test_string_concat_empty
- assert_compiles '""', %q{
- def test = "#{}"
+ def test_exit_tracing
+ # Smoke test: --zjit-trace-exits writes a Fuchsia trace (.fxt) file to /tmp
+ assert_compiles('true', <<~RUBY, extra_args: ['--zjit-trace-exits'])
+ def test(object) = object.itself
- test
- }, insns: [:concatstrings]
- end
-
- def test_regexp_interpolation
- assert_compiles '/123/', %q{
- def test = /#{1}#{2}#{3}/
+ # induce an exit just for good measure
+ array = []
+ test(array)
+ test(array)
+ def array.itself = :not_itself
+ test(array)
- test
- }, insns: [:toregexp]
+ fxt_files = Dir.glob("/tmp/perfetto-\#{Process.pid}.fxt")
+ result = fxt_files.length == 1 && !File.empty?(fxt_files.first)
+ File.unlink(*fxt_files)
+ result
+ RUBY
end
- def test_new_range_non_leaf
- assert_compiles '(0/1)..1', %q{
- def jit_entry(v) = make_range_then_exit(v)
-
- def make_range_then_exit(v)
- range = (v..1)
- super rescue range # TODO(alan): replace super with side-exit intrinsic
- end
-
- jit_entry(0) # profile
- jit_entry(0) # compile
- jit_entry(0/1r) # run without stub
- }, call_threshold: 2
+ def test_send_no_profiles_with_disabled_specialized_instruction
+ # Regression test: when specialized_instruction is disabled (as power_assert does),
+ # eval'd code uses `send` instead of `opt_send_without_block`, producing SendNoProfiles.
+ # The `times` call with a literal block is the SendNoProfiles send whose exit profiling
+ # triggers recompilation of `run`. After recompilation, `make`'s eval("proc { }") crashes
+ # in vm_make_env_each because the caller frame's EP[-1] (specval) has a stale value.
+ assert_runs ':ok', <<~RUBY
+ RubyVM::InstructionSequence.compile_option = { specialized_instruction: false }
+ eval <<~'INNERRUBY'
+ def make = eval("proc { }")
+ def run(n) = n.times { make }
+ INNERRUBY
+ run(6)
+ :ok
+ RUBY
end
- def test_raise_in_second_argument
- assert_compiles '{ok: true}', %q{
- def write(hash, key)
- hash[key] = raise rescue true
- hash
- end
-
- write({}, :ok)
- }
+ def test_float_arithmetic
+ assert_compiles '4.0', 'def test = 1.5 + 2.5; test'
+ assert_compiles '6.0', 'def test = 2.0 * 3.0; test'
+ assert_compiles '1.5', 'def test = 3.5 - 2.0; test'
+ assert_compiles '2.5', 'def test = 5.0 / 2.0; test'
+ assert_compiles '4.5', 'def test = 1.5 * 3; test' # Float * Fixnum
+ assert_compiles 'true', 'def test = (Float::NAN + 1.0).nan?; test'
+ assert_compiles 'Infinity', 'def test = Float::INFINITY * 2.0; test'
+ assert_compiles '3', 'def test = 3.7.to_i; test'
+ assert_compiles '-2', 'def test = (-2.9).to_i; test'
end
private
@@ -1926,31 +443,33 @@ class TestZJIT < Test::Unit::TestCase
# allows ZJIT to skip compiling methods.
def assert_runs(expected, test_script, insns: [], assert_compiles: false, **opts)
pipe_fd = 3
+ disasm_method = :test
script = <<~RUBY
ret_val = (_test_proc = -> { #{('RubyVM::ZJIT.assert_compiles; ' if assert_compiles)}#{test_script.lstrip} }).call
result = {
ret_val:,
#{ unless insns.empty?
- 'insns: RubyVM::InstructionSequence.of(method(:test)).to_a'
+ "insns: RubyVM::InstructionSequence.of(method(#{disasm_method.inspect})).to_a"
end}
}
IO.open(#{pipe_fd}).write(Marshal.dump(result))
RUBY
- status, out, err, result = eval_with_jit(script, pipe_fd:, **opts)
-
- message = "exited with status #{status.to_i}"
- message << "\nstdout:\n```\n#{out}```\n" unless out.empty?
- message << "\nstderr:\n```\n#{err}```\n" unless err.empty?
- assert status.success?, message
+ out, err, status, result = eval_with_jit(script, pipe_fd:, **opts)
+ assert_success(out, err, status)
result = Marshal.load(result)
assert_equal(expected, result.fetch(:ret_val).inspect)
unless insns.empty?
iseq = result.fetch(:insns)
- assert_equal("YARVInstructionSequence/SimpleDataFormat", iseq.first, "failed to get iseq disassembly")
+ assert_equal(
+ "YARVInstructionSequence/SimpleDataFormat",
+ iseq.first,
+ "Failed to get ISEQ disassembly. " \
+ "Make sure to put code directly under the '#{disasm_method}' method."
+ )
iseq_insns = iseq.last
expected_insns = Set.new(insns)
@@ -1971,14 +490,22 @@ class TestZJIT < Test::Unit::TestCase
stats: false,
debug: true,
allowed_iseqs: nil,
+ extra_args: nil,
timeout: 1000,
- pipe_fd:
+ pipe_fd: nil
)
- args = ["--disable-gems"]
+ args = ["--disable-gems", *extra_args]
if zjit
args << "--zjit-call-threshold=#{call_threshold}"
args << "--zjit-num-profiles=#{num_profiles}"
- args << "--zjit-stats" if stats
+ case stats
+ when true
+ args << "--zjit-stats"
+ when :quiet
+ args << "--zjit-stats-quiet"
+ else
+ args << "--zjit-stats=#{stats}" if stats
+ end
args << "--zjit-debug" if debug
if allowed_iseqs
jitlist = Tempfile.new("jitlist")
@@ -1988,18 +515,25 @@ class TestZJIT < Test::Unit::TestCase
end
end
args << "-e" << script_shell_encode(script)
- pipe_r, pipe_w = IO.pipe
- # Separate thread so we don't deadlock when
- # the child ruby blocks writing the output to pipe_fd
- pipe_out = nil
- pipe_reader = Thread.new do
- pipe_out = pipe_r.read
- pipe_r.close
+ ios = {}
+ if pipe_fd
+ pipe_r, pipe_w = IO.pipe
+ # Separate thread so we don't deadlock when
+ # the child ruby blocks writing the output to pipe_fd
+ pipe_out = nil
+ pipe_reader = Thread.new do
+ pipe_out = pipe_r.read
+ pipe_r.close
+ end
+ ios[pipe_fd] = pipe_w
end
- out, err, status = EnvUtil.invoke_ruby(args, '', true, true, rubybin: RbConfig.ruby, timeout: timeout, ios: { pipe_fd => pipe_w })
- pipe_w.close
- pipe_reader.join(timeout)
- [status, out, err, pipe_out]
+ result = EnvUtil.invoke_ruby(args, '', true, true, rubybin: RbConfig.ruby, timeout: timeout, ios:)
+ if pipe_fd
+ pipe_w.close
+ pipe_reader.join(timeout)
+ result << pipe_out
+ end
+ result
ensure
pipe_reader&.kill
pipe_reader&.join(timeout)
@@ -2008,6 +542,13 @@ class TestZJIT < Test::Unit::TestCase
jitlist&.unlink
end
+ def assert_success(out, err, status)
+ message = "exited with status #{status.to_i}"
+ message << "\nstdout:\n```\n#{out}```\n" unless out.empty?
+ message << "\nstderr:\n```\n#{err}```\n" unless err.empty?
+ assert status.success?, message
+ end
+
def script_shell_encode(s)
# We can't pass utf-8-encoded characters directly in a shell arg. But we can use Ruby \u constants.
s.chars.map { |c| c.ascii_only? ? c : "\\u%x" % c.codepoints[0] }.join