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