diff options
-rw-r--r-- | bootstraptest/test_yjit.rb | 16 | ||||
-rw-r--r-- | test/ruby/test_call.rb | 883 | ||||
-rw-r--r-- | vm.c | 16 | ||||
-rw-r--r-- | vm_core.h | 7 | ||||
-rw-r--r-- | vm_eval.c | 46 | ||||
-rw-r--r-- | vm_insnhelper.c | 458 | ||||
-rw-r--r-- | yjit/src/codegen.rs | 6 | ||||
-rw-r--r-- | yjit/src/stats.rs | 1 |
8 files changed, 1221 insertions, 212 deletions
diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index 69e93cabee..a6525e7af9 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -3947,3 +3947,19 @@ assert_equal 'true', %q{ calling_my_func } + +# Fix failed case for large splat +assert_equal 'true', %q{ + def d(a, b=:b) + end + + def calling_func + ary = 1380888.times; + d(*ary) + end + begin + calling_func + rescue ArgumentError + true + end +} unless defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? # Not yet working on RJIT diff --git a/test/ruby/test_call.rb b/test/ruby/test_call.rb index dbdb0752be..4d5a387f42 100644 --- a/test/ruby/test_call.rb +++ b/test/ruby/test_call.rb @@ -1,5 +1,6 @@ # frozen_string_literal: false require 'test/unit' +require '-test-/iter' class TestCall < Test::Unit::TestCase def aaa(a, b=100, *rest) @@ -116,8 +117,11 @@ class TestCall < Test::Unit::TestCase assert_equal([0, 1, 2, b], aaa(0, *ary, &ary.pop), bug16504) end + OVER_STACK_LEN = (ENV['RUBY_OVER_STACK_LEN'] || 150).to_i # Greater than VM_ARGC_STACK_MAX + OVER_STACK_ARGV = OVER_STACK_LEN.times.to_a.freeze + def test_call_cfunc_splat_large_array_bug_4040 - a = 1380.times.to_a # Greater than VM_ARGC_STACK_MAX + a = OVER_STACK_ARGV assert_equal(a, [].push(*a)) assert_equal(a, [].push(a[0], *a[1..])) @@ -199,4 +203,881 @@ class TestCall < Test::Unit::TestCase # Not all tests use such a large array to reduce testing time. assert_equal(1380888, [].push(*1380888.times.to_a).size) end + + def test_call_iseq_large_array_splat_fail + def self.a; end + def self.b(a=1); end + def self.c(k: 1); end + def self.d(**kw); end + def self.e(k: 1, **kw); end + def self.f(a=1, k: 1); end + def self.g(a=1, **kw); end + def self.h(a=1, k: 1, **kw); end + + (:a..:h).each do |meth| + assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do + instance_eval("#{meth}(*OVER_STACK_ARGV)", __FILE__, __LINE__) + end + end + end + + def test_call_iseq_large_array_splat_pass + def self.a(*a); a.length end + assert_equal OVER_STACK_LEN, a(*OVER_STACK_ARGV) + + def self.b(_, *a); a.length end + assert_equal (OVER_STACK_LEN - 1), b(*OVER_STACK_ARGV) + + def self.c(_, *a, _); a.length end + assert_equal (OVER_STACK_LEN - 2), c(*OVER_STACK_ARGV) + + def self.d(b=1, *a); a.length end + assert_equal (OVER_STACK_LEN - 1), d(*OVER_STACK_ARGV) + + def self.e(b=1, *a, _); a.length end + assert_equal (OVER_STACK_LEN - 2), e(*OVER_STACK_ARGV) + + def self.f(b, *a); a.length end + assert_equal (OVER_STACK_LEN - 1), f(*OVER_STACK_ARGV) + + def self.g(*a, k: 1); a.length end + assert_equal OVER_STACK_LEN, g(*OVER_STACK_ARGV) + + def self.h(*a, **kw); a.length end + assert_equal OVER_STACK_LEN, h(*OVER_STACK_ARGV) + + def self.i(*a, k: 1, **kw); a.length end + assert_equal OVER_STACK_LEN, i(*OVER_STACK_ARGV) + + def self.j(b=1, *a, k: 1); a.length end + assert_equal (OVER_STACK_LEN - 1), j(*OVER_STACK_ARGV) + + def self.k(b=1, *a, **kw); a.length end + assert_equal (OVER_STACK_LEN - 1), k(*OVER_STACK_ARGV) + + def self.l(b=1, *a, k: 1, **kw); a.length end + assert_equal (OVER_STACK_LEN - 1), l(*OVER_STACK_ARGV) + + def self.m(b=1, *a, _, k: 1); a.length end + assert_equal (OVER_STACK_LEN - 2), m(*OVER_STACK_ARGV) + + def self.n(b=1, *a, _, **kw); a.length end + assert_equal (OVER_STACK_LEN - 2), n(*OVER_STACK_ARGV) + + def self.o(b=1, *a, _, k: 1, **kw); a.length end + assert_equal (OVER_STACK_LEN - 2), o(*OVER_STACK_ARGV) + end + + def test_call_iseq_large_array_splat_with_large_number_of_parameters + args = OVER_STACK_ARGV.map{|i| "a#{i}"}.join(',') + args1 = (OVER_STACK_LEN-1).times.map{|i| "a#{i}"}.join(',') + + singleton_class.class_eval("def a(#{args}); [#{args}] end") + assert_equal OVER_STACK_ARGV, a(*OVER_STACK_ARGV) + + singleton_class.class_eval("def b(#{args}, b=0); [#{args}, b] end") + assert_equal(OVER_STACK_ARGV + [0], b(*OVER_STACK_ARGV)) + + singleton_class.class_eval("def c(#{args}, *b); [#{args}, b] end") + assert_equal(OVER_STACK_ARGV + [[]], c(*OVER_STACK_ARGV)) + + singleton_class.class_eval("def d(#{args1}, *b); [#{args1}, b] end") + assert_equal(OVER_STACK_ARGV[0...-1] + [[OVER_STACK_ARGV.last]], d(*OVER_STACK_ARGV)) + end if OVER_STACK_LEN < 200 + + def test_call_proc_large_array_splat_pass + [ + proc{0} , + proc{|a=1|a}, + proc{|k: 1|0}, + proc{|**kw| 0}, + proc{|k: 1, **kw| 0}, + proc{|a=1, k: 1| a}, + proc{|a=1, **kw| a}, + proc{|a=1, k: 1, **kw| a}, + ].each do |l| + assert_equal 0, l.call(*OVER_STACK_ARGV) + end + + assert_equal OVER_STACK_LEN, proc{|*a| a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 1), proc{|_, *a| a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 2), proc{|_, *a, _| a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 1), proc{|b=1, *a| a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 2), proc{|b=1, *a, _| a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 1), proc{|b=1, *a| a.length}.(*OVER_STACK_ARGV) + assert_equal OVER_STACK_LEN, proc{|*a, k: 1| a.length}.(*OVER_STACK_ARGV) + assert_equal OVER_STACK_LEN, proc{|*a, **kw| a.length}.(*OVER_STACK_ARGV) + assert_equal OVER_STACK_LEN, proc{|*a, k: 1, **kw| a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 1), proc{|b=1, *a, k: 1| a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 1), proc{|b=1, *a, **kw| a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 1), proc{|b=1, *a, k: 1, **kw| a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 2), proc{|b=1, *a, _, k: 1| a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 2), proc{|b=1, *a, _, **kw| a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 2), proc{|b=1, *a, _, k: 1, **kw| a.length}.(*OVER_STACK_ARGV) + end + + def test_call_proc_large_array_splat_with_large_number_of_parameters + args = OVER_STACK_ARGV.map{|i| "a#{i}"}.join(',') + args1 = (OVER_STACK_LEN-1).times.map{|i| "a#{i}"}.join(',') + + l = instance_eval("proc{|#{args}| [#{args}]}") + assert_equal OVER_STACK_ARGV, l.(*OVER_STACK_ARGV) + + l = instance_eval("proc{|#{args}, b| [#{args}, b]}") + assert_equal(OVER_STACK_ARGV + [nil], l.(*OVER_STACK_ARGV)) + + l = instance_eval("proc{|#{args1}| [#{args1}]}") + assert_equal(OVER_STACK_ARGV[0...-1], l.(*OVER_STACK_ARGV)) + + l = instance_eval("proc{|#{args}, *b| [#{args}, b]}") + assert_equal(OVER_STACK_ARGV + [[]], l.(*OVER_STACK_ARGV)) + + l = instance_eval("proc{|#{args1}, *b| [#{args1}, b]}") + assert_equal(OVER_STACK_ARGV[0...-1] + [[OVER_STACK_ARGV.last]], l.(*OVER_STACK_ARGV)) + + l = instance_eval("proc{|#{args}, b, *c| [#{args}, b, c]}") + assert_equal(OVER_STACK_ARGV + [nil, []], l.(*OVER_STACK_ARGV)) + + l = instance_eval("proc{|#{args}, b, *c, d| [#{args}, b, c, d]}") + assert_equal(OVER_STACK_ARGV + [nil, [], nil], l.(*OVER_STACK_ARGV)) + end if OVER_STACK_LEN < 200 + + def test_call_lambda_large_array_splat_fail + [ + ->{} , + ->(a=1){}, + ->(k: 1){}, + ->(**kw){}, + ->(k: 1, **kw){}, + ->(a=1, k: 1){}, + ->(a=1, **kw){}, + ->(a=1, k: 1, **kw){}, + ].each do |l| + assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do + l.call(*OVER_STACK_ARGV) + end + end + end + + def test_call_lambda_large_array_splat_pass + assert_equal OVER_STACK_LEN, ->(*a){a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 1), ->(_, *a){a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 2), ->(_, *a, _){a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 1), ->(b=1, *a){a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 2), ->(b=1, *a, _){a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 1), ->(b, *a){a.length}.(*OVER_STACK_ARGV) + assert_equal OVER_STACK_LEN, ->(*a, k: 1){a.length}.(*OVER_STACK_ARGV) + assert_equal OVER_STACK_LEN, ->(*a, **kw){a.length}.(*OVER_STACK_ARGV) + assert_equal OVER_STACK_LEN, ->(*a, k: 1, **kw){a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 1), ->(b=1, *a, k: 1){a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 1), ->(b=1, *a, **kw){a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 1), ->(b=1, *a, k: 1, **kw){a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 2), ->(b=1, *a, _, k: 1){a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 2), ->(b=1, *a, _, **kw){a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 2), ->(b=1, *a, _, k: 1, **kw){a.length}.(*OVER_STACK_ARGV) + end + + def test_call_yield_block_large_array_splat_pass + def self.a + yield(*OVER_STACK_ARGV) + end + + [ + proc{0} , + proc{|a=1|a}, + proc{|k: 1|0}, + proc{|**kw| 0}, + proc{|k: 1, **kw| 0}, + proc{|a=1, k: 1| a}, + proc{|a=1, **kw| a}, + proc{|a=1, k: 1, **kw| a}, + ].each do |l| + assert_equal 0, a(&l) + end + + assert_equal OVER_STACK_LEN, a{|*a| a.length} + assert_equal (OVER_STACK_LEN - 1), a{|_, *a| a.length} + assert_equal (OVER_STACK_LEN - 2), a{|_, *a, _| a.length} + assert_equal (OVER_STACK_LEN - 1), a{|b=1, *a| a.length} + assert_equal (OVER_STACK_LEN - 2), a{|b=1, *a, _| a.length} + assert_equal (OVER_STACK_LEN - 1), a{|b, *a| a.length} + assert_equal OVER_STACK_LEN, a{|*a, k: 1| a.length} + assert_equal OVER_STACK_LEN, a{|*a, **kw| a.length} + assert_equal OVER_STACK_LEN, a{|*a, k: 1, **kw| a.length} + assert_equal (OVER_STACK_LEN - 1), a{|b=1, *a, k: 1| a.length} + assert_equal (OVER_STACK_LEN - 1), a{|b=1, *a, **kw| a.length} + assert_equal (OVER_STACK_LEN - 1), a{|b=1, *a, k: 1, **kw| a.length} + assert_equal (OVER_STACK_LEN - 2), a{|b=1, *a, _, k: 1| a.length} + assert_equal (OVER_STACK_LEN - 2), a{|b=1, *a, _, **kw| a.length} + assert_equal (OVER_STACK_LEN - 2), a{|b=1, *a, _, k: 1, **kw| a.length} + end + + def test_call_yield_large_array_splat_with_large_number_of_parameters + def self.a + yield(*OVER_STACK_ARGV) + end + + args = OVER_STACK_ARGV.map{|i| "a#{i}"}.join(',') + args1 = (OVER_STACK_LEN-1).times.map{|i| "a#{i}"}.join(',') + + assert_equal OVER_STACK_ARGV, instance_eval("a{|#{args}| [#{args}]}", __FILE__, __LINE__) + assert_equal(OVER_STACK_ARGV + [nil], instance_eval("a{|#{args}, b| [#{args}, b]}", __FILE__, __LINE__)) + assert_equal(OVER_STACK_ARGV[0...-1], instance_eval("a{|#{args1}| [#{args1}]}", __FILE__, __LINE__)) + assert_equal(OVER_STACK_ARGV + [[]], instance_eval("a{|#{args}, *b| [#{args}, b]}", __FILE__, __LINE__)) + assert_equal(OVER_STACK_ARGV[0...-1] + [[OVER_STACK_ARGV.last]], instance_eval("a{|#{args1}, *b| [#{args1}, b]}", __FILE__, __LINE__)) + assert_equal(OVER_STACK_ARGV + [nil, []], instance_eval("a{|#{args}, b, *c| [#{args}, b, c]}", __FILE__, __LINE__)) + assert_equal(OVER_STACK_ARGV + [nil, [], nil], instance_eval("a{|#{args}, b, *c, d| [#{args}, b, c, d]}", __FILE__, __LINE__)) + end if OVER_STACK_LEN < 200 + + def test_call_yield_lambda_large_array_splat_fail + def self.a + yield(*OVER_STACK_ARGV) + end + [ + ->{} , + ->(a=1){}, + ->(k: 1){}, + ->(**kw){}, + ->(k: 1, **kw){}, + ->(a=1, k: 1){}, + ->(a=1, **kw){}, + ->(a=1, k: 1, **kw){}, + ].each do |l| + assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do + a(&l) + end + end + end + + def test_call_yield_lambda_large_array_splat_pass + def self.a + yield(*OVER_STACK_ARGV) + end + + assert_equal OVER_STACK_LEN, a(&->(*a){a.length}) + assert_equal (OVER_STACK_LEN - 1), a(&->(_, *a){a.length}) + assert_equal (OVER_STACK_LEN - 2), a(&->(_, *a, _){a.length}) + assert_equal (OVER_STACK_LEN - 1), a(&->(b=1, *a){a.length}) + assert_equal (OVER_STACK_LEN - 2), a(&->(b=1, *a, _){a.length}) + assert_equal (OVER_STACK_LEN - 1), a(&->(b, *a){a.length}) + assert_equal OVER_STACK_LEN, a(&->(*a, k: 1){a.length}) + assert_equal OVER_STACK_LEN, a(&->(*a, **kw){a.length}) + assert_equal OVER_STACK_LEN, a(&->(*a, k: 1, **kw){a.length}) + assert_equal (OVER_STACK_LEN - 1), a(&->(b=1, *a, k: 1){a.length}) + assert_equal (OVER_STACK_LEN - 1), a(&->(b=1, *a, **kw){a.length}) + assert_equal (OVER_STACK_LEN - 1), a(&->(b=1, *a, k: 1, **kw){a.length}) + assert_equal (OVER_STACK_LEN - 2), a(&->(b=1, *a, _, k: 1){a.length}) + assert_equal (OVER_STACK_LEN - 2), a(&->(b=1, *a, _, **kw){a.length}) + assert_equal (OVER_STACK_LEN - 2), a(&->(b=1, *a, _, k: 1, **kw){a.length}) + end + + def test_call_send_iseq_large_array_splat_fail + def self.a; end + def self.b(a=1); end + def self.c(k: 1); end + def self.d(**kw); end + def self.e(k: 1, **kw); end + def self.f(a=1, k: 1); end + def self.g(a=1, **kw); end + def self.h(a=1, k: 1, **kw); end + + (:a..:h).each do |meth| + assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do + send(meth, *OVER_STACK_ARGV) + end + end + end + + def test_call_send_iseq_large_array_splat_pass + def self.a(*a); a.length end + assert_equal OVER_STACK_LEN, send(:a, *OVER_STACK_ARGV) + + def self.b(_, *a); a.length end + assert_equal (OVER_STACK_LEN - 1), send(:b, *OVER_STACK_ARGV) + + def self.c(_, *a, _); a.length end + assert_equal (OVER_STACK_LEN - 2), send(:c, *OVER_STACK_ARGV) + + def self.d(b=1, *a); a.length end + assert_equal (OVER_STACK_LEN - 1), send(:d, *OVER_STACK_ARGV) + + def self.e(b=1, *a, _); a.length end + assert_equal (OVER_STACK_LEN - 2), send(:e, *OVER_STACK_ARGV) + + def self.f(b, *a); a.length end + assert_equal (OVER_STACK_LEN - 1), send(:f, *OVER_STACK_ARGV) + + def self.g(*a, k: 1); a.length end + assert_equal OVER_STACK_LEN, send(:g, *OVER_STACK_ARGV) + + def self.h(*a, **kw); a.length end + assert_equal OVER_STACK_LEN, send(:h, *OVER_STACK_ARGV) + + def self.i(*a, k: 1, **kw); a.length end + assert_equal OVER_STACK_LEN, send(:i, *OVER_STACK_ARGV) + + def self.j(b=1, *a, k: 1); a.length end + assert_equal (OVER_STACK_LEN - 1), send(:j, *OVER_STACK_ARGV) + + def self.k(b=1, *a, **kw); a.length end + assert_equal (OVER_STACK_LEN - 1), send(:k, *OVER_STACK_ARGV) + + def self.l(b=1, *a, k: 1, **kw); a.length end + assert_equal (OVER_STACK_LEN - 1), send(:l, *OVER_STACK_ARGV) + + def self.m(b=1, *a, _, k: 1); a.length end + assert_equal (OVER_STACK_LEN - 2), send(:m, *OVER_STACK_ARGV) + + def self.n(b=1, *a, _, **kw); a.length end + assert_equal (OVER_STACK_LEN - 2), send(:n, *OVER_STACK_ARGV) + + def self.o(b=1, *a, _, k: 1, **kw); a.length end + assert_equal (OVER_STACK_LEN - 2), send(:o, *OVER_STACK_ARGV) + end + + def test_call_send_iseq_large_array_splat_with_large_number_of_parameters + args = OVER_STACK_ARGV.map{|i| "a#{i}"}.join(',') + args1 = (OVER_STACK_LEN-1).times.map{|i| "a#{i}"}.join(',') + + singleton_class.class_eval("def a(#{args}); [#{args}] end") + assert_equal OVER_STACK_ARGV, send(:a, *OVER_STACK_ARGV) + + singleton_class.class_eval("def b(#{args}, b=0); [#{args}, b] end") + assert_equal(OVER_STACK_ARGV + [0], send(:b, *OVER_STACK_ARGV)) + + singleton_class.class_eval("def c(#{args}, *b); [#{args}, b] end") + assert_equal(OVER_STACK_ARGV + [[]], send(:c, *OVER_STACK_ARGV)) + + singleton_class.class_eval("def d(#{args1}, *b); [#{args1}, b] end") + assert_equal(OVER_STACK_ARGV[0...-1] + [[OVER_STACK_ARGV.last]], send(:d, *OVER_STACK_ARGV)) + end if OVER_STACK_LEN < 200 + + def test_call_send_cfunc_large_array_splat_fail + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do + send(:object_id, *OVER_STACK_ARGV) + end + end + + def test_call_send_cfunc_large_array_splat_pass + assert_equal OVER_STACK_LEN, [].send(:push, *OVER_STACK_ARGV).length + end + + def test_call_attr_reader_large_array_splat_fail + singleton_class.send(:attr_reader, :a) + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do + a(*OVER_STACK_ARGV) + end + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do + send(:a, *OVER_STACK_ARGV) + end + end + + def test_call_attr_writer_large_array_splat_fail + singleton_class.send(:attr_writer, :a) + singleton_class.send(:alias_method, :a, :a=) + + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 1)") do + a(*OVER_STACK_ARGV) + end + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 1)") do + send(:a, *OVER_STACK_ARGV) + end + end + + def test_call_struct_aref_large_array_splat_fail + s = Struct.new(:a).new + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do + s.a(*OVER_STACK_ARGV) + end + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do + s.send(:a, *OVER_STACK_ARGV) + end + end + + def test_call_struct_aset_large_array_splat_fail + s = Struct.new(:a) do + alias b a= + end.new + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 1)") do + s.b(*OVER_STACK_ARGV) + end + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 1)") do + s.send(:b, *OVER_STACK_ARGV) + end + end + + def test_call_alias_large_array_splat + c = Class.new do + def a; end + def c(*a); a.length end + attr_accessor :e + end + sc = Class.new(c) do + alias b a + alias d c + alias f e + alias g e= + end + + obj = sc.new + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do + obj.b(*OVER_STACK_ARGV) + end + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do + obj.f(*OVER_STACK_ARGV) + end + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 1)") do + obj.g(*OVER_STACK_ARGV) + end + + assert_equal OVER_STACK_LEN, obj.d(*OVER_STACK_ARGV) + end + + def test_call_zsuper_large_array_splat + c = Class.new do + private + def a; end + def c(*a); a.length end + attr_reader :e + end + sc = Class.new(c) do + public :a + public :c + public :e + end + + obj = sc.new + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do + obj.a(*OVER_STACK_ARGV) + end + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do + obj.e(*OVER_STACK_ARGV) + end + + assert_equal OVER_STACK_LEN, obj.c(*OVER_STACK_ARGV) + end + + class RefinedModuleLargeArrayTest + c = self + using(Module.new do + refine c do + def a; end + def c(*a) a.length end + attr_reader :e + end + end) + + def b + a(*OVER_STACK_ARGV) + end + + def d + c(*OVER_STACK_ARGV) + end + + def f + e(*OVER_STACK_ARGV) + end + end + + def test_call_refined_large_array_splat_fail + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do + RefinedModuleLargeArrayTest.new.b + end + + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do + RefinedModuleLargeArrayTest.new.f + end + end + + def test_call_refined_large_array_splat_pass + assert_equal OVER_STACK_LEN, RefinedModuleLargeArrayTest.new.d + end + + def test_call_method_missing_iseq_large_array_splat_fail + def self.method_missing(_) end + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do + nonexistent_method(*OVER_STACK_ARGV) + end + + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do + send(:nonexistent_method, *OVER_STACK_ARGV) + end + + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do + send("nonexistent_method123", *OVER_STACK_ARGV) + end + end + + def test_call_method_missing_iseq_large_array_splat_pass + def self.method_missing(m, *a) + a.length + end + assert_equal OVER_STACK_LEN, nonexistent_method(*OVER_STACK_ARGV) + assert_equal OVER_STACK_LEN, send(:nonexistent_method, *OVER_STACK_ARGV) + assert_equal OVER_STACK_LEN, send("nonexistent_method123", *OVER_STACK_ARGV) + end + + def test_call_bmethod_large_array_splat_fail + define_singleton_method(:a){} + define_singleton_method(:b){|a=1|} + define_singleton_method(:c){|k: 1|} + define_singleton_method(:d){|**kw|} + define_singleton_method(:e){|k: 1, **kw|} + define_singleton_method(:f){|a=1, k: 1|} + define_singleton_method(:g){|a=1, **kw|} + define_singleton_method(:h){|a=1, k: 1, **kw|} + + (:a..:h).each do |meth| + assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do + instance_eval("#{meth}(*OVER_STACK_ARGV)", __FILE__, __LINE__) + end + end + end + + def test_call_bmethod_large_array_splat_pass + define_singleton_method(:a){|*a| a.length} + assert_equal OVER_STACK_LEN, a(*OVER_STACK_ARGV) + + define_singleton_method(:b){|_, *a| a.length} + assert_equal (OVER_STACK_LEN - 1), b(*OVER_STACK_ARGV) + + define_singleton_method(:c){|_, *a, _| a.length} + assert_equal (OVER_STACK_LEN - 2), c(*OVER_STACK_ARGV) + + define_singleton_method(:d){|b=1, *a| a.length} + assert_equal (OVER_STACK_LEN - 1), d(*OVER_STACK_ARGV) + + define_singleton_method(:e){|b=1, *a, _| a.length} + assert_equal (OVER_STACK_LEN - 2), e(*OVER_STACK_ARGV) + + define_singleton_method(:f){|b, *a| a.length} + assert_equal (OVER_STACK_LEN - 1), f(*OVER_STACK_ARGV) + + define_singleton_method(:g){|*a, k: 1| a.length} + assert_equal OVER_STACK_LEN, g(*OVER_STACK_ARGV) + + define_singleton_method(:h){|*a, **kw| a.length} + assert_equal OVER_STACK_LEN, h(*OVER_STACK_ARGV) + + define_singleton_method(:i){|*a, k: 1, **kw| a.length} + assert_equal OVER_STACK_LEN, i(*OVER_STACK_ARGV) + + define_singleton_method(:j){|b=1, *a, k: 1| a.length} + assert_equal (OVER_STACK_LEN - 1), j(*OVER_STACK_ARGV) + + define_singleton_method(:k){|b=1, *a, **kw| a.length} + assert_equal (OVER_STACK_LEN - 1), k(*OVER_STACK_ARGV) + + define_singleton_method(:l){|b=1, *a, k: 1, **kw| a.length} + assert_equal (OVER_STACK_LEN - 1), l(*OVER_STACK_ARGV) + + define_singleton_method(:m){|b=1, *a, _, k: 1| a.length} + assert_equal (OVER_STACK_LEN - 2), m(*OVER_STACK_ARGV) + + define_singleton_method(:n){|b=1, *a, _, **kw| a.length} + assert_equal (OVER_STACK_LEN - 2), n(*OVER_STACK_ARGV) + + define_singleton_method(:o){|b=1, *a, _, k: 1, **kw| a.length} + assert_equal (OVER_STACK_LEN - 2), o(*OVER_STACK_ARGV) + end + + def test_call_method_missing_bmethod_large_array_splat_fail + define_singleton_method(:method_missing){|_|} + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do + nonexistent_method(*OVER_STACK_ARGV) + end + + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do + send(:nonexistent_method, *OVER_STACK_ARGV) + end + + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do + send("nonexistent_method123", *OVER_STACK_ARGV) + end + end + + def test_call_method_missing_bmethod_large_array_splat_pass + define_singleton_method(:method_missing){|_, *a| a.length} + assert_equal OVER_STACK_LEN, nonexistent_method(*OVER_STACK_ARGV) + assert_equal OVER_STACK_LEN, send(:nonexistent_method, *OVER_STACK_ARGV) + assert_equal OVER_STACK_LEN, send("nonexistent_method123", *OVER_STACK_ARGV) + end + + def test_call_symproc_large_array_splat_fail + define_singleton_method(:a){} + define_singleton_method(:b){|a=1|} + define_singleton_method(:c){|k: 1|} + define_singleton_method(:d){|**kw|} + define_singleton_method(:e){|k: 1, **kw|} + define_singleton_method(:f){|a=1, k: 1|} + define_singleton_method(:g){|a=1, **kw|} + define_singleton_method(:h){|a=1, k: 1, **kw|} + + (:a..:h).each do |meth| + assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do + instance_eval(":#{meth}.to_proc.(self, *OVER_STACK_ARGV)", __FILE__, __LINE__) + end + end + end + + def test_call_symproc_large_array_splat_pass + define_singleton_method(:a){|*a| a.length} + assert_equal OVER_STACK_LEN, :a.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:b){|_, *a| a.length} + assert_equal (OVER_STACK_LEN - 1), :b.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:c){|_, *a, _| a.length} + assert_equal (OVER_STACK_LEN - 2), :c.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:d){|b=1, *a| a.length} + assert_equal (OVER_STACK_LEN - 1), :d.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:e){|b=1, *a, _| a.length} + assert_equal (OVER_STACK_LEN - 2), :e.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:f){|b, *a| a.length} + assert_equal (OVER_STACK_LEN - 1), :f.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:g){|*a, k: 1| a.length} + assert_equal OVER_STACK_LEN, :g.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:h){|*a, **kw| a.length} + assert_equal OVER_STACK_LEN, :h.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:i){|*a, k: 1, **kw| a.length} + assert_equal OVER_STACK_LEN, :i.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:j){|b=1, *a, k: 1| a.length} + assert_equal (OVER_STACK_LEN - 1), :j.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:k){|b=1, *a, **kw| a.length} + assert_equal (OVER_STACK_LEN - 1), :k.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:l){|b=1, *a, k: 1, **kw| a.length} + assert_equal (OVER_STACK_LEN - 1), :l.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:m){|b=1, *a, _, k: 1| a.length} + assert_equal (OVER_STACK_LEN - 2), :m.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:n){|b=1, *a, _, **kw| a.length} + assert_equal (OVER_STACK_LEN - 2), :n.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:o){|b=1, *a, _, k: 1, **kw| a.length} + assert_equal (OVER_STACK_LEN - 2), :o.to_proc.(self, *OVER_STACK_ARGV) + end + + def test_call_rb_call_iseq_large_array_splat_fail + extend Bug::Iter::Yield + l = ->(*a){} + + def self.a; end + def self.b(a=1) end + def self.c(k: 1) end + def self.d(**kw) end + def self.e(k: 1, **kw) end + def self.f(a=1, k: 1) end + def self.g(a=1, **kw) end + def self.h(a=1, k: 1, **kw) end + + (:a..:h).each do |meth| + assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do + yield_block(meth, *OVER_STACK_ARGV, &l) + end + end + end + + def test_call_rb_call_iseq_large_array_splat_pass + extend Bug::Iter::Yield + l = ->(*a){a.length} + + def self.a(*a) a.length end + assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l) + + def self.b(_, *a) a.length end + assert_equal (OVER_STACK_LEN - 1), yield_block(:b, *OVER_STACK_ARGV, &l) + + def self.c(_, *a, _) a.length end + assert_equal (OVER_STACK_LEN - 2), yield_block(:c, *OVER_STACK_ARGV, &l) + + def self.d(b=1, *a) a.length end + assert_equal (OVER_STACK_LEN - 1), yield_block(:d, *OVER_STACK_ARGV, &l) + + def self.e(b=1, *a, _) a.length end + assert_equal (OVER_STACK_LEN - 2), yield_block(:e, *OVER_STACK_ARGV, &l) + + def self.f(b, *a) a.length end + assert_equal (OVER_STACK_LEN - 1), yield_block(:f, *OVER_STACK_ARGV, &l) + + def self.g(*a, k: 1) a.length end + assert_equal OVER_STACK_LEN, yield_block(:g, *OVER_STACK_ARGV, &l) + + def self.h(*a, **kw) a.length end + assert_equal OVER_STACK_LEN, yield_block(:h, *OVER_STACK_ARGV, &l) + + def self.i(*a, k: 1, **kw) a.length end + assert_equal OVER_STACK_LEN, yield_block(:h, *OVER_STACK_ARGV, &l) + + def self.j(b=1, *a, k: 1) a.length end + assert_equal (OVER_STACK_LEN - 1), yield_block(:j, *OVER_STACK_ARGV, &l) + + def self.k(b=1, *a, **kw) a.length end + assert_equal (OVER_STACK_LEN - 1), yield_block(:k, *OVER_STACK_ARGV, &l) + + def self.l(b=1, *a, k: 1, **kw) a.length end + assert_equal (OVER_STACK_LEN - 1), yield_block(:l, *OVER_STACK_ARGV, &l) + + def self.m(b=1, *a, _, k: 1) a.length end + assert_equal (OVER_STACK_LEN - 2), yield_block(:m, *OVER_STACK_ARGV, &l) + + def self.n(b=1, *a, _, **kw) a.length end + assert_equal (OVER_STACK_LEN - 2), yield_block(:n, *OVER_STACK_ARGV, &l) + + def self.o(b=1, *a, _, k: 1, **kw) a.length end + assert_equal (OVER_STACK_LEN - 2), yield_block(:o, *OVER_STACK_ARGV, &l) + end + + def test_call_rb_call_bmethod_large_array_splat_fail + extend Bug::Iter::Yield + l = ->(*a){} + + define_singleton_method(:a){||} + define_singleton_method(:b){|a=1|} + define_singleton_method(:c){|k: 1|} + define_singleton_method(:d){|**kw|} + define_singleton_method(:e){|k: 1, **kw|} + define_singleton_method(:f){|a=1, k: 1|} + define_singleton_method(:g){|a=1, **kw|} + define_singleton_method(:h){|a=1, k: 1, **kw|} + + (:a..:h).each do |meth| + assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do + yield_block(meth, *OVER_STACK_ARGV, &l) + end + end + end + + def test_call_rb_call_bmethod_large_array_splat_pass + extend Bug::Iter::Yield + l = ->(*a){a.length} + + define_singleton_method(:a){|*a| a.length} + assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l) + + define_singleton_method(:b){|_, *a| a.length} + assert_equal (OVER_STACK_LEN - 1), yield_block(:b, *OVER_STACK_ARGV, &l) + + define_singleton_method(:c){|_, *a, _| a.length} + assert_equal (OVER_STACK_LEN - 2), yield_block(:c, *OVER_STACK_ARGV, &l) + + define_singleton_method(:d){|b=1, *a| a.length} + assert_equal (OVER_STACK_LEN - 1), yield_block(:d, *OVER_STACK_ARGV, &l) + + define_singleton_method(:e){|b=1, *a, _| a.length} + assert_equal (OVER_STACK_LEN - 2), yield_block(:e, *OVER_STACK_ARGV, &l) + + define_singleton_method(:f){|b, *a| a.length} + assert_equal (OVER_STACK_LEN - 1), yield_block(:f, *OVER_STACK_ARGV, &l) + + define_singleton_method(:g){|*a, k: 1| a.length} + assert_equal OVER_STACK_LEN, yield_block(:g, *OVER_STACK_ARGV, &l) + + define_singleton_method(:h){|*a, **kw| a.length} + assert_equal OVER_STACK_LEN, yield_block(:h, *OVER_STACK_ARGV, &l) + + define_singleton_method(:i){|*a, k: 1, **kw| a.length} + assert_equal OVER_STACK_LEN, yield_block(:h, *OVER_STACK_ARGV, &l) + + define_singleton_method(:j){|b=1, *a, k: 1| a.length} + assert_equal (OVER_STACK_LEN - 1), yield_block(:j, *OVER_STACK_ARGV, &l) + + define_singleton_method(:k){|b=1, *a, **kw| a.length} + assert_equal (OVER_STACK_LEN - 1), yield_block(:k, *OVER_STACK_ARGV, &l) + + define_singleton_method(:l){|b=1, *a, k: 1, **kw| a.length} + assert_equal (OVER_STACK_LEN - 1), yield_block(:l, *OVER_STACK_ARGV, &l) + + define_singleton_method(:m){|b=1, *a, _, k: 1| a.length} + assert_equal (OVER_STACK_LEN - 2), yield_block(:m, *OVER_STACK_ARGV, &l) + + define_singleton_method(:n){|b=1, *a, _, **kw| a.length} + assert_equal (OVER_STACK_LEN - 2), yield_block(:n, *OVER_STACK_ARGV, &l) + + define_singleton_method(:o){|b=1, *a, _, k: 1, **kw| a.length} + assert_equal (OVER_STACK_LEN - 2), yield_block(:o, *OVER_STACK_ARGV, &l) + end + + def test_call_ifunc_iseq_large_array_splat_fail + extend Bug::Iter::Yield + def self.a(*a) + yield(*a) + end + [ + ->(){}, + ->(a=1){}, + ->(k: 1){}, + ->(**kw){}, + ->(k: 1, **kw){}, + ->(a=1, k: 1){}, + ->(a=1, **kw){}, + ->(a=1, k: 1, **kw){}, + ].each do |l| + assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do + yield_block(:a, *OVER_STACK_ARGV, &l) + end + end + end + + def test_call_ifunc_iseq_large_array_splat_pass + extend Bug::Iter::Yield + def self.a(*a) + yield(*a) + end + + l = ->(*a) do a.length end + assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(_, *a) do a.length end + assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(_, *a, _) do a.length end + assert_equal (OVER_STACK_LEN - 2), yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(b=1, *a) do a.length end + assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(b=1, *a, _) do a.length end + assert_equal (OVER_STACK_LEN - 2), yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(b, *a) do a.length end + assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(*a, k: 1) do a.length end + assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(*a, **kw) do a.length end + assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(*a, k: 1, **kw) do a.length end + assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(b=1, *a, k: 1) do a.length end + assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(b=1, *a, **kw) do a.length end + assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(b=1, *a, k: 1, **kw) do a.length end + assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(b=1, *a, _, k: 1) do a.length end + assert_equal (OVER_STACK_LEN - 2), yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(b=1, *a, _, **kw) do a.length end + assert_equal (OVER_STACK_LEN - 2), yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(b=1, *a, _, k: 1, **kw) do a.length end + assert_equal (OVER_STACK_LEN - 2), yield_block(:a, *OVER_STACK_ARGV, &l) + end end @@ -1426,17 +1426,29 @@ invoke_iseq_block_from_c(rb_execution_context_t *ec, const struct rb_captured_bl VALUE type = VM_FRAME_MAGIC_BLOCK | (is_lambda ? VM_FRAME_FLAG_LAMBDA : 0); rb_control_frame_t *cfp = ec->cfp; VALUE *sp = cfp->sp; + int flags = (kw_splat ? VM_CALL_KW_SPLAT : 0); + VALUE *use_argv = (VALUE *)argv; + VALUE av[2]; stack_check(ec); +#if VM_ARGC_STACK_MAX < 1 + /* Skip ruby array for potential autosplat case */ + if (UNLIKELY(argc > VM_ARGC_STACK_MAX && (argc != 1 || is_lambda))) { +#else + if (UNLIKELY(argc > VM_ARGC_STACK_MAX)) { +#endif + use_argv = vm_argv_ruby_array(av, argv, &flags, &argc, kw_splat); + } + CHECK_VM_STACK_OVERFLOW(cfp, argc); vm_check_canary(ec, sp); cfp->sp = sp + argc; for (i=0; i<argc; i++) { - sp[i] = argv[i]; + sp[i] = use_argv[i]; } - opt_pc = vm_yield_setup_args(ec, iseq, argc, sp, kw_splat, passed_block_handler, + opt_pc = vm_yield_setup_args(ec, iseq, argc, sp, flags, passed_block_handler, (is_lambda ? arg_setup_method : arg_setup_block)); cfp->sp = sp; @@ -296,8 +296,15 @@ struct rb_calling_info { VALUE recv; int argc; bool kw_splat; + VALUE heap_argv; }; +#ifndef VM_ARGC_STACK_MAX +#define VM_ARGC_STACK_MAX 128 +#endif + +# define CALLING_ARGC(calling) ((calling)->heap_argv ? RARRAY_LENINT((calling)->heap_argv) : (calling)->argc) + struct rb_execution_context_struct; #if 1 @@ -41,19 +41,33 @@ typedef enum call_type { static VALUE send_internal(int argc, const VALUE *argv, VALUE recv, call_type scope); static VALUE vm_call0_body(rb_execution_context_t* ec, struct rb_calling_info *calling, const VALUE *argv); +static VALUE * +vm_argv_ruby_array(VALUE *av, const VALUE *argv, int *flags, int *argc, int kw_splat) +{ + *flags |= VM_CALL_ARGS_SPLAT; + VALUE argv_ary = rb_ary_hidden_new(*argc); + rb_ary_cat(argv_ary, argv, *argc); + *argc = 2; + av[0] = argv_ary; + if (kw_splat) { + av[1] = rb_ary_pop(argv_ary); + } + else { + // Make sure flagged keyword hash passed as regular argument + // isn't treated as keywords + *flags |= VM_CALL_KW_SPLAT; + av[1] = rb_hash_new(); + } + return av; +} + +static inline VALUE vm_call0_cc(rb_execution_context_t *ec, VALUE recv, ID id, int argc, const VALUE *argv, const struct rb_callcache *cc, int kw_splat); + VALUE rb_vm_call0(rb_execution_context_t *ec, VALUE recv, ID id, int argc, const VALUE *argv, const rb_callable_method_entry_t *cme, int kw_splat) { - struct rb_calling_info calling = { - .ci = &VM_CI_ON_STACK(id, kw_splat ? VM_CALL_KW_SPLAT : 0, argc, NULL), - .cc = &VM_CC_ON_STACK(Qfalse, vm_call_general, {{ 0 }}, cme), - .block_handler = vm_passed_block_handler(ec), - .recv = recv, - .argc = argc, - .kw_splat = kw_splat, - }; - - return vm_call0_body(ec, &calling, argv); + const struct rb_callcache cc = VM_CC_ON_STACK(Qfalse, vm_call_general, {{ 0 }}, cme); + return vm_call0_cc(ec, recv, id, argc, argv, &cc, kw_splat); } VALUE @@ -73,8 +87,16 @@ rb_vm_call_with_refinements(rb_execution_context_t *ec, VALUE recv, ID id, int a static inline VALUE vm_call0_cc(rb_execution_context_t *ec, VALUE recv, ID id, int argc, const VALUE *argv, const struct rb_callcache *cc, int kw_splat) { + int flags = kw_splat ? VM_CALL_KW_SPLAT : 0; + VALUE *use_argv = (VALUE *)argv; + VALUE av[2]; + + if (UNLIKELY(vm_cc_cme(cc)->def->type == VM_METHOD_TYPE_ISEQ && argc > VM_ARGC_STACK_MAX)) { + use_argv = vm_argv_ruby_array(av, argv, &flags, &argc, kw_splat); + } + struct rb_calling_info calling = { - .ci = &VM_CI_ON_STACK(id, kw_splat ? VM_CALL_KW_SPLAT : 0, argc, NULL), + .ci = &VM_CI_ON_STACK(id, flags, argc, NULL), .cc = cc, .block_handler = vm_passed_block_handler(ec), .recv = recv, @@ -82,7 +104,7 @@ vm_call0_cc(rb_execution_context_t *ec, VALUE recv, ID id, int argc, const VALUE .kw_splat = kw_splat, }; - return vm_call0_body(ec, &calling, argv); + return vm_call0_body(ec, &calling, use_argv); } static VALUE diff --git a/vm_insnhelper.c b/vm_insnhelper.c index aafc49bfd5..0bbae89498 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -2525,22 +2525,67 @@ rb_iseq_only_kwparam_p(const rb_iseq_t *iseq) ISEQ_BODY(iseq)->param.flags.has_block == FALSE; } -static inline void -vm_caller_setup_arg_splat(rb_control_frame_t *cfp, struct rb_calling_info *calling, VALUE ary) +#define ALLOW_HEAP_ARGV (-2) +#define ALLOW_HEAP_ARGV_KEEP_KWSPLAT (-3) + +static inline bool +vm_caller_setup_arg_splat(rb_control_frame_t *cfp, struct rb_calling_info *calling, VALUE ary, int max_args) { vm_check_canary(GET_EC(), cfp->sp); + bool ret = false; if (!NIL_P(ary)) { const VALUE *ptr = RARRAY_CONST_PTR_TRANSIENT(ary); - long len = RARRAY_LEN(ary), i; + long len = RARRAY_LEN(ary); + int argc = calling->argc; - CHECK_VM_STACK_OVERFLOW(cfp, len); + if (UNLIKELY(max_args <= ALLOW_HEAP_ARGV && len + argc > VM_ARGC_STACK_MAX)) { + /* Avoid SystemStackError when splatting large arrays by storing arguments in + * a temporary array, instead of trying to keeping arguments on the VM stack. + */ + VALUE *argv = cfp->sp - argc; + VALUE argv_ary = rb_ary_hidden_new(len + argc + 1); + rb_ary_cat(argv_ary, argv, argc); + rb_ary_cat(argv_ary, ptr, len); + cfp->sp -= argc - 1; + cfp->sp[-1] = argv_ary; + calling->argc = 1; + calling->heap_argv = argv_ary; + RB_GC_GUARD(ary); + } + else { + long i; + + if (max_args >= 0 && len + argc > max_args) { + /* If only a given max_args is allowed, copy up to max args. + * Used by vm_callee_setup_block_arg for non-lambda blocks, + * where additional arguments are ignored. + * + * Also, copy up to one more argument than the maximum, + * in case it is an empty keyword hash that will be removed. + */ + calling->argc += len - (max_args - argc + 1); + len = max_args - argc + 1; + ret = true; + } + else { + /* Unset heap_argv if set originally. Can happen when + * forwarding modified arguments, where heap_argv was used + * originally, but heap_argv not supported by the forwarded + * method in all cases. + */ + calling->heap_argv = 0; + } + CHECK_VM_STACK_OVERFLOW(cfp, len); - for (i = 0; i < len; i++) { - *cfp->sp++ = ptr[i]; + for (i = 0; i < len; i++) { + *cfp->sp++ = ptr[i]; + } + calling->argc += i; } - calling->argc += i; } + + return ret; } static inline void @@ -2581,56 +2626,85 @@ vm_caller_setup_keyword_hash(const struct rb_callinfo *ci, VALUE keyword_hash) static inline void CALLER_SETUP_ARG(struct rb_control_frame_struct *restrict cfp, struct rb_calling_info *restrict calling, - const struct rb_callinfo *restrict ci) + const struct rb_callinfo *restrict ci, int max_args) { - if (UNLIKELY(IS_ARGS_SPLAT(ci) && IS_ARGS_KW_SPLAT(ci))) { - // f(*a, **kw) - VM_ASSERT(calling->kw_splat == 1); - - cfp->sp -= 2; - calling->argc -= 2; - VALUE ary = cfp->sp[0]; - VALUE kwh = vm_caller_setup_keyword_hash(ci, cfp->sp[1]); - - // splat a - vm_caller_setup_arg_splat(cfp, calling, ary); + if (UNLIKELY(IS_ARGS_SPLAT(ci))) { + if (IS_ARGS_KW_SPLAT(ci)) { + // f(*a, **kw) + VM_ASSERT(calling->kw_splat == 1); - // put kw - if (!RHASH_EMPTY_P(kwh)) { - cfp->sp[0] = kwh; - cfp->sp++; - calling->argc++; + cfp->sp -= 2; + calling->argc -= 2; + VALUE ary = cfp->sp[0]; + VALUE kwh = vm_caller_setup_keyword_hash(ci, cfp->sp[1]); + + // splat a + if (vm_caller_setup_arg_splat(cfp, calling, ary, max_args)) return; + + // put kw + if (!RHASH_EMPTY_P(kwh)) { + if (UNLIKELY(calling->heap_argv)) { + rb_ary_push(calling->heap_argv, kwh); + ((struct RHash *)kwh)->basic.flags |= RHASH_PASS_AS_KEYWORDS; + if (max_args != ALLOW_HEAP_ARGV_KEEP_KWSPLAT) { + calling->kw_splat = 0; + } + } + else { + cfp->sp[0] = kwh; + cfp->sp++; + calling->argc++; - VM_ASSERT(calling->kw_splat == 1); + VM_ASSERT(calling->kw_splat == 1); + } + } + else { + calling->kw_splat = 0; + } } else { - calling->kw_splat = 0; - } - } - else if (UNLIKELY(IS_ARGS_SPLAT(ci))) { - // f(*a) - VM_ASSERT(calling->kw_splat == 0); + // f(*a) + VM_ASSERT(calling->kw_splat == 0); - cfp->sp -= 1; - calling->argc -= 1; - VALUE ary = cfp->sp[0]; + cfp->sp -= 1; + calling->argc -= 1; + VALUE ary = cfp->sp[0]; - vm_caller_setup_arg_splat(cfp, calling, ary); + if (vm_caller_setup_arg_splat(cfp, calling, ary, max_args)) { + goto check_keyword; + } - // check the last argument - VALUE last_hash; - if (!IS_ARGS_KEYWORD(ci) && - calling->argc > 0 && - RB_TYPE_P((last_hash = cfp->sp[-1]), T_HASH) && - (((struct RHash *)last_hash)->basic.flags & RHASH_PASS_AS_KEYWORDS)) { + // check the last argument + VALUE last_hash, argv_ary; + if (UNLIKELY(argv_ary = calling->heap_argv)) { + if (!IS_ARGS_KEYWORD(ci) && + RARRAY_LEN(argv_ary) > 0 && + RB_TYPE_P((last_hash = rb_ary_last(0, NULL, argv_ary)), T_HASH) && + (((struct RHash *)last_hash)->basic.flags & RHASH_PASS_AS_KEYWORDS)) { - if (RHASH_EMPTY_P(last_hash)) { - calling->argc--; - cfp->sp -= 1; + rb_ary_pop(argv_ary); + if (!RHASH_EMPTY_P(last_hash)) { + rb_ary_push(argv_ary, rb_hash_dup(last_hash)); + calling->kw_splat = 1; + } + } } else { - cfp->sp[-1] = rb_hash_dup(last_hash); - calling->kw_splat = 1; +check_keyword: + if (!IS_ARGS_KEYWORD(ci) && + calling->argc > 0 && + RB_TYPE_P((last_hash = cfp->sp[-1]), T_HASH) && + (((struct RHash *)last_hash)->basic.flags & RHASH_PASS_AS_KEYWORDS)) { + + if (RHASH_EMPTY_P(last_hash)) { + calling->argc--; + cfp->sp -= 1; + } + else { + cfp->sp[-1] = rb_hash_dup(last_hash); + calling->kw_splat = 1; + } + } } } } @@ -2811,10 +2885,11 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling, if (LIKELY(!(vm_ci_flag(ci) & VM_CALL_KW_SPLAT))) { if (LIKELY(rb_simple_iseq_p(iseq))) { rb_control_frame_t *cfp = ec->cfp; - CALLER_SETUP_ARG(cfp, calling, ci); + int lead_num = ISEQ_BODY(iseq)->param.lead_num; + CALLER_SETUP_ARG(cfp, calling, ci, lead_num); - if (calling->argc != ISEQ_BODY(iseq)->param.lead_num) { - argument_arity_error(ec, iseq, calling->argc, ISEQ_BODY(iseq)->param.lead_num, ISEQ_BODY(iseq)->param.lead_num); + if (calling->argc != lead_num) { + argument_arity_error(ec, iseq, calling->argc, lead_num, lead_num); } VM_ASSERT(ci == calling->ci); @@ -2835,10 +2910,11 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling, } else if (rb_iseq_only_optparam_p(iseq)) { rb_control_frame_t *cfp = ec->cfp; - CALLER_SETUP_ARG(cfp, calling, ci); const int lead_num = ISEQ_BODY(iseq)->param.lead_num; const int opt_num = ISEQ_BODY(iseq)->param.opt_num; + + CALLER_SETUP_ARG(cfp, calling, ci, lead_num + opt_num); const int argc = calling->argc; const int opt = argc - lead_num; @@ -3385,86 +3461,16 @@ vm_call_cfunc_with_frame(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp return vm_call_cfunc_with_frame_(ec, reg_cfp, calling, argc, argv, stack_bottom); } -#ifndef VM_ARGC_STACK_MAX -#define VM_ARGC_STACK_MAX 128 -#endif - -static VALUE -vm_call_cfunc_setup_argv_ary(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_callinfo *ci) -{ - const bool kwsplat_p = IS_ARGS_KW_SPLAT(ci); - int argc = calling->argc; - VALUE *argv = cfp->sp - argc; - VALUE ary = argv[argc - (kwsplat_p ? 2 : 1)]; - long len = RARRAY_LEN(ary); - - if (UNLIKELY(len + argc > VM_ARGC_STACK_MAX)) { - VALUE kwhash; - - if (kwsplat_p) { - // the last argument is kwhash - cfp->sp--; - kwhash = vm_caller_setup_keyword_hash(ci, cfp->sp[0]); - calling->argc--; - argc--; - - VM_ASSERT(calling->kw_splat); - } - - vm_check_canary(ec, cfp->sp); - const VALUE *ptr = RARRAY_CONST_PTR_TRANSIENT(ary); - VALUE argv_ary = rb_ary_new_capa(len + argc - 1); - rb_obj_hide(argv_ary); - rb_ary_cat(argv_ary, argv, argc-1); - rb_ary_cat(argv_ary, ptr, len); - cfp->sp -= argc - 1; - cfp->sp[-1] = argv_ary; - calling->argc = 1; - - if (kwsplat_p) { - if (!RHASH_EMPTY_P(kwhash)) { - rb_ary_push(argv_ary, kwhash); - } - else { - calling->kw_splat = false; - } - } - else if (RARRAY_LEN(argv_ary) > 0) { - // check the last argument - long hash_idx = RARRAY_LEN(argv_ary) - 1; - VALUE last_hash = RARRAY_AREF(argv_ary, hash_idx); - - if (RB_TYPE_P(last_hash, T_HASH) && - (((struct RHash *)last_hash)->basic.flags & RHASH_PASS_AS_KEYWORDS)) { - if (RHASH_EMPTY_P(last_hash)) { - rb_ary_pop(argv_ary); - } - else { - last_hash = rb_hash_dup(last_hash); - RARRAY_ASET(argv_ary, hash_idx, last_hash); - calling->kw_splat = 1; - } - } - } - - return argv_ary; - } - else { - return Qfalse; - } -} - static VALUE vm_call_cfunc(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling) { const struct rb_callinfo *ci = calling->ci; RB_DEBUG_COUNTER_INC(ccf_cfunc); + CALLER_SETUP_ARG(reg_cfp, calling, ci, ALLOW_HEAP_ARGV_KEEP_KWSPLAT); VALUE argv_ary; - - if (UNLIKELY(IS_ARGS_SPLAT(ci)) && (argv_ary = vm_call_cfunc_setup_argv_ary(ec, reg_cfp, calling, ci))) { + if (UNLIKELY(argv_ary = calling->heap_argv)) { VM_ASSERT(!IS_ARGS_KEYWORD(ci)); - int argc = RARRAY_LENINT(argv_ary); VALUE *argv = (VALUE *)RARRAY_CONST_PTR(argv_ary); VALUE *stack_bottom = reg_cfp->sp - 2; @@ -3476,7 +3482,6 @@ vm_call_cfunc(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb return vm_call_cfunc_with_frame_(ec, reg_cfp, calling, argc, argv, stack_bottom); } else { - CALLER_SETUP_ARG(reg_cfp, calling, ci); CC_SET_FASTPATH(calling->cc, vm_call_cfunc_with_frame, !rb_splat_or_kwargs_p(ci) && !calling->kw_splat); return vm_call_cfunc_with_frame(ec, reg_cfp, calling); @@ -3545,7 +3550,7 @@ vm_call_bmethod_body(rb_execution_context_t *ec, struct rb_calling_info *calling /* control block frame */ GetProcPtr(procv, proc); - val = rb_vm_invoke_bmethod(ec, proc, calling->recv, calling->argc, argv, calling->kw_splat, calling->block_handler, vm_cc_cme(cc)); + val = rb_vm_invoke_bmethod(ec, proc, calling->recv, CALLING_ARGC(calling), argv, calling->kw_splat, calling->block_handler, vm_cc_cme(cc)); return val; } @@ -3559,11 +3564,17 @@ vm_call_bmethod(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_c int argc; const struct rb_callinfo *ci = calling->ci; - CALLER_SETUP_ARG(cfp, calling, ci); - argc = calling->argc; - argv = ALLOCA_N(VALUE, argc); - MEMCPY(argv, cfp->sp - argc, VALUE, argc); - cfp->sp += - argc - 1; + CALLER_SETUP_ARG(cfp, calling, ci, ALLOW_HEAP_ARGV); + if (UNLIKELY(calling->heap_argv)) { + argv = RARRAY_PTR(calling->heap_argv); + cfp->sp -= 2; + } + else { + argc = calling->argc; + argv = ALLOCA_N(VALUE, argc); + MEMCPY(argv, cfp->sp - argc, VALUE, argc); + cfp->sp += - argc - 1; + } return vm_call_bmethod_body(ec, calling, argv); } @@ -3666,37 +3677,53 @@ vm_call_symbol(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, missing_reason = ci_missing_reason(ci); ec->method_missing_reason = missing_reason; - /* E.g. when argc == 2 - * - * | | | | TOPN - * | | +------+ - * | | +---> | arg1 | 0 - * +------+ | +------+ - * | arg1 | -+ +-> | arg0 | 1 - * +------+ | +------+ - * | arg0 | ---+ | sym | 2 - * +------+ +------+ - * | recv | | recv | 3 - * --+------+--------+------+------ - */ - int i = argc; - CHECK_VM_STACK_OVERFLOW(reg_cfp, 1); - INC_SP(1); - MEMMOVE(&TOPN(i - 1), &TOPN(i), VALUE, i); - argc = ++calling->argc; + VALUE argv_ary; + if (UNLIKELY(argv_ary = calling->heap_argv)) { + if (rb_method_basic_definition_p(klass, idMethodMissing)) { + rb_ary_unshift(argv_ary, symbol); - if (rb_method_basic_definition_p(klass, idMethodMissing)) { - /* Inadvertent symbol creation shall be forbidden, see [Feature #5112] */ - TOPN(i) = symbol; - int priv = vm_ci_flag(ci) & (VM_CALL_FCALL | VM_CALL_VCALL); - const VALUE *argv = STACK_ADDR_FROM_TOP(argc); - VALUE exc = rb_make_no_method_exception( - rb_eNoMethodError, 0, recv, argc, argv, priv); + /* Inadvertent symbol creation shall be forbidden, see [Feature #5112] */ + int priv = vm_ci_flag(ci) & (VM_CALL_FCALL | VM_CALL_VCALL); + VALUE exc = rb_make_no_method_exception( + rb_eNoMethodError, 0, recv, RARRAY_LENINT(argv_ary), RARRAY_CONST_PTR(argv_ary), priv); - rb_exc_raise(exc); + rb_exc_raise(exc); + } + rb_ary_unshift(argv_ary, rb_str_intern(symbol)); } else { - TOPN(i) = rb_str_intern(symbol); + /* E.g. when argc == 2 + * + * | | | | TOPN + * | | +------+ + * | | +---> | arg1 | 0 + * +------+ | +------+ + * | arg1 | -+ +-> | arg0 | 1 + * +------+ | +------+ + * | arg0 | ---+ | sym | 2 + * +------+ +------+ + * | recv | | recv | 3 + * --+------+--------+------+------ + */ + int i = argc; + CHECK_VM_STACK_OVERFLOW(reg_cfp, 1); + INC_SP(1); + MEMMOVE(&TOPN(i - 1), &TOPN(i), VALUE, i); + argc = ++calling->argc; + + if (rb_method_basic_definition_p(klass, idMethodMissing)) { + /* Inadvertent symbol creation shall be forbidden, see [Feature #5112] */ + TOPN(i) = symbol; + int priv = vm_ci_flag(ci) & (VM_CALL_FCALL | VM_CALL_VCALL); + const VALUE *argv = STACK_ADDR_FROM_TOP(argc); + VALUE exc = rb_make_no_method_exception( + rb_eNoMethodError, 0, recv, argc, argv, priv); + + rb_exc_raise(exc); + } + else { + TOPN(i) = rb_str_intern(symbol); + } } } @@ -3737,17 +3764,26 @@ vm_call_opt_send(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct { RB_DEBUG_COUNTER_INC(ccf_opt_send); - int i; - VALUE sym; + int i, flags = VM_CALL_FCALL; + VALUE sym, argv_ary; - CALLER_SETUP_ARG(reg_cfp, calling, calling->ci); - - i = calling->argc - 1; - - if (calling->argc == 0) { - rb_raise(rb_eArgError, "no method name given"); + CALLER_SETUP_ARG(reg_cfp, calling, calling->ci, ALLOW_HEAP_ARGV); + if (UNLIKELY(argv_ary = calling->heap_argv)) { + sym = rb_ary_shift(argv_ary); + flags |= VM_CALL_ARGS_SPLAT; + if (calling->kw_splat) { + VALUE last_hash = rb_ary_last(0, NULL, argv_ary); + ((struct RHash *)last_hash)->basic.flags |= RHASH_PASS_AS_KEYWORDS; + calling->kw_splat = 0; + } } else { + i = calling->argc - 1; + + if (calling->argc == 0) { + rb_raise(rb_eArgError, "no method name given"); + } + sym = TOPN(i); /* E.g. when i == 2 * @@ -3768,9 +3804,9 @@ vm_call_opt_send(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct } calling->argc -= 1; DEC_SP(1); - - return vm_call_symbol(ec, reg_cfp, calling, calling->ci, sym, VM_CALL_FCALL); } + + return vm_call_symbol(ec, reg_cfp, calling, calling->ci, sym, flags); } static VALUE @@ -3780,22 +3816,29 @@ vm_call_method_missing_body(rb_execution_context_t *ec, rb_control_frame_t *reg_ RB_DEBUG_COUNTER_INC(ccf_method_missing); VALUE *argv = STACK_ADDR_FROM_TOP(calling->argc); - unsigned int argc; + unsigned int argc, flag; - CALLER_SETUP_ARG(reg_cfp, calling, orig_ci); - argc = calling->argc + 1; + CALLER_SETUP_ARG(reg_cfp, calling, orig_ci, ALLOW_HEAP_ARGV); + if (UNLIKELY(calling->heap_argv)) { + flag = VM_CALL_ARGS_SPLAT | VM_CALL_FCALL | VM_CALL_OPT_SEND | (calling->kw_splat ? VM_CALL_KW_SPLAT : 0); + argc = 1; + rb_ary_unshift(calling->heap_argv, ID2SYM(vm_ci_mid(orig_ci))); + } + else { + argc = calling->argc + 1; - unsigned int flag = VM_CALL_FCALL | VM_CALL_OPT_SEND | (calling->kw_splat ? VM_CALL_KW_SPLAT : 0); - calling->argc = argc; + flag = VM_CALL_FCALL | VM_CALL_OPT_SEND | (calling->kw_splat ? VM_CALL_KW_SPLAT : 0); + calling->argc = argc; - /* shift arguments: m(a, b, c) #=> method_missing(:m, a, b, c) */ - CHECK_VM_STACK_OVERFLOW(reg_cfp, 1); - vm_check_canary(ec, reg_cfp->sp); - if (argc > 1) { - MEMMOVE(argv+1, argv, VALUE, argc-1); + /* shift arguments: m(a, b, c) #=> method_missing(:m, a, b, c) */ + CHECK_VM_STACK_OVERFLOW(reg_cfp, 1); + vm_check_canary(ec, reg_cfp->sp); + if (argc > 1) { + MEMMOVE(argv+1, argv, VALUE, argc-1); + } + argv[0] = ID2SYM(vm_ci_mid(orig_ci)); + INC_SP(1); } - argv[0] = ID2SYM(vm_ci_mid(orig_ci)); - INC_SP(1); ec->method_missing_reason = reason; calling->ci = &VM_CI_ON_STACK(idMethodMissing, flag, argc, vm_ci_kwarg(orig_ci)); @@ -4059,13 +4102,13 @@ vm_call_optimized(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb CC_SET_FASTPATH(cc, vm_call_opt_block_call, TRUE); return vm_call_opt_block_call(ec, cfp, calling); case OPTIMIZED_METHOD_TYPE_STRUCT_AREF: - CALLER_SETUP_ARG(cfp, calling, ci); + CALLER_SETUP_ARG(cfp, calling, ci, 0); rb_check_arity(calling->argc, 0, 0); CC_SET_FASTPATH(cc, vm_call_opt_struct_aref, (vm_ci_flag(ci) & VM_CALL_ARGS_SIMPLE)); return vm_call_opt_struct_aref(ec, cfp, calling); case OPTIMIZED_METHOD_TYPE_STRUCT_ASET: - CALLER_SETUP_ARG(cfp, calling, ci); + CALLER_SETUP_ARG(cfp, calling, ci, 1); rb_check_arity(calling->argc, 1, 1); CC_SET_FASTPATH(cc, vm_call_opt_struct_aset, (vm_ci_flag(ci) & VM_CALL_ARGS_SIMPLE)); return vm_call_opt_struct_aset(ec, cfp, calling); @@ -4106,7 +4149,7 @@ vm_call_method_each_type(rb_execution_context_t *ec, rb_control_frame_t *cfp, st return vm_call_cfunc(ec, cfp, calling); case VM_METHOD_TYPE_ATTRSET: - CALLER_SETUP_ARG(cfp, calling, ci); + CALLER_SETUP_ARG(cfp, calling, ci, 1); rb_check_arity(calling->argc, 1, 1); @@ -4141,7 +4184,7 @@ vm_call_method_each_type(rb_execution_context_t *ec, rb_control_frame_t *cfp, st return v; case VM_METHOD_TYPE_IVAR: - CALLER_SETUP_ARG(cfp, calling, ci); + CALLER_SETUP_ARG(cfp, calling, ci, 0); rb_check_arity(calling->argc, 0, 0); vm_cc_attr_index_initialize(cc, INVALID_SHAPE_ID); const unsigned int ivar_mask = (VM_CALL_ARGS_SPLAT | VM_CALL_KW_SPLAT); @@ -4191,9 +4234,14 @@ vm_call_method_nome(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct const int stat = ci_missing_reason(ci); if (vm_ci_mid(ci) == idMethodMissing) { - rb_control_frame_t *reg_cfp = cfp; - VALUE *argv = STACK_ADDR_FROM_TOP(calling->argc); - vm_raise_method_missing(ec, calling->argc, argv, calling->recv, stat); + if (UNLIKELY(calling->heap_argv)) { + vm_raise_method_missing(ec, RARRAY_LENINT(calling->heap_argv), RARRAY_CONST_PTR(calling->heap_argv), calling->recv, stat); + } + else { + rb_control_frame_t *reg_cfp = cfp; + VALUE *argv = STACK_ADDR_FROM_TOP(calling->argc); + vm_raise_method_missing(ec, calling->argc, argv, calling->recv, stat); + } } else { return vm_call_method_missing_body(ec, cfp, calling, ci, stat); @@ -4517,7 +4565,7 @@ vm_callee_setup_block_arg(rb_execution_context_t *ec, struct rb_calling_info *ca rb_control_frame_t *cfp = ec->cfp; VALUE arg0; - CALLER_SETUP_ARG(cfp, calling, ci); + CALLER_SETUP_ARG(cfp, calling, ci, ISEQ_BODY(iseq)->param.lead_num); if (arg_setup_type == arg_setup_block && calling->argc == 1 && @@ -4552,16 +4600,17 @@ vm_callee_setup_block_arg(rb_execution_context_t *ec, struct rb_calling_info *ca } static int -vm_yield_setup_args(rb_execution_context_t *ec, const rb_iseq_t *iseq, const int argc, VALUE *argv, int kw_splat, VALUE block_handler, enum arg_setup_type arg_setup_type) +vm_yield_setup_args(rb_execution_context_t *ec, const rb_iseq_t *iseq, const int argc, VALUE *argv, int flags, VALUE block_handler, enum arg_setup_type arg_setup_type) { struct rb_calling_info calling_entry, *calling; calling = &calling_entry; calling->argc = argc; calling->block_handler = block_handler; - calling->kw_splat = kw_splat; + calling->kw_splat = (flags & VM_CALL_KW_SPLAT) ? 1 : 0; calling->recv = Qundef; - struct rb_callinfo dummy_ci = VM_CI_ON_STACK(0, (kw_splat ? VM_CALL_KW_SPLAT : 0), 0, 0); + calling->heap_argv = 0; + struct rb_callinfo dummy_ci = VM_CI_ON_STACK(0, flags, 0, 0); return vm_callee_setup_block_arg(ec, calling, &dummy_ci, iseq, argv, arg_setup_type); } @@ -4577,7 +4626,8 @@ vm_invoke_iseq_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, const rb_iseq_t *iseq = rb_iseq_check(captured->code.iseq); const int arg_size = ISEQ_BODY(iseq)->param.size; VALUE * const rsp = GET_SP() - calling->argc; - int opt_pc = vm_callee_setup_block_arg(ec, calling, ci, iseq, rsp, is_lambda ? arg_setup_method : arg_setup_block); + VALUE * const argv = rsp; + int opt_pc = vm_callee_setup_block_arg(ec, calling, ci, iseq, argv, is_lambda ? arg_setup_method : arg_setup_block); SET_SP(rsp); @@ -4597,15 +4647,29 @@ vm_invoke_symbol_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, const struct rb_callinfo *ci, MAYBE_UNUSED(bool is_lambda), VALUE block_handler) { - if (calling->argc < 1) { - rb_raise(rb_eArgError, "no receiver given"); + VALUE symbol = VM_BH_TO_SYMBOL(block_handler); + CALLER_SETUP_ARG(reg_cfp, calling, ci, ALLOW_HEAP_ARGV); + int flags = 0; + if (UNLIKELY(calling->heap_argv)) { +#if VM_ARGC_STACK_MAX < 0 + if (RARRAY_LEN(calling->heap_argv) < 1) { + rb_raise(rb_eArgError, "no receiver given"); + } +#endif + calling->recv = rb_ary_shift(calling->heap_argv); + // Modify stack to avoid cfp consistency error + reg_cfp->sp++; + reg_cfp->sp[-1] = reg_cfp->sp[-2]; + reg_cfp->sp[-2] = calling->recv; + flags |= VM_CALL_ARGS_SPLAT; } else { - VALUE symbol = VM_BH_TO_SYMBOL(block_handler); - CALLER_SETUP_ARG(reg_cfp, calling, ci); + if (calling->argc < 1) { + rb_raise(rb_eArgError, "no receiver given"); + } calling->recv = TOPN(--calling->argc); - return vm_call_symbol(ec, reg_cfp, calling, ci, symbol, 0); } + return vm_call_symbol(ec, reg_cfp, calling, ci, symbol, flags); } static VALUE @@ -4616,9 +4680,9 @@ vm_invoke_ifunc_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, VALUE val; int argc; const struct rb_captured_block *captured = VM_BH_TO_IFUNC_BLOCK(block_handler); - CALLER_SETUP_ARG(ec->cfp, calling, ci); + CALLER_SETUP_ARG(ec->cfp, calling, ci, ALLOW_HEAP_ARGV_KEEP_KWSPLAT); argc = calling->argc; - val = vm_yield_with_cfunc(ec, captured, captured->self, argc, STACK_ADDR_FROM_TOP(argc), calling->kw_splat, calling->block_handler, NULL); + val = vm_yield_with_cfunc(ec, captured, captured->self, CALLING_ARGC(calling), calling->heap_argv ? RARRAY_CONST_PTR(calling->heap_argv) : STACK_ADDR_FROM_TOP(argc), calling->kw_splat, calling->block_handler, NULL); POPN(argc); /* TODO: should put before C/yield? */ return val; } diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index c6f766f0fa..ce4f87649a 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -1896,6 +1896,8 @@ pub const SEND_MAX_CHAIN_DEPTH: i32 = 20; // up to 20 different offsets for case-when pub const CASE_WHEN_MAX_DEPTH: i32 = 20; +pub const MAX_SPLAT_LENGTH: i32 = 127; + // Codegen for setting an instance variable. // Preconditions: // - receiver is in REG0 @@ -5875,6 +5877,10 @@ fn gen_send_iseq( // all the remaining arguments. In the generated code // we test if this is true and if not side exit. argc = argc - 1 + array_length as i32 + remaining_opt as i32; + if argc + asm.ctx.get_stack_size() as i32 > MAX_SPLAT_LENGTH { + gen_counter_incr!(asm, send_splat_too_long); + return None; + } push_splat_args(array_length, asm); for _ in 0..remaining_opt { diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index bf90b233f7..444514b909 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -261,6 +261,7 @@ make_counters! { send_args_splat_cfunc_zuper, send_args_splat_cfunc_ruby2_keywords, send_iseq_splat_arity_error, + send_splat_too_long, send_iseq_ruby2_keywords, send_send_not_imm, send_send_wrong_args, |