summaryrefslogtreecommitdiff
path: root/test/ruby/test_optimization.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/ruby/test_optimization.rb')
-rw-r--r--test/ruby/test_optimization.rb468
1 files changed, 430 insertions, 38 deletions
diff --git a/test/ruby/test_optimization.rb b/test/ruby/test_optimization.rb
index eebbc45e57..43795d150c 100644
--- a/test/ruby/test_optimization.rb
+++ b/test/ruby/test_optimization.rb
@@ -4,7 +4,8 @@ require 'objspace'
class TestRubyOptimization < Test::Unit::TestCase
def assert_redefine_method(klass, method, code, msg = nil)
- assert_separately([], <<-"end;")# do
+ assert_separately([], "#{<<-"begin;"}\n#{<<~"end;"}")
+ begin;
class #{klass}
undef #{method}
def #{method}(*args)
@@ -44,6 +45,26 @@ class TestRubyOptimization < Test::Unit::TestCase
assert_redefine_method('Integer', '%', 'assert_equal 7, 8 % 7')
end
+ def test_fixnum_lt
+ assert_equal true, 1 < 2
+ assert_redefine_method('Integer', '<', 'assert_equal 2, 1 < 2')
+ end
+
+ def test_fixnum_le
+ assert_equal true, 1 <= 2
+ assert_redefine_method('Integer', '<=', 'assert_equal 2, 1 <= 2')
+ end
+
+ def test_fixnum_gt
+ assert_equal false, 1 > 2
+ assert_redefine_method('Integer', '>', 'assert_equal 2, 1 > 2')
+ end
+
+ def test_fixnum_ge
+ assert_equal false, 1 >= 2
+ assert_redefine_method('Integer', '>=', 'assert_equal 2, 1 >= 2')
+ end
+
def test_float_plus
assert_equal 4.0, 2.0 + 2.0
assert_redefine_method('Float', '+', 'assert_equal 2.0, 2.0 + 2.0')
@@ -64,6 +85,26 @@ class TestRubyOptimization < Test::Unit::TestCase
assert_redefine_method('Float', '/', 'assert_equal 6.66, 4.2 / 6.66', "[Bug #9238]")
end
+ def test_float_lt
+ assert_equal true, 1.1 < 2.2
+ assert_redefine_method('Float', '<', 'assert_equal 2.2, 1.1 < 2.2')
+ end
+
+ def test_float_le
+ assert_equal true, 1.1 <= 2.2
+ assert_redefine_method('Float', '<=', 'assert_equal 2.2, 1.1 <= 2.2')
+ end
+
+ def test_float_gt
+ assert_equal false, 1.1 > 2.2
+ assert_redefine_method('Float', '>', 'assert_equal 2.2, 1.1 > 2.2')
+ end
+
+ def test_float_ge
+ assert_equal false, 1.1 >= 2.2
+ assert_redefine_method('Float', '>=', 'assert_equal 2.2, 1.1 >= 2.2')
+ end
+
def test_string_length
assert_equal 6, "string".length
assert_redefine_method('String', 'length', 'assert_nil "string".length')
@@ -104,6 +145,69 @@ class TestRubyOptimization < Test::Unit::TestCase
assert_redefine_method('String', 'freeze', 'assert_nil "foo".freeze')
end
+ def test_string_uminus
+ assert_same "foo".freeze, -"foo"
+ assert_redefine_method('String', '-@', 'assert_nil(-"foo")')
+ end
+
+ def test_array_min
+ assert_equal 1, [1, 2, 4].min
+ assert_redefine_method('Array', 'min', 'assert_nil([1, 2, 4].min)')
+ assert_redefine_method('Array', 'min', 'assert_nil([1 + 0, 2, 4].min)')
+ end
+
+ def test_array_max
+ assert_equal 4, [1, 2, 4].max
+ assert_redefine_method('Array', 'max', 'assert_nil([1, 2, 4].max)')
+ assert_redefine_method('Array', 'max', 'assert_nil([1 + 0, 2, 4].max)')
+ end
+
+ def test_trace_optimized_methods
+ bug14870 = "[ruby-core:87638]"
+ expected = [:-@, :max, :min, :+, :-, :*, :/, :%, :==, :<, :<=, :>, :>=, :<<,
+ :&, :|, :[], :[]=, :length, :empty?, :nil?, :succ, :!, :=~]
+ [:c_call, :c_return].each do |type|
+ methods = []
+ tp = TracePoint.new(type) { |tp| methods << tp.method_id }
+ tp.enable do
+ x = "a"; x = -x
+ [1].max
+ [1].min
+ x = 42 + 2
+ x = 42 - 2
+ x = 42 * 2
+ x = 42 / 2
+ x = 42 % 2
+ y = x == 42
+ y = x < 42
+ y = x <= 42
+ y = x > 42
+ y = x >= 42
+ x = x << 1
+ x = x & 1
+ x = x | 1
+ x = []; x[1]
+ x[1] = 2
+ x.length
+ x.empty?
+ x.nil?
+ x = 1; x.succ
+ !x
+ x = 'a'; x =~ /a/
+ x = y
+ end
+ assert_equal(expected, methods, bug14870)
+ end
+
+ methods = []
+ tp = TracePoint.new(:c_call, :c_return) { |tp| methods << tp.method_id }
+ tp.enable do
+ x = 1
+ x != 42
+ end
+ assert_equal([:!=, :==, :==, :!=], methods, bug14870)
+ end
+
def test_string_freeze_saves_memory
n = 16384
data = '.'.freeze
@@ -141,6 +245,16 @@ class TestRubyOptimization < Test::Unit::TestCase
assert_redefine_method('String', '<<', 'assert_equal "b", "a" << "b"')
end
+ def test_fixnum_and
+ assert_equal 1, 1&3
+ assert_redefine_method('Integer', '&', 'assert_equal 3, 1&3')
+ end
+
+ def test_fixnum_or
+ assert_equal 3, 1|3
+ assert_redefine_method('Integer', '|', 'assert_equal 1, 3|1')
+ end
+
def test_array_plus
assert_equal [1,2], [1]+[2]
assert_redefine_method('Array', '+', 'assert_equal [2], [1]+[2]')
@@ -178,20 +292,22 @@ class TestRubyOptimization < Test::Unit::TestCase
def test_hash_aref_with
h = { "foo" => 1 }
assert_equal 1, h["foo"]
- assert_redefine_method('Hash', '[]', <<-end)
+ assert_redefine_method('Hash', '[]', "#{<<-"begin;"}\n#{<<~"end;"}")
+ begin;
h = { "foo" => 1 }
assert_equal "foo", h["foo"]
- end
+ end;
end
def test_hash_aset_with
h = {}
assert_equal 1, h["foo"] = 1
- assert_redefine_method('Hash', '[]=', <<-end)
+ assert_redefine_method('Hash', '[]=', "#{<<-"begin;"}\n#{<<~"end;"}")
+ begin;
h = {}
assert_equal 1, h["foo"] = 1, "assignment always returns value set"
assert_nil h["foo"]
- end
+ end;
end
class MyObj
@@ -214,7 +330,7 @@ class TestRubyOptimization < Test::Unit::TestCase
unless file
loc, = caller_locations(1, 1)
file = loc.path
- line ||= loc.lineno
+ line ||= loc.lineno + 1
end
RubyVM::InstructionSequence.new("proc {|_|_.class_eval {#{src}}}",
file, (path || file), line,
@@ -230,7 +346,8 @@ class TestRubyOptimization < Test::Unit::TestCase
def test_tailcall
bug4082 = '[ruby-core:33289]'
- tailcall(<<-EOF)
+ tailcall("#{<<-"begin;"}\n#{<<~"end;"}")
+ begin;
def fact_helper(n, res)
if n == 1
res
@@ -241,14 +358,15 @@ class TestRubyOptimization < Test::Unit::TestCase
def fact(n)
fact_helper(n, 1)
end
- EOF
+ end;
assert_equal(9131, fact(3000).to_s.size, message(bug4082) {disasm(:fact_helper)})
end
def test_tailcall_with_block
bug6901 = '[ruby-dev:46065]'
- tailcall(<<-EOF)
+ tailcall("#{<<-"begin;"}\n#{<<~"end;"}")
+ begin;
def identity(val)
val
end
@@ -258,7 +376,7 @@ class TestRubyOptimization < Test::Unit::TestCase
identity(yield)
}
end
- EOF
+ end;
assert_equal(123, delay { 123 }.call, message(bug6901) {disasm(:delay)})
end
@@ -267,11 +385,12 @@ class TestRubyOptimization < Test::Unit::TestCase
end
def test_tailcall_inhibited_by_block
- tailcall(<<-EOF)
+ tailcall("#{<<-"begin;"}\n#{<<~"end;"}")
+ begin;
def yield_result
just_yield {:ok}
end
- EOF
+ end;
assert_equal(:ok, yield_result, message {disasm(:yield_result)})
end
@@ -286,7 +405,8 @@ class TestRubyOptimization < Test::Unit::TestCase
def test_tailcall_inhibited_by_rescue
bug12082 = '[ruby-core:73871] [Bug #12082]'
- tailcall(<<-'end;')
+ EnvUtil.suppress_warning {tailcall("#{<<-"begin;"}\n#{<<~"end;"}")}
+ begin;
def to_be_rescued
return do_raise
1 + 2
@@ -303,7 +423,8 @@ class TestRubyOptimization < Test::Unit::TestCase
def test_tailcall_symbol_block_arg
bug12565 = '[ruby-core:46065]'
- tailcall(<<-EOF)
+ tailcall("#{<<-"begin;"}\n#{<<~"end;"}")
+ begin;
def apply_one_and_two(&block)
yield(1, 2)
end
@@ -311,29 +432,31 @@ class TestRubyOptimization < Test::Unit::TestCase
def add_one_and_two
apply_one_and_two(&:+)
end
- EOF
+ end;
assert_equal(3, add_one_and_two,
message(bug12565) {disasm(:add_one_and_two)})
end
def test_tailcall_interrupted_by_sigint
bug12576 = 'ruby-core:76327'
- script = <<EOS
-RubyVM::InstructionSequence.compile_option = {
- :tailcall_optimization => true,
- :trace_instruction => false
-}
-
-eval <<EOF
-def foo
- foo
-end
-puts("start")
-STDOUT.flush
-foo
-EOF
-EOS
- status, err = EnvUtil.invoke_ruby([], "", true, true, {}) {
+ script = "#{<<-"begin;"}\n#{<<~'end;'}"
+ begin;
+ RubyVM::InstructionSequence.compile_option = {
+ :tailcall_optimization => true,
+ :trace_instruction => false
+ }
+
+ eval "#{<<~"begin;"}\n#{<<~'end;1'}"
+ begin;
+ def foo
+ foo
+ end
+ puts("start")
+ STDOUT.flush
+ foo
+ end;1
+ end;
+ status, _err = EnvUtil.invoke_ruby([], "", true, true, **{}) {
|in_p, out_p, err_p, pid|
in_p.write(script)
in_p.close
@@ -360,7 +483,7 @@ EOS
def test_tailcall_condition_block
bug = '[ruby-core:78015] [Bug #12905]'
- src = "#{<<-"begin;"}\n#{<<-"end;"}"
+ src = "#{<<-"begin;"}\n#{<<~"end;"}", __FILE__, nil, __LINE__+1
begin;
def run(current, final)
if current < final
@@ -372,13 +495,13 @@ EOS
end;
obj = Object.new
- self.class.tailcall(obj.singleton_class, src, tailcall: false)
+ self.class.tailcall(obj.singleton_class, *src, tailcall: false)
e = assert_raise(SystemStackError) {
obj.run(1, Float::INFINITY)
}
level = e.backtrace_locations.size
obj = Object.new
- self.class.tailcall(obj.singleton_class, src, tailcall: true)
+ self.class.tailcall(obj.singleton_class, *src, tailcall: true)
level *= 2
mesg = message {"#{bug}: #{$!.backtrace_locations.size} / #{level} stack levels"}
assert_nothing_raised(SystemStackError, mesg) {
@@ -386,6 +509,22 @@ EOS
}
end
+ def test_tailcall_not_to_grow_stack
+ skip 'currently JIT-ed code always creates a new stack frame' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled?
+ bug16161 = '[ruby-core:94881]'
+
+ tailcall("#{<<-"begin;"}\n#{<<~"end;"}")
+ begin;
+ def foo(n)
+ return :ok if n < 1
+ foo(n - 1)
+ end
+ end;
+ assert_nothing_raised(SystemStackError, bug16161) do
+ assert_equal(:ok, foo(1_000_000), bug16161)
+ end
+ end
+
class Bug10557
def [](_)
block_given?
@@ -409,7 +548,8 @@ EOS
end
def test_string_freeze_block
- assert_separately([], <<-"end;")# do
+ assert_separately([], "#{<<-"begin;"}\n#{<<~"end;"}")
+ begin;
class String
undef freeze
def freeze
@@ -422,7 +562,8 @@ EOS
end
def test_opt_case_dispatch
- code = <<-EOF
+ code = "#{<<-"begin;"}\n#{<<~"end;"}"
+ begin;
case foo
when "foo" then :foo
when true then true
@@ -435,7 +576,7 @@ EOS
else
:nomatch
end
- EOF
+ end;
check = {
'foo' => :foo,
true => true,
@@ -454,7 +595,8 @@ EOS
assert_equal :nomatch, eval("foo = :blah\n#{code}")
check.each do |foo, _|
klass = foo.class.to_s
- assert_separately([], <<-"end;") # do
+ assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}")
+ begin;
class #{klass}
undef ===
def ===(*args)
@@ -491,6 +633,40 @@ EOS
assert_ruby_status([], 'nil&.foo &&= false', bug11816)
end
+ def test_peephole_string_literal_range
+ code = "#{<<~"begin;"}\n#{<<~"end;"}"
+ begin;
+ case ver
+ when "2.0.0".."2.3.2" then :foo
+ when "1.8.0"..."1.8.8" then :bar
+ end
+ end;
+ [ true, false ].each do |opt|
+ iseq = RubyVM::InstructionSequence.compile(code,
+ frozen_string_literal: opt)
+ insn = iseq.disasm
+ assert_match %r{putobject\s+#{Regexp.quote('"1.8.0"..."1.8.8"')}}, insn
+ assert_match %r{putobject\s+#{Regexp.quote('"2.0.0".."2.3.2"')}}, insn
+ assert_no_match(/putstring/, insn)
+ assert_no_match(/newrange/, insn)
+ end
+ end
+
+ def test_peephole_dstr
+ code = "#{<<~'begin;'}\n#{<<~'end;'}"
+ begin;
+ exp = -'a'
+ z = 'a'
+ [exp, -"#{z}"]
+ end;
+ [ false, true ].each do |fsl|
+ iseq = RubyVM::InstructionSequence.compile(code,
+ frozen_string_literal: fsl)
+ assert_same(*iseq.eval,
+ "[ruby-core:85542] [Bug #14475] fsl: #{fsl}")
+ end
+ end
+
def test_branch_condition_backquote
bug = '[ruby-core:80740] [Bug #13444] redefined backquote should be called'
class << self
@@ -541,4 +717,220 @@ EOS
def t; if false; case 42; when s {}; end; end; end
end;
end
+
+ def bptest_yield &b
+ yield
+ end
+
+ def bptest_yield_pass &b
+ bptest_yield(&b)
+ end
+
+ def bptest_bp_value &b
+ b
+ end
+
+ def bptest_bp_pass_bp_value &b
+ bptest_bp_value(&b)
+ end
+
+ def bptest_binding &b
+ binding
+ end
+
+ def bptest_set &b
+ b = Proc.new{2}
+ end
+
+ def test_block_parameter
+ assert_equal(1, bptest_yield{1})
+ assert_equal(1, bptest_yield_pass{1})
+ assert_equal(1, send(:bptest_yield){1})
+
+ assert_equal(Proc, bptest_bp_value{}.class)
+ assert_equal nil, bptest_bp_value
+ assert_equal(Proc, bptest_bp_pass_bp_value{}.class)
+ assert_equal nil, bptest_bp_pass_bp_value
+
+ assert_equal Proc, bptest_binding{}.local_variable_get(:b).class
+
+ assert_equal 2, bptest_set{1}.call
+ end
+
+ def test_block_parameter_should_not_create_objects
+ assert_separately [], <<-END
+ def foo &b
+ end
+ h1 = {}; h2 = {}
+ ObjectSpace.count_objects(h1) # rehearsal
+ GC.start; GC.disable # to disable GC while foo{}
+ ObjectSpace.count_objects(h1)
+ foo{}
+ ObjectSpace.count_objects(h2)
+
+ assert_equal 0, h2[:T_DATA] - h1[:T_DATA] # Proc is T_DATA
+ END
+ end
+
+ def test_peephole_optimization_without_trace
+ assert_separately [], <<-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
+ script = <<-EOS
+ if true
+ else
+ foo(k1:1)
+ end
+ EOS
+ GC.stress = true
+ 30.times{
+ RubyVM::InstructionSequence.compile(script)
+ }
+ END
+ end
+
+ def test_callinfo_unreachable_path
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ iseq = RubyVM::InstructionSequence.compile("if false; foo(bar: :baz); else :ok end")
+ bin = iseq.to_binary
+ iseq = RubyVM::InstructionSequence.load_from_binary(bin)
+ assert_instance_of(RubyVM::InstructionSequence, iseq)
+ assert_equal(:ok, iseq.eval)
+ end;
+ end
+
+ def test_side_effect_in_popped_splat
+ bug = '[ruby-core:84340] [Bug #14201]'
+ eval("{**(bug = nil; {})};42")
+ assert_nil(bug)
+
+ bug = '[ruby-core:85486] [Bug #14459]'
+ h = {}
+ assert_equal(bug, eval('{ok: 42, **h}; bug'))
+ assert_equal(:ok, eval('{ok: bug = :ok, **h}; bug'))
+ assert_empty(h)
+ end
+
+ def test_overwritten_blockparam
+ obj = Object.new
+ def obj.a(&block)
+ block = 1
+ return :ok if block
+ :ng
+ end
+ assert_equal(:ok, obj.a())
+ end
+
+ def test_blockparam_in_rescue
+ obj = Object.new
+ def obj.foo(&b)
+ raise
+ rescue
+ b.call
+ end
+ result = nil
+ assert_equal(42, obj.foo {result = 42})
+ assert_equal(42, result)
+ end
+
+ def test_unconditional_branch_to_leave_block
+ assert_valid_syntax("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ tap {true || tap {}}
+ end;
+ end
+
+ def test_jump_elimination_with_optimized_out_block
+ x = Object.new
+ def x.bug(obj)
+ if obj || obj
+ obj = obj
+ else
+ raise "[ruby-core:87830] [Bug #14897]"
+ end
+ obj
+ end
+ assert_equal(:ok, x.bug(:ok))
+ end
+
+ def test_jump_elimination_with_optimized_out_block_2
+ x = Object.new
+ def x.bug
+ a = "aaa"
+ ok = :NG
+ if a == "bbb" || a == "ccc" then
+ a = a
+ else
+ ok = :ok
+ end
+ ok
+ end
+ assert_equal(:ok, x.bug)
+ end
+
+ def test_peephole_jump_after_newarray
+ i = 0
+ %w(1) || 2 while (i += 1) < 100
+ assert_equal(100, i)
+ end
+
+ def test_optimized_empty_ensure
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}", timeout: 10)
+ begin;
+ assert_raise(RuntimeError) {
+ begin raise ensure nil if nil end
+ }
+ end;
+ end
+
+ def test_optimized_rescue
+ assert_in_out_err("", "#{<<~"begin;"}\n#{<<~'end;'}", [], /END \(RuntimeError\)/)
+ begin;
+ if false
+ begin
+ require "some_mad_stuff"
+ rescue LoadError
+ puts "no mad stuff loaded"
+ end
+ end
+
+ raise "END"
+ end;
+ end
+
+ class Objtostring
+ end
+
+ def test_objtostring
+ assert_raise(NoMethodError){"#{BasicObject.new}"}
+ assert_redefine_method('Symbol', 'to_s', <<-'end')
+ assert_match %r{\A#<Symbol:0x[0-9a-f]+>\z}, "#{:foo}"
+ end
+ assert_redefine_method('NilClass', 'to_s', <<-'end')
+ assert_match %r{\A#<NilClass:0x[0-9a-f]+>\z}, "#{nil}"
+ end
+ assert_redefine_method('TrueClass', 'to_s', <<-'end')
+ assert_match %r{\A#<TrueClass:0x[0-9a-f]+>\z}, "#{true}"
+ end
+ assert_redefine_method('FalseClass', 'to_s', <<-'end')
+ assert_match %r{\A#<FalseClass:0x[0-9a-f]+>\z}, "#{false}"
+ end
+ assert_redefine_method('Integer', 'to_s', <<-'end')
+ (-1..10).each { |i|
+ assert_match %r{\A#<Integer:0x[0-9a-f]+>\z}, "#{i}"
+ }
+ end
+ 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}"
+ o = Object.new
+ def o.to_s; 1; end
+ assert_match %r{\A#<Object:0x[0-9a-f]+>\z}, "#{o}"
+ end
end