1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/test/ruby/test_yjit.rb

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

939 lines
23 KiB
Ruby
Raw Normal View History

2021-06-23 19:38:37 -04:00
# frozen_string_literal: true
#
# This set of tests can be run with:
# make test-all TESTS='test/ruby/test_yjit.rb' RUN_OPTS="--yjit-call-threshold=1"
2021-06-23 19:38:37 -04:00
require 'test/unit'
require 'envutil'
require 'tmpdir'
require_relative '../lib/jit_support'
2021-06-23 19:38:37 -04:00
return unless defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
2021-06-23 19:38:37 -04:00
# Tests for YJIT with assertions on compilation and side exits
# insipired by the MJIT tests in test/ruby/test_mjit.rb
2021-06-23 19:38:37 -04:00
class TestYJIT < Test::Unit::TestCase
def test_yjit_in_ruby_description
assert_includes(RUBY_DESCRIPTION, '+YJIT')
end
Rust YJIT In December 2021, we opened an [issue] to solicit feedback regarding the porting of the YJIT codebase from C99 to Rust. There were some reservations, but this project was given the go ahead by Ruby core developers and Matz. Since then, we have successfully completed the port of YJIT to Rust. The new Rust version of YJIT has reached parity with the C version, in that it passes all the CRuby tests, is able to run all of the YJIT benchmarks, and performs similarly to the C version (because it works the same way and largely generates the same machine code). We've even incorporated some design improvements, such as a more fine-grained constant invalidation mechanism which we expect will make a big difference in Ruby on Rails applications. Because we want to be careful, YJIT is guarded behind a configure option: ```shell ./configure --enable-yjit # Build YJIT in release mode ./configure --enable-yjit=dev # Build YJIT in dev/debug mode ``` By default, YJIT does not get compiled and cargo/rustc is not required. If YJIT is built in dev mode, then `cargo` is used to fetch development dependencies, but when building in release, `cargo` is not required, only `rustc`. At the moment YJIT requires Rust 1.60.0 or newer. The YJIT command-line options remain mostly unchanged, and more details about the build process are documented in `doc/yjit/yjit.md`. The CI tests have been updated and do not take any more resources than before. The development history of the Rust port is available at the following commit for interested parties: https://github.com/Shopify/ruby/commit/1fd9573d8b4b65219f1c2407f30a0a60e537f8be Our hope is that Rust YJIT will be compiled and included as a part of system packages and compiled binaries of the Ruby 3.2 release. We do not anticipate any major problems as Rust is well supported on every platform which YJIT supports, but to make sure that this process works smoothly, we would like to reach out to those who take care of building systems packages before the 3.2 release is shipped and resolve any issues that may come up. [issue]: https://bugs.ruby-lang.org/issues/18481 Co-authored-by: Maxime Chevalier-Boisvert <maximechevalierb@gmail.com> Co-authored-by: Noah Gibbs <the.codefolio.guy@gmail.com> Co-authored-by: Kevin Newton <kddnewton@gmail.com>
2022-04-19 14:40:21 -04:00
# Check that YJIT is in the version string
def test_yjit_in_version
[
%w(--version --yjit),
%w(--version --disable-yjit --yjit),
%w(--version --disable-yjit --enable-yjit),
%w(--version --disable-yjit --enable=yjit),
%w(--version --disable=yjit --yjit),
%w(--version --disable=yjit --enable-yjit),
%w(--version --disable=yjit --enable=yjit),
*([
%w(--version --jit),
%w(--version --disable-jit --jit),
%w(--version --disable-jit --enable-jit),
%w(--version --disable-jit --enable=jit),
%w(--version --disable=jit --yjit),
%w(--version --disable=jit --enable-jit),
%w(--version --disable=jit --enable=jit),
] if JITSupport.yjit_supported?),
].each do |version_args|
assert_in_out_err(version_args) do |stdout, stderr|
assert_equal(RUBY_DESCRIPTION, stdout.first)
assert_equal([], stderr)
end
end
end
def test_command_line_switches
2021-10-25 20:22:54 -04:00
assert_in_out_err('--yjit-', '', [], /invalid option --yjit-/)
assert_in_out_err('--yjithello', '', [], /invalid option --yjithello/)
Rust YJIT In December 2021, we opened an [issue] to solicit feedback regarding the porting of the YJIT codebase from C99 to Rust. There were some reservations, but this project was given the go ahead by Ruby core developers and Matz. Since then, we have successfully completed the port of YJIT to Rust. The new Rust version of YJIT has reached parity with the C version, in that it passes all the CRuby tests, is able to run all of the YJIT benchmarks, and performs similarly to the C version (because it works the same way and largely generates the same machine code). We've even incorporated some design improvements, such as a more fine-grained constant invalidation mechanism which we expect will make a big difference in Ruby on Rails applications. Because we want to be careful, YJIT is guarded behind a configure option: ```shell ./configure --enable-yjit # Build YJIT in release mode ./configure --enable-yjit=dev # Build YJIT in dev/debug mode ``` By default, YJIT does not get compiled and cargo/rustc is not required. If YJIT is built in dev mode, then `cargo` is used to fetch development dependencies, but when building in release, `cargo` is not required, only `rustc`. At the moment YJIT requires Rust 1.60.0 or newer. The YJIT command-line options remain mostly unchanged, and more details about the build process are documented in `doc/yjit/yjit.md`. The CI tests have been updated and do not take any more resources than before. The development history of the Rust port is available at the following commit for interested parties: https://github.com/Shopify/ruby/commit/1fd9573d8b4b65219f1c2407f30a0a60e537f8be Our hope is that Rust YJIT will be compiled and included as a part of system packages and compiled binaries of the Ruby 3.2 release. We do not anticipate any major problems as Rust is well supported on every platform which YJIT supports, but to make sure that this process works smoothly, we would like to reach out to those who take care of building systems packages before the 3.2 release is shipped and resolve any issues that may come up. [issue]: https://bugs.ruby-lang.org/issues/18481 Co-authored-by: Maxime Chevalier-Boisvert <maximechevalierb@gmail.com> Co-authored-by: Noah Gibbs <the.codefolio.guy@gmail.com> Co-authored-by: Kevin Newton <kddnewton@gmail.com>
2022-04-19 14:40:21 -04:00
#assert_in_out_err('--yjit-call-threshold', '', [], /--yjit-call-threshold needs an argument/)
#assert_in_out_err('--yjit-call-threshold=', '', [], /--yjit-call-threshold needs an argument/)
end
def test_yjit_stats_and_v_no_error
_stdout, stderr, _status = EnvUtil.invoke_ruby(%w(-v --yjit-stats), '', true, true)
refute_includes(stderr, "NoMethodError")
end
def test_enable_from_env_var
yjit_child_env = {'RUBY_YJIT_ENABLE' => '1'}
assert_in_out_err([yjit_child_env, '--version'], '') do |stdout, stderr|
assert_equal(RUBY_DESCRIPTION, stdout.first)
assert_equal([], stderr)
end
assert_in_out_err([yjit_child_env, '-e puts RUBY_DESCRIPTION'], '', [RUBY_DESCRIPTION])
assert_in_out_err([yjit_child_env, '-e p RubyVM::YJIT.enabled?'], '', ['true'])
end
def test_compile_setclassvariable
script = 'class Foo; def self.foo; @@foo = 1; end; end; Foo.foo'
assert_compiles(script, insns: %i[setclassvariable], result: 1)
end
def test_compile_getclassvariable
script = 'class Foo; @@foo = 1; def self.foo; @@foo; end; end; Foo.foo'
assert_compiles(script, insns: %i[getclassvariable], result: 1)
end
2021-06-23 19:38:37 -04:00
def test_compile_putnil
assert_compiles('nil', insns: %i[putnil], result: nil)
2021-06-23 19:38:37 -04:00
end
def test_compile_putobject
assert_compiles('true', insns: %i[putobject], result: true)
assert_compiles('123', insns: %i[putobject], result: 123)
assert_compiles(':foo', insns: %i[putobject], result: :foo)
2021-06-23 19:38:37 -04:00
end
2022-05-19 11:52:52 -04:00
def test_compile_opt_succ
assert_compiles('1.succ', insns: %i[opt_succ], result: 2)
end
2021-06-23 19:38:37 -04:00
def test_compile_opt_not
assert_compiles('!false', insns: %i[opt_not], result: true)
assert_compiles('!nil', insns: %i[opt_not], result: true)
assert_compiles('!true', insns: %i[opt_not], result: false)
assert_compiles('![]', insns: %i[opt_not], result: false)
2021-06-23 19:38:37 -04:00
end
def test_compile_opt_newarray
assert_compiles('[]', insns: %i[newarray], result: [])
assert_compiles('[1+1]', insns: %i[newarray opt_plus], result: [2])
assert_compiles('[1,1+1,3,4,5,6]', insns: %i[newarray opt_plus], result: [1, 2, 3, 4, 5, 6])
2021-06-23 19:38:37 -04:00
end
def test_compile_opt_duparray
assert_compiles('[1]', insns: %i[duparray], result: [1])
assert_compiles('[1, 2, 3]', insns: %i[duparray], result: [1, 2, 3])
2021-06-23 19:38:37 -04:00
end
2021-07-17 06:26:46 -04:00
def test_compile_newrange
assert_compiles('s = 1; (s..5)', insns: %i[newrange], result: 1..5)
assert_compiles('s = 1; e = 5; (s..e)', insns: %i[newrange], result: 1..5)
assert_compiles('s = 1; (s...5)', insns: %i[newrange], result: 1...5)
assert_compiles('s = 1; (s..)', insns: %i[newrange], result: 1..)
assert_compiles('e = 5; (..e)', insns: %i[newrange], result: ..5)
end
def test_compile_duphash
assert_compiles('{ two: 2 }', insns: %i[duphash], result: { two: 2 })
end
def test_compile_newhash
assert_compiles('{}', insns: %i[newhash], result: {})
assert_compiles('{ two: 1 + 1 }', insns: %i[newhash], result: { two: 2 })
assert_compiles('{ 1 + 1 => :two }', insns: %i[newhash], result: { 2 => :two })
end
2021-06-23 19:38:37 -04:00
def test_compile_opt_nil_p
assert_compiles('nil.nil?', insns: %i[opt_nil_p], result: true)
assert_compiles('false.nil?', insns: %i[opt_nil_p], result: false)
assert_compiles('true.nil?', insns: %i[opt_nil_p], result: false)
assert_compiles('(-"").nil?', insns: %i[opt_nil_p], result: false)
assert_compiles('123.nil?', insns: %i[opt_nil_p], result: false)
2021-06-23 19:38:37 -04:00
end
def test_compile_eq_fixnum
assert_compiles('123 == 123', insns: %i[opt_eq], result: true)
assert_compiles('123 == 456', insns: %i[opt_eq], result: false)
2021-06-23 19:38:37 -04:00
end
def test_compile_eq_string
assert_compiles('-"" == -""', insns: %i[opt_eq], result: true)
assert_compiles('-"foo" == -"foo"', insns: %i[opt_eq], result: true)
assert_compiles('-"foo" == -"bar"', insns: %i[opt_eq], result: false)
2021-06-23 19:38:37 -04:00
end
2021-09-03 20:07:16 -04:00
def test_compile_eq_symbol
assert_compiles(':foo == :foo', insns: %i[opt_eq], result: true)
assert_compiles(':foo == :bar', insns: %i[opt_eq], result: false)
assert_compiles(':foo == "foo".to_sym', insns: %i[opt_eq], result: true)
end
def test_compile_eq_object
assert_compiles(<<~RUBY, insns: %i[opt_eq], result: false)
def eq(a, b)
a == b
end
eq(Object.new, Object.new)
RUBY
assert_compiles(<<~RUBY, insns: %i[opt_eq], result: true)
def eq(a, b)
a == b
end
obj = Object.new
eq(obj, obj)
RUBY
end
def test_compile_eq_arbitrary_class
assert_compiles(<<~RUBY, insns: %i[opt_eq], result: "yes")
def eq(a, b)
a == b
end
class Foo
def ==(other)
"yes"
end
end
eq(Foo.new, Foo.new)
eq(Foo.new, Foo.new)
RUBY
end
def test_compile_opt_lt
assert_compiles('1 < 2', insns: %i[opt_lt])
assert_compiles('"a" < "b"', insns: %i[opt_lt])
end
def test_compile_opt_le
assert_compiles('1 <= 2', insns: %i[opt_le])
assert_compiles('"a" <= "b"', insns: %i[opt_le])
end
def test_compile_opt_gt
assert_compiles('1 > 2', insns: %i[opt_gt])
assert_compiles('"a" > "b"', insns: %i[opt_gt])
end
def test_compile_opt_ge
assert_compiles('1 >= 2', insns: %i[opt_ge])
assert_compiles('"a" >= "b"', insns: %i[opt_ge])
end
def test_compile_opt_plus
assert_compiles('1 + 2', insns: %i[opt_plus])
assert_compiles('"a" + "b"', insns: %i[opt_plus])
assert_compiles('[:foo] + [:bar]', insns: %i[opt_plus])
end
def test_compile_opt_minus
assert_compiles('1 - 2', insns: %i[opt_minus])
assert_compiles('[:foo, :bar] - [:bar]', insns: %i[opt_minus])
end
def test_compile_opt_or
assert_compiles('1 | 2', insns: %i[opt_or])
assert_compiles('[:foo] | [:bar]', insns: %i[opt_or])
end
def test_compile_opt_and
assert_compiles('1 & 2', insns: %i[opt_and])
assert_compiles('[:foo, :bar] & [:bar]', insns: %i[opt_and])
end
def test_compile_set_and_get_global
assert_compiles('$foo = 123; $foo', insns: %i[setglobal], result: 123)
end
2021-06-19 17:03:06 -04:00
def test_compile_putspecialobject
assert_compiles('-> {}', insns: %i[putspecialobject])
end
def test_compile_tostring
assert_no_exits('"i am a string #{true}"')
end
2021-09-13 17:31:12 -04:00
def test_compile_opt_aset
assert_compiles('[1,2,3][2] = 4', insns: %i[opt_aset])
assert_compiles('{}[:foo] = :bar', insns: %i[opt_aset])
assert_compiles('[1,2,3][0..-1] = []', insns: %i[opt_aset])
assert_compiles('"foo"[3] = "d"', insns: %i[opt_aset])
end
def test_compile_attr_set
assert_no_exits(<<~EORB)
class Foo
attr_accessor :bar
end
foo = Foo.new
foo.bar = 3
foo.bar = 3
foo.bar = 3
foo.bar = 3
EORB
end
def test_compile_regexp
assert_no_exits('/#{true}/')
end
def test_compile_dynamic_symbol
assert_compiles(':"#{"foo"}"', insns: %i[intern])
assert_compiles('s = "bar"; :"foo#{s}"', insns: %i[intern])
end
def test_getlocal_with_level
assert_compiles(<<~RUBY, insns: %i[getlocal opt_plus], result: [[7]])
def foo(foo, bar)
[1].map do |x|
[1].map do |y|
foo + bar
end
end
end
foo(5, 2)
RUBY
end
2021-06-26 02:50:19 -04:00
2021-09-17 11:38:33 -04:00
def test_setlocal_with_level
assert_no_exits(<<~RUBY)
def sum(arr)
sum = 0
arr.each do |x|
sum += x
end
sum
end
sum([1,2,3])
RUBY
end
2021-06-23 19:38:37 -04:00
def test_string_then_nil
assert_compiles(<<~RUBY, insns: %i[opt_nil_p], result: true)
2021-06-23 19:38:37 -04:00
def foo(val)
val.nil?
end
foo("foo")
foo(nil)
RUBY
end
def test_nil_then_string
assert_compiles(<<~RUBY, insns: %i[opt_nil_p], result: false)
2021-06-23 19:38:37 -04:00
def foo(val)
val.nil?
end
foo(nil)
foo("foo")
RUBY
end
def test_string_concat_utf8
assert_compiles(<<~RUBY, frozen_string_literal: true, result: true)
def str_cat_utf8
s = String.new
10.times { s << "" }
s
end
str_cat_utf8 == "" * 10
RUBY
end
def test_string_concat_ascii
# Constant-get for classes (e.g. String, Encoding) can cause a side-exit in getinlinecache. For now, ignore exits.
assert_compiles(<<~RUBY, exits: :any)
str_arg = "b".encode(Encoding::ASCII)
def str_cat_ascii(arg)
s = String.new(encoding: Encoding::ASCII)
10.times { s << arg }
s
end
str_cat_ascii(str_arg) == str_arg * 10
RUBY
end
2021-06-23 19:38:37 -04:00
def test_opt_length_in_method
assert_compiles(<<~RUBY, insns: %i[opt_length], result: 5)
2021-06-23 19:38:37 -04:00
def foo(str)
str.length
end
foo("hello, ")
foo("world")
RUBY
end
2021-06-18 20:32:13 -04:00
def test_opt_regexpmatch2
assert_compiles(<<~RUBY, insns: %i[opt_regexpmatch2], result: 0)
def foo(str)
str =~ /foo/
end
foo("foobar")
RUBY
end
def test_expandarray
assert_compiles(<<~'RUBY', insns: %i[expandarray], result: [1, 2])
a, b = [1, 2]
RUBY
end
def test_expandarray_nil
assert_compiles(<<~'RUBY', insns: %i[expandarray], result: [nil, nil])
a, b = nil
[a, b]
RUBY
end
2021-09-08 21:47:09 -04:00
def test_getspecial_backref
assert_compiles("'foo' =~ /(o)./; $&", insns: %i[getspecial], result: "oo")
assert_compiles("'foo' =~ /(o)./; $`", insns: %i[getspecial], result: "f")
assert_compiles("'foo' =~ /(o)./; $'", insns: %i[getspecial], result: "")
assert_compiles("'foo' =~ /(o)./; $+", insns: %i[getspecial], result: "o")
assert_compiles("'foo' =~ /(o)./; $1", insns: %i[getspecial], result: "o")
assert_compiles("'foo' =~ /(o)./; $2", insns: %i[getspecial], result: nil)
end
New constant caching insn: opt_getconstant_path Previously YARV bytecode implemented constant caching by having a pair of instructions, opt_getinlinecache and opt_setinlinecache, wrapping a series of getconstant calls (with putobject providing supporting arguments). This commit replaces that pattern with a new instruction, opt_getconstant_path, handling both getting/setting the inline cache and fetching the constant on a cache miss. This is implemented by storing the full constant path as a null-terminated array of IDs inside of the IC structure. idNULL is used to signal an absolute constant reference. $ ./miniruby --dump=insns -e '::Foo::Bar::Baz' == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,13)> (catch: FALSE) 0000 opt_getconstant_path <ic:0 ::Foo::Bar::Baz> ( 1)[Li] 0002 leave The motivation for this is that we had increasingly found the need to disassemble the instructions between the opt_getinlinecache and opt_setinlinecache in order to determine the constant we are fetching, or otherwise store metadata. This disassembly was done: * In opt_setinlinecache, to register the IC against the constant names it is using for granular invalidation. * In rb_iseq_free, to unregister the IC from the invalidation table. * In YJIT to find the position of a opt_getinlinecache instruction to invalidate it when the cache is populated * In YJIT to register the constant names being used for invalidation. With this change we no longe need disassemly for these (in fact rb_iseq_each is now unused), as the list of constant names being referenced is held in the IC. This should also make it possible to make more optimizations in the future. This may also reduce the size of iseqs, as previously each segment required 32 bytes (on 64-bit platforms) for each constant segment. This implementation only stores one ID per-segment. There should be no significant performance change between this and the previous implementation. Previously opt_getinlinecache was a "leaf" instruction, but it included a jump (almost always to a separate cache line). Now opt_getconstant_path is a non-leaf (it may raise/autoload/call const_missing) but it does not jump. These seem to even out.
2022-08-10 13:35:48 -04:00
def test_compile_opt_getconstant_path
assert_compiles(<<~RUBY, insns: %i[opt_getconstant_path], result: 123, call_threshold: 2)
2021-06-23 19:38:37 -04:00
def get_foo
FOO
end
FOO = 123
get_foo # warm inline cache
get_foo
RUBY
end
New constant caching insn: opt_getconstant_path Previously YARV bytecode implemented constant caching by having a pair of instructions, opt_getinlinecache and opt_setinlinecache, wrapping a series of getconstant calls (with putobject providing supporting arguments). This commit replaces that pattern with a new instruction, opt_getconstant_path, handling both getting/setting the inline cache and fetching the constant on a cache miss. This is implemented by storing the full constant path as a null-terminated array of IDs inside of the IC structure. idNULL is used to signal an absolute constant reference. $ ./miniruby --dump=insns -e '::Foo::Bar::Baz' == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,13)> (catch: FALSE) 0000 opt_getconstant_path <ic:0 ::Foo::Bar::Baz> ( 1)[Li] 0002 leave The motivation for this is that we had increasingly found the need to disassemble the instructions between the opt_getinlinecache and opt_setinlinecache in order to determine the constant we are fetching, or otherwise store metadata. This disassembly was done: * In opt_setinlinecache, to register the IC against the constant names it is using for granular invalidation. * In rb_iseq_free, to unregister the IC from the invalidation table. * In YJIT to find the position of a opt_getinlinecache instruction to invalidate it when the cache is populated * In YJIT to register the constant names being used for invalidation. With this change we no longe need disassemly for these (in fact rb_iseq_each is now unused), as the list of constant names being referenced is held in the IC. This should also make it possible to make more optimizations in the future. This may also reduce the size of iseqs, as previously each segment required 32 bytes (on 64-bit platforms) for each constant segment. This implementation only stores one ID per-segment. There should be no significant performance change between this and the previous implementation. Previously opt_getinlinecache was a "leaf" instruction, but it included a jump (almost always to a separate cache line). Now opt_getconstant_path is a non-leaf (it may raise/autoload/call const_missing) but it does not jump. These seem to even out.
2022-08-10 13:35:48 -04:00
def test_opt_getconstant_path_slowpath
assert_compiles(<<~RUBY, exits: { opt_getconstant_path: 1 }, result: [42, 42, 1, 1], call_threshold: 2)
class A
FOO = 42
class << self
def foo
_foo = nil
FOO
end
end
end
result = []
result << A.foo
result << A.foo
class << A
FOO = 1
end
result << A.foo
result << A.foo
result
RUBY
end
2021-06-23 19:38:37 -04:00
def test_string_interpolation
Rust YJIT In December 2021, we opened an [issue] to solicit feedback regarding the porting of the YJIT codebase from C99 to Rust. There were some reservations, but this project was given the go ahead by Ruby core developers and Matz. Since then, we have successfully completed the port of YJIT to Rust. The new Rust version of YJIT has reached parity with the C version, in that it passes all the CRuby tests, is able to run all of the YJIT benchmarks, and performs similarly to the C version (because it works the same way and largely generates the same machine code). We've even incorporated some design improvements, such as a more fine-grained constant invalidation mechanism which we expect will make a big difference in Ruby on Rails applications. Because we want to be careful, YJIT is guarded behind a configure option: ```shell ./configure --enable-yjit # Build YJIT in release mode ./configure --enable-yjit=dev # Build YJIT in dev/debug mode ``` By default, YJIT does not get compiled and cargo/rustc is not required. If YJIT is built in dev mode, then `cargo` is used to fetch development dependencies, but when building in release, `cargo` is not required, only `rustc`. At the moment YJIT requires Rust 1.60.0 or newer. The YJIT command-line options remain mostly unchanged, and more details about the build process are documented in `doc/yjit/yjit.md`. The CI tests have been updated and do not take any more resources than before. The development history of the Rust port is available at the following commit for interested parties: https://github.com/Shopify/ruby/commit/1fd9573d8b4b65219f1c2407f30a0a60e537f8be Our hope is that Rust YJIT will be compiled and included as a part of system packages and compiled binaries of the Ruby 3.2 release. We do not anticipate any major problems as Rust is well supported on every platform which YJIT supports, but to make sure that this process works smoothly, we would like to reach out to those who take care of building systems packages before the 3.2 release is shipped and resolve any issues that may come up. [issue]: https://bugs.ruby-lang.org/issues/18481 Co-authored-by: Maxime Chevalier-Boisvert <maximechevalierb@gmail.com> Co-authored-by: Noah Gibbs <the.codefolio.guy@gmail.com> Co-authored-by: Kevin Newton <kddnewton@gmail.com>
2022-04-19 14:40:21 -04:00
assert_compiles(<<~'RUBY', insns: %i[objtostring anytostring concatstrings], result: "foobar", call_threshold: 2)
2021-06-23 19:38:37 -04:00
def make_str(foo, bar)
"#{foo}#{bar}"
end
make_str("foo", "bar")
make_str("foo", "bar")
RUBY
end
2021-06-23 19:38:37 -04:00
def test_string_interpolation_cast
assert_compiles(<<~'RUBY', insns: %i[objtostring anytostring concatstrings], result: "123")
def make_str(foo, bar)
"#{foo}#{bar}"
end
make_str(1, 23)
RUBY
end
def test_checkkeyword
assert_compiles(<<~'RUBY', insns: %i[checkkeyword], result: [2, 5])
def foo(foo: 1+1)
foo
end
[foo, foo(foo: 5)]
RUBY
end
def test_struct_aref
assert_compiles(<<~RUBY)
def foo(obj)
obj.foo
obj.bar
end
Foo = Struct.new(:foo, :bar)
foo(Foo.new(123))
foo(Foo.new(123))
RUBY
end
def test_struct_aset
2021-07-09 02:33:55 -04:00
assert_compiles(<<~RUBY)
def foo(obj)
obj.foo = 123
obj.bar = 123
end
Foo = Struct.new(:foo, :bar)
foo(Foo.new(123))
foo(Foo.new(123))
RUBY
end
def test_getblockparam
assert_compiles(<<~'RUBY', insns: [:getblockparam])
def foo &blk
2.times do
blk
end
end
foo {}
foo {}
RUBY
end
def test_getblockparamproxy
# Currently two side exits as OPTIMIZED_METHOD_TYPE_CALL is unimplemented
assert_compiles(<<~'RUBY', insns: [:getblockparamproxy], exits: { opt_send_without_block: 2 })
def foo &blk
p blk.call
p blk.call
end
foo { 1 }
foo { 2 }
RUBY
end
def test_getblockparamproxy_with_no_block
# Currently side exits on the send
assert_compiles(<<~'RUBY', insns: [:getblockparamproxy], exits: { send: 2 })
def bar
end
def foo &blk
bar(&blk)
bar(&blk)
end
foo
foo
RUBY
end
def test_send_splat
assert_compiles(<<~'RUBY', result: "3#1,2,3/P", exits: {})
def internal_method(*args)
"#{args.size}##{args.join(",")}"
end
def jit_method
send(:internal_method, *[1, 2, 3]) + "/P"
end
jit_method
RUBY
end
def test_send_multiarg
assert_compiles(<<~'RUBY', result: "3#1,2,3/Q")
def internal_method(*args)
"#{args.size}##{args.join(",")}"
end
def jit_method
send(:internal_method, 1, 2, 3) + "/Q"
end
jit_method
RUBY
end
def test_send_kwargs
# For now, this side-exits when calls include keyword args
assert_compiles(<<~'RUBY', result: "2#a:1,b:2/A", exits: {opt_send_without_block: 1})
def internal_method(**kw)
"#{kw.size}##{kw.keys.map { |k| "#{k}:#{kw[k]}" }.join(",")}"
end
def jit_method
send(:internal_method, a: 1, b: 2) + "/A"
end
jit_method
RUBY
end
def test_send_kwargs_in_receiver_only
assert_compiles(<<~'RUBY', result: "0/RK", exits: {})
def internal_method(**kw)
"#{kw.size}"
end
def jit_method
send(:internal_method) + "/RK"
end
jit_method
RUBY
end
def test_send_with_underscores
assert_compiles(<<~'RUBY', result: "0/RK", exits: {})
def internal_method(**kw)
"#{kw.size}"
end
def jit_method
__send__(:internal_method) + "/RK"
end
jit_method
RUBY
end
def test_send_kwargs_splat
# For now, this side-exits when calling with a splat
assert_compiles(<<~'RUBY', result: "2#a:1,b:2/B", exits: {opt_send_without_block: 1})
def internal_method(**kw)
"#{kw.size}##{kw.keys.map { |k| "#{k}:#{kw[k]}" }.join(",")}"
end
def jit_method
send(:internal_method, **{ a: 1, b: 2 }) + "/B"
end
jit_method
RUBY
end
def test_send_block
# Setlocal_wc_0 sometimes side-exits on write barrier
assert_compiles(<<~'RUBY', result: "b:n/b:y/b:y/b:n", exits: { :setlocal_WC_0 => 0..1 })
def internal_method(&b)
"b:#{block_given? ? "y" : "n"}"
end
def jit_method
b7 = proc { 7 }
[
send(:internal_method),
send(:internal_method, &b7),
send(:internal_method) { 7 },
send(:internal_method, &nil),
].join("/")
end
jit_method
RUBY
end
def test_send_block_calling
assert_compiles(<<~'RUBY', result: "1a2", exits: {})
def internal_method
out = yield
"1" + out + "2"
end
def jit_method
__send__(:internal_method) { "a" }
end
jit_method
RUBY
end
def test_send_block_only_receiver
assert_compiles(<<~'RUBY', result: "b:n", exits: {})
def internal_method(&b)
"b:#{block_given? ? "y" : "n"}"
end
def jit_method
send(:internal_method)
end
jit_method
RUBY
end
def test_send_block_only_sender
assert_compiles(<<~'RUBY', result: "Y/Y/Y/Y", exits: {})
def internal_method
"Y"
end
def jit_method
b7 = proc { 7 }
[
send(:internal_method),
send(:internal_method, &b7),
send(:internal_method) { 7 },
send(:internal_method, &nil),
].join("/")
end
jit_method
RUBY
end
def test_multisend
assert_compiles(<<~'RUBY', result: "77")
def internal_method
"7"
end
def jit_method
send(:send, :internal_method) + send(:send, :send, :internal_method)
end
jit_method
RUBY
end
def test_getivar_opt_plus
assert_no_exits(<<~RUBY)
class TheClass
def initialize
@levar = 1
end
def get_sum
sum = 0
# The type of levar is unknown,
# but this still should not exit
sum += @levar
sum
end
end
obj = TheClass.new
obj.get_sum
RUBY
end
def test_super_iseq
assert_compiles(<<~'RUBY', insns: %i[invokesuper opt_plus opt_mult], result: 15)
class A
def foo
1 + 2
end
end
class B < A
def foo
super * 5
end
end
B.new.foo
RUBY
end
def test_super_cfunc
assert_compiles(<<~'RUBY', insns: %i[invokesuper], result: "Hello")
class Gnirts < String
def initialize
super(-"olleH")
end
def to_s
super().reverse
end
end
Gnirts.new.to_s
RUBY
end
# Tests calling a variadic cfunc with many args
def test_build_large_struct
Rust YJIT In December 2021, we opened an [issue] to solicit feedback regarding the porting of the YJIT codebase from C99 to Rust. There were some reservations, but this project was given the go ahead by Ruby core developers and Matz. Since then, we have successfully completed the port of YJIT to Rust. The new Rust version of YJIT has reached parity with the C version, in that it passes all the CRuby tests, is able to run all of the YJIT benchmarks, and performs similarly to the C version (because it works the same way and largely generates the same machine code). We've even incorporated some design improvements, such as a more fine-grained constant invalidation mechanism which we expect will make a big difference in Ruby on Rails applications. Because we want to be careful, YJIT is guarded behind a configure option: ```shell ./configure --enable-yjit # Build YJIT in release mode ./configure --enable-yjit=dev # Build YJIT in dev/debug mode ``` By default, YJIT does not get compiled and cargo/rustc is not required. If YJIT is built in dev mode, then `cargo` is used to fetch development dependencies, but when building in release, `cargo` is not required, only `rustc`. At the moment YJIT requires Rust 1.60.0 or newer. The YJIT command-line options remain mostly unchanged, and more details about the build process are documented in `doc/yjit/yjit.md`. The CI tests have been updated and do not take any more resources than before. The development history of the Rust port is available at the following commit for interested parties: https://github.com/Shopify/ruby/commit/1fd9573d8b4b65219f1c2407f30a0a60e537f8be Our hope is that Rust YJIT will be compiled and included as a part of system packages and compiled binaries of the Ruby 3.2 release. We do not anticipate any major problems as Rust is well supported on every platform which YJIT supports, but to make sure that this process works smoothly, we would like to reach out to those who take care of building systems packages before the 3.2 release is shipped and resolve any issues that may come up. [issue]: https://bugs.ruby-lang.org/issues/18481 Co-authored-by: Maxime Chevalier-Boisvert <maximechevalierb@gmail.com> Co-authored-by: Noah Gibbs <the.codefolio.guy@gmail.com> Co-authored-by: Kevin Newton <kddnewton@gmail.com>
2022-04-19 14:40:21 -04:00
assert_compiles(<<~RUBY, insns: %i[opt_send_without_block], call_threshold: 2)
::Foo = Struct.new(:a, :b, :c, :d, :e, :f, :g, :h)
def build_foo
::Foo.new(:a, :b, :c, :d, :e, :f, :g, :h)
end
build_foo
build_foo
RUBY
end
2021-06-26 02:50:19 -04:00
def test_fib_recursion
assert_compiles(<<~'RUBY', insns: %i[opt_le opt_minus opt_plus opt_send_without_block], result: 34)
2021-06-25 17:47:23 -04:00
def fib(n)
return n if n <= 1
fib(n-1) + fib(n-2)
end
fib(9)
RUBY
end
def test_optarg_and_kwarg
assert_no_exits(<<~'RUBY')
def opt_and_kwarg(a, b=nil, c: nil)
end
2.times do
opt_and_kwarg(1, 2, c: 3)
end
RUBY
end
def test_cfunc_kwarg
assert_no_exits('{}.store(:value, foo: 123)')
assert_no_exits('{}.store(:value, foo: 123, bar: 456, baz: 789)')
assert_no_exits('{}.merge(foo: 123)')
assert_no_exits('{}.merge(foo: 123, bar: 456, baz: 789)')
end
Rust YJIT In December 2021, we opened an [issue] to solicit feedback regarding the porting of the YJIT codebase from C99 to Rust. There were some reservations, but this project was given the go ahead by Ruby core developers and Matz. Since then, we have successfully completed the port of YJIT to Rust. The new Rust version of YJIT has reached parity with the C version, in that it passes all the CRuby tests, is able to run all of the YJIT benchmarks, and performs similarly to the C version (because it works the same way and largely generates the same machine code). We've even incorporated some design improvements, such as a more fine-grained constant invalidation mechanism which we expect will make a big difference in Ruby on Rails applications. Because we want to be careful, YJIT is guarded behind a configure option: ```shell ./configure --enable-yjit # Build YJIT in release mode ./configure --enable-yjit=dev # Build YJIT in dev/debug mode ``` By default, YJIT does not get compiled and cargo/rustc is not required. If YJIT is built in dev mode, then `cargo` is used to fetch development dependencies, but when building in release, `cargo` is not required, only `rustc`. At the moment YJIT requires Rust 1.60.0 or newer. The YJIT command-line options remain mostly unchanged, and more details about the build process are documented in `doc/yjit/yjit.md`. The CI tests have been updated and do not take any more resources than before. The development history of the Rust port is available at the following commit for interested parties: https://github.com/Shopify/ruby/commit/1fd9573d8b4b65219f1c2407f30a0a60e537f8be Our hope is that Rust YJIT will be compiled and included as a part of system packages and compiled binaries of the Ruby 3.2 release. We do not anticipate any major problems as Rust is well supported on every platform which YJIT supports, but to make sure that this process works smoothly, we would like to reach out to those who take care of building systems packages before the 3.2 release is shipped and resolve any issues that may come up. [issue]: https://bugs.ruby-lang.org/issues/18481 Co-authored-by: Maxime Chevalier-Boisvert <maximechevalierb@gmail.com> Co-authored-by: Noah Gibbs <the.codefolio.guy@gmail.com> Co-authored-by: Kevin Newton <kddnewton@gmail.com>
2022-04-19 14:40:21 -04:00
# regression test simplified from URI::Generic#hostname=
def test_ctx_different_mappings
assert_compiles(<<~'RUBY', frozen_string_literal: true)
def foo(v)
!(v&.start_with?('[')) && v&.index(':')
end
foo(nil)
foo("example.com")
RUBY
end
def test_no_excessive_opt_getinlinecache_invalidation
assert_compiles(<<~'RUBY', exits: :any, result: :ok)
objects = [Object.new, Object.new]
objects.each do |o|
class << o
def foo
Object
end
end
end
9000.times {
objects[0].foo
objects[1].foo
}
stats = RubyVM::YJIT.runtime_stats
return :ok unless stats[:all_stats]
return :ok if stats[:invalidation_count] < 10
:fail
RUBY
end
2021-07-15 19:38:26 -04:00
def assert_no_exits(script)
assert_compiles(script)
end
ANY = Object.new
Rust YJIT In December 2021, we opened an [issue] to solicit feedback regarding the porting of the YJIT codebase from C99 to Rust. There were some reservations, but this project was given the go ahead by Ruby core developers and Matz. Since then, we have successfully completed the port of YJIT to Rust. The new Rust version of YJIT has reached parity with the C version, in that it passes all the CRuby tests, is able to run all of the YJIT benchmarks, and performs similarly to the C version (because it works the same way and largely generates the same machine code). We've even incorporated some design improvements, such as a more fine-grained constant invalidation mechanism which we expect will make a big difference in Ruby on Rails applications. Because we want to be careful, YJIT is guarded behind a configure option: ```shell ./configure --enable-yjit # Build YJIT in release mode ./configure --enable-yjit=dev # Build YJIT in dev/debug mode ``` By default, YJIT does not get compiled and cargo/rustc is not required. If YJIT is built in dev mode, then `cargo` is used to fetch development dependencies, but when building in release, `cargo` is not required, only `rustc`. At the moment YJIT requires Rust 1.60.0 or newer. The YJIT command-line options remain mostly unchanged, and more details about the build process are documented in `doc/yjit/yjit.md`. The CI tests have been updated and do not take any more resources than before. The development history of the Rust port is available at the following commit for interested parties: https://github.com/Shopify/ruby/commit/1fd9573d8b4b65219f1c2407f30a0a60e537f8be Our hope is that Rust YJIT will be compiled and included as a part of system packages and compiled binaries of the Ruby 3.2 release. We do not anticipate any major problems as Rust is well supported on every platform which YJIT supports, but to make sure that this process works smoothly, we would like to reach out to those who take care of building systems packages before the 3.2 release is shipped and resolve any issues that may come up. [issue]: https://bugs.ruby-lang.org/issues/18481 Co-authored-by: Maxime Chevalier-Boisvert <maximechevalierb@gmail.com> Co-authored-by: Noah Gibbs <the.codefolio.guy@gmail.com> Co-authored-by: Kevin Newton <kddnewton@gmail.com>
2022-04-19 14:40:21 -04:00
def assert_compiles(test_script, insns: [], call_threshold: 1, stdout: nil, exits: {}, result: ANY, frozen_string_literal: nil)
2021-06-23 19:38:37 -04:00
reset_stats = <<~RUBY
RubyVM::YJIT.runtime_stats
RubyVM::YJIT.reset_stats!
2021-06-23 19:38:37 -04:00
RUBY
write_results = <<~RUBY
stats = RubyVM::YJIT.runtime_stats
2021-06-23 19:38:37 -04:00
Rust YJIT In December 2021, we opened an [issue] to solicit feedback regarding the porting of the YJIT codebase from C99 to Rust. There were some reservations, but this project was given the go ahead by Ruby core developers and Matz. Since then, we have successfully completed the port of YJIT to Rust. The new Rust version of YJIT has reached parity with the C version, in that it passes all the CRuby tests, is able to run all of the YJIT benchmarks, and performs similarly to the C version (because it works the same way and largely generates the same machine code). We've even incorporated some design improvements, such as a more fine-grained constant invalidation mechanism which we expect will make a big difference in Ruby on Rails applications. Because we want to be careful, YJIT is guarded behind a configure option: ```shell ./configure --enable-yjit # Build YJIT in release mode ./configure --enable-yjit=dev # Build YJIT in dev/debug mode ``` By default, YJIT does not get compiled and cargo/rustc is not required. If YJIT is built in dev mode, then `cargo` is used to fetch development dependencies, but when building in release, `cargo` is not required, only `rustc`. At the moment YJIT requires Rust 1.60.0 or newer. The YJIT command-line options remain mostly unchanged, and more details about the build process are documented in `doc/yjit/yjit.md`. The CI tests have been updated and do not take any more resources than before. The development history of the Rust port is available at the following commit for interested parties: https://github.com/Shopify/ruby/commit/1fd9573d8b4b65219f1c2407f30a0a60e537f8be Our hope is that Rust YJIT will be compiled and included as a part of system packages and compiled binaries of the Ruby 3.2 release. We do not anticipate any major problems as Rust is well supported on every platform which YJIT supports, but to make sure that this process works smoothly, we would like to reach out to those who take care of building systems packages before the 3.2 release is shipped and resolve any issues that may come up. [issue]: https://bugs.ruby-lang.org/issues/18481 Co-authored-by: Maxime Chevalier-Boisvert <maximechevalierb@gmail.com> Co-authored-by: Noah Gibbs <the.codefolio.guy@gmail.com> Co-authored-by: Kevin Newton <kddnewton@gmail.com>
2022-04-19 14:40:21 -04:00
def collect_insns(iseq)
insns = RubyVM::YJIT.insns_compiled(iseq)
iseq.each_child { |c| insns.concat collect_insns(c) }
insns
2021-06-23 19:38:37 -04:00
end
iseq = RubyVM::InstructionSequence.of(_test_proc)
IO.open(3).write Marshal.dump({
2021-06-19 17:03:06 -04:00
result: #{result == ANY ? "nil" : "result"},
2021-06-23 19:38:37 -04:00
stats: stats,
Rust YJIT In December 2021, we opened an [issue] to solicit feedback regarding the porting of the YJIT codebase from C99 to Rust. There were some reservations, but this project was given the go ahead by Ruby core developers and Matz. Since then, we have successfully completed the port of YJIT to Rust. The new Rust version of YJIT has reached parity with the C version, in that it passes all the CRuby tests, is able to run all of the YJIT benchmarks, and performs similarly to the C version (because it works the same way and largely generates the same machine code). We've even incorporated some design improvements, such as a more fine-grained constant invalidation mechanism which we expect will make a big difference in Ruby on Rails applications. Because we want to be careful, YJIT is guarded behind a configure option: ```shell ./configure --enable-yjit # Build YJIT in release mode ./configure --enable-yjit=dev # Build YJIT in dev/debug mode ``` By default, YJIT does not get compiled and cargo/rustc is not required. If YJIT is built in dev mode, then `cargo` is used to fetch development dependencies, but when building in release, `cargo` is not required, only `rustc`. At the moment YJIT requires Rust 1.60.0 or newer. The YJIT command-line options remain mostly unchanged, and more details about the build process are documented in `doc/yjit/yjit.md`. The CI tests have been updated and do not take any more resources than before. The development history of the Rust port is available at the following commit for interested parties: https://github.com/Shopify/ruby/commit/1fd9573d8b4b65219f1c2407f30a0a60e537f8be Our hope is that Rust YJIT will be compiled and included as a part of system packages and compiled binaries of the Ruby 3.2 release. We do not anticipate any major problems as Rust is well supported on every platform which YJIT supports, but to make sure that this process works smoothly, we would like to reach out to those who take care of building systems packages before the 3.2 release is shipped and resolve any issues that may come up. [issue]: https://bugs.ruby-lang.org/issues/18481 Co-authored-by: Maxime Chevalier-Boisvert <maximechevalierb@gmail.com> Co-authored-by: Noah Gibbs <the.codefolio.guy@gmail.com> Co-authored-by: Kevin Newton <kddnewton@gmail.com>
2022-04-19 14:40:21 -04:00
insns: collect_insns(iseq),
2021-06-23 19:38:37 -04:00
disasm: iseq.disasm
})
RUBY
script = <<~RUBY
#{"# frozen_string_literal: true" if frozen_string_literal}
_test_proc = -> {
2021-06-23 19:38:37 -04:00
#{test_script}
}
#{reset_stats}
result = _test_proc.call
#{write_results}
2021-06-23 19:38:37 -04:00
RUBY
Rust YJIT In December 2021, we opened an [issue] to solicit feedback regarding the porting of the YJIT codebase from C99 to Rust. There were some reservations, but this project was given the go ahead by Ruby core developers and Matz. Since then, we have successfully completed the port of YJIT to Rust. The new Rust version of YJIT has reached parity with the C version, in that it passes all the CRuby tests, is able to run all of the YJIT benchmarks, and performs similarly to the C version (because it works the same way and largely generates the same machine code). We've even incorporated some design improvements, such as a more fine-grained constant invalidation mechanism which we expect will make a big difference in Ruby on Rails applications. Because we want to be careful, YJIT is guarded behind a configure option: ```shell ./configure --enable-yjit # Build YJIT in release mode ./configure --enable-yjit=dev # Build YJIT in dev/debug mode ``` By default, YJIT does not get compiled and cargo/rustc is not required. If YJIT is built in dev mode, then `cargo` is used to fetch development dependencies, but when building in release, `cargo` is not required, only `rustc`. At the moment YJIT requires Rust 1.60.0 or newer. The YJIT command-line options remain mostly unchanged, and more details about the build process are documented in `doc/yjit/yjit.md`. The CI tests have been updated and do not take any more resources than before. The development history of the Rust port is available at the following commit for interested parties: https://github.com/Shopify/ruby/commit/1fd9573d8b4b65219f1c2407f30a0a60e537f8be Our hope is that Rust YJIT will be compiled and included as a part of system packages and compiled binaries of the Ruby 3.2 release. We do not anticipate any major problems as Rust is well supported on every platform which YJIT supports, but to make sure that this process works smoothly, we would like to reach out to those who take care of building systems packages before the 3.2 release is shipped and resolve any issues that may come up. [issue]: https://bugs.ruby-lang.org/issues/18481 Co-authored-by: Maxime Chevalier-Boisvert <maximechevalierb@gmail.com> Co-authored-by: Noah Gibbs <the.codefolio.guy@gmail.com> Co-authored-by: Kevin Newton <kddnewton@gmail.com>
2022-04-19 14:40:21 -04:00
status, out, err, stats = eval_with_jit(script, call_threshold: call_threshold)
2021-06-23 19:38:37 -04:00
assert status.success?, "exited with status #{status.to_i}, stderr:\n#{err}"
assert_equal stdout.chomp, out.chomp if stdout
unless ANY.equal?(result)
assert_equal result, stats[:result]
end
2021-06-23 19:38:37 -04:00
runtime_stats = stats[:stats]
Rust YJIT In December 2021, we opened an [issue] to solicit feedback regarding the porting of the YJIT codebase from C99 to Rust. There were some reservations, but this project was given the go ahead by Ruby core developers and Matz. Since then, we have successfully completed the port of YJIT to Rust. The new Rust version of YJIT has reached parity with the C version, in that it passes all the CRuby tests, is able to run all of the YJIT benchmarks, and performs similarly to the C version (because it works the same way and largely generates the same machine code). We've even incorporated some design improvements, such as a more fine-grained constant invalidation mechanism which we expect will make a big difference in Ruby on Rails applications. Because we want to be careful, YJIT is guarded behind a configure option: ```shell ./configure --enable-yjit # Build YJIT in release mode ./configure --enable-yjit=dev # Build YJIT in dev/debug mode ``` By default, YJIT does not get compiled and cargo/rustc is not required. If YJIT is built in dev mode, then `cargo` is used to fetch development dependencies, but when building in release, `cargo` is not required, only `rustc`. At the moment YJIT requires Rust 1.60.0 or newer. The YJIT command-line options remain mostly unchanged, and more details about the build process are documented in `doc/yjit/yjit.md`. The CI tests have been updated and do not take any more resources than before. The development history of the Rust port is available at the following commit for interested parties: https://github.com/Shopify/ruby/commit/1fd9573d8b4b65219f1c2407f30a0a60e537f8be Our hope is that Rust YJIT will be compiled and included as a part of system packages and compiled binaries of the Ruby 3.2 release. We do not anticipate any major problems as Rust is well supported on every platform which YJIT supports, but to make sure that this process works smoothly, we would like to reach out to those who take care of building systems packages before the 3.2 release is shipped and resolve any issues that may come up. [issue]: https://bugs.ruby-lang.org/issues/18481 Co-authored-by: Maxime Chevalier-Boisvert <maximechevalierb@gmail.com> Co-authored-by: Noah Gibbs <the.codefolio.guy@gmail.com> Co-authored-by: Kevin Newton <kddnewton@gmail.com>
2022-04-19 14:40:21 -04:00
insns_compiled = stats[:insns]
2021-06-23 19:38:37 -04:00
disasm = stats[:disasm]
Rust YJIT In December 2021, we opened an [issue] to solicit feedback regarding the porting of the YJIT codebase from C99 to Rust. There were some reservations, but this project was given the go ahead by Ruby core developers and Matz. Since then, we have successfully completed the port of YJIT to Rust. The new Rust version of YJIT has reached parity with the C version, in that it passes all the CRuby tests, is able to run all of the YJIT benchmarks, and performs similarly to the C version (because it works the same way and largely generates the same machine code). We've even incorporated some design improvements, such as a more fine-grained constant invalidation mechanism which we expect will make a big difference in Ruby on Rails applications. Because we want to be careful, YJIT is guarded behind a configure option: ```shell ./configure --enable-yjit # Build YJIT in release mode ./configure --enable-yjit=dev # Build YJIT in dev/debug mode ``` By default, YJIT does not get compiled and cargo/rustc is not required. If YJIT is built in dev mode, then `cargo` is used to fetch development dependencies, but when building in release, `cargo` is not required, only `rustc`. At the moment YJIT requires Rust 1.60.0 or newer. The YJIT command-line options remain mostly unchanged, and more details about the build process are documented in `doc/yjit/yjit.md`. The CI tests have been updated and do not take any more resources than before. The development history of the Rust port is available at the following commit for interested parties: https://github.com/Shopify/ruby/commit/1fd9573d8b4b65219f1c2407f30a0a60e537f8be Our hope is that Rust YJIT will be compiled and included as a part of system packages and compiled binaries of the Ruby 3.2 release. We do not anticipate any major problems as Rust is well supported on every platform which YJIT supports, but to make sure that this process works smoothly, we would like to reach out to those who take care of building systems packages before the 3.2 release is shipped and resolve any issues that may come up. [issue]: https://bugs.ruby-lang.org/issues/18481 Co-authored-by: Maxime Chevalier-Boisvert <maximechevalierb@gmail.com> Co-authored-by: Noah Gibbs <the.codefolio.guy@gmail.com> Co-authored-by: Kevin Newton <kddnewton@gmail.com>
2022-04-19 14:40:21 -04:00
# Check that exit counts are as expected
# Full stats are only available when --enable-yjit=dev
if runtime_stats[:all_stats]
recorded_exits = runtime_stats.select { |k, v| k.to_s.start_with?("exit_") }
2021-06-23 19:38:37 -04:00
recorded_exits = recorded_exits.reject { |k, v| v == 0 }
2021-06-23 19:38:37 -04:00
recorded_exits.transform_keys! { |k| k.to_s.gsub("exit_", "").to_sym }
# Exits can be specified as a hash of stat-name symbol to integer for exact exits.
# or stat-name symbol to range if the number of side exits might vary (e.g. write
# barriers, cache misses.)
if exits != :any &&
exits != recorded_exits &&
!exits.all? { |k, v| v === recorded_exits[k] } # triple-equal checks range membership or integer equality
2021-06-23 19:38:37 -04:00
flunk "Expected #{exits.empty? ? "no" : exits.inspect} exits" \
", but got\n#{recorded_exits.inspect}"
end
end
# Only available when --enable-yjit=dev
if runtime_stats[:all_stats]
2021-06-23 19:38:37 -04:00
missed_insns = insns.dup
Rust YJIT In December 2021, we opened an [issue] to solicit feedback regarding the porting of the YJIT codebase from C99 to Rust. There were some reservations, but this project was given the go ahead by Ruby core developers and Matz. Since then, we have successfully completed the port of YJIT to Rust. The new Rust version of YJIT has reached parity with the C version, in that it passes all the CRuby tests, is able to run all of the YJIT benchmarks, and performs similarly to the C version (because it works the same way and largely generates the same machine code). We've even incorporated some design improvements, such as a more fine-grained constant invalidation mechanism which we expect will make a big difference in Ruby on Rails applications. Because we want to be careful, YJIT is guarded behind a configure option: ```shell ./configure --enable-yjit # Build YJIT in release mode ./configure --enable-yjit=dev # Build YJIT in dev/debug mode ``` By default, YJIT does not get compiled and cargo/rustc is not required. If YJIT is built in dev mode, then `cargo` is used to fetch development dependencies, but when building in release, `cargo` is not required, only `rustc`. At the moment YJIT requires Rust 1.60.0 or newer. The YJIT command-line options remain mostly unchanged, and more details about the build process are documented in `doc/yjit/yjit.md`. The CI tests have been updated and do not take any more resources than before. The development history of the Rust port is available at the following commit for interested parties: https://github.com/Shopify/ruby/commit/1fd9573d8b4b65219f1c2407f30a0a60e537f8be Our hope is that Rust YJIT will be compiled and included as a part of system packages and compiled binaries of the Ruby 3.2 release. We do not anticipate any major problems as Rust is well supported on every platform which YJIT supports, but to make sure that this process works smoothly, we would like to reach out to those who take care of building systems packages before the 3.2 release is shipped and resolve any issues that may come up. [issue]: https://bugs.ruby-lang.org/issues/18481 Co-authored-by: Maxime Chevalier-Boisvert <maximechevalierb@gmail.com> Co-authored-by: Noah Gibbs <the.codefolio.guy@gmail.com> Co-authored-by: Kevin Newton <kddnewton@gmail.com>
2022-04-19 14:40:21 -04:00
insns_compiled.each do |op|
if missed_insns.include?(op)
2021-06-23 19:38:37 -04:00
# This instruction was compiled
missed_insns.delete(op)
end
end
unless missed_insns.empty?
Rust YJIT In December 2021, we opened an [issue] to solicit feedback regarding the porting of the YJIT codebase from C99 to Rust. There were some reservations, but this project was given the go ahead by Ruby core developers and Matz. Since then, we have successfully completed the port of YJIT to Rust. The new Rust version of YJIT has reached parity with the C version, in that it passes all the CRuby tests, is able to run all of the YJIT benchmarks, and performs similarly to the C version (because it works the same way and largely generates the same machine code). We've even incorporated some design improvements, such as a more fine-grained constant invalidation mechanism which we expect will make a big difference in Ruby on Rails applications. Because we want to be careful, YJIT is guarded behind a configure option: ```shell ./configure --enable-yjit # Build YJIT in release mode ./configure --enable-yjit=dev # Build YJIT in dev/debug mode ``` By default, YJIT does not get compiled and cargo/rustc is not required. If YJIT is built in dev mode, then `cargo` is used to fetch development dependencies, but when building in release, `cargo` is not required, only `rustc`. At the moment YJIT requires Rust 1.60.0 or newer. The YJIT command-line options remain mostly unchanged, and more details about the build process are documented in `doc/yjit/yjit.md`. The CI tests have been updated and do not take any more resources than before. The development history of the Rust port is available at the following commit for interested parties: https://github.com/Shopify/ruby/commit/1fd9573d8b4b65219f1c2407f30a0a60e537f8be Our hope is that Rust YJIT will be compiled and included as a part of system packages and compiled binaries of the Ruby 3.2 release. We do not anticipate any major problems as Rust is well supported on every platform which YJIT supports, but to make sure that this process works smoothly, we would like to reach out to those who take care of building systems packages before the 3.2 release is shipped and resolve any issues that may come up. [issue]: https://bugs.ruby-lang.org/issues/18481 Co-authored-by: Maxime Chevalier-Boisvert <maximechevalierb@gmail.com> Co-authored-by: Noah Gibbs <the.codefolio.guy@gmail.com> Co-authored-by: Kevin Newton <kddnewton@gmail.com>
2022-04-19 14:40:21 -04:00
flunk "Expected to compile instructions #{missed_insns.join(", ")} but didn't.\niseq:\n#{disasm}"
2021-06-23 19:38:37 -04:00
end
end
end
def script_shell_encode(s)
# We can't pass utf-8-encoded characters directly in a shell arg. But we can use Ruby \u constants.
s.chars.map { |c| c.ascii_only? ? c : "\\u%x" % c.codepoints[0] }.join
end
Rust YJIT In December 2021, we opened an [issue] to solicit feedback regarding the porting of the YJIT codebase from C99 to Rust. There were some reservations, but this project was given the go ahead by Ruby core developers and Matz. Since then, we have successfully completed the port of YJIT to Rust. The new Rust version of YJIT has reached parity with the C version, in that it passes all the CRuby tests, is able to run all of the YJIT benchmarks, and performs similarly to the C version (because it works the same way and largely generates the same machine code). We've even incorporated some design improvements, such as a more fine-grained constant invalidation mechanism which we expect will make a big difference in Ruby on Rails applications. Because we want to be careful, YJIT is guarded behind a configure option: ```shell ./configure --enable-yjit # Build YJIT in release mode ./configure --enable-yjit=dev # Build YJIT in dev/debug mode ``` By default, YJIT does not get compiled and cargo/rustc is not required. If YJIT is built in dev mode, then `cargo` is used to fetch development dependencies, but when building in release, `cargo` is not required, only `rustc`. At the moment YJIT requires Rust 1.60.0 or newer. The YJIT command-line options remain mostly unchanged, and more details about the build process are documented in `doc/yjit/yjit.md`. The CI tests have been updated and do not take any more resources than before. The development history of the Rust port is available at the following commit for interested parties: https://github.com/Shopify/ruby/commit/1fd9573d8b4b65219f1c2407f30a0a60e537f8be Our hope is that Rust YJIT will be compiled and included as a part of system packages and compiled binaries of the Ruby 3.2 release. We do not anticipate any major problems as Rust is well supported on every platform which YJIT supports, but to make sure that this process works smoothly, we would like to reach out to those who take care of building systems packages before the 3.2 release is shipped and resolve any issues that may come up. [issue]: https://bugs.ruby-lang.org/issues/18481 Co-authored-by: Maxime Chevalier-Boisvert <maximechevalierb@gmail.com> Co-authored-by: Noah Gibbs <the.codefolio.guy@gmail.com> Co-authored-by: Kevin Newton <kddnewton@gmail.com>
2022-04-19 14:40:21 -04:00
def eval_with_jit(script, call_threshold: 1, timeout: 1000)
2021-06-23 19:38:37 -04:00
args = [
"--disable-gems",
Rust YJIT In December 2021, we opened an [issue] to solicit feedback regarding the porting of the YJIT codebase from C99 to Rust. There were some reservations, but this project was given the go ahead by Ruby core developers and Matz. Since then, we have successfully completed the port of YJIT to Rust. The new Rust version of YJIT has reached parity with the C version, in that it passes all the CRuby tests, is able to run all of the YJIT benchmarks, and performs similarly to the C version (because it works the same way and largely generates the same machine code). We've even incorporated some design improvements, such as a more fine-grained constant invalidation mechanism which we expect will make a big difference in Ruby on Rails applications. Because we want to be careful, YJIT is guarded behind a configure option: ```shell ./configure --enable-yjit # Build YJIT in release mode ./configure --enable-yjit=dev # Build YJIT in dev/debug mode ``` By default, YJIT does not get compiled and cargo/rustc is not required. If YJIT is built in dev mode, then `cargo` is used to fetch development dependencies, but when building in release, `cargo` is not required, only `rustc`. At the moment YJIT requires Rust 1.60.0 or newer. The YJIT command-line options remain mostly unchanged, and more details about the build process are documented in `doc/yjit/yjit.md`. The CI tests have been updated and do not take any more resources than before. The development history of the Rust port is available at the following commit for interested parties: https://github.com/Shopify/ruby/commit/1fd9573d8b4b65219f1c2407f30a0a60e537f8be Our hope is that Rust YJIT will be compiled and included as a part of system packages and compiled binaries of the Ruby 3.2 release. We do not anticipate any major problems as Rust is well supported on every platform which YJIT supports, but to make sure that this process works smoothly, we would like to reach out to those who take care of building systems packages before the 3.2 release is shipped and resolve any issues that may come up. [issue]: https://bugs.ruby-lang.org/issues/18481 Co-authored-by: Maxime Chevalier-Boisvert <maximechevalierb@gmail.com> Co-authored-by: Noah Gibbs <the.codefolio.guy@gmail.com> Co-authored-by: Kevin Newton <kddnewton@gmail.com>
2022-04-19 14:40:21 -04:00
"--yjit-call-threshold=#{call_threshold}",
2021-06-23 19:38:37 -04:00
"--yjit-stats"
]
args << "-e" << script_shell_encode(script)
2021-06-23 19:38:37 -04:00
stats_r, stats_w = IO.pipe
out, err, status = EnvUtil.invoke_ruby(args,
'', true, true, timeout: timeout, ios: {3 => stats_w}
)
stats_w.close
stats = stats_r.read
stats = Marshal.load(stats) if !stats.empty?
stats_r.close
[status, out, err, stats]
end
end