diff options
| author | Takashi Kokubun <takashikkbn@gmail.com> | 2026-02-10 09:32:37 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-02-10 09:32:37 -0800 |
| commit | acacbec35943a1fd3dd58af54d78fb878d80aae4 (patch) | |
| tree | f8bde567d8f15adb776e835e69f8e80f8761443b | |
| parent | 1a3ba1b6dd202ffe27d5adfc63e767684b559921 (diff) | |
ZJIT: Move JIT code tests from test_zjit.rb to Rust (#16130)
| -rw-r--r-- | test/ruby/test_zjit.rb | 4494 | ||||
| -rw-r--r-- | zjit/src/codegen.rs | 34 | ||||
| -rw-r--r-- | zjit/src/codegen_tests.rs | 4977 | ||||
| -rw-r--r-- | zjit/src/hir.rs | 2 |
4 files changed, 4980 insertions, 4527 deletions
diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 095b690c7b..333e34e927 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -112,167 +112,6 @@ class TestZJIT < Test::Unit::TestCase RUBY end - def test_call_itself - assert_compiles '42', <<~RUBY, call_threshold: 2 - def test = 42.itself - test - test - 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 = "" - test - }, insns: [:putchilledstring] - end - - def test_leave_param - assert_compiles '5', %q{ - def test(n) = n - test(5) - } - end - - def test_getglobal_with_warning - assert_compiles('"rescued"', %q{ - Warning[:deprecated] = true - - module Warning - def warn(message) - raise - end - end - - def test - $= - rescue - "rescued" - end - - $VERBOSE = true - test - }, insns: [:getglobal]) - end - - def test_setglobal - assert_compiles '1', %q{ - def test - $a = 1 - $a - end - - test - }, insns: [:setglobal] - end - - def test_string_intern - assert_compiles ':foo123', %q{ - def test - :"foo#{123}" - end - - test - }, insns: [:intern] - end - - def test_duphash - assert_compiles '{a: 1}', %q{ - def test - {a: 1} - end - - test - }, insns: [:duphash] - end - - def test_pushtoarray - assert_compiles '[1, 2, 3]', %q{ - def test - [*[], 1, 2, 3] - end - test - }, insns: [:pushtoarray] - end - - def test_splatarray_new_array - assert_compiles '[1, 2, 3]', %q{ - def test a - [*a, 3] - end - test [1, 2] - }, insns: [:splatarray] - end - - def test_splatarray_existing_array - assert_compiles '[1, 2, 3]', %q{ - def foo v - [1, 2, v] - end - def test a - foo(*a) - end - test [3] - }, insns: [:splatarray] - end - - def test_concattoarray - assert_compiles '[1, 2, 3]', %q{ - def test(*a) - [1, 2, *a] - end - test 3 - }, insns: [:concattoarray] - end - - def test_definedivar - assert_compiles '[nil, "instance-variable", nil]', %q{ - def test - v0 = defined?(@a) - @a = nil - v1 = defined?(@a) - remove_instance_variable :@a - v2 = defined?(@a) - [v0, v1, v2] - end - test - }, insns: [:definedivar] - end - - 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 - 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{ @@ -286,391 +125,6 @@ class TestZJIT < Test::Unit::TestCase assert_equal "[1, 3, 4]", out end - def test_toplevel_local_after_eval - # 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 - eval('b = 3') - c = 4 - print [a, b, c] - }) - assert_success(out, err, status) - assert_equal "[1, 3, 4]", out - end - - def test_getlocal_after_eval - assert_compiles '2', %q{ - def test - a = 1 - eval('a = 2') - a - end - test - } - end - - def test_getlocal_after_instance_eval - assert_compiles '2', %q{ - def test - a = 1 - instance_eval('a = 2') - a - end - test - } - end - - def test_getlocal_after_module_eval - assert_compiles '2', %q{ - def test - a = 1 - Kernel.module_eval('a = 2') - a - end - test - } - end - - def test_getlocal_after_class_eval - assert_compiles '2', %q{ - def test - a = 1 - Kernel.class_eval('a = 2') - a - end - test - } - end - - def test_setlocal - assert_compiles '3', %q{ - def test(n) - m = n - m - end - test(3) - } - end - - def test_return_nonparam_local - # Use dead code (if false) to create a local without initialization instructions. - assert_compiles 'nil', %q{ - def foo(a) - if false - x = nil - end - x - end - def test = foo(1) - test - test - }, call_threshold: 2 - end - - def test_nonparam_local_nil_in_jit_call - # Non-parameter locals must be initialized to nil in JIT-to-JIT calls. - # Use dead code (if false) to create locals without initialization instructions. - # Then eval a string that accesses the uninitialized locals. - assert_compiles '["x", "x", "x", "x"]', %q{ - def f(a) - a ||= 1 - if false; b = 1; end - eval("-> { p 'x#{b}' }") - end - - 4.times.map { f(1).call } - }, call_threshold: 2 - end - - def test_kwargs_with_exit_and_local_invalidation - assert_compiles ':ok', %q{ - def a(b:, c:) - if c == :b - return -> {} - end - Class # invalidate locals - - raise "c is :b!" if c == :b - end - - def test - # note opposite order of kwargs - a(c: :c, b: :b) - end - - 4.times { test } - :ok - }, call_threshold: 2 - end - - def test_kwargs_with_max_direct_send_arg_count - # Ensure that we only reorder the args when we _can_ use direct send (< 6 args). - assert_compiles '[[1, 2, 3, 4, 5, 6, 7, 8]]', %q{ - def kwargs(five, six, a:, b:, c:, d:, e:, f:) - [a, b, c, d, five, six, e, f] - end - - 5.times.flat_map do - [ - kwargs(5, 6, d: 4, c: 3, a: 1, b: 2, e: 7, f: 8), - kwargs(5, 6, d: 4, c: 3, b: 2, a: 1, e: 7, f: 8) - ] - end.uniq - }, call_threshold: 2 - end - - def test_setlocal_on_eval - assert_compiles '1', %q{ - @b = binding - eval('a = 1', @b) - eval('a', @b) - } - end - - def test_optional_arguments - assert_compiles '[[1, 2, 3], [10, 20, 3], [100, 200, 300]]', %q{ - def test(a, b = 2, c = 3) - [a, b, c] - end - [test(1), test(10, 20), test(100, 200, 300)] - } - end - - def test_optional_arguments_setlocal - assert_compiles '[[2, 2], [1, nil]]', %q{ - def test(a = (b = 2)) - [a, b] - end - [test, test(1)] - } - end - - def test_optional_arguments_cyclic - assert_compiles '[nil, 1]', %q{ - test = proc { |a=a| a } - [test.call, test.call(1)] - } - end - - def test_optional_arguments_side_exit - # This leads to FailedOptionalArguments, so not using assert_compiles - assert_runs '[:foo, nil, 1]', %q{ - def test(a = (def foo = nil)) = a - [test, (undef :foo), test(1)] - } - end - - def test_getblockparamproxy - assert_compiles '1', %q{ - def test(&block) - 0.then(&block) - end - test { 1 } - }, insns: [:getblockparamproxy] - end - - def test_getblockparam - assert_compiles '2', %q{ - def test(&blk) - blk - end - test { 2 }.call - test { 2 }.call - }, insns: [:getblockparam] - end - - def test_getblockparam_proxy_side_exit_restores_block_local - assert_compiles '2', %q{ - def test(&block) - b = block - # sideexits here - raise "test" unless block - b ? 2 : 3 - end - test {} - test {} - }, insns: [:getblockparam, :getblockparamproxy] - end - - def test_getblockparam_used_twice_in_args - assert_compiles '1', %q{ - def f(*args) = args - def test(&blk) - b = blk - f(*[1], blk) - blk - end - test {1}.call - test {1}.call - }, insns: [:getblockparam] - end - - def test_optimized_method_call_proc_call - assert_compiles '2', %q{ - p = proc { |x| x * 2 } - def test(p) - p.call(1) - end - test(p) - test(p) - }, call_threshold: 2, insns: [:opt_send_without_block] - end - - def test_optimized_method_call_proc_aref - assert_compiles '4', %q{ - p = proc { |x| x * 2 } - def test(p) - p[2] - end - test(p) - test(p) - }, call_threshold: 2, insns: [:opt_aref] - end - - def test_optimized_method_call_proc_yield - assert_compiles '6', %q{ - p = proc { |x| x * 2 } - def test(p) - p.yield(3) - end - test(p) - test(p) - }, call_threshold: 2, insns: [:opt_send_without_block] - end - - def test_optimized_method_call_proc_kw_splat - assert_compiles '3', %q{ - p = proc { |**kw| kw[:a] + kw[:b] } - def test(p, h) - p.call(**h) - end - h = { a: 1, b: 2 } - test(p, h) - test(p, h) - }, call_threshold: 2, insns: [:opt_send_without_block] - end - - def test_optimized_method_call_proc_call_splat - assert_compiles '43', %q{ - p = proc { |x| x + 1 } - def test(p) - ary = [42] - p.call(*ary) - end - test(p) - test(p) - }, call_threshold: 2 - end - - def test_optimized_method_call_proc_call_kwarg - assert_compiles '1', %q{ - p = proc { |a:| a } - def test(p) - p.call(a: 1) - end - test(p) - test(p) - }, call_threshold: 2 - end - - def test_call_a_forwardable_method - assert_runs '[]', %q{ - def test_root = forwardable - def forwardable(...) = Array.[](...) - test_root - test_root - }, call_threshold: 2 - 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_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 - - test - test - test - }, call_threshold: 3, insns: [:getlocal, :setlocal, :getlocal_WC_0, :setlocal_WC_1] - end - - def test_send_with_local_written_by_blockiseq - assert_compiles '[1, 2]', %q{ - def test - l1 = nil - l2 = nil - tap do |_| - l1 = 1 - tap do |_| - l2 = 2 - end - end - - [l1, l2] - end - - test - test - }, call_threshold: 2 - 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] - } - 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 - foo(1, 2, 3, 4, 5, 6) - end - - test # profile send - test - }, call_threshold: 2 - 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 - end - def test_send_exit_with_uninitialized_locals assert_runs 'nil', %q{ def entry(init) @@ -687,1467 +141,6 @@ class TestZJIT < Test::Unit::TestCase }, call_threshold: 2, allowed_iseqs: 'entry@-e:2' end - def test_send_optional_arguments - assert_compiles '[[1, 2], [3, 4]]', %q{ - def test(a, b = 2) = [a, b] - def entry = [test(1), test(3, 4)] - entry - entry - }, call_threshold: 2 - end - - def test_send_nil_block_arg - assert_compiles 'false', %q{ - def test = block_given? - def entry = test(&nil) - test - } - end - - def test_send_symbol_block_arg - assert_compiles '["1", "2"]', %q{ - def test = [1, 2].map(&:to_s) - test - } - end - - def test_send_variadic_with_block - assert_compiles '[[1, "a"], [2, "b"], [3, "c"]]', %q{ - A = [1, 2, 3] - B = ["a", "b", "c"] - - def test - result = [] - A.zip(B) { |x, y| result << [x, y] } - result - end - - test; test - }, call_threshold: 2 - end - - def test_send_splat - assert_runs '[1, 2]', %q{ - def test(a, b) = [a, b] - def entry(arr) = test(*arr) - entry([1, 2]) - } - end - - def test_send_kwarg - assert_runs '[1, 2]', %q{ - def test(a:, b:) = [a, b] - def entry = test(b: 2, a: 1) # change order - entry - entry - }, call_threshold: 2 - end - - def test_send_kwarg_optional - assert_compiles '[1, 2]', %q{ - def test(a: 1, b: 2) = [a, b] - def entry = test - entry - entry - }, call_threshold: 2 - end - - def test_send_kwarg_optional_too_many - assert_compiles '[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]', %q{ - def test(a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10) = [a, b, c, d, e, f, g, h, i, j] - def entry = test - entry - entry - }, call_threshold: 2 - end - - def test_send_kwarg_required_and_optional - assert_compiles '[3, 2]', %q{ - def test(a:, b: 2) = [a, b] - def entry = test(a: 3) - entry - entry - }, call_threshold: 2 - end - - def test_send_kwarg_to_hash - assert_compiles '{a: 3}', %q{ - def test(hash) = hash - def entry = test(a: 3) - entry - entry - }, call_threshold: 2 - end - - def test_send_kwarg_to_ccall - assert_compiles '["a", "b", "c"]', %q{ - def test(s) = s.each_line(chomp: true).to_a - def entry = test(%(a\nb\nc)) - entry - entry - }, call_threshold: 2 - end - - def test_send_kwarg_and_block_to_ccall - assert_compiles '["a", "b", "c"]', %q{ - def test(s) - a = [] - s.each_line(chomp: true) { |l| a << l } - a - end - def entry = test(%(a\nb\nc)) - entry - entry - }, call_threshold: 2 - end - - def test_send_kwarg_with_too_many_args_to_c_call - assert_compiles '"a b c d {kwargs: :e}"', %q{ - def test(a:, b:, c:, d:, e:) = sprintf("%s %s %s %s %s", a, b, c, d, kwargs: e) - def entry = test(e: :e, d: :d, c: :c, a: :a, b: :b) - entry - entry - }, call_threshold: 2 - end - - def test_send_kwsplat - assert_compiles '3', %q{ - def test(a:) = a - def entry = test(**{a: 3}) - entry - entry - }, call_threshold: 2 - end - - def test_send_kwrest - assert_compiles '{a: 3}', %q{ - def test(**kwargs) = kwargs - def entry = test(a: 3) - entry - entry - }, call_threshold: 2 - end - - def test_send_req_kwreq - assert_compiles '[1, 3]', %q{ - def test(a, c:) = [a, c] - def entry = test(1, c: 3) - entry - entry - }, call_threshold: 2 - end - - def test_send_req_opt_kwreq - assert_compiles '[[1, 2, 3], [-1, -2, -3]]', %q{ - def test(a, b = 2, c:) = [a, b, c] - def entry = [test(1, c: 3), test(-1, -2, c: -3)] # specify all, change kw order - entry - entry - }, call_threshold: 2 - end - - def test_send_req_opt_kwreq_kwopt - assert_compiles '[[1, 2, 3, 4], [-1, -2, -3, -4]]', %q{ - def test(a, b = 2, c:, d: 4) = [a, b, c, d] - def entry = [test(1, c: 3), test(-1, -2, d: -4, c: -3)] # specify all, change kw order - entry - entry - }, call_threshold: 2 - end - - def test_send_unexpected_keyword - assert_compiles ':error', %q{ - def test(a: 1) = a*2 - def entry - test(z: 2) - rescue ArgumentError - :error - end - - entry - entry - }, call_threshold: 2 - end - - def test_pos_optional_with_maybe_too_many_args - assert_compiles '[[1, 2, 3, 4, 5, 6], [10, 20, 30, 4, 5, 6], [10, 20, 30, 40, 50, 60]]', %q{ - def target(a = 1, b = 2, c = 3, d = 4, e = 5, f:) = [a, b, c, d, e, f] - def test = [target(f: 6), target(10, 20, 30, f: 6), target(10, 20, 30, 40, 50, f: 60)] - test - test - }, call_threshold: 2 - end - - def test_send_kwarg_partial_optional - assert_compiles '[[1, 2, 3], [1, 20, 3], [10, 2, 30]]', %q{ - def test(a: 1, b: 2, c: 3) = [a, b, c] - def entry = [test, test(b: 20), test(c: 30, a: 10)] - entry - entry - }, call_threshold: 2 - end - - def test_send_kwarg_optional_a_lot - assert_compiles '[[1, 2, 3, 4, 5, 6], [1, 2, 3, 7, 8, 9], [2, 4, 6, 8, 10, 12]]', %q{ - def test(a: 1, b: 2, c: 3, d: 4, e: 5, f: 6) = [a, b, c, d, e, f] - def entry = [test, test(d: 7, f: 9, e: 8), test(f: 12, e: 10, d: 8, c: 6, b: 4, a: 2)] - entry - entry - }, call_threshold: 2 - end - - def test_send_kwarg_non_constant_default - assert_compiles '[[1, 2], [10, 2]]', %q{ - def make_default = 2 - def test(a: 1, b: make_default) = [a, b] - def entry = [test, test(a: 10)] - entry - entry - }, call_threshold: 2 - end - - def test_send_kwarg_optional_static_with_side_exit - # verify frame reconstruction with synthesized keyword defaults is correct - assert_compiles '[10, 2, 10]', %q{ - def callee(a: 1, b: 2) - # use binding to force side-exit - x = binding.local_variable_get(:a) - [a, b, x] - end - - def entry - callee(a: 10) # b should get default value - end - - entry - entry - }, call_threshold: 2 - end - - def test_send_all_arg_types - assert_compiles '[:req, :opt, :post, :kwr, :kwo, true]', %q{ - def test(a, b = :opt, c, d:, e: :kwo) = [a, b, c, d, e, block_given?] - def entry = test(:req, :post, d: :kwr) {} - entry - entry - }, call_threshold: 2 - end - - def test_send_ccall_variadic_with_different_receiver_classes - assert_compiles '[true, true]', %q{ - def test(obj) = obj.start_with?("a") - [test("abc"), test(:abc)] - }, call_threshold: 2 - end - - def test_forwardable_iseq - assert_compiles '1', %q{ - def test(...) = 1 - test - } - end - - def test_sendforward - assert_compiles '[1, 2]', %q{ - def callee(a, b) = [a, b] - def test(...) = callee(...) - test(1, 2) - }, insns: [:sendforward] - end - - def test_iseq_with_optional_arguments - assert_compiles '[[1, 2], [3, 4]]', %q{ - def test(a, b = 2) = [a, b] - [test(1), test(3, 4)] - } - end - - def test_invokesuper - assert_compiles '[6, 60]', %q{ - class Foo - def foo(a) = a + 1 - def bar(a) = a + 10 - end - - class Bar < Foo - def foo(a) = super(a) + 2 - def bar(a) = super + 20 - end - - bar = Bar.new - [bar.foo(3), bar.bar(30)] - } - end - - def test_invokesuper_with_local_written_by_blockiseq - # Using `assert_runs` because we don't compile invokeblock yet - assert_runs '3', %q{ - class Foo - def test - yield - end - end - - class Bar < Foo - def test - a = 1 - super do - a += 2 - end - a - end - end - - Bar.new.test - } - end - - def test_invokesuper_to_iseq - assert_compiles '["B", "A"]', %q{ - class A - def foo - "A" - end - end - - class B < A - def foo - ["B", super] - end - end - - def test - B.new.foo - end - - test # profile invokesuper - test # compile + run compiled code - }, call_threshold: 2 - end - - def test_invokesuper_with_args - assert_compiles '["B", 11]', %q{ - class A - def foo(x) - x * 2 - end - end - - class B < A - def foo(x) - ["B", super(x) + 1] - end - end - - def test - B.new.foo(5) - end - - test # profile invokesuper - test # compile + run compiled code - }, call_threshold: 2 - end - - # Test super with explicit args when callee has rest parameter. - # This should fall back to dynamic dispatch since we can't handle rest params yet. - def test_invokesuper_with_args_to_rest_param - assert_compiles '["B", "a", ["b", "c"]]', %q{ - class A - def foo(x, *rest) - [x, rest] - end - end - - class B < A - def foo(x, y, z) - ["B", *super(x, y, z)] - end - end - - def test - B.new.foo("a", "b", "c") - end - - test # profile invokesuper - test # compile + run compiled code - }, call_threshold: 2 - end - - def test_invokesuper_with_block - assert_compiles '["B", "from_block"]', %q{ - class A - def foo - block_given? ? yield : "no_block" - end - end - - class B < A - def foo - ["B", super { "from_block" }] - end - end - - def test - B.new.foo - end - - test # profile invokesuper - test # compile + run compiled code - }, call_threshold: 2 - end - - def test_invokesuper_to_cfunc_no_args - assert_compiles '["MyString", 3]', %q{ - class MyString < String - def length - ["MyString", super] - end - end - - def test - MyString.new("abc").length - end - - test # profile invokesuper - test # compile + run compiled code - }, call_threshold: 2 - end - - def test_invokesuper_to_cfunc_simple_args - assert_compiles '["MyString", true]', %q{ - class MyString < String - def include?(other) - ["MyString", super(other)] - end - end - - def test - MyString.new("abc").include?("bc") - end - - test # profile invokesuper - test # compile + run compiled code - }, call_threshold: 2 - end - - - def test_invokesuper_to_cfunc_with_optional_arg - assert_compiles '["MyString", 6]', %q{ - class MyString < String - def byteindex(needle, offset = 0) - ["MyString", super(needle, offset)] - end - end - - def test - MyString.new("hello world").byteindex("world") - end - - test # profile invokesuper - test # compile + run compiled code - }, call_threshold: 2 - end - - def test_invokesuper_to_cfunc_varargs - assert_compiles '["MyString", true]', %q{ - class MyString < String - def end_with?(str) - ["MyString", super(str)] - end - end - - def test - MyString.new("abc").end_with?("bc") - end - - test # profile invokesuper - test # compile + run compiled code - }, call_threshold: 2 - end - - def test_invokesuper_multilevel - assert_compiles '["C", ["B", "A"]]', %q{ - class A - def foo - "A" - end - end - - class B < A - def foo - ["B", super] - end - end - - class C < B - def foo - ["C", super] - end - end - - def test - C.new.foo - end - - test # profile invokesuper - test # compile + run compiled code - }, call_threshold: 2 - end - - # Test implicit block forwarding - super without explicit block should forward caller's block - # Note: We call test twice to ensure ZJIT compiles it before the final call that we check - def test_invokesuper_forwards_block_implicitly - assert_compiles '["B", "forwarded_block"]', %q{ - class A - def foo - block_given? ? yield : "no_block" - end - end - - class B < A - def foo - ["B", super] # should forward the block from caller - end - end - - def test - B.new.foo { "forwarded_block" } - end - - test # profile invokesuper - test # compile + run compiled code - }, call_threshold: 2 - end - - # Test implicit block forwarding with explicit arguments - def test_invokesuper_forwards_block_implicitly_with_args - assert_compiles '["B", ["arg_value", "forwarded"]]', %q{ - class A - def foo(x) - [x, (block_given? ? yield : "no_block")] - end - end - - class B < A - def foo(x) - ["B", super(x)] # explicit args, but block should still be forwarded - end - end - - def test - B.new.foo("arg_value") { "forwarded" } - end - - test # profile - test # compile + run compiled code - }, call_threshold: 2 - end - - # Test implicit block forwarding when no block is given (should not fail) - def test_invokesuper_forwards_block_implicitly_no_block_given - assert_compiles '["B", "no_block"]', %q{ - class A - def foo - block_given? ? yield : "no_block" - end - end - - class B < A - def foo - ["B", super] # no block given by caller - end - end - - def test - B.new.foo # called without a block - end - - test # profile - test # compile + run compiled code - }, call_threshold: 2 - end - - # Test implicit block forwarding through multiple inheritance levels - def test_invokesuper_forwards_block_implicitly_multilevel - assert_compiles '["C", ["B", "deep_block"]]', %q{ - class A - def foo - block_given? ? yield : "no_block" - end - end - - class B < A - def foo - ["B", super] # forwards block to A - end - end - - class C < B - def foo - ["C", super] # forwards block to B, which forwards to A - end - end - - def test - C.new.foo { "deep_block" } - end - - test # profile - test # compile + run compiled code - }, call_threshold: 2 - end - - # Test implicit block forwarding with block parameter syntax - def test_invokesuper_forwards_block_param - assert_compiles '["B", "block_param_forwarded"]', %q{ - class A - def foo - block_given? ? yield : "no_block" - end - end - - class B < A - def foo(&block) - ["B", super] # should forward &block implicitly - end - end - - def test - B.new.foo { "block_param_forwarded" } - end - - test # profile - test # compile + run compiled code - }, call_threshold: 2 - end - - def test_invokesuper_with_blockarg - assert_compiles '["B", "different block"]', %q{ - class A - def foo - block_given? ? yield : "no block" - end - end - - class B < A - def foo(&blk) - other_block = proc { "different block" } - ["B", super(&other_block)] - end - end - - def test - B.new.foo { "passed block" } - end - - test # profile - test # compile + run compiled code - }, call_threshold: 2 - end - - def test_invokesuper_with_symbol_to_proc - assert_compiles '["B", [3, 5, 7]]', %q{ - class A - def foo(items, &blk) - items.map(&blk) - end - end - - class B < A - def foo(items) - ["B", super(items, &:succ)] - end - end - - def test - B.new.foo([2, 4, 6]) - end - - test # profile - test # compile + run compiled code - }, call_threshold: 2 - end - - def test_invokesuper_with_splat - assert_compiles '["B", 6]', %q{ - class A - def foo(a, b, c) - a + b + c - end - end - - class B < A - def foo(*args) - ["B", super(*args)] - end - end - - def test - B.new.foo(1, 2, 3) - end - - test # profile - test # compile + run compiled code - }, call_threshold: 2 - end - - def test_invokesuper_with_kwargs - assert_compiles '["B", "x=1, y=2"]', %q{ - class A - def foo(x:, y:) - "x=#{x}, y=#{y}" - end - end - - class B < A - def foo(x:, y:) - ["B", super(x: x, y: y)] - end - end - - def test - B.new.foo(x: 1, y: 2) - end - - test # profile - test # compile + run compiled code - }, call_threshold: 2 - end - - def test_invokesuper_with_kw_splat - assert_compiles '["B", "x=1, y=2"]', %q{ - class A - def foo(x:, y:) - "x=#{x}, y=#{y}" - end - end - - class B < A - def foo(**kwargs) - ["B", super(**kwargs)] - end - end - - def test - B.new.foo(x: 1, y: 2) - end - - test # profile - test # compile + run compiled code - }, call_threshold: 2 - end - - # Test that including a module after compilation correctly changes the super target. - # The included module's method should be called, not the original super target. - def test_invokesuper_with_include - assert_compiles '["B", "M"]', %q{ - class A - def foo - "A" - end - end - - class B < A - def foo - ["B", super] - end - end - - def test - B.new.foo - end - - test # profile invokesuper (super -> A#foo) - test # compile with super -> A#foo - - # Now include a module in B that defines foo - super should go to M#foo instead - module M - def foo - "M" - end - end - B.include(M) - - test # should call M#foo, not A#foo - }, call_threshold: 2 - end - - # Test that prepending a module after compilation correctly changes the super target. - # The prepended module's method should be called, not the original super target. - def test_invokesuper_with_prepend - assert_compiles '["B", "M"]', %q{ - class A - def foo - "A" - end - end - - class B < A - def foo - ["B", super] - end - end - - def test - B.new.foo - end - - test # profile invokesuper (super -> A#foo) - test # compile with super -> A#foo - - # Now prepend a module that defines foo - super should go to M#foo instead - module M - def foo - "M" - end - end - A.prepend(M) - - test # should call M#foo, not A#foo - }, call_threshold: 2 - end - - # Test super with positional and keyword arguments (pattern from chunky_png) - def test_invokesuper_with_keyword_args - assert_compiles '{content: "image data"}', %q{ - class A - def foo(attributes = {}) - @attributes = attributes - end - end - - class B < A - def foo(content = '') - super(content: content) - end - end - - def test - B.new.foo("image data") - end - - test - test - }, call_threshold: 2 - end - - def test_invokesuper_with_optional_keyword_args - assert_compiles '[1, 2, 3]', %q{ - class Parent - def foo(a, b: 2, c: 3) = [a, b, c] - end - - class Child < Parent - def foo(a) = super(a) - end - - def test = Child.new.foo(1) - - test - test - }, call_threshold: 2 - end - - def test_invokesuperforward - assert_compiles '[1, 2, 3]', %q{ - class A - def foo(a,b,c) = [a,b,c] - end - - class B < A - def foo(...) = super - end - - def test - B.new.foo(1, 2, 3) - end - - test - test - }, call_threshold: 2 - end - - def test_invokesuperforward_with_args_kwargs_and_block - assert_compiles '[[1, 2], {x: 3}, 4]', %q{ - class A - def foo(*args, **kwargs, &block) - [args, kwargs, block&.call] - end - end - - class B < A - def foo(...) = super - end - - def test - B.new.foo(1, 2, x: 3) { 4 } - end - - test - test - }, call_threshold: 2 - end - - def test_send_with_non_constant_keyword_default - assert_compiles '[[2, 4, 16], [10, 4, 16], [2, 20, 16], [2, 4, 30], [10, 20, 30]]', %q{ - def dbl(x = 1) = x * 2 - - def foo(a: dbl, b: dbl(2), c: dbl(2 ** 3)) - [a, b, c] - end - - def test - [ - foo, - foo(a: 10), - foo(b: 20), - foo(c: 30), - foo(a: 10, b: 20, c: 30) - ] - end - - test - test - }, call_threshold: 2 - end - - def test_send_with_non_constant_keyword_default_not_evaluated_when_provided - assert_compiles '[1, 2, 3]', %q{ - def foo(a: raise, b: raise, c: raise) - [a, b, c] - end - - def test - foo(a: 1, b: 2, c: 3) - end - - test - test - }, call_threshold: 2 - end - - def test_send_with_non_constant_keyword_default_evaluated_when_not_provided - assert_compiles '["a", "b", "c"]', %q{ - def raise_a = raise "a" - def raise_b = raise "b" - def raise_c = raise "c" - - def foo(a: raise_a, b: raise_b, c: raise_c) - [a, b, c] - end - - def test_a - foo(b: 2, c: 3) - rescue RuntimeError => e - e.message - end - - def test_b - foo(a: 1, c: 3) - rescue RuntimeError => e - e.message - end - - def test_c - foo(a: 1, b: 2) - rescue RuntimeError => e - e.message - end - - def test - [test_a, test_b, test_c] - end - - test - test - }, call_threshold: 2 - end - - def test_send_with_non_constant_keyword_default_jit_to_jit - # Test that kw_bits passing works correctly in JIT-to-JIT calls - assert_compiles '[2, 4, 6]', %q{ - def make_default(x) = x * 2 - - def callee(a: make_default(1), b: make_default(2), c: make_default(3)) - [a, b, c] - end - - def caller_method - callee - end - - # Warm up callee first so it gets JITted - callee - callee - - # Now warm up caller - this creates JIT-to-JIT call - caller_method - caller_method - }, call_threshold: 2 - end - - def test_send_with_non_constant_keyword_default_side_exit - # Verify frame reconstruction includes correct values for non-constant defaults - assert_compiles '[10, 2, 30]', %q{ - def make_b = 2 - - def callee(a: 1, b: make_b, c: 3) - x = binding.local_variable_get(:a) - y = binding.local_variable_get(:b) - z = binding.local_variable_get(:c) - [x, y, z] - end - - def test - callee(a: 10, c: 30) - end - - test - test - }, call_threshold: 2 - end - - def test_send_with_non_constant_keyword_default_evaluation_order - # Verify defaults are evaluated left-to-right and only when not provided - assert_compiles '[["a", "b", "c"], ["b", "c"], ["a", "c"], ["a", "b"]]', %q{ - def log(x) - $order << x - x - end - - def foo(a: log("a"), b: log("b"), c: log("c")) - [a, b, c] - end - - def test - results = [] - - $order = [] - foo - results << $order.dup - - $order = [] - foo(a: "A") - results << $order.dup - - $order = [] - foo(b: "B") - results << $order.dup - - $order = [] - foo(c: "C") - results << $order.dup - - results - end - - test - test - }, call_threshold: 2 - end - - def test_send_with_too_many_non_constant_keyword_defaults - assert_compiles '35', %q{ - def many_kwargs( k1: 1, k2: 2, k3: 3, k4: 4, k5: 5, k6: 6, k7: 7, k8: 8, k9: 9, k10: 10, k11: 11, k12: 12, k13: 13, k14: 14, k15: 15, k16: 16, k17: 17, k18: 18, k19: 19, k20: 20, k21: 21, k22: 22, k23: 23, k24: 24, k25: 25, k26: 26, k27: 27, k28: 28, k29: 29, k30: 30, k31: 31, k32: 32, k33: 33, k34: k33 + 1) = k1 + k34 - def t = many_kwargs - t - t - }, call_threshold: 2 - end - - def test_invokebuiltin - # Not using assert_compiles due to register spill - assert_runs '["."]', %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, 2, 4]', %q{ - def test(a, b) = a & b - [ - test(5, 3), - test(0b011, 0b110), - test(-0b011, 0b110) - ] - }, call_threshold: 2, insns: [:opt_and] - end - - def test_fixnum_and_side_exit - assert_compiles '[2, 2, false]', %q{ - def test(a, b) = a & b - [ - test(2, 2), - test(0b011, 0b110), - test(true, false) - ] - }, call_threshold: 2, insns: [:opt_and] - end - - def test_fixnum_or - assert_compiles '[7, 3, -3]', %q{ - def test(a, b) = a | b - [ - test(5, 3), - test(1, 2), - test(1, -4) - ] - }, call_threshold: 2, insns: [:opt_or] - end - - def test_fixnum_or_side_exit - assert_compiles '[3, 2, true]', %q{ - def test(a, b) = a | b - [ - test(1, 2), - test(2, 2), - test(true, false) - ] - }, call_threshold: 2, insns: [:opt_or] - end - - def test_fixnum_xor - assert_compiles '[6, -8, 3]', %q{ - def test(a, b) = a ^ b - [ - test(5, 3), - test(-5, 3), - test(1, 2) - ] - }, call_threshold: 2 - end - - def test_fixnum_xor_side_exit - assert_compiles '[6, 6, true]', %q{ - def test(a, b) = a ^ b - [ - test(5, 3), - test(5, 3), - test(true, false) - ] - }, call_threshold: 2 - 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_fixnum_div - assert_compiles '12', %q{ - C = 48 - def test(n) = C / n - test(4) - test(4) - }, call_threshold: 2, insns: [:opt_div] - end - - def test_fixnum_floor - assert_compiles '0', %q{ - C = 3 - def test(n) = C / n - test(4) - test(4) - }, call_threshold: 2, insns: [:opt_div] - end - - def test_fixnum_div_zero - assert_runs '"divided by 0"', %q{ - def test(n) - n / 0 - rescue ZeroDivisionError => e - e.message - end - - test(0) - test(0) - }, call_threshold: 2, insns: [:opt_div] - 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_opt_new_does_not_push_frame - assert_compiles 'nil', %q{ - class Foo - attr_reader :backtrace - - def initialize - @backtrace = caller - end - end - def test = Foo.new - - foo = test - foo.backtrace.find do |frame| - frame.include?('Class#new') - end - }, insns: [:opt_new] - end - - def test_opt_new_with_redefined - assert_compiles '"foo"', %q{ - class Foo - def self.new = "foo" - - def initialize = raise("unreachable") - end - def test = Foo.new - - test - }, insns: [:opt_new] - end - - def test_opt_new_invalidate_new - assert_compiles '["Foo", "foo"]', %q{ - class Foo; end - def test = Foo.new - test; test - result = [test.class.name] - def Foo.new = "foo" - result << test - result - }, insns: [:opt_new], call_threshold: 2 - end - def test_opt_new_with_custom_allocator assert_compiles '"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"', %q{ require "digest" @@ -2171,1208 +164,6 @@ class TestZJIT < Test::Unit::TestCase }, insns: [:opt_new], call_threshold: 2 end - def test_opt_newarray_send_include_p - assert_compiles '[true, false]', %q{ - def test(x) - [:y, 1, Object.new].include?(x) - end - [test(1), test("n")] - }, insns: [:opt_newarray_send], call_threshold: 1 - end - - def test_opt_newarray_send_include_p_redefined - assert_compiles '[:true, :false]', %q{ - class Array - alias_method :old_include?, :include? - def include?(x) - old_include?(x) ? :true : :false - end - end - - def test(x) - [:y, 1, Object.new].include?(x) - end - [test(1), test("n")] - }, insns: [:opt_newarray_send], call_threshold: 1 - end - - def test_opt_duparray_send_include_p - assert_compiles '[true, false]', %q{ - def test(x) - [:y, 1].include?(x) - end - [test(1), test("n")] - }, insns: [:opt_duparray_send], call_threshold: 1 - end - - def test_opt_duparray_send_include_p_redefined - assert_compiles '[:true, :false]', %q{ - class Array - alias_method :old_include?, :include? - def include?(x) - old_include?(x) ? :true : :false - end - end - - def test(x) - [:y, 1].include?(x) - end - [test(1), test("n")] - }, insns: [:opt_duparray_send], call_threshold: 1 - end - - def test_opt_newarray_send_pack_buffer - assert_compiles '["ABC", "ABC", "ABC", "ABC"]', %q{ - def test(num, buffer) - [num].pack('C', buffer:) - end - buf = "" - [test(65, buf), test(66, buf), test(67, buf), buf] - }, insns: [:opt_newarray_send], call_threshold: 1 - end - - def test_opt_newarray_send_pack_buffer_redefined - assert_compiles '["b", "A"]', %q{ - class Array - alias_method :old_pack, :pack - def pack(fmt, buffer: nil) - old_pack(fmt, buffer: buffer) - "b" - end - end - - def test(num, buffer) - [num].pack('C', buffer:) - end - buf = "" - [test(65, buf), buf] - }, insns: [:opt_newarray_send], call_threshold: 1 - end - - def test_opt_newarray_send_hash - assert_compiles 'Integer', %q{ - def test(x) - [1, 2, x].hash - end - test(20).class - }, insns: [:opt_newarray_send], call_threshold: 1 - end - - def test_opt_newarray_send_hash_redefined - assert_compiles '42', %q{ - Array.class_eval { def hash = 42 } - - def test(x) - [1, 2, x].hash - end - test(20) - }, insns: [:opt_newarray_send], call_threshold: 1 - end - - def test_opt_newarray_send_max - assert_compiles '[20, 40]', %q{ - def test(a,b) = [a,b].max - [test(10, 20), test(40, 30)] - }, insns: [:opt_newarray_send], call_threshold: 1 - end - - def test_opt_newarray_send_max_redefined - assert_compiles '[60, 90]', %q{ - class Array - alias_method :old_max, :max - def max - old_max * 2 - end - end - - def test(a,b) = [a,b].max - [test(15, 30), test(45, 35)] - }, insns: [:opt_newarray_send], call_threshold: 1 - 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 - } - 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 - 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 - 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 "[{}, 5]", %q{ - def test = {}.freeze - result = [test] - class Hash - def freeze = 5 - end - result << test - }, insns: [:opt_hash_freeze], call_threshold: 1 - end - - def test_opt_hash_freeze_rewritten - assert_compiles "5", %q{ - class Hash - def freeze = 5 - end - def test = {}.freeze - test - }, insns: [:opt_hash_freeze], call_threshold: 1 - end - - def test_opt_aset_hash - assert_compiles '42', %q{ - def test(h, k, v) - h[k] = v - end - h = {} - test(h, :key, 42) - test(h, :key, 42) - h[:key] - }, call_threshold: 2, insns: [:opt_aset] - end - - def test_opt_aset_hash_returns_value - assert_compiles '100', %q{ - def test(h, k, v) - h[k] = v - end - test({}, :key, 100) - test({}, :key, 100) - }, call_threshold: 2 - end - - def test_opt_aset_hash_string_key - assert_compiles '"bar"', %q{ - def test(h, k, v) - h[k] = v - end - h = {} - test(h, "foo", "bar") - test(h, "foo", "bar") - h["foo"] - }, call_threshold: 2 - end - - def test_opt_aset_hash_subclass - assert_compiles '42', %q{ - class MyHash < Hash; end - def test(h, k, v) - h[k] = v - end - h = MyHash.new - test(h, :key, 42) - test(h, :key, 42) - h[:key] - }, call_threshold: 2 - end - - def test_opt_aset_hash_too_few_args - assert_compiles '"ArgumentError"', %q{ - def test(h) - h.[]= 123 - rescue ArgumentError - "ArgumentError" - end - test({}) - test({}) - }, call_threshold: 2 - end - - def test_opt_aset_hash_too_many_args - assert_compiles '"ArgumentError"', %q{ - def test(h) - h[:a, :b] = :c - rescue ArgumentError - "ArgumentError" - end - test({}) - test({}) - }, call_threshold: 2 - end - - def test_opt_ary_freeze - assert_compiles "[[], 5]", %q{ - def test = [].freeze - result = [test] - class Array - def freeze = 5 - end - result << test - }, insns: [:opt_ary_freeze], call_threshold: 1 - end - - def test_opt_ary_freeze_rewritten - assert_compiles "5", %q{ - class Array - def freeze = 5 - end - def test = [].freeze - test - }, insns: [:opt_ary_freeze], call_threshold: 1 - end - - def test_opt_str_freeze - assert_compiles "[\"\", 5]", %q{ - def test = ''.freeze - result = [test] - class String - def freeze = 5 - end - result << test - }, insns: [:opt_str_freeze], call_threshold: 1 - end - - def test_opt_str_freeze_rewritten - assert_compiles "5", %q{ - class String - def freeze = 5 - end - def test = ''.freeze - test - }, insns: [:opt_str_freeze], call_threshold: 1 - end - - def test_opt_str_uminus - assert_compiles "[\"\", 5]", %q{ - def test = -'' - result = [test] - class String - def -@ = 5 - end - result << test - }, insns: [:opt_str_uminus], call_threshold: 1 - end - - def test_opt_str_uminus_rewritten - assert_compiles "5", %q{ - class String - def -@ = 5 - end - def test = -'' - test - }, insns: [:opt_str_uminus], call_threshold: 1 - 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_array_fixnum_aref - assert_compiles '3', %q{ - def test(x) = [1,2,3][x] - test(2) - test(2) - }, call_threshold: 2, insns: [:opt_aref] - end - - def test_array_fixnum_aref_negative_index - assert_compiles '3', %q{ - def test(x) = [1,2,3][x] - test(-1) - test(-1) - }, call_threshold: 2, insns: [:opt_aref] - end - - def test_array_fixnum_aref_out_of_bounds_positive - assert_compiles 'nil', %q{ - def test(x) = [1,2,3][x] - test(10) - test(10) - }, call_threshold: 2, insns: [:opt_aref] - end - - def test_array_fixnum_aref_out_of_bounds_negative - assert_compiles 'nil', %q{ - def test(x) = [1,2,3][x] - test(-10) - test(-10) - }, call_threshold: 2, insns: [:opt_aref] - end - - def test_array_fixnum_aref_array_subclass - assert_compiles '3', %q{ - class MyArray < Array; end - def test(arr, idx) = arr[idx] - arr = MyArray[1,2,3] - test(arr, 2) - arr = MyArray[1,2,3] - test(arr, 2) - }, call_threshold: 2, insns: [:opt_aref] - end - - def test_array_aref_non_fixnum_index - assert_compiles 'TypeError', %q{ - def test(arr, idx) = arr[idx] - test([1,2,3], 1) - test([1,2,3], 1) - begin - test([1,2,3], "1") - rescue => e - e.class - end - }, call_threshold: 2 - end - - def test_array_fixnum_aset - assert_compiles '[1, 2, 7]', %q{ - def test(arr, idx) - arr[idx] = 7 - end - arr = [1,2,3] - test(arr, 2) - arr = [1,2,3] - test(arr, 2) - arr - }, call_threshold: 2, insns: [:opt_aset] - end - - def test_array_fixnum_aset_returns_value - assert_compiles '7', %q{ - def test(arr, idx) - arr[idx] = 7 - end - test([1,2,3], 2) - test([1,2,3], 2) - }, call_threshold: 2, insns: [:opt_aset] - end - - def test_array_fixnum_aset_out_of_bounds - assert_compiles '[1, 2, 3, nil, nil, 7]', %q{ - def test(arr) - arr[5] = 7 - end - arr = [1,2,3] - test(arr) - arr = [1,2,3] - test(arr) - arr - }, call_threshold: 2 - end - - def test_array_fixnum_aset_negative_index - assert_compiles '[1, 2, 7]', %q{ - def test(arr) - arr[-1] = 7 - end - arr = [1,2,3] - test(arr) - arr = [1,2,3] - test(arr) - arr - }, call_threshold: 2 - end - - def test_array_fixnum_aset_shared - assert_compiles '[10, 999, -1, -2]', %q{ - def test(arr, idx, val) - arr[idx] = val - end - arr = (0..50).to_a - test(arr, 0, -1) - test(arr, 1, -2) - shared = arr[10, 20] - test(shared, 0, 999) - [arr[10], shared[0], arr[0], arr[1]] - }, call_threshold: 2 - end - - def test_array_fixnum_aset_frozen - assert_compiles 'FrozenError', %q{ - def test(arr, idx, val) - arr[idx] = val - end - arr = [1,2,3] - test(arr, 1, 9) - test(arr, 1, 9) - arr.freeze - begin - test(arr, 1, 9) - rescue => e - e.class - end - }, call_threshold: 2 - end - - def test_array_fixnum_aset_array_subclass - assert_compiles '7', %q{ - class MyArray < Array; end - def test(arr, idx) - arr[idx] = 7 - end - arr = MyArray.new - test(arr, 0) - arr = MyArray.new - test(arr, 0) - arr[0] - }, call_threshold: 2, insns: [:opt_aset] - end - - def test_array_aset_non_fixnum_index - assert_compiles 'TypeError', %q{ - def test(arr, idx) - arr[idx] = 7 - end - test([1,2,3], 0) - test([1,2,3], 0) - begin - test([1,2,3], "0") - rescue => e - e.class - end - }, call_threshold: 2 - end - - def test_empty_array_pop - assert_compiles 'nil', %q{ - def test(arr) = arr.pop - test([]) - test([]) - }, call_threshold: 2 - end - - def test_array_pop_no_arg - assert_compiles '42', %q{ - def test(arr) = arr.pop - test([32, 33, 42]) - test([32, 33, 42]) - }, call_threshold: 2 - end - - def test_array_pop_arg - assert_compiles '[33, 42]', %q{ - def test(arr) = arr.pop(2) - test([32, 33, 42]) - test([32, 33, 42]) - }, call_threshold: 2 - 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_new_range_fixnum_both_literals_inclusive - assert_compiles '1..2', %q{ - def test() - a = 2 - (1..a) - end - test; test - }, call_threshold: 2, insns: [:newrange] - end - - def test_new_range_fixnum_both_literals_exclusive - assert_compiles '1...2', %q{ - def test() - a = 2 - (1...a) - end - test; test - }, call_threshold: 2, insns: [:newrange] - end - - def test_new_range_fixnum_low_literal_inclusive - assert_compiles '1..3', %q{ - def test(a) - (1..a) - end - test(2); test(3) - }, call_threshold: 2, insns: [:newrange] - end - - def test_new_range_fixnum_low_literal_exclusive - assert_compiles '1...3', %q{ - def test(a) - (1...a) - end - test(2); test(3) - }, call_threshold: 2, insns: [:newrange] - end - - def test_new_range_fixnum_high_literal_inclusive - assert_compiles '3..10', %q{ - def test(a) - (a..10) - end - test(2); test(3) - }, call_threshold: 2, insns: [:newrange] - end - - def test_new_range_fixnum_high_literal_exclusive - assert_compiles '3...10', %q{ - def test(a) - (a...10) - end - test(2); test(3) - }, call_threshold: 2, insns: [:newrange] - 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. - # Using assert_runs again due to register spill. - # TODO: It should be fixed by register spill support. - assert_runs '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_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_getinstancevariable_miss - assert_compiles '[1, 1, 4]', %q{ - class C - def foo - @foo - end - - def foo_then_bar - @foo = 1 - @bar = 2 - end - - def bar_then_foo - @bar = 3 - @foo = 4 - end - end - - o1 = C.new - o1.foo_then_bar - result = [] - result << o1.foo - result << o1.foo - o2 = C.new - o2.bar_then_foo - result << o2.foo - result - } - end - - def test_setinstancevariable - assert_compiles '1', %q{ - def test() = @foo = 1 - - test() - @foo - } - end - - def test_getclassvariable - assert_compiles '42', %q{ - class Foo - def self.test = @@x - end - - Foo.class_variable_set(:@@x, 42) - Foo.test() - } - end - - def test_getclassvariable_raises - assert_compiles '"uninitialized class variable @@x in Foo"', %q{ - class Foo - def self.test = @@x - end - - begin - Foo.test - rescue NameError => e - e.message - end - } - end - - def test_setclassvariable - assert_compiles '42', %q{ - class Foo - def self.test = @@x = 42 - end - - Foo.test() - Foo.class_variable_get(:@@x) - } - end - - def test_setclassvariable_raises - assert_compiles '"can\'t modify frozen Class: Foo"', %q{ - class Foo - def self.test = @@x = 42 - freeze - end - - begin - Foo.test - rescue FrozenError => e - e.message - end - } - 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_getivar - 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] - end - - def test_attr_accessor_setivar - assert_compiles '[5, 5]', %q{ - class C - attr_accessor :foo - - def initialize - @foo = 4 - end - end - - def test(c) - c.foo = 5 - c.foo - end - - c = C.new - [test(c), test(c)] - }, call_threshold: 2, insns: [:opt_send_without_block] - end - - def test_attr_writer - assert_compiles '[5, 5]', %q{ - class C - attr_writer :foo - - def initialize - @foo = 4 - end - - def get_foo = @foo - end - - def test(c) - c.foo = 5 - c.get_foo - end - c = C.new - [test(c), test(c)] - }, call_threshold: 2, insns: [:opt_send_without_block] - end - def test_uncached_getconstant_path assert_compiles RUBY_COPYRIGHT.dump, %q{ def test = RUBY_COPYRIGHT @@ -3380,51 +171,6 @@ class TestZJIT < Test::Unit::TestCase }, call_threshold: 1, insns: [:opt_getconstant_path] end - def test_getconstant - assert_compiles '1', %q{ - class Foo - CONST = 1 - end - - def test(klass) - klass::CONST - end - - test(Foo) - test(Foo) - }, call_threshold: 2, insns: [:getconstant] - end - - def test_expandarray_no_splat - assert_compiles '[3, 4]', %q{ - def test(o) - a, b = o - [a, b] - end - test [3, 4] - }, call_threshold: 1, insns: [:expandarray] - end - - def test_expandarray_splat - assert_compiles '[3, [4]]', %q{ - def test(o) - a, *b = o - [a, b] - end - test [3, 4] - }, call_threshold: 1, insns: [:expandarray] - end - - def test_expandarray_splat_post - assert_compiles '[3, [4], 5]', %q{ - def test(o) - a, *b, c = o - [a, b, c] - end - test [3, 4, 5] - }, call_threshold: 1, insns: [:expandarray] - end - def test_getconstant_path_autoload # A constant-referencing expression can run arbitrary code through Kernel#autoload. Dir.mktmpdir('autoload') do |tmpdir| @@ -3439,78 +185,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'", @@ -3527,276 +201,6 @@ class TestZJIT < Test::Unit::TestCase }, call_threshold: 2 end - def test_bop_invalidation - assert_compiles '100', %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_method_raise - assert_compiles '[nil, nil, nil]', %q{ - class C - def assert_equal expected, actual - if expected != actual - raise "NO" - end - end - - def test_defined_method - assert_equal(nil, defined?("x".reverse(1).reverse)) - end - end - - c = C.new - result = [] - result << c.test_defined_method - result << c.test_defined_method - result << c.test_defined_method - result - } - 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. - - assert_compiles '[nil, nil, "yield"]', %q{ - def test - yield_self { yield_self { defined?(yield) } } - end - - [test, test, test{}] - }, call_threshold: 2 - end - - def test_block_given_p - assert_compiles "false", "block_given?" - assert_compiles '[false, false, true]', %q{ - def test = block_given? - [test, test, test{}] - }, call_threshold: 2, insns: [:opt_send_without_block] - end - - def test_block_given_p_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. - - assert_compiles '[false, false, true]', %q{ - def test - yield_self { yield_self { block_given? } } - end - - [test, test, test{}] - }, call_threshold: 2 - 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 @@ -3880,653 +284,6 @@ class TestZJIT < Test::Unit::TestCase } end - def test_profile_under_nested_jit_call - assert_compiles '[nil, nil, 3]', %q{ - def profile - 1 + 2 - end - - def jit_call(flag) - if flag - profile - end - end - - def entry(flag) - jit_call(flag) - end - - [entry(false), entry(false), entry(true)] - }, call_threshold: 2 - end - - def test_bop_redefined - 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_redefined_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 - end - - # ZJIT currently only generates a MethodRedefined patch point when the method - # is called on the top-level self. - def test_method_redefined_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 - - result2 = test - - [result1, result2] - }, call_threshold: 2 - end - - def test_method_redefined_with_module - assert_runs '["original", "redefined"]', %q{ - module Foo - def self.foo = "original" - end - - def test = Foo.foo - test - result1 = test - - def Foo.foo = "redefined" - result2 = test - - [result1, result2] - }, call_threshold: 2 - end - - def test_module_name_with_guard_passes - assert_compiles '"Integer"', %q{ - def test(mod) - mod.name - end - - test(String) - test(Integer) - }, call_threshold: 2 - 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 - end - - def test_objtostring_calls_to_s_on_non_strings - assert_compiles '["foo", "foo"]', %q{ - results = [] - - class Foo - def to_s - "foo" - end - end - - def test(str) - "#{str}" - end - - results << test(Foo.new) - results << test(Foo.new) - - results - } - 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 - end - - def test(foo) - "#{foo}" - end - - results << test("foo") - results << test("foo") - - results - } - 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 - end - - foo = StringSubclass.new("foo") - - def test(str) - "#{str}" - end - - results << test(foo) - results << test(foo) - - results - } - end - - def test_objtostring_profiled_string_fastpath - assert_compiles '"foo"', %q{ - def test(str) - "#{str}" - end - test('foo'); test('foo') # profile as string - }, call_threshold: 2 - end - - def test_objtostring_profiled_string_subclass_fastpath - assert_compiles '"foo"', %q{ - class MyString < String; end - - def test(str) - "#{str}" - end - - foo = MyString.new("foo") - test(foo); test(foo) # still profiles as string - }, call_threshold: 2 - end - - def test_objtostring_profiled_string_fastpath_exits_on_nonstring - assert_compiles '"1"', %q{ - def test(str) - "#{str}" - end - - test('foo') # profile as string - test(1) - }, call_threshold: 2 - end - - def test_objtostring_profiled_nonstring_calls_to_s - assert_compiles '"[1, 2, 3]"', %q{ - def test(str) - "#{str}" - end - - test([1,2,3]); # profile as nonstring - test([1,2,3]); - }, call_threshold: 2 - end - - def test_objtostring_profiled_nonstring_guard_exits_when_string - assert_compiles '"foo"', %q{ - def test(str) - "#{str}" - end - - test([1,2,3]); # profiles as nonstring - test('foo'); - }, call_threshold: 2 - end - - def test_string_bytesize_with_guard - assert_compiles '5', %q{ - def test(str) - str.bytesize - end - - test('hello') - test('world') - }, call_threshold: 2 - end - - def test_string_bytesize_multibyte - assert_compiles '4', %q{ - def test(s) - s.bytesize - end - - test("💎") - }, 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] - end - - def test_string_concat_empty - assert_compiles '""', %q{ - def test = "#{}" - - test - }, insns: [:concatstrings] - end - - def test_regexp_interpolation - assert_compiles '/123/', %q{ - def test = /#{1}#{2}#{3}/ - - test - }, insns: [:toregexp] - 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 - 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) - } - end - - def test_ivar_attr_reader_optimization_with_multi_ractor_mode - assert_compiles '42', %q{ - class Foo - class << self - attr_accessor :bar - - def get_bar - bar - rescue Ractor::IsolationError - 42 - end - end - end - - Foo.bar = [] # needs to be a ractor unshareable object - - def test - Foo.get_bar - end - - test - test - - Ractor.new { test }.value - }, call_threshold: 2 - end - - def test_ivar_get_with_multi_ractor_mode - assert_compiles '42', %q{ - class Foo - def self.set_bar - @bar = [] # needs to be a ractor unshareable object - end - - def self.bar - @bar - rescue Ractor::IsolationError - 42 - end - end - - Foo.set_bar - - def test - Foo.bar - end - - test - test - - Ractor.new { test }.value - }, call_threshold: 2 - end - - def test_ivar_get_with_already_multi_ractor_mode - assert_compiles '42', %q{ - class Foo - def self.set_bar - @bar = [] # needs to be a ractor unshareable object - end - - def self.bar - @bar - rescue Ractor::IsolationError - 42 - end - end - - Foo.set_bar - r = Ractor.new { - Ractor.receive - Foo.bar - } - - Foo.bar - Foo.bar - - r << :go - r.value - }, call_threshold: 2 - end - - def test_ivar_set_with_multi_ractor_mode - assert_compiles '42', %q{ - class Foo - def self.bar - _foo = 1 - _bar = 2 - begin - @bar = _foo + _bar - rescue Ractor::IsolationError - 42 - end - end - end - - def test - Foo.bar - end - - test - test - - Ractor.new { test }.value - } - end - - def test_struct_set - assert_compiles '[42, 42, :frozen_error]', %q{ - C = Struct.new(:foo).new(1) - - def test - C.foo = Object.new - 42 - end - - r = [test, test] - C.freeze - r << begin - test - rescue FrozenError - :frozen_error - end - }, call_threshold: 2 - end - - def test_global_tracepoint - assert_compiles 'true', %q{ - def foo = 1 - - foo - foo - - called = false - - tp = TracePoint.new(:return) { |event| - if event.method_id == :foo - called = true - end - } - tp.enable do - foo - end - called - } - end - - def test_local_tracepoint - assert_compiles 'true', %q{ - def foo = 1 - - foo - foo - - called = false - - tp = TracePoint.new(:return) { |_| called = true } - tp.enable(target: method(:foo)) do - foo - end - called - } - end - def test_line_tracepoint_on_c_method assert_compiles '"[[:line, true]]"', %q{ events = [] @@ -4573,203 +330,6 @@ class TestZJIT < Test::Unit::TestCase } end - def test_opt_case_dispatch - assert_compiles '[true, false]', %q{ - def test(x) - case x - when :foo - true - else - false - end - end - - results = [] - results << test(:foo) - results << test(1) - results - }, insns: [:opt_case_dispatch] - end - - def test_stack_overflow - assert_compiles 'nil', %q{ - def recurse(n) - return if n == 0 - recurse(n-1) - nil # no tail call - end - - recurse(2) - recurse(2) - begin - recurse(20_000) - rescue SystemStackError - # Not asserting an exception is raised here since main - # thread stack size is environment-sensitive. Only - # that we don't crash or infinite loop. - end - }, call_threshold: 2 - end - - def test_invokeblock - assert_compiles '42', %q{ - def test - yield - end - test { 42 } - }, insns: [:invokeblock] - end - - def test_invokeblock_with_args - assert_compiles '3', %q{ - def test(x, y) - yield x, y - end - test(1, 2) { |a, b| a + b } - }, insns: [:invokeblock] - end - - def test_invokeblock_no_block_given - assert_compiles ':error', %q{ - def test - yield rescue :error - end - test - }, insns: [:invokeblock] - end - - def test_invokeblock_multiple_yields - assert_compiles "[1, 2, 3]", %q{ - results = [] - def test - yield 1 - yield 2 - yield 3 - end - test { |x| results << x } - results - }, insns: [:invokeblock] - end - - def test_ccall_variadic_with_multiple_args - assert_compiles "[1, 2, 3]", %q{ - def test - a = [] - a.push(1, 2, 3) - a - end - - test - test - }, insns: [:opt_send_without_block] - end - - def test_ccall_variadic_with_no_args - assert_compiles "[1]", %q{ - def test - a = [1] - a.push - end - - test - test - }, insns: [:opt_send_without_block] - end - - def test_ccall_variadic_with_no_args_causing_argument_error - assert_compiles ":error", %q{ - def test - format - rescue ArgumentError - :error - end - - test - test - }, insns: [:opt_send_without_block] - end - - def test_allocating_in_hir_c_method_is - assert_compiles ":k", %q{ - # Put opt_new in a frame JIT code sets up that doesn't set cfp->pc - def a(f) = test(f) - def test(f) = (f.new if f) - # A parallel couple methods that will set PC at the same stack height - def second = third - def third = nil - - a(nil) - a(nil) - - class Foo - def self.new = :k - end - - second - - a(Foo) - }, call_threshold: 2, insns: [:opt_new] - end - - def test_singleton_class_invalidation_annotated_ccall - assert_compiles '[false, true]', %q{ - def define_singleton(obj, define) - if define - # Wrap in C method frame to avoid exiting JIT on defineclass - [nil].reverse_each do - class << obj - def ==(_) - true - end - end - end - end - false - end - - def test(define) - obj = BasicObject.new - # This == call gets compiled to a CCall - obj == define_singleton(obj, define) - end - - result = [] - result << test(false) # Compiles BasicObject#== - result << test(true) # Should use singleton#== now - result - }, call_threshold: 2 - end - - def test_singleton_class_invalidation_optimized_variadic_ccall - assert_compiles '[1, 1000]', %q{ - def define_singleton(arr, define) - if define - # Wrap in C method frame to avoid exiting JIT on defineclass - [nil].reverse_each do - class << arr - def push(x) - super(x * 1000) - end - end - end - end - 1 - end - - def test(define) - arr = [] - val = define_singleton(arr, define) - arr.push(val) # This CCall should be invalidated if singleton was defined - arr[0] - end - - result = [] - result << test(false) # Compiles Array#push as CCall - result << test(true) # Singleton defined, CCall should be invalidated - result - }, call_threshold: 2 - end - def test_regression_cfp_sp_set_correctly_before_leaf_gc_call assert_compiles ':ok', %q{ def check(l, r) @@ -4799,60 +359,6 @@ class TestZJIT < Test::Unit::TestCase }, call_threshold: 14, num_profiles: 5 end - def test_is_a_string_special_case - assert_compiles '[true, false, false, false, false, false]', %q{ - def test(x) - x.is_a?(String) - end - test("foo") - [test("bar"), test(1), test(false), test(:foo), test([]), test({})] - } - end - - def test_is_a_array_special_case - assert_compiles '[true, true, false, false, false, false, false]', %q{ - def test(x) - x.is_a?(Array) - end - test([]) - [test([1,2,3]), test([]), test(1), test(false), test(:foo), test("foo"), test({})] - } - end - - def test_is_a_hash_special_case - assert_compiles '[true, true, false, false, false, false, false]', %q{ - def test(x) - x.is_a?(Hash) - end - test({}) - [test({:a => "b"}), test({}), test(1), test(false), test(:foo), test([]), test("foo")] - } - end - - def test_is_a_hash_subclass - assert_compiles 'true', %q{ - class MyHash < Hash - end - def test(x) - x.is_a?(Hash) - end - test({}) - test(MyHash.new) - } - end - - def test_is_a_normal_case - assert_compiles '[true, false]', %q{ - class MyClass - end - def test(x) - x.is_a?(MyClass) - end - test("a") - [test(MyClass.new), test("a")] - } - end - def test_exit_tracing # This is a very basic smoke test. The StackProf format # this option generates is external to us. diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 29ea47c68e..c11610e26c 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -3104,35 +3104,5 @@ impl IseqCall { } #[cfg(test)] -mod tests { - use crate::codegen::MAX_ISEQ_VERSIONS; - use crate::cruby::test_utils::*; - use crate::payload::*; - - #[test] - fn test_max_iseq_versions() { - eval(&format!(" - TEST = -1 - def test = TEST - - # compile and invalidate MAX+1 times - i = 0 - while i < {MAX_ISEQ_VERSIONS} + 1 - test; test # compile a version - - Object.send(:remove_const, :TEST) - TEST = i - - i += 1 - end - ")); - - // It should not exceed MAX_ISEQ_VERSIONS - let iseq = get_method_iseq("self", "test"); - let payload = get_or_create_iseq_payload(iseq); - assert_eq!(payload.versions.len(), MAX_ISEQ_VERSIONS); - - // The last call should not discard the JIT code - assert!(matches!(unsafe { payload.versions.last().unwrap().as_ref() }.status, IseqStatus::Compiled(_))); - } -} +#[path = "codegen_tests.rs"] +mod tests; diff --git a/zjit/src/codegen_tests.rs b/zjit/src/codegen_tests.rs new file mode 100644 index 0000000000..ae5841ea7d --- /dev/null +++ b/zjit/src/codegen_tests.rs @@ -0,0 +1,4977 @@ +#![cfg(test)] + +use crate::codegen::MAX_ISEQ_VERSIONS; +use crate::cruby::*; +use crate::hir::tests::hir_build_tests::assert_contains_opcode; +use crate::payload::*; +use insta::assert_snapshot; + +#[test] +fn test_call_itself() { + assert_snapshot!(inspect(" + def test = 42.itself + test + test + "), @"42"); +} + +#[test] +fn test_nil() { + assert_snapshot!(inspect(" + def test = nil + test + test + "), @"nil"); +} + +#[test] +fn test_putobject() { + assert_snapshot!(inspect(" + def test = 1 + test + test + "), @"1"); +} + +#[test] +fn test_putstring() { + eval(r##" + def test = "#{""}" + test + "##); + assert_contains_opcode("test", YARVINSN_putstring); + assert_snapshot!(inspect(r##"test"##), @r#""""#); +} + +#[test] +fn test_putchilledstring() { + eval(r#" + def test = "" + test + "#); + assert_contains_opcode("test", YARVINSN_putchilledstring); + assert_snapshot!(inspect(r#"test"#), @r#""""#); +} + +#[test] +fn test_leave_param() { + assert_snapshot!(inspect(" + def test(n) = n + test(5) + test(5) + "), @"5"); +} + +#[test] +fn test_getglobal_with_warning() { + eval(r#" + Warning[:deprecated] = true + + module Warning + def warn(message) + raise + end + end + + def test + $= + rescue + "rescued" + end + $VERBOSE = true + test + "#); + assert_contains_opcode("test", YARVINSN_getglobal); + assert_snapshot!(inspect(r#"test"#), @r#""rescued""#); +} + +#[test] +fn test_setglobal() { + eval(" + def test + $a = 1 + $a + end + test + "); + assert_contains_opcode("test", YARVINSN_setglobal); + assert_snapshot!(inspect("test"), @"1"); +} + +#[test] +fn test_string_intern() { + eval(r#" + def test + :"foo#{123}" + end + test + "#); + assert_contains_opcode("test", YARVINSN_intern); + assert_snapshot!(inspect(r#"test"#), @":foo123"); +} + +#[test] +fn test_duphash() { + eval(" + def test + {a: 1} + end + test + "); + assert_contains_opcode("test", YARVINSN_duphash); + assert_snapshot!(inspect("test"), @"{a: 1}"); +} + +#[test] +fn test_pushtoarray() { + eval(" + def test + [*[], 1, 2, 3] + end + test + "); + assert_contains_opcode("test", YARVINSN_pushtoarray); + assert_snapshot!(inspect("test"), @"[1, 2, 3]"); +} + +#[test] +fn test_splatarray_new_array() { + eval(" + def test a + [*a, 3] + end + test [1, 2] + "); + assert_contains_opcode("test", YARVINSN_splatarray); + assert_snapshot!(inspect("test [1, 2]"), @"[1, 2, 3]"); +} + +#[test] +fn test_splatarray_existing_array() { + eval(" + def foo v + [1, 2, v] + end + def test a + foo(*a) + end + test [3] + "); + assert_contains_opcode("test", YARVINSN_splatarray); + assert_snapshot!(inspect("test [3]"), @"[1, 2, 3]"); +} + +#[test] +fn test_concattoarray() { + eval(" + def test(*a) + [1, 2, *a] + end + test 3 + "); + assert_contains_opcode("test", YARVINSN_concattoarray); + assert_snapshot!(inspect("test 3"), @"[1, 2, 3]"); +} + +#[test] +fn test_definedivar() { + eval(" + def test + v0 = defined?(@a) + @a = nil + v1 = defined?(@a) + remove_instance_variable :@a + v2 = defined?(@a) + [v0, v1, v2] + end + test + "); + assert_contains_opcode("test", YARVINSN_definedivar); + assert_snapshot!(inspect("test"), @r#"[nil, "instance-variable", nil]"#); +} + +#[test] +fn test_setglobal_with_trace_var_exception() { + eval(r#" + def test + $a = 1 + rescue + "rescued" + end + trace_var(:$a) { raise } + test + "#); + assert_contains_opcode("test", YARVINSN_setglobal); + assert_snapshot!(inspect(r#"test"#), @r#""rescued""#); +} + +#[test] +fn test_getlocal_after_eval() { + assert_snapshot!(inspect(" + def test + a = 1 + eval('a = 2') + a + end + test + test + "), @"2"); +} + +#[test] +fn test_getlocal_after_instance_eval() { + assert_snapshot!(inspect(" + def test + a = 1 + instance_eval('a = 2') + a + end + test + test + "), @"2"); +} + +#[test] +fn test_getlocal_after_module_eval() { + assert_snapshot!(inspect(" + def test + a = 1 + Kernel.module_eval('a = 2') + a + end + test + test + "), @"2"); +} + +#[test] +fn test_getlocal_after_class_eval() { + assert_snapshot!(inspect(" + def test + a = 1 + Kernel.class_eval('a = 2') + a + end + test + test + "), @"2"); +} + +#[test] +fn test_setlocal() { + assert_snapshot!(inspect(" + def test(n) + m = n + m + end + test(3) + test(3) + "), @"3"); +} + +#[test] +fn test_return_nonparam_local() { + assert_snapshot!(inspect(" + def foo(a) + if false + x = nil + end + x + end + def test = foo(1) + test + test + "), @"nil"); +} + +#[test] +fn test_nonparam_local_nil_in_jit_call() { + assert_snapshot!(inspect(r#" + def f(a) + a ||= 1 + if false; b = 1; end + eval("-> { p 'x#{b}' }") + end + + 4.times.map { f(1).call } + "#), @r#"["x", "x", "x", "x"]"#); +} + +#[test] +fn test_kwargs_with_exit_and_local_invalidation() { + assert_snapshot!(inspect(r#" + def a(b:, c:) + if c == :b + return -> {} + end + Class # invalidate locals + + raise "c is :b!" if c == :b + end + + def test + # note opposite order of kwargs + a(c: :c, b: :b) + end + + 4.times { test } + :ok + "#), @":ok"); +} + +#[test] +fn test_kwargs_with_max_direct_send_arg_count() { + assert_snapshot!(inspect(" + def kwargs(five, six, a:, b:, c:, d:, e:, f:) + [a, b, c, d, five, six, e, f] + end + + 5.times.flat_map do + [ + kwargs(5, 6, d: 4, c: 3, a: 1, b: 2, e: 7, f: 8), + kwargs(5, 6, d: 4, c: 3, b: 2, a: 1, e: 7, f: 8) + ] + end.uniq + "), @"[[1, 2, 3, 4, 5, 6, 7, 8]]"); +} + +#[test] +fn test_setlocal_on_eval() { + assert_snapshot!(inspect(" + @b = binding + eval('a = 1', @b) + eval('a', @b) + "), @"1"); +} + +#[test] +fn test_optional_arguments() { + assert_snapshot!(inspect(" + def test(a, b = 2, c = 3) + [a, b, c] + end + [test(1), test(10, 20), test(100, 200, 300)] + "), @"[[1, 2, 3], [10, 20, 3], [100, 200, 300]]"); +} + +#[test] +fn test_optional_arguments_setlocal() { + assert_snapshot!(inspect(" + def test(a = (b = 2)) + [a, b] + end + [test, test(1)] + "), @"[[2, 2], [1, nil]]"); +} + +#[test] +fn test_optional_arguments_cyclic() { + assert_snapshot!(inspect(" + test = proc { |a=a| a } + [test.call, test.call(1)] + "), @"[nil, 1]"); +} + +#[test] +fn test_getblockparamproxy() { + eval(" + def test(&block) + 0.then(&block) + end + test { 1 } + "); + assert_contains_opcode("test", YARVINSN_getblockparamproxy); + assert_snapshot!(inspect("test { 1 }"), @"1"); +} + +#[test] +fn test_getblockparam() { + eval(" + def test(&blk) + blk + end + test { 2 }.call + "); + assert_contains_opcode("test", YARVINSN_getblockparam); + assert_snapshot!(inspect("test { 2 }.call"), @"2"); +} + +#[test] +fn test_getblockparam_proxy_side_exit_restores_block_local() { + eval(" + def test(&block) + b = block + raise \"test\" unless block + b ? 2 : 3 + end + test {} + "); + assert_contains_opcode("test", YARVINSN_getblockparam); + assert_snapshot!(inspect("test {}"), @"2"); +} + +#[test] +fn test_getblockparam_used_twice_in_args() { + eval(" + def f(*args) = args + def test(&blk) + b = blk + f(*[1], blk) + blk + end + test {1}.call + "); + assert_contains_opcode("test", YARVINSN_getblockparam); + assert_snapshot!(inspect("test {1}.call"), @"1"); +} + +#[test] +fn test_optimized_method_call_proc_call() { + eval(" + def test(p) + p.call(1) + end + test(proc { |x| x * 2 }) + "); + assert_contains_opcode("test", YARVINSN_opt_send_without_block); + assert_snapshot!(inspect("test(proc { |x| x * 2 })"), @"2"); +} + +#[test] +fn test_optimized_method_call_proc_aref() { + eval(" + def test(p) + p[2] + end + test(proc { |x| x * 2 }) + "); + assert_contains_opcode("test", YARVINSN_opt_aref); + assert_snapshot!(inspect("test(proc { |x| x * 2 })"), @"4"); +} + +#[test] +fn test_optimized_method_call_proc_yield() { + eval(" + def test(p) + p.yield(3) + end + test(proc { |x| x * 2 }) + "); + assert_contains_opcode("test", YARVINSN_opt_send_without_block); + assert_snapshot!(inspect("test(proc { |x| x * 2 })"), @"6"); +} + +#[test] +fn test_optimized_method_call_proc_kw_splat() { + eval(" + def test(p, h) + p.call(**h) + end + test(proc { |**kw| kw[:a] + kw[:b] }, { a: 1, b: 2 }) + "); + assert_contains_opcode("test", YARVINSN_opt_send_without_block); + assert_snapshot!(inspect("test(proc { |**kw| kw[:a] + kw[:b] }, { a: 1, b: 2 })"), @"3"); +} + +#[test] +fn test_optimized_method_call_proc_call_splat() { + assert_snapshot!(inspect(" + p = proc { |x| x + 1 } + def test(p) + ary = [42] + p.call(*ary) + end + test(p) + test(p) + "), @"43"); +} + +#[test] +fn test_optimized_method_call_proc_call_kwarg() { + assert_snapshot!(inspect(" + p = proc { |a:| a } + def test(p) + p.call(a: 1) + end + test(p) + test(p) + "), @"1"); +} + +#[test] +fn test_setlocal_on_eval_with_spill() { + assert_snapshot!(inspect(" + @b = binding + eval('a = 1; itself', @b) + eval('a', @b) + "), @"1"); +} + +#[test] +fn test_nested_local_access() { + assert_snapshot!(inspect(" + 1.times do |l2| + 1.times do |l1| + define_method(:test) do + l1 = 1 + l2 = 2 + l3 = 3 + [l1, l2, l3] + end + end + end + + test + test + test + "), @"[1, 2, 3]"); +} + +#[test] +fn test_send_with_local_written_by_blockiseq() { + assert_snapshot!(inspect(" + def test + l1 = nil + l2 = nil + tap do |_| + l1 = 1 + tap do |_| + l2 = 2 + end + end + + [l1, l2] + end + + test + test + "), @"[1, 2]"); +} + +#[test] +fn test_send_without_block() { + assert_snapshot!(inspect(" + 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] + "), @"[1, 2, 3]"); +} + +#[test] +fn test_send_with_six_args() { + assert_snapshot!(inspect(" + def foo(a1, a2, a3, a4, a5, a6) + [a1, a2, a3, a4, a5, a6] + end + + def test + foo(1, 2, 3, 4, 5, 6) + end + + test # profile send + test + "), @"[1, 2, 3, 4, 5, 6]"); +} + +#[test] +fn test_send_optional_arguments() { + assert_snapshot!(inspect(" + def test(a, b = 2) = [a, b] + def entry = [test(1), test(3, 4)] + entry + entry + "), @"[[1, 2], [3, 4]]"); +} + +#[test] +fn test_send_nil_block_arg() { + assert_snapshot!(inspect(" + def test = block_given? + def entry = test(&nil) + test + test + "), @"false"); +} + +#[test] +fn test_send_symbol_block_arg() { + assert_snapshot!(inspect(" + def test = [1, 2].map(&:to_s) + test + test + "), @r#"["1", "2"]"#); +} + +#[test] +fn test_send_variadic_with_block() { + assert_snapshot!(inspect(" + A = [1, 2, 3] + B = [\"a\", \"b\", \"c\"] + + def test + result = [] + A.zip(B) { |x, y| result << [x, y] } + result + end + + test; test + "), @r#"[[1, "a"], [2, "b"], [3, "c"]]"#); +} + +#[test] +fn test_send_kwarg_optional() { + assert_snapshot!(inspect(" + def test(a: 1, b: 2) = [a, b] + def entry = test + entry + entry + "), @"[1, 2]"); +} + +#[test] +fn test_send_kwarg_optional_too_many() { + assert_snapshot!(inspect(" + def test(a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10) = [a, b, c, d, e, f, g, h, i, j] + def entry = test + entry + entry + "), @"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"); +} + +#[test] +fn test_send_kwarg_required_and_optional() { + assert_snapshot!(inspect(" + def test(a:, b: 2) = [a, b] + def entry = test(a: 3) + entry + entry + "), @"[3, 2]"); +} + +#[test] +fn test_send_kwarg_to_hash() { + assert_snapshot!(inspect(" + def test(hash) = hash + def entry = test(a: 3) + entry + entry + "), @"{a: 3}"); +} + +#[test] +fn test_send_kwarg_to_ccall() { + assert_snapshot!(inspect(r#" + def test(s) = s.each_line(chomp: true).to_a + def entry = test(%(a\nb\nc)) + entry + entry + "#), @r#"["a", "b", "c"]"#); +} + +#[test] +fn test_send_kwarg_and_block_to_ccall() { + assert_snapshot!(inspect(r#" + def test(s) + a = [] + s.each_line(chomp: true) { |l| a << l } + a + end + def entry = test(%(a\nb\nc)) + entry + entry + "#), @r#"["a", "b", "c"]"#); +} + +#[test] +fn test_send_kwarg_with_too_many_args_to_c_call() { + assert_snapshot!(inspect(r#" + def test(a:, b:, c:, d:, e:) = sprintf("%s %s %s %s %s", a, b, c, d, kwargs: e) + def entry = test(e: :e, d: :d, c: :c, a: :a, b: :b) + entry + entry + "#), @r#""a b c d {kwargs: :e}""#); +} + +#[test] +fn test_send_kwsplat() { + assert_snapshot!(inspect(" + def test(a:) = a + def entry = test(**{a: 3}) + entry + entry + "), @"3"); +} + +#[test] +fn test_send_kwrest() { + assert_snapshot!(inspect(" + def test(**kwargs) = kwargs + def entry = test(a: 3) + entry + entry + "), @"{a: 3}"); +} + +#[test] +fn test_send_req_kwreq() { + assert_snapshot!(inspect(" + def test(a, c:) = [a, c] + def entry = test(1, c: 3) + entry + entry + "), @"[1, 3]"); +} + +#[test] +fn test_send_req_opt_kwreq() { + assert_snapshot!(inspect(" + def test(a, b = 2, c:) = [a, b, c] + def entry = [test(1, c: 3), test(-1, -2, c: -3)] + entry + entry + "), @"[[1, 2, 3], [-1, -2, -3]]"); +} + +#[test] +fn test_send_req_opt_kwreq_kwopt() { + assert_snapshot!(inspect(" + def test(a, b = 2, c:, d: 4) = [a, b, c, d] + def entry = [test(1, c: 3), test(-1, -2, d: -4, c: -3)] + entry + entry + "), @"[[1, 2, 3, 4], [-1, -2, -3, -4]]"); +} + +#[test] +fn test_send_unexpected_keyword() { + assert_snapshot!(inspect(" + def test(a: 1) = a*2 + def entry + test(z: 2) + rescue ArgumentError + :error + end + + entry + entry + "), @":error"); +} + +#[test] +fn test_pos_optional_with_maybe_too_many_args() { + assert_snapshot!(inspect(" + def target(a = 1, b = 2, c = 3, d = 4, e = 5, f:) = [a, b, c, d, e, f] + def test = [target(f: 6), target(10, 20, 30, f: 6), target(10, 20, 30, 40, 50, f: 60)] + test + test + "), @"[[1, 2, 3, 4, 5, 6], [10, 20, 30, 4, 5, 6], [10, 20, 30, 40, 50, 60]]"); +} + +#[test] +fn test_send_kwarg_partial_optional() { + assert_snapshot!(inspect(" + def test(a: 1, b: 2, c: 3) = [a, b, c] + def entry = [test, test(b: 20), test(c: 30, a: 10)] + entry + entry + "), @"[[1, 2, 3], [1, 20, 3], [10, 2, 30]]"); +} + +#[test] +fn test_send_kwarg_optional_a_lot() { + assert_snapshot!(inspect(" + def test(a: 1, b: 2, c: 3, d: 4, e: 5, f: 6) = [a, b, c, d, e, f] + def entry = [test, test(d: 7, f: 9, e: 8), test(f: 12, e: 10, d: 8, c: 6, b: 4, a: 2)] + entry + entry + "), @"[[1, 2, 3, 4, 5, 6], [1, 2, 3, 7, 8, 9], [2, 4, 6, 8, 10, 12]]"); +} + +#[test] +fn test_send_kwarg_non_constant_default() { + assert_snapshot!(inspect(" + def make_default = 2 + def test(a: 1, b: make_default) = [a, b] + def entry = [test, test(a: 10)] + entry + entry + "), @"[[1, 2], [10, 2]]"); +} + +#[test] +fn test_send_kwarg_optional_static_with_side_exit() { + assert_snapshot!(inspect(" + def callee(a: 1, b: 2) + x = binding.local_variable_get(:a) + [a, b, x] + end + + def entry + callee(a: 10) + end + + entry + entry + "), @"[10, 2, 10]"); +} + +#[test] +fn test_send_all_arg_types() { + assert_snapshot!(inspect(" + def test(a, b = :opt, c, d:, e: :kwo) = [a, b, c, d, e, block_given?] + def entry = test(:req, :post, d: :kwr) {} + entry + entry + "), @"[:req, :opt, :post, :kwr, :kwo, true]"); +} + +#[test] +fn test_send_ccall_variadic_with_different_receiver_classes() { + assert_snapshot!(inspect(r#" + def test(obj) = obj.start_with?("a") + [test("abc"), test(:abc)] + "#), @"[true, true]"); +} + +#[test] +fn test_forwardable_iseq() { + assert_snapshot!(inspect(" + def test(...) = 1 + test + test + "), @"1"); +} + +#[test] +fn test_sendforward() { + eval(" + def callee(a, b) = [a, b] + def test(...) = callee(...) + test(1, 2) + "); + assert_contains_opcode("test", YARVINSN_sendforward); + assert_snapshot!(inspect("test(1, 2)"), @"[1, 2]"); +} + +#[test] +fn test_iseq_with_optional_arguments() { + assert_snapshot!(inspect(" + def test(a, b = 2) = [a, b] + [test(1), test(3, 4)] + "), @"[[1, 2], [3, 4]]"); +} + +#[test] +fn test_invokesuper() { + assert_snapshot!(inspect(" + class Foo + def foo(a) = a + 1 + def bar(a) = a + 10 + end + + class Bar < Foo + def foo(a) = super(a) + 2 + def bar(a) = super + 20 + end + + bar = Bar.new + [bar.foo(3), bar.bar(30)] + "), @"[6, 60]"); +} + +#[test] +fn test_invokesuper_to_iseq() { + assert_snapshot!(inspect(r#" + class A + def foo + "A" + end + end + + class B < A + def foo + ["B", super] + end + end + + def test + B.new.foo + end + + test # profile invokesuper + test # compile + run compiled code + "#), @r#"["B", "A"]"#); +} + +#[test] +fn test_invokesuper_with_args() { + assert_snapshot!(inspect(r#" + class A + def foo(x) + x * 2 + end + end + + class B < A + def foo(x) + ["B", super(x) + 1] + end + end + + def test + B.new.foo(5) + end + + test # profile invokesuper + test # compile + run compiled code + "#), @r#"["B", 11]"#); +} + +#[test] +fn test_invokesuper_with_args_to_rest_param() { + assert_snapshot!(inspect(r#" + class A + def foo(x, *rest) + [x, rest] + end + end + + class B < A + def foo(x, y, z) + ["B", *super(x, y, z)] + end + end + + def test + B.new.foo("a", "b", "c") + end + + test # profile invokesuper + test # compile + run compiled code + "#), @r#"["B", "a", ["b", "c"]]"#); +} + +#[test] +fn test_invokesuper_with_block() { + assert_snapshot!(inspect(r#" + class A + def foo + block_given? ? yield : "no_block" + end + end + + class B < A + def foo + ["B", super { "from_block" }] + end + end + + def test + B.new.foo + end + + test # profile invokesuper + test # compile + run compiled code + "#), @r#"["B", "from_block"]"#); +} + +#[test] +fn test_invokesuper_to_cfunc_no_args() { + assert_snapshot!(inspect(r#" + class MyString < String + def length + ["MyString", super] + end + end + + def test + MyString.new("abc").length + end + + test # profile invokesuper + test # compile + run compiled code + "#), @r#"["MyString", 3]"#); +} + +#[test] +fn test_invokesuper_to_cfunc_simple_args() { + assert_snapshot!(inspect(r#" + class MyString < String + def include?(other) + ["MyString", super(other)] + end + end + + def test + MyString.new("abc").include?("bc") + end + + test # profile invokesuper + test # compile + run compiled code + "#), @r#"["MyString", true]"#); +} + +#[test] +fn test_invokesuper_to_cfunc_with_optional_arg() { + assert_snapshot!(inspect(r#" + class MyString < String + def byteindex(needle, offset = 0) + ["MyString", super(needle, offset)] + end + end + + def test + MyString.new("hello world").byteindex("world") + end + + test # profile invokesuper + test # compile + run compiled code + "#), @r#"["MyString", 6]"#); +} + +#[test] +fn test_invokesuper_to_cfunc_varargs() { + assert_snapshot!(inspect(r#" + class MyString < String + def end_with?(str) + ["MyString", super(str)] + end + end + + def test + MyString.new("abc").end_with?("bc") + end + + test # profile invokesuper + test # compile + run compiled code + "#), @r#"["MyString", true]"#); +} + +#[test] +fn test_invokesuper_multilevel() { + assert_snapshot!(inspect(r#" + class A + def foo + "A" + end + end + + class B < A + def foo + ["B", super] + end + end + + class C < B + def foo + ["C", super] + end + end + + def test + C.new.foo + end + + test # profile invokesuper + test # compile + run compiled code + "#), @r#"["C", ["B", "A"]]"#); +} + +#[test] +fn test_invokesuper_forwards_block_implicitly() { + assert_snapshot!(inspect(r#" + class A + def foo + block_given? ? yield : "no_block" + end + end + + class B < A + def foo + ["B", super] # should forward the block from caller + end + end + + def test + B.new.foo { "forwarded_block" } + end + + test # profile invokesuper + test # compile + run compiled code + "#), @r#"["B", "forwarded_block"]"#); +} + +#[test] +fn test_invokesuper_forwards_block_implicitly_with_args() { + assert_snapshot!(inspect(r#" + class A + def foo(x) + [x, (block_given? ? yield : "no_block")] + end + end + + class B < A + def foo(x) + ["B", super(x)] # explicit args, but block should still be forwarded + end + end + + def test + B.new.foo("arg_value") { "forwarded" } + end + + test # profile + test # compile + run compiled code + "#), @r#"["B", ["arg_value", "forwarded"]]"#); +} + +#[test] +fn test_invokesuper_forwards_block_implicitly_no_block_given() { + assert_snapshot!(inspect(r#" + class A + def foo + block_given? ? yield : "no_block" + end + end + + class B < A + def foo + ["B", super] # no block given by caller + end + end + + def test + B.new.foo # called without a block + end + + test # profile + test # compile + run compiled code + "#), @r#"["B", "no_block"]"#); +} + +#[test] +fn test_invokesuper_forwards_block_implicitly_multilevel() { + assert_snapshot!(inspect(r#" + class A + def foo + block_given? ? yield : "no_block" + end + end + + class B < A + def foo + ["B", super] # forwards block to A + end + end + + class C < B + def foo + ["C", super] # forwards block to B, which forwards to A + end + end + + def test + C.new.foo { "deep_block" } + end + + test # profile + test # compile + run compiled code + "#), @r#"["C", ["B", "deep_block"]]"#); +} + +#[test] +fn test_invokesuper_forwards_block_param() { + assert_snapshot!(inspect(r#" + class A + def foo + block_given? ? yield : "no_block" + end + end + + class B < A + def foo(&block) + ["B", super] # should forward &block implicitly + end + end + + def test + B.new.foo { "block_param_forwarded" } + end + + test # profile + test # compile + run compiled code + "#), @r#"["B", "block_param_forwarded"]"#); +} + +#[test] +fn test_invokesuper_with_blockarg() { + assert_snapshot!(inspect(r#" + class A + def foo + block_given? ? yield : "no block" + end + end + + class B < A + def foo(&blk) + other_block = proc { "different block" } + ["B", super(&other_block)] + end + end + + def test + B.new.foo { "passed block" } + end + + test # profile + test # compile + run compiled code + "#), @r#"["B", "different block"]"#); +} + +#[test] +fn test_invokesuper_with_symbol_to_proc() { + assert_snapshot!(inspect(r#" + class A + def foo(items, &blk) + items.map(&blk) + end + end + + class B < A + def foo(items) + ["B", super(items, &:succ)] + end + end + + def test + B.new.foo([2, 4, 6]) + end + + test # profile + test # compile + run compiled code + "#), @r#"["B", [3, 5, 7]]"#); +} + +#[test] +fn test_invokesuper_with_splat() { + assert_snapshot!(inspect(r#" + class A + def foo(a, b, c) + a + b + c + end + end + + class B < A + def foo(*args) + ["B", super(*args)] + end + end + + def test + B.new.foo(1, 2, 3) + end + + test # profile + test # compile + run compiled code + "#), @r#"["B", 6]"#); +} + +#[test] +fn test_invokesuper_with_kwargs() { + assert_snapshot!(inspect(r#" + class A + def foo(x:, y:) + "x=#{x}, y=#{y}" + end + end + + class B < A + def foo(x:, y:) + ["B", super(x: x, y: y)] + end + end + + def test + B.new.foo(x: 1, y: 2) + end + + test # profile + test # compile + run compiled code + "#), @r#"["B", "x=1, y=2"]"#); +} + +#[test] +fn test_invokesuper_with_kw_splat() { + assert_snapshot!(inspect(r#" + class A + def foo(x:, y:) + "x=#{x}, y=#{y}" + end + end + + class B < A + def foo(**kwargs) + ["B", super(**kwargs)] + end + end + + def test + B.new.foo(x: 1, y: 2) + end + + test # profile + test # compile + run compiled code + "#), @r#"["B", "x=1, y=2"]"#); +} + +#[test] +fn test_invokesuper_with_include() { + assert_snapshot!(inspect(r#" + class A + def foo + "A" + end + end + + class B < A + def foo + ["B", super] + end + end + + def test + B.new.foo + end + + test # profile invokesuper (super -> A#foo) + test # compile with super -> A#foo + + # Now include a module in B that defines foo - super should go to M#foo instead + module M + def foo + "M" + end + end + B.include(M) + + test # should call M#foo, not A#foo + "#), @r#"["B", "M"]"#); +} + +#[test] +fn test_invokesuper_with_prepend() { + assert_snapshot!(inspect(r#" + class A + def foo + "A" + end + end + + class B < A + def foo + ["B", super] + end + end + + def test + B.new.foo + end + + test # profile invokesuper (super -> A#foo) + test # compile with super -> A#foo + + # Now prepend a module that defines foo - super should go to M#foo instead + module M + def foo + "M" + end + end + A.prepend(M) + + test # should call M#foo, not A#foo + "#), @r#"["B", "M"]"#); +} + +#[test] +fn test_invokesuper_with_keyword_args() { + assert_snapshot!(inspect(r#" + class A + def foo(attributes = {}) + @attributes = attributes + end + end + + class B < A + def foo(content = '') + super(content: content) + end + end + + def test + B.new.foo("image data") + end + + test + test + "#), @r#"{content: "image data"}"#); +} + +#[test] +fn test_invokesuper_with_optional_keyword_args() { + assert_snapshot!(inspect(" + class Parent + def foo(a, b: 2, c: 3) = [a, b, c] + end + + class Child < Parent + def foo(a) = super(a) + end + + def test = Child.new.foo(1) + + test + test + "), @"[1, 2, 3]"); +} + +#[test] +fn test_invokesuperforward() { + assert_snapshot!(inspect(" + class A + def foo(a,b,c) = [a,b,c] + end + + class B < A + def foo(...) = super + end + + def test + B.new.foo(1, 2, 3) + end + + test + test + "), @"[1, 2, 3]"); +} + +#[test] +fn test_invokesuperforward_with_args_kwargs_and_block() { + assert_snapshot!(inspect(" + class A + def foo(*args, **kwargs, &block) + [args, kwargs, block&.call] + end + end + + class B < A + def foo(...) = super + end + + def test + B.new.foo(1, 2, x: 3) { 4 } + end + + test + test + "), @"[[1, 2], {x: 3}, 4]"); +} + +#[test] +fn test_send_with_non_constant_keyword_default() { + assert_snapshot!(inspect(" + def dbl(x = 1) = x * 2 + + def foo(a: dbl, b: dbl(2), c: dbl(2 ** 3)) + [a, b, c] + end + + def test + [ + foo, + foo(a: 10), + foo(b: 20), + foo(c: 30), + foo(a: 10, b: 20, c: 30) + ] + end + + test + test + "), @"[[2, 4, 16], [10, 4, 16], [2, 20, 16], [2, 4, 30], [10, 20, 30]]"); +} + +#[test] +fn test_send_with_non_constant_keyword_default_not_evaluated_when_provided() { + assert_snapshot!(inspect(" + def foo(a: raise, b: raise, c: raise) + [a, b, c] + end + + def test + foo(a: 1, b: 2, c: 3) + end + + test + test + "), @"[1, 2, 3]"); +} + +#[test] +fn test_send_with_non_constant_keyword_default_evaluated_when_not_provided() { + assert_snapshot!(inspect(r#" + def raise_a = raise "a" + def raise_b = raise "b" + def raise_c = raise "c" + + def foo(a: raise_a, b: raise_b, c: raise_c) + [a, b, c] + end + + def test_a + foo(b: 2, c: 3) + rescue RuntimeError => e + e.message + end + + def test_b + foo(a: 1, c: 3) + rescue RuntimeError => e + e.message + end + + def test_c + foo(a: 1, b: 2) + rescue RuntimeError => e + e.message + end + + def test + [test_a, test_b, test_c] + end + + test + test + "#), @r#"["a", "b", "c"]"#); +} + +#[test] +fn test_send_with_non_constant_keyword_default_jit_to_jit() { + assert_snapshot!(inspect(" + def make_default(x) = x * 2 + + def callee(a: make_default(1), b: make_default(2), c: make_default(3)) + [a, b, c] + end + + def caller_method + callee + end + + # Warm up callee first so it gets JITted + callee + callee + + # Now warm up caller - this creates JIT-to-JIT call + caller_method + caller_method + "), @"[2, 4, 6]"); +} + +#[test] +fn test_send_with_non_constant_keyword_default_side_exit() { + assert_snapshot!(inspect(" + def make_b = 2 + + def callee(a: 1, b: make_b, c: 3) + x = binding.local_variable_get(:a) + y = binding.local_variable_get(:b) + z = binding.local_variable_get(:c) + [x, y, z] + end + + def test + callee(a: 10, c: 30) + end + + test + test + "), @"[10, 2, 30]"); +} + +#[test] +fn test_send_with_non_constant_keyword_default_evaluation_order() { + assert_snapshot!(inspect(r#" + def log(x) + $order << x + x + end + + def foo(a: log("a"), b: log("b"), c: log("c")) + [a, b, c] + end + + def test + results = [] + + $order = [] + foo + results << $order.dup + + $order = [] + foo(a: "A") + results << $order.dup + + $order = [] + foo(b: "B") + results << $order.dup + + $order = [] + foo(c: "C") + results << $order.dup + + results + end + + test + test + "#), @r#"[["a", "b", "c"], ["b", "c"], ["a", "c"], ["a", "b"]]"#); +} + +#[test] +fn test_send_with_too_many_non_constant_keyword_defaults() { + assert_snapshot!(inspect(" + def many_kwargs( k1: 1, k2: 2, k3: 3, k4: 4, k5: 5, k6: 6, k7: 7, k8: 8, k9: 9, k10: 10, k11: 11, k12: 12, k13: 13, k14: 14, k15: 15, k16: 16, k17: 17, k18: 18, k19: 19, k20: 20, k21: 21, k22: 22, k23: 23, k24: 24, k25: 25, k26: 26, k27: 27, k28: 28, k29: 29, k30: 30, k31: 31, k32: 32, k33: 33, k34: k33 + 1) = k1 + k34 + def t = many_kwargs + t + t + "), @"35"); +} + +#[test] +fn test_invokebuiltin_delegate() { + assert_snapshot!(inspect(" + def test = [].clone(freeze: true) + r = test + r2 = test + [r2, r2.frozen?] + "), @"[[], true]"); +} + +#[test] +fn test_opt_plus_const() { + assert_snapshot!(inspect(" + def test = 1 + 2 + test # profile opt_plus + test + "), @"3"); +} + +#[test] +fn test_opt_plus_fixnum() { + assert_snapshot!(inspect(" + def test(a, b) = a + b + test(0, 1) # profile opt_plus + test(1, 2) + "), @"3"); +} + +#[test] +fn test_opt_plus_chain() { + assert_snapshot!(inspect(" + def test(a, b, c) = a + b + c + test(0, 1, 2) # profile opt_plus + test(1, 2, 3) + "), @"6"); +} + +#[test] +fn test_opt_plus_left_imm() { + assert_snapshot!(inspect(" + def test(a) = 1 + a + test(1) # profile opt_plus + test(2) + "), @"3"); +} + +#[test] +fn test_opt_plus_type_guard_exit() { + assert_snapshot!(inspect(" + def test(a) = 1 + a + test(1) # profile opt_plus + [test(2), test(2.0)] + "), @"[3, 3.0]"); +} + +#[test] +fn test_opt_plus_type_guard_exit_with_locals() { + assert_snapshot!(inspect(" + def test(a) + local = 3 + 1 + a + local + end + test(1) # profile opt_plus + [test(2), test(2.0)] + "), @"[6, 6.0]"); +} + +#[test] +fn test_opt_plus_type_guard_nested_exit() { + assert_snapshot!(inspect(" + 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)] + "), @"[4, 4.0]"); +} + +#[test] +fn test_opt_plus_type_guard_nested_exit_with_locals() { + assert_snapshot!(inspect(" + 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)] + "), @"[9, 9.0]"); +} + +#[test] +fn test_opt_minus() { + assert_snapshot!(inspect(" + def test(a, b) = a - b + test(2, 1) # profile opt_minus + test(6, 4) + "), @"2"); +} + +#[test] +fn test_opt_mult() { + assert_snapshot!(inspect(" + def test(a, b) = a * b + test(1, 2) # profile opt_mult + test(2, 3) + "), @"6"); +} + +#[test] +fn test_opt_mult_overflow() { + assert_snapshot!(inspect(" + 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] + "), @"[6, -6, 9671406556917033397649408, -9671406556917033397649408, 21267647932558653966460912964485513216]"); +} + +#[test] +fn test_opt_eq() { + eval(" + def test(a, b) = a == b + test(0, 2) # profile opt_eq + "); + assert_contains_opcode("test", YARVINSN_opt_eq); + assert_snapshot!(inspect("[test(1, 1), test(0, 1)]"), @"[true, false]"); +} + +#[test] +fn test_opt_eq_with_minus_one() { + eval(" + def test(a) = a == -1 + test(1) # profile opt_eq + "); + assert_contains_opcode("test", YARVINSN_opt_eq); + assert_snapshot!(inspect("[test(0), test(-1)]"), @"[false, true]"); +} + +#[test] +fn test_opt_neq_dynamic() { + eval(" + def test(a, b) = a != b + test(0, 2) # profile opt_neq + "); + assert_contains_opcode("test", YARVINSN_opt_neq); + assert_snapshot!(inspect("[test(1, 1), test(0, 1)]"), @"[false, true]"); +} + +#[test] +fn test_opt_neq_fixnum() { + assert_snapshot!(inspect(" + def test(a, b) = a != b + test(0, 2) # profile opt_neq + [test(1, 1), test(0, 1)] + "), @"[false, true]"); +} + +#[test] +fn test_opt_lt() { + eval(" + def test(a, b) = a < b + test(2, 3) # profile opt_lt + "); + assert_contains_opcode("test", YARVINSN_opt_lt); + assert_snapshot!(inspect("[test(0, 1), test(0, 0), test(1, 0)]"), @"[true, false, false]"); +} + +#[test] +fn test_opt_lt_with_literal_lhs() { + eval(" + def test(n) = 2 < n + test(2) # profile opt_lt + "); + assert_contains_opcode("test", YARVINSN_opt_lt); + assert_snapshot!(inspect("[test(1), test(2), test(3)]"), @"[false, false, true]"); +} + +#[test] +fn test_opt_le() { + eval(" + def test(a, b) = a <= b + test(2, 3) # profile opt_le + "); + assert_contains_opcode("test", YARVINSN_opt_le); + assert_snapshot!(inspect("[test(0, 1), test(0, 0), test(1, 0)]"), @"[true, true, false]"); +} + +#[test] +fn test_opt_gt() { + eval(" + def test(a, b) = a > b + test(2, 3) # profile opt_gt + "); + assert_contains_opcode("test", YARVINSN_opt_gt); + assert_snapshot!(inspect("[test(0, 1), test(0, 0), test(1, 0)]"), @"[false, false, true]"); +} + +#[test] +fn test_opt_empty_p() { + eval(" + def test(x) = x.empty? + "); + assert_contains_opcode("test", YARVINSN_opt_empty_p); + assert_snapshot!(inspect("[test([1]), test(\"1\"), test({})]"), @"[false, false, true]"); +} + +#[test] +fn test_opt_succ() { + eval(" + def test(obj) = obj.succ + "); + assert_contains_opcode("test", YARVINSN_opt_succ); + assert_snapshot!(inspect(r#"[test(-1), test("A")]"#), @r#"[0, "B"]"#); +} + +#[test] +fn test_opt_and() { + eval(" + def test(x, y) = x & y + "); + assert_contains_opcode("test", YARVINSN_opt_and); + assert_snapshot!(inspect("[test(0b1101, 3), test([3, 2, 1, 4], [8, 1, 2, 3])]"), @"[1, [3, 2, 1]]"); +} + +#[test] +fn test_opt_or() { + eval(" + def test(x, y) = x | y + "); + assert_contains_opcode("test", YARVINSN_opt_or); + assert_snapshot!(inspect("[test(0b1000, 3), test([3, 2, 1], [1, 2, 3])]"), @"[11, [3, 2, 1]]"); +} + +#[test] +fn test_fixnum_and() { + eval(" + def test(a, b) = a & b + "); + assert_contains_opcode("test", YARVINSN_opt_and); + assert_snapshot!(inspect(" + [ + test(5, 3), + test(0b011, 0b110), + test(-0b011, 0b110) + ] + "), @"[1, 2, 4]"); +} + +#[test] +fn test_fixnum_and_side_exit() { + eval(" + def test(a, b) = a & b + "); + assert_contains_opcode("test", YARVINSN_opt_and); + assert_snapshot!(inspect(" + [ + test(2, 2), + test(0b011, 0b110), + test(true, false) + ] + "), @"[2, 2, false]"); +} + +#[test] +fn test_fixnum_or() { + eval(" + def test(a, b) = a | b + "); + assert_contains_opcode("test", YARVINSN_opt_or); + assert_snapshot!(inspect(" + [ + test(5, 3), + test(1, 2), + test(1, -4) + ] + "), @"[7, 3, -3]"); +} + +#[test] +fn test_fixnum_or_side_exit() { + eval(" + def test(a, b) = a | b + "); + assert_contains_opcode("test", YARVINSN_opt_or); + assert_snapshot!(inspect(" + [ + test(1, 2), + test(2, 2), + test(true, false) + ] + "), @"[3, 2, true]"); +} + +#[test] +fn test_fixnum_xor() { + assert_snapshot!(inspect(" + def test(a, b) = a ^ b + [ + test(5, 3), + test(-5, 3), + test(1, 2) + ] + "), @"[6, -8, 3]"); +} + +#[test] +fn test_fixnum_xor_side_exit() { + assert_snapshot!(inspect(" + def test(a, b) = a ^ b + [ + test(5, 3), + test(5, 3), + test(true, false) + ] + "), @"[6, 6, true]"); +} + +#[test] +fn test_fixnum_mul() { + eval(" + C = 3 + def test(n) = C * n + test(4) + test(4) + "); + assert_contains_opcode("test", YARVINSN_opt_mult); + assert_snapshot!(inspect("test(4)"), @"12"); +} + +#[test] +fn test_fixnum_div() { + eval(" + C = 48 + def test(n) = C / n + test(4) + "); + assert_contains_opcode("test", YARVINSN_opt_div); + assert_snapshot!(inspect("test(4)"), @"12"); +} + +#[test] +fn test_fixnum_floor() { + eval(" + C = 3 + def test(n) = C / n + test(4) + "); + assert_contains_opcode("test", YARVINSN_opt_div); + assert_snapshot!(inspect("test(4)"), @"0"); +} + +#[test] +fn test_opt_not() { + eval(" + def test(obj) = !obj + "); + assert_contains_opcode("test", YARVINSN_opt_not); + assert_snapshot!(inspect("[test(nil), test(false), test(0)]"), @"[true, true, false]"); +} + +#[test] +fn test_opt_regexpmatch2() { + eval(" + def test(haystack) = /needle/ =~ haystack + "); + assert_contains_opcode("test", YARVINSN_opt_regexpmatch2); + assert_snapshot!(inspect(r#"[test("kneedle"), test("")]"#), @"[1, nil]"); +} + +#[test] +fn test_opt_ge() { + eval(" + def test(a, b) = a >= b + test(2, 3) # profile opt_ge + "); + assert_contains_opcode("test", YARVINSN_opt_ge); + assert_snapshot!(inspect("[test(0, 1), test(0, 0), test(1, 0)]"), @"[false, true, true]"); +} + +#[test] +fn test_opt_new_does_not_push_frame() { + eval(" + class Foo + attr_reader :backtrace + def initialize + @backtrace = caller + end + end + def test = Foo.new + test + "); + assert_contains_opcode("test", YARVINSN_opt_new); + assert_snapshot!(inspect(" + foo = test + foo.backtrace.find { |frame| frame.include?('Class#new') } + "), @"nil"); +} + +#[test] +fn test_opt_new_with_redefined() { + eval(r#" + class Foo + def self.new = "foo" + def initialize = raise("unreachable") + end + def test = Foo.new + test + "#); + assert_contains_opcode("test", YARVINSN_opt_new); + assert_snapshot!(inspect(r#"test"#), @r#""foo""#); +} + +#[test] +fn test_opt_new_invalidate_new() { + eval(r#" + class Foo; end + def test = Foo.new + test + "#); + assert_contains_opcode("test", YARVINSN_opt_new); + assert_snapshot!(inspect(r#" + result = [test.class.name] + def Foo.new = "foo" + result << test + "#), @r#"["Foo", "foo"]"#); +} + +#[test] +fn test_opt_newarray_send_include_p() { + eval(" + def test(x) + [:y, 1, Object.new].include?(x) + end + test(1) + "); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(inspect("[test(1), test(\"n\")]"), @"[true, false]"); +} + +#[test] +fn test_opt_newarray_send_include_p_redefined() { + eval(" + class Array + alias_method :old_include?, :include? + def include?(x) + old_include?(x) ? :true : :false + end + end + def test(x) + [:y, 1, Object.new].include?(x) + end + "); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(inspect(" + def test(x) + [:y, 1, Object.new].include?(x) + end + test(1) + [test(1), test(\"n\")] + "), @"[:true, :false]"); +} + +#[test] +fn test_opt_duparray_send_include_p() { + eval(" + def test(x) + [:y, 1].include?(x) + end + test(1) + "); + assert_contains_opcode("test", YARVINSN_opt_duparray_send); + assert_snapshot!(inspect("[test(1), test(\"n\")]"), @"[true, false]"); +} + +#[test] +fn test_opt_duparray_send_include_p_redefined() { + eval(" + class Array + alias_method :old_include?, :include? + def include?(x) + old_include?(x) ? :true : :false + end + end + def test(x) + [:y, 1].include?(x) + end + "); + assert_contains_opcode("test", YARVINSN_opt_duparray_send); + assert_snapshot!(inspect(" + def test(x) + [:y, 1].include?(x) + end + test(1) + [test(1), test(\"n\")] + "), @"[:true, :false]"); +} + +#[test] +fn test_opt_newarray_send_pack_buffer() { + eval(r#" + def test(num, buffer) + [num].pack('C', buffer:) + end + test(65, "") + "#); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(inspect(r#" + buf = "" + [test(65, buf), test(66, buf), test(67, buf), buf] + "#), @r#"["ABC", "ABC", "ABC", "ABC"]"#); +} + +#[test] +fn test_opt_newarray_send_pack_buffer_redefined() { + eval(r#" + class Array + alias_method :old_pack, :pack + def pack(fmt, buffer: nil) + old_pack(fmt, buffer: buffer) + "b" + end + end + def test(num, buffer) + [num].pack('C', buffer:) + end + "#); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(inspect(r#" + def test(num, buffer) + [num].pack('C', buffer:) + end + buf = "" + test(65, buf) + buf = "" + [test(65, buf), buf] + "#), @r#"["b", "A"]"#); +} + +#[test] +fn test_opt_newarray_send_hash() { + eval(" + def test(x) + [1, 2, x].hash + end + test(20) + "); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(inspect("test(20).class"), @"Integer"); +} + +#[test] +fn test_opt_newarray_send_hash_redefined() { + eval(" + Array.class_eval { def hash = 42 } + def test(x) + [1, 2, x].hash + end + test(20) + "); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(inspect("test(20)"), @"42"); +} + +#[test] +fn test_opt_newarray_send_max() { + eval(" + def test(a,b) = [a,b].max + test(10, 20) + "); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(inspect("[test(10, 20), test(40, 30)]"), @"[20, 40]"); +} + +#[test] +fn test_opt_newarray_send_max_redefined() { + eval(" + class Array + alias_method :old_max, :max + def max + old_max * 2 + end + end + def test(a,b) = [a,b].max + "); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(inspect(" + def test(a,b) = [a,b].max + test(15, 30) + [test(15, 30), test(45, 35)] + "), @"[60, 90]"); +} + +#[test] +fn test_new_hash_empty() { + eval(" + def test = {} + test + "); + assert_contains_opcode("test", YARVINSN_newhash); + assert_snapshot!(inspect("test"), @"{}"); +} + +#[test] +fn test_new_hash_nonempty() { + eval(r#" + def test + key = "key" + value = "value" + num = 42 + result = 100 + {key => value, num => result} + end + test + "#); + assert_contains_opcode("test", YARVINSN_newhash); + assert_snapshot!(inspect(r#"test"#), @r#"{"key" => "value", 42 => 100}"#); +} + +#[test] +fn test_new_hash_single_key_value() { + eval(r#" + def test = {"key" => "value"} + test + "#); + assert_contains_opcode("test", YARVINSN_newhash); + assert_snapshot!(inspect(r#"test"#), @r#"{"key" => "value"}"#); +} + +#[test] +fn test_new_hash_with_computation() { + eval(r#" + def test(a, b) + {"sum" => a + b, "product" => a * b} + end + test(2, 3) + "#); + assert_contains_opcode("test", YARVINSN_newhash); + assert_snapshot!(inspect(r#"test(2, 3)"#), @r#"{"sum" => 5, "product" => 6}"#); +} + +#[test] +fn test_new_hash_with_user_defined_hash_method() { + assert_snapshot!(inspect(r#" + 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 + test + "#), @"true"); +} + +#[test] +fn test_new_hash_with_user_hash_method_exception() { + assert_snapshot!(inspect(r#" + class BadKey + def hash + raise "Hash method failed!" + end + end + def test + key = BadKey.new + {key => "value"} + end + begin + test + rescue => e + e.class + end + begin + test + rescue => e + e.class + end + "#), @"RuntimeError"); +} + +#[test] +fn test_new_hash_with_user_eql_method_exception() { + assert_snapshot!(inspect(r#" + class BadKey + def hash + 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 + begin + test + rescue => e + e.class + end + "#), @"RuntimeError"); +} + +#[test] +fn test_opt_hash_freeze() { + eval(" + def test = {}.freeze + test + "); + assert_contains_opcode("test", YARVINSN_opt_hash_freeze); + assert_snapshot!(inspect(" + result = [test] + class Hash + def freeze = 5 + end + result << test + "), @"[{}, 5]"); +} + +#[test] +fn test_opt_hash_freeze_rewritten() { + eval(" + class Hash + def freeze = 5 + end + def test = {}.freeze + test + "); + assert_contains_opcode("test", YARVINSN_opt_hash_freeze); + assert_snapshot!(inspect("test"), @"5"); +} + +#[test] +fn test_opt_aset_hash() { + eval(" + def test(h, k, v) + h[k] = v + end + test({}, :key, 42) + "); + assert_contains_opcode("test", YARVINSN_opt_aset); + assert_snapshot!(inspect("h = {}; test(h, :key, 42); h[:key]"), @"42"); +} + +#[test] +fn test_opt_aset_hash_returns_value() { + assert_snapshot!(inspect(" + def test(h, k, v) + h[k] = v + end + test({}, :key, 100) + test({}, :key, 100) + "), @"100"); +} + +#[test] +fn test_opt_aset_hash_string_key() { + assert_snapshot!(inspect(r#" + def test(h, k, v) + h[k] = v + end + h = {} + test(h, "foo", "bar") + test(h, "foo", "bar") + h["foo"] + "#), @r#""bar""#); +} + +#[test] +fn test_opt_aset_hash_subclass() { + assert_snapshot!(inspect(" + class MyHash < Hash; end + def test(h, k, v) + h[k] = v + end + h = MyHash.new + test(h, :key, 42) + test(h, :key, 42) + h[:key] + "), @"42"); +} + +#[test] +fn test_opt_aset_hash_too_few_args() { + assert_snapshot!(inspect(r#" + def test(h) + h.[]= 123 + rescue ArgumentError + "ArgumentError" + end + test({}) + test({}) + "#), @r#""ArgumentError""#); +} + +#[test] +fn test_opt_aset_hash_too_many_args() { + assert_snapshot!(inspect(r#" + def test(h) + h[:a, :b] = :c + rescue ArgumentError + "ArgumentError" + end + test({}) + test({}) + "#), @r#""ArgumentError""#); +} + +#[test] +fn test_opt_ary_freeze() { + eval(" + def test = [].freeze + test + "); + assert_contains_opcode("test", YARVINSN_opt_ary_freeze); + assert_snapshot!(inspect(" + result = [test] + class Array + def freeze = 5 + end + result << test + "), @"[[], 5]"); +} + +#[test] +fn test_opt_ary_freeze_rewritten() { + eval(" + class Array + def freeze = 5 + end + def test = [].freeze + test + "); + assert_contains_opcode("test", YARVINSN_opt_ary_freeze); + assert_snapshot!(inspect("test"), @"5"); +} + +#[test] +fn test_opt_str_freeze() { + eval(" + def test = ''.freeze + test + "); + assert_contains_opcode("test", YARVINSN_opt_str_freeze); + assert_snapshot!(inspect(r#" + result = [test] + class String + def freeze = 5 + end + result << test + "#), @r#"["", 5]"#); +} + +#[test] +fn test_opt_str_freeze_rewritten() { + eval(" + class String + def freeze = 5 + end + def test = ''.freeze + test + "); + assert_contains_opcode("test", YARVINSN_opt_str_freeze); + assert_snapshot!(inspect("test"), @"5"); +} + +#[test] +fn test_opt_str_uminus() { + eval(" + def test = -'' + test + "); + assert_contains_opcode("test", YARVINSN_opt_str_uminus); + assert_snapshot!(inspect(r#" + result = [test] + class String + def -@ = 5 + end + result << test + "#), @r#"["", 5]"#); +} + +#[test] +fn test_opt_str_uminus_rewritten() { + eval(" + class String + def -@ = 5 + end + def test = -'' + test + "); + assert_contains_opcode("test", YARVINSN_opt_str_uminus); + assert_snapshot!(inspect("test"), @"5"); +} + +#[test] +fn test_new_array_empty() { + eval(" + def test = [] + test + "); + assert_contains_opcode("test", YARVINSN_newarray); + assert_snapshot!(inspect("test"), @"[]"); +} + +#[test] +fn test_new_array_nonempty() { + assert_snapshot!(inspect(" + def a = 5 + def test = [a] + test + test + "), @"[5]"); +} + +#[test] +fn test_new_array_order() { + assert_snapshot!(inspect(" + def a = 3 + def b = 2 + def c = 1 + def test = [a, b, c] + test + test + "), @"[3, 2, 1]"); +} + +#[test] +fn test_array_dup() { + assert_snapshot!(inspect(" + def test = [1,2,3] + test + test + "), @"[1, 2, 3]"); +} + +#[test] +fn test_array_fixnum_aref() { + eval(" + def test(x) = [1,2,3][x] + test(2) + "); + assert_contains_opcode("test", YARVINSN_opt_aref); + assert_snapshot!(inspect("test(2)"), @"3"); +} + +#[test] +fn test_array_fixnum_aref_negative_index() { + eval(" + def test(x) = [1,2,3][x] + test(-1) + "); + assert_contains_opcode("test", YARVINSN_opt_aref); + assert_snapshot!(inspect("test(-1)"), @"3"); +} + +#[test] +fn test_array_fixnum_aref_out_of_bounds_positive() { + eval(" + def test(x) = [1,2,3][x] + test(10) + "); + assert_contains_opcode("test", YARVINSN_opt_aref); + assert_snapshot!(inspect("test(10)"), @"nil"); +} + +#[test] +fn test_array_fixnum_aref_out_of_bounds_negative() { + eval(" + def test(x) = [1,2,3][x] + test(-10) + "); + assert_contains_opcode("test", YARVINSN_opt_aref); + assert_snapshot!(inspect("test(-10)"), @"nil"); +} + +#[test] +fn test_array_fixnum_aref_array_subclass() { + eval(" + class MyArray < Array; end + def test(arr, idx) = arr[idx] + test(MyArray[1,2,3], 2) + "); + assert_contains_opcode("test", YARVINSN_opt_aref); + assert_snapshot!(inspect("test(MyArray[1,2,3], 2)"), @"3"); +} + +#[test] +fn test_array_aref_non_fixnum_index() { + assert_snapshot!(inspect(r#" + def test(arr, idx) = arr[idx] + test([1,2,3], 1) + test([1,2,3], 1) + begin + test([1,2,3], "1") + rescue => e + e.class + end + "#), @"TypeError"); +} + +#[test] +fn test_array_fixnum_aset() { + eval(" + def test(arr, idx) + arr[idx] = 7 + end + test([1,2,3], 2) + "); + assert_contains_opcode("test", YARVINSN_opt_aset); + assert_snapshot!(inspect("arr = [1,2,3]; test(arr, 2); arr"), @"[1, 2, 7]"); +} + +#[test] +fn test_array_fixnum_aset_returns_value() { + eval(" + def test(arr, idx) + arr[idx] = 7 + end + test([1,2,3], 2) + "); + assert_contains_opcode("test", YARVINSN_opt_aset); + assert_snapshot!(inspect("test([1,2,3], 2)"), @"7"); +} + +#[test] +fn test_array_fixnum_aset_out_of_bounds() { + assert_snapshot!(inspect(" + def test(arr) + arr[5] = 7 + end + arr = [1,2,3] + test(arr) + arr = [1,2,3] + test(arr) + arr + "), @"[1, 2, 3, nil, nil, 7]"); +} + +#[test] +fn test_array_fixnum_aset_negative_index() { + assert_snapshot!(inspect(" + def test(arr) + arr[-1] = 7 + end + arr = [1,2,3] + test(arr) + arr = [1,2,3] + test(arr) + arr + "), @"[1, 2, 7]"); +} + +#[test] +fn test_array_fixnum_aset_shared() { + assert_snapshot!(inspect(" + def test(arr, idx, val) + arr[idx] = val + end + arr = (0..50).to_a + test(arr, 0, -1) + test(arr, 1, -2) + shared = arr[10, 20] + test(shared, 0, 999) + [arr[10], shared[0], arr[0], arr[1]] + "), @"[10, 999, -1, -2]"); +} + +#[test] +fn test_array_fixnum_aset_frozen() { + assert_snapshot!(inspect(" + def test(arr, idx, val) + arr[idx] = val + end + arr = [1,2,3] + test(arr, 1, 9) + test(arr, 1, 9) + arr.freeze + begin + test(arr, 1, 9) + rescue => e + e.class + end + "), @"FrozenError"); +} + +#[test] +fn test_array_fixnum_aset_array_subclass() { + eval(" + class MyArray < Array; end + def test(arr, idx) + arr[idx] = 7 + end + test(MyArray.new, 0) + "); + assert_contains_opcode("test", YARVINSN_opt_aset); + assert_snapshot!(inspect("arr = MyArray.new; test(arr, 0); arr[0]"), @"7"); +} + +#[test] +fn test_array_aset_non_fixnum_index() { + assert_snapshot!(inspect(r#" + def test(arr, idx) + arr[idx] = 7 + end + test([1,2,3], 0) + test([1,2,3], 0) + begin + test([1,2,3], "0") + rescue => e + e.class + end + "#), @"TypeError"); +} + +#[test] +fn test_empty_array_pop() { + assert_snapshot!(inspect(" + def test(arr) = arr.pop + test([]) + test([]) + "), @"nil"); +} + +#[test] +fn test_array_pop_no_arg() { + assert_snapshot!(inspect(" + def test(arr) = arr.pop + test([32, 33, 42]) + test([32, 33, 42]) + "), @"42"); +} + +#[test] +fn test_array_pop_arg() { + assert_snapshot!(inspect(" + def test(arr) = arr.pop(2) + test([32, 33, 42]) + test([32, 33, 42]) + "), @"[33, 42]"); +} + +#[test] +fn test_new_range_inclusive() { + assert_snapshot!(inspect(" + def test(a, b) = a..b + test(1, 5) + test(1, 5) + "), @"1..5"); +} + +#[test] +fn test_new_range_exclusive() { + assert_snapshot!(inspect(" + def test(a, b) = a...b + test(1, 5) + test(1, 5) + "), @"1...5"); +} + +#[test] +fn test_new_range_with_literal() { + assert_snapshot!(inspect(" + def test(n) = n..10 + test(3) + test(3) + "), @"3..10"); +} + +#[test] +fn test_new_range_fixnum_both_literals_inclusive() { + eval(" + def test() + a = 2 + (1..a) + end + "); + assert_contains_opcode("test", YARVINSN_newrange); + assert_snapshot!(inspect("test; test"), @"1..2"); +} + +#[test] +fn test_new_range_fixnum_both_literals_exclusive() { + eval(" + def test() + a = 2 + (1...a) + end + "); + assert_contains_opcode("test", YARVINSN_newrange); + assert_snapshot!(inspect("test; test"), @"1...2"); +} + +#[test] +fn test_new_range_fixnum_low_literal_inclusive() { + eval(" + def test(a) = (1..a) + "); + assert_contains_opcode("test", YARVINSN_newrange); + assert_snapshot!(inspect("test(2); test(3)"), @"1..3"); +} + +#[test] +fn test_new_range_fixnum_low_literal_exclusive() { + eval(" + def test(a) = (1...a) + "); + assert_contains_opcode("test", YARVINSN_newrange); + assert_snapshot!(inspect("test(2); test(3)"), @"1...3"); +} + +#[test] +fn test_new_range_fixnum_high_literal_inclusive() { + eval(" + def test(a) = (a..10) + "); + assert_contains_opcode("test", YARVINSN_newrange); + assert_snapshot!(inspect("test(2); test(3)"), @"3..10"); +} + +#[test] +fn test_new_range_fixnum_high_literal_exclusive() { + eval(" + def test(a) = (a...10) + "); + assert_contains_opcode("test", YARVINSN_newrange); + assert_snapshot!(inspect("test(2); test(3)"), @"3...10"); +} + +#[test] +fn test_if() { + assert_snapshot!(inspect(" + def test(n) + if n < 5 + 0 + end + end + test(3) + [test(3), test(7)] + "), @"[0, nil]"); +} + +#[test] +fn test_if_else() { + assert_snapshot!(inspect(" + def test(n) + if n < 5 + 0 + else + 1 + end + end + test(3) + [test(3), test(7)] + "), @"[0, 1]"); +} + +#[test] +fn test_if_else_params() { + assert_snapshot!(inspect(" + def test(n, a, b) + if n < 5 + a + else + b + end + end + test(3, 1, 2) + [test(3, 1, 2), test(7, 10, 20)] + "), @"[1, 20]"); +} + +#[test] +fn test_if_else_nested() { + assert_snapshot!(inspect(" + 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(-1, 1, 2, 3, 4), + test( 0, 5, 6, 7, 8), + test( 3, 9, 10, 11, 12), + test( 5, 13, 14, 15, 16), + ] + "), @"[3, 8, 9, 14]"); +} + +#[test] +fn test_if_else_chained() { + assert_snapshot!(inspect(" + def test(a) + (if 2 < a then 1 else 2 end) + (if a < 4 then 10 else 20 end) + end + test(0) + [test(0), test(3), test(5)] + "), @"[12, 11, 21]"); +} + +#[test] +fn test_if_elsif_else() { + assert_snapshot!(inspect(" + def test(n) + if n < 5 + 0 + elsif 8 < n + 1 + else + 2 + end + end + test(3) + [test(3), test(7), test(9)] + "), @"[0, 2, 1]"); +} + +#[test] +fn test_ternary_operator() { + assert_snapshot!(inspect(" + def test(n, a, b) + n < 5 ? a : b + end + test(3, 1, 2) + [test(3, 1, 2), test(7, 10, 20)] + "), @"[1, 20]"); +} + +#[test] +fn test_ternary_operator_nested() { + assert_snapshot!(inspect(" + def test(n, a, b) + (n < 5 ? a : b) + 1 + end + test(3, 1, 2) + [test(3, 1, 2), test(7, 10, 20)] + "), @"[2, 21]"); +} + +#[test] +fn test_while_loop() { + assert_snapshot!(inspect(" + def test(n) + i = 0 + while i < n + i = i + 1 + end + i + end + test(10) + test(10) + "), @"10"); +} + +#[test] +fn test_while_loop_chain() { + assert_snapshot!(inspect(" + 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(5), test(10)] + "), @"[135, 270]"); +} + +#[test] +fn test_while_loop_nested() { + assert_snapshot!(inspect(" + 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(0, 0), test(1, 3), test(10, 5)] + "), @"[0, 4, 12]"); +} + +#[test] +fn test_while_loop_if_else() { + assert_snapshot!(inspect(" + def test(n) + i = 0 + while i < n + if n >= 10 + return -1 + else + i = i + 1 + end + end + i + end + test(9) + [test(9), test(10)] + "), @"[9, -1]"); +} + +#[test] +fn test_if_while_loop() { + assert_snapshot!(inspect(" + 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(9), test(10)] + "), @"[9, 12]"); +} + +#[test] +fn test_live_reg_past_ccall() { + assert_snapshot!(inspect(" + def callee = 1 + def test = callee + callee + test + test + "), @"2"); +} + +#[test] +fn test_method_call() { + assert_snapshot!(inspect(" + def callee(a, b) + a - b + end + def test + callee(4, 2) + 10 + end + test + test + "), @"12"); +} + +#[test] +fn test_recursive_fact() { + assert_snapshot!(inspect(" + def fact(n) + if n == 0 + return 1 + end + return n * fact(n-1) + end + fact(0) + [fact(0), fact(3), fact(6)] + "), @"[1, 6, 720]"); +} + +#[test] +fn test_recursive_fib() { + assert_snapshot!(inspect(" + def fib(n) + if n < 2 + return n + end + return fib(n-1) + fib(n-2) + end + fib(0) + [fib(0), fib(3), fib(4)] + "), @"[0, 2, 3]"); +} + +#[test] +fn test_spilled_basic_block_args() { + assert_snapshot!(inspect(" + 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) + test(1, 2) + "), @"55"); +} + +#[test] +fn test_putself() { + assert_snapshot!(inspect(" + class Integer + def minus(a) + self - a + end + end + 5.minus(2) + 5.minus(2) + "), @"3"); +} + +#[test] +fn test_getinstancevariable_nil() { + assert_snapshot!(inspect(" + def test() = @foo + test() + test() + "), @"nil"); +} + +#[test] +fn test_getinstancevariable() { + assert_snapshot!(inspect(" + @foo = 3 + def test() = @foo + test() + test() + "), @"3"); +} + +#[test] +fn test_getinstancevariable_miss() { + assert_snapshot!(inspect(" + class C + def foo + @foo + end + def foo_then_bar + @foo = 1 + @bar = 2 + end + def bar_then_foo + @bar = 3 + @foo = 4 + end + end + o1 = C.new + o1.foo_then_bar + result = [] + result << o1.foo + result << o1.foo + o2 = C.new + o2.bar_then_foo + result << o2.foo + result + "), @"[1, 1, 4]"); +} + +#[test] +fn test_setinstancevariable() { + assert_snapshot!(inspect(" + def test() = @foo = 1 + test() + test() + @foo + "), @"1"); +} + +#[test] +fn test_getclassvariable() { + assert_snapshot!(inspect(" + class Foo + def self.test = @@x + end + Foo.class_variable_set(:@@x, 42) + Foo.test() + Foo.test() + "), @"42"); +} + +#[test] +fn test_getclassvariable_raises() { + assert_snapshot!(inspect(r#" + class Foo + def self.test = @@x + end + begin + Foo.test + Foo.test + rescue NameError => e + e.message + end + "#), @r#""uninitialized class variable @@x in Foo""#); +} + +#[test] +fn test_setclassvariable() { + assert_snapshot!(inspect(" + class Foo + def self.test = @@x = 42 + end + Foo.test() + Foo.test() + Foo.class_variable_get(:@@x) + "), @"42"); +} + +#[test] +fn test_setclassvariable_raises() { + assert_snapshot!(inspect(r#" + class Foo + def self.test = @@x = 42 + freeze + end + begin + Foo.test + Foo.test + rescue FrozenError => e + e.message + end + "#), @r#""can't modify frozen Class: Foo""#); +} + +#[test] +fn test_attr_reader() { + eval(" + class C + attr_reader :foo + def initialize + @foo = 4 + end + end + def test(c) = c.foo + test(C.new) + "); + assert_contains_opcode("test", YARVINSN_opt_send_without_block); + assert_snapshot!(inspect("c = C.new; [test(c), test(c)]"), @"[4, 4]"); +} + +#[test] +fn test_attr_accessor_getivar() { + eval(" + class C + attr_accessor :foo + def initialize + @foo = 4 + end + end + def test(c) = c.foo + test(C.new) + "); + assert_contains_opcode("test", YARVINSN_opt_send_without_block); + assert_snapshot!(inspect("c = C.new; [test(c), test(c)]"), @"[4, 4]"); +} + +#[test] +fn test_attr_accessor_setivar() { + eval(" + class C + attr_accessor :foo + def initialize + @foo = 4 + end + end + def test(c) + c.foo = 5 + c.foo + end + test(C.new) + "); + assert_contains_opcode("test", YARVINSN_opt_send_without_block); + assert_snapshot!(inspect("c = C.new; [test(c), test(c)]"), @"[5, 5]"); +} + +#[test] +fn test_attr_writer() { + eval(" + class C + attr_writer :foo + def initialize + @foo = 4 + end + def get_foo = @foo + end + def test(c) + c.foo = 5 + c.get_foo + end + test(C.new) + "); + assert_contains_opcode("test", YARVINSN_opt_send_without_block); + assert_snapshot!(inspect("c = C.new; [test(c), test(c)]"), @"[5, 5]"); +} + +#[test] +fn test_getconstant() { + eval(" + class Foo + CONST = 1 + end + def test(klass) + klass::CONST + end + test(Foo) + "); + assert_contains_opcode("test", YARVINSN_getconstant); + assert_snapshot!(inspect("test(Foo)"), @"1"); +} + +#[test] +fn test_expandarray_no_splat() { + eval(" + def test(o) + a, b = o + [a, b] + end + test [3, 4] + "); + assert_contains_opcode("test", YARVINSN_expandarray); + assert_snapshot!(inspect("test [3, 4]"), @"[3, 4]"); +} + +#[test] +fn test_expandarray_splat() { + eval(" + def test(o) + a, *b = o + [a, b] + end + test [3, 4] + "); + assert_contains_opcode("test", YARVINSN_expandarray); + assert_snapshot!(inspect("test [3, 4]"), @"[3, [4]]"); +} + +#[test] +fn test_expandarray_splat_post() { + eval(" + def test(o) + a, *b, c = o + [a, b, c] + end + test [3, 4, 5] + "); + assert_contains_opcode("test", YARVINSN_expandarray); + assert_snapshot!(inspect("test [3, 4, 5]"), @"[3, [4], 5]"); +} + +#[test] +fn test_constant_invalidation() { + eval(" + class C; end + def test = C + test + test + C = 123 + "); + assert_contains_opcode("test", YARVINSN_opt_getconstant_path); + assert_snapshot!(inspect("test"), @"123"); +} + +#[test] +fn test_constant_path_invalidation() { + eval(" + module A + module B; end + end + module Foo + C = 'Foo::C' + end + A::B = Foo + def test = A::B::C + "); + assert_contains_opcode("test", YARVINSN_opt_getconstant_path); + assert_snapshot!(inspect(r#" + 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 + "#), @r#"["Foo::C", "Foo::C", "Bar::C"]"#); +} + +#[test] +fn test_dupn() { + eval(" + def test(array) = (array[1, 2] ||= :rhs) + test([1, 1]) + "); + assert_contains_opcode("test", YARVINSN_dupn); + assert_snapshot!(inspect(" + one = [1, 1] + start_empty = [] + [test(one), one, test(start_empty), start_empty] + "), @"[[1], [1, 1], :rhs, [nil, :rhs]]"); +} + +#[test] +fn test_bop_invalidation() { + assert_snapshot!(inspect(r#" + def test + eval("class Integer; def +(_) = 100; end") + 1 + 2 + end + test + test + "#), @"100"); +} + +#[test] +fn test_defined_with_defined_values() { + eval(" + class Foo; end + def bar; end + $ruby = 1 + def test = [defined?(Foo), defined?(bar), defined?($ruby)] + test + "); + assert_contains_opcode("test", YARVINSN_defined); + assert_snapshot!(inspect("test"), @r#"["constant", "method", "global-variable"]"#); +} + +#[test] +fn test_defined_with_undefined_values() { + eval(" + def test = [defined?(FooUndef), defined?(bar_undef), defined?($ruby_undef)] + test + "); + assert_contains_opcode("test", YARVINSN_defined); + assert_snapshot!(inspect("test"), @"[nil, nil, nil]"); +} + +#[test] +fn test_defined_with_method_call() { + eval(r#" + def test = [defined?("x".reverse(1)), defined?("x".reverse(1).reverse)] + test + "#); + assert_contains_opcode("test", YARVINSN_defined); + assert_snapshot!(inspect(r#"test"#), @r#"["method", nil]"#); +} + +#[test] +fn test_defined_method_raise() { + assert_snapshot!(inspect(r#" + class C + def assert_equal expected, actual + if expected != actual + raise "NO" + end + end + def test_defined_method + assert_equal(nil, defined?("x".reverse(1).reverse)) + end + end + c = C.new + result = [] + result << c.test_defined_method + result << c.test_defined_method + result << c.test_defined_method + result + "#), @"[nil, nil, nil]"); +} + +#[test] +fn test_defined_yield() { + eval(" + def test = defined?(yield) + "); + assert_contains_opcode("test", YARVINSN_defined); + assert_snapshot!(inspect("[test, test, test{}]"), @r#"[nil, nil, "yield"]"#); +} + +#[test] +fn test_defined_yield_from_block() { + assert_snapshot!(inspect(" + def test + yield_self { yield_self { defined?(yield) } } + end + [test, test, test{}] + "), @r#"[nil, nil, "yield"]"#); +} + +#[test] +fn test_block_given_p() { + assert_snapshot!(inspect(" + def test = block_given? + [test, test, test{}] + "), @"[false, false, true]"); +} + +#[test] +fn test_block_given_p_from_block() { + assert_snapshot!(inspect(" + def test + yield_self { yield_self { block_given? } } + end + [test, test, test{}] + "), @"[false, false, true]"); +} + +#[test] +fn test_invokeblock_without_block_after_jit_call() { + assert_snapshot!(inspect(r#" + def test(*arr, &b) + arr.class + yield + end + test { } + begin + test + rescue => e + e.message + end + "#), @r#""no block given (yield)""#); +} + +#[test] +fn test_putspecialobject_vm_core_and_cbase() { + eval(" + def test + alias bar test + 10 + end + test + "); + assert_contains_opcode("test", YARVINSN_putspecialobject); + assert_snapshot!(inspect("bar"), @"10"); +} + +#[test] +fn test_putspecialobject_const_base() { + assert_snapshot!(inspect(" + Foo = 1 + def test = Foo + test + test + "), @"1"); +} + +#[test] +fn test_branchnil() { + eval(" + def test(x) + x&.succ + end + test(0) + "); + assert_contains_opcode("test", YARVINSN_branchnil); + assert_snapshot!(inspect("[test(1), test(nil)]"), @"[2, nil]"); +} + +#[test] +fn test_nil_nil() { + eval(" + def test = nil.nil? + test + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(inspect("test"), @"true"); +} + +#[test] +fn test_non_nil_nil() { + eval(" + def test = 1.nil? + test + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(inspect("test"), @"false"); +} + +#[test] +fn test_getspecial_last_match() { + eval(r#" + def test(str) + str =~ /hello/ + $& + end + test("hello world") + "#); + assert_contains_opcode("test", YARVINSN_getspecial); + assert_snapshot!(inspect(r#"test("hello world")"#), @r#""hello""#); +} + +#[test] +fn test_getspecial_match_pre() { + eval(r#" + def test(str) + str =~ /world/ + $` + end + test("hello world") + "#); + assert_contains_opcode("test", YARVINSN_getspecial); + assert_snapshot!(inspect(r#"test("hello world")"#), @r#""hello ""#); +} + +#[test] +fn test_getspecial_match_post() { + eval(r#" + def test(str) + str =~ /hello/ + $' + end + test("hello world") + "#); + assert_contains_opcode("test", YARVINSN_getspecial); + assert_snapshot!(inspect(r#"test("hello world")"#), @r#"" world""#); +} + +#[test] +fn test_getspecial_match_last_group() { + eval(r#" + def test(str) + str =~ /(hello) (world)/ + $+ + end + test("hello world") + "#); + assert_contains_opcode("test", YARVINSN_getspecial); + assert_snapshot!(inspect(r#"test("hello world")"#), @r#""world""#); +} + +#[test] +fn test_getspecial_numbered_match_1() { + eval(r#" + def test(str) + str =~ /(hello) (world)/ + $1 + end + test("hello world") + "#); + assert_contains_opcode("test", YARVINSN_getspecial); + assert_snapshot!(inspect(r#"test("hello world")"#), @r#""hello""#); +} + +#[test] +fn test_getspecial_numbered_match_2() { + eval(r#" + def test(str) + str =~ /(hello) (world)/ + $2 + end + test("hello world") + "#); + assert_contains_opcode("test", YARVINSN_getspecial); + assert_snapshot!(inspect(r#"test("hello world")"#), @r#""world""#); +} + +#[test] +fn test_getspecial_numbered_match_nonexistent() { + eval(r#" + def test(str) + str =~ /(hello)/ + $2 + end + test("hello world") + "#); + assert_contains_opcode("test", YARVINSN_getspecial); + assert_snapshot!(inspect(r#"test("hello world")"#), @"nil"); +} + +#[test] +fn test_getspecial_no_match() { + eval(r#" + def test(str) + str =~ /xyz/ + $& + end + test("hello world") + "#); + assert_contains_opcode("test", YARVINSN_getspecial); + assert_snapshot!(inspect(r#"test("hello world")"#), @"nil"); +} + +#[test] +fn test_getspecial_complex_pattern() { + eval(r#" + def test(str) + str =~ /(\d+)/ + $1 + end + test("abc123def") + "#); + assert_contains_opcode("test", YARVINSN_getspecial); + assert_snapshot!(inspect(r#"test("abc123def")"#), @r#""123""#); +} + +#[test] +fn test_getspecial_multiple_groups() { + eval(r#" + def test(str) + str =~ /(\d+)-(\d+)/ + $2 + end + test("123-456") + "#); + assert_contains_opcode("test", YARVINSN_getspecial); + assert_snapshot!(inspect(r#"test("123-456")"#), @r#""456""#); +} + +#[test] +fn test_profile_under_nested_jit_call() { + assert_snapshot!(inspect(" + def profile + 1 + 2 + end + def jit_call(flag) + if flag + profile + end + end + def entry(flag) + jit_call(flag) + end + [entry(false), entry(false), entry(true)] + "), @"[nil, nil, 3]"); +} + +#[test] +fn test_bop_redefined() { + assert_snapshot!(inspect(" + def test + 1 + 2 + end + test + [test, Integer.class_eval { def +(_) = 100 }, test] + "), @"[3, :+, 100]"); +} + +#[test] +fn test_bop_redefined_with_adjacent_patch_points() { + assert_snapshot!(inspect(" + def test + 1 + 2 + 3 + 4 + 5 + end + test + [test, Integer.class_eval { def +(_) = 100 }, test] + "), @"[15, :+, 100]"); +} + +#[test] +fn test_method_redefined_with_top_self() { + assert_snapshot!(inspect(r#" + def foo + "original" + end + def test = foo + test; test + result1 = test + def foo + "redefined" + end + result2 = test + [result1, result2] + "#), @r#"["original", "redefined"]"#); +} + +#[test] +fn test_method_redefined_with_module() { + assert_snapshot!(inspect(r#" + module Foo + def self.foo = "original" + end + def test = Foo.foo + test + result1 = test + def Foo.foo = "redefined" + result2 = test + [result1, result2] + "#), @r#"["original", "redefined"]"#); +} + +#[test] +fn test_module_name_with_guard_passes() { + assert_snapshot!(inspect(r#" + def test(mod) + mod.name + end + test(String) + test(Integer) + "#), @r#""Integer""#); +} + +#[test] +fn test_module_name_with_guard_side_exit() { + assert_snapshot!(inspect(r#" + class MyClass + def name = "Bar" + end + def test(mod) + mod.name + end + results = [] + results << test(String) + results << test(Integer) + results << test(MyClass.new) + results + "#), @r#"["String", "Integer", "Bar"]"#); +} + +#[test] +fn test_objtostring_calls_to_s_on_non_strings() { + assert_snapshot!(inspect(r##" + results = [] + class Foo + def to_s + "foo" + end + end + def test(str) + "#{str}" + end + results << test(Foo.new) + results << test(Foo.new) + results + "##), @r#"["foo", "foo"]"#); +} + +#[test] +fn test_objtostring_rewrite_does_not_call_to_s_on_strings() { + assert_snapshot!(inspect(r##" + results = [] + class String + def to_s + "bad" + end + end + def test(foo) + "#{foo}" + end + results << test("foo") + results << test("foo") + results + "##), @r#"["foo", "foo"]"#); +} + +#[test] +fn test_objtostring_rewrite_does_not_call_to_s_on_string_subclasses() { + assert_snapshot!(inspect(r##" + results = [] + class StringSubclass < String + def to_s + "bad" + end + end + foo = StringSubclass.new("foo") + def test(str) + "#{str}" + end + results << test(foo) + results << test(foo) + results + "##), @r#"["foo", "foo"]"#); +} + +#[test] +fn test_objtostring_profiled_string_fastpath() { + assert_snapshot!(inspect(r##" + def test(str) + "#{str}" + end + test('foo'); test('foo') + "##), @r#""foo""#); +} + +#[test] +fn test_objtostring_profiled_string_subclass_fastpath() { + assert_snapshot!(inspect(r##" + class MyString < String; end + def test(str) + "#{str}" + end + foo = MyString.new("foo") + test(foo); test(foo) + "##), @r#""foo""#); +} + +#[test] +fn test_objtostring_profiled_string_fastpath_exits_on_nonstring() { + assert_snapshot!(inspect(r##" + def test(str) + "#{str}" + end + test('foo') + test(1) + "##), @r#""1""#); +} + +#[test] +fn test_objtostring_profiled_nonstring_calls_to_s() { + assert_snapshot!(inspect(r##" + def test(str) + "#{str}" + end + test([1,2,3]); + test([1,2,3]); + "##), @r#""[1, 2, 3]""#); +} + +#[test] +fn test_objtostring_profiled_nonstring_guard_exits_when_string() { + assert_snapshot!(inspect(r##" + def test(str) + "#{str}" + end + test([1,2,3]); + test('foo'); + "##), @r#""foo""#); +} + +#[test] +fn test_string_bytesize_with_guard() { + assert_snapshot!(inspect(" + def test(str) + str.bytesize + end + test('hello') + test('world') + "), @"5"); +} + +#[test] +fn test_string_bytesize_multibyte() { + assert_snapshot!(inspect(r#" + def test(s) + s.bytesize + end + test("💎") + test("💎") + "#), @"4"); +} + +#[test] +fn test_nil_value_nil_opt_with_guard() { + eval(" + def test(val) = val.nil? + test(nil) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(inspect("test(nil)"), @"true"); +} + +#[test] +fn test_nil_value_nil_opt_with_guard_side_exit() { + eval(" + def test(val) = val.nil? + test(nil) + test(nil) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(inspect("test(1)"), @"false"); +} + +#[test] +fn test_true_nil_opt_with_guard() { + eval(" + def test(val) = val.nil? + test(true) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(inspect("test(true)"), @"false"); +} + +#[test] +fn test_true_nil_opt_with_guard_side_exit() { + eval(" + def test(val) = val.nil? + test(true) + test(true) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(inspect("test(nil)"), @"true"); +} + +#[test] +fn test_false_nil_opt_with_guard() { + eval(" + def test(val) = val.nil? + test(false) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(inspect("test(false)"), @"false"); +} + +#[test] +fn test_false_nil_opt_with_guard_side_exit() { + eval(" + def test(val) = val.nil? + test(false) + test(false) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(inspect("test(nil)"), @"true"); +} + +#[test] +fn test_integer_nil_opt_with_guard() { + eval(" + def test(val) = val.nil? + test(1) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(inspect("test(2)"), @"false"); +} + +#[test] +fn test_integer_nil_opt_with_guard_side_exit() { + eval(" + def test(val) = val.nil? + test(1) + test(2) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(inspect("test(nil)"), @"true"); +} + +#[test] +fn test_float_nil_opt_with_guard() { + eval(" + def test(val) = val.nil? + test(1.0) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(inspect("test(2.0)"), @"false"); +} + +#[test] +fn test_float_nil_opt_with_guard_side_exit() { + eval(" + def test(val) = val.nil? + test(1.0) + test(2.0) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(inspect("test(nil)"), @"true"); +} + +#[test] +fn test_symbol_nil_opt_with_guard() { + eval(" + def test(val) = val.nil? + test(:foo) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(inspect("test(:bar)"), @"false"); +} + +#[test] +fn test_symbol_nil_opt_with_guard_side_exit() { + eval(" + def test(val) = val.nil? + test(:foo) + test(:bar) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(inspect("test(nil)"), @"true"); +} + +#[test] +fn test_class_nil_opt_with_guard() { + eval(" + def test(val) = val.nil? + test(String) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(inspect("test(Integer)"), @"false"); +} + +#[test] +fn test_class_nil_opt_with_guard_side_exit() { + eval(" + def test(val) = val.nil? + test(String) + test(Integer) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(inspect("test(nil)"), @"true"); +} + +#[test] +fn test_module_nil_opt_with_guard() { + eval(" + def test(val) = val.nil? + test(Enumerable) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(inspect("test(Kernel)"), @"false"); +} + +#[test] +fn test_module_nil_opt_with_guard_side_exit() { + eval(" + def test(val) = val.nil? + test(Enumerable) + test(Kernel) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(inspect("test(nil)"), @"true"); +} + +#[test] +fn test_basic_object_guard_works_with_immediate() { + assert_snapshot!(inspect(" + class Foo; end + def test(val) = val.class + test(Foo.new) + test(Foo.new) + test(nil) + "), @"NilClass"); +} + +#[test] +fn test_basic_object_guard_works_with_false() { + assert_snapshot!(inspect(" + class Foo; end + def test(val) = val.class + test(Foo.new) + test(Foo.new) + test(false) + "), @"FalseClass"); +} + +#[test] +fn test_string_concat() { + eval(r##" + def test = "#{1}#{2}#{3}" + test + "##); + assert_contains_opcode("test", YARVINSN_concatstrings); + assert_snapshot!(inspect(r##"test"##), @r#""123""#); +} + +#[test] +fn test_string_concat_empty() { + eval(r##" + def test = "#{}" + test + "##); + assert_contains_opcode("test", YARVINSN_concatstrings); + assert_snapshot!(inspect(r##"test"##), @r#""""#); +} + +#[test] +fn test_regexp_interpolation() { + eval(r##" + def test = /#{1}#{2}#{3}/ + test + "##); + assert_contains_opcode("test", YARVINSN_toregexp); + assert_snapshot!(inspect(r##"test"##), @"/123/"); +} + +#[test] +fn test_new_range_non_leaf() { + assert_snapshot!(inspect(" + def jit_entry(v) = make_range_then_exit(v) + def make_range_then_exit(v) + range = (v..1) + super rescue range + end + jit_entry(0) + jit_entry(0) + jit_entry(0/1r) + "), @"(0/1)..1"); +} + +#[test] +fn test_raise_in_second_argument() { + assert_snapshot!(inspect(" + def write(hash, key) + hash[key] = raise rescue true + hash + end + write({}, :warmup) + write({}, :ok) + "), @"{ok: true}"); +} + +#[test] +fn test_struct_set() { + assert_snapshot!(inspect(" + C = Struct.new(:foo).new(1) + def test + C.foo = Object.new + 42 + end + r = [test, test] + C.freeze + r << begin + test + rescue FrozenError + :frozen_error + end + "), @"[42, 42, :frozen_error]"); +} + +#[test] +fn test_opt_case_dispatch() { + eval(" + def test(x) + case x + when :foo + true + else + false + end + end + test(:warmup) + "); + assert_contains_opcode("test", YARVINSN_opt_case_dispatch); + assert_snapshot!(inspect("[test(:foo), test(1)]"), @"[true, false]"); +} + +#[test] +fn test_stack_overflow() { + assert_snapshot!(inspect(" + def recurse(n) + return if n == 0 + recurse(n-1) + nil + end + recurse(2) + recurse(2) + begin + recurse(20_000) + rescue SystemStackError + end + "), @"nil"); +} + +#[test] +fn test_invokeblock() { + eval(" + def test + yield + end + test { 41 } + "); + assert_contains_opcode("test", YARVINSN_invokeblock); + assert_snapshot!(inspect("test { 42 }"), @"42"); +} + +#[test] +fn test_invokeblock_with_args() { + eval(" + def test(x, y) + yield x, y + end + test(1, 2) { |a, b| a + b } + "); + assert_contains_opcode("test", YARVINSN_invokeblock); + assert_snapshot!(inspect("test(1, 2) { |a, b| a + b }"), @"3"); +} + +#[test] +fn test_invokeblock_no_block_given() { + eval(" + def test + yield rescue :error + end + test { } + "); + assert_contains_opcode("test", YARVINSN_invokeblock); + assert_snapshot!(inspect("test"), @":error"); +} + +#[test] +fn test_invokeblock_multiple_yields() { + eval(" + def test + yield 1 + yield 2 + yield 3 + end + test { |x| x } + "); + assert_contains_opcode("test", YARVINSN_invokeblock); + assert_snapshot!(inspect(" + results = [] + test { |x| results << x } + results + "), @"[1, 2, 3]"); +} + +#[test] +fn test_ccall_variadic_with_multiple_args() { + eval(" + def test + a = [] + a.push(1, 2, 3) + a + end + test + "); + assert_contains_opcode("test", YARVINSN_opt_send_without_block); + assert_snapshot!(inspect("test"), @"[1, 2, 3]"); +} + +#[test] +fn test_ccall_variadic_with_no_args() { + eval(" + def test + a = [1] + a.push + end + test + "); + assert_contains_opcode("test", YARVINSN_opt_send_without_block); + assert_snapshot!(inspect("test"), @"[1]"); +} + +#[test] +fn test_ccall_variadic_with_no_args_causing_argument_error() { + eval(" + def test + format + rescue ArgumentError + :error + end + test + "); + assert_contains_opcode("test", YARVINSN_opt_send_without_block); + assert_snapshot!(inspect("test"), @":error"); +} + +#[test] +fn test_allocating_in_hir_c_method_is() { + eval(" + def a(f) = test(f) + def test(f) = (f.new if f) + def second = third + def third = nil + a(nil) + a(nil) + class Foo + def self.new = :k + end + second + "); + assert_contains_opcode("test", YARVINSN_opt_new); + assert_snapshot!(inspect("a(Foo)"), @":k"); +} + +#[test] +fn test_singleton_class_invalidation_annotated_ccall() { + assert_snapshot!(inspect(" + def define_singleton(obj, define) + if define + [nil].reverse_each do + class << obj + def ==(_) + true + end + end + end + end + false + end + def test(define) + obj = BasicObject.new + obj == define_singleton(obj, define) + end + result = [] + result << test(false) + result << test(true) + result + "), @"[false, true]"); +} + +#[test] +fn test_singleton_class_invalidation_optimized_variadic_ccall() { + assert_snapshot!(inspect(" + def define_singleton(arr, define) + if define + [nil].reverse_each do + class << arr + def push(x) + super(x * 1000) + end + end + end + end + 1 + end + def test(define) + arr = [] + val = define_singleton(arr, define) + arr.push(val) + arr[0] + end + result = [] + result << test(false) + result << test(true) + result + "), @"[1, 1000]"); +} + +#[test] +fn test_is_a_string_special_case() { + assert_snapshot!(inspect(r#" + def test(x) + x.is_a?(String) + end + test("foo") + [test("bar"), test(1), test(false), test(:foo), test([]), test({})] + "#), @"[true, false, false, false, false, false]"); +} + +#[test] +fn test_is_a_array_special_case() { + assert_snapshot!(inspect(" + def test(x) + x.is_a?(Array) + end + test([]) + [test([1,2,3]), test([]), test(1), test(false), test(:foo), test('foo'), test({})] + "), @"[true, true, false, false, false, false, false]"); +} + +#[test] +fn test_is_a_hash_special_case() { + assert_snapshot!(inspect(" + def test(x) + x.is_a?(Hash) + end + test({}) + [test({:a => 'b'}), test({}), test(1), test(false), test(:foo), test([]), test('foo')] + "), @"[true, true, false, false, false, false, false]"); +} + +#[test] +fn test_is_a_hash_subclass() { + assert_snapshot!(inspect(" + class MyHash < Hash + end + def test(x) + x.is_a?(Hash) + end + test({}) + test(MyHash.new) + "), @"true"); +} + +#[test] +fn test_is_a_normal_case() { + assert_snapshot!(inspect(r#" + class MyClass + end + def test(x) + x.is_a?(MyClass) + end + test("a") + [test(MyClass.new), test("a")] + "#), @"[true, false]"); +} + +#[test] +fn test_fixnum_div_zero() { + eval(" + def test(n) + n / 0 + rescue ZeroDivisionError => e + e.message + end + test(0) + "); + assert_contains_opcode("test", YARVINSN_opt_div); + assert_snapshot!(inspect(r#"test(0)"#), @r#""divided by 0""#); +} + +#[test] +fn test_invokesuper_with_local_written_by_blockiseq() { + assert_snapshot!(inspect(r#" + class A + def foo = "A" + end + class B < A + def foo + x = nil + [nil].each do |_| + x = super + end + x + end + end + def test = B.new.foo + test + test + "#), @r#""A""#); +} + +#[test] +fn test_max_iseq_versions() { + eval(&format!(" + TEST = -1 + def test = TEST + + # compile and invalidate MAX+1 times + i = 0 + while i < {MAX_ISEQ_VERSIONS} + 1 + test; test # compile a version + + Object.send(:remove_const, :TEST) + TEST = i + + i += 1 + end + ")); + + // It should not exceed MAX_ISEQ_VERSIONS + let iseq = get_method_iseq("self", "test"); + let payload = get_or_create_iseq_payload(iseq); + assert_eq!(payload.versions.len(), MAX_ISEQ_VERSIONS); + + // The last call should not discard the JIT code + assert!(matches!(unsafe { payload.versions.last().unwrap().as_ref() }.status, IseqStatus::Compiled(_))); +} + +#[test] +fn test_optional_arguments_side_exit() { + assert_snapshot!(inspect(" + def test(a = (def foo = nil)) = a + test + [test, (undef :foo), test(1)] + "), @"[:foo, nil, 1]"); +} + +#[test] +fn test_call_a_forwardable_method() { + assert_snapshot!(inspect(" + def test_root = forwardable + def forwardable(...) = Array.[](...) + test_root + test_root + "), @"[]"); +} + +#[test] +fn test_send_on_heap_object_in_spilled_arg() { + assert_snapshot!(inspect(" + def entry(a1, a2, a3, a4, a5, a6, a7, a8, a9) + a9.itself.class + end + entry(1, 2, 3, 4, 5, 6, 7, 8, {}) + entry(1, 2, 3, 4, 5, 6, 7, 8, {}) + "), @"Hash"); +} + +#[test] +fn test_send_splat() { + assert_snapshot!(inspect(" + def test(a, b) = [a, b] + def entry(arr) = test(*arr) + entry([1, 2]) + entry([1, 2]) + "), @"[1, 2]"); +} + +#[test] +fn test_send_kwarg() { + assert_snapshot!(inspect(" + def test(a:, b:) = [a, b] + def entry = test(b: 2, a: 1) + entry + entry + "), @"[1, 2]"); +} + +#[test] +fn test_spilled_method_args() { + assert_snapshot!(inspect(" + 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 + test + "), @"55"); +} + +#[test] +fn test_spilled_method_args_first_and_last() { + assert_snapshot!(inspect(" + def a(n1,n2,n3,n4,n5,n6,n7,n8,n9) = n1+n9 + a(2,0,0,0,0,0,0,0,-1) + a(2,0,0,0,0,0,0,0,-1) + "), @"1"); +} + +#[test] +fn test_spilled_method_args_last() { + assert_snapshot!(inspect(" + def a(n1,n2,n3,n4,n5,n6,n7,n8) = n8 + a(1,1,1,1,1,1,1,0) + a(1,1,1,1,1,1,1,0) + "), @"0"); +} + +#[test] +fn test_spilled_method_args_self() { + assert_snapshot!(inspect(" + def a(n1,n2,n3,n4,n5,n6,n7,n8) = self + a(1,0,0,0,0,0,0,0).to_s + a(1,0,0,0,0,0,0,0).to_s + "), @r#""main""#); +} + +#[test] +fn test_spilled_param_new_array() { + assert_snapshot!(inspect(" + def a(n1,n2,n3,n4,n5,n6,n7,n8) = [n8] + a(0,0,0,0,0,0,0, :ok) + a(0,0,0,0,0,0,0, :ok) + "), @"[:ok]"); +} + +#[test] +fn test_forty_param_method() { + assert_snapshot!(inspect(" + def foo(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,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) + 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) + "), @"1"); +} + +#[test] +fn test_toplevel_local_after_eval() { + assert_snapshot!(inspect(" + a = 1 + b = 2 + eval('b = 3') + c = 4 + [a, b, c] + "), @"[1, 3, 4]"); +} + +#[test] +fn test_send_exit_with_uninitialized_locals() { + assert_snapshot!(inspect(" + def entry(init) + function_stub_exit(init) + end + + def function_stub_exit(init) + uninitialized_local = 1 if init + uninitialized_local + end + + entry(true) + entry(false) + "), @"nil"); +} + +#[test] +fn test_invokebuiltin_dir_glob() { + assert_snapshot!(inspect(r#" + def test = Dir.glob(".") + test + test + "#), @r#"["."]"#); +} + +#[test] +fn test_profiled_fact() { + assert_snapshot!(inspect(" + def fact(n) + if n == 0 + return 1 + end + return n * fact(n-1) + end + fact(1) + [fact(0), fact(3), fact(6)] + "), @"[1, 6, 720]"); +} + +#[test] +fn test_profiled_fib() { + assert_snapshot!(inspect(" + def fib(n) + if n < 2 + return n + end + return fib(n-1) + fib(n-2) + end + fib(3) + [fib(0), fib(3), fib(4)] + "), @"[0, 2, 3]"); +} + +#[test] +fn test_single_ractor_mode_invalidation() { + assert_snapshot!(inspect(r#" + C = Object.new + + def test + C + rescue Ractor::IsolationError + "errored but not crashed" + end + + test + test + + Ractor.new { + test + }.value + "#), @r#""errored but not crashed""#); +} + +#[test] +fn test_ivar_attr_reader_optimization_with_multi_ractor_mode() { + assert_snapshot!(inspect(" + class Foo + class << self + attr_accessor :bar + + def get_bar + bar + rescue Ractor::IsolationError + 42 + end + end + end + + Foo.bar = [] + + def test + Foo.get_bar + end + + test + test + + Ractor.new { test }.value + "), @"42"); +} + +#[test] +fn test_ivar_get_with_multi_ractor_mode() { + assert_snapshot!(inspect(" + class Foo + def self.set_bar + @bar = [] + end + + def self.bar + @bar + rescue Ractor::IsolationError + 42 + end + end + + Foo.set_bar + + def test + Foo.bar + end + + test + test + + Ractor.new { test }.value + "), @"42"); +} + +#[test] +fn test_ivar_get_with_already_multi_ractor_mode() { + assert_snapshot!(inspect(" + class Foo + def self.set_bar + @bar = [] + end + + def self.bar + @bar + rescue Ractor::IsolationError + 42 + end + end + + Foo.set_bar + r = Ractor.new { + Ractor.receive + Foo.bar + } + + Foo.bar + Foo.bar + + r << :go + r.value + "), @"42"); +} + +#[test] +fn test_ivar_set_with_multi_ractor_mode() { + assert_snapshot!(inspect(" + class Foo + def self.bar + _foo = 1 + _bar = 2 + begin + @bar = _foo + _bar + rescue Ractor::IsolationError + 42 + end + end + end + + def test + Foo.bar + end + + test + test + + Ractor.new { test }.value + "), @"42"); +} + +#[test] +fn test_global_tracepoint() { + assert_snapshot!(inspect(" + def foo = 1 + + foo + foo + + called = false + + tp = TracePoint.new(:return) { |event| + if event.method_id == :foo + called = true + end + } + tp.enable do + foo + end + called + "), @"true"); +} + +#[test] +fn test_local_tracepoint() { + assert_snapshot!(inspect(" + def foo = 1 + + foo + foo + + called = false + + tp = TracePoint.new(:return) { |_| called = true } + tp.enable(target: method(:foo)) do + foo + end + called + "), @"true"); +} diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index cf2371ef5a..b6dcbd67c2 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -19,7 +19,7 @@ use crate::profile::{TypeDistributionSummary, ProfiledType}; use crate::stats::Counter; use SendFallbackReason::*; -mod tests; +pub(crate) mod tests; mod opt_tests; /// An index of an [`Insn`] in a [`Function`]. This is a popular |
