mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00

if catch_except_p is FALSE. If catch_except_p is TRUE, stack values should be on VM's stack when exception is thrown and the JIT-ed frame is re-executed by VM's exception handler. If it's FALSE, the JIT-ed frame won't be re-executed and don't need to keep values on VM's stack. Using local variables allows us to reduce cfp->sp motion. Moving cfp->sp is needed only for insns whose handles_frame? is false. So it improves performance. _mjit_compile_insn.erb: Prepare `stack_size` variable for GET_SP, STACK_ADDR_FROM_TOP, TOPN macros. Share pc and sp motion partial view. Use cancel handler created in mjit_compile.c. _mjit_compile_send.erb: ditto. Also, when iseq->body->catch_except_p is TRUE, this stops to call mjit_exec directly. I described the reason in vm_insnhelper.h's comment for EXEC_EC_CFP. _mjit_compile_pc_and_sp.erb: Shared logic for moving sp and pc. As you can see from thsi file, when status->local_stack_p is TRUE and insn.handles_frame? is false, moving sp is skipped. But if insn.handles_frame? is true, values should be rolled back to VM's stack. common.mk: add dependency for the file _mjit_compile_insn_body.erb: Set sp value before canceling JIT on DISPATCH_ORIGINAL_INSN. Replace GET_SP, STACK_ADDR_FROM_TOP, TOPN macros for the case ocal_stack_p is TRUE and insn.handles_frame? is false. In that case, values are not available on VM's stack and those macros should be replaced. mjit_compile.inc.erb: updated comments of macros which are supported by JIT compiler. All references to `cfp->sp` should be replaced and thus INC_SP, SET_SV, PUSH are no longer supported for now, because they are not used now. vm_exec.h: moved EXEC_EC_CFP definition to vm_insnhelper.h because it's tighly coupled to CALL_METHOD. vm_insnhelper.h: Have revised EXEC_EC_CFP definition moved from vm_exec.h. Now it triggers mjit_exec for VM, and has the guard for catch_except_p on JIT-ed code. See comments for details. CALL_METHOD delegates triggering mjit_exec to EXEC_EC_CFP. insns.def: Stopped using EXEC_EC_CFP for the case we don't want to trigger mjit_exec. Those insns (defineclass, opt_call_c_function) are not supported by JIT and it's safe to use RESTORE_REGS(), NEXT_INSN(). expandarray is changed to pass GET_SP() to replace the macro in _mjit_compile_insn_body.erb. vm_insnhelper.c: change to take sp for the above reason. [close https://github.com/ruby/ruby/pull/1828] This patch resurrects the performance which was attached in [Feature #14235]. * Benchmark Optcarrot (with configuration for benchmark_driver.gem) https://github.com/benchmark-driver/optcarrot $ benchmark-driver benchmark.yml --verbose 1 --rbenv 'before;before+JIT::before,--jit;after;after+JIT::after,--jit' --repeat-count 10 before: ruby 2.6.0dev (2018-03-04 trunk 62652) [x86_64-linux] before+JIT: ruby 2.6.0dev (2018-03-04 trunk 62652) +JIT [x86_64-linux] after: ruby 2.6.0dev (2018-03-04 local-variable.. 62652) [x86_64-linux] last_commit=mjit_compile.c: use local variables for stack after+JIT: ruby 2.6.0dev (2018-03-04 local-variable.. 62652) +JIT [x86_64-linux] last_commit=mjit_compile.c: use local variables for stack Calculating ------------------------------------- before before+JIT after after+JIT optcarrot 53.552 59.680 53.697 63.358 fps Comparison: optcarrot after+JIT: 63.4 fps before+JIT: 59.7 fps - 1.06x slower after: 53.7 fps - 1.18x slower before: 53.6 fps - 1.18x slower git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@62655 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
550 lines
13 KiB
Ruby
550 lines
13 KiB
Ruby
# frozen_string_literal: true
|
|
require 'test/unit'
|
|
require_relative '../lib/jit_support'
|
|
|
|
# Test for --jit option
|
|
class TestJIT < Test::Unit::TestCase
|
|
include JITSupport
|
|
# Ensure all supported insns can be compiled. Only basic tests are included.
|
|
# TODO: ensure --dump=insns includes the expected insn
|
|
|
|
def setup
|
|
unless JITSupport.supported?
|
|
skip 'JIT seems not supported on this platform'
|
|
end
|
|
end
|
|
|
|
def test_compile_insn_nop
|
|
assert_compile_once('nil rescue true', result_inspect: 'nil')
|
|
end
|
|
|
|
def test_compile_insn_local
|
|
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1')
|
|
begin;
|
|
foo = 1
|
|
foo
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_blockparam
|
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '3', success_count: 2)
|
|
begin;
|
|
def foo(&b)
|
|
a = b
|
|
b = 2
|
|
a.call + 2
|
|
end
|
|
|
|
print foo { 1 }
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_getblockparamproxy
|
|
skip "support this in mjit_compile"
|
|
end
|
|
|
|
def test_compile_insn_getspecial
|
|
assert_compile_once('$1', result_inspect: 'nil')
|
|
end
|
|
|
|
def test_compile_insn_setspecial
|
|
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true')
|
|
begin;
|
|
true if nil.nil?..nil.nil?
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_instancevariable
|
|
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1')
|
|
begin;
|
|
@foo = 1
|
|
@foo
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_classvariable
|
|
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1')
|
|
begin;
|
|
@@foo = 1
|
|
@@foo
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_constant
|
|
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1')
|
|
begin;
|
|
FOO = 1
|
|
FOO
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_global
|
|
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1')
|
|
begin;
|
|
$foo = 1
|
|
$foo
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_putnil
|
|
assert_compile_once('nil', result_inspect: 'nil')
|
|
end
|
|
|
|
def test_compile_insn_putself
|
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hello', success_count: 1)
|
|
begin;
|
|
proc { print "hello" }.call
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_putobject
|
|
assert_compile_once('0', result_inspect: '0') # putobject_OP_INT2FIX_O_0_C_
|
|
assert_compile_once('1', result_inspect: '1') # putobject_OP_INT2FIX_O_1_C_
|
|
assert_compile_once('2', result_inspect: '2')
|
|
end
|
|
|
|
def test_compile_insn_putspecialobject_putiseq
|
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hello', success_count: 2)
|
|
begin;
|
|
print proc {
|
|
def method_definition
|
|
'hello'
|
|
end
|
|
method_definition
|
|
}.call
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_putstring_concatstrings_tostring
|
|
assert_compile_once('"a#{}b" + "c"', result_inspect: '"abc"')
|
|
end
|
|
|
|
def test_compile_insn_freezestring
|
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~'end;'}", stdout: 'true', success_count: 1)
|
|
begin;
|
|
# frozen_string_literal: true
|
|
print proc { "#{true}".frozen? }.call
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_toregexp
|
|
assert_compile_once('/#{true}/ =~ "true"', result_inspect: '0')
|
|
end
|
|
|
|
def test_compile_insn_newarray
|
|
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '[1, 2, 3]')
|
|
begin;
|
|
a, b, c = 1, 2, 3
|
|
[a, b, c]
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_intern_duparray
|
|
assert_compile_once('[:"#{0}"] + [1,2,3]', result_inspect: '[:"0", 1, 2, 3]')
|
|
end
|
|
|
|
def test_compile_insn_expandarray
|
|
assert_compile_once('y = [ true, false, nil ]; x, = y; x', result_inspect: 'true')
|
|
end
|
|
|
|
def test_compile_insn_concatarray
|
|
assert_compile_once('["t", "r", *x = "u", "e"].join', result_inspect: '"true"')
|
|
end
|
|
|
|
def test_compile_insn_splatarray
|
|
assert_compile_once('[*(1..2)]', result_inspect: '[1, 2]')
|
|
end
|
|
|
|
def test_compile_insn_newhash
|
|
assert_compile_once('a = 1; { a: a }', result_inspect: '{:a=>1}')
|
|
end
|
|
|
|
def test_compile_insn_newrange
|
|
assert_compile_once('a = 1; 0..a', result_inspect: '0..1')
|
|
end
|
|
|
|
def test_compile_insn_pop
|
|
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1')
|
|
begin;
|
|
a = false
|
|
b = 1
|
|
a || b
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_dup
|
|
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '3')
|
|
begin;
|
|
a = 1
|
|
a&.+(2)
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_dupn
|
|
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true')
|
|
begin;
|
|
klass = Class.new
|
|
klass::X ||= true
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_swap_topn
|
|
assert_compile_once('{}["true"] = true', result_inspect: 'true')
|
|
end
|
|
|
|
def test_compile_insn_reverse
|
|
assert_compile_once('q, (w, e), r = 1, [2, 3], 4; e == 3', result_inspect: 'true')
|
|
end
|
|
|
|
def test_compile_insn_reput
|
|
skip "write test"
|
|
end
|
|
|
|
def test_compile_insn_setn
|
|
assert_compile_once('[nil][0] = 1', result_inspect: '1')
|
|
end
|
|
|
|
def test_compile_insn_adjuststack
|
|
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true')
|
|
begin;
|
|
x = [true]
|
|
x[0] ||= nil
|
|
x[0]
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_defined
|
|
assert_compile_once('defined?(a)', result_inspect: 'nil')
|
|
end
|
|
|
|
def test_compile_insn_checkkeyword
|
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'true', success_count: 1)
|
|
begin;
|
|
def test(x: rand)
|
|
x
|
|
end
|
|
print test(x: true)
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_tracecoverage
|
|
skip "write test"
|
|
end
|
|
|
|
def test_compile_insn_defineclass
|
|
skip "support this in mjit_compile (low priority)"
|
|
end
|
|
|
|
def test_compile_insn_send
|
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '1', success_count: 2)
|
|
begin;
|
|
print proc { yield_self { 1 } }.call
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_opt_str_freeze
|
|
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"foo"')
|
|
begin;
|
|
'foo'.freeze
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_opt_str_uminus
|
|
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"bar"')
|
|
begin;
|
|
-'bar'
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_opt_newarray_max
|
|
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '2')
|
|
begin;
|
|
a = 1
|
|
b = 2
|
|
[a, b].max
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_opt_newarray_min
|
|
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1')
|
|
begin;
|
|
a = 1
|
|
b = 2
|
|
[a, b].min
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_opt_send_without_block
|
|
assert_compile_once('print', result_inspect: 'nil')
|
|
end
|
|
|
|
def test_compile_insn_invokesuper
|
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '3', success_count: 4)
|
|
begin;
|
|
mod = Module.new {
|
|
def test
|
|
super + 2
|
|
end
|
|
}
|
|
klass = Class.new {
|
|
prepend mod
|
|
def test
|
|
1
|
|
end
|
|
}
|
|
print klass.new.test
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_invokeblock_leave
|
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '2', success_count: 2)
|
|
begin;
|
|
def foo
|
|
yield
|
|
end
|
|
print foo { 2 }
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_throw
|
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '4', success_count: 2)
|
|
begin;
|
|
def test
|
|
proc do
|
|
if 1+1 == 1
|
|
return 3
|
|
else
|
|
return 4
|
|
end
|
|
5
|
|
end.call
|
|
end
|
|
print test
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_jump_branchif
|
|
assert_compile_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: 'nil')
|
|
begin;
|
|
a = false
|
|
1 + 1 while false
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_branchunless
|
|
assert_compile_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '1')
|
|
begin;
|
|
a = true
|
|
if a
|
|
1
|
|
else
|
|
2
|
|
end
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_branchnil
|
|
assert_compile_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '3')
|
|
begin;
|
|
a = 2
|
|
a&.+(1)
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_branchiftype
|
|
assert_compile_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '"42"')
|
|
begin;
|
|
a = '2'
|
|
"4#{a}"
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_inlinecache
|
|
assert_compile_once('Struct', result_inspect: 'Struct')
|
|
end
|
|
|
|
def test_compile_insn_once
|
|
assert_compile_once('/#{true}/o =~ "true" && $~.to_a', result_inspect: '["true"]')
|
|
end
|
|
|
|
def test_compile_insn_checkmatch_opt_case_dispatch
|
|
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"world"')
|
|
begin;
|
|
case 'hello'
|
|
when /hello/
|
|
'world'
|
|
end
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_opt_calc
|
|
assert_compile_once('4 + 2 - ((2 * 3 / 2) % 2)', result_inspect: '5')
|
|
assert_compile_once('4 + 2', result_inspect: '6')
|
|
end
|
|
|
|
def test_compile_insn_opt_cmp
|
|
assert_compile_once('(1 == 1) && (1 != 2)', result_inspect: 'true')
|
|
end
|
|
|
|
def test_compile_insn_opt_rel
|
|
assert_compile_once('1 < 2 && 1 <= 1 && 2 > 1 && 1 >= 1', result_inspect: 'true')
|
|
end
|
|
|
|
def test_compile_insn_opt_ltlt
|
|
assert_compile_once('[1] << 2', result_inspect: '[1, 2]')
|
|
end
|
|
|
|
def test_compile_insn_opt_aref
|
|
# optimized call (optimized JIT) -> send call
|
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '21', success_count: 2, min_calls: 1)
|
|
begin;
|
|
obj = Object.new
|
|
def obj.[](h)
|
|
h
|
|
end
|
|
|
|
block = proc { |h| h[1] }
|
|
print block.call({ 1 => 2 })
|
|
print block.call(obj)
|
|
end;
|
|
|
|
# send call -> optimized call (send JIT) -> optimized call
|
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '122', success_count: 1, min_calls: 2)
|
|
begin;
|
|
obj = Object.new
|
|
def obj.[](h)
|
|
h
|
|
end
|
|
|
|
block = proc { |h| h[1] }
|
|
print block.call(obj)
|
|
print block.call({ 1 => 2 })
|
|
print block.call({ 1 => 2 })
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_opt_aset
|
|
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '5')
|
|
begin;
|
|
hash = { '1' => 2 }
|
|
(hash['2'] = 2) + (hash[1.to_s] = 3)
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_opt_length_size
|
|
assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '4')
|
|
begin;
|
|
array = [1, 2]
|
|
array.length + array.size
|
|
end;
|
|
end
|
|
|
|
def test_compile_insn_opt_empty_p
|
|
assert_compile_once('[].empty?', result_inspect: 'true')
|
|
end
|
|
|
|
def test_compile_insn_opt_succ
|
|
assert_compile_once('1.succ', result_inspect: '2')
|
|
end
|
|
|
|
def test_compile_insn_opt_not
|
|
assert_compile_once('!!true', result_inspect: 'true')
|
|
end
|
|
|
|
def test_compile_insn_opt_regexpmatch1
|
|
assert_compile_once("/true/ =~ 'true'", result_inspect: '0')
|
|
end
|
|
|
|
def test_compile_insn_opt_regexpmatch2
|
|
assert_compile_once("'true' =~ /true/", result_inspect: '0')
|
|
end
|
|
|
|
def test_compile_insn_opt_call_c_function
|
|
skip "support this in opt_call_c_function (low priority)"
|
|
end
|
|
|
|
def test_jit_output
|
|
out, err = eval_with_jit('5.times { puts "MJIT" }', verbose: 1, min_calls: 5)
|
|
assert_equal("MJIT\n" * 5, out)
|
|
assert_match(/^#{JIT_SUCCESS_PREFIX}: block in <main>@-e:1 -> .+_ruby_mjit_p\d+u\d+\.c$/, err)
|
|
assert_match(/^Successful MJIT finish$/, err)
|
|
end
|
|
|
|
def test_local_stack_on_exception
|
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '3', success_count: 2)
|
|
begin;
|
|
def b
|
|
raise
|
|
rescue
|
|
2
|
|
end
|
|
|
|
def a
|
|
# Calling #b should be vm_exec, not direct mjit_exec.
|
|
# Otherwise `1` on local variable would be purged.
|
|
1 + b
|
|
end
|
|
|
|
print a
|
|
end;
|
|
end
|
|
|
|
def test_local_stack_with_sp_motion_by_blockargs
|
|
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '1', success_count: 2)
|
|
begin;
|
|
def b(base)
|
|
1
|
|
end
|
|
|
|
# This method is simple enough to have false in catch_except_p.
|
|
# So local_stack_p would be true in JIT compiler.
|
|
def a
|
|
m = method(:b)
|
|
|
|
# ci->flag has VM_CALL_ARGS_BLOCKARG and cfp->sp is moved in vm_caller_setup_arg_block.
|
|
# So, for this send insn, JIT-ed code should use cfp->sp instead of local variables for stack.
|
|
Module.module_eval(&m)
|
|
end
|
|
|
|
print a
|
|
end;
|
|
end
|
|
|
|
private
|
|
|
|
# The shortest way to test one proc
|
|
def assert_compile_once(script, result_inspect:)
|
|
if script.match?(/\A\n.+\n\z/m)
|
|
script = script.gsub(/^/, ' ')
|
|
else
|
|
script = " #{script} "
|
|
end
|
|
assert_eval_with_jit("p proc {#{script}}.call", stdout: "#{result_inspect}\n", success_count: 1)
|
|
end
|
|
|
|
# Shorthand for normal test cases
|
|
def assert_eval_with_jit(script, stdout: nil, success_count:, min_calls: 1)
|
|
out, err = eval_with_jit(script, verbose: 1, min_calls: min_calls)
|
|
actual = err.scan(/^#{JIT_SUCCESS_PREFIX}:/).size
|
|
assert_equal(
|
|
success_count, actual,
|
|
"Expected #{success_count} times of JIT success, but succeeded #{actual} times.\n\n"\
|
|
"script:\n#{code_block(script)}\nstderr:\n#{code_block(err)}",
|
|
)
|
|
if stdout
|
|
assert_equal(stdout, out, "Expected stdout #{out.inspect} to match #{stdout.inspect} with script:\n#{code_block(script)}")
|
|
end
|
|
end
|
|
|
|
# Run Ruby script with --jit-wait (Synchronous JIT compilation).
|
|
# Returns [stdout, stderr]
|
|
def eval_with_jit(script, **opts)
|
|
stdout, stderr, status = super
|
|
assert_equal(true, status.success?, "Failed to run script with JIT:\n#{code_block(script)}\nstdout:\n#{code_block(stdout)}\nstderr:\n#{code_block(stderr)}")
|
|
[stdout, stderr]
|
|
end
|
|
|
|
def code_block(code)
|
|
"```\n#{code}\n```\n\n"
|
|
end
|
|
end
|