2018-02-08 07:40:33 -05:00
# frozen_string_literal: true
require 'test/unit'
2018-02-22 10:11:12 -05:00
require_relative '../lib/jit_support'
2018-02-11 22:33:00 -05:00
# Test for --jit option
class TestJIT < Test :: Unit :: TestCase
2018-02-22 10:11:12 -05:00
include JITSupport
2018-02-10 11:09:55 -05:00
# Ensure all supported insns can be compiled. Only basic tests are included.
# TODO: ensure --dump=insns includes the expected insn
2018-02-08 08:56:32 -05:00
2018-02-11 23:00:45 -05:00
def setup
2018-02-22 10:11:12 -05:00
unless JITSupport . supported?
2018-02-11 23:00:45 -05:00
skip 'JIT seems not supported on this platform'
end
end
2018-02-11 22:33:00 -05:00
def test_compile_insn_nop
2018-02-10 11:09:55 -05:00
assert_compile_once ( 'nil rescue true' , result_inspect : 'nil' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_local
assert_compile_once ( " #{ << ~ " begin; " } \n #{ << ~ " end; " } " , result_inspect : '1' )
begin ;
2018-02-10 11:09:55 -05:00
foo = 1
foo
2018-02-11 22:33:00 -05:00
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_blockparam
assert_eval_with_jit ( " #{ << ~ " begin; " } \n #{ << ~ " end; " } " , stdout : '3' , success_count : 2 )
begin ;
2018-02-10 11:09:55 -05:00
def foo ( & b )
a = b
b = 2
a . call + 2
end
print foo { 1 }
2018-02-11 22:33:00 -05:00
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_getblockparamproxy
skip " support this in mjit_compile "
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_getspecial
2018-02-10 11:09:55 -05:00
assert_compile_once ( '$1' , result_inspect : 'nil' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_setspecial
assert_compile_once ( " #{ << ~ " begin; " } \n #{ << ~ " end; " } " , result_inspect : 'true' )
begin ;
2018-02-10 11:09:55 -05:00
true if nil . nil? .. nil . nil?
2018-02-11 22:33:00 -05:00
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_instancevariable
assert_compile_once ( " #{ << ~ " begin; " } \n #{ << ~ " end; " } " , result_inspect : '1' )
begin ;
2018-02-10 11:09:55 -05:00
@foo = 1
@foo
2018-02-11 22:33:00 -05:00
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_classvariable
assert_compile_once ( " #{ << ~ " begin; " } \n #{ << ~ " end; " } " , result_inspect : '1' )
begin ;
2018-02-10 11:09:55 -05:00
@@foo = 1
@@foo
2018-02-11 22:33:00 -05:00
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_constant
assert_compile_once ( " #{ << ~ " begin; " } \n #{ << ~ " end; " } " , result_inspect : '1' )
begin ;
2018-02-10 11:09:55 -05:00
FOO = 1
FOO
2018-02-11 22:33:00 -05:00
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_global
assert_compile_once ( " #{ << ~ " begin; " } \n #{ << ~ " end; " } " , result_inspect : '1' )
begin ;
2018-02-10 11:09:55 -05:00
$foo = 1
$foo
2018-02-11 22:33:00 -05:00
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_putnil
2018-02-10 11:09:55 -05:00
assert_compile_once ( 'nil' , result_inspect : 'nil' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_putself
assert_eval_with_jit ( " #{ << ~ " begin; " } \n #{ << ~ " end; " } " , stdout : 'hello' , success_count : 1 )
begin ;
2018-02-10 11:09:55 -05:00
proc { print " hello " } . call
2018-02-11 22:33:00 -05:00
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_putobject
2018-02-10 11:09:55 -05:00
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' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_putspecialobject_putiseq
assert_eval_with_jit ( " #{ << ~ " begin; " } \n #{ << ~ " end; " } " , stdout : 'hello' , success_count : 2 )
begin ;
2018-02-10 11:09:55 -05:00
print proc {
def method_definition
'hello'
end
method_definition
} . call
2018-02-11 22:33:00 -05:00
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_putstring_concatstrings_tostring
2018-02-10 11:09:55 -05:00
assert_compile_once ( '"a#{}b" + "c"' , result_inspect : '"abc"' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_freezestring
assert_eval_with_jit ( " #{ << ~ " begin; " } \n #{ << ~ 'end;' } " , stdout : 'true' , success_count : 1 )
begin ;
2018-02-10 11:09:55 -05:00
# frozen_string_literal: true
print proc { " #{ true } " . frozen? } . call
2018-02-11 22:33:00 -05:00
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_toregexp
2018-02-10 11:09:55 -05:00
assert_compile_once ( '/#{true}/ =~ "true"' , result_inspect : '0' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
mjit_compile.c: use local variables for stack
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
2018-03-04 02:04:40 -05:00
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
2018-02-10 11:09:55 -05:00
assert_compile_once ( '[:"#{0}"] + [1,2,3]' , result_inspect : '[:"0", 1, 2, 3]' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_expandarray
2018-02-10 11:09:55 -05:00
assert_compile_once ( 'y = [ true, false, nil ]; x, = y; x' , result_inspect : 'true' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_concatarray
2018-02-10 11:09:55 -05:00
assert_compile_once ( '["t", "r", *x = "u", "e"].join' , result_inspect : '"true"' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_splatarray
2018-02-10 11:09:55 -05:00
assert_compile_once ( '[*(1..2)]' , result_inspect : '[1, 2]' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_newhash
2018-02-10 11:09:55 -05:00
assert_compile_once ( 'a = 1; { a: a }' , result_inspect : '{:a=>1}' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_newrange
2018-02-10 11:09:55 -05:00
assert_compile_once ( 'a = 1; 0..a' , result_inspect : '0..1' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_pop
assert_compile_once ( " #{ << ~ " begin; " } \n #{ << ~ " end; " } " , result_inspect : '1' )
begin ;
2018-02-10 11:09:55 -05:00
a = false
b = 1
a || b
2018-02-11 22:33:00 -05:00
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_dup
assert_compile_once ( " #{ << ~ " begin; " } \n #{ << ~ " end; " } " , result_inspect : '3' )
begin ;
2018-02-10 11:09:55 -05:00
a = 1
a & . + ( 2 )
2018-02-11 22:33:00 -05:00
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_dupn
assert_compile_once ( " #{ << ~ " begin; " } \n #{ << ~ " end; " } " , result_inspect : 'true' )
begin ;
2018-02-10 11:09:55 -05:00
klass = Class . new
klass :: X || = true
2018-02-11 22:33:00 -05:00
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_swap_topn
2018-02-10 11:09:55 -05:00
assert_compile_once ( '{}["true"] = true' , result_inspect : 'true' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_reverse
2018-02-10 11:09:55 -05:00
assert_compile_once ( 'q, (w, e), r = 1, [2, 3], 4; e == 3' , result_inspect : 'true' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_reput
skip " write test "
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_setn
2018-02-10 11:09:55 -05:00
assert_compile_once ( '[nil][0] = 1' , result_inspect : '1' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_adjuststack
assert_compile_once ( " #{ << ~ " begin; " } \n #{ << ~ " end; " } " , result_inspect : 'true' )
begin ;
2018-02-10 11:09:55 -05:00
x = [ true ]
x [ 0 ] || = nil
x [ 0 ]
2018-02-11 22:33:00 -05:00
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_defined
2018-02-10 11:09:55 -05:00
assert_compile_once ( 'defined?(a)' , result_inspect : 'nil' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_checkkeyword
assert_eval_with_jit ( " #{ << ~ " begin; " } \n #{ << ~ " end; " } " , stdout : 'true' , success_count : 1 )
begin ;
2018-02-10 11:09:55 -05:00
def test ( x : rand )
x
end
print test ( x : true )
2018-02-11 22:33:00 -05:00
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_tracecoverage
skip " write test "
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_defineclass
skip " support this in mjit_compile (low priority) "
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_send
assert_eval_with_jit ( " #{ << ~ " begin; " } \n #{ << ~ " end; " } " , stdout : '1' , success_count : 2 )
begin ;
2018-02-10 11:09:55 -05:00
print proc { yield_self { 1 } } . call
2018-02-11 22:33:00 -05:00
end ;
end
def test_compile_insn_opt_str_freeze
assert_compile_once ( " #{ << ~ " begin; " } \n #{ << ~ " end; " } " , result_inspect : '"foo"' )
begin ;
'foo' . freeze
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_opt_str_uminus
assert_compile_once ( " #{ << ~ " begin; " } \n #{ << ~ " end; " } " , result_inspect : '"bar"' )
begin ;
- 'bar'
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_opt_newarray_max
assert_compile_once ( " #{ << ~ " begin; " } \n #{ << ~ " end; " } " , result_inspect : '2' )
begin ;
2018-02-10 11:09:55 -05:00
a = 1
b = 2
2018-02-11 22:33:00 -05:00
[ a , b ] . max
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
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
2018-02-10 11:09:55 -05:00
assert_compile_once ( 'print' , result_inspect : 'nil' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_invokesuper
assert_eval_with_jit ( " #{ << ~ " begin; " } \n #{ << ~ " end; " } " , stdout : '3' , success_count : 4 )
begin ;
2018-02-10 11:09:55 -05:00
mod = Module . new {
def test
super + 2
end
}
klass = Class . new {
prepend mod
def test
1
end
}
print klass . new . test
2018-02-11 22:33:00 -05:00
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_invokeblock_leave
assert_eval_with_jit ( " #{ << ~ " begin; " } \n #{ << ~ " end; " } " , stdout : '2' , success_count : 2 )
begin ;
2018-02-10 11:09:55 -05:00
def foo
yield
end
print foo { 2 }
2018-02-11 22:33:00 -05:00
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_throw
assert_eval_with_jit ( " #{ << ~ " begin; " } \n #{ << ~ " end; " } " , stdout : '4' , success_count : 2 )
begin ;
2018-02-10 11:09:55 -05:00
def test
proc do
if 1 + 1 == 1
return 3
else
return 4
end
5
end . call
end
print test
2018-02-11 22:33:00 -05:00
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_jump_branchif
assert_compile_once ( " #{ << ~ " begin; " } \n #{ << ~ 'end;' } " , result_inspect : 'nil' )
begin ;
2018-02-10 11:09:55 -05:00
a = false
1 + 1 while false
2018-02-11 22:33:00 -05:00
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_branchunless
assert_compile_once ( " #{ << ~ " begin; " } \n #{ << ~ 'end;' } " , result_inspect : '1' )
begin ;
2018-02-10 11:09:55 -05:00
a = true
if a
1
else
2
end
2018-02-11 22:33:00 -05:00
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_branchnil
assert_compile_once ( " #{ << ~ " begin; " } \n #{ << ~ 'end;' } " , result_inspect : '3' )
begin ;
2018-02-10 11:09:55 -05:00
a = 2
a & . + ( 1 )
2018-02-11 22:33:00 -05:00
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_branchiftype
assert_compile_once ( " #{ << ~ " begin; " } \n #{ << ~ 'end;' } " , result_inspect : '"42"' )
begin ;
2018-02-10 11:09:55 -05:00
a = '2'
" 4 #{ a } "
2018-02-11 22:33:00 -05:00
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_inlinecache
2018-02-10 11:09:55 -05:00
assert_compile_once ( 'Struct' , result_inspect : 'Struct' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_once
2018-02-10 11:09:55 -05:00
assert_compile_once ( '/#{true}/o =~ "true" && $~.to_a' , result_inspect : '["true"]' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_checkmatch_opt_case_dispatch
assert_compile_once ( " #{ << ~ " begin; " } \n #{ << ~ " end; " } " , result_inspect : '"world"' )
begin ;
2018-02-10 11:09:55 -05:00
case 'hello'
when / hello /
'world'
end
2018-02-11 22:33:00 -05:00
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_opt_calc
2018-02-10 11:09:55 -05:00
assert_compile_once ( '4 + 2 - ((2 * 3 / 2) % 2)' , result_inspect : '5' )
2018-02-11 22:33:00 -05:00
assert_compile_once ( '4 + 2' , result_inspect : '6' )
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_opt_cmp
2018-02-10 11:09:55 -05:00
assert_compile_once ( '(1 == 1) && (1 != 2)' , result_inspect : 'true' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_opt_rel
2018-02-10 11:09:55 -05:00
assert_compile_once ( '1 < 2 && 1 <= 1 && 2 > 1 && 1 >= 1' , result_inspect : 'true' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_opt_ltlt
2018-02-10 11:09:55 -05:00
assert_compile_once ( '[1] << 2' , result_inspect : '[1, 2]' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
2018-02-13 10:58:38 -05:00
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' )
2018-02-11 22:33:00 -05:00
begin ;
2018-02-10 11:09:55 -05:00
hash = { '1' = > 2 }
2018-02-13 10:58:38 -05:00
( hash [ '2' ] = 2 ) + ( hash [ 1 . to_s ] = 3 )
2018-02-11 22:33:00 -05:00
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_opt_length_size
assert_compile_once ( " #{ << ~ " begin; " } \n #{ << ~ " end; " } " , result_inspect : '4' )
begin ;
2018-02-10 11:09:55 -05:00
array = [ 1 , 2 ]
2018-02-11 22:33:00 -05:00
array . length + array . size
end ;
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_opt_empty_p
2018-02-10 11:09:55 -05:00
assert_compile_once ( '[].empty?' , result_inspect : 'true' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_opt_succ
2018-02-10 11:09:55 -05:00
assert_compile_once ( '1.succ' , result_inspect : '2' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_opt_not
2018-02-10 11:09:55 -05:00
assert_compile_once ( '!!true' , result_inspect : 'true' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_opt_regexpmatch1
2018-02-10 11:09:55 -05:00
assert_compile_once ( " /true/ =~ 'true' " , result_inspect : '0' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_opt_regexpmatch2
2018-02-10 11:09:55 -05:00
assert_compile_once ( " 'true' =~ /true/ " , result_inspect : '0' )
2018-02-11 22:33:00 -05:00
end
2018-02-10 11:09:55 -05:00
2018-02-11 22:33:00 -05:00
def test_compile_insn_opt_call_c_function
skip " support this in opt_call_c_function (low priority) "
2018-02-08 07:40:33 -05:00
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
mjit_compile.c: use local variables for stack
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
2018-03-04 02:04:40 -05:00
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
compile.c: mark all ISeq ancestors as catch_except_p
This change assumes that continuously reading `parent_iseq` from block
ISeq would reach non-block ISeq finally.
test/ruby/test_jit.rb: add test that catches 2-depth exception
Combination of r62654 and r62678 caused following error in this test.
-e:12:in `wrapper': Stack consistency error (sp: 14, bp: 13) (fatal)
== disasm: #<ISeq:wrapper@-e:10 (10,0)-(12,3)> (catch: FALSE)===========
local table (size: 2, argc: 2 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 2] paths<Arg> [ 1] prefixes<Arg>
0000 putself ( 11)[LiCa]
0001 getlocal_WC_0 paths
0003 getlocal_WC_0 prefixes
0005 opt_send_without_block <callinfo!mid:catch_true, argc:2, FCALL|ARGS_SIMPLE>, <callcache>
0008 leave ( 12)[Re]
As you can see, it says `catch: FALSE`, but obviously it catches
exception raised from `return path`.
As of r62655, it was kind of intentional because I only cared about
expiration of JIT-ed frame and I've thought calling `vm_exec` is only
needed once for it. So r62654 was NOT actually checking if it may catch
exception.
But for r62678, obviously we should set catch_except_p=TRUE for all
ISeqs which may catch exception. Otherwise catch table lookup would
fail.
With this bugfix, code generated by r62655 might be worse, but at least
while loop can be marked as `catch: FALSE` as expected.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@62717 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2018-03-10 09:52:12 -05:00
def test_catching_deep_exception
assert_eval_with_jit ( " #{ << ~ " begin; " } \n #{ << ~ " end; " } " , stdout : '1' , success_count : 4 )
begin ;
def catch_true ( paths , prefixes ) # catch_except_p: TRUE
prefixes . each do | prefix | # catch_except_p: TRUE
paths . each do | path | # catch_except_p: FALSE
return path
end
end
end
def wrapper ( paths , prefixes )
catch_true ( paths , prefixes )
end
print wrapper ( [ '1' ] , [ '2' ] )
end ;
end
2018-02-08 07:40:33 -05:00
private
2018-02-10 11:09:55 -05:00
# The shortest way to test one proc
def assert_compile_once ( script , result_inspect : )
2018-02-11 23:35:16 -05:00
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 )
2018-02-10 11:09:55 -05:00
end
2018-02-08 07:40:33 -05:00
# Shorthand for normal test cases
2018-02-13 10:58:38 -05:00
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 )
2018-02-11 22:33:00 -05:00
actual = err . scan ( / ^ #{ JIT_SUCCESS_PREFIX } : / ) . size
2018-03-11 05:32:48 -04:00
# Debugging on CI
2018-03-17 02:33:28 -04:00
if err . include? ( " gcc: error trying to exec 'cc1': execvp: No such file or directory " ) && RbConfig :: CONFIG [ 'CC' ] == 'gcc'
$stderr . puts " \n test/ruby/test_jit.rb: DEBUG OUTPUT: "
cc1 = %x`gcc -print-prog-name=cc1` . rstrip
if $? . success?
$stderr . puts " cc1 path: #{ cc1 } "
$stderr . puts " executable?: #{ File . executable? ( cc1 ) } "
$stderr . puts " ls: \n #{ IO . popen ( [ 'ls' , '-la' , File . dirname ( cc1 ) ] , & :read ) } "
else
$stderr . puts 'Failed to fetch cc1 path'
end
2018-03-11 05:32:48 -04:00
end
2018-02-11 22:33:00 -05:00
assert_equal (
success_count , actual ,
" Expected #{ success_count } times of JIT success, but succeeded #{ actual } times. \n \n " \
" script: \n #{ code_block ( script ) } \n stderr: \n #{ code_block ( err ) } " ,
)
2018-02-08 07:40:33 -05:00
if stdout
2018-02-10 11:09:55 -05:00
assert_equal ( stdout , out , " Expected stdout #{ out . inspect } to match #{ stdout . inspect } with script: \n #{ code_block ( script ) } " )
2018-02-08 07:40:33 -05:00
end
end
# Run Ruby script with --jit-wait (Synchronous JIT compilation).
# Returns [stdout, stderr]
2018-02-11 22:33:00 -05:00
def eval_with_jit ( script , ** opts )
stdout , stderr , status = super
2018-02-08 07:58:27 -05:00
assert_equal ( true , status . success? , " Failed to run script with JIT: \n #{ code_block ( script ) } \n stdout: \n #{ code_block ( stdout ) } \n stderr: \n #{ code_block ( stderr ) } " )
2018-02-08 07:40:33 -05:00
[ stdout , stderr ]
end
def code_block ( code )
" ``` \n #{ code } \n ``` \n \n "
end
end