1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/bootstraptest/test_yjit.rb
Aaron Patterson 05b5a7f011 Add a guard that we start executing on the first PC
Methods with optional parameters don't always start executing at the
first PC, but we compile all methods assuming that they do.  This commit
adds a guard to ensure that we're actually starting at the first PC for
methods with optional params
2021-10-20 18:19:37 -04:00

1507 lines
21 KiB
Ruby

# 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]
}
# 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
GC.verify_compaction_references(double_heap: true, toward: :empty)
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 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
}
# 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
}