mirror of
https://github.com/ruby/ruby.git
synced 2025-08-15 13:39:04 +02:00
1465 lines
31 KiB
Ruby
1465 lines
31 KiB
Ruby
# frozen_string_literal: true
|
|
#
|
|
# This set of tests can be run with:
|
|
# make test-all TESTS=test/ruby/test_zjit.rb
|
|
|
|
require 'test/unit'
|
|
require 'envutil'
|
|
require_relative '../lib/jit_support'
|
|
return unless JITSupport.zjit_supported?
|
|
|
|
class TestZJIT < Test::Unit::TestCase
|
|
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_setlocal
|
|
assert_compiles '3', %q{
|
|
def test(n)
|
|
m = n
|
|
m
|
|
end
|
|
test(3)
|
|
}
|
|
end
|
|
|
|
def test_setlocal_on_eval
|
|
assert_compiles '1', %q{
|
|
@b = binding
|
|
eval('a = 1', @b)
|
|
eval('a', @b)
|
|
}
|
|
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_read_local_written_by_children_iseqs
|
|
omit "This test fails right now because Send doesn't compile."
|
|
|
|
assert_compiles '[1, 2]', %q{
|
|
def test
|
|
l1 = nil
|
|
l2 = nil
|
|
tap do |_|
|
|
l1 = 1
|
|
tap do |_|
|
|
l2 = 2
|
|
end
|
|
end
|
|
|
|
[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_invokebuiltin
|
|
omit 'Test fails at the moment due to not handling optional parameters'
|
|
assert_compiles '["."]', %q{
|
|
def test = Dir.glob(".")
|
|
test
|
|
}
|
|
end
|
|
|
|
def test_invokebuiltin_delegate
|
|
assert_compiles '[[], true]', %q{
|
|
def test = [].clone(freeze: true)
|
|
r = test
|
|
[r, r.frozen?]
|
|
}
|
|
end
|
|
|
|
def test_opt_plus_const
|
|
assert_compiles '3', %q{
|
|
def test = 1 + 2
|
|
test # profile opt_plus
|
|
test
|
|
}, call_threshold: 2
|
|
end
|
|
|
|
def test_opt_plus_fixnum
|
|
assert_compiles '3', %q{
|
|
def test(a, b) = a + b
|
|
test(0, 1) # profile opt_plus
|
|
test(1, 2)
|
|
}, call_threshold: 2
|
|
end
|
|
|
|
def test_opt_plus_chain
|
|
assert_compiles '6', %q{
|
|
def test(a, b, c) = a + b + c
|
|
test(0, 1, 2) # profile opt_plus
|
|
test(1, 2, 3)
|
|
}, call_threshold: 2
|
|
end
|
|
|
|
def test_opt_plus_left_imm
|
|
assert_compiles '3', %q{
|
|
def test(a) = 1 + a
|
|
test(1) # profile opt_plus
|
|
test(2)
|
|
}, call_threshold: 2
|
|
end
|
|
|
|
def test_opt_plus_type_guard_exit
|
|
assert_compiles '[3, 3.0]', %q{
|
|
def test(a) = 1 + a
|
|
test(1) # profile opt_plus
|
|
[test(2), test(2.0)]
|
|
}, call_threshold: 2
|
|
end
|
|
|
|
def test_opt_plus_type_guard_exit_with_locals
|
|
assert_compiles '[6, 6.0]', %q{
|
|
def test(a)
|
|
local = 3
|
|
1 + a + local
|
|
end
|
|
test(1) # profile opt_plus
|
|
[test(2), test(2.0)]
|
|
}, call_threshold: 2
|
|
end
|
|
|
|
def test_opt_plus_type_guard_nested_exit
|
|
assert_compiles '[4, 4.0]', %q{
|
|
def side_exit(n) = 1 + n
|
|
def jit_frame(n) = 1 + side_exit(n)
|
|
def entry(n) = jit_frame(n)
|
|
entry(2) # profile send
|
|
[entry(2), entry(2.0)]
|
|
}, call_threshold: 2
|
|
end
|
|
|
|
def test_opt_plus_type_guard_nested_exit_with_locals
|
|
assert_compiles '[9, 9.0]', %q{
|
|
def side_exit(n)
|
|
local = 2
|
|
1 + n + local
|
|
end
|
|
def jit_frame(n)
|
|
local = 3
|
|
1 + side_exit(n) + local
|
|
end
|
|
def entry(n) = jit_frame(n)
|
|
entry(2) # profile send
|
|
[entry(2), entry(2.0)]
|
|
}, call_threshold: 2
|
|
end
|
|
|
|
# Test argument ordering
|
|
def test_opt_minus
|
|
assert_compiles '2', %q{
|
|
def test(a, b) = a - b
|
|
test(2, 1) # profile opt_minus
|
|
test(6, 4)
|
|
}, call_threshold: 2
|
|
end
|
|
|
|
def test_opt_mult
|
|
assert_compiles '6', %q{
|
|
def test(a, b) = a * b
|
|
test(1, 2) # profile opt_mult
|
|
test(2, 3)
|
|
}, call_threshold: 2
|
|
end
|
|
|
|
def test_opt_mult_overflow
|
|
assert_compiles '[6, -6, 9671406556917033397649408, -9671406556917033397649408, 21267647932558653966460912964485513216]', %q{
|
|
def test(a, b)
|
|
a * b
|
|
end
|
|
test(1, 1) # profile opt_mult
|
|
|
|
r1 = test(2, 3)
|
|
r2 = test(2, -3)
|
|
r3 = test(2 << 40, 2 << 41)
|
|
r4 = test(2 << 40, -2 << 41)
|
|
r5 = test(1 << 62, 1 << 62)
|
|
|
|
[r1, r2, r3, r4, r5]
|
|
}, call_threshold: 2
|
|
end
|
|
|
|
def test_opt_eq
|
|
assert_compiles '[true, false]', %q{
|
|
def test(a, b) = a == b
|
|
test(0, 2) # profile opt_eq
|
|
[test(1, 1), test(0, 1)]
|
|
}, insns: [:opt_eq], call_threshold: 2
|
|
end
|
|
|
|
def test_opt_eq_with_minus_one
|
|
assert_compiles '[false, true]', %q{
|
|
def test(a) = a == -1
|
|
test(1) # profile opt_eq
|
|
[test(0), test(-1)]
|
|
}, insns: [:opt_eq], call_threshold: 2
|
|
end
|
|
|
|
def test_opt_neq_dynamic
|
|
# TODO(max): Don't split this test; instead, run all tests with and without
|
|
# profiling.
|
|
assert_compiles '[false, true]', %q{
|
|
def test(a, b) = a != b
|
|
test(0, 2) # profile opt_neq
|
|
[test(1, 1), test(0, 1)]
|
|
}, insns: [:opt_neq], call_threshold: 1
|
|
end
|
|
|
|
def test_opt_neq_fixnum
|
|
assert_compiles '[false, true]', %q{
|
|
def test(a, b) = a != b
|
|
test(0, 2) # profile opt_neq
|
|
[test(1, 1), test(0, 1)]
|
|
}, call_threshold: 2
|
|
end
|
|
|
|
def test_opt_lt
|
|
assert_compiles '[true, false, false]', %q{
|
|
def test(a, b) = a < b
|
|
test(2, 3) # profile opt_lt
|
|
[test(0, 1), test(0, 0), test(1, 0)]
|
|
}, insns: [:opt_lt], call_threshold: 2
|
|
end
|
|
|
|
def test_opt_lt_with_literal_lhs
|
|
assert_compiles '[false, false, true]', %q{
|
|
def test(n) = 2 < n
|
|
test(2) # profile opt_lt
|
|
[test(1), test(2), test(3)]
|
|
}, insns: [:opt_lt], call_threshold: 2
|
|
end
|
|
|
|
def test_opt_le
|
|
assert_compiles '[true, true, false]', %q{
|
|
def test(a, b) = a <= b
|
|
test(2, 3) # profile opt_le
|
|
[test(0, 1), test(0, 0), test(1, 0)]
|
|
}, insns: [:opt_le], call_threshold: 2
|
|
end
|
|
|
|
def test_opt_gt
|
|
assert_compiles '[false, false, true]', %q{
|
|
def test(a, b) = a > b
|
|
test(2, 3) # profile opt_gt
|
|
[test(0, 1), test(0, 0), test(1, 0)]
|
|
}, insns: [:opt_gt], call_threshold: 2
|
|
end
|
|
|
|
def test_opt_empty_p
|
|
assert_compiles('[false, false, true]', <<~RUBY, insns: [:opt_empty_p])
|
|
def test(x) = x.empty?
|
|
return test([1]), test("1"), test({})
|
|
RUBY
|
|
end
|
|
|
|
def test_opt_succ
|
|
assert_compiles('[0, "B"]', <<~RUBY, insns: [:opt_succ])
|
|
def test(obj) = obj.succ
|
|
return test(-1), test("A")
|
|
RUBY
|
|
end
|
|
|
|
def test_opt_and
|
|
assert_compiles('[1, [3, 2, 1]]', <<~RUBY, insns: [:opt_and])
|
|
def test(x, y) = x & y
|
|
return test(0b1101, 3), test([3, 2, 1, 4], [8, 1, 2, 3])
|
|
RUBY
|
|
end
|
|
|
|
def test_opt_or
|
|
assert_compiles('[11, [3, 2, 1]]', <<~RUBY, insns: [:opt_or])
|
|
def test(x, y) = x | y
|
|
return test(0b1000, 3), test([3, 2, 1], [1, 2, 3])
|
|
RUBY
|
|
end
|
|
|
|
def test_fixnum_and
|
|
assert_compiles '1', %q{
|
|
def test(a, b) = a & b
|
|
test(2, 2)
|
|
test(2, 2)
|
|
test(5, 3)
|
|
}, call_threshold: 2, insns: [:opt_and]
|
|
end
|
|
|
|
def test_fixnum_and_side_exit
|
|
assert_compiles 'false', %q{
|
|
def test(a, b) = a & b
|
|
test(2, 2)
|
|
test(2, 2)
|
|
test(true, false)
|
|
}, call_threshold: 2, insns: [:opt_and]
|
|
end
|
|
|
|
def test_fixnum_or
|
|
assert_compiles '3', %q{
|
|
def test(a, b) = a | b
|
|
test(5, 3)
|
|
test(5, 3)
|
|
test(1, 2)
|
|
}, call_threshold: 2, insns: [:opt_or]
|
|
end
|
|
|
|
def test_fixnum_or_side_exit
|
|
assert_compiles 'true', %q{
|
|
def test(a, b) = a | b
|
|
test(2, 2)
|
|
test(2, 2)
|
|
test(true, false)
|
|
}, call_threshold: 2, insns: [:opt_or]
|
|
end
|
|
|
|
def test_fixnum_mul
|
|
assert_compiles '12', %q{
|
|
C = 3
|
|
def test(n) = C * n
|
|
test(4)
|
|
test(4)
|
|
test(4)
|
|
}, call_threshold: 2, insns: [:opt_mult]
|
|
end
|
|
|
|
def test_opt_not
|
|
assert_compiles('[true, true, false]', <<~RUBY, insns: [:opt_not])
|
|
def test(obj) = !obj
|
|
return test(nil), test(false), test(0)
|
|
RUBY
|
|
end
|
|
|
|
def test_opt_regexpmatch2
|
|
assert_compiles('[1, nil]', <<~RUBY, insns: [:opt_regexpmatch2])
|
|
def test(haystack) = /needle/ =~ haystack
|
|
return test("kneedle"), test("")
|
|
RUBY
|
|
end
|
|
|
|
def test_opt_ge
|
|
assert_compiles '[false, true, true]', %q{
|
|
def test(a, b) = a >= b
|
|
test(2, 3) # profile opt_ge
|
|
[test(0, 1), test(0, 0), test(1, 0)]
|
|
}, insns: [:opt_ge], call_threshold: 2
|
|
end
|
|
|
|
def test_opt_hash_freeze
|
|
assert_compiles '{}', <<~RUBY, insns: [:opt_hash_freeze]
|
|
def test = {}.freeze
|
|
test
|
|
RUBY
|
|
end
|
|
|
|
def test_opt_ary_freeze
|
|
assert_compiles '[]', <<~RUBY, insns: [:opt_ary_freeze]
|
|
def test = [].freeze
|
|
test
|
|
RUBY
|
|
end
|
|
|
|
def test_opt_str_freeze
|
|
assert_compiles '""', <<~RUBY, insns: [:opt_str_freeze]
|
|
def test = "".freeze
|
|
test
|
|
RUBY
|
|
end
|
|
|
|
def test_opt_str_uminus
|
|
assert_compiles '""', <<~RUBY, insns: [:opt_str_uminus]
|
|
def test = -""
|
|
test
|
|
RUBY
|
|
end
|
|
|
|
def test_new_array_empty
|
|
assert_compiles '[]', %q{
|
|
def test = []
|
|
test
|
|
}, insns: [:newarray]
|
|
end
|
|
|
|
def test_new_array_nonempty
|
|
assert_compiles '[5]', %q{
|
|
def a = 5
|
|
def test = [a]
|
|
test
|
|
}
|
|
end
|
|
|
|
def test_new_array_order
|
|
assert_compiles '[3, 2, 1]', %q{
|
|
def a = 3
|
|
def b = 2
|
|
def c = 1
|
|
def test = [a, b, c]
|
|
test
|
|
}
|
|
end
|
|
|
|
def test_array_dup
|
|
assert_compiles '[1, 2, 3]', %q{
|
|
def test = [1,2,3]
|
|
test
|
|
}
|
|
end
|
|
|
|
def test_new_range_inclusive
|
|
assert_compiles '1..5', %q{
|
|
def test(a, b) = a..b
|
|
test(1, 5)
|
|
}
|
|
end
|
|
|
|
def test_new_range_exclusive
|
|
assert_compiles '1...5', %q{
|
|
def test(a, b) = a...b
|
|
test(1, 5)
|
|
}
|
|
end
|
|
|
|
def test_new_range_with_literal
|
|
assert_compiles '3..10', %q{
|
|
def test(n) = n..10
|
|
test(3)
|
|
}
|
|
end
|
|
|
|
def test_if
|
|
assert_compiles '[0, nil]', %q{
|
|
def test(n)
|
|
if n < 5
|
|
0
|
|
end
|
|
end
|
|
[test(3), test(7)]
|
|
}
|
|
end
|
|
|
|
def test_if_else
|
|
assert_compiles '[0, 1]', %q{
|
|
def test(n)
|
|
if n < 5
|
|
0
|
|
else
|
|
1
|
|
end
|
|
end
|
|
[test(3), test(7)]
|
|
}
|
|
end
|
|
|
|
def test_if_else_params
|
|
assert_compiles '[1, 20]', %q{
|
|
def test(n, a, b)
|
|
if n < 5
|
|
a
|
|
else
|
|
b
|
|
end
|
|
end
|
|
[test(3, 1, 2), test(7, 10, 20)]
|
|
}
|
|
end
|
|
|
|
def test_if_else_nested
|
|
assert_compiles '[3, 8, 9, 14]', %q{
|
|
def test(a, b, c, d, e)
|
|
if 2 < a
|
|
if a < 4
|
|
b
|
|
else
|
|
c
|
|
end
|
|
else
|
|
if a < 0
|
|
d
|
|
else
|
|
e
|
|
end
|
|
end
|
|
end
|
|
[
|
|
test(-1, 1, 2, 3, 4),
|
|
test( 0, 5, 6, 7, 8),
|
|
test( 3, 9, 10, 11, 12),
|
|
test( 5, 13, 14, 15, 16),
|
|
]
|
|
}
|
|
end
|
|
|
|
def test_if_else_chained
|
|
assert_compiles '[12, 11, 21]', %q{
|
|
def test(a)
|
|
(if 2 < a then 1 else 2 end) + (if a < 4 then 10 else 20 end)
|
|
end
|
|
[test(0), test(3), test(5)]
|
|
}
|
|
end
|
|
|
|
def test_if_elsif_else
|
|
assert_compiles '[0, 2, 1]', %q{
|
|
def test(n)
|
|
if n < 5
|
|
0
|
|
elsif 8 < n
|
|
1
|
|
else
|
|
2
|
|
end
|
|
end
|
|
[test(3), test(7), test(9)]
|
|
}
|
|
end
|
|
|
|
def test_ternary_operator
|
|
assert_compiles '[1, 20]', %q{
|
|
def test(n, a, b)
|
|
n < 5 ? a : b
|
|
end
|
|
[test(3, 1, 2), test(7, 10, 20)]
|
|
}
|
|
end
|
|
|
|
def test_ternary_operator_nested
|
|
assert_compiles '[2, 21]', %q{
|
|
def test(n, a, b)
|
|
(n < 5 ? a : b) + 1
|
|
end
|
|
[test(3, 1, 2), test(7, 10, 20)]
|
|
}
|
|
end
|
|
|
|
def test_while_loop
|
|
assert_compiles '10', %q{
|
|
def test(n)
|
|
i = 0
|
|
while i < n
|
|
i = i + 1
|
|
end
|
|
i
|
|
end
|
|
test(10)
|
|
}
|
|
end
|
|
|
|
def test_while_loop_chain
|
|
assert_compiles '[135, 270]', %q{
|
|
def test(n)
|
|
i = 0
|
|
while i < n
|
|
i = i + 1
|
|
end
|
|
while i < n * 10
|
|
i = i * 3
|
|
end
|
|
i
|
|
end
|
|
[test(5), test(10)]
|
|
}
|
|
end
|
|
|
|
def test_while_loop_nested
|
|
assert_compiles '[0, 4, 12]', %q{
|
|
def test(n, m)
|
|
i = 0
|
|
while i < n
|
|
j = 0
|
|
while j < m
|
|
j += 2
|
|
end
|
|
i += j
|
|
end
|
|
i
|
|
end
|
|
[test(0, 0), test(1, 3), test(10, 5)]
|
|
}
|
|
end
|
|
|
|
def test_while_loop_if_else
|
|
assert_compiles '[9, -1]', %q{
|
|
def test(n)
|
|
i = 0
|
|
while i < n
|
|
if n >= 10
|
|
return -1
|
|
else
|
|
i = i + 1
|
|
end
|
|
end
|
|
i
|
|
end
|
|
[test(9), test(10)]
|
|
}
|
|
end
|
|
|
|
def test_if_while_loop
|
|
assert_compiles '[9, 12]', %q{
|
|
def test(n)
|
|
i = 0
|
|
if n < 10
|
|
while i < n
|
|
i += 1
|
|
end
|
|
else
|
|
while i < n
|
|
i += 3
|
|
end
|
|
end
|
|
i
|
|
end
|
|
[test(9), test(10)]
|
|
}
|
|
end
|
|
|
|
def test_live_reg_past_ccall
|
|
assert_compiles '2', %q{
|
|
def callee = 1
|
|
def test = callee + callee
|
|
test
|
|
}
|
|
end
|
|
|
|
def test_method_call
|
|
assert_compiles '12', %q{
|
|
def callee(a, b)
|
|
a - b
|
|
end
|
|
|
|
def test
|
|
callee(4, 2) + 10
|
|
end
|
|
|
|
test # profile test
|
|
test
|
|
}, call_threshold: 2
|
|
end
|
|
|
|
def test_recursive_fact
|
|
assert_compiles '[1, 6, 720]', %q{
|
|
def fact(n)
|
|
if n == 0
|
|
return 1
|
|
end
|
|
return n * fact(n-1)
|
|
end
|
|
[fact(0), fact(3), fact(6)]
|
|
}
|
|
end
|
|
|
|
def test_profiled_fact
|
|
assert_compiles '[1, 6, 720]', %q{
|
|
def fact(n)
|
|
if n == 0
|
|
return 1
|
|
end
|
|
return n * fact(n-1)
|
|
end
|
|
fact(1) # profile fact
|
|
[fact(0), fact(3), fact(6)]
|
|
}, call_threshold: 3, num_profiles: 2
|
|
end
|
|
|
|
def test_recursive_fib
|
|
assert_compiles '[0, 2, 3]', %q{
|
|
def fib(n)
|
|
if n < 2
|
|
return n
|
|
end
|
|
return fib(n-1) + fib(n-2)
|
|
end
|
|
[fib(0), fib(3), fib(4)]
|
|
}
|
|
end
|
|
|
|
def test_profiled_fib
|
|
assert_compiles '[0, 2, 3]', %q{
|
|
def fib(n)
|
|
if n < 2
|
|
return n
|
|
end
|
|
return fib(n-1) + fib(n-2)
|
|
end
|
|
fib(3) # profile fib
|
|
[fib(0), fib(3), fib(4)]
|
|
}, call_threshold: 5, num_profiles: 3
|
|
end
|
|
|
|
def test_spilled_basic_block_args
|
|
assert_compiles '55', %q{
|
|
def test(n1, n2)
|
|
n3 = 3
|
|
n4 = 4
|
|
n5 = 5
|
|
n6 = 6
|
|
n7 = 7
|
|
n8 = 8
|
|
n9 = 9
|
|
n10 = 10
|
|
if n1 < n2
|
|
n1 + n2 + n3 + n4 + n5 + n6 + n7 + n8 + n9 + n10
|
|
end
|
|
end
|
|
test(1, 2)
|
|
}
|
|
end
|
|
|
|
def test_spilled_method_args
|
|
assert_runs '55', %q{
|
|
def foo(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10)
|
|
n1 + n2 + n3 + n4 + n5 + n6 + n7 + n8 + n9 + n10
|
|
end
|
|
|
|
def test
|
|
foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
|
end
|
|
|
|
test
|
|
}
|
|
|
|
assert_compiles '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)
|
|
}
|
|
|
|
assert_compiles '0', %q{
|
|
def a(n1,n2,n3,n4,n5,n6,n7,n8) = n8
|
|
a(1,1,1,1,1,1,1,0)
|
|
}
|
|
|
|
# self param with spilled param
|
|
assert_compiles '"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
|
|
assert_compiles '[:ok]', %q{
|
|
def a(n1,n2,n3,n4,n5,n6,n7,n8) = [n8]
|
|
a(0,0,0,0,0,0,0, :ok)
|
|
}
|
|
end
|
|
|
|
def test_forty_param_method
|
|
# This used to a trigger a miscomp on A64 due
|
|
# to a memory displacement larger than 9 bits.
|
|
assert_compiles '1', %Q{
|
|
def foo(#{'_,' * 39} n40) = n40
|
|
|
|
foo(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1)
|
|
}
|
|
end
|
|
|
|
|
|
def test_opt_aref_with
|
|
assert_compiles ':ok', %q{
|
|
def aref_with(hash) = hash["key"]
|
|
|
|
aref_with({ "key" => :ok })
|
|
}
|
|
end
|
|
|
|
def test_putself
|
|
assert_compiles '3', %q{
|
|
class Integer
|
|
def minus(a)
|
|
self - a
|
|
end
|
|
end
|
|
5.minus(2)
|
|
}
|
|
end
|
|
|
|
def test_getinstancevariable
|
|
assert_compiles 'nil', %q{
|
|
def test() = @foo
|
|
|
|
test()
|
|
}
|
|
assert_compiles '3', %q{
|
|
@foo = 3
|
|
def test() = @foo
|
|
|
|
test()
|
|
}
|
|
end
|
|
|
|
def test_setinstancevariable
|
|
assert_compiles '1', %q{
|
|
def test() = @foo = 1
|
|
|
|
test()
|
|
@foo
|
|
}
|
|
end
|
|
|
|
def test_uncached_getconstant_path
|
|
assert_compiles RUBY_COPYRIGHT.dump, %q{
|
|
def test = RUBY_COPYRIGHT
|
|
test
|
|
}, call_threshold: 1, insns: [:opt_getconstant_path]
|
|
end
|
|
|
|
def test_getconstant_path_autoload
|
|
# A constant-referencing expression can run arbitrary code through Kernel#autoload.
|
|
Dir.mktmpdir('autoload') do |tmpdir|
|
|
autoload_path = File.join(tmpdir, 'test_getconstant_path_autoload.rb')
|
|
File.write(autoload_path, 'X = RUBY_COPYRIGHT')
|
|
|
|
assert_compiles RUBY_COPYRIGHT.dump, %Q{
|
|
Object.autoload(:X, #{File.realpath(autoload_path).inspect})
|
|
def test = X
|
|
test
|
|
}, call_threshold: 1, insns: [:opt_getconstant_path]
|
|
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_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'",
|
|
"-e:3:in 'Object#entry'",
|
|
"-e:5:in 'block in <main>'",
|
|
"-e:6:in '<main>'",
|
|
]
|
|
assert_compiles backtrace.inspect, %q{
|
|
def jit_frame2 = caller # 1
|
|
def jit_frame1 = jit_frame2 # 2
|
|
def entry = jit_frame1 # 3
|
|
entry # profile send # 4
|
|
entry # 5
|
|
}, call_threshold: 2
|
|
end
|
|
|
|
def test_bop_invalidation
|
|
omit 'Invalidation on BOP redefinition is not implemented yet'
|
|
assert_compiles '', %q{
|
|
def test
|
|
eval(<<~RUBY)
|
|
class Integer
|
|
def +(_) = 100
|
|
end
|
|
RUBY
|
|
1 + 2
|
|
end
|
|
test
|
|
}
|
|
end
|
|
|
|
def test_defined_yield
|
|
assert_compiles "nil", "defined?(yield)"
|
|
assert_compiles '[nil, nil, "yield"]', %q{
|
|
def test = defined?(yield)
|
|
[test, test, test{}]
|
|
}, call_threshold: 2, insns: [:defined]
|
|
end
|
|
|
|
def test_defined_yield_from_block
|
|
# This will do some EP hopping to find the local EP,
|
|
# so it's slightly different than doing it outside of a block.
|
|
|
|
omit 'Test fails at the moment due to missing Send codegen'
|
|
|
|
assert_compiles '[nil, nil, "yield"]', %q{
|
|
def test
|
|
yield_self { yield_self { defined?(yield) } }
|
|
end
|
|
|
|
[test, test, test{}]
|
|
}, call_threshold: 2, insns: [:defined]
|
|
end
|
|
|
|
def test_invokeblock_without_block_after_jit_call
|
|
assert_compiles '"no block given (yield)"', %q{
|
|
def test(*arr, &b)
|
|
arr.class
|
|
yield
|
|
end
|
|
begin
|
|
test
|
|
rescue => e
|
|
e.message
|
|
end
|
|
}
|
|
end
|
|
|
|
def test_putspecialobject_vm_core_and_cbase
|
|
assert_compiles '10', %q{
|
|
def test
|
|
alias bar test
|
|
10
|
|
end
|
|
|
|
test
|
|
bar
|
|
}, insns: [:putspecialobject]
|
|
end
|
|
|
|
def test_putspecialobject_const_base
|
|
assert_compiles '1', %q{
|
|
Foo = 1
|
|
|
|
def test = Foo
|
|
|
|
# First call: populates the constant cache
|
|
test
|
|
# Second call: triggers ZJIT compilation with warm cache
|
|
# RubyVM::ZJIT.assert_compiles will panic if this fails to compile
|
|
test
|
|
}, call_threshold: 2
|
|
end
|
|
|
|
def test_branchnil
|
|
assert_compiles '[2, nil]', %q{
|
|
def test(x)
|
|
x&.succ
|
|
end
|
|
[test(1), test(nil)]
|
|
}, call_threshold: 1, insns: [:branchnil]
|
|
end
|
|
|
|
def test_nil_nil
|
|
assert_compiles 'true', %q{
|
|
def test = nil.nil?
|
|
test
|
|
}, insns: [:opt_nil_p]
|
|
end
|
|
|
|
def test_non_nil_nil
|
|
assert_compiles 'false', %q{
|
|
def test = 1.nil?
|
|
test
|
|
}, insns: [:opt_nil_p]
|
|
end
|
|
|
|
# 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
|
|
insn_names = RubyVM::INSTRUCTION_NAMES
|
|
zjit, others = insn_names.map.with_index.partition { |name, _| name.start_with?('zjit_') }
|
|
zjit_indexes = zjit.map(&:last)
|
|
other_indexes = others.map(&:last)
|
|
zjit_indexes.product(other_indexes).each do |zjit_index, other_index|
|
|
assert zjit_index > other_index, "'#{insn_names[zjit_index]}' at #{zjit_index} "\
|
|
"must be defined after '#{insn_names[other_index]}' at #{other_index}"
|
|
end
|
|
end
|
|
|
|
def test_require_rubygems
|
|
assert_runs 'true', %q{
|
|
require 'rubygems'
|
|
}, call_threshold: 2
|
|
end
|
|
|
|
def test_require_rubygems_with_auto_compact
|
|
assert_runs 'true', %q{
|
|
GC.auto_compact = true
|
|
require 'rubygems'
|
|
}, call_threshold: 2
|
|
end
|
|
|
|
def test_stats
|
|
assert_runs 'true', %q{
|
|
def test = 1
|
|
test
|
|
RubyVM::ZJIT.stats[:zjit_insns_count] > 0
|
|
}, stats: true
|
|
end
|
|
|
|
def test_zjit_option_uses_array_each_in_ruby
|
|
omit 'ZJIT wrongly compiles Array#each, so it is disabled for now'
|
|
assert_runs '"<internal:array>"', %q{
|
|
Array.instance_method(:each).source_location&.first
|
|
}
|
|
end
|
|
|
|
def test_profile_under_nested_jit_call
|
|
assert_compiles '[nil, nil, 3]', %q{
|
|
def profile
|
|
1 + 2
|
|
end
|
|
|
|
def 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_redefinition
|
|
assert_runs '[3, :+, 100]', %q{
|
|
def test
|
|
1 + 2
|
|
end
|
|
|
|
test # profile opt_plus
|
|
[test, Integer.class_eval { def +(_) = 100 }, test]
|
|
}, call_threshold: 2
|
|
end
|
|
|
|
def test_bop_redefinition_with_adjacent_patch_points
|
|
assert_runs '[15, :+, 100]', %q{
|
|
def test
|
|
1 + 2 + 3 + 4 + 5
|
|
end
|
|
|
|
test # profile opt_plus
|
|
[test, Integer.class_eval { def +(_) = 100 }, test]
|
|
}, call_threshold: 2
|
|
end
|
|
|
|
# ZJIT currently only generates a MethodRedefined patch point when the method
|
|
# is called on the top-level self.
|
|
def test_method_redefinition_with_top_self
|
|
assert_runs '["original", "redefined"]', %q{
|
|
def foo
|
|
"original"
|
|
end
|
|
|
|
def test = foo
|
|
|
|
test; test
|
|
|
|
result1 = test
|
|
|
|
# Redefine the method
|
|
def foo
|
|
"redefined"
|
|
end
|
|
|
|
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_string_bytesize_with_guard
|
|
assert_compiles '5', %q{
|
|
def test(str)
|
|
str.bytesize
|
|
end
|
|
|
|
test('hello')
|
|
test('world')
|
|
}, call_threshold: 2
|
|
end
|
|
|
|
def test_nil_value_nil_opt_with_guard
|
|
assert_compiles 'true', %q{
|
|
def test(val) = val.nil?
|
|
|
|
test(nil)
|
|
test(nil)
|
|
}, call_threshold: 2, insns: [:opt_nil_p]
|
|
end
|
|
|
|
def test_nil_value_nil_opt_with_guard_side_exit
|
|
assert_compiles 'false', %q{
|
|
def test(val) = val.nil?
|
|
|
|
test(nil)
|
|
test(nil)
|
|
test(1)
|
|
}, call_threshold: 2, insns: [:opt_nil_p]
|
|
end
|
|
|
|
def test_true_nil_opt_with_guard
|
|
assert_compiles 'false', %q{
|
|
def test(val) = val.nil?
|
|
|
|
test(true)
|
|
test(true)
|
|
}, call_threshold: 2, insns: [:opt_nil_p]
|
|
end
|
|
|
|
def test_true_nil_opt_with_guard_side_exit
|
|
assert_compiles 'true', %q{
|
|
def test(val) = val.nil?
|
|
|
|
test(true)
|
|
test(true)
|
|
test(nil)
|
|
}, call_threshold: 2, insns: [:opt_nil_p]
|
|
end
|
|
|
|
def test_false_nil_opt_with_guard
|
|
assert_compiles 'false', %q{
|
|
def test(val) = val.nil?
|
|
|
|
test(false)
|
|
test(false)
|
|
}, call_threshold: 2, insns: [:opt_nil_p]
|
|
end
|
|
|
|
def test_false_nil_opt_with_guard_side_exit
|
|
assert_compiles 'true', %q{
|
|
def test(val) = val.nil?
|
|
|
|
test(false)
|
|
test(false)
|
|
test(nil)
|
|
}, call_threshold: 2, insns: [:opt_nil_p]
|
|
end
|
|
|
|
def test_integer_nil_opt_with_guard
|
|
assert_compiles 'false', %q{
|
|
def test(val) = val.nil?
|
|
|
|
test(1)
|
|
test(2)
|
|
}, call_threshold: 2, insns: [:opt_nil_p]
|
|
end
|
|
|
|
def test_integer_nil_opt_with_guard_side_exit
|
|
assert_compiles 'true', %q{
|
|
def test(val) = val.nil?
|
|
|
|
test(1)
|
|
test(2)
|
|
test(nil)
|
|
}, call_threshold: 2, insns: [:opt_nil_p]
|
|
end
|
|
|
|
def test_float_nil_opt_with_guard
|
|
assert_compiles 'false', %q{
|
|
def test(val) = val.nil?
|
|
|
|
test(1.0)
|
|
test(2.0)
|
|
}, call_threshold: 2, insns: [:opt_nil_p]
|
|
end
|
|
|
|
def test_float_nil_opt_with_guard_side_exit
|
|
assert_compiles 'true', %q{
|
|
def test(val) = val.nil?
|
|
|
|
test(1.0)
|
|
test(2.0)
|
|
test(nil)
|
|
}, call_threshold: 2, insns: [:opt_nil_p]
|
|
end
|
|
|
|
def test_symbol_nil_opt_with_guard
|
|
assert_compiles 'false', %q{
|
|
def test(val) = val.nil?
|
|
|
|
test(:foo)
|
|
test(:bar)
|
|
}, call_threshold: 2, insns: [:opt_nil_p]
|
|
end
|
|
|
|
def test_symbol_nil_opt_with_guard_side_exit
|
|
assert_compiles 'true', %q{
|
|
def test(val) = val.nil?
|
|
|
|
test(:foo)
|
|
test(:bar)
|
|
test(nil)
|
|
}, call_threshold: 2, insns: [:opt_nil_p]
|
|
end
|
|
|
|
def test_class_nil_opt_with_guard
|
|
assert_compiles 'false', %q{
|
|
def test(val) = val.nil?
|
|
|
|
test(String)
|
|
test(Integer)
|
|
}, call_threshold: 2, insns: [:opt_nil_p]
|
|
end
|
|
|
|
def test_class_nil_opt_with_guard_side_exit
|
|
assert_compiles 'true', %q{
|
|
def test(val) = val.nil?
|
|
|
|
test(String)
|
|
test(Integer)
|
|
test(nil)
|
|
}, call_threshold: 2, insns: [:opt_nil_p]
|
|
end
|
|
|
|
def test_module_nil_opt_with_guard
|
|
assert_compiles 'false', %q{
|
|
def test(val) = val.nil?
|
|
|
|
test(Enumerable)
|
|
test(Kernel)
|
|
}, call_threshold: 2, insns: [:opt_nil_p]
|
|
end
|
|
|
|
def test_module_nil_opt_with_guard_side_exit
|
|
assert_compiles 'true', %q{
|
|
def test(val) = val.nil?
|
|
|
|
test(Enumerable)
|
|
test(Kernel)
|
|
test(nil)
|
|
}, call_threshold: 2, insns: [:opt_nil_p]
|
|
end
|
|
|
|
private
|
|
|
|
# Assert that every method call in `test_script` can be compiled by ZJIT
|
|
# at a given call_threshold
|
|
def assert_compiles(expected, test_script, insns: [], **opts)
|
|
assert_runs(expected, test_script, insns:, assert_compiles: true, **opts)
|
|
end
|
|
|
|
# Assert that `test_script` runs successfully with ZJIT enabled.
|
|
# Unlike `assert_compiles`, `assert_runs(assert_compiles: false)`
|
|
# allows ZJIT to skip compiling methods.
|
|
def assert_runs(expected, test_script, insns: [], assert_compiles: false, **opts)
|
|
pipe_fd = 3
|
|
|
|
script = <<~RUBY
|
|
ret_val = (_test_proc = -> { #{('RubyVM::ZJIT.assert_compiles; ' if assert_compiles)}#{test_script.lstrip} }).call
|
|
result = {
|
|
ret_val:,
|
|
#{ unless insns.empty?
|
|
'insns: RubyVM::InstructionSequence.of(method(:test)).to_a'
|
|
end}
|
|
}
|
|
IO.open(#{pipe_fd}).write(Marshal.dump(result))
|
|
RUBY
|
|
|
|
status, out, err, result = eval_with_jit(script, pipe_fd:, **opts)
|
|
|
|
message = "exited with status #{status.to_i}"
|
|
message << "\nstdout:\n```\n#{out}```\n" unless out.empty?
|
|
message << "\nstderr:\n```\n#{err}```\n" unless err.empty?
|
|
assert status.success?, message
|
|
|
|
result = Marshal.load(result)
|
|
assert_equal(expected, result.fetch(:ret_val).inspect)
|
|
|
|
unless insns.empty?
|
|
iseq = result.fetch(:insns)
|
|
assert_equal("YARVInstructionSequence/SimpleDataFormat", iseq.first, "failed to get iseq disassembly")
|
|
iseq_insns = iseq.last
|
|
|
|
expected_insns = Set.new(insns)
|
|
iseq_insns.each do
|
|
next unless it.is_a?(Array)
|
|
expected_insns.delete(it.first)
|
|
end
|
|
assert(expected_insns.empty?, -> { "Not present in ISeq: #{expected_insns.to_a}" })
|
|
end
|
|
end
|
|
|
|
# Run a Ruby process with ZJIT options and a pipe for writing test results
|
|
def eval_with_jit(script, call_threshold: 1, num_profiles: 1, stats: false, debug: true, timeout: 1000, pipe_fd:)
|
|
args = [
|
|
"--disable-gems",
|
|
"--zjit-call-threshold=#{call_threshold}",
|
|
"--zjit-num-profiles=#{num_profiles}",
|
|
]
|
|
args << "--zjit-stats" if stats
|
|
args << "--zjit-debug" if debug
|
|
args << "-e" << script_shell_encode(script)
|
|
pipe_r, pipe_w = IO.pipe
|
|
# Separate thread so we don't deadlock when
|
|
# the child ruby blocks writing the output to pipe_fd
|
|
pipe_out = nil
|
|
pipe_reader = Thread.new do
|
|
pipe_out = pipe_r.read
|
|
pipe_r.close
|
|
end
|
|
out, err, status = EnvUtil.invoke_ruby(args, '', true, true, rubybin: RbConfig.ruby, timeout: timeout, ios: { pipe_fd => pipe_w })
|
|
pipe_w.close
|
|
pipe_reader.join(timeout)
|
|
[status, out, err, pipe_out]
|
|
ensure
|
|
pipe_reader&.kill
|
|
pipe_reader&.join(timeout)
|
|
pipe_r&.close
|
|
pipe_w&.close
|
|
end
|
|
|
|
def script_shell_encode(s)
|
|
# We can't pass utf-8-encoded characters directly in a shell arg. But we can use Ruby \u constants.
|
|
s.chars.map { |c| c.ascii_only? ? c : "\\u%x" % c.codepoints[0] }.join
|
|
end
|
|
end
|