mirror of
https://github.com/ruby/ruby.git
synced 2025-08-28 07:26:00 +02:00

In `jit_rb_int_lshift()`, we guard against the right hand side changing since we want to avoid generating variable length shifts. When control reaches a `putobject` and `opt_ltlt` pair, though, we know that the right hand side never changes. This commit detects this situation and substitutes an implementation that does not guard against the right hand side changing, saving that work. Deleted some `putobject` Rust tests since they aren't that valuable and cause linking issues. Nice boost to `optcarrot` and `protoboeuf`: ``` ---------- ------------------ bench yjit-pre/yjit-post optcarrot 1.09 protoboeuf 1.12 ---------- ------------------ ```
4788 lines
79 KiB
Ruby
4788 lines
79 KiB
Ruby
# To run the tests in this file only, with YJIT enabled:
|
|
# make btest BTESTS=bootstraptest/test_yjit.rb RUN_OPTS="--yjit-call-threshold=1"
|
|
|
|
# regression test for popping before side exit
|
|
assert_equal "ok", %q{
|
|
def foo(a, *) = a
|
|
|
|
def call(args, &)
|
|
foo(1) # spill at where the block arg will be
|
|
foo(*args, &)
|
|
end
|
|
|
|
call([1, 2])
|
|
|
|
begin
|
|
call([])
|
|
rescue ArgumentError
|
|
:ok
|
|
end
|
|
}
|
|
|
|
# regression test for send processing before side exit
|
|
assert_equal "ok", %q{
|
|
def foo(a, *) = :foo
|
|
|
|
def call(args)
|
|
send(:foo, *args)
|
|
end
|
|
|
|
call([1, 2])
|
|
|
|
begin
|
|
call([])
|
|
rescue ArgumentError
|
|
:ok
|
|
end
|
|
}
|
|
|
|
# test discarding extra yield arguments
|
|
assert_equal "2210150001501015", %q{
|
|
def splat_kw(ary) = yield *ary, a: 1
|
|
|
|
def splat(ary) = yield *ary
|
|
|
|
def kw = yield 1, 2, a: 0
|
|
|
|
def simple = yield 0, 1
|
|
|
|
def calls
|
|
[
|
|
splat([1, 1, 2]) { |x, y| x + y },
|
|
splat([1, 1, 2]) { |y, opt = raise| opt + y},
|
|
splat_kw([0, 1]) { |a:| a },
|
|
kw { |a:| a },
|
|
kw { |a| a },
|
|
simple { 5.itself },
|
|
simple { |a| a },
|
|
simple { |opt = raise| opt },
|
|
simple { |*rest| rest },
|
|
simple { |opt_kw: 5| opt_kw },
|
|
# autosplat ineractions
|
|
[0, 1, 2].yield_self { |a, b| [a, b] },
|
|
[0, 1, 2].yield_self { |a, opt = raise| [a, opt] },
|
|
[1].yield_self { |a, opt = 4| a + opt },
|
|
]
|
|
end
|
|
|
|
calls.join
|
|
}
|
|
|
|
# test autosplat with empty splat
|
|
assert_equal "ok", %q{
|
|
def m(pos, splat) = yield pos, *splat
|
|
|
|
m([:ok], []) {|v0,| v0 }
|
|
}
|
|
|
|
# regression test for send stack shifting
|
|
assert_normal_exit %q{
|
|
def foo(a, b)
|
|
a.singleton_methods(b)
|
|
end
|
|
|
|
def call_foo
|
|
[1, 1, 1, 1, 1, 1, send(:foo, 1, 1)]
|
|
end
|
|
|
|
call_foo
|
|
}
|
|
|
|
# regression test for keyword splat with yield
|
|
assert_equal 'nil', %q{
|
|
def splat_kw(kwargs) = yield(**kwargs)
|
|
|
|
splat_kw({}) { _1 }.inspect
|
|
}
|
|
|
|
# regression test for arity check with splat
|
|
assert_equal '[:ae, :ae]', %q{
|
|
def req_one(a_, b_ = 1) = raise
|
|
|
|
def test(args)
|
|
req_one *args
|
|
rescue ArgumentError
|
|
:ae
|
|
end
|
|
|
|
[test(Array.new 5), test([])]
|
|
} unless rjit_enabled? # Not yet working on RJIT
|
|
|
|
# regression test for arity check with splat and send
|
|
assert_equal '[:ae, :ae]', %q{
|
|
def two_reqs(a, b_, _ = 1) = a.gsub(a, a)
|
|
|
|
def test(name, args)
|
|
send(name, *args)
|
|
rescue ArgumentError
|
|
:ae
|
|
end
|
|
|
|
[test(:two_reqs, ["g", nil, nil, nil]), test(:two_reqs, ["g"])]
|
|
}
|
|
|
|
# regression test for GC marking stubs in invalidated code
|
|
assert_normal_exit %q{
|
|
garbage = Array.new(10_000) { [] } # create garbage to cause iseq movement
|
|
eval(<<~RUBY)
|
|
def foo(n, garbage)
|
|
if n == 2
|
|
# 1.times.each to create a cfunc frame to preserve the JIT frame
|
|
# which will return to a stub housed in an invalidated block
|
|
return 1.times.each do
|
|
Object.define_method(:foo) {}
|
|
garbage.clear
|
|
GC.verify_compaction_references(toward: :empty, expand_heap: true)
|
|
end
|
|
end
|
|
|
|
foo(n + 1, garbage)
|
|
end
|
|
RUBY
|
|
|
|
foo(1, garbage)
|
|
}
|
|
|
|
# regression test for callee block handler overlapping with arguments
|
|
assert_equal '3', %q{
|
|
def foo(_req, *args) = args.last
|
|
|
|
def call_foo = foo(0, 1, 2, 3, &->{})
|
|
|
|
call_foo
|
|
}
|
|
|
|
# call leaf builtin with a block argument
|
|
assert_equal '0', "0.abs(&nil)"
|
|
|
|
# regression test for invokeblock iseq guard
|
|
assert_equal 'ok', %q{
|
|
return :ok unless defined?(GC.compact)
|
|
def foo = yield
|
|
10.times do |i|
|
|
ret = eval("foo { #{i} }")
|
|
raise "failed at #{i}" unless ret == i
|
|
GC.compact
|
|
end
|
|
:ok
|
|
} unless rjit_enabled? # Not yet working on RJIT
|
|
|
|
# regression test for overly generous guard elision
|
|
assert_equal '[0, :sum, 0, :sum]', %q{
|
|
# In faulty versions, the following happens:
|
|
# 1. YJIT puts object on the temp stack with type knowledge
|
|
# (CArray or CString) about RBASIC_CLASS(object).
|
|
# 2. In iter=0, due to the type knowledge, YJIT generates
|
|
# a call to sum() without any guard on RBASIC_CLASS(object).
|
|
# 3. In iter=1, a singleton class is added to the object,
|
|
# changing RBASIC_CLASS(object), falsifying the type knowledge.
|
|
# 4. Because the code from (1) has no class guard, it is incorrectly
|
|
# reused and the wrong method is invoked.
|
|
# Putting a literal is important for gaining type knowledge.
|
|
def carray(iter)
|
|
array = []
|
|
array.sum(iter.times { def array.sum(_) = :sum })
|
|
end
|
|
|
|
def cstring(iter)
|
|
string = "".dup
|
|
string.sum(iter.times { def string.sum(_) = :sum })
|
|
end
|
|
|
|
[carray(0), carray(1), cstring(0), cstring(1)]
|
|
}
|
|
|
|
# regression test for return type of Integer#/
|
|
# It can return a T_BIGNUM when inputs are T_FIXNUM.
|
|
assert_equal 0x3fffffffffffffff.to_s, %q{
|
|
def call(fixnum_min)
|
|
(fixnum_min / -1) - 1
|
|
end
|
|
|
|
call(-(2**62))
|
|
}
|
|
|
|
# regression test for return type of String#<<
|
|
assert_equal 'Sub', %q{
|
|
def call(sub) = (sub << sub).itself
|
|
|
|
class Sub < String; end
|
|
|
|
call(Sub.new('o')).class
|
|
}
|
|
|
|
# test splat filling required and feeding rest
|
|
assert_equal '[0, 1, 2, [3, 4]]', %q{
|
|
public def lead_rest(a, b, *rest)
|
|
[self, a, b, rest]
|
|
end
|
|
|
|
def call(args) = 0.lead_rest(*args)
|
|
|
|
call([1, 2, 3, 4])
|
|
}
|
|
|
|
# test missing opts are nil initialized
|
|
assert_equal '[[0, 1, nil, 3], [0, 1, nil, 3], [0, 1, nil, 3, []], [0, 1, nil, 3, []]]', %q{
|
|
public def lead_opts(a, b=binding.local_variable_get(:c), c=3)
|
|
[self, a, b, c]
|
|
end
|
|
|
|
public def opts_rest(a=raise, b=binding.local_variable_get(:c), c=3, *rest)
|
|
[self, a, b, c, rest]
|
|
end
|
|
|
|
def call(args)
|
|
[
|
|
0.lead_opts(1),
|
|
0.lead_opts(*args),
|
|
|
|
0.opts_rest(1),
|
|
0.opts_rest(*args),
|
|
]
|
|
end
|
|
|
|
call([1])
|
|
}
|
|
|
|
# test filled optionals with unspecified keyword param
|
|
assert_equal 'ok', %q{
|
|
def opt_rest_opt_kw(_=1, *, k: :ok) = k
|
|
|
|
def call = opt_rest_opt_kw(0)
|
|
|
|
call
|
|
}
|
|
|
|
# test splat empty array with rest param
|
|
assert_equal '[0, 1, 2, []]', %q{
|
|
public def foo(a=1, b=2, *rest)
|
|
[self, a, b, rest]
|
|
end
|
|
|
|
def call(args) = 0.foo(*args)
|
|
|
|
call([])
|
|
}
|
|
|
|
# Regression test for yielding with autosplat to block with
|
|
# optional parameters. https://github.com/Shopify/yjit/issues/313
|
|
assert_equal '[:a, :b, :a, :b]', %q{
|
|
def yielder(arg) = yield(arg) + yield(arg)
|
|
|
|
yielder([:a, :b]) do |c = :c, d = :d|
|
|
[c, d]
|
|
end
|
|
}
|
|
|
|
# Regression test for GC mishap while doing shape transition
|
|
assert_equal '[:ok]', %q{
|
|
# [Bug #19601]
|
|
class RegressionTest
|
|
def initialize
|
|
@a = @b = @fourth_ivar_does_shape_transition = nil
|
|
end
|
|
|
|
def extender
|
|
@first_extended_ivar = [:ok]
|
|
end
|
|
end
|
|
|
|
GC.stress = true
|
|
|
|
# Used to crash due to GC run in rb_ensure_iv_list_size()
|
|
# not marking the newly allocated [:ok].
|
|
RegressionTest.new.extender.itself
|
|
} unless rjit_enabled? # Skip on RJIT since this uncovers a crash
|
|
|
|
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)
|
|
}
|
|
|
|
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 assert on --yjit-call-threshold=2
|
|
Foo = 1
|
|
|
|
eval("def foo = [#{(['Foo,']*256).join}]")
|
|
|
|
foo
|
|
foo
|
|
|
|
Object.send(:remove_const, :Foo)
|
|
}
|
|
|
|
assert_normal_exit %q{
|
|
# Test to ensure send on overridden c functions
|
|
# doesn't corrupt the stack
|
|
class Bar
|
|
def bar(x)
|
|
x
|
|
end
|
|
end
|
|
|
|
class Foo
|
|
def bar
|
|
Bar.new
|
|
end
|
|
end
|
|
|
|
foo = Foo.new
|
|
# before this change, this line would error
|
|
# because "s" would still be on the stack
|
|
# String.to_s is the overridden method here
|
|
p foo.bar.bar("s".__send__(:to_s))
|
|
}
|
|
|
|
|
|
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 '[nil, nil, nil, nil, nil, nil]', %q{
|
|
# Tests defined? on non-heap objects
|
|
[NilClass, TrueClass, FalseClass, Integer, Float, Symbol].each do |klass|
|
|
klass.class_eval("def foo = defined?(@foo)")
|
|
end
|
|
|
|
[nil, true, false, 0xFABCAFE, 0.42, :cake].map do |instance|
|
|
instance.foo
|
|
instance.foo
|
|
end
|
|
}
|
|
|
|
assert_equal '[nil, "instance-variable", nil, "instance-variable"]', %q{
|
|
# defined? on object that changes shape between calls
|
|
class Foo
|
|
def foo
|
|
defined?(@foo)
|
|
end
|
|
|
|
def add
|
|
@foo = 1
|
|
end
|
|
|
|
def remove
|
|
self.remove_instance_variable(:@foo)
|
|
end
|
|
end
|
|
|
|
obj = Foo.new
|
|
[obj.foo, (obj.add; obj.foo), (obj.remove; obj.foo), (obj.add; obj.foo)]
|
|
}
|
|
|
|
assert_equal '["instance-variable", 5]', %q{
|
|
# defined? on object too complex for shape information
|
|
class Foo
|
|
def initialize
|
|
100.times { |i| instance_variable_set("@foo#{i}", i) }
|
|
end
|
|
|
|
def foo
|
|
[defined?(@foo5), @foo5]
|
|
end
|
|
end
|
|
|
|
Foo.new.foo
|
|
}
|
|
|
|
# getinstancevariable with shape too complex
|
|
assert_normal_exit %q{
|
|
class Foo
|
|
def initialize
|
|
@a = 1
|
|
end
|
|
|
|
def getter
|
|
@foobar
|
|
end
|
|
end
|
|
|
|
# Initialize ivars in changing order, making the Foo
|
|
# class have shape too complex
|
|
100.times do |x|
|
|
foo = Foo.new
|
|
foo.instance_variable_set(:"@a#{x}", 1)
|
|
foo.instance_variable_set(:"@foobar", 777)
|
|
|
|
# The getter method eventually sees shape too complex
|
|
r = foo.getter
|
|
if r != 777
|
|
raise "error"
|
|
end
|
|
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
|
|
}
|
|
|
|
# BOP redefinition works on Integer#<=
|
|
assert_equal 'false', %q{
|
|
def le(x, y) = x <= y
|
|
|
|
le(2, 2)
|
|
|
|
class Integer
|
|
def <=(_) = false
|
|
end
|
|
|
|
le(2, 2)
|
|
}
|
|
|
|
# BOP redefinition works on Integer#>
|
|
assert_equal 'false', %q{
|
|
def gt(x, y) = x > y
|
|
|
|
gt(3, 2)
|
|
|
|
class Integer
|
|
def >(_) = false
|
|
end
|
|
|
|
gt(3, 2)
|
|
}
|
|
|
|
# BOP redefinition works on Integer#>=
|
|
assert_equal 'false', %q{
|
|
def ge(x, y) = x >= y
|
|
|
|
ge(2, 2)
|
|
|
|
class Integer
|
|
def >=(_) = false
|
|
end
|
|
|
|
ge(2, 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(expand_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)
|
|
]
|
|
}
|
|
|
|
# Test default value block for Hash with opt_aref_with
|
|
assert_equal "false", <<~RUBY, frozen_string_literal: false
|
|
def index_with_string(h)
|
|
h["foo"]
|
|
end
|
|
|
|
h = Hash.new { |h, k| k.frozen? }
|
|
|
|
index_with_string(h)
|
|
index_with_string(h)
|
|
RUBY
|
|
|
|
# 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
|
|
}
|
|
|
|
# splatting an empty array on a specialized method
|
|
assert_equal 'ok', %q{
|
|
def run
|
|
"ok".to_s(*[])
|
|
end
|
|
|
|
run
|
|
run
|
|
}
|
|
|
|
# splatting an single element array on a specialized method
|
|
assert_equal '[1]', %q{
|
|
def run
|
|
[].<<(*[1])
|
|
end
|
|
|
|
run
|
|
run
|
|
}
|
|
|
|
# specialized method with wrong args
|
|
assert_equal 'ok', %q{
|
|
def run(x)
|
|
"bad".to_s(123) if x
|
|
rescue
|
|
:ok
|
|
end
|
|
|
|
run(false)
|
|
run(true)
|
|
}
|
|
|
|
# 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 without block param
|
|
assert_equal '[1, false, 2, false]', %q{
|
|
def bar
|
|
block_given? && yield
|
|
end
|
|
|
|
def foo(&block)
|
|
bar(&block)
|
|
end
|
|
|
|
[foo { 1 }, foo, foo { 2 }, foo]
|
|
}
|
|
|
|
# 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 that invalidation of String#to_s doesn't crash
|
|
assert_equal 'meh', %q{
|
|
def inval_method
|
|
"".to_s
|
|
end
|
|
|
|
inval_method
|
|
|
|
class String
|
|
def to_s
|
|
"meh"
|
|
end
|
|
end
|
|
|
|
inval_method
|
|
}
|
|
|
|
# test that overriding to_s on a String subclass works consistently
|
|
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
|
|
|
|
# Should return '' both times
|
|
test_to_s("")
|
|
test_to_s("")
|
|
|
|
# Can return '' if YJIT optimises String#to_s too aggressively
|
|
test_to_s(OBJ)
|
|
test_to_s(OBJ)
|
|
}
|
|
|
|
# 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 that String unary plus returns the same object ID for an unfrozen string.
|
|
assert_equal 'true', <<~RUBY, frozen_string_literal: false
|
|
def jittable_method
|
|
str = "bar"
|
|
|
|
old_obj_id = str.object_id
|
|
uplus_str = +str
|
|
|
|
uplus_str.object_id == old_obj_id
|
|
end
|
|
jittable_method
|
|
RUBY
|
|
|
|
# Test that String unary plus returns a different unfrozen string when given a frozen string
|
|
assert_equal 'false', %q{
|
|
# Logic needs to be inside an ISEQ, such as a method, for YJIT to compile it
|
|
def jittable_method
|
|
frozen_str = "foo".freeze
|
|
|
|
old_obj_id = frozen_str.object_id
|
|
uplus_str = +frozen_str
|
|
|
|
uplus_str.object_id == old_obj_id || uplus_str.frozen?
|
|
end
|
|
|
|
jittable_method
|
|
}
|
|
|
|
# String-subclass objects should behave as expected inside string-interpolation via concatstrings
|
|
assert_equal 'monkeys / monkeys, yo!', %q{
|
|
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
|
|
|
|
def jittable_method
|
|
m = MyString.new('monkeys')
|
|
"#{m} / #{m.to_s}"
|
|
end
|
|
|
|
jittable_method
|
|
}
|
|
|
|
# String-subclass objects should behave as expected for string equality
|
|
assert_equal 'false', %q{
|
|
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
|
|
|
|
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
|
|
|
|
def jittable_method
|
|
a = "a"
|
|
ma = MyString.new("a")
|
|
|
|
a.object_id != a.to_s.object_id ||
|
|
ma.object_id == ma.to_s.object_id
|
|
end
|
|
jittable_method
|
|
}
|
|
|
|
# Test freeze on string subclass
|
|
assert_equal 'true', %q{
|
|
class MyString < String; end
|
|
|
|
def jittable_method
|
|
fma = MyString.new("a").freeze
|
|
|
|
# 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
|
|
|
|
def jittable_method
|
|
ma = MyString.new("a")
|
|
fma = MyString.new("a").freeze
|
|
|
|
# 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
|
|
}
|
|
|
|
# test getbyte on string class
|
|
assert_equal '[97, :nil, 97, :nil, :raised]', %q{
|
|
def getbyte(s, i)
|
|
byte = begin
|
|
s.getbyte(i)
|
|
rescue TypeError
|
|
:raised
|
|
end
|
|
|
|
byte || :nil
|
|
end
|
|
|
|
getbyte("a", 0)
|
|
getbyte("a", 0)
|
|
|
|
[getbyte("a", 0), getbyte("a", 1), getbyte("a", -1), getbyte("a", -2), getbyte("a", "a")]
|
|
} unless rjit_enabled? # Not yet working on RJIT
|
|
|
|
# Basic test for String#setbyte
|
|
assert_equal 'AoZ', %q{
|
|
s = +"foo"
|
|
s.setbyte(0, 65)
|
|
s.setbyte(-1, 90)
|
|
s
|
|
}
|
|
|
|
# String#setbyte IndexError
|
|
assert_equal 'String#setbyte', %q{
|
|
def ccall = "".setbyte(1, 0)
|
|
begin
|
|
ccall
|
|
rescue => e
|
|
e.backtrace.first.split("'").last
|
|
end
|
|
}
|
|
|
|
# String#setbyte TypeError
|
|
assert_equal 'String#setbyte', %q{
|
|
def ccall = "".setbyte(nil, 0)
|
|
begin
|
|
ccall
|
|
rescue => e
|
|
e.backtrace.first.split("'").last
|
|
end
|
|
}
|
|
|
|
# String#setbyte FrozenError
|
|
assert_equal 'String#setbyte', %q{
|
|
def ccall = "a".freeze.setbyte(0, 0)
|
|
begin
|
|
ccall
|
|
rescue => e
|
|
e.backtrace.first.split("'").last
|
|
end
|
|
}
|
|
|
|
# non-leaf String#setbyte
|
|
assert_equal 'String#setbyte', %q{
|
|
def to_int
|
|
@caller = caller
|
|
0
|
|
end
|
|
|
|
def ccall = "a".dup.setbyte(self, 98)
|
|
ccall
|
|
|
|
@caller.first.split("'").last
|
|
}
|
|
|
|
# non-leaf String#byteslice
|
|
assert_equal 'TypeError', %q{
|
|
def ccall = "".byteslice(nil, nil)
|
|
begin
|
|
ccall
|
|
rescue => e
|
|
e.class
|
|
end
|
|
}
|
|
|
|
# Test << operator on string subclass
|
|
assert_equal 'abab', %q{
|
|
class MyString < String; end
|
|
|
|
def jittable_method
|
|
a = -"a"
|
|
mb = MyString.new("b")
|
|
|
|
buf = String.new
|
|
mbuf = MyString.new
|
|
|
|
buf << a << mb
|
|
mbuf << a << mb
|
|
|
|
buf + mbuf
|
|
end
|
|
jittable_method
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# invokesuper with a block
|
|
assert_equal 'true', %q{
|
|
class A
|
|
def foo = block_given?
|
|
end
|
|
|
|
class B < A
|
|
def foo = super()
|
|
end
|
|
|
|
B.new.foo { }
|
|
B.new.foo { }
|
|
}
|
|
|
|
# invokesuper in a block
|
|
assert_equal '[0, 2]', %q{
|
|
class A
|
|
def foo(x) = x * 2
|
|
end
|
|
|
|
class B < A
|
|
def foo
|
|
2.times.map do |x|
|
|
super(x)
|
|
end
|
|
end
|
|
end
|
|
|
|
B.new.foo
|
|
B.new.foo
|
|
}
|
|
|
|
# invokesuper zsuper in a bmethod
|
|
assert_equal 'ok', %q{
|
|
class Foo
|
|
define_method(:itself) { super }
|
|
end
|
|
begin
|
|
Foo.new.itself
|
|
rescue RuntimeError
|
|
:ok
|
|
end
|
|
}
|
|
|
|
# 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)]
|
|
}
|
|
|
|
# Flonum and Flonum
|
|
assert_equal '[2.0, 0.0, 1.0, 4.0]', %q{
|
|
[1.0 + 1.0, 1.0 - 1.0, 1.0 * 1.0, 8.0 / 2.0]
|
|
}
|
|
|
|
# Flonum and Fixnum
|
|
assert_equal '[2.0, 0.0, 1.0, 4.0]', %q{
|
|
[1.0 + 1, 1.0 - 1, 1.0 * 1, 8.0 / 2]
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# splatkw
|
|
assert_equal '[1, 2]', %q{
|
|
def foo(a:) = [a, yield]
|
|
|
|
def entry(&block)
|
|
a = { a: 1 }
|
|
foo(**a, &block)
|
|
end
|
|
|
|
entry { 2 }
|
|
}
|
|
assert_equal '[1, 2]', %q{
|
|
def foo(a:) = [a, yield]
|
|
|
|
def entry(obj, &block)
|
|
foo(**obj, &block)
|
|
end
|
|
|
|
entry({ a: 3 }) { 2 }
|
|
obj = Object.new
|
|
def obj.to_hash = { a: 1 }
|
|
entry(obj) { 2 }
|
|
}
|
|
|
|
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]', %q{
|
|
class NilClass
|
|
private
|
|
def to_ary
|
|
[1, 2]
|
|
end
|
|
end
|
|
|
|
def expandarray_redefined_nilclass
|
|
a, b = nil
|
|
[a, b]
|
|
end
|
|
|
|
expandarray_redefined_nilclass
|
|
expandarray_redefined_nilclass
|
|
} unless rjit_enabled?
|
|
|
|
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 '[nil, 2, nil]', %q{
|
|
def foo(arr)
|
|
a, b, c = arr
|
|
end
|
|
|
|
a, b, c1 = foo([0, 1])
|
|
a, b, c2 = foo([0, 1, 2])
|
|
a, b, c3 = foo([0, 1])
|
|
[c1, c2, c3]
|
|
}
|
|
|
|
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
|
|
} unless rjit_enabled? # RJIT calls extra Ruby methods
|
|
|
|
# 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
|
|
} unless rjit_enabled? # RJIT calls extra Ruby methods
|
|
|
|
# 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
|
|
} unless rjit_enabled? # RJIT calls extra Ruby methods
|
|
|
|
# test c_call invalidation
|
|
assert_equal '[[:c_call, :itself]]', %q{
|
|
# enable the event once to make sure invalidation
|
|
# happens the second time we enable it
|
|
TracePoint.new(:c_call) {}.enable{}
|
|
|
|
def compiled
|
|
itself
|
|
end
|
|
|
|
# assume first call compiles
|
|
compiled
|
|
|
|
events = []
|
|
tp = TracePoint.new(:c_call) { |tp| events << [tp.event, tp.method_id] }
|
|
tp.enable { compiled }
|
|
|
|
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 '[:ok]', %q{
|
|
def kwargs(value:)
|
|
value
|
|
end
|
|
|
|
5.times.map { kwargs() rescue :ok }.uniq
|
|
}
|
|
|
|
assert_equal '[:ok]', %q{
|
|
def kwargs(a:, b: nil)
|
|
value
|
|
end
|
|
|
|
5.times.map { kwargs(b: 123) rescue :ok }.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
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# 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
|
|
|
|
def bar
|
|
:bar
|
|
end
|
|
|
|
|
|
test
|
|
test
|
|
|
|
RubyVM::YJIT.simulate_oom! if defined?(RubyVM::YJIT)
|
|
|
|
# Old simulat_omm! leaves one byte of space and this fills it up
|
|
bar
|
|
bar
|
|
|
|
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
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# checktype
|
|
assert_equal 'false', %q{
|
|
def function()
|
|
[1, 2] in [Integer, String]
|
|
end
|
|
function()
|
|
}
|
|
|
|
# opt_send_without_block (VM_METHOD_TYPE_ATTRSET)
|
|
assert_equal 'foo', %q{
|
|
class Foo
|
|
attr_writer :foo
|
|
|
|
def foo()
|
|
self.foo = "foo"
|
|
end
|
|
end
|
|
foo = Foo.new
|
|
foo.foo
|
|
}
|
|
|
|
# anytostring, intern
|
|
assert_equal 'true', %q{
|
|
def foo()
|
|
:"#{true}"
|
|
end
|
|
foo()
|
|
}
|
|
|
|
# toregexp, objtostring
|
|
assert_equal '/true/', %q{
|
|
def foo()
|
|
/#{true}/
|
|
end
|
|
foo().inspect
|
|
}
|
|
|
|
# concatstrings, objtostring
|
|
assert_equal '9001', %q{
|
|
def foo()
|
|
"#{9001}"
|
|
end
|
|
foo()
|
|
}
|
|
|
|
# opt_send_without_block (VM_METHOD_TYPE_CFUNC)
|
|
assert_equal 'nil', %q{
|
|
def foo
|
|
nil.inspect # argc: 0
|
|
end
|
|
foo
|
|
}
|
|
assert_equal '4', %q{
|
|
def foo
|
|
2.pow(2) # argc: 1
|
|
end
|
|
foo
|
|
}
|
|
assert_equal 'aba', %q{
|
|
def foo
|
|
"abc".tr("c", "a") # argc: 2
|
|
end
|
|
foo
|
|
}
|
|
assert_equal 'true', %q{
|
|
def foo
|
|
respond_to?(:inspect) # argc: -1
|
|
end
|
|
foo
|
|
}
|
|
assert_equal '["a", "b"]', %q{
|
|
def foo
|
|
"a\nb".lines(chomp: true) # kwargs
|
|
end
|
|
foo
|
|
}
|
|
|
|
# invokebuiltin
|
|
assert_equal '123', %q{
|
|
def foo(obj)
|
|
obj.foo = 123
|
|
end
|
|
|
|
struct = Struct.new(:foo)
|
|
obj = struct.new
|
|
foo(obj)
|
|
}
|
|
|
|
# invokebuiltin_delegate
|
|
assert_equal '.', %q{
|
|
def foo(path)
|
|
Dir.open(path).path
|
|
end
|
|
foo(".")
|
|
}
|
|
|
|
# opt_invokebuiltin_delegate_leave
|
|
assert_equal '[0]', %q{"\x00".unpack("c")}
|
|
|
|
# opt_send_without_block (VM_METHOD_TYPE_ISEQ)
|
|
assert_equal '1', %q{
|
|
def foo = 1
|
|
def bar = foo
|
|
bar
|
|
}
|
|
assert_equal '[1, 2, 3]', %q{
|
|
def foo(a, b) = [1, a, b]
|
|
def bar = foo(2, 3)
|
|
bar
|
|
}
|
|
assert_equal '[1, 2, 3, 4, 5, 6]', %q{
|
|
def foo(a, b, c:, d:, e: 0, f: 6) = [a, b, c, d, e, f]
|
|
def bar = foo(1, 2, c: 3, d: 4, e: 5)
|
|
bar
|
|
}
|
|
assert_equal '[1, 2, 3, 4]', %q{
|
|
def foo(a, b = 2) = [a, b]
|
|
def bar = foo(1) + foo(3, 4)
|
|
bar
|
|
}
|
|
|
|
assert_equal '1', %q{
|
|
def foo(a) = a
|
|
def bar = foo(1) { 2 }
|
|
bar
|
|
}
|
|
assert_equal '[1, 2]', %q{
|
|
def foo(a, &block) = [a, block.call]
|
|
def bar = foo(1) { 2 }
|
|
bar
|
|
}
|
|
|
|
# opt_send_without_block (VM_METHOD_TYPE_IVAR)
|
|
assert_equal 'foo', %q{
|
|
class Foo
|
|
attr_reader :foo
|
|
|
|
def initialize
|
|
@foo = "foo"
|
|
end
|
|
end
|
|
Foo.new.foo
|
|
}
|
|
|
|
# opt_send_without_block (VM_METHOD_TYPE_OPTIMIZED)
|
|
assert_equal 'foo', %q{
|
|
Foo = Struct.new(:bar)
|
|
Foo.new("bar").bar = "foo"
|
|
}
|
|
assert_equal 'foo', %q{
|
|
Foo = Struct.new(:bar)
|
|
Foo.new("foo").bar
|
|
}
|
|
|
|
# getblockparamproxy
|
|
assert_equal 'foo', %q{
|
|
def foo(&block)
|
|
block.call
|
|
end
|
|
foo { "foo" }
|
|
}
|
|
|
|
# getblockparam
|
|
assert_equal 'foo', %q{
|
|
def foo(&block)
|
|
block
|
|
end
|
|
foo { "foo" }.call
|
|
}
|
|
|
|
assert_equal '[1, 2]', %q{
|
|
def foo
|
|
x = [2]
|
|
[1, *x]
|
|
end
|
|
|
|
foo
|
|
foo
|
|
}
|
|
|
|
# respond_to? with changing symbol
|
|
assert_equal 'false', %q{
|
|
def foo(name)
|
|
:sym.respond_to?(name)
|
|
end
|
|
foo(:to_s)
|
|
foo(:to_s)
|
|
foo(:not_exist)
|
|
}
|
|
|
|
# respond_to? with method being defined
|
|
assert_equal 'true', %q{
|
|
def foo
|
|
:sym.respond_to?(:not_yet_defined)
|
|
end
|
|
foo
|
|
foo
|
|
module Kernel
|
|
def not_yet_defined = true
|
|
end
|
|
foo
|
|
}
|
|
|
|
# respond_to? with undef method
|
|
assert_equal 'false', %q{
|
|
module Kernel
|
|
def to_be_removed = true
|
|
end
|
|
def foo
|
|
:sym.respond_to?(:to_be_removed)
|
|
end
|
|
foo
|
|
foo
|
|
class Object
|
|
undef_method :to_be_removed
|
|
end
|
|
foo
|
|
}
|
|
|
|
# respond_to? with respond_to_missing?
|
|
assert_equal 'true', %q{
|
|
class Foo
|
|
end
|
|
def foo(x)
|
|
x.respond_to?(:bar)
|
|
end
|
|
foo(Foo.new)
|
|
foo(Foo.new)
|
|
class Foo
|
|
def respond_to_missing?(*) = true
|
|
end
|
|
foo(Foo.new)
|
|
}
|
|
|
|
# bmethod
|
|
assert_equal '[1, 2, 3]', %q{
|
|
one = 1
|
|
define_method(:foo) do
|
|
one
|
|
end
|
|
|
|
3.times.map { |i| foo + i }
|
|
}
|
|
|
|
# return inside bmethod
|
|
assert_equal 'ok', %q{
|
|
define_method(:foo) do
|
|
1.tap { return :ok }
|
|
end
|
|
|
|
foo
|
|
}
|
|
|
|
# bmethod optional and keywords
|
|
assert_equal '[[1, nil, 2]]', %q{
|
|
define_method(:opt_and_kwargs) do |a = {}, b: nil, c: nil|
|
|
[a, b, c]
|
|
end
|
|
|
|
5.times.map { opt_and_kwargs(1, c: 2) }.uniq
|
|
}
|
|
|
|
# bmethod with forwarded block
|
|
assert_equal '2', %q{
|
|
define_method(:foo) do |&block|
|
|
block.call
|
|
end
|
|
|
|
def bar(&block)
|
|
foo(&block)
|
|
end
|
|
|
|
bar { 1 }
|
|
bar { 2 }
|
|
}
|
|
|
|
# bmethod with forwarded block and arguments
|
|
assert_equal '5', %q{
|
|
define_method(:foo) do |n, &block|
|
|
n + block.call
|
|
end
|
|
|
|
def bar(n, &block)
|
|
foo(n, &block)
|
|
end
|
|
|
|
bar(0) { 1 }
|
|
bar(3) { 2 }
|
|
}
|
|
|
|
# bmethod with forwarded unwanted block
|
|
assert_equal '1', %q{
|
|
one = 1
|
|
define_method(:foo) do
|
|
one
|
|
end
|
|
|
|
def bar(&block)
|
|
foo(&block)
|
|
end
|
|
|
|
bar { }
|
|
bar { }
|
|
}
|
|
|
|
# test for return stub lifetime issue
|
|
assert_equal '1', %q{
|
|
def foo(n)
|
|
if n == 2
|
|
return 1.times { Object.define_method(:foo) {} }
|
|
end
|
|
|
|
foo(n + 1)
|
|
end
|
|
|
|
foo(1)
|
|
}
|
|
|
|
# case-when with redefined ===
|
|
assert_equal 'ok', %q{
|
|
class Symbol
|
|
def ===(a)
|
|
true
|
|
end
|
|
end
|
|
|
|
def cw(arg)
|
|
case arg
|
|
when :b
|
|
:ok
|
|
when 4
|
|
:ng
|
|
end
|
|
end
|
|
|
|
cw(4)
|
|
}
|
|
|
|
assert_equal 'threw', %q{
|
|
def foo(args)
|
|
wrap(*args)
|
|
rescue ArgumentError
|
|
'threw'
|
|
end
|
|
|
|
def wrap(a)
|
|
[a]
|
|
end
|
|
|
|
foo([Hash.ruby2_keywords_hash({})])
|
|
}
|
|
|
|
assert_equal 'threw', %q{
|
|
# C call
|
|
def bar(args)
|
|
Array(*args)
|
|
rescue ArgumentError
|
|
'threw'
|
|
end
|
|
|
|
bar([Hash.ruby2_keywords_hash({})])
|
|
}
|
|
|
|
# Test instance_of? and is_a?
|
|
assert_equal 'true', %q{
|
|
1.instance_of?(Integer) && 1.is_a?(Integer)
|
|
}
|
|
|
|
# Test instance_of? and is_a? for singleton classes
|
|
assert_equal 'true', %q{
|
|
a = []
|
|
def a.test = :test
|
|
a.instance_of?(Array) && a.is_a?(Array)
|
|
}
|
|
|
|
# Test instance_of? for singleton_class
|
|
# Yes this does really return false
|
|
assert_equal 'false', %q{
|
|
a = []
|
|
def a.test = :test
|
|
a.instance_of?(a.singleton_class)
|
|
}
|
|
|
|
# Test is_a? for singleton_class
|
|
assert_equal 'true', %q{
|
|
a = []
|
|
def a.test = :test
|
|
a.is_a?(a.singleton_class)
|
|
}
|
|
|
|
# Test send with splat to a cfunc
|
|
assert_equal 'true', %q{
|
|
1.send(:==, 1, *[])
|
|
}
|
|
|
|
# Test empty splat with cfunc
|
|
assert_equal '2', %q{
|
|
def foo
|
|
Integer.sqrt(4, *[])
|
|
end
|
|
# call twice to deal with constant exiting
|
|
foo
|
|
foo
|
|
}
|
|
|
|
# Test non-empty splat with cfunc
|
|
assert_equal 'Hello World', %q{
|
|
def bar
|
|
args = ["Hello "]
|
|
greeting = +"World"
|
|
greeting.insert(0, *args)
|
|
greeting
|
|
end
|
|
bar
|
|
}
|
|
|
|
# Regression: this creates a temp stack with > 127 elements
|
|
assert_normal_exit %q{
|
|
def foo(a)
|
|
[
|
|
a, a, a, a, a, a, a, a, a, a,
|
|
a, a, a, a, a, a, a, a, a, a,
|
|
a, a, a, a, a, a, a, a, a, a,
|
|
a, a, a, a, a, a, a, a, a, a,
|
|
a, a, a, a, a, a, a, a, a, a,
|
|
a, a, a, a, a, a, a, a, a, a,
|
|
a, a, a, a, a, a, a, a, a, a,
|
|
a, a, a, a, a, a, a, a, a, a,
|
|
a, a, a, a, a, a, a, a, a, a,
|
|
a, a, a, a, a, a, a, a, a, a,
|
|
a, a, a, a, a, a, a, a, a, a,
|
|
a, a, a, a, a, a, a, a, a, a,
|
|
a, a, a, a, a, a, a, a,
|
|
]
|
|
end
|
|
|
|
def entry
|
|
foo(1)
|
|
end
|
|
|
|
entry
|
|
}
|
|
|
|
# Test that splat and rest combined
|
|
# properly dupe the array
|
|
assert_equal "[]", %q{
|
|
def foo(*rest)
|
|
rest << 1
|
|
end
|
|
|
|
def test(splat)
|
|
foo(*splat)
|
|
end
|
|
|
|
EMPTY = []
|
|
custom = Object.new
|
|
def custom.to_a
|
|
EMPTY
|
|
end
|
|
|
|
test(custom)
|
|
test(custom)
|
|
EMPTY
|
|
}
|
|
|
|
# Rest with send
|
|
assert_equal '[1, 2, 3]', %q{
|
|
def bar(x, *rest)
|
|
rest.insert(0, x)
|
|
end
|
|
send(:bar, 1, 2, 3)
|
|
}
|
|
|
|
# Fix splat block arg bad compilation
|
|
assert_equal "foo", %q{
|
|
def literal(*args, &block)
|
|
s = ''.dup
|
|
literal_append(s, *args, &block)
|
|
s
|
|
end
|
|
|
|
def literal_append(sql, v)
|
|
sql << v
|
|
end
|
|
|
|
literal("foo")
|
|
}
|
|
|
|
# regression test for accidentally having a parameter truncated
|
|
# due to Rust/C signature mismatch. Used to crash with
|
|
# > [BUG] rb_vm_insn_addr2insn: invalid insn address ...
|
|
# or
|
|
# > ... `Err` value: TryFromIntError(())'
|
|
assert_normal_exit %q{
|
|
n = 16384
|
|
eval(
|
|
"def foo(arg); " + "_=arg;" * n + '_=1;' + "Object; end"
|
|
)
|
|
foo 1
|
|
}
|
|
|
|
# Regression test for CantCompile not using starting_ctx
|
|
assert_normal_exit %q{
|
|
class Integer
|
|
def ===(other)
|
|
false
|
|
end
|
|
end
|
|
|
|
def my_func(x)
|
|
case x
|
|
when 1
|
|
1
|
|
when 2
|
|
2
|
|
else
|
|
3
|
|
end
|
|
end
|
|
|
|
my_func(1)
|
|
}
|
|
|
|
# Regression test for CantCompile not using starting_ctx
|
|
assert_equal "ArgumentError", %q{
|
|
def literal(*args, &block)
|
|
s = ''.dup
|
|
args = [1, 2, 3]
|
|
literal_append(s, *args, &block)
|
|
s
|
|
end
|
|
|
|
def literal_append(sql, v)
|
|
[sql.inspect, v.inspect]
|
|
end
|
|
|
|
begin
|
|
literal("foo")
|
|
rescue ArgumentError
|
|
"ArgumentError"
|
|
end
|
|
}
|
|
|
|
# Rest with block
|
|
# Simplified code from railsbench
|
|
assert_equal '[{"/a"=>"b", :as=>:c, :via=>:post}, [], nil]', %q{
|
|
def match(path, *rest, &block)
|
|
[path, rest, block]
|
|
end
|
|
|
|
def map_method(method, args, &block)
|
|
options = args.last
|
|
args.pop
|
|
options[:via] = method
|
|
match(*args, options, &block)
|
|
end
|
|
|
|
def post(*args, &block)
|
|
map_method(:post, args, &block)
|
|
end
|
|
|
|
post "/a" => "b", as: :c
|
|
}
|
|
|
|
# Test rest and kw_args
|
|
assert_equal '[true, true, true, true]', %q{
|
|
def my_func(*args, base: nil, sort: true)
|
|
[args, base, sort]
|
|
end
|
|
|
|
def calling_my_func
|
|
results = []
|
|
results << (my_func("test") == [["test"], nil, true])
|
|
results << (my_func("test", base: :base) == [["test"], :base, true])
|
|
results << (my_func("test", sort: false) == [["test"], nil, false])
|
|
results << (my_func("test", "other", base: :base) == [["test", "other"], :base, true])
|
|
results
|
|
end
|
|
calling_my_func
|
|
}
|
|
|
|
# Test Integer#[] with 2 args
|
|
assert_equal '0', %q{
|
|
3[0, 0]
|
|
}
|
|
|
|
# unspecified_bits + checkkeyword
|
|
assert_equal '2', %q{
|
|
def callee = 1
|
|
|
|
# checkkeyword should see unspecified_bits=0 (use bar), not Integer 1 (set bar = foo).
|
|
def foo(foo, bar: foo) = bar
|
|
|
|
def entry(&block)
|
|
# write 1 at stack[3]. Calling #callee spills stack[3].
|
|
1 + (1 + (1 + (1 + callee)))
|
|
# &block is written to a register instead of stack[3]. When &block is popped and
|
|
# unspecified_bits is pushed, it must be written to stack[3], not to a register.
|
|
foo(1, bar: 2, &block)
|
|
end
|
|
|
|
entry # call branch_stub_hit (spill temps)
|
|
entry # doesn't call branch_stub_hit (not spill temps)
|
|
}
|
|
|
|
# Test rest and optional_params
|
|
assert_equal '[true, true, true, true]', %q{
|
|
def my_func(stuff, base=nil, sort=true, *args)
|
|
[stuff, base, sort, args]
|
|
end
|
|
|
|
def calling_my_func
|
|
results = []
|
|
results << (my_func("test") == ["test", nil, true, []])
|
|
results << (my_func("test", :base) == ["test", :base, true, []])
|
|
results << (my_func("test", :base, false) == ["test", :base, false, []])
|
|
results << (my_func("test", :base, false, "other", "other") == ["test", :base, false, ["other", "other"]])
|
|
results
|
|
end
|
|
calling_my_func
|
|
}
|
|
|
|
# Test rest and optional_params and splat
|
|
assert_equal '[true, true, true, true, true]', %q{
|
|
def my_func(stuff, base=nil, sort=true, *args)
|
|
[stuff, base, sort, args]
|
|
end
|
|
|
|
def calling_my_func
|
|
results = []
|
|
splat = ["test"]
|
|
results << (my_func(*splat) == ["test", nil, true, []])
|
|
splat = [:base]
|
|
results << (my_func("test", *splat) == ["test", :base, true, []])
|
|
splat = [:base, false]
|
|
results << (my_func("test", *splat) == ["test", :base, false, []])
|
|
splat = [:base, false, "other", "other"]
|
|
results << (my_func("test", *splat) == ["test", :base, false, ["other", "other"]])
|
|
splat = ["test", :base, false, "other", "other"]
|
|
results << (my_func(*splat) == ["test", :base, false, ["other", "other"]])
|
|
results
|
|
end
|
|
calling_my_func
|
|
}
|
|
|
|
# Regression test: rest and optional and splat
|
|
assert_equal 'true', %q{
|
|
def my_func(base=nil, *args)
|
|
[base, args]
|
|
end
|
|
|
|
def calling_my_func
|
|
array = []
|
|
my_func(:base, :rest1, *array) == [:base, [:rest1]]
|
|
end
|
|
|
|
calling_my_func
|
|
}
|
|
|
|
# Fix failed case for large splat
|
|
assert_equal 'true', %q{
|
|
def d(a, b=:b)
|
|
end
|
|
|
|
def calling_func
|
|
ary = 1380888.times;
|
|
d(*ary)
|
|
end
|
|
begin
|
|
calling_func
|
|
rescue ArgumentError
|
|
true
|
|
end
|
|
} unless rjit_enabled? # Not yet working on RJIT
|
|
|
|
# Regression test: register allocator on expandarray
|
|
assert_equal '[]', %q{
|
|
func = proc { [] }
|
|
proc do
|
|
_x, _y = func.call
|
|
end.call
|
|
}
|
|
|
|
# Catch TAG_BREAK in a non-FINISH frame with JIT code
|
|
assert_equal '1', %q{
|
|
def entry
|
|
catch_break
|
|
end
|
|
|
|
def catch_break
|
|
while_true do
|
|
break
|
|
end
|
|
1
|
|
end
|
|
|
|
def while_true
|
|
while true
|
|
yield
|
|
end
|
|
end
|
|
|
|
entry
|
|
}
|
|
|
|
assert_equal '6', %q{
|
|
class Base
|
|
def number = 1 + yield
|
|
end
|
|
|
|
class Sub < Base
|
|
def number = super + 2
|
|
end
|
|
|
|
Sub.new.number { 3 }
|
|
}
|
|
|
|
# Integer multiplication and overflow
|
|
assert_equal '[6, -6, 9671406556917033397649408, -9671406556917033397649408, 21267647932558653966460912964485513216]', %q{
|
|
def foo(a, b)
|
|
a * b
|
|
end
|
|
|
|
r1 = foo(2, 3)
|
|
r2 = foo(2, -3)
|
|
r3 = foo(2 << 40, 2 << 41)
|
|
r4 = foo(2 << 40, -2 << 41)
|
|
r5 = foo(1 << 62, 1 << 62)
|
|
|
|
[r1, r2, r3, r4, r5]
|
|
}
|
|
|
|
# Integer multiplication and overflow (minimized regression test from test-basic)
|
|
assert_equal '8515157028618240000', %q{2128789257154560000 * 4}
|
|
|
|
# Inlined method calls
|
|
assert_equal 'nil', %q{
|
|
def putnil = nil
|
|
def entry = putnil
|
|
entry.inspect
|
|
}
|
|
assert_equal '1', %q{
|
|
def putobject_1 = 1
|
|
def entry = putobject_1
|
|
entry
|
|
}
|
|
assert_equal 'false', %q{
|
|
def putobject(_unused_arg1) = false
|
|
def entry = putobject(nil)
|
|
entry
|
|
}
|
|
assert_equal 'true', %q{
|
|
def entry = yield
|
|
entry { true }
|
|
}
|
|
assert_equal 'sym', %q{
|
|
def entry = :sym.to_sym
|
|
entry
|
|
}
|
|
|
|
assert_normal_exit %q{
|
|
ivars = 1024.times.map { |i| "@iv_#{i} = #{i}\n" }.join
|
|
Foo = Class.new
|
|
Foo.class_eval "def initialize() #{ivars} end"
|
|
Foo.new
|
|
}
|
|
|
|
assert_equal '0', %q{
|
|
def spill
|
|
1.to_i # not inlined
|
|
end
|
|
|
|
def inline(_stack1, _stack2, _stack3, _stack4, _stack5)
|
|
0 # inlined
|
|
end
|
|
|
|
def entry
|
|
# RegTemps is 00111110 prior to the #inline call.
|
|
# Its return value goes to stack_idx=0, which conflicts with stack_idx=5.
|
|
inline(spill, 2, 3, 4, 5)
|
|
end
|
|
|
|
entry
|
|
}
|
|
|
|
# Integer succ and overflow
|
|
assert_equal '[2, 4611686018427387904]', %q{
|
|
[1.succ, 4611686018427387903.succ]
|
|
}
|
|
|
|
# Integer right shift
|
|
assert_equal '[0, 1, -4]', %q{
|
|
[0 >> 1, 2 >> 1, -7 >> 1]
|
|
}
|
|
|
|
# Integer XOR
|
|
assert_equal '[0, 0, 4]', %q{
|
|
[0 ^ 0, 1 ^ 1, 7 ^ 3]
|
|
}
|
|
|
|
assert_equal '[nil, "yield"]', %q{
|
|
def defined_yield = defined?(yield)
|
|
[defined_yield, defined_yield {}]
|
|
}
|
|
|
|
# splat with ruby2_keywords into rest parameter
|
|
assert_equal '[[{:a=>1}], {}]', %q{
|
|
ruby2_keywords def foo(*args) = args
|
|
|
|
def bar(*args, **kw) = [args, kw]
|
|
|
|
def pass_bar(*args) = bar(*args)
|
|
|
|
def body
|
|
args = foo(a: 1)
|
|
pass_bar(*args)
|
|
end
|
|
|
|
body
|
|
}
|
|
|
|
# concatarray
|
|
assert_equal '[1, 2]', %q{
|
|
def foo(a, b) = [a, b]
|
|
arr = [2]
|
|
foo(*[1], *arr)
|
|
}
|
|
|
|
# pushtoarray
|
|
assert_equal '[1, 2]', %q{
|
|
def foo(a, b) = [a, b]
|
|
arr = [1]
|
|
foo(*arr, 2)
|
|
}
|
|
|
|
# pop before fallback
|
|
assert_normal_exit %q{
|
|
class Foo
|
|
attr_reader :foo
|
|
|
|
def try = foo(0, &nil)
|
|
end
|
|
|
|
Foo.new.try
|
|
}
|
|
|
|
# a kwrest case
|
|
assert_equal '[1, 2, {:complete=>false}]', %q{
|
|
def rest(foo: 1, bar: 2, **kwrest)
|
|
[foo, bar, kwrest]
|
|
end
|
|
|
|
def callsite = rest(complete: false)
|
|
|
|
callsite
|
|
}
|
|
|
|
# splat+kw_splat+opt+rest
|
|
assert_equal '[1, []]', %q{
|
|
def opt_rest(a = 0, *rest) = [a, rest]
|
|
|
|
def call_site(args) = opt_rest(*args, **nil)
|
|
|
|
call_site([1])
|
|
}
|
|
|
|
# splat and nil kw_splat
|
|
assert_equal 'ok', %q{
|
|
def identity(x) = x
|
|
|
|
def splat_nil_kw_splat(args) = identity(*args, **nil)
|
|
|
|
splat_nil_kw_splat([:ok])
|
|
}
|
|
|
|
# empty splat and kwsplat into leaf builtins
|
|
assert_equal '[1, 1, 1]', %q{
|
|
empty = []
|
|
[1.abs(*empty), 1.abs(**nil), 1.bit_length(*empty, **nil)]
|
|
}
|
|
|
|
# splat into C methods with -1 arity
|
|
assert_equal '[[1, 2, 3], [0, 2, 3], [1, 2, 3], [2, 2, 3], [], [], [{}]]', %q{
|
|
class Foo < Array
|
|
def push(args) = super(1, *args)
|
|
end
|
|
|
|
def test_cfunc_vargs_splat(sub_instance, array_class, empty_kw_hash)
|
|
splat = [2, 3]
|
|
kw_splat = [empty_kw_hash]
|
|
[
|
|
sub_instance.push(splat),
|
|
array_class[0, *splat, **nil],
|
|
array_class[1, *splat, &nil],
|
|
array_class[2, *splat, **nil, &nil],
|
|
array_class.send(:[], *kw_splat),
|
|
# kw_splat disables keywords hash handling
|
|
array_class[*kw_splat],
|
|
array_class[*kw_splat, **nil],
|
|
]
|
|
end
|
|
|
|
test_cfunc_vargs_splat(Foo.new, Array, Hash.ruby2_keywords_hash({}))
|
|
}
|
|
|
|
# Class#new (arity=-1), splat, and ruby2_keywords
|
|
assert_equal '[0, {1=>1}]', %q{
|
|
class KwInit
|
|
attr_reader :init_args
|
|
def initialize(x = 0, **kw)
|
|
@init_args = [x, kw]
|
|
end
|
|
end
|
|
|
|
def test(klass, args)
|
|
klass.new(*args).init_args
|
|
end
|
|
|
|
test(KwInit, [Hash.ruby2_keywords_hash({1 => 1})])
|
|
}
|
|
|
|
# Chilled string setivar trigger warning
|
|
assert_equal 'literal string will be frozen in the future', %q{
|
|
Warning[:deprecated] = true
|
|
$VERBOSE = true
|
|
$warning = "no-warning"
|
|
module ::Warning
|
|
def self.warn(message)
|
|
$warning = message.split("warning: ").last.strip
|
|
end
|
|
end
|
|
|
|
class String
|
|
def setivar!
|
|
@ivar = 42
|
|
end
|
|
end
|
|
|
|
def setivar!(str)
|
|
str.setivar!
|
|
end
|
|
|
|
10.times { setivar!("mutable".dup) }
|
|
10.times do
|
|
setivar!("frozen".freeze)
|
|
rescue FrozenError
|
|
end
|
|
|
|
setivar!("chilled") # Emit warning
|
|
$warning
|
|
}
|
|
|
|
# arity=-2 cfuncs
|
|
assert_equal '["", "1/2", [0, [:ok, 1]]]', %q{
|
|
def test_cases(file, chain)
|
|
new_chain = chain.allocate # to call initialize directly
|
|
new_chain.send(:initialize, [0], ok: 1)
|
|
|
|
[
|
|
file.join,
|
|
file.join("1", "2"),
|
|
new_chain.to_a,
|
|
]
|
|
end
|
|
|
|
test_cases(File, Enumerator::Chain)
|
|
}
|
|
|
|
# singleton class should invalidate Type::CString assumption
|
|
assert_equal 'foo', %q{
|
|
def define_singleton(str, define)
|
|
if define
|
|
# Wrap a C method frame to avoid exiting JIT code on defineclass
|
|
[nil].reverse_each do
|
|
class << str
|
|
def +(_)
|
|
"foo"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
"bar"
|
|
end
|
|
|
|
def entry(define)
|
|
str = ""
|
|
# When `define` is false, #+ compiles to rb_str_plus() without a class guard.
|
|
# When the code is reused with `define` is true, the class of `str` is changed
|
|
# to a singleton class, so the block should be invalidated.
|
|
str + define_singleton(str, define)
|
|
end
|
|
|
|
entry(false)
|
|
entry(true)
|
|
}
|
|
|
|
assert_equal '[:ok, :ok, :ok]', %q{
|
|
def identity(x) = x
|
|
def foo(x, _) = x
|
|
def bar(_, _, _, _, x) = x
|
|
|
|
def tests
|
|
[
|
|
identity(:ok),
|
|
foo(:ok, 2),
|
|
bar(1, 2, 3, 4, :ok),
|
|
]
|
|
end
|
|
|
|
tests
|
|
}
|
|
|
|
# test integer left shift with constant rhs
|
|
assert_equal [0x80000000000, 'a+', :ok].inspect, %q{
|
|
def shift(val) = val << 43
|
|
|
|
def tests
|
|
int = shift(1)
|
|
str = shift("a")
|
|
|
|
Integer.define_method(:<<) { |_| :ok }
|
|
redef = shift(1)
|
|
|
|
[int, str, redef]
|
|
end
|
|
|
|
tests
|
|
}
|