2022-06-23 20:27:02 -04:00
|
|
|
assert_equal 'true', %q{
|
|
|
|
# regression test for tracking type of locals for too long
|
|
|
|
def local_setting_cmp(five)
|
|
|
|
victim = 5
|
|
|
|
five.define_singleton_method(:respond_to?) do |_, _|
|
|
|
|
victim = nil
|
|
|
|
end
|
|
|
|
|
|
|
|
# +1 makes YJIT track that victim is a number and
|
|
|
|
# defined? calls respond_to? from above indirectly
|
|
|
|
unless (victim + 1) && defined?(five.something)
|
|
|
|
# Would return wrong result if we still think `five` is a number
|
|
|
|
victim.nil?
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local_setting_cmp(Object.new)
|
|
|
|
local_setting_cmp(Object.new)
|
|
|
|
}
|
|
|
|
|
2021-12-14 19:47:42 -05:00
|
|
|
assert_equal '18374962167983112447', %q{
|
|
|
|
# regression test for incorrectly discarding 32 bits of a pointer when it
|
|
|
|
# comes to default values.
|
|
|
|
def large_literal_default(n: 0xff00_fabcafe0_00ff)
|
|
|
|
n
|
|
|
|
end
|
|
|
|
|
|
|
|
def call_graph_root
|
|
|
|
large_literal_default
|
|
|
|
end
|
|
|
|
|
|
|
|
call_graph_root
|
|
|
|
call_graph_root
|
|
|
|
}
|
|
|
|
|
2021-12-08 12:24:37 -05:00
|
|
|
assert_normal_exit %q{
|
2022-06-13 09:53:09 -04:00
|
|
|
# regression test for a leak caught by an assert on --yjit-call-threshold=2
|
2021-12-08 12:24:37 -05:00
|
|
|
Foo = 1
|
|
|
|
|
|
|
|
eval("def foo = [#{(['Foo,']*256).join}]")
|
|
|
|
|
|
|
|
foo
|
|
|
|
foo
|
|
|
|
|
|
|
|
Object.send(:remove_const, :Foo)
|
|
|
|
}
|
|
|
|
|
2021-12-07 15:37:32 -05:00
|
|
|
assert_equal '[nil, nil, nil, nil, nil, nil]', %q{
|
|
|
|
[NilClass, TrueClass, FalseClass, Integer, Float, Symbol].each do |klass|
|
|
|
|
klass.class_eval("def foo = @foo")
|
|
|
|
end
|
|
|
|
|
|
|
|
[nil, true, false, 0xFABCAFE, 0.42, :cake].map do |instance|
|
|
|
|
instance.foo
|
|
|
|
instance.foo
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
2021-12-06 17:09:52 -05:00
|
|
|
assert_equal '0', %q{
|
|
|
|
# This is a regression test for incomplete invalidation from
|
|
|
|
# opt_setinlinecache. This test might be brittle, so
|
|
|
|
# feel free to remove it in the future if it's too annoying.
|
|
|
|
# This test assumes --yjit-call-threshold=2.
|
|
|
|
module M
|
|
|
|
Foo = 1
|
|
|
|
def foo
|
|
|
|
Foo
|
|
|
|
end
|
|
|
|
|
|
|
|
def pin_self_type_then_foo
|
|
|
|
_ = @foo
|
|
|
|
foo
|
|
|
|
end
|
|
|
|
|
|
|
|
def only_ints
|
|
|
|
1 + self
|
|
|
|
foo
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class Integer
|
|
|
|
include M
|
|
|
|
end
|
|
|
|
|
|
|
|
class Sub
|
|
|
|
include M
|
|
|
|
end
|
|
|
|
|
|
|
|
foo_method = M.instance_method(:foo)
|
|
|
|
|
|
|
|
dbg = ->(message) do
|
|
|
|
return # comment this out to get printouts
|
|
|
|
|
|
|
|
$stderr.puts RubyVM::YJIT.disasm(foo_method)
|
|
|
|
$stderr.puts message
|
|
|
|
end
|
|
|
|
|
|
|
|
2.times { 42.only_ints }
|
|
|
|
|
|
|
|
dbg["There should be two versions of getinlineache"]
|
|
|
|
|
|
|
|
module M
|
|
|
|
remove_const(:Foo)
|
|
|
|
end
|
|
|
|
|
|
|
|
dbg["There should be no getinlinecaches"]
|
|
|
|
|
|
|
|
2.times do
|
|
|
|
42.only_ints
|
|
|
|
rescue NameError => err
|
|
|
|
_ = "caught name error #{err}"
|
|
|
|
end
|
|
|
|
|
|
|
|
dbg["There should be one version of getinlineache"]
|
|
|
|
|
|
|
|
2.times do
|
|
|
|
Sub.new.pin_self_type_then_foo
|
|
|
|
rescue NameError
|
|
|
|
_ = 'second specialization'
|
|
|
|
end
|
|
|
|
|
|
|
|
dbg["There should be two versions of getinlineache"]
|
|
|
|
|
|
|
|
module M
|
|
|
|
Foo = 1
|
|
|
|
end
|
|
|
|
|
|
|
|
dbg["There should still be two versions of getinlineache"]
|
|
|
|
|
|
|
|
42.only_ints
|
|
|
|
|
|
|
|
dbg["There should be no getinlinecaches"]
|
|
|
|
|
|
|
|
# Find name of the first VM instruction in M#foo.
|
|
|
|
insns = RubyVM::InstructionSequence.of(foo_method).to_a
|
|
|
|
if defined?(RubyVM::YJIT.blocks_for) && (insns.last.find { Array === _1 }&.first == :opt_getinlinecache)
|
|
|
|
RubyVM::YJIT.blocks_for(RubyVM::InstructionSequence.of(foo_method))
|
|
|
|
.filter { _1.iseq_start_index == 0 }.count
|
|
|
|
else
|
|
|
|
0 # skip the test
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
2021-09-03 19:06:32 -04:00
|
|
|
# Check that frozen objects are respected
|
|
|
|
assert_equal 'great', %q{
|
|
|
|
class Foo
|
|
|
|
attr_accessor :bar
|
|
|
|
def initialize
|
|
|
|
@bar = 1
|
|
|
|
freeze
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
foo = Foo.new
|
|
|
|
|
|
|
|
5.times do
|
|
|
|
begin
|
|
|
|
foo.bar = 2
|
|
|
|
rescue FrozenError
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
foo.bar == 1 ? "great" : "NG"
|
|
|
|
}
|
|
|
|
|
2021-07-27 14:57:30 -04:00
|
|
|
# Check that global variable set works
|
|
|
|
assert_equal 'string', %q{
|
|
|
|
def foo
|
|
|
|
$foo = "string"
|
|
|
|
end
|
2021-07-27 13:48:33 -04:00
|
|
|
|
2021-07-27 14:57:30 -04:00
|
|
|
foo
|
|
|
|
}
|
|
|
|
|
2021-07-29 12:41:59 -04:00
|
|
|
# Check that exceptions work when setting global variables
|
|
|
|
assert_equal 'rescued', %q{
|
|
|
|
def set_var
|
|
|
|
$var = 100
|
|
|
|
rescue
|
|
|
|
:rescued
|
|
|
|
end
|
|
|
|
|
|
|
|
set_var
|
|
|
|
trace_var(:$var) { raise }
|
|
|
|
set_var
|
|
|
|
}
|
|
|
|
|
2021-07-27 14:57:30 -04:00
|
|
|
# Check that global variables work
|
2021-07-27 13:48:33 -04:00
|
|
|
assert_equal 'string', %q{
|
|
|
|
$foo = "string"
|
|
|
|
|
|
|
|
def foo
|
|
|
|
$foo
|
|
|
|
end
|
|
|
|
|
|
|
|
foo
|
|
|
|
}
|
|
|
|
|
2021-07-29 12:41:59 -04:00
|
|
|
# Check that exceptions work when getting global variable
|
|
|
|
assert_equal 'rescued', %q{
|
|
|
|
module Warning
|
|
|
|
def warn(message)
|
|
|
|
raise
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def get_var
|
|
|
|
$=
|
|
|
|
rescue
|
|
|
|
:rescued
|
|
|
|
end
|
|
|
|
|
|
|
|
$VERBOSE = true
|
|
|
|
get_var
|
|
|
|
get_var
|
|
|
|
}
|
|
|
|
|
2021-07-19 14:12:51 -04:00
|
|
|
# Check that global tracepoints work
|
|
|
|
assert_equal 'true', %q{
|
|
|
|
def foo
|
|
|
|
1
|
|
|
|
end
|
|
|
|
|
|
|
|
foo
|
|
|
|
foo
|
|
|
|
foo
|
|
|
|
|
|
|
|
called = false
|
|
|
|
|
|
|
|
tp = TracePoint.new(:return) { |event|
|
|
|
|
if event.method_id == :foo
|
|
|
|
called = true
|
|
|
|
end
|
|
|
|
}
|
|
|
|
tp.enable
|
|
|
|
foo
|
|
|
|
tp.disable
|
|
|
|
called
|
|
|
|
}
|
|
|
|
|
|
|
|
# Check that local tracepoints work
|
|
|
|
assert_equal 'true', %q{
|
|
|
|
def foo
|
|
|
|
1
|
|
|
|
end
|
|
|
|
|
|
|
|
foo
|
|
|
|
foo
|
|
|
|
foo
|
|
|
|
|
|
|
|
called = false
|
|
|
|
|
|
|
|
tp = TracePoint.new(:return) { |_| called = true }
|
|
|
|
tp.enable(target: method(:foo))
|
|
|
|
foo
|
|
|
|
tp.disable
|
|
|
|
called
|
|
|
|
}
|
|
|
|
|
2021-07-15 14:09:08 -04:00
|
|
|
# Make sure that optional param methods return the correct value
|
|
|
|
assert_equal '1', %q{
|
|
|
|
def m(ary = [])
|
|
|
|
yield(ary)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Warm the JIT with a 0 param call
|
|
|
|
2.times { m { } }
|
|
|
|
m(1) { |v| v }
|
|
|
|
}
|
|
|
|
|
2021-06-10 12:40:30 -04:00
|
|
|
# Test for topn
|
|
|
|
assert_equal 'array', %q{
|
|
|
|
def threequals(a)
|
|
|
|
case a
|
|
|
|
when Array
|
|
|
|
"array"
|
|
|
|
when Hash
|
|
|
|
"hash"
|
|
|
|
else
|
|
|
|
"unknown"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
threequals([])
|
|
|
|
threequals([])
|
|
|
|
threequals([])
|
|
|
|
}
|
|
|
|
|
2021-05-07 16:10:09 -04:00
|
|
|
# Test for opt_mod
|
|
|
|
assert_equal '2', %q{
|
|
|
|
def mod(a, b)
|
|
|
|
a % b
|
|
|
|
end
|
|
|
|
|
|
|
|
mod(7, 5)
|
|
|
|
mod(7, 5)
|
|
|
|
}
|
|
|
|
|
2021-07-06 14:45:36 -04:00
|
|
|
# Test for opt_mult
|
|
|
|
assert_equal '12', %q{
|
|
|
|
def mult(a, b)
|
|
|
|
a * b
|
|
|
|
end
|
|
|
|
|
|
|
|
mult(6, 2)
|
|
|
|
mult(6, 2)
|
|
|
|
}
|
|
|
|
|
2021-07-06 16:26:56 -04:00
|
|
|
# Test for opt_div
|
|
|
|
assert_equal '3', %q{
|
|
|
|
def div(a, b)
|
|
|
|
a / b
|
|
|
|
end
|
|
|
|
|
|
|
|
div(6, 2)
|
|
|
|
div(6, 2)
|
|
|
|
}
|
|
|
|
|
2021-03-09 14:01:16 -05:00
|
|
|
# BOP redefined methods work when JIT compiled
|
|
|
|
assert_equal 'false', %q{
|
|
|
|
def less_than x
|
|
|
|
x < 10
|
|
|
|
end
|
|
|
|
|
|
|
|
class Integer
|
|
|
|
def < x
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
less_than 2
|
|
|
|
less_than 2
|
|
|
|
less_than 2
|
|
|
|
}
|
|
|
|
|
|
|
|
# BOP redefinition works on Integer#<
|
|
|
|
assert_equal 'false', %q{
|
|
|
|
def less_than x
|
|
|
|
x < 10
|
|
|
|
end
|
|
|
|
|
|
|
|
less_than 2
|
|
|
|
less_than 2
|
|
|
|
|
|
|
|
class Integer
|
|
|
|
def < x
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
less_than 2
|
|
|
|
}
|
|
|
|
|
2021-02-10 19:57:06 -05:00
|
|
|
# Putobject, less-than operator, fixnums
|
|
|
|
assert_equal '2', %q{
|
|
|
|
def check_index(index)
|
|
|
|
if 0x40000000 < index
|
|
|
|
raise "wat? #{index}"
|
|
|
|
end
|
|
|
|
index
|
|
|
|
end
|
|
|
|
check_index 2
|
|
|
|
check_index 2
|
|
|
|
}
|
|
|
|
|
2021-02-12 14:35:57 -05:00
|
|
|
# foo leaves a temp on the stack before the call
|
|
|
|
assert_equal '6', %q{
|
|
|
|
def bar
|
|
|
|
return 5
|
2021-02-03 17:39:38 -05:00
|
|
|
end
|
|
|
|
|
2021-02-12 14:35:57 -05:00
|
|
|
def foo
|
|
|
|
return 1 + bar
|
2021-02-03 17:39:38 -05:00
|
|
|
end
|
|
|
|
|
2021-02-12 14:35:57 -05:00
|
|
|
foo()
|
|
|
|
retval = foo()
|
2021-02-05 12:18:55 -05:00
|
|
|
}
|
|
|
|
|
2021-02-12 14:35:57 -05:00
|
|
|
# Method with one arguments
|
2021-02-05 12:18:55 -05:00
|
|
|
# foo leaves a temp on the stack before the call
|
2021-02-12 14:35:57 -05:00
|
|
|
assert_equal '7', %q{
|
|
|
|
def bar(a)
|
|
|
|
return a + 1
|
2021-02-05 12:18:55 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def foo
|
2021-02-12 14:35:57 -05:00
|
|
|
return 1 + bar(5)
|
2021-02-05 12:18:55 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
foo()
|
|
|
|
retval = foo()
|
|
|
|
}
|
|
|
|
|
2021-02-12 14:35:57 -05:00
|
|
|
# Method with two arguments
|
2021-02-09 17:34:02 -05:00
|
|
|
# foo leaves a temp on the stack before the call
|
|
|
|
assert_equal '0', %q{
|
|
|
|
def bar(a, b)
|
|
|
|
return a - b
|
|
|
|
end
|
|
|
|
|
|
|
|
def foo
|
|
|
|
return 1 + bar(1, 2)
|
|
|
|
end
|
|
|
|
|
|
|
|
foo()
|
|
|
|
retval = foo()
|
|
|
|
}
|
|
|
|
|
2021-04-15 14:16:55 -04:00
|
|
|
# Passing argument types to callees
|
|
|
|
assert_equal '8.5', %q{
|
|
|
|
def foo(x, y)
|
|
|
|
x + y
|
|
|
|
end
|
|
|
|
|
|
|
|
def bar
|
|
|
|
foo(7, 1.5)
|
|
|
|
end
|
|
|
|
|
|
|
|
bar
|
|
|
|
bar
|
|
|
|
}
|
|
|
|
|
2021-02-09 17:34:02 -05:00
|
|
|
# Recursive Ruby-to-Ruby calls
|
|
|
|
assert_equal '21', %q{
|
|
|
|
def fib(n)
|
|
|
|
if n < 2
|
|
|
|
return n
|
|
|
|
end
|
|
|
|
|
|
|
|
return fib(n-1) + fib(n-2)
|
|
|
|
end
|
|
|
|
|
|
|
|
r = fib(8)
|
|
|
|
}
|
|
|
|
|
2021-02-05 12:18:55 -05:00
|
|
|
# Ruby-to-Ruby call and C call
|
|
|
|
assert_normal_exit %q{
|
|
|
|
def bar
|
|
|
|
puts('hi!')
|
|
|
|
end
|
|
|
|
|
|
|
|
def foo
|
|
|
|
bar
|
|
|
|
end
|
|
|
|
|
|
|
|
foo()
|
|
|
|
foo()
|
|
|
|
}
|
|
|
|
|
2021-05-05 16:06:19 -04:00
|
|
|
# Method aliasing
|
|
|
|
assert_equal '42', %q{
|
|
|
|
class Foo
|
|
|
|
def method_a
|
|
|
|
42
|
|
|
|
end
|
|
|
|
|
|
|
|
alias method_b method_a
|
|
|
|
|
|
|
|
def method_a
|
|
|
|
:somethingelse
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
@obj = Foo.new
|
|
|
|
|
|
|
|
def test
|
|
|
|
@obj.method_b
|
|
|
|
end
|
|
|
|
|
|
|
|
test
|
|
|
|
test
|
|
|
|
}
|
|
|
|
|
|
|
|
# Method aliasing with method from parent class
|
|
|
|
assert_equal '777', %q{
|
|
|
|
class A
|
|
|
|
def method_a
|
|
|
|
777
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class B < A
|
|
|
|
alias method_b method_a
|
|
|
|
end
|
|
|
|
|
|
|
|
@obj = B.new
|
|
|
|
|
|
|
|
def test
|
|
|
|
@obj.method_b
|
|
|
|
end
|
|
|
|
|
|
|
|
test
|
|
|
|
test
|
|
|
|
}
|
|
|
|
|
2021-02-25 12:09:29 -05:00
|
|
|
# The hash method is a C function and uses the self argument
|
|
|
|
assert_equal 'true', %q{
|
|
|
|
def lehashself
|
|
|
|
hash
|
|
|
|
end
|
|
|
|
|
|
|
|
a = lehashself
|
|
|
|
b = lehashself
|
|
|
|
a == b
|
|
|
|
}
|
|
|
|
|
2021-02-12 14:35:57 -05:00
|
|
|
# Method redefinition (code invalidation) test
|
|
|
|
assert_equal '1', %q{
|
|
|
|
def ret1
|
|
|
|
return 1
|
|
|
|
end
|
|
|
|
|
|
|
|
klass = Class.new do
|
|
|
|
def alias_then_hash(klass, method_to_redefine)
|
|
|
|
# Redefine the method to be ret1
|
|
|
|
klass.alias_method(method_to_redefine, :ret1)
|
|
|
|
hash
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
instance = klass.new
|
|
|
|
|
|
|
|
i = 0
|
|
|
|
while i < 12
|
|
|
|
if i < 11
|
|
|
|
# Redefine the bar method
|
|
|
|
instance.alias_then_hash(klass, :bar)
|
|
|
|
else
|
|
|
|
# Redefine the hash method to be ret1
|
|
|
|
retval = instance.alias_then_hash(klass, :hash)
|
|
|
|
end
|
|
|
|
i += 1
|
|
|
|
end
|
|
|
|
|
|
|
|
retval
|
|
|
|
}
|
|
|
|
|
2021-04-27 16:27:56 -04:00
|
|
|
# Code invalidation and opt_getinlinecache
|
|
|
|
assert_normal_exit %q{
|
|
|
|
class Foo; end
|
|
|
|
|
|
|
|
# Uses the class constant Foo
|
|
|
|
def use_constant(arg)
|
|
|
|
[Foo.new, arg]
|
|
|
|
end
|
|
|
|
|
|
|
|
def propagate_type
|
|
|
|
i = Array.new
|
|
|
|
i.itself # make it remember that i is on-heap
|
|
|
|
use_constant(i)
|
|
|
|
end
|
|
|
|
|
|
|
|
propagate_type
|
|
|
|
propagate_type
|
|
|
|
use_constant(Foo.new)
|
|
|
|
class Jo; end # bump global constant state
|
|
|
|
use_constant(3)
|
|
|
|
}
|
|
|
|
|
2021-03-11 14:15:01 -05:00
|
|
|
# Method redefinition (code invalidation) and GC
|
2021-02-12 14:35:57 -05:00
|
|
|
assert_equal '7', %q{
|
|
|
|
def bar()
|
|
|
|
return 5
|
|
|
|
end
|
|
|
|
|
|
|
|
def foo()
|
|
|
|
bar()
|
|
|
|
end
|
|
|
|
|
|
|
|
foo()
|
|
|
|
foo()
|
|
|
|
|
|
|
|
def bar()
|
|
|
|
return 7
|
|
|
|
end
|
|
|
|
|
|
|
|
4.times { GC.start }
|
|
|
|
|
|
|
|
foo()
|
|
|
|
foo()
|
|
|
|
}
|
|
|
|
|
|
|
|
# Method redefinition with two block versions
|
|
|
|
assert_equal '7', %q{
|
|
|
|
def bar()
|
|
|
|
return 5
|
|
|
|
end
|
|
|
|
|
|
|
|
def foo(n)
|
|
|
|
return ((n < 5)? 5:false), bar()
|
|
|
|
end
|
|
|
|
|
|
|
|
foo(4)
|
|
|
|
foo(4)
|
|
|
|
foo(10)
|
|
|
|
foo(10)
|
|
|
|
|
|
|
|
def bar()
|
|
|
|
return 7
|
|
|
|
end
|
|
|
|
|
|
|
|
4.times { GC.start }
|
|
|
|
|
|
|
|
foo(4)
|
|
|
|
foo(4)[1]
|
|
|
|
}
|
|
|
|
|
2021-08-31 14:26:18 -04:00
|
|
|
# Method redefinition while the method is on the stack
|
|
|
|
assert_equal '[777, 1]', %q{
|
|
|
|
def foo
|
|
|
|
redef()
|
|
|
|
777
|
|
|
|
end
|
|
|
|
|
|
|
|
def redef
|
|
|
|
# Redefine the global foo
|
|
|
|
eval("def foo; 1; end", TOPLEVEL_BINDING)
|
|
|
|
|
|
|
|
# Collect dead code
|
|
|
|
GC.stress = true
|
|
|
|
GC.start
|
|
|
|
|
|
|
|
# But we will return to the original foo,
|
|
|
|
# which remains alive because it's on the stack
|
|
|
|
end
|
|
|
|
|
|
|
|
# Must produce [777, 1]
|
|
|
|
[foo, foo]
|
|
|
|
}
|
|
|
|
|
2021-02-05 12:18:55 -05:00
|
|
|
# Test for GC safety. Don't invalidate dead iseqs.
|
|
|
|
assert_normal_exit %q{
|
|
|
|
Class.new do
|
|
|
|
def foo
|
|
|
|
itself
|
|
|
|
end
|
|
|
|
|
|
|
|
new.foo
|
2021-02-19 15:44:53 -05:00
|
|
|
new.foo
|
|
|
|
new.foo
|
2021-02-05 12:18:55 -05:00
|
|
|
new.foo
|
|
|
|
end
|
|
|
|
|
|
|
|
4.times { GC.start }
|
|
|
|
def itself
|
|
|
|
self
|
|
|
|
end
|
|
|
|
}
|
2021-02-16 15:28:21 -05:00
|
|
|
|
2021-04-21 16:01:03 -04:00
|
|
|
# test setinstancevariable on extended objects
|
|
|
|
assert_equal '1', %q{
|
|
|
|
class Extended
|
|
|
|
attr_reader :one
|
|
|
|
|
|
|
|
def write_many
|
|
|
|
@a = 1
|
|
|
|
@b = 2
|
|
|
|
@c = 3
|
|
|
|
@d = 4
|
|
|
|
@one = 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
foo = Extended.new
|
|
|
|
foo.write_many
|
|
|
|
foo.write_many
|
|
|
|
foo.write_many
|
|
|
|
}
|
|
|
|
|
|
|
|
# test setinstancevariable on embedded objects
|
|
|
|
assert_equal '1', %q{
|
|
|
|
class Embedded
|
|
|
|
attr_reader :one
|
|
|
|
|
|
|
|
def write_one
|
|
|
|
@one = 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
foo = Embedded.new
|
|
|
|
foo.write_one
|
|
|
|
foo.write_one
|
|
|
|
foo.write_one
|
|
|
|
}
|
|
|
|
|
|
|
|
# test setinstancevariable after extension
|
|
|
|
assert_equal '[10, 11, 12, 13, 1]', %q{
|
|
|
|
class WillExtend
|
|
|
|
attr_reader :one
|
|
|
|
|
|
|
|
def make_extended
|
|
|
|
@foo1 = 10
|
|
|
|
@foo2 = 11
|
|
|
|
@foo3 = 12
|
|
|
|
@foo4 = 13
|
|
|
|
end
|
|
|
|
|
|
|
|
def write_one
|
|
|
|
@one = 1
|
|
|
|
end
|
|
|
|
|
|
|
|
def read_all
|
|
|
|
[@foo1, @foo2, @foo3, @foo4, @one]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
foo = WillExtend.new
|
|
|
|
foo.write_one
|
|
|
|
foo.write_one
|
|
|
|
foo.make_extended
|
|
|
|
foo.write_one
|
|
|
|
foo.read_all
|
|
|
|
}
|
|
|
|
|
|
|
|
# test setinstancevariable on frozen object
|
|
|
|
assert_equal 'object was not modified', %q{
|
|
|
|
class WillFreeze
|
|
|
|
def write
|
|
|
|
@ivar = 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
wf = WillFreeze.new
|
|
|
|
wf.write
|
|
|
|
wf.write
|
|
|
|
wf.freeze
|
|
|
|
|
|
|
|
begin
|
|
|
|
wf.write
|
|
|
|
rescue FrozenError
|
|
|
|
"object was not modified"
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
2021-03-05 15:45:44 -05:00
|
|
|
# Test getinstancevariable and inline caches
|
|
|
|
assert_equal '6', %q{
|
|
|
|
class Foo
|
|
|
|
def initialize
|
|
|
|
@x1 = 1
|
|
|
|
@x2 = 1
|
|
|
|
@x2 = 1
|
|
|
|
@x3 = 1
|
|
|
|
@x4 = 3
|
|
|
|
end
|
|
|
|
|
|
|
|
def bar
|
|
|
|
x = 1
|
|
|
|
@x4 + @x4
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
f = Foo.new
|
|
|
|
f.bar
|
|
|
|
f.bar
|
|
|
|
}
|
|
|
|
|
2021-02-16 15:28:21 -05:00
|
|
|
# Test that getinstancevariable codegen checks for extended table size
|
|
|
|
assert_equal "nil\n", %q{
|
|
|
|
class A
|
|
|
|
def read
|
|
|
|
@ins1000
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
ins = A.new
|
|
|
|
other = A.new
|
|
|
|
10.times { other.instance_variable_set(:"@otr#{_1}", 'value') }
|
|
|
|
1001.times { ins.instance_variable_set(:"@ins#{_1}", 'value') }
|
|
|
|
|
|
|
|
ins.read
|
|
|
|
ins.read
|
|
|
|
ins.read
|
|
|
|
|
|
|
|
p other.read
|
|
|
|
}
|
2021-02-19 11:20:55 -05:00
|
|
|
|
|
|
|
# Test that opt_aref checks the class of the receiver
|
2021-02-19 15:44:53 -05:00
|
|
|
assert_equal 'special', %q{
|
2021-02-19 11:20:55 -05:00
|
|
|
def foo(array)
|
|
|
|
array[30]
|
|
|
|
end
|
|
|
|
|
2021-02-19 15:44:53 -05:00
|
|
|
foo([])
|
|
|
|
foo([])
|
2021-02-19 11:20:55 -05:00
|
|
|
|
|
|
|
special = []
|
|
|
|
def special.[](idx)
|
2021-02-19 15:44:53 -05:00
|
|
|
'special'
|
2021-02-19 11:20:55 -05:00
|
|
|
end
|
|
|
|
|
2021-02-19 15:44:53 -05:00
|
|
|
foo(special)
|
2021-02-19 11:20:55 -05:00
|
|
|
}
|
2021-02-19 15:03:12 -05:00
|
|
|
|
|
|
|
# Test that object references in generated code get marked and moved
|
|
|
|
assert_equal "good", %q{
|
|
|
|
def bar
|
|
|
|
"good"
|
|
|
|
end
|
|
|
|
|
|
|
|
def foo
|
|
|
|
bar
|
|
|
|
end
|
|
|
|
|
|
|
|
foo
|
|
|
|
foo
|
|
|
|
|
2021-10-22 10:53:42 -04:00
|
|
|
begin
|
2022-07-07 17:32:35 -04:00
|
|
|
GC.verify_compaction_references(expand_heap: true, toward: :empty)
|
2021-10-22 10:53:42 -04:00
|
|
|
rescue NotImplementedError
|
|
|
|
# in case compaction isn't supported
|
|
|
|
end
|
2021-02-19 15:03:12 -05:00
|
|
|
|
|
|
|
foo
|
|
|
|
}
|
2021-03-12 12:22:19 -05:00
|
|
|
|
|
|
|
# Test polymorphic getinstancevariable. T_OBJECT -> T_STRING
|
|
|
|
assert_equal 'ok', %q{
|
|
|
|
@hello = @h1 = @h2 = @h3 = @h4 = 'ok'
|
|
|
|
str = ""
|
|
|
|
str.instance_variable_set(:@hello, 'ok')
|
|
|
|
|
|
|
|
public def get
|
|
|
|
@hello
|
|
|
|
end
|
|
|
|
|
|
|
|
get
|
|
|
|
get
|
|
|
|
str.get
|
|
|
|
str.get
|
|
|
|
}
|
|
|
|
|
|
|
|
# Test polymorphic getinstancevariable, two different classes
|
|
|
|
assert_equal 'ok', %q{
|
|
|
|
class Embedded
|
|
|
|
def initialize
|
|
|
|
@ivar = 0
|
|
|
|
end
|
|
|
|
|
|
|
|
def get
|
|
|
|
@ivar
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class Extended < Embedded
|
|
|
|
def initialize
|
|
|
|
@v1 = @v2 = @v3 = @v4 = @ivar = 'ok'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
embed = Embedded.new
|
|
|
|
extend = Extended.new
|
|
|
|
|
|
|
|
embed.get
|
|
|
|
embed.get
|
|
|
|
extend.get
|
|
|
|
extend.get
|
|
|
|
}
|
|
|
|
|
|
|
|
# Test megamorphic getinstancevariable
|
|
|
|
assert_equal 'ok', %q{
|
|
|
|
parent = Class.new do
|
|
|
|
def initialize
|
|
|
|
@hello = @h1 = @h2 = @h3 = @h4 = 'ok'
|
|
|
|
end
|
|
|
|
|
|
|
|
def get
|
|
|
|
@hello
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
subclasses = 300.times.map { Class.new(parent) }
|
|
|
|
subclasses.each { _1.new.get }
|
|
|
|
parent.new.get
|
|
|
|
}
|
2021-03-11 14:46:19 -05:00
|
|
|
|
|
|
|
# Test polymorphic opt_aref. array -> hash
|
|
|
|
assert_equal '[42, :key]', %q{
|
|
|
|
def index(obj, idx)
|
|
|
|
obj[idx]
|
|
|
|
end
|
|
|
|
|
|
|
|
index([], 0) # get over compilation threshold
|
|
|
|
|
|
|
|
[
|
|
|
|
index([42], 0),
|
|
|
|
index({0=>:key}, 0),
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
# Test polymorphic opt_aref. hash -> array -> custom class
|
|
|
|
assert_equal '[nil, nil, :custom]', %q{
|
|
|
|
def index(obj, idx)
|
|
|
|
obj[idx]
|
|
|
|
end
|
|
|
|
|
|
|
|
custom = Object.new
|
|
|
|
def custom.[](_idx)
|
|
|
|
:custom
|
|
|
|
end
|
|
|
|
|
|
|
|
index({}, 0) # get over compilation threshold
|
|
|
|
|
|
|
|
[
|
|
|
|
index({}, 0),
|
|
|
|
index([], 0),
|
|
|
|
index(custom, 0)
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
# Test polymorphic opt_aref. array -> custom class
|
|
|
|
assert_equal '[42, :custom]', %q{
|
|
|
|
def index(obj, idx)
|
|
|
|
obj[idx]
|
|
|
|
end
|
|
|
|
|
|
|
|
custom = Object.new
|
|
|
|
def custom.[](_idx)
|
|
|
|
:custom
|
|
|
|
end
|
|
|
|
|
|
|
|
index([], 0) # get over compilation threshold
|
|
|
|
|
|
|
|
[
|
|
|
|
index([42], 0),
|
|
|
|
index(custom, 0)
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
# Test custom hash method with opt_aref
|
|
|
|
assert_equal '[nil, :ok]', %q{
|
|
|
|
def index(obj, idx)
|
|
|
|
obj[idx]
|
|
|
|
end
|
|
|
|
|
|
|
|
custom = Object.new
|
|
|
|
def custom.hash
|
|
|
|
42
|
|
|
|
end
|
|
|
|
|
|
|
|
h = {custom => :ok}
|
|
|
|
|
|
|
|
[
|
|
|
|
index(h, 0),
|
|
|
|
index(h, custom)
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
# Test default value block for Hash with opt_aref
|
|
|
|
assert_equal '[42, :default]', %q{
|
|
|
|
def index(obj, idx)
|
|
|
|
obj[idx]
|
|
|
|
end
|
|
|
|
|
|
|
|
h = Hash.new { :default }
|
|
|
|
h[0] = 42
|
|
|
|
|
|
|
|
[
|
|
|
|
index(h, 0),
|
|
|
|
index(h, 1)
|
|
|
|
]
|
|
|
|
}
|
2021-03-22 20:12:34 -04:00
|
|
|
|
|
|
|
# A regression test for making sure cfp->sp is proper when
|
|
|
|
# hitting stubs. See :stub-sp-flush:
|
|
|
|
assert_equal 'ok', %q{
|
|
|
|
class D
|
|
|
|
def foo
|
|
|
|
Object.new
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
GC.stress = true
|
|
|
|
10.times do
|
|
|
|
D.new.foo
|
|
|
|
# ^
|
|
|
|
# This hits a stub with sp_offset > 0
|
|
|
|
end
|
|
|
|
|
|
|
|
:ok
|
|
|
|
}
|
|
|
|
|
|
|
|
# Test polymorphic callsite, cfunc -> iseq
|
|
|
|
assert_equal '[Cfunc, Iseq]', %q{
|
|
|
|
public def call_itself
|
|
|
|
itself # the polymorphic callsite
|
|
|
|
end
|
|
|
|
|
|
|
|
class Cfunc; end
|
|
|
|
|
|
|
|
class Iseq
|
|
|
|
def itself
|
|
|
|
self
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
call_itself # cross threshold
|
|
|
|
|
|
|
|
[Cfunc.call_itself, Iseq.call_itself]
|
|
|
|
}
|
|
|
|
|
|
|
|
# Test polymorphic callsite, iseq -> cfunc
|
|
|
|
assert_equal '[Iseq, Cfunc]', %q{
|
|
|
|
public def call_itself
|
|
|
|
itself # the polymorphic callsite
|
|
|
|
end
|
|
|
|
|
|
|
|
class Cfunc; end
|
|
|
|
|
|
|
|
class Iseq
|
|
|
|
def itself
|
|
|
|
self
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
call_itself # cross threshold
|
|
|
|
|
|
|
|
[Iseq.call_itself, Cfunc.call_itself]
|
|
|
|
}
|
2021-04-01 14:31:57 -04:00
|
|
|
|
|
|
|
# attr_reader method
|
|
|
|
assert_equal '[100, 299]', %q{
|
|
|
|
class A
|
|
|
|
attr_reader :foo
|
|
|
|
|
|
|
|
def initialize
|
|
|
|
@foo = 100
|
|
|
|
end
|
|
|
|
|
|
|
|
# Make it extended
|
|
|
|
def fill!
|
|
|
|
@bar = @jojo = @as = @sdfsdf = @foo = 299
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def bar(ins)
|
|
|
|
ins.foo
|
|
|
|
end
|
|
|
|
|
|
|
|
ins = A.new
|
|
|
|
oth = A.new
|
|
|
|
oth.fill!
|
|
|
|
|
|
|
|
bar(ins)
|
|
|
|
bar(oth)
|
|
|
|
|
|
|
|
[bar(ins), bar(oth)]
|
|
|
|
}
|
|
|
|
|
2021-04-29 11:47:19 -04:00
|
|
|
# get ivar on object, then on hash
|
|
|
|
assert_equal '[42, 100]', %q{
|
|
|
|
class Hash
|
|
|
|
attr_accessor :foo
|
|
|
|
end
|
|
|
|
|
|
|
|
class A
|
|
|
|
attr_reader :foo
|
|
|
|
|
|
|
|
def initialize
|
|
|
|
@foo = 42
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def use(val)
|
|
|
|
val.foo
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
h = {}
|
|
|
|
h.foo = 100
|
|
|
|
obj = A.new
|
|
|
|
|
|
|
|
use(obj)
|
|
|
|
[use(obj), use(h)]
|
|
|
|
}
|
|
|
|
|
2021-04-01 14:31:57 -04:00
|
|
|
# get ivar on String
|
|
|
|
assert_equal '[nil, nil, 42, 42]', %q{
|
|
|
|
# @foo to exercise the getinstancevariable instruction
|
|
|
|
public def get_foo
|
|
|
|
@foo
|
|
|
|
end
|
|
|
|
|
|
|
|
get_foo
|
|
|
|
get_foo # compile it for the top level object
|
|
|
|
|
|
|
|
class String
|
|
|
|
attr_reader :foo
|
|
|
|
end
|
|
|
|
|
|
|
|
def run
|
|
|
|
str = String.new
|
|
|
|
|
|
|
|
getter = str.foo
|
|
|
|
insn = str.get_foo
|
|
|
|
|
|
|
|
str.instance_variable_set(:@foo, 42)
|
|
|
|
|
|
|
|
[getter, insn, str.foo, str.get_foo]
|
|
|
|
end
|
|
|
|
|
|
|
|
run
|
|
|
|
run
|
|
|
|
}
|
|
|
|
|
|
|
|
# splatting an empty array on a getter
|
|
|
|
assert_equal '42', %q{
|
|
|
|
@foo = 42
|
|
|
|
module Kernel
|
|
|
|
attr_reader :foo
|
|
|
|
end
|
|
|
|
|
|
|
|
def run
|
|
|
|
foo(*[])
|
|
|
|
end
|
|
|
|
|
|
|
|
run
|
|
|
|
run
|
|
|
|
}
|
2021-04-09 19:08:23 -04:00
|
|
|
|
|
|
|
# getinstancevariable on Symbol
|
|
|
|
assert_equal '[nil, nil]', %q{
|
|
|
|
# @foo to exercise the getinstancevariable instruction
|
|
|
|
public def get_foo
|
|
|
|
@foo
|
|
|
|
end
|
|
|
|
|
|
|
|
dyn_sym = ("a" + "b").to_sym
|
|
|
|
sym = :static
|
|
|
|
|
|
|
|
# compile get_foo
|
|
|
|
dyn_sym.get_foo
|
|
|
|
dyn_sym.get_foo
|
|
|
|
|
|
|
|
[dyn_sym.get_foo, sym.get_foo]
|
|
|
|
}
|
|
|
|
|
|
|
|
# attr_reader on Symbol
|
|
|
|
assert_equal '[nil, nil]', %q{
|
|
|
|
class Symbol
|
|
|
|
attr_reader :foo
|
|
|
|
end
|
|
|
|
|
|
|
|
public def get_foo
|
|
|
|
foo
|
|
|
|
end
|
|
|
|
|
|
|
|
dyn_sym = ("a" + "b").to_sym
|
|
|
|
sym = :static
|
|
|
|
|
|
|
|
# compile get_foo
|
|
|
|
dyn_sym.get_foo
|
|
|
|
dyn_sym.get_foo
|
|
|
|
|
|
|
|
[dyn_sym.get_foo, sym.get_foo]
|
|
|
|
}
|
2021-04-28 12:59:26 -04:00
|
|
|
|
|
|
|
# passing too few arguments to method with optional parameters
|
|
|
|
assert_equal 'raised', %q{
|
|
|
|
def opt(a, b = 0)
|
|
|
|
end
|
|
|
|
|
|
|
|
def use
|
|
|
|
opt
|
|
|
|
end
|
|
|
|
|
|
|
|
use rescue nil
|
|
|
|
begin
|
|
|
|
use
|
|
|
|
:ng
|
|
|
|
rescue ArgumentError
|
|
|
|
:raised
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
# passing too many arguments to method with optional parameters
|
|
|
|
assert_equal 'raised', %q{
|
|
|
|
def opt(a, b = 0)
|
|
|
|
end
|
|
|
|
|
|
|
|
def use
|
|
|
|
opt(1, 2, 3, 4)
|
|
|
|
end
|
|
|
|
|
|
|
|
use rescue nil
|
|
|
|
begin
|
|
|
|
use
|
|
|
|
:ng
|
|
|
|
rescue ArgumentError
|
|
|
|
:raised
|
|
|
|
end
|
|
|
|
}
|
2021-05-04 12:35:51 -04:00
|
|
|
|
|
|
|
# test calling Ruby method with a block
|
|
|
|
assert_equal '[1, 2, 42]', %q{
|
2021-05-10 17:05:12 -04:00
|
|
|
def thing(a, b)
|
|
|
|
[a, b, yield]
|
|
|
|
end
|
2021-05-04 12:35:51 -04:00
|
|
|
|
2021-05-10 17:05:12 -04:00
|
|
|
def use
|
|
|
|
thing(1,2) { 42 }
|
|
|
|
end
|
2021-05-04 12:35:51 -04:00
|
|
|
|
2021-05-10 17:05:12 -04:00
|
|
|
use
|
|
|
|
use
|
2021-05-04 12:35:51 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
# test calling C method with a block
|
|
|
|
assert_equal '[42, 42]', %q{
|
2021-05-10 17:05:12 -04:00
|
|
|
def use(array, initial)
|
|
|
|
array.reduce(initial) { |a, b| a + b }
|
|
|
|
end
|
|
|
|
|
|
|
|
use([], 0)
|
|
|
|
[use([2, 2], 38), use([14, 14, 14], 0)]
|
|
|
|
}
|
|
|
|
|
|
|
|
# test calling block param
|
|
|
|
assert_equal '[1, 2, 42]', %q{
|
|
|
|
def foo(&block)
|
|
|
|
block.call
|
|
|
|
end
|
2021-05-04 12:35:51 -04:00
|
|
|
|
2021-05-10 17:05:12 -04:00
|
|
|
[foo {1}, foo {2}, foo {42}]
|
|
|
|
}
|
|
|
|
|
|
|
|
# test calling block param failing
|
|
|
|
assert_equal '42', %q{
|
|
|
|
def foo(&block)
|
|
|
|
block.call
|
|
|
|
end
|
|
|
|
|
|
|
|
foo {} # warmup
|
|
|
|
|
|
|
|
begin
|
|
|
|
foo
|
|
|
|
rescue NoMethodError => e
|
|
|
|
42 if nil == e.receiver
|
|
|
|
end
|
2021-05-04 12:35:51 -04:00
|
|
|
}
|
2021-05-06 12:24:50 -04:00
|
|
|
|
|
|
|
# test calling method taking block param
|
|
|
|
assert_equal '[Proc, 1, 2, 3, Proc]', %q{
|
|
|
|
def three(a, b, c, &block)
|
|
|
|
[a, b, c, block.class]
|
|
|
|
end
|
|
|
|
|
|
|
|
def zero(&block)
|
|
|
|
block.class
|
|
|
|
end
|
|
|
|
|
|
|
|
def use_three
|
|
|
|
three(1, 2, 3) {}
|
|
|
|
end
|
|
|
|
|
|
|
|
def use_zero
|
|
|
|
zero {}
|
|
|
|
end
|
|
|
|
|
|
|
|
use_three
|
|
|
|
use_zero
|
|
|
|
|
|
|
|
[use_zero] + use_three
|
|
|
|
}
|
2021-05-27 13:59:41 -04:00
|
|
|
|
|
|
|
# test building empty array
|
|
|
|
assert_equal '[]', %q{
|
|
|
|
def build_arr
|
|
|
|
[]
|
|
|
|
end
|
|
|
|
|
|
|
|
build_arr
|
|
|
|
build_arr
|
|
|
|
}
|
|
|
|
|
|
|
|
# test building array of one element
|
|
|
|
assert_equal '[5]', %q{
|
|
|
|
def build_arr(val)
|
|
|
|
[val]
|
|
|
|
end
|
|
|
|
|
|
|
|
build_arr(5)
|
|
|
|
build_arr(5)
|
|
|
|
}
|
|
|
|
|
|
|
|
# test building array of several element
|
|
|
|
assert_equal '[5, 5, 5, 5, 5]', %q{
|
|
|
|
def build_arr(val)
|
|
|
|
[val, val, val, val, val]
|
|
|
|
end
|
|
|
|
|
|
|
|
build_arr(5)
|
|
|
|
build_arr(5)
|
|
|
|
}
|
|
|
|
|
|
|
|
# test building empty hash
|
|
|
|
assert_equal '{}', %q{
|
|
|
|
def build_hash
|
|
|
|
{}
|
|
|
|
end
|
|
|
|
|
|
|
|
build_hash
|
|
|
|
build_hash
|
|
|
|
}
|
|
|
|
|
|
|
|
# test building hash with values
|
|
|
|
assert_equal '{:foo=>:bar}', %q{
|
|
|
|
def build_hash(val)
|
|
|
|
{ foo: val }
|
|
|
|
end
|
|
|
|
|
|
|
|
build_hash(:bar)
|
|
|
|
build_hash(:bar)
|
|
|
|
}
|
2021-06-02 11:15:39 -04:00
|
|
|
|
|
|
|
# test string interpolation with known types
|
|
|
|
assert_equal 'foobar', %q{
|
|
|
|
def make_str
|
|
|
|
foo = -"foo"
|
|
|
|
bar = -"bar"
|
|
|
|
"#{foo}#{bar}"
|
|
|
|
end
|
|
|
|
|
|
|
|
make_str
|
|
|
|
make_str
|
|
|
|
}
|
|
|
|
|
|
|
|
# test string interpolation with unknown types
|
|
|
|
assert_equal 'foobar', %q{
|
|
|
|
def make_str(foo, bar)
|
|
|
|
"#{foo}#{bar}"
|
|
|
|
end
|
|
|
|
|
|
|
|
make_str("foo", "bar")
|
|
|
|
make_str("foo", "bar")
|
|
|
|
}
|
|
|
|
|
|
|
|
# test string interpolation with known non-strings
|
|
|
|
assert_equal 'foo123', %q{
|
|
|
|
def make_str
|
|
|
|
foo = -"foo"
|
|
|
|
bar = 123
|
|
|
|
"#{foo}#{bar}"
|
|
|
|
end
|
|
|
|
|
|
|
|
make_str
|
|
|
|
make_str
|
|
|
|
}
|
|
|
|
|
|
|
|
# test string interpolation with unknown non-strings
|
|
|
|
assert_equal 'foo123', %q{
|
|
|
|
def make_str(foo, bar)
|
|
|
|
"#{foo}#{bar}"
|
|
|
|
end
|
|
|
|
|
|
|
|
make_str("foo", 123)
|
|
|
|
make_str("foo", 123)
|
|
|
|
}
|
2021-06-08 10:52:57 -04:00
|
|
|
|
2022-05-20 19:39:37 -04:00
|
|
|
# test that invalidation of String#to_s doesn't crash
|
|
|
|
assert_equal 'meh', %q{
|
2022-06-01 10:22:08 -04:00
|
|
|
def inval_method
|
|
|
|
"".to_s
|
|
|
|
end
|
|
|
|
|
|
|
|
inval_method
|
|
|
|
|
2022-05-20 19:39:37 -04:00
|
|
|
class String
|
|
|
|
def to_s
|
|
|
|
"meh"
|
|
|
|
end
|
|
|
|
end
|
2022-06-01 10:22:08 -04:00
|
|
|
|
|
|
|
inval_method
|
|
|
|
}
|
|
|
|
|
2022-06-10 13:52:43 -04:00
|
|
|
# test that overriding to_s on a String subclass works consistently
|
2022-06-01 10:22:08 -04:00
|
|
|
assert_equal 'meh', %q{
|
|
|
|
class MyString < String
|
|
|
|
def to_s
|
|
|
|
"meh"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_to_s(obj)
|
|
|
|
obj.to_s
|
|
|
|
end
|
|
|
|
|
|
|
|
OBJ = MyString.new
|
|
|
|
|
2022-06-10 13:52:43 -04:00
|
|
|
# Should return '' both times
|
2022-06-01 10:22:08 -04:00
|
|
|
test_to_s("")
|
|
|
|
test_to_s("")
|
|
|
|
|
|
|
|
# Can return '' if YJIT optimises String#to_s too aggressively
|
|
|
|
test_to_s(OBJ)
|
|
|
|
test_to_s(OBJ)
|
2022-05-20 19:39:37 -04:00
|
|
|
}
|
|
|
|
|
2021-11-19 16:57:09 -05:00
|
|
|
# test string interpolation with overridden to_s
|
|
|
|
assert_equal 'foo', %q{
|
|
|
|
class String
|
|
|
|
def to_s
|
|
|
|
"bad"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def make_str(foo)
|
|
|
|
"#{foo}"
|
|
|
|
end
|
|
|
|
|
|
|
|
make_str("foo")
|
|
|
|
make_str("foo")
|
|
|
|
}
|
|
|
|
|
2022-06-07 11:20:57 -04:00
|
|
|
# Test that String unary plus returns the same object ID for an unfrozen string.
|
2022-06-13 09:53:09 -04:00
|
|
|
assert_equal 'true', %q{
|
|
|
|
def jittable_method
|
|
|
|
str = "bar"
|
2022-06-07 11:20:57 -04:00
|
|
|
|
2022-06-13 09:53:09 -04:00
|
|
|
old_obj_id = str.object_id
|
|
|
|
uplus_str = +str
|
2022-06-07 11:20:57 -04:00
|
|
|
|
2022-06-13 09:53:09 -04:00
|
|
|
uplus_str.object_id == old_obj_id
|
2022-06-07 11:20:57 -04:00
|
|
|
end
|
2022-06-13 09:53:09 -04:00
|
|
|
jittable_method
|
2022-06-07 11:20:57 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
# Test that String unary plus returns a different unfrozen string when given a frozen string
|
|
|
|
assert_equal 'false', %q{
|
2022-06-13 09:53:09 -04:00
|
|
|
# Logic needs to be inside an ISEQ, such as a method, for YJIT to compile it
|
|
|
|
def jittable_method
|
|
|
|
frozen_str = "foo".freeze
|
2022-06-07 11:20:57 -04:00
|
|
|
|
2022-06-13 09:53:09 -04:00
|
|
|
old_obj_id = frozen_str.object_id
|
|
|
|
uplus_str = +frozen_str
|
2022-06-07 11:20:57 -04:00
|
|
|
|
2022-06-13 09:53:09 -04:00
|
|
|
uplus_str.object_id == old_obj_id || uplus_str.frozen?
|
2022-06-07 11:20:57 -04:00
|
|
|
end
|
|
|
|
|
2022-06-13 09:53:09 -04:00
|
|
|
jittable_method
|
2022-06-07 11:20:57 -04:00
|
|
|
}
|
2021-11-19 16:57:09 -05:00
|
|
|
|
2022-06-10 13:52:43 -04:00
|
|
|
# String-subclass objects should behave as expected inside string-interpolation via concatstrings
|
2022-06-13 09:53:09 -04:00
|
|
|
assert_equal 'monkeys / monkeys, yo!', %q{
|
2022-06-10 13:52:43 -04:00
|
|
|
class MyString < String
|
|
|
|
# This is a terrible idea in production code, but we'd like YJIT to match CRuby
|
|
|
|
def to_s
|
|
|
|
super + ", yo!"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-06-13 09:53:09 -04:00
|
|
|
def jittable_method
|
|
|
|
m = MyString.new('monkeys')
|
|
|
|
"#{m} / #{m.to_s}"
|
|
|
|
end
|
2022-06-10 13:52:43 -04:00
|
|
|
|
2022-06-13 09:53:09 -04:00
|
|
|
jittable_method
|
2022-06-10 13:52:43 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
# String-subclass objects should behave as expected for string equality
|
2022-06-13 09:53:09 -04:00
|
|
|
assert_equal 'false', %q{
|
2022-06-10 13:52:43 -04:00
|
|
|
class MyString < String
|
|
|
|
# This is a terrible idea in production code, but we'd like YJIT to match CRuby
|
|
|
|
def ==(b)
|
|
|
|
"#{self}_" == b
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-06-13 09:53:09 -04:00
|
|
|
def jittable_method
|
|
|
|
ma = MyString.new("a")
|
|
|
|
|
|
|
|
# Check equality with string-subclass receiver
|
|
|
|
ma == "a" || ma != "a_" ||
|
|
|
|
# Check equality with string receiver
|
|
|
|
"a_" == ma || "a" != ma ||
|
|
|
|
# Check equality between string subclasses
|
|
|
|
ma != MyString.new("a_") ||
|
|
|
|
# Make sure "string always equals itself" check isn't used with overridden equality
|
|
|
|
ma == ma
|
|
|
|
end
|
|
|
|
jittable_method
|
|
|
|
}
|
|
|
|
|
|
|
|
# Test to_s duplicates a string subclass object but not a string
|
|
|
|
assert_equal 'false', %q{
|
|
|
|
class MyString < String; end
|
2022-06-10 13:52:43 -04:00
|
|
|
|
2022-06-13 09:53:09 -04:00
|
|
|
def jittable_method
|
|
|
|
a = "a"
|
|
|
|
ma = MyString.new("a")
|
2022-06-10 13:52:43 -04:00
|
|
|
|
2022-06-13 09:53:09 -04:00
|
|
|
a.object_id != a.to_s.object_id ||
|
|
|
|
ma.object_id == ma.to_s.object_id
|
|
|
|
end
|
|
|
|
jittable_method
|
2022-06-10 13:52:43 -04:00
|
|
|
}
|
|
|
|
|
2022-06-13 09:53:09 -04:00
|
|
|
# Test freeze on string subclass
|
|
|
|
assert_equal 'true', %q{
|
2022-06-10 13:52:43 -04:00
|
|
|
class MyString < String; end
|
|
|
|
|
2022-06-13 09:53:09 -04:00
|
|
|
def jittable_method
|
|
|
|
fma = MyString.new("a").freeze
|
2022-06-10 13:52:43 -04:00
|
|
|
|
2022-06-13 09:53:09 -04:00
|
|
|
# Freezing a string subclass should not duplicate it
|
|
|
|
fma.object_id == fma.freeze.object_id
|
|
|
|
end
|
|
|
|
jittable_method
|
|
|
|
}
|
|
|
|
|
|
|
|
# Test unary minus on string subclass
|
|
|
|
assert_equal 'true', %q{
|
|
|
|
class MyString < String; end
|
2022-06-10 13:52:43 -04:00
|
|
|
|
2022-06-13 09:53:09 -04:00
|
|
|
def jittable_method
|
|
|
|
ma = MyString.new("a")
|
|
|
|
fma = MyString.new("a").freeze
|
2022-06-10 13:52:43 -04:00
|
|
|
|
2022-06-13 09:53:09 -04:00
|
|
|
# Unary minus on frozen string subclass should not duplicate it
|
|
|
|
fma.object_id == (-fma).object_id &&
|
|
|
|
# Unary minus on unfrozen string subclass should duplicate it
|
|
|
|
ma.object_id != (-ma).object_id
|
|
|
|
end
|
|
|
|
jittable_method
|
|
|
|
}
|
|
|
|
|
|
|
|
# Test unary plus on string subclass
|
|
|
|
assert_equal 'true', %q{
|
|
|
|
class MyString < String; end
|
|
|
|
|
|
|
|
def jittable_method
|
|
|
|
fma = MyString.new("a").freeze
|
|
|
|
|
|
|
|
# Unary plus on frozen string subclass should not duplicate it
|
|
|
|
fma.object_id != (+fma).object_id
|
|
|
|
end
|
|
|
|
jittable_method
|
2022-06-10 13:52:43 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
# Test << operator on string subclass
|
|
|
|
assert_equal 'abab', %q{
|
|
|
|
class MyString < String; end
|
|
|
|
|
2022-06-13 09:53:09 -04:00
|
|
|
def jittable_method
|
|
|
|
a = -"a"
|
|
|
|
mb = MyString.new("b")
|
2022-06-10 13:52:43 -04:00
|
|
|
|
2022-06-13 09:53:09 -04:00
|
|
|
buf = String.new
|
|
|
|
mbuf = MyString.new
|
2022-06-10 13:52:43 -04:00
|
|
|
|
2022-06-13 09:53:09 -04:00
|
|
|
buf << a << mb
|
|
|
|
mbuf << a << mb
|
2022-06-10 13:52:43 -04:00
|
|
|
|
2022-06-13 09:53:09 -04:00
|
|
|
buf + mbuf
|
|
|
|
end
|
|
|
|
jittable_method
|
2022-06-10 13:52:43 -04:00
|
|
|
}
|
|
|
|
|
2021-07-09 02:33:55 -04:00
|
|
|
# test invokebuiltin as used in struct assignment
|
|
|
|
assert_equal '123', %q{
|
|
|
|
def foo(obj)
|
|
|
|
obj.foo = 123
|
|
|
|
end
|
|
|
|
|
|
|
|
struct = Struct.new(:foo)
|
|
|
|
obj = struct.new
|
|
|
|
foo(obj)
|
|
|
|
foo(obj)
|
|
|
|
}
|
|
|
|
|
2021-06-12 17:02:51 -04:00
|
|
|
# test invokebuiltin_delegate as used inside Dir.open
|
|
|
|
assert_equal '.', %q{
|
|
|
|
def foo(path)
|
|
|
|
Dir.open(path).path
|
|
|
|
end
|
|
|
|
|
|
|
|
foo(".")
|
|
|
|
foo(".")
|
|
|
|
}
|
|
|
|
|
|
|
|
# test invokebuiltin_delegate_leave in method called from jit
|
|
|
|
assert_normal_exit %q{
|
|
|
|
def foo(obj)
|
|
|
|
obj.clone
|
|
|
|
end
|
|
|
|
|
|
|
|
foo(Object.new)
|
|
|
|
foo(Object.new)
|
|
|
|
}
|
|
|
|
|
|
|
|
# test invokebuiltin_delegate_leave in method called from cfunc
|
|
|
|
assert_normal_exit %q{
|
|
|
|
def foo(obj)
|
|
|
|
[obj].map(&:clone)
|
|
|
|
end
|
|
|
|
|
|
|
|
foo(Object.new)
|
|
|
|
foo(Object.new)
|
|
|
|
}
|
|
|
|
|
2021-06-16 18:06:06 -04:00
|
|
|
# defining TrueClass#!
|
|
|
|
assert_equal '[false, false, :ok]', %q{
|
|
|
|
def foo(obj)
|
|
|
|
!obj
|
|
|
|
end
|
|
|
|
|
|
|
|
x = foo(true)
|
|
|
|
y = foo(true)
|
|
|
|
|
|
|
|
class TrueClass
|
|
|
|
def !
|
|
|
|
:ok
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
z = foo(true)
|
|
|
|
|
|
|
|
[x, y, z]
|
|
|
|
}
|
|
|
|
|
|
|
|
# defining FalseClass#!
|
|
|
|
assert_equal '[true, true, :ok]', %q{
|
|
|
|
def foo(obj)
|
|
|
|
!obj
|
|
|
|
end
|
|
|
|
|
|
|
|
x = foo(false)
|
|
|
|
y = foo(false)
|
|
|
|
|
|
|
|
class FalseClass
|
|
|
|
def !
|
|
|
|
:ok
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
z = foo(false)
|
|
|
|
|
|
|
|
[x, y, z]
|
|
|
|
}
|
|
|
|
|
|
|
|
# defining NilClass#!
|
|
|
|
assert_equal '[true, true, :ok]', %q{
|
|
|
|
def foo(obj)
|
|
|
|
!obj
|
|
|
|
end
|
|
|
|
|
|
|
|
x = foo(nil)
|
|
|
|
y = foo(nil)
|
|
|
|
|
|
|
|
class NilClass
|
|
|
|
def !
|
|
|
|
:ok
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
z = foo(nil)
|
|
|
|
|
|
|
|
[x, y, z]
|
|
|
|
}
|
|
|
|
|
|
|
|
# polymorphic opt_not
|
|
|
|
assert_equal '[true, true, false, false, false, false, false]', %q{
|
|
|
|
def foo(obj)
|
|
|
|
!obj
|
|
|
|
end
|
|
|
|
|
|
|
|
foo(0)
|
|
|
|
[foo(nil), foo(false), foo(true), foo([]), foo(0), foo(4.2), foo(:sym)]
|
|
|
|
}
|
|
|
|
|
2021-06-08 10:52:57 -04:00
|
|
|
# getlocal with 2 levels
|
|
|
|
assert_equal '7', %q{
|
|
|
|
def foo(foo, bar)
|
|
|
|
while foo > 0
|
|
|
|
while bar > 0
|
|
|
|
return foo + bar
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
foo(5,2)
|
|
|
|
foo(5,2)
|
|
|
|
}
|
2021-06-17 14:29:28 -04:00
|
|
|
|
2021-06-18 19:05:02 -04:00
|
|
|
# test pattern matching
|
|
|
|
assert_equal '[:ok, :ok]', %q{
|
|
|
|
class C
|
|
|
|
def destructure_keys
|
|
|
|
{}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
pattern_match = ->(i) do
|
|
|
|
case i
|
|
|
|
in a: 0
|
|
|
|
:ng
|
|
|
|
else
|
|
|
|
:ok
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
[{}, C.new].map(&pattern_match)
|
|
|
|
}
|
|
|
|
|
2021-06-17 14:29:28 -04:00
|
|
|
# Call to object with singleton
|
|
|
|
assert_equal '123', %q{
|
|
|
|
obj = Object.new
|
|
|
|
def obj.foo
|
|
|
|
123
|
|
|
|
end
|
|
|
|
|
|
|
|
def foo(obj)
|
|
|
|
obj.foo()
|
|
|
|
end
|
|
|
|
|
|
|
|
foo(obj)
|
|
|
|
foo(obj)
|
|
|
|
}
|
|
|
|
|
2021-06-21 20:18:54 -04:00
|
|
|
# Call method on an object that has a non-material
|
|
|
|
# singleton class.
|
|
|
|
# TODO: assert that it takes no side exits? This
|
|
|
|
# test case revealed that we were taking exits unnecessarily.
|
|
|
|
assert_normal_exit %q{
|
|
|
|
def foo(obj)
|
|
|
|
obj.itself
|
|
|
|
end
|
|
|
|
|
|
|
|
o = Object.new.singleton_class
|
|
|
|
foo(o)
|
|
|
|
foo(o)
|
|
|
|
}
|
|
|
|
|
2021-06-17 14:29:28 -04:00
|
|
|
# Call to singleton class
|
|
|
|
assert_equal '123', %q{
|
|
|
|
class Foo
|
|
|
|
def self.foo
|
|
|
|
123
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def foo(obj)
|
|
|
|
obj.foo()
|
|
|
|
end
|
|
|
|
|
|
|
|
foo(Foo)
|
|
|
|
foo(Foo)
|
|
|
|
}
|
2021-06-21 19:26:50 -04:00
|
|
|
|
|
|
|
# invokesuper edge case
|
|
|
|
assert_equal '[:A, [:A, :B]]', %q{
|
|
|
|
class B
|
|
|
|
def foo = :B
|
|
|
|
end
|
|
|
|
|
|
|
|
class A < B
|
|
|
|
def foo = [:A, super()]
|
|
|
|
end
|
|
|
|
|
|
|
|
A.new.foo
|
|
|
|
A.new.foo # compile A#foo
|
|
|
|
|
|
|
|
class C < A
|
|
|
|
define_method(:bar, A.instance_method(:foo))
|
|
|
|
end
|
|
|
|
|
|
|
|
C.new.bar
|
|
|
|
}
|
2021-06-23 20:28:42 -04:00
|
|
|
|
|
|
|
# Same invokesuper bytecode, multiple destinations
|
|
|
|
assert_equal '[:Forward, :SecondTerminus]', %q{
|
|
|
|
module Terminus
|
|
|
|
def foo = :Terminus
|
|
|
|
end
|
|
|
|
|
|
|
|
module SecondTerminus
|
|
|
|
def foo = :SecondTerminus
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
module Forward
|
|
|
|
def foo = [:Forward, super]
|
|
|
|
end
|
|
|
|
|
|
|
|
class B
|
|
|
|
include SecondTerminus
|
|
|
|
end
|
|
|
|
|
|
|
|
class A < B
|
|
|
|
include Terminus
|
|
|
|
include Forward
|
|
|
|
end
|
|
|
|
|
|
|
|
A.new.foo
|
|
|
|
A.new.foo # compile
|
|
|
|
|
|
|
|
class B
|
|
|
|
include Forward
|
|
|
|
alias bar foo
|
|
|
|
end
|
|
|
|
|
|
|
|
# A.ancestors.take(5) == [A, Forward, Terminus, B, Forward, SecondTerminus]
|
|
|
|
|
|
|
|
A.new.bar
|
|
|
|
}
|
|
|
|
|
|
|
|
# invokesuper calling into itself
|
|
|
|
assert_equal '[:B, [:B, :m]]', %q{
|
|
|
|
module M
|
|
|
|
def foo = :m
|
|
|
|
end
|
|
|
|
|
|
|
|
class B
|
|
|
|
include M
|
|
|
|
def foo = [:B, super]
|
|
|
|
end
|
|
|
|
|
|
|
|
ins = B.new
|
|
|
|
ins.singleton_class # materialize the singleton class
|
|
|
|
ins.foo
|
|
|
|
ins.foo # compile
|
|
|
|
|
|
|
|
ins.singleton_class.define_method(:bar, B.instance_method(:foo))
|
|
|
|
ins.bar
|
|
|
|
}
|
2021-06-22 22:22:30 -04:00
|
|
|
|
2021-08-30 23:57:31 -04:00
|
|
|
# invokesuper changed ancestor
|
|
|
|
assert_equal '[:A, [:M, :B]]', %q{
|
|
|
|
class B
|
|
|
|
def foo
|
|
|
|
:B
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class A < B
|
|
|
|
def foo
|
|
|
|
[:A, super]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
module M
|
|
|
|
def foo
|
|
|
|
[:M, super]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
ins = A.new
|
|
|
|
ins.foo
|
|
|
|
ins.foo
|
|
|
|
A.include(M)
|
|
|
|
ins.foo
|
|
|
|
}
|
|
|
|
|
|
|
|
# invokesuper changed ancestor via prepend
|
|
|
|
assert_equal '[:A, [:M, :B]]', %q{
|
|
|
|
class B
|
|
|
|
def foo
|
|
|
|
:B
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class A < B
|
|
|
|
def foo
|
|
|
|
[:A, super]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
module M
|
|
|
|
def foo
|
|
|
|
[:M, super]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
ins = A.new
|
|
|
|
ins.foo
|
|
|
|
ins.foo
|
|
|
|
B.prepend(M)
|
|
|
|
ins.foo
|
|
|
|
}
|
|
|
|
|
|
|
|
# invokesuper replaced method
|
|
|
|
assert_equal '[:A, :Btwo]', %q{
|
|
|
|
class B
|
|
|
|
def foo
|
|
|
|
:B
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class A < B
|
|
|
|
def foo
|
|
|
|
[:A, super]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
ins = A.new
|
|
|
|
ins.foo
|
|
|
|
ins.foo
|
|
|
|
class B
|
|
|
|
def foo
|
|
|
|
:Btwo
|
|
|
|
end
|
|
|
|
end
|
|
|
|
ins.foo
|
|
|
|
}
|
|
|
|
|
2021-06-22 22:22:30 -04:00
|
|
|
# Call to fixnum
|
|
|
|
assert_equal '[true, false]', %q{
|
|
|
|
def is_odd(obj)
|
|
|
|
obj.odd?
|
|
|
|
end
|
|
|
|
|
|
|
|
is_odd(1)
|
|
|
|
is_odd(1)
|
|
|
|
|
|
|
|
[is_odd(123), is_odd(456)]
|
|
|
|
}
|
|
|
|
|
|
|
|
# Call to bignum
|
|
|
|
assert_equal '[true, false]', %q{
|
|
|
|
def is_odd(obj)
|
|
|
|
obj.odd?
|
|
|
|
end
|
|
|
|
|
|
|
|
bignum = 99999999999999999999
|
|
|
|
is_odd(bignum)
|
|
|
|
is_odd(bignum)
|
|
|
|
|
|
|
|
[is_odd(bignum), is_odd(bignum+1)]
|
|
|
|
}
|
|
|
|
|
|
|
|
# Call to fixnum and bignum
|
|
|
|
assert_equal '[true, false, true, false]', %q{
|
|
|
|
def is_odd(obj)
|
|
|
|
obj.odd?
|
|
|
|
end
|
|
|
|
|
|
|
|
bignum = 99999999999999999999
|
|
|
|
is_odd(bignum)
|
|
|
|
is_odd(bignum)
|
|
|
|
is_odd(123)
|
|
|
|
is_odd(123)
|
|
|
|
|
|
|
|
[is_odd(123), is_odd(456), is_odd(bignum), is_odd(bignum+1)]
|
|
|
|
}
|
|
|
|
|
|
|
|
# Call to static and dynamic symbol
|
|
|
|
assert_equal 'bar', %q{
|
|
|
|
def to_string(obj)
|
|
|
|
obj.to_s
|
|
|
|
end
|
|
|
|
|
|
|
|
to_string(:foo)
|
|
|
|
to_string(:foo)
|
|
|
|
to_string((-"bar").to_sym)
|
|
|
|
to_string((-"bar").to_sym)
|
|
|
|
}
|
2021-06-24 11:15:53 -04:00
|
|
|
|
|
|
|
# Call to flonum and heap float
|
|
|
|
assert_equal '[nil, nil, nil, 1]', %q{
|
|
|
|
def is_inf(obj)
|
|
|
|
obj.infinite?
|
|
|
|
end
|
|
|
|
|
|
|
|
is_inf(0.0)
|
|
|
|
is_inf(0.0)
|
|
|
|
is_inf(1e256)
|
|
|
|
is_inf(1e256)
|
|
|
|
|
|
|
|
[
|
|
|
|
is_inf(0.0),
|
|
|
|
is_inf(1.0),
|
|
|
|
is_inf(1e256),
|
|
|
|
is_inf(1.0/0.0)
|
|
|
|
]
|
|
|
|
}
|
2021-07-14 15:16:56 -04:00
|
|
|
|
|
|
|
assert_equal '[1, 2, 3, 4, 5]', %q{
|
|
|
|
def splatarray
|
|
|
|
[*(1..5)]
|
|
|
|
end
|
|
|
|
|
|
|
|
splatarray
|
|
|
|
splatarray
|
|
|
|
}
|
2021-07-14 13:46:18 -04:00
|
|
|
|
|
|
|
assert_equal '[1, 1, 2, 1, 2, 3]', %q{
|
|
|
|
def expandarray
|
|
|
|
arr = [1, 2, 3]
|
|
|
|
|
|
|
|
a, = arr
|
|
|
|
b, c, = arr
|
|
|
|
d, e, f = arr
|
|
|
|
|
|
|
|
[a, b, c, d, e, f]
|
|
|
|
end
|
|
|
|
|
|
|
|
expandarray
|
|
|
|
expandarray
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_equal '[1, 1]', %q{
|
|
|
|
def expandarray_useless_splat
|
|
|
|
arr = (1..10).to_a
|
|
|
|
|
|
|
|
a, * = arr
|
|
|
|
b, (*) = arr
|
|
|
|
|
|
|
|
[a, b]
|
|
|
|
end
|
|
|
|
|
|
|
|
expandarray_useless_splat
|
|
|
|
expandarray_useless_splat
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_equal '[:not_heap, nil, nil]', %q{
|
|
|
|
def expandarray_not_heap
|
|
|
|
a, b, c = :not_heap
|
|
|
|
[a, b, c]
|
|
|
|
end
|
|
|
|
|
|
|
|
expandarray_not_heap
|
|
|
|
expandarray_not_heap
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_equal '[:not_array, nil, nil]', %q{
|
|
|
|
def expandarray_not_array(obj)
|
|
|
|
a, b, c = obj
|
|
|
|
[a, b, c]
|
|
|
|
end
|
|
|
|
|
|
|
|
obj = Object.new
|
|
|
|
def obj.to_ary
|
|
|
|
[:not_array]
|
|
|
|
end
|
|
|
|
|
|
|
|
expandarray_not_array(obj)
|
|
|
|
expandarray_not_array(obj)
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_equal '[1, 2, nil]', %q{
|
|
|
|
def expandarray_rhs_too_small
|
|
|
|
a, b, c = [1, 2]
|
|
|
|
[a, b, c]
|
|
|
|
end
|
|
|
|
|
|
|
|
expandarray_rhs_too_small
|
|
|
|
expandarray_rhs_too_small
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_equal '[1, [2]]', %q{
|
|
|
|
def expandarray_splat
|
|
|
|
a, *b = [1, 2]
|
|
|
|
[a, b]
|
|
|
|
end
|
|
|
|
|
|
|
|
expandarray_splat
|
|
|
|
expandarray_splat
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_equal '2', %q{
|
|
|
|
def expandarray_postarg
|
|
|
|
*, a = [1, 2]
|
|
|
|
a
|
|
|
|
end
|
|
|
|
|
|
|
|
expandarray_postarg
|
|
|
|
expandarray_postarg
|
|
|
|
}
|
2021-07-15 15:35:20 -04:00
|
|
|
|
|
|
|
assert_equal '10', %q{
|
|
|
|
obj = Object.new
|
|
|
|
val = nil
|
|
|
|
obj.define_singleton_method(:to_ary) { val = 10; [] }
|
|
|
|
|
|
|
|
def expandarray_always_call_to_ary(object)
|
|
|
|
* = object
|
|
|
|
end
|
|
|
|
|
|
|
|
expandarray_always_call_to_ary(obj)
|
|
|
|
expandarray_always_call_to_ary(obj)
|
|
|
|
|
|
|
|
val
|
|
|
|
}
|
2021-08-10 18:41:27 -04:00
|
|
|
|
|
|
|
# regression test of local type change
|
|
|
|
assert_equal '1.1', %q{
|
|
|
|
def bar(baz, quux)
|
|
|
|
if baz.integer?
|
|
|
|
baz, quux = quux, nil
|
|
|
|
end
|
|
|
|
baz.to_s
|
|
|
|
end
|
|
|
|
|
|
|
|
bar(123, 1.1)
|
|
|
|
bar(123, 1.1)
|
|
|
|
}
|
2021-08-25 17:00:45 -04:00
|
|
|
|
|
|
|
# test enabling a line TracePoint in a C method call
|
|
|
|
assert_equal '[[:line, true]]', %q{
|
|
|
|
events = []
|
|
|
|
events.instance_variable_set(
|
|
|
|
:@tp,
|
|
|
|
TracePoint.new(:line) { |tp| events << [tp.event, tp.lineno] if tp.path == __FILE__ }
|
|
|
|
)
|
|
|
|
def events.to_str
|
|
|
|
@tp.enable; ''
|
|
|
|
end
|
|
|
|
|
|
|
|
# Stay in generated code while enabling tracing
|
|
|
|
def events.compiled(obj)
|
|
|
|
String(obj)
|
|
|
|
@tp.disable; __LINE__
|
|
|
|
end
|
|
|
|
|
|
|
|
line = events.compiled(events)
|
|
|
|
events[0][-1] = (events[0][-1] == line)
|
|
|
|
|
|
|
|
events
|
|
|
|
}
|
|
|
|
|
|
|
|
# test enabling a c_return TracePoint in a C method call
|
|
|
|
assert_equal '[[:c_return, :String, :string_alias, "events_to_str"]]', %q{
|
|
|
|
events = []
|
|
|
|
events.instance_variable_set(:@tp, TracePoint.new(:c_return) { |tp| events << [tp.event, tp.method_id, tp.callee_id, tp.return_value] })
|
|
|
|
def events.to_str
|
|
|
|
@tp.enable; 'events_to_str'
|
|
|
|
end
|
|
|
|
|
|
|
|
# Stay in generated code while enabling tracing
|
|
|
|
alias string_alias String
|
|
|
|
def events.compiled(obj)
|
|
|
|
string_alias(obj)
|
|
|
|
@tp.disable
|
|
|
|
end
|
|
|
|
|
|
|
|
events.compiled(events)
|
|
|
|
|
|
|
|
events
|
|
|
|
}
|
|
|
|
|
|
|
|
# test enabling a TracePoint that targets a particular line in a C method call
|
|
|
|
assert_equal '[true]', %q{
|
|
|
|
events = []
|
|
|
|
events.instance_variable_set(:@tp, TracePoint.new(:line) { |tp| events << tp.lineno })
|
|
|
|
def events.to_str
|
|
|
|
@tp.enable(target: method(:compiled))
|
|
|
|
''
|
|
|
|
end
|
|
|
|
|
|
|
|
# Stay in generated code while enabling tracing
|
|
|
|
def events.compiled(obj)
|
|
|
|
String(obj)
|
|
|
|
__LINE__
|
|
|
|
end
|
|
|
|
|
|
|
|
line = events.compiled(events)
|
|
|
|
events[0] = (events[0] == line)
|
|
|
|
|
|
|
|
events
|
|
|
|
}
|
|
|
|
|
|
|
|
# test enabling tracing in the middle of splatarray
|
|
|
|
assert_equal '[true]', %q{
|
|
|
|
events = []
|
|
|
|
obj = Object.new
|
|
|
|
obj.instance_variable_set(:@tp, TracePoint.new(:line) { |tp| events << tp.lineno })
|
|
|
|
def obj.to_a
|
|
|
|
@tp.enable(target: method(:compiled))
|
|
|
|
[]
|
|
|
|
end
|
|
|
|
|
|
|
|
# Enable tracing in the middle of the splatarray instruction
|
|
|
|
def obj.compiled(obj)
|
|
|
|
* = *obj
|
|
|
|
__LINE__
|
|
|
|
end
|
|
|
|
|
|
|
|
obj.compiled([])
|
|
|
|
line = obj.compiled(obj)
|
|
|
|
events[0] = (events[0] == line)
|
|
|
|
|
|
|
|
events
|
|
|
|
}
|
|
|
|
|
|
|
|
# test enabling tracing in the middle of opt_aref. Different since the codegen
|
|
|
|
# for it ends in a jump.
|
|
|
|
assert_equal '[true]', %q{
|
|
|
|
def lookup(hash, tp)
|
|
|
|
hash[42]
|
|
|
|
tp.disable; __LINE__
|
|
|
|
end
|
|
|
|
|
|
|
|
lines = []
|
|
|
|
tp = TracePoint.new(:line) { lines << _1.lineno if _1.path == __FILE__ }
|
|
|
|
|
|
|
|
lookup(:foo, tp)
|
|
|
|
lookup({}, tp)
|
|
|
|
|
|
|
|
enable_tracing_on_missing = Hash.new { tp.enable }
|
|
|
|
|
|
|
|
expected_line = lookup(enable_tracing_on_missing, tp)
|
|
|
|
|
|
|
|
lines[0] = true if lines[0] == expected_line
|
|
|
|
|
|
|
|
lines
|
|
|
|
}
|
|
|
|
|
|
|
|
# test enabling c_call tracing before compiling
|
|
|
|
assert_equal '[[:c_call, :itself]]', %q{
|
|
|
|
def shouldnt_compile
|
|
|
|
itself
|
|
|
|
end
|
|
|
|
|
|
|
|
events = []
|
|
|
|
tp = TracePoint.new(:c_call) { |tp| events << [tp.event, tp.method_id] }
|
|
|
|
|
|
|
|
# assume first call compiles
|
|
|
|
tp.enable { shouldnt_compile }
|
|
|
|
|
|
|
|
events
|
|
|
|
}
|
|
|
|
|
|
|
|
# test enabling c_return tracing before compiling
|
|
|
|
assert_equal '[[:c_return, :itself, main]]', %q{
|
|
|
|
def shouldnt_compile
|
|
|
|
itself
|
|
|
|
end
|
|
|
|
|
|
|
|
events = []
|
|
|
|
tp = TracePoint.new(:c_return) { |tp| events << [tp.event, tp.method_id, tp.return_value] }
|
|
|
|
|
|
|
|
# assume first call compiles
|
|
|
|
tp.enable { shouldnt_compile }
|
|
|
|
|
|
|
|
events
|
|
|
|
}
|
|
|
|
|
|
|
|
# test enabling tracing for a suspended fiber
|
|
|
|
assert_equal '[[:return, 42]]', %q{
|
|
|
|
def traced_method
|
|
|
|
Fiber.yield
|
|
|
|
42
|
|
|
|
end
|
|
|
|
|
|
|
|
events = []
|
|
|
|
tp = TracePoint.new(:return) { events << [_1.event, _1.return_value] }
|
|
|
|
# assume first call compiles
|
|
|
|
fiber = Fiber.new { traced_method }
|
|
|
|
fiber.resume
|
|
|
|
tp.enable(target: method(:traced_method))
|
|
|
|
fiber.resume
|
|
|
|
|
|
|
|
events
|
|
|
|
}
|
|
|
|
|
|
|
|
# test compiling on non-tracing ractor then running on a tracing one
|
|
|
|
assert_equal '[:itself]', %q{
|
|
|
|
def traced_method
|
|
|
|
itself
|
|
|
|
end
|
|
|
|
|
|
|
|
tracing_ractor = Ractor.new do
|
|
|
|
# 1: start tracing
|
|
|
|
events = []
|
|
|
|
tp = TracePoint.new(:c_call) { events << _1.method_id }
|
|
|
|
tp.enable
|
|
|
|
Ractor.yield(nil)
|
|
|
|
|
2021-08-25 17:08:00 -04:00
|
|
|
# 3: run compiled method on tracing ractor
|
2021-08-25 17:00:45 -04:00
|
|
|
Ractor.yield(nil)
|
|
|
|
traced_method
|
|
|
|
|
|
|
|
events
|
|
|
|
ensure
|
|
|
|
tp&.disable
|
|
|
|
end
|
|
|
|
|
|
|
|
tracing_ractor.take
|
|
|
|
|
|
|
|
# 2: compile on non tracing ractor
|
|
|
|
traced_method
|
|
|
|
|
|
|
|
tracing_ractor.take
|
|
|
|
tracing_ractor.take
|
|
|
|
}
|
|
|
|
|
|
|
|
# Try to hit a lazy branch stub while another ractor enables tracing
|
|
|
|
assert_equal '42', %q{
|
|
|
|
def compiled(arg)
|
|
|
|
if arg
|
|
|
|
arg + 1
|
|
|
|
else
|
|
|
|
itself
|
|
|
|
itself
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
ractor = Ractor.new do
|
|
|
|
compiled(false)
|
|
|
|
Ractor.yield(nil)
|
|
|
|
compiled(41)
|
|
|
|
end
|
|
|
|
|
|
|
|
tp = TracePoint.new(:line) { itself }
|
|
|
|
ractor.take
|
|
|
|
tp.enable
|
|
|
|
|
|
|
|
ractor.take
|
|
|
|
}
|
2021-08-27 21:35:34 -04:00
|
|
|
|
|
|
|
# Test equality with changing types
|
|
|
|
assert_equal '[true, false, false, false]', %q{
|
|
|
|
def eq(a, b)
|
|
|
|
a == b
|
|
|
|
end
|
|
|
|
|
|
|
|
[
|
|
|
|
eq("foo", "foo"),
|
|
|
|
eq("foo", "bar"),
|
|
|
|
eq(:foo, "bar"),
|
|
|
|
eq("foo", :bar)
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
2021-09-12 02:29:24 -04:00
|
|
|
# Redefined String eq
|
2021-08-27 21:35:34 -04:00
|
|
|
assert_equal 'true', %q{
|
|
|
|
class String
|
|
|
|
def ==(other)
|
|
|
|
true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-09-12 02:29:24 -04:00
|
|
|
def eq(a, b)
|
|
|
|
a == b
|
|
|
|
end
|
|
|
|
|
|
|
|
eq("foo", "bar")
|
|
|
|
eq("foo", "bar")
|
|
|
|
}
|
|
|
|
|
|
|
|
# Redefined Integer eq
|
|
|
|
assert_equal 'true', %q{
|
|
|
|
class Integer
|
|
|
|
def ==(other)
|
|
|
|
true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def eq(a, b)
|
|
|
|
a == b
|
|
|
|
end
|
|
|
|
|
|
|
|
eq(1, 2)
|
|
|
|
eq(1, 2)
|
2021-08-27 21:35:34 -04:00
|
|
|
}
|
2021-09-17 11:38:14 -04:00
|
|
|
|
|
|
|
# aset on array with invalid key
|
|
|
|
assert_normal_exit %q{
|
|
|
|
def foo(arr)
|
|
|
|
arr[:foo] = 123
|
|
|
|
end
|
|
|
|
|
|
|
|
foo([1]) rescue nil
|
|
|
|
foo([1]) rescue nil
|
|
|
|
}
|
2021-09-23 16:27:05 -04:00
|
|
|
|
2021-10-22 04:24:34 -04:00
|
|
|
# test ractor exception on when setting ivar
|
2021-09-23 16:27:05 -04:00
|
|
|
assert_equal '42', %q{
|
|
|
|
class A
|
|
|
|
def self.foo
|
|
|
|
_foo = 1
|
|
|
|
_bar = 2
|
|
|
|
begin
|
2021-10-22 04:24:34 -04:00
|
|
|
@bar = _foo + _bar
|
2021-09-23 16:27:05 -04:00
|
|
|
rescue Ractor::IsolationError
|
|
|
|
42
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
A.foo
|
|
|
|
A.foo
|
|
|
|
|
|
|
|
Ractor.new { A.foo }.take
|
|
|
|
}
|
2021-09-24 16:57:53 -04:00
|
|
|
|
|
|
|
assert_equal '["plain", "special", "sub", "plain"]', %q{
|
|
|
|
def foo(arg)
|
|
|
|
arg.to_s
|
|
|
|
end
|
|
|
|
|
|
|
|
class Sub < String
|
|
|
|
end
|
|
|
|
|
|
|
|
special = String.new("special")
|
|
|
|
special.singleton_class
|
|
|
|
|
|
|
|
[
|
|
|
|
foo("plain"),
|
|
|
|
foo(special),
|
|
|
|
foo(Sub.new("sub")),
|
|
|
|
foo("plain")
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_equal '["sub", "sub"]', %q{
|
|
|
|
def foo(arg)
|
|
|
|
arg.to_s
|
|
|
|
end
|
|
|
|
|
|
|
|
class Sub < String
|
|
|
|
def to_s
|
|
|
|
super
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
sub = Sub.new("sub")
|
|
|
|
|
|
|
|
[foo(sub), foo(sub)]
|
|
|
|
}
|
2021-09-28 15:51:33 -04:00
|
|
|
|
2021-10-08 14:40:38 -04:00
|
|
|
assert_equal '[1]', %q{
|
|
|
|
def kwargs(value:)
|
|
|
|
value
|
|
|
|
end
|
|
|
|
|
|
|
|
5.times.map { kwargs(value: 1) }.uniq
|
|
|
|
}
|
|
|
|
|
2021-12-16 13:19:24 -05:00
|
|
|
assert_equal '[:ok]', %q{
|
|
|
|
def kwargs(value:)
|
|
|
|
value
|
|
|
|
end
|
|
|
|
|
|
|
|
5.times.map { kwargs() rescue :ok }.uniq
|
|
|
|
}
|
|
|
|
|
2021-12-16 18:08:07 -05:00
|
|
|
assert_equal '[:ok]', %q{
|
|
|
|
def kwargs(a:, b: nil)
|
|
|
|
value
|
|
|
|
end
|
|
|
|
|
|
|
|
5.times.map { kwargs(b: 123) rescue :ok }.uniq
|
|
|
|
}
|
|
|
|
|
2021-10-08 14:40:38 -04:00
|
|
|
assert_equal '[[1, 2]]', %q{
|
|
|
|
def kwargs(left:, right:)
|
|
|
|
[left, right]
|
|
|
|
end
|
|
|
|
|
|
|
|
5.times.flat_map do
|
|
|
|
[
|
|
|
|
kwargs(left: 1, right: 2),
|
|
|
|
kwargs(right: 2, left: 1)
|
|
|
|
]
|
|
|
|
end.uniq
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_equal '[[1, 2]]', %q{
|
|
|
|
def kwargs(lead, kwarg:)
|
|
|
|
[lead, kwarg]
|
|
|
|
end
|
|
|
|
|
|
|
|
5.times.map { kwargs(1, kwarg: 2) }.uniq
|
|
|
|
}
|
|
|
|
|
2021-12-16 13:19:24 -05:00
|
|
|
# optional and keyword args
|
|
|
|
assert_equal '[[1, 2, 3]]', %q{
|
|
|
|
def opt_and_kwargs(a, b=2, c: nil)
|
|
|
|
[a,b,c]
|
|
|
|
end
|
|
|
|
|
|
|
|
5.times.map { opt_and_kwargs(1, c: 3) }.uniq
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_equal '[[1, 2, 3]]', %q{
|
|
|
|
def opt_and_kwargs(a, b=nil, c: nil)
|
|
|
|
[a,b,c]
|
|
|
|
end
|
|
|
|
|
|
|
|
5.times.map { opt_and_kwargs(1, 2, c: 3) }.uniq
|
|
|
|
}
|
|
|
|
|
2021-12-30 14:13:37 -05:00
|
|
|
# Bug #18453
|
|
|
|
assert_equal '[[1, nil, 2]]', %q{
|
|
|
|
def opt_and_kwargs(a = {}, b: nil, c: nil)
|
|
|
|
[a, b, c]
|
|
|
|
end
|
|
|
|
|
|
|
|
5.times.map { opt_and_kwargs(1, c: 2) }.uniq
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_equal '[[{}, nil, 1]]', %q{
|
|
|
|
def opt_and_kwargs(a = {}, b: nil, c: nil)
|
|
|
|
[a, b, c]
|
|
|
|
end
|
|
|
|
|
|
|
|
5.times.map { opt_and_kwargs(c: 1) }.uniq
|
|
|
|
}
|
2021-12-16 13:19:24 -05:00
|
|
|
|
2021-10-07 11:01:37 -04:00
|
|
|
# leading and keyword arguments are swapped into the right order
|
|
|
|
assert_equal '[[1, 2, 3, 4, 5, 6]]', %q{
|
|
|
|
def kwargs(five, six, a:, b:, c:, d:)
|
|
|
|
[a, b, c, d, five, six]
|
2021-09-28 15:51:33 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
5.times.flat_map do
|
|
|
|
[
|
2021-10-07 11:01:37 -04:00
|
|
|
kwargs(5, 6, a: 1, b: 2, c: 3, d: 4),
|
|
|
|
kwargs(5, 6, a: 1, b: 2, d: 4, c: 3),
|
|
|
|
kwargs(5, 6, a: 1, c: 3, b: 2, d: 4),
|
|
|
|
kwargs(5, 6, a: 1, c: 3, d: 4, b: 2),
|
|
|
|
kwargs(5, 6, a: 1, d: 4, b: 2, c: 3),
|
|
|
|
kwargs(5, 6, a: 1, d: 4, c: 3, b: 2),
|
|
|
|
kwargs(5, 6, b: 2, a: 1, c: 3, d: 4),
|
|
|
|
kwargs(5, 6, b: 2, a: 1, d: 4, c: 3),
|
|
|
|
kwargs(5, 6, b: 2, c: 3, a: 1, d: 4),
|
|
|
|
kwargs(5, 6, b: 2, c: 3, d: 4, a: 1),
|
|
|
|
kwargs(5, 6, b: 2, d: 4, a: 1, c: 3),
|
|
|
|
kwargs(5, 6, b: 2, d: 4, c: 3, a: 1),
|
|
|
|
kwargs(5, 6, c: 3, a: 1, b: 2, d: 4),
|
|
|
|
kwargs(5, 6, c: 3, a: 1, d: 4, b: 2),
|
|
|
|
kwargs(5, 6, c: 3, b: 2, a: 1, d: 4),
|
|
|
|
kwargs(5, 6, c: 3, b: 2, d: 4, a: 1),
|
|
|
|
kwargs(5, 6, c: 3, d: 4, a: 1, b: 2),
|
|
|
|
kwargs(5, 6, c: 3, d: 4, b: 2, a: 1),
|
|
|
|
kwargs(5, 6, d: 4, a: 1, b: 2, c: 3),
|
|
|
|
kwargs(5, 6, d: 4, a: 1, c: 3, b: 2),
|
|
|
|
kwargs(5, 6, d: 4, b: 2, a: 1, c: 3),
|
|
|
|
kwargs(5, 6, d: 4, b: 2, c: 3, a: 1),
|
|
|
|
kwargs(5, 6, d: 4, c: 3, a: 1, b: 2),
|
|
|
|
kwargs(5, 6, d: 4, c: 3, b: 2, a: 1)
|
2021-09-28 15:51:33 -04:00
|
|
|
]
|
|
|
|
end.uniq
|
|
|
|
}
|
2021-10-07 11:01:37 -04:00
|
|
|
|
|
|
|
# implicit hashes get skipped and don't break compilation
|
|
|
|
assert_equal '[[:key]]', %q{
|
|
|
|
def implicit(hash)
|
|
|
|
hash.keys
|
|
|
|
end
|
|
|
|
|
|
|
|
5.times.map { implicit(key: :value) }.uniq
|
|
|
|
}
|
|
|
|
|
|
|
|
# default values on keywords don't mess up argument order
|
|
|
|
assert_equal '[2]', %q{
|
|
|
|
def default_value
|
|
|
|
1
|
|
|
|
end
|
|
|
|
|
|
|
|
def default_expression(value: default_value)
|
|
|
|
value
|
|
|
|
end
|
|
|
|
|
|
|
|
5.times.map { default_expression(value: 2) }.uniq
|
|
|
|
}
|
2021-10-18 17:01:40 -04:00
|
|
|
|
2021-11-01 10:54:59 -04:00
|
|
|
# constant default values on keywords
|
|
|
|
assert_equal '[3]', %q{
|
|
|
|
def default_expression(value: 3)
|
|
|
|
value
|
|
|
|
end
|
|
|
|
|
|
|
|
5.times.map { default_expression }.uniq
|
|
|
|
}
|
|
|
|
|
|
|
|
# non-constant default values on keywords
|
|
|
|
assert_equal '[3]', %q{
|
|
|
|
def default_value
|
|
|
|
3
|
|
|
|
end
|
|
|
|
|
|
|
|
def default_expression(value: default_value)
|
|
|
|
value
|
|
|
|
end
|
|
|
|
|
|
|
|
5.times.map { default_expression }.uniq
|
|
|
|
}
|
|
|
|
|
2021-11-05 17:01:07 -04:00
|
|
|
# reordered optional kwargs
|
|
|
|
assert_equal '[[100, 1]]', %q{
|
|
|
|
def foo(capacity: 100, max: nil)
|
|
|
|
[capacity, max]
|
|
|
|
end
|
|
|
|
|
|
|
|
5.times.map { foo(max: 1) }.uniq
|
|
|
|
}
|
|
|
|
|
|
|
|
# invalid lead param
|
|
|
|
assert_equal 'ok', %q{
|
|
|
|
def bar(baz: 2)
|
|
|
|
baz
|
|
|
|
end
|
|
|
|
|
|
|
|
def foo
|
|
|
|
bar(1, baz: 123)
|
|
|
|
end
|
|
|
|
|
|
|
|
begin
|
|
|
|
foo
|
|
|
|
foo
|
|
|
|
rescue ArgumentError => e
|
|
|
|
print "ok"
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
# reordered required kwargs
|
|
|
|
assert_equal '[[1, 2, 3, 4]]', %q{
|
|
|
|
def foo(default1: 1, required1:, default2: 3, required2:)
|
|
|
|
[default1, required1, default2, required2]
|
|
|
|
end
|
|
|
|
|
|
|
|
5.times.map { foo(required1: 2, required2: 4) }.uniq
|
|
|
|
}
|
|
|
|
|
|
|
|
# reordered default expression kwargs
|
|
|
|
assert_equal '[[:one, :two, 3]]', %q{
|
|
|
|
def foo(arg1: (1+0), arg2: (2+0), arg3: (3+0))
|
|
|
|
[arg1, arg2, arg3]
|
|
|
|
end
|
|
|
|
|
|
|
|
5.times.map { foo(arg2: :two, arg1: :one) }.uniq
|
|
|
|
}
|
|
|
|
|
|
|
|
# complex kwargs
|
|
|
|
assert_equal '[[1, 2, 3, 4]]', %q{
|
|
|
|
def foo(required:, specified: 999, simple_default: 3, complex_default: "4".to_i)
|
|
|
|
[required, specified, simple_default, complex_default]
|
|
|
|
end
|
|
|
|
|
|
|
|
5.times.map { foo(specified: 2, required: 1) }.uniq
|
|
|
|
}
|
|
|
|
|
2022-01-05 19:00:21 -05:00
|
|
|
# cfunc kwargs
|
|
|
|
assert_equal '{:foo=>123}', %q{
|
|
|
|
def foo(bar)
|
|
|
|
bar.store(:value, foo: 123)
|
|
|
|
bar[:value]
|
|
|
|
end
|
|
|
|
|
|
|
|
foo({})
|
|
|
|
foo({})
|
|
|
|
}
|
|
|
|
|
|
|
|
# cfunc kwargs
|
|
|
|
assert_equal '{:foo=>123}', %q{
|
|
|
|
def foo(bar)
|
|
|
|
bar.replace(foo: 123)
|
|
|
|
end
|
|
|
|
|
|
|
|
foo({})
|
|
|
|
foo({})
|
|
|
|
}
|
|
|
|
|
|
|
|
# cfunc kwargs
|
|
|
|
assert_equal '{:foo=>123, :bar=>456}', %q{
|
|
|
|
def foo(bar)
|
|
|
|
bar.replace(foo: 123, bar: 456)
|
|
|
|
end
|
|
|
|
|
|
|
|
foo({})
|
|
|
|
foo({})
|
|
|
|
}
|
|
|
|
|
|
|
|
# variadic cfunc kwargs
|
|
|
|
assert_equal '{:foo=>123}', %q{
|
|
|
|
def foo(bar)
|
|
|
|
bar.merge(foo: 123)
|
|
|
|
end
|
|
|
|
|
|
|
|
foo({})
|
|
|
|
foo({})
|
|
|
|
}
|
|
|
|
|
|
|
|
# optimized cfunc kwargs
|
|
|
|
assert_equal 'false', %q{
|
|
|
|
def foo
|
|
|
|
:foo.eql?(foo: :foo)
|
|
|
|
end
|
|
|
|
|
|
|
|
foo
|
|
|
|
foo
|
|
|
|
}
|
|
|
|
|
2021-10-18 17:01:40 -04:00
|
|
|
# attr_reader on frozen object
|
|
|
|
assert_equal 'false', %q{
|
|
|
|
class Foo
|
|
|
|
attr_reader :exception
|
|
|
|
|
|
|
|
def failed?
|
|
|
|
!exception.nil?
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
foo = Foo.new.freeze
|
|
|
|
foo.failed?
|
|
|
|
foo.failed?
|
|
|
|
}
|
2021-10-20 13:20:08 -04:00
|
|
|
|
|
|
|
# regression test for doing kwarg shuffle before checking for interrupts
|
|
|
|
assert_equal 'ok', %q{
|
|
|
|
def new_media_drop(attributes:, product_drop:, context:, sources:)
|
|
|
|
nil.nomethod rescue nil # force YJIT to bail to side exit
|
|
|
|
|
|
|
|
[attributes, product_drop, context, sources]
|
|
|
|
end
|
|
|
|
|
|
|
|
def load_medias(product_drop: nil, raw_medias:, context:)
|
|
|
|
raw_medias.map do |raw_media|
|
|
|
|
case new_media_drop(context: context, attributes: raw_media, product_drop: product_drop, sources: [])
|
|
|
|
in [Hash, ProductDrop, Context, Array]
|
|
|
|
else
|
|
|
|
raise "bad shuffle"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class Context; end
|
|
|
|
|
|
|
|
class ProductDrop
|
|
|
|
attr_reader :title
|
|
|
|
def initialize(title)
|
|
|
|
@title = title
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Make a thread so we have thread switching interrupts
|
|
|
|
th = Thread.new do
|
|
|
|
while true; end
|
|
|
|
end
|
|
|
|
1_000.times do |i|
|
|
|
|
load_medias(product_drop: ProductDrop.new("foo"), raw_medias: [{}, {}], context: Context.new)
|
|
|
|
end
|
|
|
|
th.kill.join
|
|
|
|
|
|
|
|
:ok
|
|
|
|
}
|
2021-10-21 15:07:32 -04:00
|
|
|
|
|
|
|
# regression test for tracing attr_accessor methods.
|
|
|
|
assert_equal "true", %q{
|
|
|
|
c = Class.new do
|
|
|
|
attr_accessor :x
|
|
|
|
alias y x
|
|
|
|
alias y= x=
|
|
|
|
end
|
|
|
|
obj = c.new
|
|
|
|
|
|
|
|
ar_meth = obj.method(:x)
|
|
|
|
aw_meth = obj.method(:x=)
|
|
|
|
aar_meth = obj.method(:y)
|
|
|
|
aaw_meth = obj.method(:y=)
|
|
|
|
events = []
|
|
|
|
trace = TracePoint.new(:c_call, :c_return){|tp|
|
|
|
|
next if tp.path != __FILE__
|
|
|
|
next if tp.method_id == :call
|
|
|
|
case tp.event
|
|
|
|
when :c_call
|
|
|
|
events << [tp.event, tp.method_id, tp.callee_id]
|
|
|
|
when :c_return
|
|
|
|
events << [tp.event, tp.method_id, tp.callee_id, tp.return_value]
|
|
|
|
end
|
|
|
|
}
|
|
|
|
test_proc = proc do
|
|
|
|
obj.x = 1
|
|
|
|
obj.x
|
|
|
|
obj.y = 2
|
|
|
|
obj.y
|
|
|
|
aw_meth.call(1)
|
|
|
|
ar_meth.call
|
|
|
|
aaw_meth.call(2)
|
|
|
|
aar_meth.call
|
|
|
|
end
|
|
|
|
test_proc.call # populate call caches
|
|
|
|
trace.enable(&test_proc)
|
|
|
|
expected = [
|
|
|
|
[:c_call, :x=, :x=],
|
|
|
|
[:c_return, :x=, :x=, 1],
|
|
|
|
[:c_call, :x, :x],
|
|
|
|
[:c_return, :x, :x, 1],
|
|
|
|
[:c_call, :x=, :y=],
|
|
|
|
[:c_return, :x=, :y=, 2],
|
|
|
|
[:c_call, :x, :y],
|
|
|
|
[:c_return, :x, :y, 2],
|
|
|
|
] * 2
|
|
|
|
|
|
|
|
expected == events
|
|
|
|
}
|
2021-10-25 10:40:33 -04:00
|
|
|
|
|
|
|
# duphash
|
|
|
|
assert_equal '{:foo=>123}', %q{
|
|
|
|
def foo
|
|
|
|
{foo: 123}
|
|
|
|
end
|
|
|
|
|
|
|
|
foo
|
|
|
|
foo
|
|
|
|
}
|
2021-10-27 10:55:43 -04:00
|
|
|
|
|
|
|
# newhash
|
|
|
|
assert_equal '{:foo=>2}', %q{
|
|
|
|
def foo
|
|
|
|
{foo: 1+1}
|
|
|
|
end
|
|
|
|
|
|
|
|
foo
|
|
|
|
foo
|
|
|
|
}
|
2021-11-04 12:30:30 -04:00
|
|
|
|
|
|
|
# block invalidation edge case
|
|
|
|
assert_equal 'undef', %q{
|
|
|
|
class A
|
|
|
|
def foo(arg)
|
|
|
|
arg.times { A.remove_method(:bar) }
|
|
|
|
self
|
|
|
|
end
|
|
|
|
|
|
|
|
def bar
|
|
|
|
4
|
|
|
|
end
|
|
|
|
|
|
|
|
def use(arg)
|
|
|
|
# two consecutive sends. When bar is removed, the return address
|
|
|
|
# for calling it is already on foo's control frame
|
|
|
|
foo(arg).bar
|
|
|
|
rescue NoMethodError
|
|
|
|
:undef
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
A.new.use 0
|
|
|
|
A.new.use 0
|
|
|
|
A.new.use 1
|
|
|
|
}
|
|
|
|
|
|
|
|
# block invalidation edge case
|
|
|
|
assert_equal 'ok', %q{
|
|
|
|
class A
|
|
|
|
Good = :ng
|
|
|
|
def foo(arg)
|
|
|
|
arg.times { A.const_set(:Good, :ok) }
|
|
|
|
self
|
|
|
|
end
|
|
|
|
|
|
|
|
def id(arg)
|
|
|
|
arg
|
|
|
|
end
|
|
|
|
|
|
|
|
def use(arg)
|
|
|
|
# send followed by an opt_getinlinecache.
|
|
|
|
# The return address remains on the control frame
|
|
|
|
# when opt_getinlinecache is invalidated.
|
|
|
|
foo(arg).id(Good)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
A.new.use 0
|
|
|
|
A.new.use 0
|
|
|
|
A.new.use 1
|
|
|
|
}
|
|
|
|
|
YJIT: Add ability to exit to interpreter from stubs
Previously, YJIT assumed that it's always possible to generate a new
basic block when servicing a stub in branch_stub_hit(). When YJIT is out
of executable memory, for example, this assumption doesn't hold up.
Add handling to branch_stub_hit() for servicing stubs without consuming
more executable memory by adding a code path that exits to the
interpreter at the location the branch stub represents. The new code
path reconstructs interpreter state in branch_stub_hit() and then exits
with a new snippet called `code_for_exit_from_stub` that returns
`Qundef` from the YJIT native stack frame.
As this change adds another place where we regenerate code from
`branch_t`, extract the logic for it into a new function and call it
regenerate_branch(). While we are at it, make the branch shrinking code
path in branch_stub_hit() more explicit.
This new functionality is hard to test without full support for out of
memory conditions. To verify this change, I ran
`RUBY_YJIT_ENABLE=1 make check -j12` with the following patch to stress
test the new code path:
```diff
diff --git a/yjit_core.c b/yjit_core.c
index 4ab63d9806..5788b8c5ed 100644
--- a/yjit_core.c
+++ b/yjit_core.c
@@ -878,8 +878,12 @@ branch_stub_hit(branch_t *branch, const uint32_t target_idx, rb_execution_contex
cb_set_write_ptr(cb, branch->end_addr);
}
+if (rand() < RAND_MAX/2) {
// Compile the new block version
p_block = gen_block_version(target, target_ctx, ec);
+}else{
+ p_block = NULL;
+}
if (!p_block && branch_modified) {
// We couldn't generate a new block for the branch, but we modified the branch.
```
We can enable the new test along with other OOM tests once full support
lands.
Other small changes:
* yjit_utils.c (print_str): Update to work with new native frame shape.
Follow up for 8fa0ee4d404.
* yjit_iface.c (rb_yjit_init): Run yjit_init_core() after
yjit_init_codegen() so `cb` and `ocb` are available.
2021-11-26 18:00:42 -05:00
|
|
|
assert_equal 'ok', %q{
|
|
|
|
# test hitting a branch stub when out of memory
|
|
|
|
def nimai(jita)
|
|
|
|
if jita
|
|
|
|
:ng
|
|
|
|
else
|
|
|
|
:ok
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
nimai(true)
|
|
|
|
nimai(true)
|
|
|
|
|
|
|
|
RubyVM::YJIT.simulate_oom! if defined?(RubyVM::YJIT)
|
|
|
|
|
|
|
|
nimai(false)
|
2021-11-30 18:11:42 -05:00
|
|
|
}
|
YJIT: Add ability to exit to interpreter from stubs
Previously, YJIT assumed that it's always possible to generate a new
basic block when servicing a stub in branch_stub_hit(). When YJIT is out
of executable memory, for example, this assumption doesn't hold up.
Add handling to branch_stub_hit() for servicing stubs without consuming
more executable memory by adding a code path that exits to the
interpreter at the location the branch stub represents. The new code
path reconstructs interpreter state in branch_stub_hit() and then exits
with a new snippet called `code_for_exit_from_stub` that returns
`Qundef` from the YJIT native stack frame.
As this change adds another place where we regenerate code from
`branch_t`, extract the logic for it into a new function and call it
regenerate_branch(). While we are at it, make the branch shrinking code
path in branch_stub_hit() more explicit.
This new functionality is hard to test without full support for out of
memory conditions. To verify this change, I ran
`RUBY_YJIT_ENABLE=1 make check -j12` with the following patch to stress
test the new code path:
```diff
diff --git a/yjit_core.c b/yjit_core.c
index 4ab63d9806..5788b8c5ed 100644
--- a/yjit_core.c
+++ b/yjit_core.c
@@ -878,8 +878,12 @@ branch_stub_hit(branch_t *branch, const uint32_t target_idx, rb_execution_contex
cb_set_write_ptr(cb, branch->end_addr);
}
+if (rand() < RAND_MAX/2) {
// Compile the new block version
p_block = gen_block_version(target, target_ctx, ec);
+}else{
+ p_block = NULL;
+}
if (!p_block && branch_modified) {
// We couldn't generate a new block for the branch, but we modified the branch.
```
We can enable the new test along with other OOM tests once full support
lands.
Other small changes:
* yjit_utils.c (print_str): Update to work with new native frame shape.
Follow up for 8fa0ee4d404.
* yjit_iface.c (rb_yjit_init): Run yjit_init_core() after
yjit_init_codegen() so `cb` and `ocb` are available.
2021-11-26 18:00:42 -05:00
|
|
|
|
2021-11-04 12:30:30 -04:00
|
|
|
assert_equal 'new', %q{
|
2021-11-30 18:11:42 -05:00
|
|
|
# test block invalidation while out of memory
|
2021-11-04 12:30:30 -04:00
|
|
|
def foo
|
|
|
|
:old
|
|
|
|
end
|
|
|
|
|
|
|
|
def test
|
|
|
|
foo
|
|
|
|
end
|
|
|
|
|
|
|
|
test
|
|
|
|
test
|
|
|
|
|
|
|
|
RubyVM::YJIT.simulate_oom! if defined?(RubyVM::YJIT)
|
|
|
|
|
|
|
|
def foo
|
|
|
|
:new
|
|
|
|
end
|
|
|
|
|
|
|
|
test
|
2021-11-30 18:11:42 -05:00
|
|
|
}
|
2021-11-25 14:56:58 -05:00
|
|
|
|
2021-11-19 23:44:13 -05:00
|
|
|
assert_equal 'ok', %q{
|
|
|
|
# Try to compile new method while OOM
|
|
|
|
def foo
|
|
|
|
:ok
|
|
|
|
end
|
|
|
|
|
|
|
|
RubyVM::YJIT.simulate_oom! if defined?(RubyVM::YJIT)
|
|
|
|
|
|
|
|
foo
|
|
|
|
foo
|
|
|
|
}
|
|
|
|
|
2021-11-25 14:56:58 -05:00
|
|
|
# struct aref embedded
|
|
|
|
assert_equal '2', %q{
|
|
|
|
def foo(s)
|
|
|
|
s.foo
|
|
|
|
end
|
|
|
|
|
|
|
|
S = Struct.new(:foo)
|
|
|
|
foo(S.new(1))
|
|
|
|
foo(S.new(2))
|
|
|
|
}
|
|
|
|
|
|
|
|
# struct aref non-embedded
|
|
|
|
assert_equal '4', %q{
|
|
|
|
def foo(s)
|
|
|
|
s.d
|
|
|
|
end
|
|
|
|
|
|
|
|
S = Struct.new(:a, :b, :c, :d, :e)
|
|
|
|
foo(S.new(1,2,3,4,5))
|
|
|
|
foo(S.new(1,2,3,4,5))
|
|
|
|
}
|
|
|
|
|
|
|
|
# struct aset embedded
|
|
|
|
assert_equal '123', %q{
|
|
|
|
def foo(s)
|
|
|
|
s.foo = 123
|
|
|
|
end
|
|
|
|
|
|
|
|
s = Struct.new(:foo).new
|
|
|
|
foo(s)
|
|
|
|
s = Struct.new(:foo).new
|
|
|
|
foo(s)
|
|
|
|
s.foo
|
|
|
|
}
|
|
|
|
|
|
|
|
# struct aset non-embedded
|
|
|
|
assert_equal '[1, 2, 3, 4, 5]', %q{
|
|
|
|
def foo(s)
|
|
|
|
s.a = 1
|
|
|
|
s.b = 2
|
|
|
|
s.c = 3
|
|
|
|
s.d = 4
|
|
|
|
s.e = 5
|
|
|
|
end
|
|
|
|
|
|
|
|
S = Struct.new(:a, :b, :c, :d, :e)
|
|
|
|
s = S.new
|
|
|
|
foo(s)
|
|
|
|
s = S.new
|
|
|
|
foo(s)
|
|
|
|
[s.a, s.b, s.c, s.d, s.e]
|
|
|
|
}
|
|
|
|
|
|
|
|
# struct aref too many args
|
|
|
|
assert_equal 'ok', %q{
|
|
|
|
def foo(s)
|
|
|
|
s.foo(:bad)
|
|
|
|
end
|
|
|
|
|
|
|
|
s = Struct.new(:foo).new
|
|
|
|
foo(s) rescue :ok
|
|
|
|
foo(s) rescue :ok
|
|
|
|
}
|
|
|
|
|
|
|
|
# struct aset too many args
|
|
|
|
assert_equal 'ok', %q{
|
|
|
|
def foo(s)
|
|
|
|
s.set_foo(123, :bad)
|
|
|
|
end
|
|
|
|
|
|
|
|
s = Struct.new(:foo) do
|
|
|
|
alias :set_foo :foo=
|
|
|
|
end
|
|
|
|
foo(s) rescue :ok
|
|
|
|
foo(s) rescue :ok
|
|
|
|
}
|
2022-01-05 04:07:52 -05:00
|
|
|
|
|
|
|
# File.join is a cfunc accepting variable arguments as a Ruby array (argc = -2)
|
|
|
|
assert_equal 'foo/bar', %q{
|
|
|
|
def foo
|
|
|
|
File.join("foo", "bar")
|
|
|
|
end
|
|
|
|
|
|
|
|
foo
|
|
|
|
foo
|
|
|
|
}
|
|
|
|
|
|
|
|
# File.join is a cfunc accepting variable arguments as a Ruby array (argc = -2)
|
|
|
|
assert_equal '', %q{
|
|
|
|
def foo
|
|
|
|
File.join()
|
|
|
|
end
|
|
|
|
|
|
|
|
foo
|
|
|
|
foo
|
|
|
|
}
|
Rust YJIT
In December 2021, we opened an [issue] to solicit feedback regarding the
porting of the YJIT codebase from C99 to Rust. There were some
reservations, but this project was given the go ahead by Ruby core
developers and Matz. Since then, we have successfully completed the port
of YJIT to Rust.
The new Rust version of YJIT has reached parity with the C version, in
that it passes all the CRuby tests, is able to run all of the YJIT
benchmarks, and performs similarly to the C version (because it works
the same way and largely generates the same machine code). We've even
incorporated some design improvements, such as a more fine-grained
constant invalidation mechanism which we expect will make a big
difference in Ruby on Rails applications.
Because we want to be careful, YJIT is guarded behind a configure
option:
```shell
./configure --enable-yjit # Build YJIT in release mode
./configure --enable-yjit=dev # Build YJIT in dev/debug mode
```
By default, YJIT does not get compiled and cargo/rustc is not required.
If YJIT is built in dev mode, then `cargo` is used to fetch development
dependencies, but when building in release, `cargo` is not required,
only `rustc`. At the moment YJIT requires Rust 1.60.0 or newer.
The YJIT command-line options remain mostly unchanged, and more details
about the build process are documented in `doc/yjit/yjit.md`.
The CI tests have been updated and do not take any more resources than
before.
The development history of the Rust port is available at the following
commit for interested parties:
https://github.com/Shopify/ruby/commit/1fd9573d8b4b65219f1c2407f30a0a60e537f8be
Our hope is that Rust YJIT will be compiled and included as a part of
system packages and compiled binaries of the Ruby 3.2 release. We do not
anticipate any major problems as Rust is well supported on every
platform which YJIT supports, but to make sure that this process works
smoothly, we would like to reach out to those who take care of building
systems packages before the 3.2 release is shipped and resolve any
issues that may come up.
[issue]: https://bugs.ruby-lang.org/issues/18481
Co-authored-by: Maxime Chevalier-Boisvert <maximechevalierb@gmail.com>
Co-authored-by: Noah Gibbs <the.codefolio.guy@gmail.com>
Co-authored-by: Kevin Newton <kddnewton@gmail.com>
2022-04-19 14:40:21 -04:00
|
|
|
|
|
|
|
# Make sure we're correctly reading RStruct's as.ary union for embedded RStructs
|
|
|
|
assert_equal '3,12', %q{
|
|
|
|
pt_struct = Struct.new(:x, :y)
|
|
|
|
p = pt_struct.new(3, 12)
|
|
|
|
def pt_inspect(pt)
|
|
|
|
"#{pt.x},#{pt.y}"
|
|
|
|
end
|
|
|
|
|
|
|
|
# Make sure pt_inspect is JITted
|
|
|
|
10.times { pt_inspect(p) }
|
|
|
|
|
|
|
|
# Make sure it's returning '3,12' instead of e.g. '3,false'
|
|
|
|
pt_inspect(p)
|
|
|
|
}
|
|
|
|
|
|
|
|
# Regression test for deadlock between branch_stub_hit and ractor_receive_if
|
|
|
|
assert_equal '10', %q{
|
|
|
|
r = Ractor.new Ractor.current do |main|
|
|
|
|
main << 1
|
|
|
|
main << 2
|
|
|
|
main << 3
|
|
|
|
main << 4
|
|
|
|
main << 5
|
|
|
|
main << 6
|
|
|
|
main << 7
|
|
|
|
main << 8
|
|
|
|
main << 9
|
|
|
|
main << 10
|
|
|
|
end
|
|
|
|
|
|
|
|
a = []
|
|
|
|
a << Ractor.receive_if{|msg| msg == 10}
|
|
|
|
a << Ractor.receive_if{|msg| msg == 9}
|
|
|
|
a << Ractor.receive_if{|msg| msg == 8}
|
|
|
|
a << Ractor.receive_if{|msg| msg == 7}
|
|
|
|
a << Ractor.receive_if{|msg| msg == 6}
|
|
|
|
a << Ractor.receive_if{|msg| msg == 5}
|
|
|
|
a << Ractor.receive_if{|msg| msg == 4}
|
|
|
|
a << Ractor.receive_if{|msg| msg == 3}
|
|
|
|
a << Ractor.receive_if{|msg| msg == 2}
|
|
|
|
a << Ractor.receive_if{|msg| msg == 1}
|
|
|
|
|
|
|
|
a.length
|
|
|
|
}
|