diff options
Diffstat (limited to 'test/ruby/test_optimization.rb')
| -rw-r--r-- | test/ruby/test_optimization.rb | 272 |
1 files changed, 266 insertions, 6 deletions
diff --git a/test/ruby/test_optimization.rb b/test/ruby/test_optimization.rb index a7a0582dbb..5d16984eef 100644 --- a/test/ruby/test_optimization.rb +++ b/test/ruby/test_optimization.rb @@ -591,7 +591,6 @@ class TestRubyOptimization < Test::Unit::TestCase end def test_tailcall_not_to_grow_stack - omit 'currently JIT-ed code always creates a new stack frame' if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? bug16161 = '[ruby-core:94881]' tailcall("#{<<-"begin;"}\n#{<<~"end;"}") @@ -607,11 +606,11 @@ class TestRubyOptimization < Test::Unit::TestCase end class Bug10557 - def [](_) + def [](_, &) block_given? end - def []=(_, _) + def []=(_, _, &) block_given? end end @@ -749,6 +748,98 @@ class TestRubyOptimization < Test::Unit::TestCase end end + def test_peephole_array_freeze + code = "#{<<~'begin;'}\n#{<<~'end;'}" + begin; + [1].freeze + end; + iseq = RubyVM::InstructionSequence.compile(code) + insn = iseq.disasm + assert_match(/opt_ary_freeze/, insn) + assert_no_match(/duparray/, insn) + assert_no_match(/send/, insn) + assert_predicate([1].freeze, :frozen?) + assert_in_out_err([], <<~RUBY, [":ok"]) + class Array + prepend Module.new { + def freeze + :ok + end + } + end + p [1].freeze + RUBY + end + + def test_peephole_array_freeze_empty + code = "#{<<~'begin;'}\n#{<<~'end;'}" + begin; + [].freeze + end; + iseq = RubyVM::InstructionSequence.compile(code) + insn = iseq.disasm + assert_match(/opt_ary_freeze/, insn) + assert_no_match(/duparray/, insn) + assert_no_match(/send/, insn) + assert_predicate([].freeze, :frozen?) + assert_in_out_err([], <<~RUBY, [":ok"]) + class Array + prepend Module.new { + def freeze + :ok + end + } + end + p [].freeze + RUBY + end + + def test_peephole_hash_freeze + code = "#{<<~'begin;'}\n#{<<~'end;'}" + begin; + {a:1}.freeze + end; + iseq = RubyVM::InstructionSequence.compile(code) + insn = iseq.disasm + assert_match(/opt_hash_freeze/, insn) + assert_no_match(/duphash/, insn) + assert_no_match(/send/, insn) + assert_predicate([1].freeze, :frozen?) + assert_in_out_err([], <<~RUBY, [":ok"]) + class Hash + prepend Module.new { + def freeze + :ok + end + } + end + p({a:1}.freeze) + RUBY + end + + def test_peephole_hash_freeze_empty + code = "#{<<~'begin;'}\n#{<<~'end;'}" + begin; + {}.freeze + end; + iseq = RubyVM::InstructionSequence.compile(code) + insn = iseq.disasm + assert_match(/opt_hash_freeze/, insn) + assert_no_match(/duphash/, insn) + assert_no_match(/send/, insn) + assert_predicate([].freeze, :frozen?) + assert_in_out_err([], <<~RUBY, [":ok"]) + class Hash + prepend Module.new { + def freeze + :ok + end + } + end + p({}.freeze) + RUBY + end + def test_branch_condition_backquote bug = '[ruby-core:80740] [Bug #13444] redefined backquote should be called' class << self @@ -855,14 +946,14 @@ class TestRubyOptimization < Test::Unit::TestCase end def test_peephole_optimization_without_trace - assert_separately [], <<-END + assert_ruby_status [], <<-END RubyVM::InstructionSequence.compile_option = {trace_instruction: false} eval "def foo; 1.times{|(a), &b| nil && a}; end" END end def test_clear_unreachable_keyword_args - assert_separately [], <<-END, timeout: 60 + assert_ruby_status [], <<-END, timeout: 60 script = <<-EOS if true else @@ -989,7 +1080,7 @@ class TestRubyOptimization < Test::Unit::TestCase class Objtostring end - def test_objtostring + def test_objtostring_immediate assert_raise(NoMethodError){"#{BasicObject.new}"} assert_redefine_method('Symbol', 'to_s', <<-'end') assert_match %r{\A#<Symbol:0x[0-9a-f]+>\z}, "#{:foo}" @@ -1003,11 +1094,17 @@ class TestRubyOptimization < Test::Unit::TestCase assert_redefine_method('FalseClass', 'to_s', <<-'end') assert_match %r{\A#<FalseClass:0x[0-9a-f]+>\z}, "#{false}" end + end + + def test_objtostring_fixnum assert_redefine_method('Integer', 'to_s', <<-'end') (-1..10).each { |i| assert_match %r{\A#<Integer:0x[0-9a-f]+>\z}, "#{i}" } end + end + + def test_objtostring assert_equal "TestRubyOptimization::Objtostring", "#{Objtostring}" assert_match %r{\A#<Class:0x[0-9a-f]+>\z}, "#{Class.new}" assert_match %r{\A#<Module:0x[0-9a-f]+>\z}, "#{Module.new}" @@ -1015,4 +1112,167 @@ class TestRubyOptimization < Test::Unit::TestCase def o.to_s; 1; end assert_match %r{\A#<Object:0x[0-9a-f]+>\z}, "#{o}" end + + def test_opt_duparray_send_include_p + [ + 'x = :b; [:a, :b].include?(x)', + '@c = :b; [:a, :b].include?(@c)', + '@c = "b"; %i[a b].include?(@c.to_sym)', + '[:a, :b].include?(self) == false', + ].each do |code| + iseq = RubyVM::InstructionSequence.compile(code) + insn = iseq.disasm + assert_match(/opt_duparray_send/, insn) + assert_no_match(/\bduparray\b/, insn) + assert_equal(true, eval(code)) + end + + x, y = :b, :c + assert_equal(true, [:a, :b].include?(x)) + assert_equal(false, [:a, :b].include?(y)) + + assert_in_out_err([], <<~RUBY, ["1,2", "3,3", "1,2", "4,4"]) + class Array + prepend(Module.new do + def include?(i) + puts self.join(",") + # Modify self to prove that we are operating on a copy. + map! { i } + puts self.join(",") + end + end) + end + def x(i) + [1, 2].include?(i) + end + x(3) + x(4) + RUBY + + # Ensure raises happen correctly. + assert_in_out_err([], <<~RUBY, ["will raise", "int 1 not 3"]) + class Integer + undef_method :== + def == x + raise "int \#{self} not \#{x}" + end + end + x = 3 + puts "will raise" + begin + p [1, 2].include?(x) + rescue + puts $! + end + RUBY + end + + def test_opt_newarray_send_include_p + [ + 'b = :b; [:a, b].include?(:b)', + # Use Object.new to ensure that we get newarray rather than duparray. + 'value = 1; [Object.new, true, "true", 1].include?(value)', + 'value = 1; [Object.new, "1"].include?(value.to_s)', + '[Object.new, "1"].include?(self) == false', + ].each do |code| + iseq = RubyVM::InstructionSequence.compile(code) + insn = iseq.disasm + assert_match(/opt_newarray_send/, insn) + assert_no_match(/\bnewarray\b/, insn) + assert_equal(true, eval(code)) + end + + x, y = :b, :c + assert_equal(true, [:a, x].include?(x)) + assert_equal(false, [:a, x].include?(y)) + + assert_in_out_err([], <<~RUBY, ["1,3", "3,3", "1,4", "4,4"]) + class Array + prepend(Module.new do + def include?(i) + puts self.join(",") + # Modify self to prove that we are operating on a copy. + map! { i } + puts self.join(",") + end + end) + end + def x(i) + [1, i].include?(i) + end + x(3) + x(4) + RUBY + + # Ensure raises happen correctly. + assert_in_out_err([], <<~RUBY, ["will raise", "int 1 not 3"]) + class Integer + undef_method :== + def == x + raise "int \#{self} not \#{x}" + end + end + x = 3 + puts "will raise" + begin + p [1, x].include?(x) + rescue + puts $! + end + RUBY + end + + def test_opt_new_with_safe_navigation + payload = nil + assert_nil payload&.new + end + + def test_opt_new + pos_initialize = " + def initialize a, b + @a = a + @b = b + end + " + kw_initialize = " + def initialize a:, b: + @a = a + @b = b + end + " + kw_hash_initialize = " + def initialize a, **kw + @a = a + @b = kw[:b] + end + " + pos_prelude = "class OptNewFoo; #{pos_initialize}; end;" + kw_prelude = "class OptNewFoo; #{kw_initialize}; end;" + kw_hash_prelude = "class OptNewFoo; #{kw_hash_initialize}; end;" + [ + "#{pos_prelude} OptNewFoo.new 1, 2", + "#{pos_prelude} a = 1; b = 2; OptNewFoo.new a, b", + "#{pos_prelude} def optnew_foo(a, b) = OptNewFoo.new(a, b); optnew_foo 1, 2", + "#{pos_prelude} def optnew_foo(*a) = OptNewFoo.new(*a); optnew_foo 1, 2", + "#{pos_prelude} def optnew_foo(...) = OptNewFoo.new(...); optnew_foo 1, 2", + "#{kw_prelude} def optnew_foo(**a) = OptNewFoo.new(**a); optnew_foo a: 1, b: 2", + "#{kw_hash_prelude} def optnew_foo(*a, **b) = OptNewFoo.new(*a, **b); optnew_foo 1, b: 2", + ].each do |code| + iseq = RubyVM::InstructionSequence.compile(code) + insn = iseq.disasm + assert_match(/opt_new/, insn) + assert_match(/OptNewFoo:.+@a=1, @b=2/, iseq.eval.inspect) + # clean up to avoid warnings + Object.send :remove_const, :OptNewFoo + Object.remove_method :optnew_foo if defined?(optnew_foo) + end + [ + 'def optnew_foo(&) = OptNewFoo.new(&)', + 'def optnew_foo(a, ...) = OptNewFoo.new(a, ...)', + ].each do |code| + iseq = RubyVM::InstructionSequence.compile(code) + insn = iseq.disasm + assert_no_match(/opt_new/, insn) + end + end end |
