mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
ac5d6faea8
Previously, YJIT incorrectly discarded the upper 32 bits of the object pointer when writing out VALUEs to setup default keyword arguments. In addition to incorrectly truncating, the output pointers were not properly tracked for handling GC compaction moving the referenced objects. YJIT previously attempted to encode a mov instruction with a memory destination and a 64 bit immediate when there is no such encoding possible in the ISA. Add an assert to mitigate not being able to catch this at build time.
2687 lines
41 KiB
Ruby
2687 lines
41 KiB
Ruby
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
|
|
}
|
|
|
|
assert_normal_exit %q{
|
|
# regression test for a leak caught by an asert on --yjit-call-threshold=2
|
|
Foo = 1
|
|
|
|
eval("def foo = [#{(['Foo,']*256).join}]")
|
|
|
|
foo
|
|
foo
|
|
|
|
Object.send(:remove_const, :Foo)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
# 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"
|
|
}
|
|
|
|
# Check that global variable set works
|
|
assert_equal 'string', %q{
|
|
def foo
|
|
$foo = "string"
|
|
end
|
|
|
|
foo
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# Check that global variables work
|
|
assert_equal 'string', %q{
|
|
$foo = "string"
|
|
|
|
def foo
|
|
$foo
|
|
end
|
|
|
|
foo
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# 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 }
|
|
}
|
|
|
|
# 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([])
|
|
}
|
|
|
|
# Test for opt_mod
|
|
assert_equal '2', %q{
|
|
def mod(a, b)
|
|
a % b
|
|
end
|
|
|
|
mod(7, 5)
|
|
mod(7, 5)
|
|
}
|
|
|
|
# Test for opt_mult
|
|
assert_equal '12', %q{
|
|
def mult(a, b)
|
|
a * b
|
|
end
|
|
|
|
mult(6, 2)
|
|
mult(6, 2)
|
|
}
|
|
|
|
# Test for opt_div
|
|
assert_equal '3', %q{
|
|
def div(a, b)
|
|
a / b
|
|
end
|
|
|
|
div(6, 2)
|
|
div(6, 2)
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# foo leaves a temp on the stack before the call
|
|
assert_equal '6', %q{
|
|
def bar
|
|
return 5
|
|
end
|
|
|
|
def foo
|
|
return 1 + bar
|
|
end
|
|
|
|
foo()
|
|
retval = foo()
|
|
}
|
|
|
|
# Method with one arguments
|
|
# foo leaves a temp on the stack before the call
|
|
assert_equal '7', %q{
|
|
def bar(a)
|
|
return a + 1
|
|
end
|
|
|
|
def foo
|
|
return 1 + bar(5)
|
|
end
|
|
|
|
foo()
|
|
retval = foo()
|
|
}
|
|
|
|
# Method with two arguments
|
|
# 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()
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# 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)
|
|
}
|
|
|
|
# Ruby-to-Ruby call and C call
|
|
assert_normal_exit %q{
|
|
def bar
|
|
puts('hi!')
|
|
end
|
|
|
|
def foo
|
|
bar
|
|
end
|
|
|
|
foo()
|
|
foo()
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# 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)
|
|
}
|
|
|
|
# Method redefinition (code invalidation) and GC
|
|
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]
|
|
}
|
|
|
|
# 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]
|
|
}
|
|
|
|
# Test for GC safety. Don't invalidate dead iseqs.
|
|
assert_normal_exit %q{
|
|
Class.new do
|
|
def foo
|
|
itself
|
|
end
|
|
|
|
new.foo
|
|
new.foo
|
|
new.foo
|
|
new.foo
|
|
end
|
|
|
|
4.times { GC.start }
|
|
def itself
|
|
self
|
|
end
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# Test that opt_aref checks the class of the receiver
|
|
assert_equal 'special', %q{
|
|
def foo(array)
|
|
array[30]
|
|
end
|
|
|
|
foo([])
|
|
foo([])
|
|
|
|
special = []
|
|
def special.[](idx)
|
|
'special'
|
|
end
|
|
|
|
foo(special)
|
|
}
|
|
|
|
# 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
|
|
|
|
begin
|
|
GC.verify_compaction_references(double_heap: true, toward: :empty)
|
|
rescue NotImplementedError
|
|
# in case compaction isn't supported
|
|
end
|
|
|
|
foo
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# 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)
|
|
]
|
|
}
|
|
|
|
# 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]
|
|
}
|
|
|
|
# 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)]
|
|
}
|
|
|
|
# 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)]
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# 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]
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# test calling Ruby method with a block
|
|
assert_equal '[1, 2, 42]', %q{
|
|
def thing(a, b)
|
|
[a, b, yield]
|
|
end
|
|
|
|
def use
|
|
thing(1,2) { 42 }
|
|
end
|
|
|
|
use
|
|
use
|
|
}
|
|
|
|
# test calling C method with a block
|
|
assert_equal '[42, 42]', %q{
|
|
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
|
|
|
|
[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
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# 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)
|
|
}
|
|
|
|
# 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)
|
|
}
|
|
|
|
# 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")
|
|
}
|
|
|
|
|
|
# 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)
|
|
}
|
|
|
|
# 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)
|
|
}
|
|
|
|
# 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)]
|
|
}
|
|
|
|
# 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)
|
|
}
|
|
|
|
# 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)
|
|
}
|
|
|
|
# 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)
|
|
}
|
|
|
|
# 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)
|
|
}
|
|
|
|
# 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)
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# 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)
|
|
}
|
|
|
|
# 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)
|
|
]
|
|
}
|
|
|
|
assert_equal '[1, 2, 3, 4, 5]', %q{
|
|
def splatarray
|
|
[*(1..5)]
|
|
end
|
|
|
|
splatarray
|
|
splatarray
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
# 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)
|
|
}
|
|
|
|
# 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)
|
|
|
|
# 3: run compiled method on tracing ractor
|
|
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
|
|
}
|
|
|
|
# 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)
|
|
]
|
|
}
|
|
|
|
# Redefined String eq
|
|
assert_equal 'true', %q{
|
|
class String
|
|
def ==(other)
|
|
true
|
|
end
|
|
end
|
|
|
|
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)
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# test ractor exception on when setting ivar
|
|
assert_equal '42', %q{
|
|
class A
|
|
def self.foo
|
|
_foo = 1
|
|
_bar = 2
|
|
begin
|
|
@bar = _foo + _bar
|
|
rescue Ractor::IsolationError
|
|
42
|
|
end
|
|
end
|
|
end
|
|
|
|
A.foo
|
|
A.foo
|
|
|
|
Ractor.new { A.foo }.take
|
|
}
|
|
|
|
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)]
|
|
}
|
|
|
|
assert_equal '[1]', %q{
|
|
def kwargs(value:)
|
|
value
|
|
end
|
|
|
|
5.times.map { kwargs(value: 1) }.uniq
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
# 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]
|
|
end
|
|
|
|
5.times.flat_map do
|
|
[
|
|
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)
|
|
]
|
|
end.uniq
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# 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?
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# duphash
|
|
assert_equal '{:foo=>123}', %q{
|
|
def foo
|
|
{foo: 123}
|
|
end
|
|
|
|
foo
|
|
foo
|
|
}
|
|
|
|
# newhash
|
|
assert_equal '{:foo=>2}', %q{
|
|
def foo
|
|
{foo: 1+1}
|
|
end
|
|
|
|
foo
|
|
foo
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
assert_equal 'new', %q{
|
|
# test block invalidation while out of memory
|
|
def foo
|
|
:old
|
|
end
|
|
|
|
def test
|
|
foo
|
|
end
|
|
|
|
test
|
|
test
|
|
|
|
RubyVM::YJIT.simulate_oom! if defined?(RubyVM::YJIT)
|
|
|
|
def foo
|
|
:new
|
|
end
|
|
|
|
test
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
# 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
|
|
}
|