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:
1fd9573d8b

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>
This commit is contained in:
Alan Wu 2022-04-19 14:40:21 -04:00 коммит произвёл Alan Wu
Родитель f553180a86
Коммит f90549cd38
55 изменённых файлов: 16141 добавлений и 11525 удалений

1
.github/CODEOWNERS поставляемый
Просмотреть файл

@ -4,6 +4,7 @@
# YJIT sources and tests
yjit* @maximecb @xrxr @tenderlove
yjit/* @maximecb @xrxr @tenderlove
doc/yjit/* @maximecb @xrxr @tenderlove
bootstraptest/test_yjit* @maximecb @xrxr @tenderlove
test/ruby/test_yjit* @maximecb @xrxr @tenderlove

61
.github/workflows/yjit-ubuntu.yml поставляемый
Просмотреть файл

@ -16,33 +16,51 @@ concurrency:
cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }}
jobs:
cargo:
name: Rust cargo test
# GitHub Action's image seems to already contain a Rust 1.58.0.
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
# For now we can't run cargo test --offline because it complains about the
# capstone dependency, even though the dependency is optional
#- run: cargo test --offline
- run: RUST_BACKTRACE=1 cargo test
working-directory: yjit
# Also compile and test with all features enabled
- run: RUST_BACKTRACE=1 cargo test --all-features
working-directory: yjit
# Check that we can build in release mode too
- run: cargo build --release
working-directory: yjit
make:
strategy:
matrix:
test_task: ["check"] # "test-bundler-parallel",
os:
- ubuntu-20.04
# - ubuntu-18.04
yjit_opts: [
"--yjit",
"--yjit --yjit-call-threshold=1",
]
configure: ["", "cppflags=-DRUBY_DEBUG"]
include:
- test_task: "test-all TESTS=--repeat-count=2"
os: ubuntu-20.04
configure: ""
yjit_enable_env: RUBY_YJIT_ENABLE
- test_task: "test-bundled-gems"
os: ubuntu-20.04
configure: "cppflags=-DRUBY_DEBUG"
yjit_enable_env: RUBY_YJIT_ENABLE
fail-fast: false
matrix:
include:
- test_task: "check-yjit-bindings"
configure: "--with-gcc=clang-12 --enable-yjit=dev"
- test_task: "check"
configure: "--enable-yjit" # release build
- test_task: "check"
configure: "--enable-yjit=dev"
- test_task: "check"
configure: "--enable-yjit=dev"
yjit_opts: "--yjit-call-threshold=1"
- test_task: "test-all TESTS=--repeat-count=2"
configure: "--enable-yjit=dev"
- test_task: "test-bundled-gems"
configure: "--enable-yjit=dev"
env:
GITPULLOPTIONS: --no-tags origin ${{github.ref}}
RUN_OPTS: ${{ matrix.yjit_opts }}
RUBY_DEBUG: ci
runs-on: ${{ matrix.os }}
runs-on: ubuntu-20.04
if: ${{ !startsWith(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }}
steps:
- run: mkdir build
@ -76,7 +94,7 @@ jobs:
- name: Run configure
run: ../src/configure -C --disable-install-doc ${{ matrix.configure }}
- run: make incs
- run: make
- run: make -j
- run: make leaked-globals
if: ${{ matrix.test_task == 'check' }}
- run: make prepare-gems
@ -87,7 +105,6 @@ jobs:
if: ${{ matrix.test_task == 'check' }}
- name: Enable YJIT through ENV
run: echo "RUBY_YJIT_ENABLE=1" >> $GITHUB_ENV
if: ${{ matrix.yjit_enable_env }}
- run: make -s ${{ matrix.test_task }} RUN_OPTS="$RUN_OPTS"
timeout-minutes: 60
env:

38
.github/workflows/yjit_asm_tests.yml поставляемый
Просмотреть файл

@ -1,38 +0,0 @@
name: YJIT x86 assembler tests
on:
push:
paths-ignore:
- 'doc/**'
- '**.md'
- '**.rdoc'
pull_request:
paths-ignore:
- 'doc/**'
- '**.md'
- '**.rdoc'
concurrency:
group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }}
cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }}
jobs:
test:
runs-on: ubuntu-latest
if: ${{ !startsWith(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }}
steps:
- name: Install dependencies
run: |
set -x
sudo apt-get update -q || :
sudo apt-get install --no-install-recommends -q -y build-essential
- name: git config
run: |
git config --global advice.detachedHead 0
git config --global init.defaultBranch garbage
- uses: actions/checkout@v3
with:
path: src
- name: Run ASM tests
run: ./misc/test_yjit_asm.sh
working-directory: src

Просмотреть файл

@ -2057,7 +2057,6 @@ assert_equal '[:itself]', %q{
itself
end
tracing_ractor = Ractor.new do
# 1: start tracing
events = []
@ -2806,3 +2805,48 @@ assert_equal '', %q{
foo
foo
}
# Make sure we're correctly reading RStruct's as.ary union for embedded RStructs
assert_equal '3,12', %q{
pt_struct = Struct.new(:x, :y)
p = pt_struct.new(3, 12)
def pt_inspect(pt)
"#{pt.x},#{pt.y}"
end
# Make sure pt_inspect is JITted
10.times { pt_inspect(p) }
# Make sure it's returning '3,12' instead of e.g. '3,false'
pt_inspect(p)
}
# Regression test for deadlock between branch_stub_hit and ractor_receive_if
assert_equal '10', %q{
r = Ractor.new Ractor.current do |main|
main << 1
main << 2
main << 3
main << 4
main << 5
main << 6
main << 7
main << 8
main << 9
main << 10
end
a = []
a << Ractor.receive_if{|msg| msg == 10}
a << Ractor.receive_if{|msg| msg == 9}
a << Ractor.receive_if{|msg| msg == 8}
a << Ractor.receive_if{|msg| msg == 7}
a << Ractor.receive_if{|msg| msg == 6}
a << Ractor.receive_if{|msg| msg == 5}
a << Ractor.receive_if{|msg| msg == 4}
a << Ractor.receive_if{|msg| msg == 3}
a << Ractor.receive_if{|msg| msg == 2}
a << Ractor.receive_if{|msg| msg == 1}
a.length
}

Просмотреть файл

@ -0,0 +1,422 @@
# Simple tests that we know we can pass
# To keep track of what we got working during the Rust port
# And avoid breaking/losing functionality
#
# Say "Thread" here to dodge WASM CI check. We use ractors here
# which WASM doesn't support and it only greps for "Thread".
# Test for opt_mod
assert_equal '2', %q{
def mod(a, b)
a % b
end
mod(7, 5)
mod(7, 5)
}
# Test for opt_mult
assert_equal '12', %q{
def mult(a, b)
a * b
end
mult(6, 2)
mult(6, 2)
}
# Test for opt_div
assert_equal '3', %q{
def div(a, b)
a / b
end
div(6, 2)
div(6, 2)
}
assert_equal '5', %q{
def plus(a, b)
a + b
end
plus(3, 2)
}
assert_equal '1', %q{
def foo(a, b)
a - b
end
foo(3, 2)
}
assert_equal 'true', %q{
def foo(a, b)
a < b
end
foo(2, 3)
}
# Bitwise left shift
assert_equal '4', %q{
def foo(a, b)
1 << 2
end
foo(1, 2)
}
assert_equal '-7', %q{
def foo(a, b)
-7
end
foo(1, 2)
}
# Putstring
assert_equal 'foo', %q{
def foo(a, b)
"foo"
end
foo(1, 2)
}
assert_equal '-6', %q{
def foo(a, b)
a + -7
end
foo(1, 2)
}
assert_equal 'true', %q{
def foo(a, b)
a == b
end
foo(3, 3)
}
assert_equal 'true', %q{
def foo(a, b)
a < b
end
foo(3, 5)
}
assert_equal '777', %q{
def foo(a)
if a
777
else
333
end
end
foo(true)
}
assert_equal '5', %q{
def foo(a, b)
while a < b
a += 1
end
a
end
foo(1, 5)
}
# opt_aref
assert_equal '2', %q{
def foo(a, b)
a[b]
end
foo([0, 1, 2], 2)
}
# Simple function calls with 0, 1, 2 arguments
assert_equal '-2', %q{
def bar()
-2
end
def foo(a, b)
bar()
end
foo(3, 2)
}
assert_equal '2', %q{
def bar(a)
a
end
def foo(a, b)
bar(b)
end
foo(3, 2)
}
assert_equal '1', %q{
def bar(a, b)
a - b
end
def foo(a, b)
bar(a, b)
end
foo(3, 2)
}
# Regression test for assembler bug
assert_equal '1', %q{
def check_index(index)
if 0x40000000 < index
return -1
end
1
end
check_index 2
}
# Setivar test
assert_equal '2', %q{
class Klass
attr_accessor :a
def set()
@a = 2
end
def get()
@a
end
end
o = Klass.new
o.set()
o.a
}
# Regression for putobject bug
assert_equal '1.5', %q{
def foo(x)
x
end
def bar
foo(1.5)
end
bar()
}
# Getivar with an extended ivar table
assert_equal '3', %q{
class Foo
def initialize
@x1 = 1
@x2 = 1
@x3 = 1
@x4 = 3
end
def bar
@x4
end
end
f = Foo.new
f.bar
}
assert_equal 'true', %q{
x = [[false, true]]
for i, j in x
;
end
j
}
# Regression for getivar
assert_equal '[nil]', %q{
[TrueClass].each do |klass|
klass.class_eval("def foo = @foo")
end
[true].map do |instance|
instance.foo
end
}
# Regression for send
assert_equal 'ok', %q{
def bar(baz: 2)
baz
end
def foo
bar(1, baz: 123)
end
begin
foo
foo
rescue ArgumentError => e
print "ok"
end
}
# Array access regression test
assert_equal '[0, 1, 2, 3, 4, 5]', %q{
def expandarray_useless_splat
arr = [0, 1, 2, 3, 4, 5]
a, * = arr
end
expandarray_useless_splat
}
# Make sure we're correctly reading RStruct's as.ary union for embedded RStructs
assert_equal '3,12', %q{
pt_struct = Struct.new(:x, :y)
p = pt_struct.new(3, 12)
def pt_inspect(pt)
"#{pt.x},#{pt.y}"
end
# Make sure pt_inspect is JITted
10.times { pt_inspect(p) }
# Make sure it's returning '3,12' instead of e.g. '3,false'
pt_inspect(p)
}
assert_equal '2', %q{
def foo(s)
s.foo
end
S = Struct.new(:foo)
foo(S.new(1))
foo(S.new(2))
}
# Try to compile new method while OOM
assert_equal 'ok', %q{
def foo
:ok
end
RubyVM::YJIT.simulate_oom! if defined?(RubyVM::YJIT)
foo
}
# test hitting a branch stub when out of memory
assert_equal 'ok', %q{
def nimai(jita)
if jita
:ng
else
:ok
end
end
nimai(true)
nimai(true)
RubyVM::YJIT.simulate_oom! if defined?(RubyVM::YJIT)
nimai(false)
}
# Ractor.current returns a current ractor
assert_equal 'Ractor', %q{
Ractor.current.class
}
# Ractor.new returns new Ractor
assert_equal 'Ractor', %q{
Ractor.new{}.class
}
# Ractor.allocate is not supported
assert_equal "[:ok, :ok]", %q{
rs = []
begin
Ractor.allocate
rescue => e
rs << :ok if e.message == 'allocator undefined for Ractor'
end
begin
Ractor.new{}.dup
rescue
rs << :ok if e.message == 'allocator undefined for Ractor'
end
rs
}
# A return value of a Ractor block will be a message from the Ractor.
assert_equal 'ok', %q{
# join
r = Ractor.new do
'ok'
end
r.take
}
# Passed arguments to Ractor.new will be a block parameter
# The values are passed with Ractor-communication pass.
assert_equal 'ok', %q{
# ping-pong with arg
r = Ractor.new 'ok' do |msg|
msg
end
r.take
}
# Pass multiple arguments to Ractor.new
assert_equal 'ok', %q{
# ping-pong with two args
r = Ractor.new 'ping', 'pong' do |msg, msg2|
[msg, msg2]
end
'ok' if r.take == ['ping', 'pong']
}
# Ractor#send passes an object with copy to a Ractor
# and Ractor.receive in the Ractor block can receive the passed value.
assert_equal 'ok', %q{
r = Ractor.new do
msg = Ractor.receive
end
r.send 'ok'
r.take
}
assert_equal '[1, 2, 3]', %q{
def foo(arr)
arr << 1
arr << 2
arr << 3
arr
end
def bar()
foo([])
end
bar()
}

Просмотреть файл

@ -153,7 +153,7 @@ COMMONOBJS = array.$(OBJEXT) \
vm_dump.$(OBJEXT) \
vm_sync.$(OBJEXT) \
vm_trace.$(OBJEXT) \
yjit.$(OBJEXT) \
$(YJIT_OBJ) \
$(COROUTINE_OBJ) \
$(DTRACE_OBJ) \
$(BUILTIN_ENCOBJS) \
@ -1974,7 +1974,6 @@ ast.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h
ast.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
ast.$(OBJEXT): {$(VPATH)}builtin.h
ast.$(OBJEXT): {$(VPATH)}config.h
ast.$(OBJEXT): {$(VPATH)}darray.h
ast.$(OBJEXT): {$(VPATH)}defines.h
ast.$(OBJEXT): {$(VPATH)}encoding.h
ast.$(OBJEXT): {$(VPATH)}id.h
@ -2352,7 +2351,6 @@ builtin.$(OBJEXT): {$(VPATH)}builtin.c
builtin.$(OBJEXT): {$(VPATH)}builtin.h
builtin.$(OBJEXT): {$(VPATH)}builtin_binary.inc
builtin.$(OBJEXT): {$(VPATH)}config.h
builtin.$(OBJEXT): {$(VPATH)}darray.h
builtin.$(OBJEXT): {$(VPATH)}defines.h
builtin.$(OBJEXT): {$(VPATH)}id.h
builtin.$(OBJEXT): {$(VPATH)}intern.h
@ -2542,7 +2540,6 @@ class.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
class.$(OBJEXT): {$(VPATH)}class.c
class.$(OBJEXT): {$(VPATH)}config.h
class.$(OBJEXT): {$(VPATH)}constant.h
class.$(OBJEXT): {$(VPATH)}darray.h
class.$(OBJEXT): {$(VPATH)}debug_counter.h
class.$(OBJEXT): {$(VPATH)}defines.h
class.$(OBJEXT): {$(VPATH)}encoding.h
@ -2936,7 +2933,6 @@ compile.$(OBJEXT): {$(VPATH)}builtin.h
compile.$(OBJEXT): {$(VPATH)}compile.c
compile.$(OBJEXT): {$(VPATH)}config.h
compile.$(OBJEXT): {$(VPATH)}constant.h
compile.$(OBJEXT): {$(VPATH)}darray.h
compile.$(OBJEXT): {$(VPATH)}debug_counter.h
compile.$(OBJEXT): {$(VPATH)}defines.h
compile.$(OBJEXT): {$(VPATH)}encindex.h
@ -3327,7 +3323,6 @@ cont.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h
cont.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
cont.$(OBJEXT): {$(VPATH)}config.h
cont.$(OBJEXT): {$(VPATH)}cont.c
cont.$(OBJEXT): {$(VPATH)}darray.h
cont.$(OBJEXT): {$(VPATH)}debug_counter.h
cont.$(OBJEXT): {$(VPATH)}defines.h
cont.$(OBJEXT): {$(VPATH)}eval_intern.h
@ -3519,7 +3514,6 @@ debug.$(OBJEXT): {$(VPATH)}backward/2/long_long.h
debug.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h
debug.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
debug.$(OBJEXT): {$(VPATH)}config.h
debug.$(OBJEXT): {$(VPATH)}darray.h
debug.$(OBJEXT): {$(VPATH)}debug.c
debug.$(OBJEXT): {$(VPATH)}debug_counter.h
debug.$(OBJEXT): {$(VPATH)}defines.h
@ -5980,7 +5974,6 @@ error.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
error.$(OBJEXT): {$(VPATH)}builtin.h
error.$(OBJEXT): {$(VPATH)}config.h
error.$(OBJEXT): {$(VPATH)}constant.h
error.$(OBJEXT): {$(VPATH)}darray.h
error.$(OBJEXT): {$(VPATH)}defines.h
error.$(OBJEXT): {$(VPATH)}encoding.h
error.$(OBJEXT): {$(VPATH)}error.c
@ -6190,7 +6183,6 @@ eval.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h
eval.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
eval.$(OBJEXT): {$(VPATH)}config.h
eval.$(OBJEXT): {$(VPATH)}constant.h
eval.$(OBJEXT): {$(VPATH)}darray.h
eval.$(OBJEXT): {$(VPATH)}debug_counter.h
eval.$(OBJEXT): {$(VPATH)}defines.h
eval.$(OBJEXT): {$(VPATH)}encoding.h
@ -6636,7 +6628,6 @@ gc.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
gc.$(OBJEXT): {$(VPATH)}builtin.h
gc.$(OBJEXT): {$(VPATH)}config.h
gc.$(OBJEXT): {$(VPATH)}constant.h
gc.$(OBJEXT): {$(VPATH)}darray.h
gc.$(OBJEXT): {$(VPATH)}debug.h
gc.$(OBJEXT): {$(VPATH)}debug_counter.h
gc.$(OBJEXT): {$(VPATH)}defines.h
@ -6858,7 +6849,6 @@ goruby.$(OBJEXT): {$(VPATH)}backward/2/long_long.h
goruby.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h
goruby.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
goruby.$(OBJEXT): {$(VPATH)}config.h
goruby.$(OBJEXT): {$(VPATH)}darray.h
goruby.$(OBJEXT): {$(VPATH)}defines.h
goruby.$(OBJEXT): {$(VPATH)}golf_prelude.c
goruby.$(OBJEXT): {$(VPATH)}golf_prelude.rb
@ -7428,7 +7418,6 @@ io.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
io.$(OBJEXT): {$(VPATH)}builtin.h
io.$(OBJEXT): {$(VPATH)}config.h
io.$(OBJEXT): {$(VPATH)}constant.h
io.$(OBJEXT): {$(VPATH)}darray.h
io.$(OBJEXT): {$(VPATH)}defines.h
io.$(OBJEXT): {$(VPATH)}dln.h
io.$(OBJEXT): {$(VPATH)}encindex.h
@ -7826,7 +7815,6 @@ iseq.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
iseq.$(OBJEXT): {$(VPATH)}builtin.h
iseq.$(OBJEXT): {$(VPATH)}config.h
iseq.$(OBJEXT): {$(VPATH)}constant.h
iseq.$(OBJEXT): {$(VPATH)}darray.h
iseq.$(OBJEXT): {$(VPATH)}debug_counter.h
iseq.$(OBJEXT): {$(VPATH)}defines.h
iseq.$(OBJEXT): {$(VPATH)}encoding.h
@ -9270,7 +9258,6 @@ miniinit.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h
miniinit.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
miniinit.$(OBJEXT): {$(VPATH)}builtin.h
miniinit.$(OBJEXT): {$(VPATH)}config.h
miniinit.$(OBJEXT): {$(VPATH)}darray.h
miniinit.$(OBJEXT): {$(VPATH)}defines.h
miniinit.$(OBJEXT): {$(VPATH)}dir.rb
miniinit.$(OBJEXT): {$(VPATH)}encoding.h
@ -9492,7 +9479,6 @@ mjit.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
mjit.$(OBJEXT): {$(VPATH)}builtin.h
mjit.$(OBJEXT): {$(VPATH)}config.h
mjit.$(OBJEXT): {$(VPATH)}constant.h
mjit.$(OBJEXT): {$(VPATH)}darray.h
mjit.$(OBJEXT): {$(VPATH)}debug.h
mjit.$(OBJEXT): {$(VPATH)}debug_counter.h
mjit.$(OBJEXT): {$(VPATH)}defines.h
@ -9712,7 +9698,6 @@ mjit_compile.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
mjit_compile.$(OBJEXT): {$(VPATH)}builtin.h
mjit_compile.$(OBJEXT): {$(VPATH)}config.h
mjit_compile.$(OBJEXT): {$(VPATH)}constant.h
mjit_compile.$(OBJEXT): {$(VPATH)}darray.h
mjit_compile.$(OBJEXT): {$(VPATH)}debug_counter.h
mjit_compile.$(OBJEXT): {$(VPATH)}defines.h
mjit_compile.$(OBJEXT): {$(VPATH)}id.h
@ -9909,7 +9894,6 @@ node.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h
node.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
node.$(OBJEXT): {$(VPATH)}config.h
node.$(OBJEXT): {$(VPATH)}constant.h
node.$(OBJEXT): {$(VPATH)}darray.h
node.$(OBJEXT): {$(VPATH)}defines.h
node.$(OBJEXT): {$(VPATH)}id.h
node.$(OBJEXT): {$(VPATH)}id_table.h
@ -10896,7 +10880,6 @@ proc.$(OBJEXT): {$(VPATH)}backward/2/long_long.h
proc.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h
proc.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
proc.$(OBJEXT): {$(VPATH)}config.h
proc.$(OBJEXT): {$(VPATH)}darray.h
proc.$(OBJEXT): {$(VPATH)}defines.h
proc.$(OBJEXT): {$(VPATH)}encoding.h
proc.$(OBJEXT): {$(VPATH)}eval_intern.h
@ -11112,7 +11095,6 @@ process.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h
process.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
process.$(OBJEXT): {$(VPATH)}config.h
process.$(OBJEXT): {$(VPATH)}constant.h
process.$(OBJEXT): {$(VPATH)}darray.h
process.$(OBJEXT): {$(VPATH)}debug_counter.h
process.$(OBJEXT): {$(VPATH)}defines.h
process.$(OBJEXT): {$(VPATH)}dln.h
@ -11329,7 +11311,6 @@ ractor.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h
ractor.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
ractor.$(OBJEXT): {$(VPATH)}builtin.h
ractor.$(OBJEXT): {$(VPATH)}config.h
ractor.$(OBJEXT): {$(VPATH)}darray.h
ractor.$(OBJEXT): {$(VPATH)}debug_counter.h
ractor.$(OBJEXT): {$(VPATH)}defines.h
ractor.$(OBJEXT): {$(VPATH)}encoding.h
@ -13276,7 +13257,6 @@ ruby.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h
ruby.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
ruby.$(OBJEXT): {$(VPATH)}config.h
ruby.$(OBJEXT): {$(VPATH)}constant.h
ruby.$(OBJEXT): {$(VPATH)}darray.h
ruby.$(OBJEXT): {$(VPATH)}debug_counter.h
ruby.$(OBJEXT): {$(VPATH)}defines.h
ruby.$(OBJEXT): {$(VPATH)}dln.h
@ -13480,7 +13460,6 @@ scheduler.$(OBJEXT): {$(VPATH)}backward/2/long_long.h
scheduler.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h
scheduler.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
scheduler.$(OBJEXT): {$(VPATH)}config.h
scheduler.$(OBJEXT): {$(VPATH)}darray.h
scheduler.$(OBJEXT): {$(VPATH)}defines.h
scheduler.$(OBJEXT): {$(VPATH)}encoding.h
scheduler.$(OBJEXT): {$(VPATH)}fiber/scheduler.h
@ -13841,7 +13820,6 @@ signal.$(OBJEXT): {$(VPATH)}backward/2/long_long.h
signal.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h
signal.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
signal.$(OBJEXT): {$(VPATH)}config.h
signal.$(OBJEXT): {$(VPATH)}darray.h
signal.$(OBJEXT): {$(VPATH)}debug_counter.h
signal.$(OBJEXT): {$(VPATH)}defines.h
signal.$(OBJEXT): {$(VPATH)}encoding.h
@ -14826,7 +14804,6 @@ struct.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h
struct.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
struct.$(OBJEXT): {$(VPATH)}builtin.h
struct.$(OBJEXT): {$(VPATH)}config.h
struct.$(OBJEXT): {$(VPATH)}darray.h
struct.$(OBJEXT): {$(VPATH)}defines.h
struct.$(OBJEXT): {$(VPATH)}encoding.h
struct.$(OBJEXT): {$(VPATH)}id.h
@ -15230,7 +15207,6 @@ thread.$(OBJEXT): {$(VPATH)}backward/2/long_long.h
thread.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h
thread.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
thread.$(OBJEXT): {$(VPATH)}config.h
thread.$(OBJEXT): {$(VPATH)}darray.h
thread.$(OBJEXT): {$(VPATH)}debug.h
thread.$(OBJEXT): {$(VPATH)}debug_counter.h
thread.$(OBJEXT): {$(VPATH)}defines.h
@ -16176,7 +16152,6 @@ variable.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h
variable.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
variable.$(OBJEXT): {$(VPATH)}config.h
variable.$(OBJEXT): {$(VPATH)}constant.h
variable.$(OBJEXT): {$(VPATH)}darray.h
variable.$(OBJEXT): {$(VPATH)}debug_counter.h
variable.$(OBJEXT): {$(VPATH)}defines.h
variable.$(OBJEXT): {$(VPATH)}encoding.h
@ -16383,7 +16358,6 @@ version.$(OBJEXT): {$(VPATH)}backward/2/long_long.h
version.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h
version.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
version.$(OBJEXT): {$(VPATH)}config.h
version.$(OBJEXT): {$(VPATH)}darray.h
version.$(OBJEXT): {$(VPATH)}debug_counter.h
version.$(OBJEXT): {$(VPATH)}defines.h
version.$(OBJEXT): {$(VPATH)}id.h
@ -16594,7 +16568,6 @@ vm.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
vm.$(OBJEXT): {$(VPATH)}builtin.h
vm.$(OBJEXT): {$(VPATH)}config.h
vm.$(OBJEXT): {$(VPATH)}constant.h
vm.$(OBJEXT): {$(VPATH)}darray.h
vm.$(OBJEXT): {$(VPATH)}debug_counter.h
vm.$(OBJEXT): {$(VPATH)}defines.h
vm.$(OBJEXT): {$(VPATH)}defs/opt_operand.def
@ -16821,7 +16794,6 @@ vm_backtrace.$(OBJEXT): {$(VPATH)}backward/2/long_long.h
vm_backtrace.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h
vm_backtrace.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
vm_backtrace.$(OBJEXT): {$(VPATH)}config.h
vm_backtrace.$(OBJEXT): {$(VPATH)}darray.h
vm_backtrace.$(OBJEXT): {$(VPATH)}debug.h
vm_backtrace.$(OBJEXT): {$(VPATH)}defines.h
vm_backtrace.$(OBJEXT): {$(VPATH)}encoding.h
@ -17021,7 +16993,6 @@ vm_dump.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h
vm_dump.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
vm_dump.$(OBJEXT): {$(VPATH)}config.h
vm_dump.$(OBJEXT): {$(VPATH)}constant.h
vm_dump.$(OBJEXT): {$(VPATH)}darray.h
vm_dump.$(OBJEXT): {$(VPATH)}defines.h
vm_dump.$(OBJEXT): {$(VPATH)}gc.h
vm_dump.$(OBJEXT): {$(VPATH)}id.h
@ -17210,7 +17181,6 @@ vm_sync.$(OBJEXT): {$(VPATH)}backward/2/long_long.h
vm_sync.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h
vm_sync.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
vm_sync.$(OBJEXT): {$(VPATH)}config.h
vm_sync.$(OBJEXT): {$(VPATH)}darray.h
vm_sync.$(OBJEXT): {$(VPATH)}debug_counter.h
vm_sync.$(OBJEXT): {$(VPATH)}defines.h
vm_sync.$(OBJEXT): {$(VPATH)}gc.h
@ -17403,7 +17373,6 @@ vm_trace.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h
vm_trace.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
vm_trace.$(OBJEXT): {$(VPATH)}builtin.h
vm_trace.$(OBJEXT): {$(VPATH)}config.h
vm_trace.$(OBJEXT): {$(VPATH)}darray.h
vm_trace.$(OBJEXT): {$(VPATH)}debug.h
vm_trace.$(OBJEXT): {$(VPATH)}debug_counter.h
vm_trace.$(OBJEXT): {$(VPATH)}defines.h
@ -17616,7 +17585,6 @@ yjit.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
yjit.$(OBJEXT): {$(VPATH)}builtin.h
yjit.$(OBJEXT): {$(VPATH)}config.h
yjit.$(OBJEXT): {$(VPATH)}constant.h
yjit.$(OBJEXT): {$(VPATH)}darray.h
yjit.$(OBJEXT): {$(VPATH)}debug_counter.h
yjit.$(OBJEXT): {$(VPATH)}defines.h
yjit.$(OBJEXT): {$(VPATH)}encoding.h
@ -17801,13 +17769,4 @@ yjit.$(OBJEXT): {$(VPATH)}yjit.c
yjit.$(OBJEXT): {$(VPATH)}yjit.h
yjit.$(OBJEXT): {$(VPATH)}yjit.rb
yjit.$(OBJEXT): {$(VPATH)}yjit.rbinc
yjit.$(OBJEXT): {$(VPATH)}yjit_asm.c
yjit.$(OBJEXT): {$(VPATH)}yjit_asm.h
yjit.$(OBJEXT): {$(VPATH)}yjit_codegen.c
yjit.$(OBJEXT): {$(VPATH)}yjit_codegen.h
yjit.$(OBJEXT): {$(VPATH)}yjit_core.c
yjit.$(OBJEXT): {$(VPATH)}yjit_core.h
yjit.$(OBJEXT): {$(VPATH)}yjit_iface.c
yjit.$(OBJEXT): {$(VPATH)}yjit_iface.h
yjit.$(OBJEXT): {$(VPATH)}yjit_utils.c
# AUTOGENERATED DEPENDENCIES END

Просмотреть файл

@ -3749,6 +3749,47 @@ AS_IF([test x"$MJIT_SUPPORT" = "xyes"],
AC_SUBST(MJIT_SUPPORT)
AC_ARG_ENABLE(yjit,
AS_HELP_STRING([--enable-yjit],
[enable experimental in-process JIT compiler that requires Rust build tools [default=no]]),
[YJIT_SUPPORT=$enableval], [YJIT_SUPPORT=no])
CARGO=
CARGO_BUILD_ARGS=
YJIT_LIBS=
AS_CASE(["${YJIT_SUPPORT}"],
[yes|dev], [
AS_IF([test x"$enable_jit_support" = "xno"],
AC_MSG_ERROR([--disable-jit-support but --enable-yjit. YJIT requires JIT support])
)
AC_CHECK_TOOL(RUSTC, [rustc], [no])
AS_IF([test x"$RUSTC" = "xno"],
AC_MSG_ERROR([rustc is required. Installation instructions available at https://www.rust-lang.org/tools/install])
)
AS_IF([test x"$YJIT_SUPPORT" = "xyes"],
[rb_rust_target_subdir=release
CARGO_BUILD_ARGS='--release'],
[rb_rust_target_subdir=debug
CARGO_BUILD_ARGS='--features stats,disasm,asm_comments'
AC_CHECK_TOOL(CARGO, [cargo], [no])
AS_IF([test x"$CARGO" = "xno"],
AC_MSG_ERROR([cargo is required. Installation instructions available at https://www.rust-lang.org/tools/install])
)
AC_DEFINE(RUBY_DEBUG, 1)])
YJIT_LIBS="yjit/target/${rb_rust_target_subdir}/libyjit.a"
YJIT_OBJ='yjit.$(OBJEXT)'
AC_DEFINE(USE_YJIT, 1)
], [AC_DEFINE(USE_YJIT, 0)])
dnl These variables end up in ::RbConfig::CONFIG
AC_SUBST(YJIT_SUPPORT)dnl what flavor of YJIT the Ruby build includes
AC_SUBST(RUSTC)dnl Rust compiler command
AC_SUBST(CARGO)dnl Cargo command for Rust builds
AC_SUBST(CARGO_BUILD_ARGS)dnl for selecting Rust build profiles
AC_SUBST(YJIT_LIBS)dnl for optionally building the Rust parts of YJIT
AC_SUBST(YJIT_OBJ)dnl for optionally building the C parts of YJIT
AC_ARG_ENABLE(install-static-library,
AS_HELP_STRING([--disable-install-static-library], [do not install static ruby library]),
[INSTALL_STATIC_LIBRARY=$enableval
@ -4401,6 +4442,7 @@ config_summary "warnflags" "$warnflags"
config_summary "strip command" "$STRIP"
config_summary "install doc" "$DOCTARGETS"
config_summary "JIT support" "$MJIT_SUPPORT"
config_summary "YJIT support" "$YJIT_SUPPORT"
config_summary "man page type" "$MANTYPE"
config_summary "search path" "$search_path"
config_summary "static-linked-ext" ${EXTSTATIC:+"yes"}

Просмотреть файл

@ -357,6 +357,61 @@ ifneq ($(REVISION_IN_HEADER),$(REVISION_LATEST))
$(srcdir)/revision.h: $(REVISION_H)
endif
# Show Cargo progress when doing `make V=1`
CARGO_VERBOSE_0 = -q
CARGO_VERBOSE_1 =
CARGO_VERBOSE = $(CARGO_VERBOSE_$(V))
# Select between different build profiles with macro substitution
.PHONY: yjit-static-lib
yjit-static-lib: yjit-static-lib-$(YJIT_SUPPORT)
# YJIT_SUPPORT=yes when `configure` gets `--enable-yjit`
yjit-static-lib-yes:
$(ECHO) 'building Rust YJIT (release mode)'
$(Q) $(RUSTC) \
--crate-name=yjit \
--crate-type=staticlib \
--edition=2021 \
-C opt-level=3 \
-C overflow-checks=on \
'--out-dir=$(CARGO_TARGET_DIR)/release/' \
$(top_srcdir)/yjit/src/lib.rs
yjit-static-lib-no:
$(ECHO) 'Error: Tried to build YJIT without configuring it first. Check `make showconfig`?'
@false
yjit-static-lib-dev:
$(ECHO) 'building Rust YJIT (dev mode)'
$(Q) cd $(top_srcdir)/yjit && \
CARGO_TARGET_DIR='$(CARGO_TARGET_DIR)' \
CARGO_TERM_PROGRESS_WHEN='never' \
$(CARGO) $(CARGO_VERBOSE) build $(CARGO_BUILD_ARGS)
# This PHONY prerequisite makes it so that we always run cargo. When there are
# no Rust changes on rebuild, Cargo does not touch the mtime of the static
# library and GNU make avoids relinking. $(empty) seems to be important to
# trigger rebuild each time in release mode.
$(YJIT_LIBS): yjit-static-lib
$(empty)
# Put this here instead of in common.mk to avoid breaking nmake builds
# TODO: might need to move for BSD Make support
miniruby$(EXEEXT): $(YJIT_LIBS)
# Generate Rust bindings. See source for details.
# Needs `./configure --enable-yjit=dev` and Clang.
ifneq ($(strip $(CARGO)),) # if configure found Cargo
.PHONY: yjit-bindgen
yjit-bindgen: yjit.$(OBJEXT)
YJIT_SRC_ROOT_PATH='$(top_srcdir)' $(CARGO) run --manifest-path '$(top_srcdir)/yjit/bindgen/Cargo.toml' -- $(CFLAGS) $(XCFLAGS) $(CPPFLAGS)
# For CI, check whether YJIT's FFI bindings are up-to-date.
check-yjit-bindings: yjit-bindgen
git -C "$(top_srcdir)" diff --exit-code yjit/src/cruby_bindings.inc.rs
endif
# Query on the generated rdoc
#
# $ make rdoc:Integer#+

Просмотреть файл

@ -51,39 +51,55 @@ Because there is no GC for generated code yet, your software could run out of ex
## Installation
Current YJIT versions are installed by default with CRuby. Make sure to specify the "--yjit" command line option to enable it at runtime.
### Requirements
Experimental YJIT patches that have not yet been merged with CRuby can be found in ruby-build:
You will need to install:
- A C compiler such as GCC or Clang
- GNU Make and Autoconf
- The Rust compiler `rustc` and Cargo (if you want to build in dev/debug mode)
To install the Rust build toolchain, we suggest following the [recommended installation method][rust-install]. Rust also provides first class [support][editor-tools] for many source code editors.
[rust-install]: https://www.rust-lang.org/tools/install
[editor-tools]: https://www.rust-lang.org/tools
### Building YJIT
Start by cloning the `ruby/ruby` repository:
```
ruby-build yjit-dev ~/.rubies/ruby-yjit-dev
```
They can also be found in the Shopify/yjit repository, which is cloned and build like CRuby.
Start by cloning the `Shopify/yjit` repository:
```
git clone https://github.com/Shopify/yjit
git clone https://github.com/ruby/ruby yjit
cd yjit
```
The YJIT `ruby` binary can be built with either GCC or Clang. For development, we recommend enabling debug symbols so that assertions are enabled as this makes debugging easier. Enabling debug mode will also make it possible for you to disassemble code generated by YJIT. However, this causes a performance hit. For maximum performance, compile with GCC, without the `-DRUBY_DEBUG` or `-DYJIT_STATS` build options. More detailed build instructions are provided in the [Ruby README](https://github.com/ruby/ruby#how-to-compile-and-install).
To support disassembly of the generated code, `libcapstone` is also required (`brew install capstone` on MacOS, `sudo apt-get install -y libcapstone-dev` on Ubuntu/Debian and `sudo dnf -y install capstone-devel` on Fedora).
The YJIT `ruby` binary can be built with either GCC or Clang. It can be built either in dev (debug) mode or in release mode. For maximum performance, compile YJIT in release mode with GCC. More detailed build instructions are provided in the [Ruby README](https://github.com/ruby/ruby#how-to-compile-and-install). To support disassembly of the generated code, `libcapstone` is also required (`brew install capstone` on MacOS, `sudo apt-get install -y libcapstone-dev` on Ubuntu/Debian and `sudo dnf -y install capstone-devel` on Fedora).
```
# Configure with debugging/stats options for development, build and install
# Configure in release mode for maximum performance, build and install
./autogen.sh
./configure cppflags="-DRUBY_DEBUG -DYJIT_STATS" --prefix=$HOME/.rubies/ruby-yjit --disable-install-doc --disable--install-rdoc
make -j16 install
./configure --enable-yjit --prefix=$HOME/.rubies/ruby-yjit --disable-install-doc --disable--install-rdoc
make -j install
```
or
```
# Configure in dev (debug) mode for development, build and install
./autogen.sh
./configure --enable-yjit=dev --prefix=$HOME/.rubies/ruby-yjit --disable-install-doc --disable--install-rdoc
make -j install
```
On macOS, you may need to specify where to find openssl, libyaml and gdbm:
```
# Configure with debugging/stats options for development, build and install
./configure cppflags="-DRUBY_DEBUG -DYJIT_STATS" --prefix=$HOME/.rubies/ruby-yjit --disable-install-doc --disable--install-rdoc --with-opt-dir=$(brew --prefix openssl):$(brew --prefix readline):$(brew --prefix libyaml):$(brew --prefix gdbm)
make -j16 install
# Install dependencies
brew install openssl readline libyaml
# Configure in dev (debug) mode for development, build and install
./autogen.sh
./configure --enable-yjit=dev --prefix=$HOME/.rubies/ruby-yjit --disable-install-doc --disable--install-rdoc --with-opt-dir=$(brew --prefix openssl):$(brew --prefix readline):$(brew --prefix libyaml)
make -j install
```
Typically configure will choose default C compiler. To specify the C compiler, use
@ -100,7 +116,7 @@ You can test that YJIT works correctly by running:
make btest
# Complete set of tests
make -j16 test-all
make -j test-all
```
## Usage
@ -128,10 +144,10 @@ The machine code generated for a given method can be printed by adding `puts Rub
YJIT supports all command-line options supported by upstream CRuby, but also adds a few YJIT-specific options:
- `--disable-yjit`: turn off YJIT (enabled by default)
- `--yjit-stats`: produce statistics after the execution of a program (must compile with `cppflags=-DRUBY_DEBUG` to use this)
- `--yjit-exec-mem-size=N`: size of the executable memory block to allocate, in MiB (default 256 MiB)
- `--yjit`: enable YJIT (disabled by default)
- `--yjit-call-threshold=N`: number of calls after which YJIT begins to compile a function (default 2)
- `--yjit-exec-mem-size=N`: size of the executable memory block to allocate, in MiB (default 256 MiB)
- `--yjit-stats`: produce statistics after the execution of a program (must compile with `cppflags=-DRUBY_DEBUG` to use this)
- `--yjit-max-versions=N`: maximum number of versions to generate per basic block (default 4)
- `--yjit-greedy-versioning`: greedy versioning mode (disabled by default, may increase code size)
@ -215,12 +231,16 @@ you can contribute things we will want to merge into YJIT.
### Source Code Organization
The YJIT source code is divided between:
- `yjit_asm.c`: x86 in-memory assembler we use to generate machine code
- `yjit_codegen.c`: logic for translating Ruby bytecode to machine code
- `yjit_core.c`: basic block versioning logic, core structure of YJIT
- `yjit_iface.c`: code YJIT uses to interface with the rest of CRuby
- `yjit.c`: code YJIT uses to interface with the rest of CRuby
- `yjit.h`: C definitions YJIT exposes to the rest of the CRuby
- `yjit.rb`: `YJIT` Ruby module that is exposed to Ruby
- `yjit/src/asm/*`: in-memory assembler we use to generate machine code
- `yjit/src/codegen.rs`: logic for translating Ruby bytecode to machine code
- `yjit/src/core.rb`: basic block versioning logic, core structure of YJIT
- `yjit/src/stats.rs`: gathering of run-time statistics
- `yjit/src/options.rs`: handling of command-line options
- `yjit/bindgen/src/main.rs`: C bindings exposed to the Rust codebase through bindgen
- `yjit/src/cruby.rs`: C bindings manually exposed to the Rust codebase
- `misc/test_yjit_asm.sh`: script to compile and run the in-memory assembler tests
- `misc/yjit_asm_tests.c`: tests for the in-memory assembler
@ -229,6 +249,20 @@ The core of CRuby's interpreter logic is found in:
- `vm_insnshelper.c`: logic used by Ruby's bytecode instructions
- `vm_exec.c`: Ruby interpreter loop
### Generating C bindings with bindgen
In order to expose C functions to the Rust codebase, you will need to generate C bindings:
```sh
CC=clang ./configure --enable-yjit=dev
make -j yjit-bindgen
```
This uses the bindgen tools to generate/update `yjit/src/cruby_bindings.inc.rs` based on the
bindings listed in `yjit/bindgen/src/main.rs`. Avoid manually editing this file
as it could be automatically regenerated at a later time. If you need to manually add C bindings,
add them to `yjit/cruby.rs` instead.
### Coding & Debugging Protips
There are 3 test suites:
@ -240,7 +274,7 @@ There are 3 test suites:
The tests can be run in parallel like this:
```
make -j16 test-all RUN_OPTS="--yjit-call-threshold=1"
make -j test-all RUN_OPTS="--yjit-call-threshold=1"
```
Or single-threaded like this, to more easily identify which specific test is failing:
@ -273,3 +307,46 @@ You can use the Intel syntax for disassembly in LLDB, keeping it consistent with
```
echo "settings set target.x86-disassembly-flavor intel" >> ~/.lldbinit
```
## Running YJIT on M1
It is possible to run YJIT on an Apple M1 via Rosetta. You can find basic
instructions below, but there are a few caveats listed further down.
First, install Rosetta:
```
$ softwareupdate --install-rosetta
```
Now any command can be run with Rosetta via the `arch` command line tool.
Then you can start your shell in an x86 environment:
```
$ arch -x86_64 zsh
```
You can double check your current architecture via the `arch` command:
```
$ arch -x86_64 zsh
$ arch
i386
```
You may need to set the default target for `rustc` to x86-64, e.g.
```
$ rustup default stable-x86_64-apple-darwin
```
While in your i386 shell, install Cargo and Homebrew, then hack away!
### M1 Caveats
1. You must install a version of Homebrew for each architecture
2. Cargo will install in $HOME/.cargo by default, and I don't know a good way to change architectures after install
3. `dev` won't work if you have i386 Homebrew installed on an M1
If you use Fish shell you can [read this link](https://tenderlovemaking.com/2022/01/07/homebrew-rosetta-and-ruby.html) for information on making the dev environment easier.

Просмотреть файл

@ -165,7 +165,6 @@ coverage.o: $(top_srcdir)/ccan/check_type/check_type.h
coverage.o: $(top_srcdir)/ccan/container_of/container_of.h
coverage.o: $(top_srcdir)/ccan/list/list.h
coverage.o: $(top_srcdir)/ccan/str/str.h
coverage.o: $(top_srcdir)/darray.h
coverage.o: $(top_srcdir)/gc.h
coverage.o: $(top_srcdir)/internal.h
coverage.o: $(top_srcdir)/internal/array.h

Просмотреть файл

@ -533,7 +533,6 @@ objspace_dump.o: $(top_srcdir)/ccan/check_type/check_type.h
objspace_dump.o: $(top_srcdir)/ccan/container_of/container_of.h
objspace_dump.o: $(top_srcdir)/ccan/list/list.h
objspace_dump.o: $(top_srcdir)/ccan/str/str.h
objspace_dump.o: $(top_srcdir)/darray.h
objspace_dump.o: $(top_srcdir)/gc.h
objspace_dump.o: $(top_srcdir)/internal.h
objspace_dump.o: $(top_srcdir)/internal/array.h

Просмотреть файл

@ -26,9 +26,6 @@ typedef struct ruby_cmdline_options {
#if USE_MJIT
struct mjit_options mjit;
#endif
#if YJIT_SUPPORTED_P
struct rb_yjit_options yjit;
#endif
int sflag, xflag;
unsigned int warning: 1;

12
iseq.c
Просмотреть файл

@ -175,7 +175,9 @@ rb_iseq_free(const rb_iseq_t *iseq)
iseq_clear_ic_references(iseq);
struct rb_iseq_constant_body *const body = ISEQ_BODY(iseq);
mjit_free_iseq(iseq); /* Notify MJIT */
rb_yjit_iseq_free(body);
#if YJIT_BUILD
rb_yjit_iseq_free(body->yjit_payload);
#endif
ruby_xfree((void *)body->iseq_encoded);
ruby_xfree((void *)body->insns_info.body);
if (body->insns_info.positions) ruby_xfree((void *)body->insns_info.positions);
@ -439,7 +441,9 @@ rb_iseq_update_references(rb_iseq_t *iseq)
#if USE_MJIT
mjit_update_references(iseq);
#endif
rb_yjit_iseq_update_references(body);
#if YJIT_BUILD
rb_yjit_iseq_update_references(body->yjit_payload);
#endif
}
}
@ -527,7 +531,9 @@ rb_iseq_mark(const rb_iseq_t *iseq)
#if USE_MJIT
mjit_mark_cc_entries(body);
#endif
rb_yjit_iseq_mark(body);
#if YJIT_BUILD
rb_yjit_iseq_mark(body->yjit_payload);
#endif
}
if (FL_TEST_RAW((VALUE)iseq, ISEQ_NOT_LOADED_YET)) {

1
load.c
Просмотреть файл

@ -14,6 +14,7 @@
#include "internal/variable.h"
#include "iseq.h"
#include "probes.h"
#include "darray.h"
#include "ruby/encoding.h"
#include "ruby/util.h"

Просмотреть файл

@ -1,10 +0,0 @@
#!/bin/sh
set -e
set -x
clang -std=gnu99 -Wall -Werror -Wno-error=unused-function -Wshorten-64-to-32 -I "${0%/*/*}" "${0%/*}/yjit_asm_tests.c" -o asm_test
./asm_test
rm asm_test

Просмотреть файл

@ -1,443 +0,0 @@
// For MAP_ANONYMOUS on GNU/Linux
#define _GNU_SOURCE 1
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
// This test executable doesn't compile with the rest of Ruby
// so we need to define a rb_bug().
_Noreturn
static void rb_bug(const char *message, ...)
{
va_list args;
va_start(args, message);
vfprintf(stderr, message, args);
va_end(args);
abort();
}
#include "yjit_asm.c"
// Print the bytes in a code block
void print_bytes(codeblock_t* cb)
{
for (uint32_t i = 0; i < cb->write_pos; ++i)
{
printf("%02X", (int)*cb_get_ptr(cb, i));
}
printf("\n");
}
// Check that the code block contains the given sequence of bytes
void check_bytes(codeblock_t* cb, const char* bytes)
{
printf("checking encoding: %s\n", bytes);
size_t len = strlen(bytes);
assert (len % 2 == 0);
size_t num_bytes = len / 2;
if (cb->write_pos != num_bytes)
{
fprintf(stderr, "incorrect encoding length, expected %ld, got %d\n",
num_bytes,
cb->write_pos
);
printf("%s\n", bytes);
print_bytes(cb);
exit(-1);
}
for (uint32_t i = 0; i < num_bytes; ++i)
{
char byte_str[] = {0, 0, 0, 0};
strncpy(byte_str, bytes + (2 * i), 2);
char* endptr;
long int byte = strtol(byte_str, &endptr, 16);
uint8_t cb_byte = *cb_get_ptr(cb, i);
if (cb_byte != byte)
{
fprintf(stderr, "incorrect encoding at position %d, expected %02X, got %02X\n",
i,
(int)byte,
(int)cb_byte
);
printf("%s\n", bytes);
print_bytes(cb);
exit(-1);
}
}
}
void run_assembler_tests(void)
{
printf("Running assembler tests\n");
codeblock_t cb_obj;
codeblock_t* cb = &cb_obj;
uint8_t* mem_block = alloc_exec_mem(4096);
cb_init(cb, mem_block, 4096);
// add
cb_set_pos(cb, 0); add(cb, CL, imm_opnd(3)); check_bytes(cb, "80C103");
cb_set_pos(cb, 0); add(cb, CL, BL); check_bytes(cb, "00D9");
cb_set_pos(cb, 0); add(cb, CL, SPL); check_bytes(cb, "4000E1");
cb_set_pos(cb, 0); add(cb, CX, BX); check_bytes(cb, "6601D9");
cb_set_pos(cb, 0); add(cb, RAX, RBX); check_bytes(cb, "4801D8");
cb_set_pos(cb, 0); add(cb, ECX, EDX); check_bytes(cb, "01D1");
cb_set_pos(cb, 0); add(cb, RDX, R14); check_bytes(cb, "4C01F2");
cb_set_pos(cb, 0); add(cb, mem_opnd(64, RAX, 0), RDX); check_bytes(cb, "480110");
cb_set_pos(cb, 0); add(cb, RDX, mem_opnd(64, RAX, 0)); check_bytes(cb, "480310");
cb_set_pos(cb, 0); add(cb, RDX, mem_opnd(64, RAX, 8)); check_bytes(cb, "48035008");
cb_set_pos(cb, 0); add(cb, RDX, mem_opnd(64, RAX, 255)); check_bytes(cb, "480390FF000000");
cb_set_pos(cb, 0); add(cb, mem_opnd(64, RAX, 127), imm_opnd(255)); check_bytes(cb, "4881407FFF000000");
cb_set_pos(cb, 0); add(cb, mem_opnd(32, RAX, 0), EDX); check_bytes(cb, "0110");
cb_set_pos(cb, 0); add(cb, RSP, imm_opnd(8)); check_bytes(cb, "4883C408");
cb_set_pos(cb, 0); add(cb, ECX, imm_opnd(8)); check_bytes(cb, "83C108");
cb_set_pos(cb, 0); add(cb, ECX, imm_opnd(255)); check_bytes(cb, "81C1FF000000");
// and
cb_set_pos(cb, 0); and(cb, EBP, R12D); check_bytes(cb, "4421E5");
cb_set_pos(cb, 0); and(cb, mem_opnd(64, RAX, 0), imm_opnd(0x08)); check_bytes(cb, "48832008");
// call
{
cb_set_pos(cb, 0);
uint32_t fn_label = cb_new_label(cb, "foo");
call_label(cb, fn_label);
cb_link_labels(cb);
check_bytes(cb, "E8FBFFFFFF");
}
cb_set_pos(cb, 0); call(cb, RAX); check_bytes(cb, "FFD0");
cb_set_pos(cb, 0); call(cb, mem_opnd(64, RSP, 8)); check_bytes(cb, "FF542408");
// cmovcc
cb_set_pos(cb, 0); cmovg(cb, ESI, EDI); check_bytes(cb, "0F4FF7");
cb_set_pos(cb, 0); cmovg(cb, ESI, mem_opnd(32, RBP, 12)); check_bytes(cb, "0F4F750C");
cb_set_pos(cb, 0); cmovl(cb, EAX, ECX); check_bytes(cb, "0F4CC1");
cb_set_pos(cb, 0); cmovl(cb, RBX, RBP); check_bytes(cb, "480F4CDD");
cb_set_pos(cb, 0); cmovle(cb, ESI, mem_opnd(32, RSP, 4)); check_bytes(cb, "0F4E742404");
// cmp
cb_set_pos(cb, 0); cmp(cb, CL, DL); check_bytes(cb, "38D1");
cb_set_pos(cb, 0); cmp(cb, ECX, EDI); check_bytes(cb, "39F9");
cb_set_pos(cb, 0); cmp(cb, RDX, mem_opnd(64, R12, 0)); check_bytes(cb, "493B1424");
cb_set_pos(cb, 0); cmp(cb, RAX, imm_opnd(2)); check_bytes(cb, "4883F802");
// cqo
cb_set_pos(cb, 0); cqo(cb); check_bytes(cb, "4899");
// div
/*
test(
delegate void (CodeBlock cb) { cb.div(X86Opnd(EDX)); },
"F7F2"
);
test(
delegate void (CodeBlock cb) { cb.div(X86Opnd(32, RSP, -12)); },
"F77424F4"
);
*/
// jcc to label
{
cb_set_pos(cb, 0);
uint32_t loop_label = cb_new_label(cb, "loop");
jge_label(cb, loop_label);
cb_link_labels(cb);
check_bytes(cb, "0F8DFAFFFFFF");
}
{
cb_set_pos(cb, 0);
uint32_t loop_label = cb_new_label(cb, "loop");
jo_label(cb, loop_label);
cb_link_labels(cb);
check_bytes(cb, "0F80FAFFFFFF");
}
// jmp to label
{
cb_set_pos(cb, 0);
uint32_t loop_label = cb_new_label(cb, "loop");
jmp_label(cb, loop_label);
cb_link_labels(cb);
check_bytes(cb, "E9FBFFFFFF");
}
// jmp with RM operand
cb_set_pos(cb, 0); jmp_rm(cb, R12); check_bytes(cb, "41FFE4");
// lea
cb_set_pos(cb, 0); lea(cb, RDX, mem_opnd(64, RCX, 8)); check_bytes(cb, "488D5108");
cb_set_pos(cb, 0); lea(cb, RAX, mem_opnd(8, RIP, 0)); check_bytes(cb, "488D0500000000");
cb_set_pos(cb, 0); lea(cb, RAX, mem_opnd(8, RIP, 5)); check_bytes(cb, "488D0505000000");
cb_set_pos(cb, 0); lea(cb, RDI, mem_opnd(8, RIP, 5)); check_bytes(cb, "488D3D05000000");
// mov
cb_set_pos(cb, 0); mov(cb, EAX, imm_opnd(7)); check_bytes(cb, "B807000000");
cb_set_pos(cb, 0); mov(cb, EAX, imm_opnd(-3)); check_bytes(cb, "B8FDFFFFFF");
cb_set_pos(cb, 0); mov(cb, R15, imm_opnd(3)); check_bytes(cb, "41BF03000000");
cb_set_pos(cb, 0); mov(cb, EAX, EBX); check_bytes(cb, "89D8");
cb_set_pos(cb, 0); mov(cb, EAX, ECX); check_bytes(cb, "89C8");
cb_set_pos(cb, 0); mov(cb, EDX, mem_opnd(32, RBX, 128)); check_bytes(cb, "8B9380000000");
// Test `mov rax, 3` => `mov eax, 3` optimization
cb_set_pos(cb, 0); mov(cb, R8, imm_opnd(0x34)); check_bytes(cb, "41B834000000");
cb_set_pos(cb, 0); mov(cb, R8, imm_opnd(0x80000000)); check_bytes(cb, "49B80000008000000000");
cb_set_pos(cb, 0); mov(cb, R8, imm_opnd(-1)); check_bytes(cb, "49B8FFFFFFFFFFFFFFFF");
cb_set_pos(cb, 0); mov(cb, RAX, imm_opnd(0x34)); check_bytes(cb, "B834000000");
cb_set_pos(cb, 0); mov(cb, RAX, imm_opnd(0x80000000)); check_bytes(cb, "48B80000008000000000");
cb_set_pos(cb, 0); mov(cb, RAX, imm_opnd(-52)); check_bytes(cb, "48B8CCFFFFFFFFFFFFFF");
cb_set_pos(cb, 0); mov(cb, RAX, imm_opnd(-1)); check_bytes(cb, "48B8FFFFFFFFFFFFFFFF");
/*
test(
delegate void (CodeBlock cb) { cb.mov(X86Opnd(AL), X86Opnd(8, RCX, 0, 1, RDX)); },
"8A0411"
);
*/
cb_set_pos(cb, 0); mov(cb, CL, R9B); check_bytes(cb, "4488C9");
cb_set_pos(cb, 0); mov(cb, RBX, RAX); check_bytes(cb, "4889C3");
cb_set_pos(cb, 0); mov(cb, RDI, RBX); check_bytes(cb, "4889DF");
cb_set_pos(cb, 0); mov(cb, SIL, imm_opnd(11)); check_bytes(cb, "40B60B");
cb_set_pos(cb, 0); mov(cb, mem_opnd(8, RSP, 0), imm_opnd(-3)); check_bytes(cb, "C60424FD");
cb_set_pos(cb, 0); mov(cb, mem_opnd(64, RDI, 8), imm_opnd(1)); check_bytes(cb, "48C7470801000000");
// movsx
cb_set_pos(cb, 0); movsx(cb, AX, AL); check_bytes(cb, "660FBEC0");
cb_set_pos(cb, 0); movsx(cb, EDX, AL); check_bytes(cb, "0FBED0");
cb_set_pos(cb, 0); movsx(cb, RAX, BL); check_bytes(cb, "480FBEC3");
cb_set_pos(cb, 0); movsx(cb, ECX, AX); check_bytes(cb, "0FBFC8");
cb_set_pos(cb, 0); movsx(cb, R11, CL); check_bytes(cb, "4C0FBED9");
cb_set_pos(cb, 0); movsx(cb, R10, mem_opnd(32, RSP, 12)); check_bytes(cb, "4C6354240C");
cb_set_pos(cb, 0); movsx(cb, RAX, mem_opnd(8, RSP, 0)); check_bytes(cb, "480FBE0424");
// neg
cb_set_pos(cb, 0); neg(cb, RAX); check_bytes(cb, "48F7D8");
// nop
cb_set_pos(cb, 0); nop(cb, 1); check_bytes(cb, "90");
// not
cb_set_pos(cb, 0); not(cb, AX); check_bytes(cb, "66F7D0");
cb_set_pos(cb, 0); not(cb, EAX); check_bytes(cb, "F7D0");
cb_set_pos(cb, 0); not(cb, mem_opnd(64, R12, 0)); check_bytes(cb, "49F71424");
cb_set_pos(cb, 0); not(cb, mem_opnd(32, RSP, 301)); check_bytes(cb, "F794242D010000");
cb_set_pos(cb, 0); not(cb, mem_opnd(32, RSP, 0)); check_bytes(cb, "F71424");
cb_set_pos(cb, 0); not(cb, mem_opnd(32, RSP, 3)); check_bytes(cb, "F7542403");
cb_set_pos(cb, 0); not(cb, mem_opnd(32, RBP, 0)); check_bytes(cb, "F75500");
cb_set_pos(cb, 0); not(cb, mem_opnd(32, RBP, 13)); check_bytes(cb, "F7550D");
cb_set_pos(cb, 0); not(cb, RAX); check_bytes(cb, "48F7D0");
cb_set_pos(cb, 0); not(cb, R11); check_bytes(cb, "49F7D3");
cb_set_pos(cb, 0); not(cb, mem_opnd(32, RAX, 0)); check_bytes(cb, "F710");
cb_set_pos(cb, 0); not(cb, mem_opnd(32, RSI, 0)); check_bytes(cb, "F716");
cb_set_pos(cb, 0); not(cb, mem_opnd(32, RDI, 0)); check_bytes(cb, "F717");
cb_set_pos(cb, 0); not(cb, mem_opnd(32, RDX, 55)); check_bytes(cb, "F75237");
cb_set_pos(cb, 0); not(cb, mem_opnd(32, RDX, 1337)); check_bytes(cb, "F79239050000");
cb_set_pos(cb, 0); not(cb, mem_opnd(32, RDX, -55)); check_bytes(cb, "F752C9");
cb_set_pos(cb, 0); not(cb, mem_opnd(32, RDX, -555)); check_bytes(cb, "F792D5FDFFFF");
/*
test(
delegate void (CodeBlock cb) { cb.not(X86Opnd(32, RAX, 0, 1, RBX)); },
"F71418"
);
test(
delegate void (CodeBlock cb) { cb.not(X86Opnd(32, RAX, 0, 1, R12)); },
"42F71420"
);
test(
delegate void (CodeBlock cb) { cb.not(X86Opnd(32, R15, 0, 1, R12)); },
"43F71427"
);
test(
delegate void (CodeBlock cb) { cb.not(X86Opnd(32, R15, 5, 1, R12)); },
"43F7542705"
);
test(
delegate void (CodeBlock cb) { cb.not(X86Opnd(32, R15, 5, 8, R12)); },
"43F754E705"
);
test(
delegate void (CodeBlock cb) { cb.not(X86Opnd(32, R15, 5, 8, R13)); },
"43F754EF05"
);
test(
delegate void (CodeBlock cb) { cb.not(X86Opnd(32, R12, 5, 4, R9)); },
"43F7548C05"
);
test(
delegate void (CodeBlock cb) { cb.not(X86Opnd(32, R12, 301, 4, R9)); },
"43F7948C2D010000"
);
test(
delegate void (CodeBlock cb) { cb.not(X86Opnd(32, RAX, 5, 4, RDX)); },
"F7549005"
);
test(
delegate void (CodeBlock cb) { cb.not(X86Opnd(64, RAX, 0, 2, RDX)); },
"48F71450"
);
test(
delegate void (CodeBlock cb) { cb.not(X86Opnd(32, RSP, 0, 1, RBX)); },
"F7141C"
);
test(
delegate void (CodeBlock cb) { cb.not(X86Opnd(32, RSP, 3, 1, RBX)); },
"F7541C03"
);
test(
delegate void (CodeBlock cb) { cb.not(X86Opnd(32, RBP, 13, 1, RDX)); },
"F754150D"
);
*/
// or
cb_set_pos(cb, 0); or(cb, EDX, ESI); check_bytes(cb, "09F2");
// pop
cb_set_pos(cb, 0); pop(cb, RAX); check_bytes(cb, "58");
cb_set_pos(cb, 0); pop(cb, RBX); check_bytes(cb, "5B");
cb_set_pos(cb, 0); pop(cb, RSP); check_bytes(cb, "5C");
cb_set_pos(cb, 0); pop(cb, RBP); check_bytes(cb, "5D");
cb_set_pos(cb, 0); pop(cb, R12); check_bytes(cb, "415C");
cb_set_pos(cb, 0); pop(cb, mem_opnd(64, RAX, 0)); check_bytes(cb, "8F00");
cb_set_pos(cb, 0); pop(cb, mem_opnd(64, R8, 0)); check_bytes(cb, "418F00");
cb_set_pos(cb, 0); pop(cb, mem_opnd(64, R8, 3)); check_bytes(cb, "418F4003");
cb_set_pos(cb, 0); pop(cb, mem_opnd_sib(64, RAX, RCX, 8, 3)); check_bytes(cb, "8F44C803");
cb_set_pos(cb, 0); pop(cb, mem_opnd_sib(64, R8, RCX, 8, 3)); check_bytes(cb, "418F44C803");
// push
cb_set_pos(cb, 0); push(cb, RAX); check_bytes(cb, "50");
cb_set_pos(cb, 0); push(cb, RBX); check_bytes(cb, "53");
cb_set_pos(cb, 0); push(cb, R12); check_bytes(cb, "4154");
cb_set_pos(cb, 0); push(cb, mem_opnd(64, RAX, 0)); check_bytes(cb, "FF30");
cb_set_pos(cb, 0); push(cb, mem_opnd(64, R8, 0)); check_bytes(cb, "41FF30");
cb_set_pos(cb, 0); push(cb, mem_opnd(64, R8, 3)); check_bytes(cb, "41FF7003");
cb_set_pos(cb, 0); push(cb, mem_opnd_sib(64, RAX, RCX, 8, 3)); check_bytes(cb, "FF74C803");
cb_set_pos(cb, 0); push(cb, mem_opnd_sib(64, R8, RCX, 8, 3)); check_bytes(cb, "41FF74C803");
// ret
cb_set_pos(cb, 0); ret(cb); check_bytes(cb, "C3");
// sal
cb_set_pos(cb, 0); sal(cb, CX, imm_opnd(1)); check_bytes(cb, "66D1E1");
cb_set_pos(cb, 0); sal(cb, ECX, imm_opnd(1)); check_bytes(cb, "D1E1");
cb_set_pos(cb, 0); sal(cb, EBP, imm_opnd(5)); check_bytes(cb, "C1E505");
cb_set_pos(cb, 0); sal(cb, mem_opnd(32, RSP, 68), imm_opnd(1)); check_bytes(cb, "D1642444");
// sar
cb_set_pos(cb, 0); sar(cb, EDX, imm_opnd(1)); check_bytes(cb, "D1FA");
// shr
cb_set_pos(cb, 0); shr(cb, R14, imm_opnd(7)); check_bytes(cb, "49C1EE07");
/*
// sqrtsd
test(
delegate void (CodeBlock cb) { cb.sqrtsd(X86Opnd(XMM2), X86Opnd(XMM6)); },
"F20F51D6"
);
*/
// sub
cb_set_pos(cb, 0); sub(cb, EAX, imm_opnd(1)); check_bytes(cb, "83E801");
cb_set_pos(cb, 0); sub(cb, RAX, imm_opnd(2)); check_bytes(cb, "4883E802");
// test
cb_set_pos(cb, 0); test(cb, AL, AL); check_bytes(cb, "84C0");
cb_set_pos(cb, 0); test(cb, AX, AX); check_bytes(cb, "6685C0");
cb_set_pos(cb, 0); test(cb, CL, imm_opnd(8)); check_bytes(cb, "F6C108");
cb_set_pos(cb, 0); test(cb, DL, imm_opnd(7)); check_bytes(cb, "F6C207");
cb_set_pos(cb, 0); test(cb, RCX, imm_opnd(8)); check_bytes(cb, "F6C108");
cb_set_pos(cb, 0); test(cb, mem_opnd(8, RDX, 8), imm_opnd(8)); check_bytes(cb, "F6420808");
cb_set_pos(cb, 0); test(cb, mem_opnd(8, RDX, 8), imm_opnd(255)); check_bytes(cb, "F64208FF");
cb_set_pos(cb, 0); test(cb, DX, imm_opnd(0xFFFF)); check_bytes(cb, "66F7C2FFFF");
cb_set_pos(cb, 0); test(cb, mem_opnd(16, RDX, 8), imm_opnd(0xFFFF)); check_bytes(cb, "66F74208FFFF");
cb_set_pos(cb, 0); test(cb, mem_opnd(8, RSI, 0), imm_opnd(1)); check_bytes(cb, "F60601");
cb_set_pos(cb, 0); test(cb, mem_opnd(8, RSI, 16), imm_opnd(1)); check_bytes(cb, "F6461001");
cb_set_pos(cb, 0); test(cb, mem_opnd(8, RSI, -16), imm_opnd(1)); check_bytes(cb, "F646F001");
cb_set_pos(cb, 0); test(cb, mem_opnd(32, RSI, 64), EAX); check_bytes(cb, "854640");
cb_set_pos(cb, 0); test(cb, mem_opnd(64, RDI, 42), RAX); check_bytes(cb, "4885472A");
cb_set_pos(cb, 0); test(cb, RAX, RAX); check_bytes(cb, "4885C0");
cb_set_pos(cb, 0); test(cb, RAX, RSI); check_bytes(cb, "4885F0");
cb_set_pos(cb, 0); test(cb, mem_opnd(64, RSI, 64), imm_opnd(~0x08)); check_bytes(cb, "48F74640F7FFFFFF");
// xchg
cb_set_pos(cb, 0); xchg(cb, RAX, RCX); check_bytes(cb, "4891");
cb_set_pos(cb, 0); xchg(cb, RAX, R13); check_bytes(cb, "4995");
cb_set_pos(cb, 0); xchg(cb, RCX, RBX); check_bytes(cb, "4887D9");
cb_set_pos(cb, 0); xchg(cb, R9, R15); check_bytes(cb, "4D87F9");
// xor
cb_set_pos(cb, 0); xor(cb, EAX, EAX); check_bytes(cb, "31C0");
printf("Assembler tests done\n");
}
void assert_equal(int expected, int actual)
{
if (expected != actual) {
fprintf(stderr, "expected %d, got %d\n", expected, actual);
exit(-1);
}
}
void run_runtime_tests(void)
{
printf("Running runtime tests\n");
codeblock_t codeblock;
codeblock_t* cb = &codeblock;
uint8_t* mem_block = alloc_exec_mem(4096);
cb_init(cb, mem_block, 4096);
int (*function)(void);
function = (int (*)(void))mem_block;
#define TEST(BODY) cb_set_pos(cb, 0); BODY ret(cb); cb_mark_all_executable(cb); assert_equal(7, function());
// add
TEST({ mov(cb, RAX, imm_opnd(0)); add(cb, RAX, imm_opnd(7)); })
TEST({ mov(cb, RAX, imm_opnd(0)); mov(cb, RCX, imm_opnd(7)); add(cb, RAX, RCX); })
// and
TEST({ mov(cb, RAX, imm_opnd(31)); and(cb, RAX, imm_opnd(7)); })
TEST({ mov(cb, RAX, imm_opnd(31)); mov(cb, RCX, imm_opnd(7)); and(cb, RAX, RCX); })
// or
TEST({ mov(cb, RAX, imm_opnd(3)); or(cb, RAX, imm_opnd(4)); })
TEST({ mov(cb, RAX, imm_opnd(3)); mov(cb, RCX, imm_opnd(4)); or(cb, RAX, RCX); })
// push/pop
TEST({ mov(cb, RCX, imm_opnd(7)); push(cb, RCX); pop(cb, RAX); })
// shr
TEST({ mov(cb, RAX, imm_opnd(31)); shr(cb, RAX, imm_opnd(2)); })
// sub
TEST({ mov(cb, RAX, imm_opnd(12)); sub(cb, RAX, imm_opnd(5)); })
TEST({ mov(cb, RAX, imm_opnd(12)); mov(cb, RCX, imm_opnd(5)); sub(cb, RAX, RCX); })
// xor
TEST({ mov(cb, RAX, imm_opnd(13)); xor(cb, RAX, imm_opnd(10)); })
TEST({ mov(cb, RAX, imm_opnd(13)); mov(cb, RCX, imm_opnd(10)); xor(cb, RAX, RCX); })
#undef TEST
printf("Runtime tests done\n");
}
int main(int argc, char** argv)
{
run_assembler_tests();
run_runtime_tests();
return 0;
}

53
ruby.c
Просмотреть файл

@ -1028,36 +1028,23 @@ set_option_encoding_once(const char *type, VALUE *name, const char *e, long elen
#define yjit_opt_match_arg(s, l, name) \
opt_match(s, l, name) && (*(s) && *(s+1) ? 1 : (rb_raise(rb_eRuntimeError, "--yjit-" name " needs an argument"), 0))
#if YJIT_SUPPORTED_P
static void
setup_yjit_options(const char *s, struct rb_yjit_options *yjit_opt)
#if YJIT_BUILD
static bool
setup_yjit_options(const char *s)
{
const size_t l = strlen(s);
if (l == 0) {
return;
}
else if (yjit_opt_match_arg(s, l, "exec-mem-size")) {
yjit_opt->exec_mem_size = atoi(s + 1);
}
else if (yjit_opt_match_arg(s, l, "call-threshold")) {
yjit_opt->call_threshold = atoi(s + 1);
}
else if (yjit_opt_match_arg(s, l, "max-versions")) {
yjit_opt->max_versions = atoi(s + 1);
}
else if (yjit_opt_match_noarg(s, l, "greedy-versioning")) {
yjit_opt->greedy_versioning = true;
}
else if (yjit_opt_match_noarg(s, l, "no-type-prop")) {
yjit_opt->no_type_prop = true;
}
else if (yjit_opt_match_noarg(s, l, "stats")) {
yjit_opt->gen_stats = true;
}
else {
rb_raise(rb_eRuntimeError,
"invalid yjit option `%s' (--help will show valid yjit options)", s);
// The option parsing is done in yjit/src/options.rs
bool rb_yjit_parse_option(const char* s);
bool success = rb_yjit_parse_option(s);
if (success) {
return true;
}
rb_raise(
rb_eRuntimeError,
"invalid YJIT option `%s' (--help will show valid yjit options)",
s
);
}
#endif
@ -1446,11 +1433,11 @@ proc_options(long argc, char **argv, ruby_cmdline_options_t *opt, int envopt)
#endif
}
else if (is_option_with_optarg("yjit", '-', true, false, false)) {
#if YJIT_SUPPORTED_P
#if YJIT_BUILD
FEATURE_SET(opt->features, FEATURE_BIT(yjit));
setup_yjit_options(s, &opt->yjit);
setup_yjit_options(s);
#else
rb_warn("Ruby was built without JIT support");
rb_warn("Ruby was built without YJIT support");
#endif
}
else if (strcmp("yydebug", s) == 0) {
@ -1835,8 +1822,8 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt)
exit(1);
}
#endif
#if YJIT_SUPPORTED_P
rb_yjit_init(&opt->yjit);
#if YJIT_BUILD
rb_yjit_init();
#endif
}
if (opt->dump & (DUMP_BIT(version) | DUMP_BIT(version_v))) {

Просмотреть файл

@ -27,6 +27,8 @@ CC_WRAPPER = @XCC_WRAPPER@
CC = @CC@
CPP = @CPP@
LD = @LD@
RUSTC = @RUSTC@
CARGO = @CARGO@
YACC = bison
PURIFY =
AUTOCONF = autoconf
@ -106,6 +108,11 @@ MJIT_MIN_HEADER_NAME = rb_mjit_min_header-$(RUBY_PROGRAM_VERSION).h
MJIT_MIN_HEADER = $(MJIT_HEADER_BUILD_DIR)/$(MJIT_MIN_HEADER_NAME)
MJIT_HEADER_BUILD_DIR = $(EXTOUT)/include/$(arch)
MJIT_TABS=@MJIT_TABS@
YJIT_SUPPORT=@YJIT_SUPPORT@
YJIT_LIBS=@YJIT_LIBS@
YJIT_OBJ=@YJIT_OBJ@
CARGO_TARGET_DIR=@abs_top_builddir@/yjit/target
CARGO_BUILD_ARGS=@CARGO_BUILD_ARGS@
LDFLAGS = @STATIC@ $(CFLAGS) @LDFLAGS@
EXE_LDFLAGS = $(LDFLAGS)
EXTLDFLAGS = @EXTLDFLAGS@
@ -120,7 +127,7 @@ XDLDFLAGS = @DLDFLAGS@
DLDFLAGS = @LIBRUBY_DLDFLAGS@ $(XLDFLAGS) $(ARCH_FLAG)
SOLIBS = @SOLIBS@
ENABLE_DEBUG_ENV = @ENABLE_DEBUG_ENV@
MAINLIBS = @MAINLIBS@
MAINLIBS = @YJIT_LIBS@ @MAINLIBS@
ARCHMINIOBJS = @MINIOBJS@
DLNOBJ = @DLNOBJ@
ENCOBJS = @ENCOBJS@
@ -291,11 +298,22 @@ PRE_LIBRUBY_UPDATE = [ -n "$(LIBRUBY_SO_UPDATE)" ] || $(gnumake:yes=exec) $(RM)
# We must `rm' the library each time this rule is invoked because "updating" a
# MAB library on Apple/NeXT (see --enable-fat-binary in configure) is not
# supported.
#
# In YJIT builds, merge libyjit.a with libruby_static.a
$(LIBRUBY_A):
@$(RM) $@
@-[ -z "$(EXTSTATIC)" ] || $(PRE_LIBRUBY_UPDATE)
$(ECHO) linking static-library $@
$(Q) $(AR) $(ARFLAGS) $@ $(LIBRUBY_A_OBJS) $(INITOBJS)
$(Q) if [ -f '$(YJIT_LIBS)' ]; then \
set -eu && \
echo 'merging $(YJIT_LIBS) into $@' && \
$(RMALL) '$(CARGO_TARGET_DIR)/libyjit/' && \
$(MAKEDIRS) '$(CARGO_TARGET_DIR)/libyjit/' && \
$(CP) '$(YJIT_LIBS)' '$(CARGO_TARGET_DIR)/libyjit/' && \
(cd '$(CARGO_TARGET_DIR)/libyjit/' && $(AR) -x libyjit.a) && \
$(AR) $(ARFLAGS) $@ $$(find '$(CARGO_TARGET_DIR)/libyjit/' -name '*.o') ; \
fi
@-$(RANLIB) $@ 2> /dev/null || true
verify-static-library: $(LIBRUBY_A)

Просмотреть файл

@ -13,6 +13,7 @@ class TestYJIT < Test::Unit::TestCase
assert_includes(RUBY_DESCRIPTION, '+YJIT')
end
# Check that YJIT is in the version string
def test_yjit_in_version
[
%w(--version --yjit),
@ -42,9 +43,8 @@ class TestYJIT < Test::Unit::TestCase
def test_command_line_switches
assert_in_out_err('--yjit-', '', [], /invalid option --yjit-/)
assert_in_out_err('--yjithello', '', [], /invalid option --yjithello/)
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/)
assert_in_out_err('--yjit-greedy-versioning=1', '', [], /warning: argument to --yjit-greedy-versioning is ignored/)
#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
@ -356,7 +356,7 @@ class TestYJIT < Test::Unit::TestCase
end
def test_compile_opt_getinlinecache
assert_compiles(<<~RUBY, insns: %i[opt_getinlinecache], result: 123, min_calls: 2)
assert_compiles(<<~RUBY, insns: %i[opt_getinlinecache], result: 123, call_threshold: 2)
def get_foo
FOO
end
@ -369,7 +369,7 @@ class TestYJIT < Test::Unit::TestCase
end
def test_opt_getinlinecache_slowpath
assert_compiles(<<~RUBY, exits: { opt_getinlinecache: 1 }, result: [42, 42, 1, 1], min_calls: 2)
assert_compiles(<<~RUBY, exits: { opt_getinlinecache: 1 }, result: [42, 42, 1, 1], call_threshold: 2)
class A
FOO = 42
class << self
@ -397,7 +397,7 @@ class TestYJIT < Test::Unit::TestCase
end
def test_string_interpolation
assert_compiles(<<~'RUBY', insns: %i[objtostring anytostring concatstrings], result: "foobar", min_calls: 2)
assert_compiles(<<~'RUBY', insns: %i[objtostring anytostring concatstrings], result: "foobar", call_threshold: 2)
def make_str(foo, bar)
"#{foo}#{bar}"
end
@ -489,7 +489,7 @@ class TestYJIT < Test::Unit::TestCase
# Tests calling a variadic cfunc with many args
def test_build_large_struct
assert_compiles(<<~RUBY, insns: %i[opt_send_without_block], min_calls: 2)
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
@ -530,8 +530,8 @@ class TestYJIT < Test::Unit::TestCase
assert_no_exits('{}.merge(foo: 123, bar: 456, baz: 789)')
end
# regression test simplified from URI::Generic#hostname=
def test_ctx_different_mappings
# regression test simplified from URI::Generic#hostname=
assert_compiles(<<~'RUBY', frozen_string_literal: true)
def foo(v)
!(v&.start_with?('[')) && v&.index(':')
@ -572,7 +572,7 @@ class TestYJIT < Test::Unit::TestCase
end
ANY = Object.new
def assert_compiles(test_script, insns: [], min_calls: 1, stdout: nil, exits: {}, result: ANY, frozen_string_literal: nil)
def assert_compiles(test_script, insns: [], call_threshold: 1, stdout: nil, exits: {}, result: ANY, frozen_string_literal: nil)
reset_stats = <<~RUBY
RubyVM::YJIT.runtime_stats
RubyVM::YJIT.reset_stats!
@ -581,29 +581,17 @@ class TestYJIT < Test::Unit::TestCase
write_results = <<~RUBY
stats = RubyVM::YJIT.runtime_stats
def collect_blocks(blocks)
blocks.sort_by(&:address).map { |b| [b.iseq_start_index, b.iseq_end_index] }
end
def collect_iseqs(iseq)
iseq_array = iseq.to_a
insns = iseq_array.last.grep(Array)
blocks = RubyVM::YJIT.blocks_for(iseq)
h = {
name: iseq_array[5],
insns: insns,
blocks: collect_blocks(blocks),
}
arr = [h]
iseq.each_child { |c| arr.concat collect_iseqs(c) }
arr
def collect_insns(iseq)
insns = RubyVM::YJIT.insns_compiled(iseq)
iseq.each_child { |c| insns.concat collect_insns(c) }
insns
end
iseq = RubyVM::InstructionSequence.of(_test_proc)
IO.open(3).write Marshal.dump({
result: #{result == ANY ? "nil" : "result"},
stats: stats,
iseqs: collect_iseqs(iseq),
insns: collect_insns(iseq),
disasm: iseq.disasm
})
RUBY
@ -618,7 +606,7 @@ class TestYJIT < Test::Unit::TestCase
#{write_results}
RUBY
status, out, err, stats = eval_with_jit(script, min_calls: min_calls)
status, out, err, stats = eval_with_jit(script, call_threshold: call_threshold)
assert status.success?, "exited with status #{status.to_i}, stderr:\n#{err}"
@ -629,10 +617,11 @@ class TestYJIT < Test::Unit::TestCase
end
runtime_stats = stats[:stats]
iseqs = stats[:iseqs]
insns_compiled = stats[:insns]
disasm = stats[:disasm]
# Only available when RUBY_DEBUG enabled
# Check that exit counts are as expected
# Full stats are only available when RUBY_DEBUG enabled
if runtime_stats[:all_stats]
recorded_exits = runtime_stats.select { |k, v| k.to_s.start_with?("exit_") }
recorded_exits = recorded_exits.reject { |k, v| v == 0 }
@ -647,38 +636,24 @@ class TestYJIT < Test::Unit::TestCase
# Only available when RUBY_DEBUG enabled
if runtime_stats[:all_stats]
missed_insns = insns.dup
all_compiled_blocks = {}
iseqs.each do |iseq|
compiled_blocks = iseq[:blocks].map { |from, to| (from...to) }
all_compiled_blocks[iseq[:name]] = compiled_blocks
compiled_insns = iseq[:insns]
next_idx = 0
compiled_insns.map! do |insn|
# TODO: not sure this is accurate for determining insn size
idx = next_idx
next_idx += insn.length
[idx, *insn]
end
compiled_insns.each do |idx, op, *arguments|
next unless missed_insns.include?(op)
next unless compiled_blocks.any? { |block| block === idx }
insns_compiled.each do |op|
if missed_insns.include?(op)
# This instruction was compiled
missed_insns.delete(op)
end
end
unless missed_insns.empty?
flunk "Expected to compile instructions #{missed_insns.join(", ")} but didn't.\nCompiled ranges: #{all_compiled_blocks.inspect}\niseq:\n#{disasm}"
flunk "Expected to compile instructions #{missed_insns.join(", ")} but didn't.\niseq:\n#{disasm}"
end
end
end
def eval_with_jit(script, min_calls: 1, timeout: 1000)
def eval_with_jit(script, call_threshold: 1, timeout: 1000)
args = [
"--disable-gems",
"--yjit-call-threshold=#{min_calls}",
"--yjit-call-threshold=#{call_threshold}",
"--yjit-stats"
]
args << "-e" << script

7
vm.c
Просмотреть файл

@ -1890,7 +1890,7 @@ rb_vm_check_redefinition_opt_method(const rb_method_entry_t *me, VALUE klass)
if (st_lookup(vm_opt_method_def_table, (st_data_t)me->def, &bop)) {
int flag = vm_redefinition_check_flag(klass);
if (flag != 0) {
rb_yjit_bop_redefined(klass, me, (enum ruby_basic_operators)bop);
rb_yjit_bop_redefined(flag, (enum ruby_basic_operators)bop);
ruby_vm_redefined_flag[bop] |= flag;
}
}
@ -3971,6 +3971,11 @@ Init_vm_objects(void)
vm->frozen_strings = st_init_table_with_size(&rb_fstring_hash_type, 10000);
}
/* Stub for builtin function when not building YJIT units*/
#if !YJIT_BUILD
void Init_builtin_yjit(void) {}
#endif
/* top self */
static VALUE

Просмотреть файл

@ -85,7 +85,6 @@
#include "ruby/st.h"
#include "ruby_atomic.h"
#include "vm_opts.h"
#include "darray.h"
#include "ruby/thread_native.h"
#include THREAD_IMPL_H
@ -322,10 +321,6 @@ pathobj_realpath(VALUE pathobj)
/* Forward declarations */
struct rb_mjit_unit;
// List of YJIT block versions
typedef rb_darray(struct yjit_block_version *) rb_yjit_block_array_t;
typedef rb_darray(rb_yjit_block_array_t) rb_yjit_block_array_array_t;
struct rb_iseq_constant_body {
enum iseq_type {
ISEQ_TYPE_TOP,
@ -470,7 +465,11 @@ struct rb_iseq_constant_body {
struct rb_mjit_unit *jit_unit;
#endif
rb_yjit_block_array_array_t yjit_blocks; // empty, or has a size equal to iseq_size
#if USE_YJIT
// YJIT stores some data on each iseq.
// Note: Cannot use YJIT_BUILD here since yjit.h includes this header.
void *yjit_payload;
#endif
};
/* T_IMEMO/iseq */
@ -1206,7 +1205,7 @@ typedef rb_control_frame_t *
#define GC_GUARDED_PTR_REF(p) VM_TAGGED_PTR_REF((p), 0x03)
#define GC_GUARDED_PTR_P(p) (((VALUE)(p)) & 0x01)
enum {
enum vm_frame_env_flags {
/* Frame/Environment flag bits:
* MMMM MMMM MMMM MMMM ____ _FFF FFFF EEEX (LSB)
*

Просмотреть файл

@ -4926,8 +4926,13 @@ vm_opt_newarray_min(rb_execution_context_t *ec, rb_num_t num, const VALUE *ptr)
#define IMEMO_CONST_CACHE_SHAREABLE IMEMO_FL_USER0
// For each getconstant, associate the ID that corresponds to the first operand
// to that instruction with the inline cache.
// This is the iterator used by vm_ic_compile for rb_iseq_each. It is used as a
// callback for each instruction within the ISEQ, and is meant to return a
// boolean indicating whether or not to keep iterating.
//
// This is used to walk through the ISEQ and find all getconstant instructions
// between the starting opt_getinlinecache and the ending opt_setinlinecache and
// associating the inline cache with the constant name components on the VM.
static bool
vm_ic_compile_i(VALUE *code, VALUE insn, size_t index, void *ic)
{

Просмотреть файл

@ -123,7 +123,7 @@ vm_cme_invalidate(rb_callable_method_entry_t *cme)
METHOD_ENTRY_INVALIDATED_SET(cme);
RB_DEBUG_COUNTER_INC(cc_cme_invalidate);
rb_yjit_cme_invalidate((VALUE)cme);
rb_yjit_cme_invalidate(cme);
}
static int
@ -148,7 +148,7 @@ rb_clear_constant_cache_for_id(ID id)
ruby_vm_constant_cache_invalidations += ics->num_entries;
}
rb_yjit_constant_state_changed();
rb_yjit_constant_state_changed(id);
}
static void

996
yjit.c

Разница между файлами не показана из-за своего большого размера Загрузить разницу

74
yjit.h
Просмотреть файл

@ -22,52 +22,58 @@
# define YJIT_SUPPORTED_P 0
#endif
struct rb_yjit_options {
// Enable compilation with YJIT
bool yjit_enabled;
// Is the output binary going to include YJIT?
#if USE_MJIT && USE_YJIT && YJIT_SUPPORTED_P
# define YJIT_BUILD 1
#else
# define YJIT_BUILD 0
#endif
// Size of the executable memory block to allocate in MiB
unsigned exec_mem_size;
// Number of method calls after which to start generating code
// Threshold==1 means compile on first execution
unsigned call_threshold;
// Generate versions greedily until the limit is hit
bool greedy_versioning;
// Disable the propagation of type information
bool no_type_prop;
// Maximum number of versions per block
// 1 means always create generic versions
unsigned max_versions;
// Capture and print out stats
bool gen_stats;
// Run backend tests
bool test_backend;
};
#if YJIT_BUILD
// Expose these as declarations since we are building YJIT.
bool rb_yjit_enabled_p(void);
unsigned rb_yjit_call_threshold(void);
void rb_yjit_invalidate_all_method_lookup_assumptions(void);
void rb_yjit_method_lookup_change(VALUE klass, ID mid);
void rb_yjit_cme_invalidate(VALUE cme);
void rb_yjit_cme_invalidate(rb_callable_method_entry_t *cme);
void rb_yjit_collect_vm_usage_insn(int insn);
void rb_yjit_collect_binding_alloc(void);
void rb_yjit_collect_binding_set(void);
bool rb_yjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec);
void rb_yjit_init(struct rb_yjit_options *options);
void rb_yjit_bop_redefined(VALUE klass, const rb_method_entry_t *me, enum ruby_basic_operators bop);
void rb_yjit_constant_state_changed(void);
void rb_yjit_iseq_mark(const struct rb_iseq_constant_body *body);
void rb_yjit_iseq_update_references(const struct rb_iseq_constant_body *body);
void rb_yjit_iseq_free(const struct rb_iseq_constant_body *body);
void rb_yjit_init(void);
void rb_yjit_bop_redefined(int redefined_flag, enum ruby_basic_operators bop);
void rb_yjit_constant_state_changed(ID id);
void rb_yjit_iseq_mark(void *payload);
void rb_yjit_iseq_update_references(void *payload);
void rb_yjit_iseq_free(void *payload);
void rb_yjit_before_ractor_spawn(void);
void rb_yjit_constant_ic_update(const rb_iseq_t *const iseq, IC ic);
void rb_yjit_tracing_invalidate_all(void);
#else
// !YJIT_BUILD
// In these builds, YJIT could never be turned on. Provide dummy implementations.
static inline bool rb_yjit_enabled_p(void) { return false; }
static inline unsigned rb_yjit_call_threshold(void) { return UINT_MAX; }
static inline void rb_yjit_invalidate_all_method_lookup_assumptions(void) {}
static inline void rb_yjit_method_lookup_change(VALUE klass, ID mid) {}
static inline void rb_yjit_cme_invalidate(rb_callable_method_entry_t *cme) {}
static inline void rb_yjit_collect_vm_usage_insn(int insn) {}
static inline void rb_yjit_collect_binding_alloc(void) {}
static inline void rb_yjit_collect_binding_set(void) {}
static inline bool rb_yjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec) { return false; }
static inline void rb_yjit_init(void) {}
static inline void rb_yjit_bop_redefined(int redefined_flag, enum ruby_basic_operators bop) {}
static inline void rb_yjit_constant_state_changed(ID id) {}
static inline void rb_yjit_iseq_mark(void *payload) {}
static inline void rb_yjit_iseq_update_references(void *payload) {}
static inline void rb_yjit_iseq_free(void *payload) {}
static inline void rb_yjit_before_ractor_spawn(void) {}
static inline void rb_yjit_constant_ic_update(const rb_iseq_t *const iseq, IC ic) {}
static inline void rb_yjit_tracing_invalidate_all(void) {}
#endif // #if YJIT_BUILD
#endif // #ifndef YJIT_H

165
yjit.rb
Просмотреть файл

@ -9,155 +9,58 @@
# for which CRuby is built. There is also no API stability guarantee as to in
# what situations this module is defined.
module RubyVM::YJIT
if defined?(Disasm)
def self.disasm(iseq, tty: $stdout && $stdout.tty?)
iseq = RubyVM::InstructionSequence.of(iseq)
# Check if YJIT is enabled
def self.enabled?
Primitive.cexpr! 'RBOOL(rb_yjit_enabled_p())'
end
blocks = blocks_for(iseq)
return if blocks.empty?
def self.stats_enabled?
Primitive.rb_yjit_stats_enabled_p
end
str = String.new
str << iseq.disasm
str << "\n"
# Sort the blocks by increasing addresses
sorted_blocks = blocks.sort_by(&:address)
highlight = ->(str) {
if tty
"\x1b[1m#{str}\x1b[0m"
else
str
end
}
cs = Disasm.new
sorted_blocks.each_with_index do |block, i|
str << "== BLOCK #{i+1}/#{blocks.length}: #{block.code.length} BYTES, ISEQ RANGE [#{block.iseq_start_index},#{block.iseq_end_index}) ".ljust(80, "=")
str << "\n"
comments = comments_for(block.address, block.address + block.code.length)
comment_idx = 0
cs.disasm(block.code, block.address).each do |i|
while (comment = comments[comment_idx]) && comment.address <= i.address
str << " ; #{highlight.call(comment.comment)}\n"
comment_idx += 1
end
str << sprintf(
" %<address>08x: %<instruction>s\t%<details>s\n",
address: i.address,
instruction: i.mnemonic,
details: i.op_str
)
end
end
block_sizes = blocks.map { |block| block.code.length }
total_bytes = block_sizes.sum
str << "\n"
str << "Total code size: #{total_bytes} bytes"
str << "\n"
str
end
def self.comments_for(start_address, end_address)
Primitive.comments_for(start_address, end_address)
end
def self.graphviz_for(iseq)
iseq = RubyVM::InstructionSequence.of(iseq)
cs = Disasm.new
highlight = ->(comment) { "<b>#{comment}</b>" }
linebreak = "<br align=\"left\"/>\n"
buff = +''
blocks = blocks_for(iseq).sort_by(&:id)
buff << "digraph g {\n"
# Write the iseq info as a legend
buff << " legend [shape=record fontsize=\"30\" fillcolor=\"lightgrey\" style=\"filled\"];\n"
buff << " legend [label=\"{ Instruction Disassembly For: | {#{iseq.base_label}@#{iseq.absolute_path}:#{iseq.first_lineno}}}\"];\n"
# Subgraph contains disassembly
buff << " subgraph disasm {\n"
buff << " node [shape=record fontname=\"courier\"];\n"
buff << " edge [fontname=\"courier\" penwidth=3];\n"
blocks.each do |block|
disasm = disasm_block(cs, block, highlight)
# convert newlines to breaks that graphviz understands
disasm.gsub!(/\n/, linebreak)
# strip leading whitespace
disasm.gsub!(/^\s+/, '')
buff << "b#{block.id} [label=<#{disasm}>];\n"
buff << block.outgoing_ids.map { |id|
next_block = blocks.bsearch { |nb| id <=> nb.id }
if next_block.address == (block.address + block.code.length)
"b#{block.id} -> b#{id}[label=\"Fall\"];"
else
"b#{block.id} -> b#{id}[label=\"Jump\" style=dashed];"
end
}.join("\n")
buff << "\n"
end
buff << " }"
buff << "}"
buff
end
def self.disasm_block(cs, block, highlight)
comments = comments_for(block.address, block.address + block.code.length)
comment_idx = 0
str = +''
cs.disasm(block.code, block.address).each do |i|
while (comment = comments[comment_idx]) && comment.address <= i.address
str << " ; #{highlight.call(comment.comment)}\n"
comment_idx += 1
end
str << sprintf(
" %<address>08x: %<instruction>s\t%<details>s\n",
address: i.address,
instruction: i.mnemonic,
details: i.op_str
)
end
str
end
# Discard statistics collected for --yjit-stats.
def self.reset_stats!
Primitive.rb_yjit_reset_stats_bang
end
# Return a hash for statistics generated for the --yjit-stats command line option.
# Return nil when option is not passed or unavailable.
def self.runtime_stats
# defined in yjit_iface.c
Primitive.get_yjit_stats
Primitive.rb_yjit_get_stats
end
# Discard statistics collected for --yjit-stats.
def self.reset_stats!
# defined in yjit_iface.c
Primitive.reset_stats_bang
# Produce disassembly for an iseq
def self.disasm(iseq)
# If a method or proc is passed in, get its iseq
iseq = RubyVM::InstructionSequence.of(iseq)
if self.enabled?
# Produce the disassembly string
# Include the YARV iseq disasm in the string for additional context
iseq.disasm + "\n" + Primitive.rb_yjit_disasm_iseq(iseq)
else
iseq.disasm
end
end
def self.stats_enabled?
Primitive.yjit_stats_enabled_p
end
# Produce a list of instructions compiled by YJIT for an iseq
def self.insns_compiled(iseq)
# If a method or proc is passed in, get its iseq
iseq = RubyVM::InstructionSequence.of(iseq)
def self.enabled?
Primitive.cexpr! 'RBOOL(rb_yjit_enabled_p())'
if self.enabled?
Primitive.rb_yjit_insns_compiled(iseq)
else
Qnil
end
end
def self.simulate_oom!
Primitive.simulate_oom_bang
Primitive.rb_yjit_simulate_oom_bang
end
# Avoid calling a method here to not interfere with compilation tests
if Primitive.yjit_stats_enabled_p
if Primitive.rb_yjit_stats_enabled_p
at_exit { _print_stats }
end

2
yjit/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,2 @@
# Build output
target/

42
yjit/Cargo.lock сгенерированный Normal file
Просмотреть файл

@ -0,0 +1,42 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "capstone"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66b5d1f14c3539b6ff22fcb602fea5f1c4416148c8b7965a2e74860aa169b7b5"
dependencies = [
"capstone-sys",
"libc",
]
[[package]]
name = "capstone-sys"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df653a22d0ad34b0d91cc92a6289d96e44aac1c9a96250a094c9aeec4a91084f"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "cc"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]]
name = "libc"
version = "0.2.124"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50"
[[package]]
name = "yjit"
version = "0.1.0"
dependencies = [
"capstone",
]

39
yjit/Cargo.toml Normal file
Просмотреть файл

@ -0,0 +1,39 @@
# NOTE: please avoid adding dependencies to external crates as these can
# make building and packaging YJIT more challenging.
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[package]
name = "yjit"
version = "0.1.0" # YJIT version
edition = "2021" # Rust 2021 edition to compile with
rust-version = "1.60.0" # Minimally supported rust version
publish = false # Don't publish to crates.io
[lib]
crate-type = ["staticlib"]
[dependencies]
# No required dependencies to simplify build process. TODO: Link to yet to be
# written rationale. Optional For development and testing purposes
capstone = { version = "0.10.0", optional = true }
[features]
# NOTE: Development builds select a set of these via configure.ac
# For debugging, `make V=1` shows exact cargo invocation.
disasm = ["capstone"]
stats = []
asm_comments = []
[profile.dev]
opt-level = 0
debug = true
debug-assertions = true
overflow-checks = true
[profile.release]
# NOTE: --enable-yjit builds use `rustc` without going through Cargo. You
# might want to update the `rustc` invocation if you change this profile.
opt-level = 3
# The extra robustness that comes from checking for arithmetic overflow is
# worth the performance cost for the compiler.
overflow-checks = true

345
yjit/bindgen/Cargo.lock сгенерированный Normal file
Просмотреть файл

@ -0,0 +1,345 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "bindgen"
version = "0.59.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"clap",
"env_logger",
"lazy_static",
"lazycell",
"log",
"peeking_take_while",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"which",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clang-sys"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cc00842eed744b858222c4c9faf7243aafc6d33f92f96935263ef4d8a41ce21"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "clap"
version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "env_logger"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.124"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50"
[[package]]
name = "libloading"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "log"
version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nom"
version = "7.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "proc-macro2"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "shlex"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "unicode-width"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "which"
version = "4.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae"
dependencies = [
"either",
"lazy_static",
"libc",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "yjit-bindgen"
version = "0.1.0"
dependencies = [
"bindgen",
]

9
yjit/bindgen/Cargo.toml Normal file
Просмотреть файл

@ -0,0 +1,9 @@
[package]
name = "yjit-bindgen"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bindgen = "0.59.2"

286
yjit/bindgen/src/main.rs Normal file
Просмотреть файл

@ -0,0 +1,286 @@
//! See https://docs.rs/bindgen/0.59.2/bindgen/struct.Builder.html
//! This is the binding generation tool that the YJIT cruby module talks about.
//! More docs later once we have more experience with this, for now, check
//! the output to make sure it looks reasonable and allowlist things you want
//! to use in Rust.
extern crate bindgen;
use std::env;
use std::path::PathBuf;
const SRC_ROOT_ENV: &str = "YJIT_SRC_ROOT_PATH";
fn main() {
// Path to repo is a required input for supporting running `configure`
// in a directory away from the code.
let src_root = env::var(SRC_ROOT_ENV).expect(
format!(
r#"The "{}" env var must be a path to the root of the Ruby repo"#,
SRC_ROOT_ENV
)
.as_ref(),
);
let src_root = PathBuf::from(src_root);
assert!(
src_root.is_dir(),
"{} must be set to a path to a directory",
SRC_ROOT_ENV
);
// Remove this flag so rust-bindgen generates bindings
// that are internal functions not public in libruby
let filtered_clang_args = env::args().filter(|arg| arg != "-fvisibility=hidden");
let bindings = bindgen::builder()
.clang_args(filtered_clang_args)
.header("internal.h")
.header("internal/re.h")
.header("include/ruby/ruby.h")
.header("vm_core.h")
.header("vm_callinfo.h")
// Our C file for glue code
.header(src_root.join("yjit.c").to_str().unwrap())
// Don't want to copy over C comment
.generate_comments(false)
// Don't want layout tests as they are platform dependent
.layout_tests(false)
// Block for stability since output is different on Darwin and Linux
.blocklist_type("size_t")
.blocklist_type("fpos_t")
// Prune these types since they are system dependant and we don't use them
.blocklist_type("__.*")
// From include/ruby/internal/intern/string.h
.allowlist_function("rb_utf8_str_new")
// This struct is public to Ruby C extensions
// From include/ruby/internal/core/rbasic.h
.allowlist_type("RBasic")
// From internal.h
// This function prints info about a value and is useful for debugging
.allowlist_function("rb_obj_info_dump")
// From ruby/internal/intern/object.h
.allowlist_function("rb_obj_is_kind_of")
// From include/hash.h
.allowlist_function("rb_hash_new")
// From internal/hash.h
.allowlist_function("rb_hash_new_with_size")
.allowlist_function("rb_hash_resurrect")
// From include/ruby/internal/intern/hash.h
.allowlist_function("rb_hash_aset")
.allowlist_function("rb_hash_aref")
.allowlist_function("rb_hash_bulk_insert")
// From include/ruby/internal/intern/array.h
.allowlist_function("rb_ary_new_capa")
.allowlist_function("rb_ary_store")
.allowlist_function("rb_ary_resurrect")
.allowlist_function("rb_ary_clear")
// From internal/array.h
.allowlist_function("rb_ec_ary_new_from_values")
.allowlist_function("rb_ary_tmp_new_from_values")
// From include/ruby/internal/intern/class.h
.allowlist_function("rb_singleton_class")
// From include/ruby/internal/core/rclass.h
.allowlist_function("rb_class_get_superclass")
// From include/ruby/internal/intern/gc.h
.allowlist_function("rb_gc_mark")
.allowlist_function("rb_gc_mark_movable")
.allowlist_function("rb_gc_location")
// VALUE variables for Ruby class objects
// From include/ruby/internal/globals.h
.allowlist_var("rb_cBasicObject")
.allowlist_var("rb_cModule")
.allowlist_var("rb_cNilClass")
.allowlist_var("rb_cTrueClass")
.allowlist_var("rb_cFalseClass")
.allowlist_var("rb_cInteger")
.allowlist_var("rb_cSymbol")
.allowlist_var("rb_cFloat")
.allowlist_var("rb_cString")
.allowlist_var("rb_cThread")
.allowlist_var("rb_cArray")
.allowlist_var("rb_cHash")
// From ruby/internal/globals.h
.allowlist_var("rb_mKernel")
// From vm_callinfo.h
.allowlist_type("VM_CALL.*") // This doesn't work, possibly due to the odd structure of the #defines
.allowlist_type("vm_call_flag_bits") // So instead we include the other enum and do the bit-shift ourselves.
.allowlist_type("rb_call_data")
.blocklist_type("rb_callcache.*") // Not used yet - opaque to make it easy to import rb_call_data
.opaque_type("rb_callcache.*")
.blocklist_type("rb_callinfo_kwarg") // Contains a VALUE[] array of undefined size, so we don't import
.opaque_type("rb_callinfo_kwarg")
.allowlist_type("rb_callinfo")
// From vm_insnhelper.h
.allowlist_var("VM_ENV_DATA_INDEX_ME_CREF")
.allowlist_var("rb_block_param_proxy")
// From include/ruby/internal/intern/range.h
.allowlist_function("rb_range_new")
// From include/ruby/internal/symbol.h
.allowlist_function("rb_intern")
.allowlist_function("rb_id2sym")
.allowlist_function("rb_sym2id")
.allowlist_function("rb_str_intern")
// From internal/string.h
.allowlist_function("rb_ec_str_resurrect")
.allowlist_function("rb_str_concat_literals")
.allowlist_function("rb_obj_as_string_result")
// From include/ruby/internal/intern/parse.h
.allowlist_function("rb_backref_get")
// From include/ruby/internal/intern/re.h
.allowlist_function("rb_reg_last_match")
.allowlist_function("rb_reg_match_pre")
.allowlist_function("rb_reg_match_post")
.allowlist_function("rb_reg_match_last")
.allowlist_function("rb_reg_nth_match")
// From internal/re.h
.allowlist_function("rb_reg_new_ary")
// `ruby_value_type` is a C enum and this stops it from
// prefixing all the members with the name of the type
.prepend_enum_name(false)
.translate_enum_integer_types(true) // so we get fixed width Rust types for members
// From include/ruby/internal/value_type.h
.allowlist_type("ruby_value_type") // really old C extension API
// Autogenerated into id.h
.allowlist_type("ruby_method_ids")
// From method.h
.allowlist_type("rb_method_visibility_t")
.allowlist_type("rb_method_type_t")
.allowlist_type("method_optimized_type")
.allowlist_type("rb_callable_method_entry_t")
.allowlist_type("rb_callable_method_entry_struct")
.allowlist_function("rb_method_entry_at")
.allowlist_type("rb_method_entry_t")
.blocklist_type("rb_method_cfunc_t")
.blocklist_type("rb_method_definition_.*") // Large struct with a bitfield and union of many types - don't import (yet?)
.opaque_type("rb_method_definition_.*")
// From vm_core.h
.allowlist_var("rb_mRubyVMFrozenCore")
.allowlist_var("VM_BLOCK_HANDLER_NONE")
.allowlist_type("vm_frame_env_flags")
.allowlist_type("rb_seq_param_keyword_struct")
.allowlist_type("ruby_basic_operators")
.allowlist_var(".*_REDEFINED_OP_FLAG")
.allowlist_type("rb_num_t")
.allowlist_function("rb_callable_method_entry")
.allowlist_function("rb_vm_frame_method_entry")
.allowlist_type("IVC") // pointer to iseq_inline_iv_cache_entry
.allowlist_type("IC") // pointer to iseq_inline_constant_cache
.allowlist_type("iseq_inline_constant_cache_entry")
.blocklist_type("rb_cref_t") // don't need this directly, opaqued to allow IC import
.opaque_type("rb_cref_t")
.allowlist_type("iseq_inline_iv_cache_entry")
.allowlist_type("ICVARC") // pointer to iseq_inline_cvar_cache_entry
.allowlist_type("iseq_inline_cvar_cache_entry")
.blocklist_type("rb_execution_context_.*") // Large struct with various-type fields and an ifdef, so we don't import
.opaque_type("rb_execution_context_.*")
.blocklist_type("rb_control_frame_struct")
.opaque_type("rb_control_frame_struct")
// From yjit.c
.allowlist_function("rb_iseq_(get|set)_yjit_payload")
.allowlist_function("rb_iseq_pc_at_idx")
.allowlist_function("rb_iseq_opcode_at_pc")
.allowlist_function("rb_yjit_mark_writable")
.allowlist_function("rb_yjit_mark_executable")
.allowlist_function("rb_yjit_get_page_size")
.allowlist_function("rb_leaf_invokebuiltin_iseq_p")
.allowlist_function("rb_leaf_builtin_function")
.allowlist_function("rb_set_cfp_(pc|sp)")
.allowlist_function("rb_cfp_get_iseq")
.allowlist_function("rb_yjit_multi_ractor_p")
.allowlist_function("rb_c_method_tracing_currently_enabled")
.allowlist_function("rb_full_cfunc_return")
.allowlist_function("rb_yjit_vm_lock_then_barrier")
.allowlist_function("rb_yjit_vm_unlock")
.allowlist_function("rb_assert_(iseq|cme)_handle")
.allowlist_function("rb_IMEMO_TYPE_P")
.allowlist_function("rb_iseq_reset_jit_func")
.allowlist_function("rb_yjit_dump_iseq_loc")
.allowlist_function("rb_yjit_for_each_iseq")
.allowlist_function("rb_yjit_obj_written")
// from vm_sync.h
.allowlist_function("rb_vm_barrier")
// Not sure why it's picking these up, but don't.
.blocklist_type("FILE")
.blocklist_type("_IO_.*")
// From internal/compile.h
.allowlist_function("rb_vm_insn_decode")
// From iseq.h
.allowlist_function("rb_vm_insn_addr2opcode")
.allowlist_function("rb_iseqw_to_iseq")
.allowlist_function("rb_iseq_each")
// From builtin.h
.allowlist_type("rb_builtin_function.*")
// From internal/variable.h
.allowlist_function("rb_gvar_(get|set)")
.allowlist_function("rb_obj_ensure_iv_index_mapping")
// From include/ruby/internal/intern/variable.h
.allowlist_function("rb_attr_get")
.allowlist_function("rb_ivar_get")
// From include/ruby/internal/intern/vm.h
.allowlist_function("rb_get_alloc_func")
// From gc.h and internal/gc.h
.allowlist_function("rb_class_allocate_instance")
.allowlist_function("rb_obj_info")
// We define VALUE manually, don't import it
.blocklist_type("VALUE")
// From iseq.h
.opaque_type("rb_iseq_t")
.blocklist_type("rb_iseq_t")
// Finish the builder and generate the bindings.
.generate()
// Unwrap the Result and panic on failure.
.expect("Unable to generate bindings");
let mut out_path: PathBuf = src_root;
out_path.push("yjit");
out_path.push("src");
out_path.push("cruby_bindings.inc.rs");
bindings
.write_to_file(out_path)
.expect("Couldn't write bindings!");
}

392
yjit/src/asm/mod.rs Normal file
Просмотреть файл

@ -0,0 +1,392 @@
use std::collections::BTreeMap;
use std::mem;
// Lots of manual vertical alignment in there that rustfmt doesn't handle well.
#[rustfmt::skip]
pub mod x86_64;
/// Pointer to a piece of machine code
/// We may later change this to wrap an u32
/// Note: there is no NULL constant for CodePtr. You should use Option<CodePtr> instead.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Debug)]
#[repr(C)]
pub struct CodePtr(*const u8);
impl CodePtr {
pub fn raw_ptr(&self) -> *const u8 {
let CodePtr(ptr) = *self;
return ptr;
}
fn into_i64(&self) -> i64 {
let CodePtr(ptr) = self;
*ptr as i64
}
fn into_usize(&self) -> usize {
let CodePtr(ptr) = self;
*ptr as usize
}
}
impl From<*mut u8> for CodePtr {
fn from(value: *mut u8) -> Self {
assert!(value as usize != 0);
return CodePtr(value);
}
}
/// Compute an offset in bytes of a given struct field
macro_rules! offset_of {
($struct_type:ty, $field_name:tt) => {{
// Null pointer to our struct type
let foo = (0 as *const $struct_type);
unsafe {
let ptr_field = (&(*foo).$field_name as *const _ as usize);
let ptr_base = (foo as usize);
ptr_field - ptr_base
}
}};
}
pub(crate) use offset_of;
//
// TODO: need a field_size_of macro, to compute the size of a struct field in bytes
//
// 1 is not aligned so this won't match any pages
const ALIGNED_WRITE_POSITION_NONE: usize = 1;
/// Reference to an ASM label
struct LabelRef {
// Position in the code block where the label reference exists
pos: usize,
// Label which this refers to
label_idx: usize,
}
/// Block of memory into which instructions can be assembled
pub struct CodeBlock {
// Block of non-executable memory used for dummy code blocks
// This memory is owned by this block and lives as long as the block
dummy_block: Vec<u8>,
// Pointer to memory we are writing into
mem_block: *mut u8,
// Memory block size
mem_size: usize,
// Current writing position
write_pos: usize,
// Table of registered label addresses
label_addrs: Vec<usize>,
// Table of registered label names
label_names: Vec<String>,
// References to labels
label_refs: Vec<LabelRef>,
// Comments for assembly instructions, if that feature is enabled
asm_comments: BTreeMap<usize, Vec<String>>,
// Keep track of the current aligned write position.
// Used for changing protection when writing to the JIT buffer
current_aligned_write_pos: usize,
// Memory protection works at page granularity and this is the
// the size of each page. Used to implement W^X.
page_size: usize,
// Set if the CodeBlock is unable to output some instructions,
// for example, when there is not enough space or when a jump
// target is too far away.
dropped_bytes: bool,
}
impl CodeBlock {
pub fn new_dummy(mem_size: usize) -> Self {
// Allocate some non-executable memory
let mut dummy_block = vec![0; mem_size];
let mem_ptr = dummy_block.as_mut_ptr();
Self {
dummy_block: dummy_block,
mem_block: mem_ptr,
mem_size: mem_size,
write_pos: 0,
label_addrs: Vec::new(),
label_names: Vec::new(),
label_refs: Vec::new(),
asm_comments: BTreeMap::new(),
current_aligned_write_pos: ALIGNED_WRITE_POSITION_NONE,
page_size: 4096,
dropped_bytes: false,
}
}
pub fn new(mem_block: *mut u8, mem_size: usize, page_size: usize) -> Self {
Self {
dummy_block: vec![0; 0],
mem_block: mem_block,
mem_size: mem_size,
write_pos: 0,
label_addrs: Vec::new(),
label_names: Vec::new(),
label_refs: Vec::new(),
asm_comments: BTreeMap::new(),
current_aligned_write_pos: ALIGNED_WRITE_POSITION_NONE,
page_size,
dropped_bytes: false,
}
}
// Check if this code block has sufficient remaining capacity
pub fn has_capacity(&self, num_bytes: usize) -> bool {
self.write_pos + num_bytes < self.mem_size
}
/// Add an assembly comment if the feature is on.
/// If not, this becomes an inline no-op.
#[inline]
pub fn add_comment(&mut self, comment: &str) {
if cfg!(feature = "asm_comments") {
let cur_ptr = self.get_write_ptr().into_usize();
let this_line_comments = self.asm_comments.get(&cur_ptr);
// If there's no current list of comments for this line number, add one.
if this_line_comments.is_none() {
let new_comments = Vec::new();
self.asm_comments.insert(cur_ptr, new_comments);
}
let this_line_comments = self.asm_comments.get_mut(&cur_ptr).unwrap();
// Unless this comment is the same as the last one at this same line, add it.
let string_comment = String::from(comment);
if this_line_comments.last() != Some(&string_comment) {
this_line_comments.push(string_comment);
}
}
}
pub fn comments_at(&self, pos: usize) -> Option<&Vec<String>> {
self.asm_comments.get(&pos)
}
pub fn get_mem_size(&self) -> usize {
self.mem_size
}
pub fn get_write_pos(&self) -> usize {
self.write_pos
}
// Set the current write position
pub fn set_pos(&mut self, pos: usize) {
// Assert here since while CodeBlock functions do bounds checking, there is
// nothing stopping users from taking out an out-of-bounds pointer and
// doing bad accesses with it.
assert!(pos < self.mem_size);
self.write_pos = pos;
}
// Align the current write pointer to a multiple of bytes
pub fn align_pos(&mut self, multiple: u32) {
// Compute the alignment boundary that is lower or equal
// Do everything with usize
let multiple: usize = multiple.try_into().unwrap();
let pos = self.get_write_ptr().raw_ptr() as usize;
let remainder = pos % multiple;
let prev_aligned = pos - remainder;
if prev_aligned == pos {
// Already aligned so do nothing
} else {
// Align by advancing
let pad = multiple - remainder;
self.set_pos(self.get_write_pos() + pad);
}
}
// Set the current write position from a pointer
pub fn set_write_ptr(&mut self, code_ptr: CodePtr) {
let pos = (code_ptr.raw_ptr() as usize) - (self.mem_block as usize);
self.set_pos(pos);
}
// Get a direct pointer into the executable memory block
pub fn get_ptr(&self, offset: usize) -> CodePtr {
unsafe {
let ptr = self.mem_block.offset(offset as isize);
CodePtr(ptr)
}
}
// Get a direct pointer to the current write position
pub fn get_write_ptr(&mut self) -> CodePtr {
self.get_ptr(self.write_pos)
}
// Write a single byte at the current position
pub fn write_byte(&mut self, byte: u8) {
if self.write_pos < self.mem_size {
self.mark_position_writable(self.write_pos);
unsafe { self.mem_block.add(self.write_pos).write(byte) };
self.write_pos += 1;
} else {
self.dropped_bytes = true;
}
}
// Read a single byte at the given position
pub fn read_byte(&self, pos: usize) -> u8 {
assert!(pos < self.mem_size);
unsafe { self.mem_block.add(pos).read() }
}
// Write multiple bytes starting from the current position
pub fn write_bytes(&mut self, bytes: &[u8]) {
for byte in bytes {
self.write_byte(*byte);
}
}
// Write a signed integer over a given number of bits at the current position
pub fn write_int(&mut self, val: u64, num_bits: u32) {
assert!(num_bits > 0);
assert!(num_bits % 8 == 0);
// Switch on the number of bits
match num_bits {
8 => self.write_byte(val as u8),
16 => self.write_bytes(&[(val & 0xff) as u8, ((val >> 8) & 0xff) as u8]),
32 => self.write_bytes(&[
(val & 0xff) as u8,
((val >> 8) & 0xff) as u8,
((val >> 16) & 0xff) as u8,
((val >> 24) & 0xff) as u8,
]),
_ => {
let mut cur = val;
// Write out the bytes
for _byte in 0..(num_bits / 8) {
self.write_byte((cur & 0xff) as u8);
cur >>= 8;
}
}
}
}
/// Check if bytes have been dropped (unwritten because of insufficient space)
pub fn has_dropped_bytes(&self) -> bool {
self.dropped_bytes
}
/// Allocate a new label with a given name
pub fn new_label(&mut self, name: String) -> usize {
// This label doesn't have an address yet
self.label_addrs.push(0);
self.label_names.push(name);
return self.label_addrs.len() - 1;
}
/// Write a label at the current address
pub fn write_label(&mut self, label_idx: usize) {
// TODO: make sure that label_idx is valid
// TODO: add an asseer here
self.label_addrs[label_idx] = self.write_pos;
}
// Add a label reference at the current write position
pub fn label_ref(&mut self, label_idx: usize) {
// TODO: make sure that label_idx is valid
// TODO: add an asseer here
// Keep track of the reference
self.label_refs.push(LabelRef {
pos: self.write_pos,
label_idx,
});
}
// Link internal label references
pub fn link_labels(&mut self) {
let orig_pos = self.write_pos;
// For each label reference
for label_ref in mem::take(&mut self.label_refs) {
let ref_pos = label_ref.pos;
let label_idx = label_ref.label_idx;
assert!(ref_pos < self.mem_size);
let label_addr = self.label_addrs[label_idx];
assert!(label_addr < self.mem_size);
// Compute the offset from the reference's end to the label
let offset = (label_addr as i64) - ((ref_pos + 4) as i64);
self.set_pos(ref_pos);
self.write_int(offset as u64, 32);
}
self.write_pos = orig_pos;
// Clear the label positions and references
self.label_addrs.clear();
self.label_names.clear();
assert!(self.label_refs.is_empty());
}
pub fn mark_position_writable(&mut self, write_pos: usize) {
let page_size = self.page_size;
let aligned_position = (write_pos / page_size) * page_size;
if self.current_aligned_write_pos != aligned_position {
self.current_aligned_write_pos = aligned_position;
#[cfg(not(test))]
unsafe {
use core::ffi::c_void;
let page_ptr = self.get_ptr(aligned_position).raw_ptr() as *mut c_void;
crate::cruby::rb_yjit_mark_writable(page_ptr, page_size.try_into().unwrap());
}
}
}
pub fn mark_all_executable(&mut self) {
self.current_aligned_write_pos = ALIGNED_WRITE_POSITION_NONE;
#[cfg(not(test))]
unsafe {
use core::ffi::c_void;
// NOTE(alan): Right now we do allocate one big chunck and give the top half to the outlined codeblock
// The start of the top half of the region isn't necessarily a page boundary...
let cb_start = self.get_ptr(0).raw_ptr() as *mut c_void;
crate::cruby::rb_yjit_mark_executable(cb_start, self.mem_size.try_into().unwrap());
}
}
}
/// Wrapper struct so we can use the type system to distinguish
/// Between the inlined and outlined code blocks
pub struct OutlinedCb {
// This must remain private
cb: CodeBlock,
}
impl OutlinedCb {
pub fn wrap(cb: CodeBlock) -> Self {
OutlinedCb { cb: cb }
}
pub fn unwrap(&mut self) -> &mut CodeBlock {
&mut self.cb
}
}

1395
yjit/src/asm/x86_64/mod.rs Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,447 @@
#![cfg(test)]
use crate::asm::x86_64::*;
use std::fmt;
/// Produce hex string output from the bytes in a code block
impl<'a> fmt::LowerHex for super::CodeBlock {
fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result {
for pos in 0..self.write_pos {
let byte = self.read_byte(pos);
fmtr.write_fmt(format_args!("{:02x}", byte))?;
}
Ok(())
}
}
/// Check that the bytes for an instruction sequence match a hex string
fn check_bytes<R>(bytes: &str, run: R) where R: FnOnce(&mut super::CodeBlock) {
let mut cb = super::CodeBlock::new_dummy(4096);
run(&mut cb);
assert_eq!(format!("{:x}", cb), bytes);
}
#[test]
fn test_add() {
check_bytes("80c103", |cb| add(cb, CL, imm_opnd(3)));
check_bytes("00d9", |cb| add(cb, CL, BL));
check_bytes("4000e1", |cb| add(cb, CL, SPL));
check_bytes("6601d9", |cb| add(cb, CX, BX));
check_bytes("4801d8", |cb| add(cb, RAX, RBX));
check_bytes("01d1", |cb| add(cb, ECX, EDX));
check_bytes("4c01f2", |cb| add(cb, RDX, R14));
check_bytes("480110", |cb| add(cb, mem_opnd(64, RAX, 0), RDX));
check_bytes("480310", |cb| add(cb, RDX, mem_opnd(64, RAX, 0)));
check_bytes("48035008", |cb| add(cb, RDX, mem_opnd(64, RAX, 8)));
check_bytes("480390ff000000", |cb| add(cb, RDX, mem_opnd(64, RAX, 255)));
check_bytes("4881407fff000000", |cb| add(cb, mem_opnd(64, RAX, 127), imm_opnd(255)));
check_bytes("0110", |cb| add(cb, mem_opnd(32, RAX, 0), EDX));
check_bytes("4883c408", |cb| add(cb, RSP, imm_opnd(8)));
check_bytes("83c108", |cb| add(cb, ECX, imm_opnd(8)));
check_bytes("81c1ff000000", |cb| add(cb, ECX, imm_opnd(255)));
}
#[test]
fn test_add_unsigned() {
// ADD r/m8, imm8
check_bytes("4180c001", |cb| add(cb, R8B, uimm_opnd(1)));
check_bytes("4180c07f", |cb| add(cb, R8B, imm_opnd(i8::MAX.try_into().unwrap())));
// ADD r/m16, imm16
check_bytes("664183c001", |cb| add(cb, R8W, uimm_opnd(1)));
check_bytes("664181c0ff7f", |cb| add(cb, R8W, uimm_opnd(i16::MAX.try_into().unwrap())));
// ADD r/m32, imm32
check_bytes("4183c001", |cb| add(cb, R8D, uimm_opnd(1)));
check_bytes("4181c0ffffff7f", |cb| add(cb, R8D, uimm_opnd(i32::MAX.try_into().unwrap())));
// ADD r/m64, imm32
check_bytes("4983c001", |cb| add(cb, R8, uimm_opnd(1)));
check_bytes("4981c0ffffff7f", |cb| add(cb, R8, uimm_opnd(i32::MAX.try_into().unwrap())));
}
#[test]
fn test_and() {
check_bytes("4421e5", |cb| and(cb, EBP, R12D));
check_bytes("48832008", |cb| and(cb, mem_opnd(64, RAX, 0), imm_opnd(0x08)));
}
#[test]
fn test_call_label() {
check_bytes("e8fbffffff", |cb| {
let label_idx = cb.new_label("fn".to_owned());
call_label(cb, label_idx);
cb.link_labels();
});
}
#[test]
fn test_call_ptr() {
// calling a lower address
check_bytes("e8fbffffff", |cb| {
let ptr = cb.get_write_ptr();
call_ptr(cb, RAX, ptr.raw_ptr());
});
}
#[test]
fn test_call_reg() {
check_bytes("ffd0", |cb| call(cb, RAX));
}
#[test]
fn test_call_mem() {
check_bytes("ff542408", |cb| call(cb, mem_opnd(64, RSP, 8)));
}
#[test]
fn test_cmovcc() {
check_bytes("0f4ff7", |cb| cmovg(cb, ESI, EDI));
check_bytes("0f4f750c", |cb| cmovg(cb, ESI, mem_opnd(32, RBP, 12)));
check_bytes("0f4cc1", |cb| cmovl(cb, EAX, ECX));
check_bytes("480f4cdd", |cb| cmovl(cb, RBX, RBP));
check_bytes("0f4e742404", |cb| cmovle(cb, ESI, mem_opnd(32, RSP, 4)));
}
#[test]
fn test_cmp() {
check_bytes("38d1", |cb| cmp(cb, CL, DL));
check_bytes("39f9", |cb| cmp(cb, ECX, EDI));
check_bytes("493b1424", |cb| cmp(cb, RDX, mem_opnd(64, R12, 0)));
check_bytes("4883f802", |cb| cmp(cb, RAX, imm_opnd(2)));
}
#[test]
fn test_cqo() {
check_bytes("4899", |cb| cqo(cb));
}
#[test]
fn test_jge_label() {
check_bytes("0f8dfaffffff", |cb| {
let label_idx = cb.new_label("loop".to_owned());
jge_label(cb, label_idx);
cb.link_labels();
});
}
#[test]
fn test_jmp_label() {
// Forward jump
check_bytes("e900000000", |cb| {
let label_idx = cb.new_label("next".to_owned());
jmp_label(cb, label_idx);
cb.write_label(label_idx);
cb.link_labels();
});
// Backwards jump
check_bytes("e9fbffffff", |cb| {
let label_idx = cb.new_label("loop".to_owned());
cb.write_label(label_idx);
jmp_label(cb, label_idx);
cb.link_labels();
});
}
#[test]
fn test_jmp_rm() {
check_bytes("41ffe4", |cb| jmp_rm(cb, R12));
}
#[test]
fn test_jo_label() {
check_bytes("0f80faffffff", |cb| {
let label_idx = cb.new_label("loop".to_owned());
jo_label(cb, label_idx);
cb.link_labels();
});
}
#[test]
fn test_lea() {
check_bytes("488d5108", |cb| lea(cb, RDX, mem_opnd(64, RCX, 8)));
check_bytes("488d0500000000", |cb| lea(cb, RAX, mem_opnd(8, RIP, 0)));
check_bytes("488d0505000000", |cb| lea(cb, RAX, mem_opnd(8, RIP, 5)));
check_bytes("488d3d05000000", |cb| lea(cb, RDI, mem_opnd(8, RIP, 5)));
}
#[test]
fn test_mov() {
check_bytes("b807000000", |cb| mov(cb, EAX, imm_opnd(7)));
check_bytes("b8fdffffff", |cb| mov(cb, EAX, imm_opnd(-3)));
check_bytes("41bf03000000", |cb| mov(cb, R15, imm_opnd(3)));
check_bytes("89d8", |cb| mov(cb, EAX, EBX));
check_bytes("89c8", |cb| mov(cb, EAX, ECX));
check_bytes("8b9380000000", |cb| mov(cb, EDX, mem_opnd(32, RBX, 128)));
check_bytes("488b442404", |cb| mov(cb, RAX, mem_opnd(64, RSP, 4)));
// Test `mov rax, 3` => `mov eax, 3` optimization
check_bytes("41b834000000", |cb| mov(cb, R8, imm_opnd(0x34)));
check_bytes("49b80000008000000000", |cb| mov(cb, R8, imm_opnd(0x80000000)));
check_bytes("49b8ffffffffffffffff", |cb| mov(cb, R8, imm_opnd(-1)));
check_bytes("b834000000", |cb| mov(cb, RAX, imm_opnd(0x34)));
check_bytes("48b8020000000000c0ff", |cb| mov(cb, RAX, imm_opnd(-18014398509481982)));
check_bytes("48b80000008000000000", |cb| mov(cb, RAX, imm_opnd(0x80000000)));
check_bytes("48b8ccffffffffffffff", |cb| mov(cb, RAX, imm_opnd(-52))); // yasm thinks this could use a dword immediate instead of qword
check_bytes("48b8ffffffffffffffff", |cb| mov(cb, RAX, imm_opnd(-1))); // yasm thinks this could use a dword immediate instead of qword
check_bytes("4488c9", |cb| mov(cb, CL, R9B));
check_bytes("4889c3", |cb| mov(cb, RBX, RAX));
check_bytes("4889df", |cb| mov(cb, RDI, RBX));
check_bytes("40b60b", |cb| mov(cb, SIL, imm_opnd(11)));
check_bytes("c60424fd", |cb| mov(cb, mem_opnd(8, RSP, 0), imm_opnd(-3)));
check_bytes("48c7470801000000", |cb| mov(cb, mem_opnd(64, RDI, 8), imm_opnd(1)));
//check_bytes("67c7400411000000", |cb| mov(cb, mem_opnd(32, EAX, 4), imm_opnd(0x34))); // We don't distinguish between EAX and RAX here - that's probably fine?
check_bytes("c7400411000000", |cb| mov(cb, mem_opnd(32, RAX, 4), imm_opnd(17)));
check_bytes("41895814", |cb| mov(cb, mem_opnd(32, R8, 20), EBX));
check_bytes("4d8913", |cb| mov(cb, mem_opnd(64, R11, 0), R10));
check_bytes("48c742f8f4ffffff", |cb| mov(cb, mem_opnd(64, RDX, -8), imm_opnd(-12)));
}
#[test]
fn test_mov_unsigned() {
// MOV AL, imm8
check_bytes("b001", |cb| mov(cb, AL, uimm_opnd(1)));
check_bytes("b0ff", |cb| mov(cb, AL, uimm_opnd(u8::MAX.into())));
// MOV AX, imm16
check_bytes("66b80100", |cb| mov(cb, AX, uimm_opnd(1)));
check_bytes("66b8ffff", |cb| mov(cb, AX, uimm_opnd(u16::MAX.into())));
// MOV EAX, imm32
check_bytes("b801000000", |cb| mov(cb, EAX, uimm_opnd(1)));
check_bytes("b8ffffffff", |cb| mov(cb, EAX, uimm_opnd(u32::MAX.into())));
check_bytes("41b800000000", |cb| mov(cb, R8, uimm_opnd(0)));
check_bytes("41b8ffffffff", |cb| mov(cb, R8, uimm_opnd(0xFF_FF_FF_FF)));
// MOV RAX, imm64, will move down into EAX since it fits into 32 bits
check_bytes("b801000000", |cb| mov(cb, RAX, uimm_opnd(1)));
check_bytes("b8ffffffff", |cb| mov(cb, RAX, uimm_opnd(u32::MAX.into())));
// MOV RAX, imm64, will not move down into EAX since it does not fit into 32 bits
check_bytes("48b80000000001000000", |cb| mov(cb, RAX, uimm_opnd(u32::MAX as u64 + 1)));
check_bytes("48b8ffffffffffffffff", |cb| mov(cb, RAX, uimm_opnd(u64::MAX.into())));
check_bytes("49b8ffffffffffffffff", |cb| mov(cb, R8, uimm_opnd(u64::MAX)));
// MOV r8, imm8
check_bytes("41b001", |cb| mov(cb, R8B, uimm_opnd(1)));
check_bytes("41b0ff", |cb| mov(cb, R8B, uimm_opnd(u8::MAX.into())));
// MOV r16, imm16
check_bytes("6641b80100", |cb| mov(cb, R8W, uimm_opnd(1)));
check_bytes("6641b8ffff", |cb| mov(cb, R8W, uimm_opnd(u16::MAX.into())));
// MOV r32, imm32
check_bytes("41b801000000", |cb| mov(cb, R8D, uimm_opnd(1)));
check_bytes("41b8ffffffff", |cb| mov(cb, R8D, uimm_opnd(u32::MAX.into())));
// MOV r64, imm64, will move down into 32 bit since it fits into 32 bits
check_bytes("41b801000000", |cb| mov(cb, R8, uimm_opnd(1)));
// MOV r64, imm64, will not move down into 32 bit since it does not fit into 32 bits
check_bytes("49b8ffffffffffffffff", |cb| mov(cb, R8, uimm_opnd(u64::MAX)));
}
#[test]
fn test_mov_iprel() {
check_bytes("8b0500000000", |cb| mov(cb, EAX, mem_opnd(32, RIP, 0)));
check_bytes("8b0505000000", |cb| mov(cb, EAX, mem_opnd(32, RIP, 5)));
check_bytes("488b0500000000", |cb| mov(cb, RAX, mem_opnd(64, RIP, 0)));
check_bytes("488b0505000000", |cb| mov(cb, RAX, mem_opnd(64, RIP, 5)));
check_bytes("488b3d05000000", |cb| mov(cb, RDI, mem_opnd(64, RIP, 5)));
}
#[test]
fn test_movsx() {
check_bytes("660fbec0", |cb| movsx(cb, AX, AL));
check_bytes("0fbed0", |cb| movsx(cb, EDX, AL));
check_bytes("480fbec3", |cb| movsx(cb, RAX, BL));
check_bytes("0fbfc8", |cb| movsx(cb, ECX, AX));
check_bytes("4c0fbed9", |cb| movsx(cb, R11, CL));
check_bytes("4c6354240c", |cb| movsx(cb, R10, mem_opnd(32, RSP, 12)));
check_bytes("480fbe0424", |cb| movsx(cb, RAX, mem_opnd(8, RSP, 0)));
check_bytes("490fbf5504", |cb| movsx(cb, RDX, mem_opnd(16, R13, 4)));
}
#[test]
fn test_nop() {
check_bytes("90", |cb| nop(cb, 1));
check_bytes("6690", |cb| nop(cb, 2));
check_bytes("0f1f00", |cb| nop(cb, 3));
check_bytes("0f1f4000", |cb| nop(cb, 4));
check_bytes("0f1f440000", |cb| nop(cb, 5));
check_bytes("660f1f440000", |cb| nop(cb, 6));
check_bytes("0f1f8000000000", |cb| nop(cb, 7));
check_bytes("0f1f840000000000", |cb| nop(cb, 8));
check_bytes("660f1f840000000000", |cb| nop(cb, 9));
check_bytes("660f1f84000000000090", |cb| nop(cb, 10));
check_bytes("660f1f8400000000006690", |cb| nop(cb, 11));
check_bytes("660f1f8400000000000f1f00", |cb| nop(cb, 12));
}
#[test]
fn test_not() {
check_bytes("66f7d0", |cb| not(cb, AX));
check_bytes("f7d0", |cb| not(cb, EAX));
check_bytes("49f71424", |cb| not(cb, mem_opnd(64, R12, 0)));
check_bytes("f794242d010000", |cb| not(cb, mem_opnd(32, RSP, 301)));
check_bytes("f71424", |cb| not(cb, mem_opnd(32, RSP, 0)));
check_bytes("f7542403", |cb| not(cb, mem_opnd(32, RSP, 3)));
check_bytes("f75500", |cb| not(cb, mem_opnd(32, RBP, 0)));
check_bytes("f7550d", |cb| not(cb, mem_opnd(32, RBP, 13)));
check_bytes("48f7d0", |cb| not(cb, RAX));
check_bytes("49f7d3", |cb| not(cb, R11));
check_bytes("f710", |cb| not(cb, mem_opnd(32, RAX, 0)));
check_bytes("f716", |cb| not(cb, mem_opnd(32, RSI, 0)));
check_bytes("f717", |cb| not(cb, mem_opnd(32, RDI, 0)));
check_bytes("f75237", |cb| not(cb, mem_opnd(32, RDX, 55)));
check_bytes("f79239050000", |cb| not(cb, mem_opnd(32, RDX, 1337)));
check_bytes("f752c9", |cb| not(cb, mem_opnd(32, RDX, -55)));
check_bytes("f792d5fdffff", |cb| not(cb, mem_opnd(32, RDX, -555)));
}
#[test]
fn test_or() {
check_bytes("09f2", |cb| or(cb, EDX, ESI));
}
#[test]
fn test_pop() {
check_bytes("58", |cb| pop(cb, RAX));
check_bytes("5b", |cb| pop(cb, RBX));
check_bytes("5c", |cb| pop(cb, RSP));
check_bytes("5d", |cb| pop(cb, RBP));
check_bytes("415c", |cb| pop(cb, R12));
check_bytes("8f00", |cb| pop(cb, mem_opnd(64, RAX, 0)));
check_bytes("418f00", |cb| pop(cb, mem_opnd(64, R8, 0)));
check_bytes("418f4003", |cb| pop(cb, mem_opnd(64, R8, 3)));
check_bytes("8f44c803", |cb| pop(cb, mem_opnd_sib(64, RAX, RCX, 8, 3)));
check_bytes("418f44c803", |cb| pop(cb, mem_opnd_sib(64, R8, RCX, 8, 3)));
}
#[test]
fn test_push() {
check_bytes("50", |cb| push(cb, RAX));
check_bytes("53", |cb| push(cb, RBX));
check_bytes("4154", |cb| push(cb, R12));
check_bytes("ff30", |cb| push(cb, mem_opnd(64, RAX, 0)));
check_bytes("41ff30", |cb| push(cb, mem_opnd(64, R8, 0)));
check_bytes("41ff7003", |cb| push(cb, mem_opnd(64, R8, 3)));
check_bytes("ff74c803", |cb| push(cb, mem_opnd_sib(64, RAX, RCX, 8, 3)));
check_bytes("41ff74c803", |cb| push(cb, mem_opnd_sib(64, R8, RCX, 8, 3)));
}
#[test]
fn test_ret() {
check_bytes("c3", |cb| ret(cb));
}
#[test]
fn test_sal() {
check_bytes("66d1e1", |cb| sal(cb, CX, uimm_opnd(1)));
check_bytes("d1e1", |cb| sal(cb, ECX, uimm_opnd(1)));
check_bytes("c1e505", |cb| sal(cb, EBP, uimm_opnd(5)));
check_bytes("d1642444", |cb| sal(cb, mem_opnd(32, RSP, 68), uimm_opnd(1)));
}
#[test]
fn test_sar() {
check_bytes("d1fa", |cb| sar(cb, EDX, uimm_opnd(1)));
}
#[test]
fn test_shr() {
check_bytes("49c1ee07", |cb| shr(cb, R14, uimm_opnd(7)));
}
#[test]
fn test_sub() {
check_bytes("83e801", |cb| sub(cb, EAX, imm_opnd(1)));
check_bytes("4883e802", |cb| sub(cb, RAX, imm_opnd(2)));
}
#[test]
fn test_test() {
check_bytes("84c0", |cb| test(cb, AL, AL));
check_bytes("6685c0", |cb| test(cb, AX, AX));
check_bytes("f6c108", |cb| test(cb, CL, uimm_opnd(8)));
check_bytes("f6c207", |cb| test(cb, DL, uimm_opnd(7)));
check_bytes("f6c108", |cb| test(cb, RCX, uimm_opnd(8)));
check_bytes("f6420808", |cb| test(cb, mem_opnd(8, RDX, 8), uimm_opnd(8)));
check_bytes("f64208ff", |cb| test(cb, mem_opnd(8, RDX, 8), uimm_opnd(255)));
check_bytes("66f7c2ffff", |cb| test(cb, DX, uimm_opnd(0xffff)));
check_bytes("66f74208ffff", |cb| test(cb, mem_opnd(16, RDX, 8), uimm_opnd(0xffff)));
check_bytes("f60601", |cb| test(cb, mem_opnd(8, RSI, 0), uimm_opnd(1)));
check_bytes("f6461001", |cb| test(cb, mem_opnd(8, RSI, 16), uimm_opnd(1)));
check_bytes("f646f001", |cb| test(cb, mem_opnd(8, RSI, -16), uimm_opnd(1)));
check_bytes("854640", |cb| test(cb, mem_opnd(32, RSI, 64), EAX));
check_bytes("4885472a", |cb| test(cb, mem_opnd(64, RDI, 42), RAX));
check_bytes("4885c0", |cb| test(cb, RAX, RAX));
check_bytes("4885f0", |cb| test(cb, RAX, RSI));
check_bytes("48f74640f7ffffff", |cb| test(cb, mem_opnd(64, RSI, 64), imm_opnd(!0x08)));
check_bytes("48f7464008000000", |cb| test(cb, mem_opnd(64, RSI, 64), imm_opnd(0x08)));
check_bytes("48f7c108000000", |cb| test(cb, RCX, imm_opnd(0x08)));
//check_bytes("48a9f7ffff0f", |cb| test(cb, RAX, imm_opnd(0x0FFFFFF7)));
}
#[test]
fn test_xchg() {
check_bytes("4891", |cb| xchg(cb, RAX, RCX));
check_bytes("4995", |cb| xchg(cb, RAX, R13));
check_bytes("4887d9", |cb| xchg(cb, RCX, RBX));
check_bytes("4d87f9", |cb| xchg(cb, R9, R15));
}
#[test]
fn test_xor() {
check_bytes("31c0", |cb| xor(cb, EAX, EAX));
}
#[test]
#[cfg(feature = "disasm")]
fn basic_capstone_usage() -> std::result::Result<(), capstone::Error> {
// Test drive Capstone with simple input
extern crate capstone;
use capstone::prelude::*;
let cs = Capstone::new()
.x86()
.mode(arch::x86::ArchMode::Mode64)
.syntax(arch::x86::ArchSyntax::Intel)
.build()?;
let insns = cs.disasm_all(&[0xCC], 0x1000)?;
match insns.as_ref() {
[insn] => {
assert_eq!(Some("int3"), insn.mnemonic());
Ok(())
}
_ => Err(capstone::Error::CustomError(
"expected to disassemble to int3",
)),
}
}
#[test]
#[cfg(feature = "asm_comments")]
fn block_comments() {
let mut cb = super::CodeBlock::new_dummy(4096);
let first_write_ptr = cb.get_write_ptr().into_usize();
cb.add_comment("Beginning");
xor(&mut cb, EAX, EAX); // 2 bytes long
let second_write_ptr = cb.get_write_ptr().into_usize();
cb.add_comment("Two bytes in");
cb.add_comment("Still two bytes in");
cb.add_comment("Still two bytes in"); // Duplicate, should be ignored
test(&mut cb, mem_opnd(64, RSI, 64), imm_opnd(!0x08)); // 8 bytes long
let third_write_ptr = cb.get_write_ptr().into_usize();
cb.add_comment("Ten bytes in");
assert_eq!(&vec!( "Beginning".to_string() ), cb.comments_at(first_write_ptr).unwrap());
assert_eq!(&vec!( "Two bytes in".to_string(), "Still two bytes in".to_string() ), cb.comments_at(second_write_ptr).unwrap());
assert_eq!(&vec!( "Ten bytes in".to_string() ), cb.comments_at(third_write_ptr).unwrap());
}

6180
yjit/src/codegen.rs Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

2071
yjit/src/core.rs Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

919
yjit/src/cruby.rs Normal file
Просмотреть файл

@ -0,0 +1,919 @@
//! This module deals with making relevant C functions available to Rust YJIT.
//! Some C functions we use we maintain, some are public C extension APIs,
//! some are internal CRuby APIs.
//!
//! ## General notes about linking
//!
//! The YJIT crate compiles to a native static library, which for our purposes
//! we can understand as a collection of object files. On ELF platforms at least,
//! object files can refer to "external symbols" which we could take some
//! liberty and understand as assembly labels that refer to code defined in other
//! object files resolved when linking. When we are linking, say to produce miniruby,
//! the linker resolves and put concrete addresses for each usage of C function in
//! the Rust static library.
//!
//! By declaring external functions and using them, we are asserting the symbols
//! we use have definition in one of the object files we pass to the linker. Declaring
//! a function here that has no definition anywhere causes a linking error.
//!
//! There are more things going on during linking and this section makes a lot of
//! simplifications but hopefully this gives a good enough working mental model.
//!
//! ## Difference from example in the Rustonomicon
//!
//! You might be wondering about why this is different from the [FFI example]
//! in the Nomicon, an official book about Unsafe Rust.
//!
//! There is no `#[link]` attribute because we are not linking against an external
//! library, but rather implicitly asserting that we'll supply a concrete definition
//! for all C functions we call, similar to how pure C projects put functions
//! across different compilation units and link them together.
//!
//! TODO(alan): is the model different enough on Windows that this setup is unworkable?
//! Seems prudent to at least learn more about Windows binary tooling before
//! committing to a design.
//!
//! Alan recommends reading the Nomicon cover to cover as he thinks the book is
//! not very long in general and especially for something that can save hours of
//! debugging Undefined Behavior (UB) down the road.
//!
//! UBs can cause Safe Rust to crash, at which point it's hard to tell which
//! usage of `unsafe` in the codebase invokes UB. Providing safe Rust interface
//! wrapping `unsafe` Rust is a good technique, but requires practice and knowledge
//! about what's well defined and what's undefined.
//!
//! For an extremely advanced example of building safe primitives using Unsafe Rust,
//! see the [GhostCell] paper. Some parts of the paper assume less background knowledge
//! than other parts, so there should be learning opportunities in it for all experience
//! levels.
//!
//! ## Binding generation
//!
//! For the moment declarations on the Rust side are hand written. The code is boilerplate
//! and could be generated automatically with a custom tooling that depend on
//! rust-lang/rust-bindgen. The output Rust code could be checked in to version control
//! and verified on CI like `make update-deps`.
//!
//! Upsides for this design:
//! - the YJIT static lib that links with miniruby and friends will not need bindgen
//! as a dependency at all. This is an important property so Ruby end users can
//! build a YJIT enabled Ruby with no internet connection using a release tarball
//! - Less hand-typed boilerplate
//! - Helps reduce risk of C definitions and Rust declaration going out of sync since
//! CI verifies synchronicity
//!
//! Downsides and known unknowns:
//! - Using rust-bindgen this way seems unusual. We might be depending on parts
//! that the project is not committed to maintaining
//! - This setup assumes rust-bindgen gives deterministic output, which can't be taken
//! for granted
//! - YJIT contributors will need to install libclang on their system to get rust-bindgen
//! to work if they want to run the generation tool locally
//!
//! The elephant in the room is that we'll still need to use Unsafe Rust to call C functions,
//! and the binding generation can't magically save us from learning Unsafe Rust.
//!
//!
//! [FFI example]: https://doc.rust-lang.org/nomicon/ffi.html
//! [GhostCell]: http://plv.mpi-sws.org/rustbelt/ghostcell/
// CRuby types use snake_case. Allow them so we use one name across languages.
#![allow(non_camel_case_types)]
// A lot of imported CRuby globals aren't all-caps
#![allow(non_upper_case_globals)]
use std::convert::From;
use std::ffi::CString;
use std::os::raw::{c_char, c_int, c_long, c_uint, c_void};
use std::panic::{catch_unwind, UnwindSafe};
// We check that we can do this with the configure script and a couple of
// static asserts. u64 and not usize to play nice with lowering to x86.
pub type size_t = u64;
/// A type alias for the redefinition flags coming from CRuby. These are just
/// shifted 1s but not explicitly an enum.
pub type RedefinitionFlag = u32;
// Textually include output from rust-bindgen as suggested by its user guide.
include!("cruby_bindings.inc.rs");
// TODO: For #defines that affect memory layout, we need to check for them
// on build and fail if they're wrong. e.g. USE_FLONUM *must* be true.
// TODO:
// Temporary, these external bindings will likely be auto-generated
// and textually included in this file
extern "C" {
#[link_name = "rb_yjit_alloc_exec_mem"] // we can rename functions with this attribute
pub fn alloc_exec_mem(mem_size: u32) -> *mut u8;
#[link_name = "rb_insn_name"]
pub fn raw_insn_name(insn: VALUE) -> *const c_char;
#[link_name = "rb_insn_len"]
pub fn raw_insn_len(v: VALUE) -> c_int;
#[link_name = "rb_yarv_class_of"]
pub fn CLASS_OF(v: VALUE) -> VALUE;
#[link_name = "rb_get_ec_cfp"]
pub fn get_ec_cfp(ec: EcPtr) -> CfpPtr;
#[link_name = "rb_get_cfp_pc"]
pub fn get_cfp_pc(cfp: CfpPtr) -> *mut VALUE;
#[link_name = "rb_get_cfp_sp"]
pub fn get_cfp_sp(cfp: CfpPtr) -> *mut VALUE;
#[link_name = "rb_get_cfp_self"]
pub fn get_cfp_self(cfp: CfpPtr) -> VALUE;
#[link_name = "rb_get_cfp_ep"]
pub fn get_cfp_ep(cfp: CfpPtr) -> *mut VALUE;
#[link_name = "rb_get_cme_def_type"]
pub fn get_cme_def_type(cme: *const rb_callable_method_entry_t) -> rb_method_type_t;
#[link_name = "rb_get_cme_def_method_serial"]
pub fn get_cme_def_method_serial(cme: *const rb_callable_method_entry_t) -> u64;
#[link_name = "rb_get_cme_def_body_attr_id"]
pub fn get_cme_def_body_attr_id(cme: *const rb_callable_method_entry_t) -> ID;
#[link_name = "rb_get_cme_def_body_optimized_type"]
pub fn get_cme_def_body_optimized_type(
cme: *const rb_callable_method_entry_t,
) -> method_optimized_type;
#[link_name = "rb_get_cme_def_body_optimized_index"]
pub fn get_cme_def_body_optimized_index(cme: *const rb_callable_method_entry_t) -> c_uint;
#[link_name = "rb_get_cme_def_body_cfunc"]
pub fn get_cme_def_body_cfunc(cme: *const rb_callable_method_entry_t)
-> *mut rb_method_cfunc_t;
#[link_name = "rb_get_def_method_serial"]
/// While this returns a uintptr_t in C, we always use it as a Rust u64
pub fn get_def_method_serial(def: *const rb_method_definition_t) -> u64;
#[link_name = "rb_get_def_original_id"]
pub fn get_def_original_id(def: *const rb_method_definition_t) -> ID;
#[link_name = "rb_get_mct_argc"]
pub fn get_mct_argc(mct: *const rb_method_cfunc_t) -> c_int;
#[link_name = "rb_get_mct_func"]
pub fn get_mct_func(mct: *const rb_method_cfunc_t) -> *const u8;
#[link_name = "rb_get_def_iseq_ptr"]
pub fn get_def_iseq_ptr(def: *const rb_method_definition_t) -> IseqPtr;
#[link_name = "rb_iseq_encoded_size"]
pub fn get_iseq_encoded_size(iseq: IseqPtr) -> c_uint;
#[link_name = "rb_get_iseq_body_local_iseq"]
pub fn get_iseq_body_local_iseq(iseq: IseqPtr) -> IseqPtr;
#[link_name = "rb_get_iseq_body_iseq_encoded"]
pub fn get_iseq_body_iseq_encoded(iseq: IseqPtr) -> *mut VALUE;
#[link_name = "rb_get_iseq_body_builtin_inline_p"]
pub fn get_iseq_body_builtin_inline_p(iseq: IseqPtr) -> bool;
#[link_name = "rb_get_iseq_body_stack_max"]
pub fn get_iseq_body_stack_max(iseq: IseqPtr) -> c_uint;
#[link_name = "rb_get_iseq_flags_has_opt"]
pub fn get_iseq_flags_has_opt(iseq: IseqPtr) -> bool;
#[link_name = "rb_get_iseq_flags_has_kw"]
pub fn get_iseq_flags_has_kw(iseq: IseqPtr) -> bool;
#[link_name = "rb_get_iseq_flags_has_rest"]
pub fn get_iseq_flags_has_rest(iseq: IseqPtr) -> bool;
#[link_name = "rb_get_iseq_flags_has_post"]
pub fn get_iseq_flags_has_post(iseq: IseqPtr) -> bool;
#[link_name = "rb_get_iseq_flags_has_kwrest"]
pub fn get_iseq_flags_has_kwrest(iseq: IseqPtr) -> bool;
#[link_name = "rb_get_iseq_flags_has_block"]
pub fn get_iseq_flags_has_block(iseq: IseqPtr) -> bool;
#[link_name = "rb_get_iseq_flags_has_accepts_no_kwarg"]
pub fn get_iseq_flags_has_accepts_no_kwarg(iseq: IseqPtr) -> bool;
#[link_name = "rb_get_iseq_body_local_table_size"]
pub fn get_iseq_body_local_table_size(iseq: IseqPtr) -> c_uint;
#[link_name = "rb_get_iseq_body_param_keyword"]
pub fn get_iseq_body_param_keyword(iseq: IseqPtr) -> *const rb_seq_param_keyword_struct;
#[link_name = "rb_get_iseq_body_param_size"]
pub fn get_iseq_body_param_size(iseq: IseqPtr) -> c_uint;
#[link_name = "rb_get_iseq_body_param_lead_num"]
pub fn get_iseq_body_param_lead_num(iseq: IseqPtr) -> c_int;
#[link_name = "rb_get_iseq_body_param_opt_num"]
pub fn get_iseq_body_param_opt_num(iseq: IseqPtr) -> c_int;
#[link_name = "rb_get_iseq_body_param_opt_table"]
pub fn get_iseq_body_param_opt_table(iseq: IseqPtr) -> *const VALUE;
#[link_name = "rb_get_cikw_keyword_len"]
pub fn get_cikw_keyword_len(cikw: *const rb_callinfo_kwarg) -> c_int;
#[link_name = "rb_get_cikw_keywords_idx"]
pub fn get_cikw_keywords_idx(cikw: *const rb_callinfo_kwarg, idx: c_int) -> VALUE;
#[link_name = "rb_get_call_data_ci"]
pub fn get_call_data_ci(cd: *const rb_call_data) -> *const rb_callinfo;
#[link_name = "rb_yarv_str_eql_internal"]
pub fn rb_str_eql_internal(str1: VALUE, str2: VALUE) -> VALUE;
#[link_name = "rb_yarv_ary_entry_internal"]
pub fn rb_ary_entry_internal(ary: VALUE, offset: c_long) -> VALUE;
#[link_name = "rb_FL_TEST"]
pub fn FL_TEST(obj: VALUE, flags: VALUE) -> VALUE;
#[link_name = "rb_FL_TEST_RAW"]
pub fn FL_TEST_RAW(obj: VALUE, flags: VALUE) -> VALUE;
#[link_name = "rb_RB_TYPE_P"]
pub fn RB_TYPE_P(obj: VALUE, t: ruby_value_type) -> bool;
#[link_name = "rb_BASIC_OP_UNREDEFINED_P"]
pub fn BASIC_OP_UNREDEFINED_P(bop: ruby_basic_operators, klass: RedefinitionFlag) -> bool;
#[link_name = "rb_RSTRUCT_LEN"]
pub fn RSTRUCT_LEN(st: VALUE) -> c_long;
#[link_name = "rb_RSTRUCT_SET"]
pub fn RSTRUCT_SET(st: VALUE, k: c_int, v: VALUE);
// Ruby only defines these in vm_insnhelper.c, not in any header.
// Parsing it would result in a lot of duplicate definitions.
pub fn rb_vm_opt_mod(recv: VALUE, obj: VALUE) -> VALUE;
pub fn rb_vm_splat_array(flag: VALUE, ary: VALUE) -> VALUE;
pub fn rb_vm_defined(
ec: EcPtr,
reg_cfp: CfpPtr,
op_type: rb_num_t,
obj: VALUE,
v: VALUE,
) -> bool;
pub fn rb_vm_set_ivar_idx(obj: VALUE, idx: u32, val: VALUE) -> VALUE;
pub fn rb_vm_setinstancevariable(iseq: IseqPtr, obj: VALUE, id: ID, val: VALUE, ic: IVC);
pub fn rb_aliased_callable_method_entry(
me: *const rb_callable_method_entry_t,
) -> *const rb_callable_method_entry_t;
pub fn rb_iseq_only_optparam_p(iseq: IseqPtr) -> bool;
pub fn rb_iseq_only_kwparam_p(iseq: IseqPtr) -> bool;
pub fn rb_vm_getclassvariable(iseq: IseqPtr, cfp: CfpPtr, id: ID, ic: ICVARC) -> VALUE;
pub fn rb_vm_setclassvariable(
iseq: IseqPtr,
cfp: CfpPtr,
id: ID,
val: VALUE,
ic: ICVARC,
) -> VALUE;
pub fn rb_vm_ic_hit_p(ic: IC, reg_ep: *const VALUE) -> bool;
#[link_name = "rb_vm_ci_argc"]
pub fn vm_ci_argc(ci: *const rb_callinfo) -> c_int;
#[link_name = "rb_vm_ci_mid"]
pub fn vm_ci_mid(ci: *const rb_callinfo) -> ID;
#[link_name = "rb_vm_ci_flag"]
pub fn vm_ci_flag(ci: *const rb_callinfo) -> c_uint;
#[link_name = "rb_vm_ci_kwarg"]
pub fn vm_ci_kwarg(ci: *const rb_callinfo) -> *const rb_callinfo_kwarg;
#[link_name = "rb_METHOD_ENTRY_VISI"]
pub fn METHOD_ENTRY_VISI(me: *const rb_callable_method_entry_t) -> rb_method_visibility_t;
pub fn rb_yjit_branch_stub_hit(
branch_ptr: *const c_void,
target_idx: u32,
ec: EcPtr,
) -> *const c_void;
pub fn rb_str_bytesize(str: VALUE) -> VALUE;
#[link_name = "rb_RCLASS_ORIGIN"]
pub fn RCLASS_ORIGIN(v: VALUE) -> VALUE;
}
/// Helper so we can get a Rust string for insn_name()
pub fn insn_name(opcode: usize) -> String {
use std::ffi::CStr;
unsafe {
// Look up Ruby's NULL-terminated insn name string
let op_name = raw_insn_name(VALUE(opcode));
// Convert the op name C string to a Rust string and concat
let op_name = CStr::from_ptr(op_name).to_str().unwrap();
// Convert into an owned string
op_name.to_string()
}
}
#[allow(unused_variables)]
pub fn insn_len(opcode: usize) -> u32 {
#[cfg(test)]
panic!("insn_len is a CRuby function, and we don't link against CRuby for Rust testing!");
#[cfg(not(test))]
unsafe {
raw_insn_len(VALUE(opcode)).try_into().unwrap()
}
}
/// Opaque iseq type for opaque iseq pointers from vm_core.h
/// See: <https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs>
#[repr(C)]
pub struct rb_iseq_t {
_data: [u8; 0],
_marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
}
/// An object handle similar to VALUE in the C code. Our methods assume
/// that this is a handle. Sometimes the C code briefly uses VALUE as
/// an unsigned integer type and don't necessarily store valid handles but
/// thankfully those cases are rare and don't cross the FFI boundary.
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
#[repr(transparent)] // same size and alignment as simply `usize`
pub struct VALUE(pub usize);
/// Pointer to an ISEQ
pub type IseqPtr = *const rb_iseq_t;
/// Opaque execution-context type from vm_core.h
#[repr(C)]
pub struct rb_execution_context_struct {
_data: [u8; 0],
_marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
}
/// Alias for rb_execution_context_struct used by CRuby sometimes
pub type rb_execution_context_t = rb_execution_context_struct;
/// Pointer to an execution context (rb_execution_context_struct)
pub type EcPtr = *const rb_execution_context_struct;
// From method.h
#[repr(C)]
pub struct rb_method_definition_t {
_data: [u8; 0],
_marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
}
type rb_method_definition_struct = rb_method_definition_t;
/// Opaque cfunc type from method.h
#[repr(C)]
pub struct rb_method_cfunc_t {
_data: [u8; 0],
_marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
}
/// Opaque FILE type from the C standard library
#[repr(C)]
pub struct FILE {
_data: [u8; 0],
_marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
}
/// Opaque call-cache type from vm_callinfo.h
#[repr(C)]
pub struct rb_callcache {
_data: [u8; 0],
_marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
}
/// Opaque call-info type from vm_callinfo.h
#[repr(C)]
pub struct rb_callinfo_kwarg {
_data: [u8; 0],
_marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
}
/// Opaque control_frame (CFP) struct from vm_core.h
#[repr(C)]
pub struct rb_control_frame_struct {
_data: [u8; 0],
_marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
}
/// Pointer to a control frame pointer (CFP)
pub type CfpPtr = *mut rb_control_frame_struct;
/// Opaque struct from vm_core.h
#[repr(C)]
pub struct rb_cref_t {
_data: [u8; 0],
_marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
}
impl VALUE {
/// Dump info about the value to the console similarly to rp(VALUE)
pub fn dump_info(self) {
unsafe { rb_obj_info_dump(self) }
}
/// Return whether the value is truthy or falsy in Ruby -- only nil and false are falsy.
pub fn test(self) -> bool {
let VALUE(cval) = self;
let VALUE(qnilval) = Qnil;
(cval & !qnilval) != 0
}
/// Return true if the number is an immediate integer, flonum or static symbol
fn immediate_p(self) -> bool {
let VALUE(cval) = self;
(cval & 7) != 0
}
/// Return true if the value is a Ruby immediate integer, flonum, static symbol, nil or false
pub fn special_const_p(self) -> bool {
self.immediate_p() || !self.test()
}
/// Return true if the value is a Ruby Fixnum (immediate-size integer)
pub fn fixnum_p(self) -> bool {
let VALUE(cval) = self;
(cval & 1) == 1
}
/// Return true if the value is an immediate Ruby floating-point number (flonum)
pub fn flonum_p(self) -> bool {
let VALUE(cval) = self;
(cval & 3) == 2
}
/// Return true for a static (non-heap) Ruby symbol
pub fn static_sym_p(self) -> bool {
let VALUE(cval) = self;
(cval & 0xff) == RUBY_SYMBOL_FLAG
}
/// Returns true or false depending on whether the value is nil
pub fn nil_p(self) -> bool {
self == Qnil
}
/// Read the flags bits from the RBasic object, then return a Ruby type enum (e.g. RUBY_T_ARRAY)
pub fn builtin_type(self) -> ruby_value_type {
assert!(!self.special_const_p());
let VALUE(cval) = self;
let rbasic_ptr = cval as *const RBasic;
let flags_bits: usize = unsafe { (*rbasic_ptr).flags }.as_usize();
(flags_bits & (RUBY_T_MASK as usize)) as ruby_value_type
}
pub fn class_of(self) -> VALUE {
unsafe { CLASS_OF(self) }
}
pub fn as_isize(self) -> isize {
let VALUE(is) = self;
is as isize
}
pub fn as_i32(self) -> i32 {
self.as_i64().try_into().unwrap()
}
pub fn as_u32(self) -> u32 {
let VALUE(i) = self;
i.try_into().unwrap()
}
pub fn as_i64(self) -> i64 {
let VALUE(i) = self;
i as i64
}
pub fn as_u64(self) -> u64 {
let VALUE(i) = self;
i.try_into().unwrap()
}
pub fn as_usize(self) -> usize {
let VALUE(us) = self;
us as usize
}
pub fn as_ptr<T>(self) -> *const T {
let VALUE(us) = self;
us as *const T
}
pub fn as_mut_ptr<T>(self) -> *mut T {
let VALUE(us) = self;
us as *mut T
}
/// For working with opague pointers and encoding null check.
/// Similar to [std::ptr::NonNull], but for `*const T`. `NonNull<T>`
/// is for `*mut T` while our C functions are setup to use `*const T`.
/// Casting from `NonNull<T>` to `*const T` is too noisy.
pub fn as_optional_ptr<T>(self) -> Option<*const T> {
let ptr: *const T = self.as_ptr();
if ptr.is_null() {
None
} else {
Some(ptr)
}
}
/// Assert that `self` is an iseq in debug builds
pub fn as_iseq(self) -> IseqPtr {
let ptr: IseqPtr = self.as_ptr();
#[cfg(debug_assertions)]
if !ptr.is_null() {
unsafe { rb_assert_iseq_handle(self) }
}
ptr
}
/// Assert that `self` is a method entry in debug builds
pub fn as_cme(self) -> *const rb_callable_method_entry_t {
let ptr: *const rb_callable_method_entry_t = self.as_ptr();
#[cfg(debug_assertions)]
if !ptr.is_null() {
unsafe { rb_assert_cme_handle(self) }
}
ptr
}
}
impl VALUE {
pub fn fixnum_from_usize(item: usize) -> Self {
assert!(item <= (RUBY_FIXNUM_MAX as usize)); // An unsigned will always be greater than RUBY_FIXNUM_MIN
let k: usize = item.wrapping_add(item.wrapping_add(1));
VALUE(k)
}
}
impl From<IseqPtr> for VALUE {
/// For `.into()` convenience
fn from(iseq: IseqPtr) -> Self {
VALUE(iseq as usize)
}
}
impl From<*const rb_callable_method_entry_t> for VALUE {
/// For `.into()` convenience
fn from(cme: *const rb_callable_method_entry_t) -> Self {
VALUE(cme as usize)
}
}
impl From<VALUE> for u64 {
fn from(value: VALUE) -> Self {
let VALUE(uimm) = value;
uimm as u64
}
}
impl From<VALUE> for i64 {
fn from(value: VALUE) -> Self {
let VALUE(uimm) = value;
assert!(uimm <= (i64::MAX as usize));
uimm as i64
}
}
impl From<VALUE> for i32 {
fn from(value: VALUE) -> Self {
let VALUE(uimm) = value;
assert!(uimm <= (i32::MAX as usize));
uimm as i32
}
}
/// Produce a Ruby string from a Rust string slice
pub fn rust_str_to_ruby(str: &str) -> VALUE {
unsafe { rb_utf8_str_new(str.as_ptr() as *const i8, str.len() as i64) }
}
/// Produce a Ruby symbol from a Rust string slice
pub fn rust_str_to_sym(str: &str) -> VALUE {
let c_str = CString::new(str).unwrap();
let c_ptr: *const c_char = c_str.as_ptr();
unsafe { rb_id2sym(rb_intern(c_ptr)) }
}
/// A location in Rust code for integrating with debugging facilities defined in C.
/// Use the [src_loc!] macro to crate an instance.
pub struct SourceLocation {
pub file: CString,
pub line: c_int,
}
/// Make a [SourceLocation] at the current spot.
macro_rules! src_loc {
() => {
// NOTE(alan): `CString::new` allocates so we might want to limit this to debug builds.
$crate::cruby::SourceLocation {
file: std::ffi::CString::new(file!()).unwrap(), // ASCII source file paths
line: line!().try_into().unwrap(), // not that many lines
}
};
}
pub(crate) use src_loc;
/// Run GC write barrier. Required after making a new edge in the object reference
/// graph from `old` to `young`.
macro_rules! obj_written {
($old: expr, $young: expr) => {
let (old, young): (VALUE, VALUE) = ($old, $young);
let src_loc = $crate::cruby::src_loc!();
unsafe { rb_yjit_obj_written(old, young, src_loc.file.as_ptr(), src_loc.line) };
};
}
pub(crate) use obj_written;
/// Acquire the VM lock, make sure all other Ruby threads are asleep then run
/// some code while holding the lock. Returns whatever `func` returns.
/// Use with [src_loc!].
///
/// Required for code patching in the presence of ractors.
pub fn with_vm_lock<F, R>(loc: SourceLocation, func: F) -> R
where
F: FnOnce() -> R + UnwindSafe,
{
let file = loc.file.as_ptr();
let line = loc.line;
let mut recursive_lock_level: c_uint = 0;
unsafe { rb_yjit_vm_lock_then_barrier(&mut recursive_lock_level, file, line) };
let ret = match catch_unwind(func) {
Ok(result) => result,
Err(_) => {
// Theoretically we can recover from some of these panics,
// but it's too late if the unwind reaches here.
use std::{io, process, str};
let _ = catch_unwind(|| {
// IO functions can panic too.
eprintln!(
"YJIT panicked while holding VM lock acquired at {}:{}. Aborting...",
str::from_utf8(loc.file.as_bytes()).unwrap_or("<not utf8>"),
line,
);
});
process::abort();
}
};
unsafe { rb_yjit_vm_unlock(&mut recursive_lock_level, file, line) };
ret
}
// Non-idiomatic capitalization for consistency with CRuby code
#[allow(non_upper_case_globals)]
pub const Qfalse: VALUE = VALUE(0);
#[allow(non_upper_case_globals)]
pub const Qnil: VALUE = VALUE(8);
#[allow(non_upper_case_globals)]
pub const Qtrue: VALUE = VALUE(20);
#[allow(non_upper_case_globals)]
pub const Qundef: VALUE = VALUE(52);
pub const RUBY_SYMBOL_FLAG: usize = 0x0c;
pub const RUBY_LONG_MIN: isize = std::os::raw::c_long::MIN as isize;
pub const RUBY_LONG_MAX: isize = std::os::raw::c_long::MAX as isize;
pub const RUBY_FIXNUM_MIN: isize = RUBY_LONG_MIN / 2;
pub const RUBY_FIXNUM_MAX: isize = RUBY_LONG_MAX / 2;
pub const RUBY_FIXNUM_FLAG: usize = 0x1;
pub const RUBY_FLONUM_FLAG: usize = 0x2;
pub const RUBY_FLONUM_MASK: usize = 0x3;
pub const RUBY_IMMEDIATE_MASK: usize = 0x7;
pub const RUBY_SPECIAL_SHIFT: usize = 8;
// Constants from vm_core.h
pub const VM_SPECIAL_OBJECT_VMCORE: usize = 0x1;
pub const VM_ENV_DATA_INDEX_SPECVAL: isize = -1;
pub const VM_ENV_DATA_INDEX_FLAGS: isize = 0;
pub const VM_ENV_DATA_SIZE: usize = 3;
// From vm_callinfo.h
pub const VM_CALL_ARGS_SPLAT: u32 = 1 << VM_CALL_ARGS_SPLAT_bit;
pub const VM_CALL_ARGS_BLOCKARG: u32 = 1 << VM_CALL_ARGS_BLOCKARG_bit;
pub const VM_CALL_FCALL: u32 = 1 << VM_CALL_FCALL_bit;
pub const VM_CALL_KWARG: u32 = 1 << VM_CALL_KWARG_bit;
pub const VM_CALL_KW_SPLAT: u32 = 1 << VM_CALL_KW_SPLAT_bit;
pub const VM_CALL_TAILCALL: u32 = 1 << VM_CALL_TAILCALL_bit;
pub const SIZEOF_VALUE: usize = 8;
pub const SIZEOF_VALUE_I32: i32 = SIZEOF_VALUE as i32;
pub const RUBY_FL_SINGLETON: usize = RUBY_FL_USER_0;
pub const ROBJECT_EMBED: usize = RUBY_FL_USER_1;
pub const ROBJECT_EMBED_LEN_MAX: usize = 3; // This is a complex calculation in ruby/internal/core/robject.h
pub const RMODULE_IS_REFINEMENT: usize = RUBY_FL_USER_3;
// Constants from include/ruby/internal/fl_type.h
pub const RUBY_FL_USHIFT: usize = 12;
pub const RUBY_FL_USER_0: usize = 1 << (RUBY_FL_USHIFT + 0);
pub const RUBY_FL_USER_1: usize = 1 << (RUBY_FL_USHIFT + 1);
pub const RUBY_FL_USER_2: usize = 1 << (RUBY_FL_USHIFT + 2);
pub const RUBY_FL_USER_3: usize = 1 << (RUBY_FL_USHIFT + 3);
pub const RUBY_FL_USER_4: usize = 1 << (RUBY_FL_USHIFT + 4);
pub const RUBY_FL_USER_5: usize = 1 << (RUBY_FL_USHIFT + 5);
pub const RUBY_FL_USER_6: usize = 1 << (RUBY_FL_USHIFT + 6);
pub const RUBY_FL_USER_7: usize = 1 << (RUBY_FL_USHIFT + 7);
pub const RUBY_FL_USER_8: usize = 1 << (RUBY_FL_USHIFT + 8);
pub const RUBY_FL_USER_9: usize = 1 << (RUBY_FL_USHIFT + 9);
pub const RUBY_FL_USER_10: usize = 1 << (RUBY_FL_USHIFT + 10);
pub const RUBY_FL_USER_11: usize = 1 << (RUBY_FL_USHIFT + 11);
pub const RUBY_FL_USER_12: usize = 1 << (RUBY_FL_USHIFT + 12);
pub const RUBY_FL_USER_13: usize = 1 << (RUBY_FL_USHIFT + 13);
pub const RUBY_FL_USER_14: usize = 1 << (RUBY_FL_USHIFT + 14);
pub const RUBY_FL_USER_15: usize = 1 << (RUBY_FL_USHIFT + 15);
pub const RUBY_FL_USER_16: usize = 1 << (RUBY_FL_USHIFT + 16);
pub const RUBY_FL_USER_17: usize = 1 << (RUBY_FL_USHIFT + 17);
pub const RUBY_FL_USER_18: usize = 1 << (RUBY_FL_USHIFT + 18);
pub const RUBY_FL_USER_19: usize = 1 << (RUBY_FL_USHIFT + 19);
// Constants from include/ruby/internal/core/rarray.h
pub const RARRAY_EMBED_FLAG: usize = RUBY_FL_USER_1;
pub const RARRAY_EMBED_LEN_SHIFT: usize = RUBY_FL_USHIFT + 3;
pub const RARRAY_EMBED_LEN_MASK: usize = RUBY_FL_USER_3 | RUBY_FL_USER_4;
// From internal/struct.h
pub const RSTRUCT_EMBED_LEN_MASK: usize = RUBY_FL_USER_2 | RUBY_FL_USER_1;
// From iseq.h
pub const ISEQ_TRANSLATED: usize = RUBY_FL_USER_7;
// We'll need to encode a lot of Ruby struct/field offsets as constants unless we want to
// redeclare all the Ruby C structs and write our own offsetof macro. For now, we use constants.
pub const RUBY_OFFSET_RBASIC_FLAGS: i32 = 0; // struct RBasic, field "flags"
pub const RUBY_OFFSET_RBASIC_KLASS: i32 = 8; // struct RBasic, field "klass"
pub const RUBY_OFFSET_RARRAY_AS_HEAP_LEN: i32 = 16; // struct RArray, subfield "as.heap.len"
pub const RUBY_OFFSET_RARRAY_AS_HEAP_PTR: i32 = 32; // struct RArray, subfield "as.heap.ptr"
pub const RUBY_OFFSET_RARRAY_AS_ARY: i32 = 16; // struct RArray, subfield "as.ary"
pub const RUBY_OFFSET_RSTRUCT_AS_HEAP_PTR: i32 = 24; // struct RStruct, subfield "as.heap.ptr"
pub const RUBY_OFFSET_RSTRUCT_AS_ARY: i32 = 16; // struct RStruct, subfield "as.ary"
pub const RUBY_OFFSET_ROBJECT_AS_ARY: i32 = 16; // struct RObject, subfield "as.ary"
pub const RUBY_OFFSET_ROBJECT_AS_HEAP_NUMIV: i32 = 16; // struct RObject, subfield "as.heap.numiv"
pub const RUBY_OFFSET_ROBJECT_AS_HEAP_IVPTR: i32 = 24; // struct RObject, subfield "as.heap.ivptr"
// Constants from rb_control_frame_t vm_core.h
pub const RUBY_OFFSET_CFP_PC: i32 = 0;
pub const RUBY_OFFSET_CFP_SP: i32 = 8;
pub const RUBY_OFFSET_CFP_ISEQ: i32 = 16;
pub const RUBY_OFFSET_CFP_SELF: i32 = 24;
pub const RUBY_OFFSET_CFP_EP: i32 = 32;
pub const RUBY_OFFSET_CFP_BLOCK_CODE: i32 = 40;
pub const RUBY_OFFSET_CFP_BP: i32 = 48; // field __bp__
pub const RUBY_OFFSET_CFP_JIT_RETURN: i32 = 56;
pub const RUBY_SIZEOF_CONTROL_FRAME: usize = 64;
// Constants from rb_execution_context_t vm_core.h
pub const RUBY_OFFSET_EC_CFP: i32 = 16;
pub const RUBY_OFFSET_EC_INTERRUPT_FLAG: i32 = 32; // rb_atomic_t (u32)
pub const RUBY_OFFSET_EC_INTERRUPT_MASK: i32 = 36; // rb_atomic_t (u32)
pub const RUBY_OFFSET_EC_THREAD_PTR: i32 = 48;
// Constants from rb_thread_t in vm_core.h
pub const RUBY_OFFSET_THREAD_SELF: i32 = 16;
// Constants from iseq_inline_constant_cache (IC) and iseq_inline_constant_cache_entry (ICE) in vm_core.h
pub const RUBY_OFFSET_IC_ENTRY: i32 = 0;
pub const RUBY_OFFSET_ICE_VALUE: i32 = 8;
// TODO: need to dynamically autogenerate constants for all the YARV opcodes from insns.def
// TODO: typing of these adds unnecessary casting
pub const OP_NOP: usize = 0;
pub const OP_GETLOCAL: usize = 1;
pub const OP_SETLOCAL: usize = 2;
pub const OP_GETBLOCKPARAM: usize = 3;
pub const OP_SETBLOCKPARAM: usize = 4;
pub const OP_GETBLOCKPARAMPROXY: usize = 5;
pub const OP_GETSPECIAL: usize = 6;
pub const OP_SETSPECIAL: usize = 7;
pub const OP_GETINSTANCEVARIABLE: usize = 8;
pub const OP_SETINSTANCEVARIABLE: usize = 9;
pub const OP_GETCLASSVARIABLE: usize = 10;
pub const OP_SETCLASSVARIABLE: usize = 11;
pub const OP_GETCONSTANT: usize = 12;
pub const OP_SETCONSTANT: usize = 13;
pub const OP_GETGLOBAL: usize = 14;
pub const OP_SETGLOBAL: usize = 15;
pub const OP_PUTNIL: usize = 16;
pub const OP_PUTSELF: usize = 17;
pub const OP_PUTOBJECT: usize = 18;
pub const OP_PUTSPECIALOBJECT: usize = 19;
pub const OP_PUTSTRING: usize = 20;
pub const OP_CONCATSTRINGS: usize = 21;
pub const OP_ANYTOSTRING: usize = 22;
pub const OP_TOREGEXP: usize = 23;
pub const OP_INTERN: usize = 24;
pub const OP_NEWARRAY: usize = 25;
pub const OP_NEWARRAYKWSPLAT: usize = 26;
pub const OP_DUPARRAY: usize = 27;
pub const OP_DUPHASH: usize = 28;
pub const OP_EXPANDARRAY: usize = 29;
pub const OP_CONCATARRAY: usize = 30;
pub const OP_SPLATARRAY: usize = 31;
pub const OP_NEWHASH: usize = 32;
pub const OP_NEWRANGE: usize = 33;
pub const OP_POP: usize = 34;
pub const OP_DUP: usize = 35;
pub const OP_DUPN: usize = 36;
pub const OP_SWAP: usize = 37;
pub const OP_TOPN: usize = 38;
pub const OP_SETN: usize = 39;
pub const OP_ADJUSTSTACK: usize = 40;
pub const OP_DEFINED: usize = 41;
pub const OP_CHECKMATCH: usize = 42;
pub const OP_CHECKKEYWORD: usize = 43;
pub const OP_CHECKTYPE: usize = 44;
pub const OP_DEFINECLASS: usize = 45;
pub const OP_DEFINEMETHOD: usize = 46;
pub const OP_DEFINESMETHOD: usize = 47;
pub const OP_SEND: usize = 48;
pub const OP_OPT_SEND_WITHOUT_BLOCK: usize = 49;
pub const OP_OBJTOSTRING: usize = 50;
pub const OP_OPT_STR_FREEZE: usize = 51;
pub const OP_OPT_NIL_P: usize = 52;
pub const OP_OPT_STR_UMINUS: usize = 53;
pub const OP_OPT_NEWARRAY_MAX: usize = 54;
pub const OP_OPT_NEWARRAY_MIN: usize = 55;
pub const OP_INVOKESUPER: usize = 56;
pub const OP_INVOKEBLOCK: usize = 57;
pub const OP_LEAVE: usize = 58;
pub const OP_THROW: usize = 59;
pub const OP_JUMP: usize = 60;
pub const OP_BRANCHIF: usize = 61;
pub const OP_BRANCHUNLESS: usize = 62;
pub const OP_BRANCHNIL: usize = 63;
pub const OP_OPT_GETINLINECACHE: usize = 64;
pub const OP_OPT_SETINLINECACHE: usize = 65;
pub const OP_ONCE: usize = 66;
pub const OP_OPT_CASE_DISPATCH: usize = 67;
pub const OP_OPT_PLUS: usize = 68;
pub const OP_OPT_MINUS: usize = 69;
pub const OP_OPT_MULT: usize = 70;
pub const OP_OPT_DIV: usize = 71;
pub const OP_OPT_MOD: usize = 72;
pub const OP_OPT_EQ: usize = 73;
pub const OP_OPT_NEQ: usize = 74;
pub const OP_OPT_LT: usize = 75;
pub const OP_OPT_LE: usize = 76;
pub const OP_OPT_GT: usize = 77;
pub const OP_OPT_GE: usize = 78;
pub const OP_OPT_LTLT: usize = 79;
pub const OP_OPT_AND: usize = 80;
pub const OP_OPT_OR: usize = 81;
pub const OP_OPT_AREF: usize = 82;
pub const OP_OPT_ASET: usize = 83;
pub const OP_OPT_ASET_WITH: usize = 84;
pub const OP_OPT_AREF_WITH: usize = 85;
pub const OP_OPT_LENGTH: usize = 86;
pub const OP_OPT_SIZE: usize = 87;
pub const OP_OPT_EMPTY_P: usize = 88;
pub const OP_OPT_SUCC: usize = 89;
pub const OP_OPT_NOT: usize = 90;
pub const OP_OPT_REGEXPMATCH2: usize = 91;
pub const OP_INVOKEBUILTIN: usize = 92;
pub const OP_OPT_INVOKEBUILTIN_DELEGATE: usize = 93;
pub const OP_OPT_INVOKEBUILTIN_DELEGATE_LEAVE: usize = 94;
pub const OP_GETLOCAL_WC_0: usize = 95;
pub const OP_GETLOCAL_WC_1: usize = 96;
pub const OP_SETLOCAL_WC_0: usize = 97;
pub const OP_SETLOCAL_WC_1: usize = 98;
pub const OP_PUTOBJECT_INT2FIX_0_: usize = 99;
pub const OP_PUTOBJECT_INT2FIX_1_: usize = 100;
pub const VM_INSTRUCTION_SIZE: usize = 202;

Просмотреть файл

@ -0,0 +1,783 @@
/* automatically generated by rust-bindgen 0.59.2 */
pub const INTEGER_REDEFINED_OP_FLAG: u32 = 1;
pub const FLOAT_REDEFINED_OP_FLAG: u32 = 2;
pub const STRING_REDEFINED_OP_FLAG: u32 = 4;
pub const ARRAY_REDEFINED_OP_FLAG: u32 = 8;
pub const HASH_REDEFINED_OP_FLAG: u32 = 16;
pub const SYMBOL_REDEFINED_OP_FLAG: u32 = 64;
pub const TIME_REDEFINED_OP_FLAG: u32 = 128;
pub const REGEXP_REDEFINED_OP_FLAG: u32 = 256;
pub const NIL_REDEFINED_OP_FLAG: u32 = 512;
pub const TRUE_REDEFINED_OP_FLAG: u32 = 1024;
pub const FALSE_REDEFINED_OP_FLAG: u32 = 2048;
pub const PROC_REDEFINED_OP_FLAG: u32 = 4096;
pub const VM_ENV_DATA_INDEX_ME_CREF: i32 = -2;
pub const VM_BLOCK_HANDLER_NONE: u32 = 0;
pub type ID = ::std::os::raw::c_ulong;
extern "C" {
pub fn rb_singleton_class(obj: VALUE) -> VALUE;
}
pub type rb_alloc_func_t = ::std::option::Option<unsafe extern "C" fn(klass: VALUE) -> VALUE>;
extern "C" {
pub fn rb_get_alloc_func(klass: VALUE) -> rb_alloc_func_t;
}
#[repr(C)]
pub struct RBasic {
pub flags: VALUE,
pub klass: VALUE,
}
pub const RUBY_T_NONE: ruby_value_type = 0;
pub const RUBY_T_OBJECT: ruby_value_type = 1;
pub const RUBY_T_CLASS: ruby_value_type = 2;
pub const RUBY_T_MODULE: ruby_value_type = 3;
pub const RUBY_T_FLOAT: ruby_value_type = 4;
pub const RUBY_T_STRING: ruby_value_type = 5;
pub const RUBY_T_REGEXP: ruby_value_type = 6;
pub const RUBY_T_ARRAY: ruby_value_type = 7;
pub const RUBY_T_HASH: ruby_value_type = 8;
pub const RUBY_T_STRUCT: ruby_value_type = 9;
pub const RUBY_T_BIGNUM: ruby_value_type = 10;
pub const RUBY_T_FILE: ruby_value_type = 11;
pub const RUBY_T_DATA: ruby_value_type = 12;
pub const RUBY_T_MATCH: ruby_value_type = 13;
pub const RUBY_T_COMPLEX: ruby_value_type = 14;
pub const RUBY_T_RATIONAL: ruby_value_type = 15;
pub const RUBY_T_NIL: ruby_value_type = 17;
pub const RUBY_T_TRUE: ruby_value_type = 18;
pub const RUBY_T_FALSE: ruby_value_type = 19;
pub const RUBY_T_SYMBOL: ruby_value_type = 20;
pub const RUBY_T_FIXNUM: ruby_value_type = 21;
pub const RUBY_T_UNDEF: ruby_value_type = 22;
pub const RUBY_T_IMEMO: ruby_value_type = 26;
pub const RUBY_T_NODE: ruby_value_type = 27;
pub const RUBY_T_ICLASS: ruby_value_type = 28;
pub const RUBY_T_ZOMBIE: ruby_value_type = 29;
pub const RUBY_T_MOVED: ruby_value_type = 30;
pub const RUBY_T_MASK: ruby_value_type = 31;
pub type ruby_value_type = u32;
pub type st_data_t = ::std::os::raw::c_ulong;
pub type st_index_t = st_data_t;
extern "C" {
pub fn rb_class_get_superclass(klass: VALUE) -> VALUE;
}
extern "C" {
pub static mut rb_mKernel: VALUE;
}
extern "C" {
pub static mut rb_cBasicObject: VALUE;
}
extern "C" {
pub static mut rb_cArray: VALUE;
}
extern "C" {
pub static mut rb_cFalseClass: VALUE;
}
extern "C" {
pub static mut rb_cFloat: VALUE;
}
extern "C" {
pub static mut rb_cHash: VALUE;
}
extern "C" {
pub static mut rb_cInteger: VALUE;
}
extern "C" {
pub static mut rb_cModule: VALUE;
}
extern "C" {
pub static mut rb_cNilClass: VALUE;
}
extern "C" {
pub static mut rb_cString: VALUE;
}
extern "C" {
pub static mut rb_cSymbol: VALUE;
}
extern "C" {
pub static mut rb_cThread: VALUE;
}
extern "C" {
pub static mut rb_cTrueClass: VALUE;
}
extern "C" {
pub fn rb_ary_new_capa(capa: ::std::os::raw::c_long) -> VALUE;
}
extern "C" {
pub fn rb_ary_store(ary: VALUE, key: ::std::os::raw::c_long, val: VALUE);
}
extern "C" {
pub fn rb_ary_resurrect(ary: VALUE) -> VALUE;
}
extern "C" {
pub fn rb_ary_clear(ary: VALUE) -> VALUE;
}
extern "C" {
pub fn rb_hash_new() -> VALUE;
}
extern "C" {
pub fn rb_hash_aref(hash: VALUE, key: VALUE) -> VALUE;
}
extern "C" {
pub fn rb_hash_aset(hash: VALUE, key: VALUE, val: VALUE) -> VALUE;
}
extern "C" {
pub fn rb_hash_bulk_insert(argc: ::std::os::raw::c_long, argv: *const VALUE, hash: VALUE);
}
extern "C" {
pub fn rb_sym2id(obj: VALUE) -> ID;
}
extern "C" {
pub fn rb_id2sym(id: ID) -> VALUE;
}
extern "C" {
pub fn rb_intern(name: *const ::std::os::raw::c_char) -> ID;
}
extern "C" {
pub fn rb_gc_mark(obj: VALUE);
}
extern "C" {
pub fn rb_gc_mark_movable(obj: VALUE);
}
extern "C" {
pub fn rb_gc_location(obj: VALUE) -> VALUE;
}
extern "C" {
pub fn rb_obj_is_kind_of(obj: VALUE, klass: VALUE) -> VALUE;
}
extern "C" {
pub fn rb_backref_get() -> VALUE;
}
extern "C" {
pub fn rb_range_new(beg: VALUE, end: VALUE, excl: ::std::os::raw::c_int) -> VALUE;
}
extern "C" {
pub fn rb_reg_nth_match(n: ::std::os::raw::c_int, md: VALUE) -> VALUE;
}
extern "C" {
pub fn rb_reg_last_match(md: VALUE) -> VALUE;
}
extern "C" {
pub fn rb_reg_match_pre(md: VALUE) -> VALUE;
}
extern "C" {
pub fn rb_reg_match_post(md: VALUE) -> VALUE;
}
extern "C" {
pub fn rb_reg_match_last(md: VALUE) -> VALUE;
}
extern "C" {
pub fn rb_utf8_str_new(
ptr: *const ::std::os::raw::c_char,
len: ::std::os::raw::c_long,
) -> VALUE;
}
extern "C" {
pub fn rb_str_intern(str_: VALUE) -> VALUE;
}
extern "C" {
pub fn rb_ivar_get(obj: VALUE, name: ID) -> VALUE;
}
extern "C" {
pub fn rb_attr_get(obj: VALUE, name: ID) -> VALUE;
}
extern "C" {
pub fn rb_obj_info_dump(obj: VALUE);
}
extern "C" {
pub fn rb_reg_new_ary(ary: VALUE, options: ::std::os::raw::c_int) -> VALUE;
}
pub const idDot2: ruby_method_ids = 128;
pub const idDot3: ruby_method_ids = 129;
pub const idUPlus: ruby_method_ids = 132;
pub const idUMinus: ruby_method_ids = 133;
pub const idPow: ruby_method_ids = 134;
pub const idCmp: ruby_method_ids = 135;
pub const idPLUS: ruby_method_ids = 43;
pub const idMINUS: ruby_method_ids = 45;
pub const idMULT: ruby_method_ids = 42;
pub const idDIV: ruby_method_ids = 47;
pub const idMOD: ruby_method_ids = 37;
pub const idLTLT: ruby_method_ids = 136;
pub const idGTGT: ruby_method_ids = 137;
pub const idLT: ruby_method_ids = 60;
pub const idLE: ruby_method_ids = 138;
pub const idGT: ruby_method_ids = 62;
pub const idGE: ruby_method_ids = 139;
pub const idEq: ruby_method_ids = 140;
pub const idEqq: ruby_method_ids = 141;
pub const idNeq: ruby_method_ids = 142;
pub const idNot: ruby_method_ids = 33;
pub const idAnd: ruby_method_ids = 38;
pub const idOr: ruby_method_ids = 124;
pub const idBackquote: ruby_method_ids = 96;
pub const idEqTilde: ruby_method_ids = 143;
pub const idNeqTilde: ruby_method_ids = 144;
pub const idAREF: ruby_method_ids = 145;
pub const idASET: ruby_method_ids = 146;
pub const idCOLON2: ruby_method_ids = 147;
pub const idANDOP: ruby_method_ids = 148;
pub const idOROP: ruby_method_ids = 149;
pub const idANDDOT: ruby_method_ids = 150;
pub const tPRESERVED_ID_BEGIN: ruby_method_ids = 150;
pub const idNilP: ruby_method_ids = 151;
pub const idNULL: ruby_method_ids = 152;
pub const idEmptyP: ruby_method_ids = 153;
pub const idEqlP: ruby_method_ids = 154;
pub const idRespond_to: ruby_method_ids = 155;
pub const idRespond_to_missing: ruby_method_ids = 156;
pub const idIFUNC: ruby_method_ids = 157;
pub const idCFUNC: ruby_method_ids = 158;
pub const id_core_set_method_alias: ruby_method_ids = 159;
pub const id_core_set_variable_alias: ruby_method_ids = 160;
pub const id_core_undef_method: ruby_method_ids = 161;
pub const id_core_define_method: ruby_method_ids = 162;
pub const id_core_define_singleton_method: ruby_method_ids = 163;
pub const id_core_set_postexe: ruby_method_ids = 164;
pub const id_core_hash_merge_ptr: ruby_method_ids = 165;
pub const id_core_hash_merge_kwd: ruby_method_ids = 166;
pub const id_core_raise: ruby_method_ids = 167;
pub const id_core_sprintf: ruby_method_ids = 168;
pub const id_debug_created_info: ruby_method_ids = 169;
pub const tPRESERVED_ID_END: ruby_method_ids = 170;
pub const tTOKEN_LOCAL_BEGIN: ruby_method_ids = 169;
pub const tMax: ruby_method_ids = 170;
pub const tMin: ruby_method_ids = 171;
pub const tFreeze: ruby_method_ids = 172;
pub const tInspect: ruby_method_ids = 173;
pub const tIntern: ruby_method_ids = 174;
pub const tObject_id: ruby_method_ids = 175;
pub const tConst_added: ruby_method_ids = 176;
pub const tConst_missing: ruby_method_ids = 177;
pub const tMethodMissing: ruby_method_ids = 178;
pub const tMethod_added: ruby_method_ids = 179;
pub const tSingleton_method_added: ruby_method_ids = 180;
pub const tMethod_removed: ruby_method_ids = 181;
pub const tSingleton_method_removed: ruby_method_ids = 182;
pub const tMethod_undefined: ruby_method_ids = 183;
pub const tSingleton_method_undefined: ruby_method_ids = 184;
pub const tLength: ruby_method_ids = 185;
pub const tSize: ruby_method_ids = 186;
pub const tGets: ruby_method_ids = 187;
pub const tSucc: ruby_method_ids = 188;
pub const tEach: ruby_method_ids = 189;
pub const tProc: ruby_method_ids = 190;
pub const tLambda: ruby_method_ids = 191;
pub const tSend: ruby_method_ids = 192;
pub const t__send__: ruby_method_ids = 193;
pub const t__attached__: ruby_method_ids = 194;
pub const t__recursive_key__: ruby_method_ids = 195;
pub const tInitialize: ruby_method_ids = 196;
pub const tInitialize_copy: ruby_method_ids = 197;
pub const tInitialize_clone: ruby_method_ids = 198;
pub const tInitialize_dup: ruby_method_ids = 199;
pub const tTo_int: ruby_method_ids = 200;
pub const tTo_ary: ruby_method_ids = 201;
pub const tTo_str: ruby_method_ids = 202;
pub const tTo_sym: ruby_method_ids = 203;
pub const tTo_hash: ruby_method_ids = 204;
pub const tTo_proc: ruby_method_ids = 205;
pub const tTo_io: ruby_method_ids = 206;
pub const tTo_a: ruby_method_ids = 207;
pub const tTo_s: ruby_method_ids = 208;
pub const tTo_i: ruby_method_ids = 209;
pub const tTo_f: ruby_method_ids = 210;
pub const tTo_r: ruby_method_ids = 211;
pub const tBt: ruby_method_ids = 212;
pub const tBt_locations: ruby_method_ids = 213;
pub const tCall: ruby_method_ids = 214;
pub const tMesg: ruby_method_ids = 215;
pub const tException: ruby_method_ids = 216;
pub const tLocals: ruby_method_ids = 217;
pub const tNOT: ruby_method_ids = 218;
pub const tAND: ruby_method_ids = 219;
pub const tOR: ruby_method_ids = 220;
pub const tDiv: ruby_method_ids = 221;
pub const tDivmod: ruby_method_ids = 222;
pub const tFdiv: ruby_method_ids = 223;
pub const tQuo: ruby_method_ids = 224;
pub const tName: ruby_method_ids = 225;
pub const tNil: ruby_method_ids = 226;
pub const tUScore: ruby_method_ids = 227;
pub const tNUMPARAM_1: ruby_method_ids = 228;
pub const tNUMPARAM_2: ruby_method_ids = 229;
pub const tNUMPARAM_3: ruby_method_ids = 230;
pub const tNUMPARAM_4: ruby_method_ids = 231;
pub const tNUMPARAM_5: ruby_method_ids = 232;
pub const tNUMPARAM_6: ruby_method_ids = 233;
pub const tNUMPARAM_7: ruby_method_ids = 234;
pub const tNUMPARAM_8: ruby_method_ids = 235;
pub const tNUMPARAM_9: ruby_method_ids = 236;
pub const tTOKEN_LOCAL_END: ruby_method_ids = 237;
pub const tTOKEN_INSTANCE_BEGIN: ruby_method_ids = 236;
pub const tTOKEN_INSTANCE_END: ruby_method_ids = 237;
pub const tTOKEN_GLOBAL_BEGIN: ruby_method_ids = 236;
pub const tLASTLINE: ruby_method_ids = 237;
pub const tBACKREF: ruby_method_ids = 238;
pub const tERROR_INFO: ruby_method_ids = 239;
pub const tTOKEN_GLOBAL_END: ruby_method_ids = 240;
pub const tTOKEN_CONST_BEGIN: ruby_method_ids = 239;
pub const tTOKEN_CONST_END: ruby_method_ids = 240;
pub const tTOKEN_CLASS_BEGIN: ruby_method_ids = 239;
pub const tTOKEN_CLASS_END: ruby_method_ids = 240;
pub const tTOKEN_ATTRSET_BEGIN: ruby_method_ids = 239;
pub const tTOKEN_ATTRSET_END: ruby_method_ids = 240;
pub const tNEXT_ID: ruby_method_ids = 240;
pub const idMax: ruby_method_ids = 2721;
pub const idMin: ruby_method_ids = 2737;
pub const idFreeze: ruby_method_ids = 2753;
pub const idInspect: ruby_method_ids = 2769;
pub const idIntern: ruby_method_ids = 2785;
pub const idObject_id: ruby_method_ids = 2801;
pub const idConst_added: ruby_method_ids = 2817;
pub const idConst_missing: ruby_method_ids = 2833;
pub const idMethodMissing: ruby_method_ids = 2849;
pub const idMethod_added: ruby_method_ids = 2865;
pub const idSingleton_method_added: ruby_method_ids = 2881;
pub const idMethod_removed: ruby_method_ids = 2897;
pub const idSingleton_method_removed: ruby_method_ids = 2913;
pub const idMethod_undefined: ruby_method_ids = 2929;
pub const idSingleton_method_undefined: ruby_method_ids = 2945;
pub const idLength: ruby_method_ids = 2961;
pub const idSize: ruby_method_ids = 2977;
pub const idGets: ruby_method_ids = 2993;
pub const idSucc: ruby_method_ids = 3009;
pub const idEach: ruby_method_ids = 3025;
pub const idProc: ruby_method_ids = 3041;
pub const idLambda: ruby_method_ids = 3057;
pub const idSend: ruby_method_ids = 3073;
pub const id__send__: ruby_method_ids = 3089;
pub const id__attached__: ruby_method_ids = 3105;
pub const id__recursive_key__: ruby_method_ids = 3121;
pub const idInitialize: ruby_method_ids = 3137;
pub const idInitialize_copy: ruby_method_ids = 3153;
pub const idInitialize_clone: ruby_method_ids = 3169;
pub const idInitialize_dup: ruby_method_ids = 3185;
pub const idTo_int: ruby_method_ids = 3201;
pub const idTo_ary: ruby_method_ids = 3217;
pub const idTo_str: ruby_method_ids = 3233;
pub const idTo_sym: ruby_method_ids = 3249;
pub const idTo_hash: ruby_method_ids = 3265;
pub const idTo_proc: ruby_method_ids = 3281;
pub const idTo_io: ruby_method_ids = 3297;
pub const idTo_a: ruby_method_ids = 3313;
pub const idTo_s: ruby_method_ids = 3329;
pub const idTo_i: ruby_method_ids = 3345;
pub const idTo_f: ruby_method_ids = 3361;
pub const idTo_r: ruby_method_ids = 3377;
pub const idBt: ruby_method_ids = 3393;
pub const idBt_locations: ruby_method_ids = 3409;
pub const idCall: ruby_method_ids = 3425;
pub const idMesg: ruby_method_ids = 3441;
pub const idException: ruby_method_ids = 3457;
pub const idLocals: ruby_method_ids = 3473;
pub const idNOT: ruby_method_ids = 3489;
pub const idAND: ruby_method_ids = 3505;
pub const idOR: ruby_method_ids = 3521;
pub const idDiv: ruby_method_ids = 3537;
pub const idDivmod: ruby_method_ids = 3553;
pub const idFdiv: ruby_method_ids = 3569;
pub const idQuo: ruby_method_ids = 3585;
pub const idName: ruby_method_ids = 3601;
pub const idNil: ruby_method_ids = 3617;
pub const idUScore: ruby_method_ids = 3633;
pub const idNUMPARAM_1: ruby_method_ids = 3649;
pub const idNUMPARAM_2: ruby_method_ids = 3665;
pub const idNUMPARAM_3: ruby_method_ids = 3681;
pub const idNUMPARAM_4: ruby_method_ids = 3697;
pub const idNUMPARAM_5: ruby_method_ids = 3713;
pub const idNUMPARAM_6: ruby_method_ids = 3729;
pub const idNUMPARAM_7: ruby_method_ids = 3745;
pub const idNUMPARAM_8: ruby_method_ids = 3761;
pub const idNUMPARAM_9: ruby_method_ids = 3777;
pub const idLASTLINE: ruby_method_ids = 3799;
pub const idBACKREF: ruby_method_ids = 3815;
pub const idERROR_INFO: ruby_method_ids = 3831;
pub const tLAST_OP_ID: ruby_method_ids = 169;
pub const idLAST_OP_ID: ruby_method_ids = 10;
pub type ruby_method_ids = u32;
extern "C" {
pub fn rb_ary_tmp_new_from_values(
arg1: VALUE,
arg2: ::std::os::raw::c_long,
arg3: *const VALUE,
) -> VALUE;
}
extern "C" {
pub fn rb_ec_ary_new_from_values(
ec: *mut rb_execution_context_struct,
n: ::std::os::raw::c_long,
elts: *const VALUE,
) -> VALUE;
}
pub type rb_serial_t = ::std::os::raw::c_ulonglong;
extern "C" {
pub fn rb_class_allocate_instance(klass: VALUE) -> VALUE;
}
extern "C" {
pub fn rb_obj_info(obj: VALUE) -> *const ::std::os::raw::c_char;
}
pub const imemo_env: imemo_type = 0;
pub const imemo_cref: imemo_type = 1;
pub const imemo_svar: imemo_type = 2;
pub const imemo_throw_data: imemo_type = 3;
pub const imemo_ifunc: imemo_type = 4;
pub const imemo_memo: imemo_type = 5;
pub const imemo_ment: imemo_type = 6;
pub const imemo_iseq: imemo_type = 7;
pub const imemo_tmpbuf: imemo_type = 8;
pub const imemo_ast: imemo_type = 9;
pub const imemo_parser_strterm: imemo_type = 10;
pub const imemo_callinfo: imemo_type = 11;
pub const imemo_callcache: imemo_type = 12;
pub const imemo_constcache: imemo_type = 13;
pub type imemo_type = u32;
pub const METHOD_VISI_UNDEF: rb_method_visibility_t = 0;
pub const METHOD_VISI_PUBLIC: rb_method_visibility_t = 1;
pub const METHOD_VISI_PRIVATE: rb_method_visibility_t = 2;
pub const METHOD_VISI_PROTECTED: rb_method_visibility_t = 3;
pub const METHOD_VISI_MASK: rb_method_visibility_t = 3;
pub type rb_method_visibility_t = u32;
#[repr(C)]
pub struct rb_method_entry_struct {
pub flags: VALUE,
pub defined_class: VALUE,
pub def: *mut rb_method_definition_struct,
pub called_id: ID,
pub owner: VALUE,
}
pub type rb_method_entry_t = rb_method_entry_struct;
#[repr(C)]
pub struct rb_callable_method_entry_struct {
pub flags: VALUE,
pub defined_class: VALUE,
pub def: *mut rb_method_definition_struct,
pub called_id: ID,
pub owner: VALUE,
}
pub type rb_callable_method_entry_t = rb_callable_method_entry_struct;
pub const VM_METHOD_TYPE_ISEQ: rb_method_type_t = 0;
pub const VM_METHOD_TYPE_CFUNC: rb_method_type_t = 1;
pub const VM_METHOD_TYPE_ATTRSET: rb_method_type_t = 2;
pub const VM_METHOD_TYPE_IVAR: rb_method_type_t = 3;
pub const VM_METHOD_TYPE_BMETHOD: rb_method_type_t = 4;
pub const VM_METHOD_TYPE_ZSUPER: rb_method_type_t = 5;
pub const VM_METHOD_TYPE_ALIAS: rb_method_type_t = 6;
pub const VM_METHOD_TYPE_UNDEF: rb_method_type_t = 7;
pub const VM_METHOD_TYPE_NOTIMPLEMENTED: rb_method_type_t = 8;
pub const VM_METHOD_TYPE_OPTIMIZED: rb_method_type_t = 9;
pub const VM_METHOD_TYPE_MISSING: rb_method_type_t = 10;
pub const VM_METHOD_TYPE_REFINED: rb_method_type_t = 11;
pub type rb_method_type_t = u32;
pub const OPTIMIZED_METHOD_TYPE_SEND: method_optimized_type = 0;
pub const OPTIMIZED_METHOD_TYPE_CALL: method_optimized_type = 1;
pub const OPTIMIZED_METHOD_TYPE_BLOCK_CALL: method_optimized_type = 2;
pub const OPTIMIZED_METHOD_TYPE_STRUCT_AREF: method_optimized_type = 3;
pub const OPTIMIZED_METHOD_TYPE_STRUCT_ASET: method_optimized_type = 4;
pub const OPTIMIZED_METHOD_TYPE__MAX: method_optimized_type = 5;
pub type method_optimized_type = u32;
extern "C" {
pub fn rb_method_entry_at(obj: VALUE, id: ID) -> *const rb_method_entry_t;
}
extern "C" {
pub fn rb_callable_method_entry(klass: VALUE, id: ID) -> *const rb_callable_method_entry_t;
}
pub type rb_num_t = ::std::os::raw::c_ulong;
#[repr(C)]
pub struct iseq_inline_constant_cache_entry {
pub flags: VALUE,
pub value: VALUE,
pub _unused1: VALUE,
pub _unused2: VALUE,
pub ic_cref: *const rb_cref_t,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct iseq_inline_constant_cache {
pub entry: *mut iseq_inline_constant_cache_entry,
pub get_insn_idx: ::std::os::raw::c_uint,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct iseq_inline_iv_cache_entry {
pub entry: *mut rb_iv_index_tbl_entry,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct iseq_inline_cvar_cache_entry {
pub entry: *mut rb_cvar_class_tbl_entry,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct rb_iseq_constant_body__bindgen_ty_1_rb_iseq_param_keyword {
pub num: ::std::os::raw::c_int,
pub required_num: ::std::os::raw::c_int,
pub bits_start: ::std::os::raw::c_int,
pub rest_start: ::std::os::raw::c_int,
pub table: *const ID,
pub default_values: *mut VALUE,
}
pub const BOP_PLUS: ruby_basic_operators = 0;
pub const BOP_MINUS: ruby_basic_operators = 1;
pub const BOP_MULT: ruby_basic_operators = 2;
pub const BOP_DIV: ruby_basic_operators = 3;
pub const BOP_MOD: ruby_basic_operators = 4;
pub const BOP_EQ: ruby_basic_operators = 5;
pub const BOP_EQQ: ruby_basic_operators = 6;
pub const BOP_LT: ruby_basic_operators = 7;
pub const BOP_LE: ruby_basic_operators = 8;
pub const BOP_LTLT: ruby_basic_operators = 9;
pub const BOP_AREF: ruby_basic_operators = 10;
pub const BOP_ASET: ruby_basic_operators = 11;
pub const BOP_LENGTH: ruby_basic_operators = 12;
pub const BOP_SIZE: ruby_basic_operators = 13;
pub const BOP_EMPTY_P: ruby_basic_operators = 14;
pub const BOP_NIL_P: ruby_basic_operators = 15;
pub const BOP_SUCC: ruby_basic_operators = 16;
pub const BOP_GT: ruby_basic_operators = 17;
pub const BOP_GE: ruby_basic_operators = 18;
pub const BOP_NOT: ruby_basic_operators = 19;
pub const BOP_NEQ: ruby_basic_operators = 20;
pub const BOP_MATCH: ruby_basic_operators = 21;
pub const BOP_FREEZE: ruby_basic_operators = 22;
pub const BOP_UMINUS: ruby_basic_operators = 23;
pub const BOP_MAX: ruby_basic_operators = 24;
pub const BOP_MIN: ruby_basic_operators = 25;
pub const BOP_CALL: ruby_basic_operators = 26;
pub const BOP_AND: ruby_basic_operators = 27;
pub const BOP_OR: ruby_basic_operators = 28;
pub const BOP_LAST_: ruby_basic_operators = 29;
pub type ruby_basic_operators = u32;
pub type rb_control_frame_t = rb_control_frame_struct;
extern "C" {
pub static mut rb_mRubyVMFrozenCore: VALUE;
}
extern "C" {
pub static mut rb_block_param_proxy: VALUE;
}
pub type IC = *mut iseq_inline_constant_cache;
pub type IVC = *mut iseq_inline_iv_cache_entry;
pub type ICVARC = *mut iseq_inline_cvar_cache_entry;
pub const VM_FRAME_MAGIC_METHOD: vm_frame_env_flags = 286326785;
pub const VM_FRAME_MAGIC_BLOCK: vm_frame_env_flags = 572653569;
pub const VM_FRAME_MAGIC_CLASS: vm_frame_env_flags = 858980353;
pub const VM_FRAME_MAGIC_TOP: vm_frame_env_flags = 1145307137;
pub const VM_FRAME_MAGIC_CFUNC: vm_frame_env_flags = 1431633921;
pub const VM_FRAME_MAGIC_IFUNC: vm_frame_env_flags = 1717960705;
pub const VM_FRAME_MAGIC_EVAL: vm_frame_env_flags = 2004287489;
pub const VM_FRAME_MAGIC_RESCUE: vm_frame_env_flags = 2022178817;
pub const VM_FRAME_MAGIC_DUMMY: vm_frame_env_flags = 2040070145;
pub const VM_FRAME_MAGIC_MASK: vm_frame_env_flags = 2147418113;
pub const VM_FRAME_FLAG_FINISH: vm_frame_env_flags = 32;
pub const VM_FRAME_FLAG_BMETHOD: vm_frame_env_flags = 64;
pub const VM_FRAME_FLAG_CFRAME: vm_frame_env_flags = 128;
pub const VM_FRAME_FLAG_LAMBDA: vm_frame_env_flags = 256;
pub const VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM: vm_frame_env_flags = 512;
pub const VM_FRAME_FLAG_CFRAME_KW: vm_frame_env_flags = 1024;
pub const VM_FRAME_FLAG_PASSED: vm_frame_env_flags = 2048;
pub const VM_ENV_FLAG_LOCAL: vm_frame_env_flags = 2;
pub const VM_ENV_FLAG_ESCAPED: vm_frame_env_flags = 4;
pub const VM_ENV_FLAG_WB_REQUIRED: vm_frame_env_flags = 8;
pub const VM_ENV_FLAG_ISOLATED: vm_frame_env_flags = 16;
pub type vm_frame_env_flags = u32;
extern "C" {
pub fn rb_vm_frame_method_entry(
cfp: *const rb_control_frame_t,
) -> *const rb_callable_method_entry_t;
}
pub const VM_CALL_ARGS_SPLAT_bit: vm_call_flag_bits = 0;
pub const VM_CALL_ARGS_BLOCKARG_bit: vm_call_flag_bits = 1;
pub const VM_CALL_FCALL_bit: vm_call_flag_bits = 2;
pub const VM_CALL_VCALL_bit: vm_call_flag_bits = 3;
pub const VM_CALL_ARGS_SIMPLE_bit: vm_call_flag_bits = 4;
pub const VM_CALL_BLOCKISEQ_bit: vm_call_flag_bits = 5;
pub const VM_CALL_KWARG_bit: vm_call_flag_bits = 6;
pub const VM_CALL_KW_SPLAT_bit: vm_call_flag_bits = 7;
pub const VM_CALL_TAILCALL_bit: vm_call_flag_bits = 8;
pub const VM_CALL_SUPER_bit: vm_call_flag_bits = 9;
pub const VM_CALL_ZSUPER_bit: vm_call_flag_bits = 10;
pub const VM_CALL_OPT_SEND_bit: vm_call_flag_bits = 11;
pub const VM_CALL_KW_SPLAT_MUT_bit: vm_call_flag_bits = 12;
pub const VM_CALL__END: vm_call_flag_bits = 13;
pub type vm_call_flag_bits = u32;
#[repr(C)]
pub struct rb_callinfo {
pub flags: VALUE,
pub kwarg: *const rb_callinfo_kwarg,
pub mid: VALUE,
pub flag: VALUE,
pub argc: VALUE,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct rb_call_data {
pub ci: *const rb_callinfo,
pub cc: *const rb_callcache,
}
extern "C" {
pub fn rb_obj_as_string_result(str_: VALUE, obj: VALUE) -> VALUE;
}
extern "C" {
pub fn rb_str_concat_literals(num: size_t, strary: *const VALUE) -> VALUE;
}
extern "C" {
pub fn rb_ec_str_resurrect(ec: *mut rb_execution_context_struct, str_: VALUE) -> VALUE;
}
extern "C" {
pub fn rb_hash_new_with_size(size: st_index_t) -> VALUE;
}
extern "C" {
pub fn rb_hash_resurrect(hash: VALUE) -> VALUE;
}
extern "C" {
pub fn rb_obj_ensure_iv_index_mapping(obj: VALUE, id: ID) -> u32;
}
extern "C" {
pub fn rb_gvar_get(arg1: ID) -> VALUE;
}
extern "C" {
pub fn rb_gvar_set(arg1: ID, arg2: VALUE) -> VALUE;
}
extern "C" {
pub fn rb_vm_insn_decode(encoded: VALUE) -> ::std::os::raw::c_int;
}
#[repr(C)]
pub struct rb_iv_index_tbl_entry {
pub index: u32,
pub class_serial: rb_serial_t,
pub class_value: VALUE,
}
#[repr(C)]
pub struct rb_cvar_class_tbl_entry {
pub index: u32,
pub global_cvar_state: rb_serial_t,
pub class_value: VALUE,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct rb_builtin_function {
pub func_ptr: *const ::std::os::raw::c_void,
pub argc: ::std::os::raw::c_int,
pub index: ::std::os::raw::c_int,
pub name: *const ::std::os::raw::c_char,
pub compiler: ::std::option::Option<
unsafe extern "C" fn(
arg1: *mut FILE,
arg2: ::std::os::raw::c_long,
arg3: ::std::os::raw::c_uint,
arg4: bool,
),
>,
}
extern "C" {
pub fn rb_vm_insn_addr2opcode(addr: *const ::std::os::raw::c_void) -> ::std::os::raw::c_int;
}
pub type rb_iseq_each_i = ::std::option::Option<
unsafe extern "C" fn(
code: *mut VALUE,
insn: VALUE,
index: size_t,
data: *mut ::std::os::raw::c_void,
) -> bool,
>;
extern "C" {
pub fn rb_iseq_each(
iseq: *const rb_iseq_t,
start_index: size_t,
iterator: rb_iseq_each_i,
data: *mut ::std::os::raw::c_void,
);
}
extern "C" {
pub fn rb_iseqw_to_iseq(iseqw: VALUE) -> *const rb_iseq_t;
}
extern "C" {
pub fn rb_vm_barrier();
}
extern "C" {
pub fn rb_yjit_mark_writable(mem_block: *mut ::std::os::raw::c_void, mem_size: u32);
}
extern "C" {
pub fn rb_yjit_mark_executable(mem_block: *mut ::std::os::raw::c_void, mem_size: u32);
}
extern "C" {
pub fn rb_yjit_get_page_size() -> u32;
}
extern "C" {
pub fn rb_c_method_tracing_currently_enabled(ec: *mut rb_execution_context_t) -> bool;
}
extern "C" {
pub fn rb_full_cfunc_return(ec: *mut rb_execution_context_t, return_value: VALUE);
}
extern "C" {
pub fn rb_iseq_get_yjit_payload(iseq: *const rb_iseq_t) -> *mut ::std::os::raw::c_void;
}
extern "C" {
pub fn rb_iseq_set_yjit_payload(iseq: *const rb_iseq_t, payload: *mut ::std::os::raw::c_void);
}
extern "C" {
pub fn rb_iseq_reset_jit_func(iseq: *const rb_iseq_t);
}
extern "C" {
pub fn rb_iseq_pc_at_idx(iseq: *const rb_iseq_t, insn_idx: u32) -> *mut VALUE;
}
extern "C" {
pub fn rb_iseq_opcode_at_pc(iseq: *const rb_iseq_t, pc: *const VALUE) -> ::std::os::raw::c_int;
}
pub type rb_seq_param_keyword_struct = rb_iseq_constant_body__bindgen_ty_1_rb_iseq_param_keyword;
extern "C" {
pub fn rb_leaf_invokebuiltin_iseq_p(iseq: *const rb_iseq_t) -> bool;
}
extern "C" {
pub fn rb_leaf_builtin_function(iseq: *const rb_iseq_t) -> *const rb_builtin_function;
}
extern "C" {
pub fn rb_set_cfp_pc(cfp: *mut rb_control_frame_struct, pc: *const VALUE);
}
extern "C" {
pub fn rb_set_cfp_sp(cfp: *mut rb_control_frame_struct, sp: *mut VALUE);
}
extern "C" {
pub fn rb_cfp_get_iseq(cfp: *mut rb_control_frame_struct) -> *mut rb_iseq_t;
}
extern "C" {
pub fn rb_yjit_dump_iseq_loc(iseq: *const rb_iseq_t, insn_idx: u32);
}
extern "C" {
pub fn rb_yjit_multi_ractor_p() -> bool;
}
extern "C" {
pub fn rb_assert_iseq_handle(handle: VALUE);
}
extern "C" {
pub fn rb_IMEMO_TYPE_P(imemo: VALUE, imemo_type: imemo_type) -> ::std::os::raw::c_int;
}
extern "C" {
pub fn rb_assert_cme_handle(handle: VALUE);
}
pub type iseq_callback = ::std::option::Option<unsafe extern "C" fn(arg1: *const rb_iseq_t)>;
extern "C" {
pub fn rb_yjit_for_each_iseq(callback: iseq_callback);
}
extern "C" {
pub fn rb_yjit_obj_written(
old: VALUE,
young: VALUE,
file: *const ::std::os::raw::c_char,
line: ::std::os::raw::c_int,
);
}
extern "C" {
pub fn rb_yjit_vm_lock_then_barrier(
recursive_lock_level: *mut ::std::os::raw::c_uint,
file: *const ::std::os::raw::c_char,
line: ::std::os::raw::c_int,
);
}
extern "C" {
pub fn rb_yjit_vm_unlock(
recursive_lock_level: *mut ::std::os::raw::c_uint,
file: *const ::std::os::raw::c_char,
line: ::std::os::raw::c_int,
);
}

218
yjit/src/disasm.rs Normal file
Просмотреть файл

@ -0,0 +1,218 @@
use crate::asm::*;
use crate::codegen::*;
use crate::core::*;
use crate::cruby::*;
use crate::yjit::yjit_enabled_p;
use std::fmt::Write;
/// Primitive called in yjit.rb
/// Produce a string representing the disassembly for an ISEQ
#[no_mangle]
pub extern "C" fn rb_yjit_disasm_iseq(_ec: EcPtr, _ruby_self: VALUE, iseqw: VALUE) -> VALUE {
#[cfg(not(feature = "disasm"))]
{
let _ = iseqw;
return Qnil;
}
#[cfg(feature = "disasm")]
{
// TODO:
//if unsafe { CLASS_OF(iseqw) != rb_cISeq } {
// return Qnil;
//}
if !yjit_enabled_p() {
return Qnil;
}
// Get the iseq pointer from the wrapper
let iseq = unsafe { rb_iseqw_to_iseq(iseqw) };
let out_string = disasm_iseq(iseq);
return rust_str_to_ruby(&out_string);
}
}
#[cfg(feature = "disasm")]
fn disasm_iseq(iseq: IseqPtr) -> String {
let mut out = String::from("");
// Get a list of block versions generated for this iseq
let mut block_list = get_iseq_block_list(iseq);
// Get a list of codeblocks relevant to this iseq
let global_cb = CodegenGlobals::get_inline_cb();
// Sort the blocks by increasing start addresses
block_list.sort_by(|a, b| {
use std::cmp::Ordering;
// Get the start addresses for each block
let addr_a = a.borrow().get_start_addr().unwrap().raw_ptr();
let addr_b = b.borrow().get_start_addr().unwrap().raw_ptr();
if addr_a < addr_b {
Ordering::Less
} else if addr_a == addr_b {
Ordering::Equal
} else {
Ordering::Greater
}
});
// Compute total code size in bytes for all blocks in the function
let mut total_code_size = 0;
for blockref in &block_list {
total_code_size += blockref.borrow().code_size();
}
// Initialize capstone
extern crate capstone;
use capstone::prelude::*;
let cs = Capstone::new()
.x86()
.mode(arch::x86::ArchMode::Mode64)
.syntax(arch::x86::ArchSyntax::Intel)
.build()
.unwrap();
out.push_str(&format!("NUM BLOCK VERSIONS: {}\n", block_list.len()));
out.push_str(&format!(
"TOTAL INLINE CODE SIZE: {} bytes\n",
total_code_size
));
// For each block, sorted by increasing start address
for block_idx in 0..block_list.len() {
let block = block_list[block_idx].borrow();
let blockid = block.get_blockid();
let end_idx = block.get_end_idx();
let start_addr = block.get_start_addr().unwrap().raw_ptr();
let end_addr = block.get_end_addr().unwrap().raw_ptr();
let code_size = block.code_size();
// Write some info about the current block
let block_ident = format!(
"BLOCK {}/{}, ISEQ RANGE [{},{}), {} bytes ",
block_idx + 1,
block_list.len(),
blockid.idx,
end_idx,
code_size
);
out.push_str(&format!("== {:=<60}\n", block_ident));
// Disassemble the instructions
let code_slice = unsafe { std::slice::from_raw_parts(start_addr, code_size) };
let insns = cs.disasm_all(code_slice, start_addr as u64).unwrap();
// For each instruction in this block
for insn in insns.as_ref() {
// Comments for this block
if let Some(comment_list) = global_cb.comments_at(insn.address() as usize) {
for comment in comment_list {
out.push_str(&format!(" \x1b[1m# {}\x1b[0m\n", comment));
}
}
out.push_str(&format!(" {}\n", insn));
}
// If this is not the last block
if block_idx < block_list.len() - 1 {
// Compute the size of the gap between this block and the next
let next_block = block_list[block_idx + 1].borrow();
let next_start_addr = next_block.get_start_addr().unwrap().raw_ptr();
let gap_size = (next_start_addr as usize) - (end_addr as usize);
// Log the size of the gap between the blocks if nonzero
if gap_size > 0 {
out.push_str(&format!("... {} byte gap ...\n", gap_size));
}
}
}
return out;
}
/// Primitive called in yjit.rb
/// Produce a list of instructions compiled for an isew
#[no_mangle]
pub extern "C" fn rb_yjit_insns_compiled(_ec: EcPtr, _ruby_self: VALUE, iseqw: VALUE) -> VALUE {
{
// TODO:
//if unsafe { CLASS_OF(iseqw) != rb_cISeq } {
// return Qnil;
//}
if !yjit_enabled_p() {
return Qnil;
}
// Get the iseq pointer from the wrapper
let iseq = unsafe { rb_iseqw_to_iseq(iseqw) };
// Get the list of instructions compiled
let insn_vec = insns_compiled(iseq);
unsafe {
let insn_ary = rb_ary_new_capa((insn_vec.len() * 2) as i64);
// For each instruction compiled
for idx in 0..insn_vec.len() {
let op_name = &insn_vec[idx].0;
let insn_idx = insn_vec[idx].1;
let op_sym = rust_str_to_sym(&op_name);
// Store the instruction index and opcode symbol
rb_ary_store(
insn_ary,
(2 * idx + 0) as i64,
VALUE::fixnum_from_usize(insn_idx as usize),
);
rb_ary_store(insn_ary, (2 * idx + 1) as i64, op_sym);
}
insn_ary
}
}
}
fn insns_compiled(iseq: IseqPtr) -> Vec<(String, u32)> {
let mut insn_vec = Vec::new();
// Get a list of block versions generated for this iseq
let block_list = get_iseq_block_list(iseq);
// For each block associated with this iseq
for blockref in &block_list {
let block = blockref.borrow();
let start_idx = block.get_blockid().idx;
let end_idx = block.get_end_idx();
assert!(end_idx <= unsafe { get_iseq_encoded_size(iseq) });
// For each YARV instruction in the block
let mut insn_idx = start_idx;
while insn_idx < end_idx {
// Get the current pc and opcode
let pc = unsafe { rb_iseq_pc_at_idx(iseq, insn_idx) };
// try_into() call below is unfortunate. Maybe pick i32 instead of usize for opcodes.
let opcode: usize = unsafe { rb_iseq_opcode_at_pc(iseq, pc) }
.try_into()
.unwrap();
// Get the mnemonic for this opcode
let op_name = insn_name(opcode);
// Add the instruction to the list
insn_vec.push((op_name, insn_idx));
// Move to the next instruction
insn_idx += insn_len(opcode);
}
}
return insn_vec;
}

585
yjit/src/invariants.rs Normal file
Просмотреть файл

@ -0,0 +1,585 @@
//! Code to track assumptions made during code generation and invalidate
//! generated code if and when these assumptions are invalidated.
use crate::asm::OutlinedCb;
use crate::codegen::*;
use crate::core::*;
use crate::cruby::*;
use crate::options::*;
use crate::stats::*;
use crate::utils::IntoUsize;
use crate::yjit::yjit_enabled_p;
use std::collections::{HashMap, HashSet};
use std::mem;
use std::os::raw::c_void;
// Invariants to track:
// assume_bop_not_redefined(jit, INTEGER_REDEFINED_OP_FLAG, BOP_PLUS)
// assume_method_lookup_stable(comptime_recv_klass, cme, jit);
// assume_single_ractor_mode(jit)
// assume_stable_global_constant_state(jit);
/// Used to track all of the various block references that contain assumptions
/// about the state of the virtual machine.
pub struct Invariants {
/// Tracks block assumptions about callable method entry validity.
cme_validity: HashMap<*const rb_callable_method_entry_t, HashSet<BlockRef>>,
/// Tracks block assumptions about method lookup. Maps a class to a table of
/// method ID points to a set of blocks. While a block `b` is in the table,
/// b->callee_cme == rb_callable_method_entry(klass, mid).
method_lookup: HashMap<VALUE, HashMap<ID, HashSet<BlockRef>>>,
/// A map from a class and its associated basic operator to a set of blocks
/// that are assuming that that operator is not redefined. This is used for
/// quick access to all of the blocks that are making this assumption when
/// the operator is redefined.
basic_operator_blocks: HashMap<(RedefinitionFlag, ruby_basic_operators), HashSet<BlockRef>>,
/// A map from a block to a set of classes and their associated basic
/// operators that the block is assuming are not redefined. This is used for
/// quick access to all of the assumptions that a block is making when it
/// needs to be invalidated.
block_basic_operators: HashMap<BlockRef, HashSet<(RedefinitionFlag, ruby_basic_operators)>>,
/// Tracks the set of blocks that are assuming the interpreter is running
/// with only one ractor. This is important for things like accessing
/// constants which can have different semantics when multiple ractors are
/// running.
single_ractor: HashSet<BlockRef>,
/// A map from an ID to the set of blocks that are assuming a constant with
/// that ID as part of its name has not been redefined. For example, if
/// a constant `A::B` is redefined, then all blocks that are assuming that
/// `A` and `B` have not be redefined must be invalidated.
constant_state_blocks: HashMap<ID, HashSet<BlockRef>>,
/// A map from a block to a set of IDs that it is assuming have not been
/// redefined.
block_constant_states: HashMap<BlockRef, HashSet<ID>>,
}
/// Private singleton instance of the invariants global struct.
static mut INVARIANTS: Option<Invariants> = None;
impl Invariants {
pub fn init() {
// Wrapping this in unsafe to assign directly to a global.
unsafe {
INVARIANTS = Some(Invariants {
cme_validity: HashMap::new(),
method_lookup: HashMap::new(),
basic_operator_blocks: HashMap::new(),
block_basic_operators: HashMap::new(),
single_ractor: HashSet::new(),
constant_state_blocks: HashMap::new(),
block_constant_states: HashMap::new(),
});
}
}
/// Get a mutable reference to the codegen globals instance
pub fn get_instance() -> &'static mut Invariants {
unsafe { INVARIANTS.as_mut().unwrap() }
}
}
/// A public function that can be called from within the code generation
/// functions to ensure that the block being generated is invalidated when the
/// basic operator is redefined.
pub fn assume_bop_not_redefined(
jit: &mut JITState,
ocb: &mut OutlinedCb,
klass: RedefinitionFlag,
bop: ruby_basic_operators,
) -> bool {
if unsafe { BASIC_OP_UNREDEFINED_P(bop, klass) } {
jit_ensure_block_entry_exit(jit, ocb);
let invariants = Invariants::get_instance();
invariants
.basic_operator_blocks
.entry((klass, bop))
.or_insert(HashSet::new())
.insert(jit.get_block());
invariants
.block_basic_operators
.entry(jit.get_block())
.or_insert(HashSet::new())
.insert((klass, bop));
return true;
} else {
return false;
}
}
// Remember that a block assumes that
// `rb_callable_method_entry(receiver_klass, cme->called_id) == cme` and that
// `cme` is valid.
// When either of these assumptions becomes invalid, rb_yjit_method_lookup_change() or
// rb_yjit_cme_invalidate() invalidates the block.
//
// @raise NoMemoryError
pub fn assume_method_lookup_stable(
jit: &mut JITState,
ocb: &mut OutlinedCb,
receiver_klass: VALUE,
callee_cme: *const rb_callable_method_entry_t,
) {
// RUBY_ASSERT(rb_callable_method_entry(receiver_klass, cme->called_id) == cme);
// RUBY_ASSERT_ALWAYS(RB_TYPE_P(receiver_klass, T_CLASS) || RB_TYPE_P(receiver_klass, T_ICLASS));
// RUBY_ASSERT_ALWAYS(!rb_objspace_garbage_object_p(receiver_klass));
jit_ensure_block_entry_exit(jit, ocb);
let block = jit.get_block();
block
.borrow_mut()
.add_cme_dependency(receiver_klass, callee_cme);
Invariants::get_instance()
.cme_validity
.entry(callee_cme)
.or_insert(HashSet::new())
.insert(block.clone());
let mid = unsafe { (*callee_cme).called_id };
Invariants::get_instance()
.method_lookup
.entry(receiver_klass)
.or_insert(HashMap::new())
.entry(mid)
.or_insert(HashSet::new())
.insert(block.clone());
}
/// Tracks that a block is assuming it is operating in single-ractor mode.
#[must_use]
pub fn assume_single_ractor_mode(jit: &mut JITState, ocb: &mut OutlinedCb) -> bool {
if unsafe { rb_yjit_multi_ractor_p() } {
false
} else {
jit_ensure_block_entry_exit(jit, ocb);
Invariants::get_instance()
.single_ractor
.insert(jit.get_block());
true
}
}
/// Walk through the ISEQ to go from the current opt_getinlinecache to the
/// subsequent opt_setinlinecache and find all of the name components that are
/// associated with this constant (which correspond to the getconstant
/// arguments).
pub fn assume_stable_constant_names(jit: &mut JITState, ocb: &mut OutlinedCb) {
/// Tracks that a block is assuming that the name component of a constant
/// has not changed since the last call to this function.
unsafe extern "C" fn assume_stable_constant_name(
code: *mut VALUE,
insn: VALUE,
index: u64,
data: *mut c_void,
) -> bool {
if insn.as_usize() == OP_OPT_SETINLINECACHE {
return false;
}
if insn.as_usize() == OP_GETCONSTANT {
let jit = &mut *(data as *mut JITState);
// The first operand to GETCONSTANT is always the ID associated with
// the constant lookup. We are grabbing this out in order to
// associate this block with the stability of this constant name.
let id = code.add(index.as_usize() + 1).read().as_u64() as ID;
let invariants = Invariants::get_instance();
invariants
.constant_state_blocks
.entry(id)
.or_insert(HashSet::new())
.insert(jit.get_block());
invariants
.block_constant_states
.entry(jit.get_block())
.or_insert(HashSet::new())
.insert(id);
}
true
}
jit_ensure_block_entry_exit(jit, ocb);
unsafe {
let iseq = jit.get_iseq();
let encoded = get_iseq_body_iseq_encoded(iseq);
let start_index = jit.get_pc().offset_from(encoded);
rb_iseq_each(
iseq,
start_index.try_into().unwrap(),
Some(assume_stable_constant_name),
jit as *mut _ as *mut c_void,
);
};
}
/// Called when a basic operator is redefined. Note that all the blocks assuming
/// the stability of different operators are invalidated together and we don't
/// do fine-grained tracking.
#[no_mangle]
pub extern "C" fn rb_yjit_bop_redefined(klass: RedefinitionFlag, bop: ruby_basic_operators) {
// If YJIT isn't enabled, do nothing
if !yjit_enabled_p() {
return;
}
with_vm_lock(src_loc!(), || {
// Loop through the blocks that are associated with this class and basic
// operator and invalidate them.
Invariants::get_instance()
.basic_operator_blocks
.remove(&(klass, bop))
.map(|blocks| {
for block in blocks.iter() {
invalidate_block_version(block);
incr_counter!(invalidate_bop_redefined);
}
});
});
}
/// Callback for when a cme becomes invalid. Invalidate all blocks that depend
/// on the given cme being valid.
#[no_mangle]
pub extern "C" fn rb_yjit_cme_invalidate(callee_cme: *const rb_callable_method_entry_t) {
// If YJIT isn't enabled, do nothing
if !yjit_enabled_p() {
return;
}
with_vm_lock(src_loc!(), || {
if let Some(blocks) = Invariants::get_instance().cme_validity.remove(&callee_cme) {
for block in blocks.iter() {
invalidate_block_version(block);
incr_counter!(invalidate_method_lookup);
}
}
});
}
/// Callback for when rb_callable_method_entry(klass, mid) is going to change.
/// Invalidate blocks that assume stable method lookup of `mid` in `klass` when this happens.
/// This needs to be wrapped on the C side with RB_VM_LOCK_ENTER().
#[no_mangle]
pub extern "C" fn rb_yjit_method_lookup_change(klass: VALUE, mid: ID) {
// If YJIT isn't enabled, do nothing
if !yjit_enabled_p() {
return;
}
with_vm_lock(src_loc!(), || {
Invariants::get_instance()
.method_lookup
.entry(klass)
.and_modify(|deps| {
if let Some(deps) = deps.remove(&mid) {
for block in &deps {
invalidate_block_version(block);
incr_counter!(invalidate_method_lookup);
}
}
});
});
}
/// Callback for then Ruby is about to spawn a ractor. In that case we need to
/// invalidate every block that is assuming single ractor mode.
#[no_mangle]
pub extern "C" fn rb_yjit_before_ractor_spawn() {
// If YJIT isn't enabled, do nothing
if !yjit_enabled_p() {
return;
}
with_vm_lock(src_loc!(), || {
// Clear the set of blocks inside Invariants
let blocks = mem::take(&mut Invariants::get_instance().single_ractor);
// Invalidate the blocks
for block in &blocks {
invalidate_block_version(block);
incr_counter!(invalidate_ractor_spawn);
}
});
}
/// Callback for when the global constant state changes.
#[no_mangle]
pub extern "C" fn rb_yjit_constant_state_changed(id: ID) {
// If YJIT isn't enabled, do nothing
if !yjit_enabled_p() {
return;
}
with_vm_lock(src_loc!(), || {
if get_option!(global_constant_state) {
// If the global-constant-state option is set, then we're going to
// invalidate every block that depends on any constant.
Invariants::get_instance()
.constant_state_blocks
.keys()
.for_each(|id| {
if let Some(blocks) =
Invariants::get_instance().constant_state_blocks.remove(&id)
{
for block in &blocks {
invalidate_block_version(block);
incr_counter!(invalidate_constant_state_bump);
}
}
});
} else {
// If the global-constant-state option is not set, then we're only going
// to invalidate the blocks that are associated with the given ID.
if let Some(blocks) = Invariants::get_instance().constant_state_blocks.remove(&id) {
for block in &blocks {
invalidate_block_version(block);
incr_counter!(invalidate_constant_state_bump);
}
}
}
});
}
/// Callback for marking GC objects inside [Invariants].
/// See `struct yjijt_root_struct` in C.
#[no_mangle]
pub extern "C" fn rb_yjit_root_mark() {
// Comment from C YJIT:
//
// Why not let the GC move the cme keys in this table?
// Because this is basically a compare_by_identity Hash.
// If a key moves, we would need to reinsert it into the table so it is rehashed.
// That is tricky to do, espcially as it could trigger allocation which could
// trigger GC. Not sure if it is okay to trigger GC while the GC is updating
// references.
//
// NOTE(alan): since we are using Rust data structures that don't interact
// with the Ruby GC now, it might be feasible to allow movement.
let invariants = Invariants::get_instance();
// Mark CME imemos
for cme in invariants.cme_validity.keys() {
let cme: VALUE = (*cme).into();
unsafe { rb_gc_mark(cme) };
}
// Mark class and iclass objects
for klass in invariants.method_lookup.keys() {
// TODO: This is a leak. Unused blocks linger in the table forever, preventing the
// callee class they speculate on from being collected.
// We could do a bespoke weak reference scheme on classes similar to
// the interpreter's call cache. See finalizer for T_CLASS and cc_table_free().
unsafe { rb_gc_mark(*klass) };
}
}
/// Remove all invariant assumptions made by the block by removing the block as
/// as a key in all of the relevant tables.
pub fn block_assumptions_free(blockref: &BlockRef) {
let invariants = Invariants::get_instance();
{
let block = blockref.borrow();
// For each method lookup dependency
for dep in block.iter_cme_deps() {
// Remove tracking for cme validity
if let Some(blockset) = invariants.cme_validity.get_mut(&dep.callee_cme) {
blockset.remove(blockref);
}
// Remove tracking for lookup stability
if let Some(id_to_block_set) = invariants.method_lookup.get_mut(&dep.receiver_klass) {
let mid = unsafe { (*dep.callee_cme).called_id };
if let Some(block_set) = id_to_block_set.get_mut(&mid) {
block_set.remove(&blockref);
}
}
}
}
// Remove tracking for basic operators that the given block assumes have
// not been redefined.
if let Some(bops) = invariants.block_basic_operators.remove(&blockref) {
// Remove tracking for the given block from the list of blocks associated
// with the given basic operator.
for key in &bops {
if let Some(blocks) = invariants.basic_operator_blocks.get_mut(key) {
blocks.remove(&blockref);
}
}
}
invariants.single_ractor.remove(&blockref);
// Remove tracking for constant state for a given ID.
if let Some(ids) = invariants.block_constant_states.remove(&blockref) {
for id in ids {
if let Some(blocks) = invariants.constant_state_blocks.get_mut(&id) {
blocks.remove(&blockref);
}
}
}
}
/// Callback from the opt_setinlinecache instruction in the interpreter.
/// Invalidate the block for the matching opt_getinlinecache so it could regenerate code
/// using the new value in the constant cache.
#[no_mangle]
pub extern "C" fn rb_yjit_constant_ic_update(iseq: *const rb_iseq_t, ic: IC) {
// If YJIT isn't enabled, do nothing
if !yjit_enabled_p() {
return;
}
if !unsafe { (*(*ic).entry).ic_cref }.is_null() || unsafe { rb_yjit_multi_ractor_p() } {
// We can't generate code in these situations, so no need to invalidate.
// See gen_opt_getinlinecache.
return;
}
with_vm_lock(src_loc!(), || {
let code = unsafe { get_iseq_body_iseq_encoded(iseq) };
let get_insn_idx = unsafe { (*ic).get_insn_idx };
// This should come from a running iseq, so direct threading translation
// should have been done
assert!(unsafe { FL_TEST(iseq.into(), VALUE(ISEQ_TRANSLATED)) } != VALUE(0));
assert!(get_insn_idx < unsafe { get_iseq_encoded_size(iseq) });
// Ensure that the instruction the get_insn_idx is pointing to is in
// fact a opt_getinlinecache instruction.
assert_eq!(
unsafe {
let opcode_pc = code.add(get_insn_idx.as_usize());
let translated_opcode: VALUE = opcode_pc.read();
rb_vm_insn_decode(translated_opcode)
},
OP_OPT_GETINLINECACHE.try_into().unwrap()
);
// Find the matching opt_getinlinecache and invalidate all the blocks there
// RUBY_ASSERT(insn_op_type(BIN(opt_getinlinecache), 1) == TS_IC);
let ic_pc = unsafe { code.add(get_insn_idx.as_usize() + 2) };
let ic_operand: IC = unsafe { ic_pc.read() }.as_mut_ptr();
if ic == ic_operand {
for block in take_version_list(BlockId {
iseq,
idx: get_insn_idx,
}) {
invalidate_block_version(&block);
incr_counter!(invalidate_constant_ic_fill);
}
} else {
panic!("ic->get_insn_index not set properly");
}
});
}
// Invalidate all generated code and patch C method return code to contain
// logic for firing the c_return TracePoint event. Once rb_vm_barrier()
// returns, all other ractors are pausing inside RB_VM_LOCK_ENTER(), which
// means they are inside a C routine. If there are any generated code on-stack,
// they are waiting for a return from a C routine. For every routine call, we
// patch in an exit after the body of the containing VM instruction. This makes
// it so all the invalidated code exit as soon as execution logically reaches
// the next VM instruction. The interpreter takes care of firing the tracing
// event if it so happens that the next VM instruction has one attached.
//
// The c_return event needs special handling as our codegen never outputs code
// that contains tracing logic. If we let the normal output code run until the
// start of the next VM instruction by relying on the patching scheme above, we
// would fail to fire the c_return event. The interpreter doesn't fire the
// event at an instruction boundary, so simply exiting to the interpreter isn't
// enough. To handle it, we patch in the full logic at the return address. See
// full_cfunc_return().
//
// In addition to patching, we prevent future entries into invalidated code by
// removing all live blocks from their iseq.
#[no_mangle]
pub extern "C" fn rb_yjit_tracing_invalidate_all() {
if !yjit_enabled_p() {
return;
}
use crate::asm::x86_64::jmp_ptr;
// Stop other ractors since we are going to patch machine code.
with_vm_lock(src_loc!(), || {
// Make it so all live block versions are no longer valid branch targets
unsafe { rb_yjit_for_each_iseq(Some(invalidate_all_blocks_for_tracing)) };
extern "C" fn invalidate_all_blocks_for_tracing(iseq: IseqPtr) {
if let Some(payload) = unsafe { load_iseq_payload(iseq) } {
// C comment:
// Leaking the blocks for now since we might have situations where
// a different ractor is waiting for the VM lock in branch_stub_hit().
// If we free the block that ractor can wake up with a dangling block.
//
// Deviation: since we ref count the the blocks now, we might be deallocating and
// not leak the block.
//
// Empty all blocks on the iseq so we don't compile new blocks that jump to the
// invalidated region.
let blocks = payload.take_all_blocks();
for blockref in blocks {
block_assumptions_free(&blockref);
}
}
// Reset output code entry point
unsafe { rb_iseq_reset_jit_func(iseq) };
}
let cb = CodegenGlobals::get_inline_cb();
// Apply patches
let old_pos = cb.get_write_pos();
let patches = CodegenGlobals::take_global_inval_patches();
for patch in &patches {
cb.set_write_ptr(patch.inline_patch_pos);
jmp_ptr(cb, patch.outlined_target_pos);
// FIXME: Can't easily check we actually wrote out the JMP at the moment.
// assert!(!cb.has_dropped_bytes(), "patches should have space and jump offsets should fit in JMP rel32");
}
cb.set_pos(old_pos);
// Freeze invalidated part of the codepage. We only want to wait for
// running instances of the code to exit from now on, so we shouldn't
// change the code. There could be other ractors sleeping in
// branch_stub_hit(), for example. We could harden this by changing memory
// protection on the frozen range.
assert!(
CodegenGlobals::get_inline_frozen_bytes() <= old_pos,
"frozen bytes should increase monotonically"
);
CodegenGlobals::set_inline_frozen_bytes(old_pos);
CodegenGlobals::get_outlined_cb()
.unwrap()
.mark_all_executable();
cb.mark_all_executable();
});
}

17
yjit/src/lib.rs Normal file
Просмотреть файл

@ -0,0 +1,17 @@
// Silence dead code warnings until we are done porting YJIT
#![allow(unused_imports)]
#![allow(dead_code)]
#![allow(unused_assignments)]
#![allow(unused_macros)]
#![allow(clippy::style)] // We are laid back about style
mod asm;
mod codegen;
mod core;
mod cruby;
mod disasm;
mod invariants;
mod options;
mod stats;
mod utils;
mod yjit;

121
yjit/src/options.rs Normal file
Просмотреть файл

@ -0,0 +1,121 @@
use std::ffi::CStr;
// Command-line options
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[repr(C)]
pub struct Options {
// Size of the executable memory block to allocate in MiB
pub exec_mem_size: usize,
// Number of method calls after which to start generating code
// Threshold==1 means compile on first execution
pub call_threshold: usize,
// Generate versions greedily until the limit is hit
pub greedy_versioning: bool,
// Disable the propagation of type information
pub no_type_prop: bool,
// Maximum number of versions per block
// 1 means always create generic versions
pub max_versions: usize,
// Capture and print out stats
pub gen_stats: bool,
/// Dump compiled and executed instructions for debugging
pub dump_insns: bool,
/// Verify context objects (debug mode only)
pub verify_ctx: bool,
/// Whether or not to assume a global constant state (and therefore
/// invalidating code whenever any constant changes) versus assuming
/// constant name components (and therefore invalidating code whenever a
/// matching name component changes)
pub global_constant_state: bool,
}
// Initialize the options to default values
pub static mut OPTIONS: Options = Options {
exec_mem_size: 256,
call_threshold: 10,
greedy_versioning: false,
no_type_prop: false,
max_versions: 4,
gen_stats: false,
dump_insns: false,
verify_ctx: false,
global_constant_state: false,
};
/// Macro to get an option value by name
macro_rules! get_option {
// Unsafe is ok here because options are initialized
// once before any Ruby code executes
($option_name:ident) => {
unsafe { OPTIONS.$option_name }
};
}
pub(crate) use get_option;
/// Expected to receive what comes after the third dash in "--yjit-*".
/// Empty string means user passed only "--yjit". C code rejects when
/// they pass exact "--yjit-".
pub fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> {
let c_str: &CStr = unsafe { CStr::from_ptr(str_ptr) };
let opt_str: &str = c_str.to_str().ok()?;
//println!("{}", opt_str);
// Split the option name and value strings
// Note that some options do not contain an assignment
let parts = opt_str.split_once("=");
let (opt_name, opt_val) = match parts {
Some((before_eq, after_eq)) => (before_eq, after_eq),
None => (opt_str, ""),
};
// Match on the option name and value strings
match (opt_name, opt_val) {
("", "") => (), // Simply --yjit
("exec-mem-size", _) => match opt_val.parse() {
Ok(n) => unsafe { OPTIONS.exec_mem_size = n },
Err(_) => {
return None;
}
},
("call-threshold", _) => match opt_val.parse() {
Ok(n) => unsafe { OPTIONS.call_threshold = n },
Err(_) => {
return None;
}
},
("max-versions", _) => match opt_val.parse() {
Ok(n) => unsafe { OPTIONS.max_versions = n },
Err(_) => {
return None;
}
},
("greedy-versioning", "") => unsafe { OPTIONS.greedy_versioning = true },
("no-type-prop", "") => unsafe { OPTIONS.no_type_prop = true },
("stats", "") => unsafe { OPTIONS.gen_stats = true },
("dump-insns", "") => unsafe { OPTIONS.dump_insns = true },
("verify-ctx", "") => unsafe { OPTIONS.verify_ctx = true },
("global-constant-state", "") => unsafe { OPTIONS.global_constant_state = true },
// Option name not recognized
_ => {
return None;
}
}
// dbg!(unsafe {OPTIONS});
// Option successfully parsed
return Some(());
}

271
yjit/src/stats.rs Normal file
Просмотреть файл

@ -0,0 +1,271 @@
//! Everything related to the collection of runtime stats in YJIT
//! See the stats feature and the --yjit-stats command-line option
use crate::codegen::CodegenGlobals;
use crate::cruby::*;
use crate::options::*;
use crate::yjit::yjit_enabled_p;
// YJIT exit counts for each instruction type
static mut EXIT_OP_COUNT: [u64; VM_INSTRUCTION_SIZE] = [0; VM_INSTRUCTION_SIZE];
// Macro to declare the stat counters
macro_rules! make_counters {
($($counter_name:ident,)+) => {
// Struct containing the counter values
#[derive(Default, Debug)]
pub struct Counters { $(pub $counter_name: u64),+ }
// Global counters instance, initialized to zero
pub static mut COUNTERS: Counters = Counters { $($counter_name: 0),+ };
// Counter names constant
const COUNTER_NAMES: &'static [&'static str] = &[ $(stringify!($counter_name)),+ ];
// Map a counter name string to a counter pointer
fn get_counter_ptr(name: &str) -> *mut u64 {
match name {
$( stringify!($counter_name) => { ptr_to_counter!($counter_name) } ),+
_ => panic!()
}
}
}
}
/// Macro to increment a counter by name
macro_rules! incr_counter {
// Unsafe is ok here because options are initialized
// once before any Ruby code executes
($counter_name:ident) => {
#[allow(unused_unsafe)]
{
unsafe { COUNTERS.$counter_name += 1 }
}
};
}
pub(crate) use incr_counter;
/// Macro to get a raw pointer to a given counter
macro_rules! ptr_to_counter {
($counter_name:ident) => {
unsafe {
let ctr_ptr = std::ptr::addr_of_mut!(COUNTERS.$counter_name);
ctr_ptr
}
};
}
pub(crate) use ptr_to_counter;
// Declare all the counters we track
make_counters! {
exec_instruction,
send_keywords,
send_kw_splat,
send_args_splat,
send_block_arg,
send_ivar_set_method,
send_zsuper_method,
send_undef_method,
send_optimized_method,
send_optimized_method_send,
send_optimized_method_call,
send_optimized_method_block_call,
send_missing_method,
send_bmethod,
send_refined_method,
send_cfunc_ruby_array_varg,
send_cfunc_argc_mismatch,
send_cfunc_toomany_args,
send_cfunc_tracing,
send_cfunc_kwargs,
send_attrset_kwargs,
send_iseq_tailcall,
send_iseq_arity_error,
send_iseq_only_keywords,
send_iseq_kwargs_req_and_opt_missing,
send_iseq_kwargs_mismatch,
send_iseq_complex_callee,
send_not_implemented_method,
send_getter_arity,
send_se_cf_overflow,
send_se_protected_check_failed,
traced_cfunc_return,
invokesuper_me_changed,
invokesuper_block,
leave_se_interrupt,
leave_interp_return,
leave_start_pc_non_zero,
getivar_se_self_not_heap,
getivar_idx_out_of_range,
getivar_megamorphic,
setivar_se_self_not_heap,
setivar_idx_out_of_range,
setivar_val_heapobject,
setivar_name_not_mapped,
setivar_not_object,
setivar_frozen,
oaref_argc_not_one,
oaref_arg_not_fixnum,
opt_getinlinecache_miss,
binding_allocations,
binding_set,
vm_insns_count,
compiled_iseq_count,
compiled_block_count,
compilation_failure,
exit_from_branch_stub,
invalidation_count,
invalidate_method_lookup,
invalidate_bop_redefined,
invalidate_ractor_spawn,
invalidate_constant_state_bump,
invalidate_constant_ic_fill,
constant_state_bumps,
expandarray_splat,
expandarray_postarg,
expandarray_not_array,
expandarray_rhs_too_small,
gbpp_block_param_modified,
gbpp_block_handler_not_iseq,
}
//===========================================================================
/// Primitive called in yjit.rb
/// Check if stats generation is enabled
#[no_mangle]
pub extern "C" fn rb_yjit_stats_enabled_p(_ec: EcPtr, _ruby_self: VALUE) -> VALUE {
#[cfg(feature = "stats")]
if get_option!(gen_stats) {
return Qtrue;
}
return Qfalse;
}
/// Primitive called in yjit.rb.
/// Export all YJIT statistics as a Ruby hash.
#[no_mangle]
pub extern "C" fn rb_yjit_get_stats(_ec: EcPtr, _ruby_self: VALUE) -> VALUE {
with_vm_lock(src_loc!(), || rb_yjit_gen_stats_dict())
}
/// Export all YJIT statistics as a Ruby hash.
fn rb_yjit_gen_stats_dict() -> VALUE {
// If YJIT is not enabled, return Qnil
if !yjit_enabled_p() {
return Qnil;
}
let hash = unsafe { rb_hash_new() };
// Inline and outlined code size
unsafe {
// Get the inline and outlined code blocks
let cb = CodegenGlobals::get_inline_cb();
let ocb = CodegenGlobals::get_outlined_cb();
// Inline code size
let key = rust_str_to_sym("inline_code_size");
let value = VALUE::fixnum_from_usize(cb.get_write_pos());
rb_hash_aset(hash, key, value);
// Outlined code size
let key = rust_str_to_sym("outlined_code_size");
let value = VALUE::fixnum_from_usize(ocb.unwrap().get_write_pos());
rb_hash_aset(hash, key, value);
}
// If we're not generating stats, the hash is done
if !get_option!(gen_stats) {
return hash;
}
// If the stats feature is enabled
#[cfg(feature = "stats")]
unsafe {
// Indicate that the complete set of stats is available
rb_hash_aset(hash, rust_str_to_sym("all_stats"), Qtrue);
// For each counter we track
for counter_name in COUNTER_NAMES {
// Get the counter value
let counter_ptr = get_counter_ptr(counter_name);
let counter_val = *counter_ptr;
// Put counter into hash
let key = rust_str_to_sym(counter_name);
let value = VALUE::fixnum_from_usize(counter_val as usize);
rb_hash_aset(hash, key, value);
}
// For each entry in exit_op_count, add a stats entry with key "exit_INSTRUCTION_NAME"
// and the value is the count of side exits for that instruction.
for op_idx in 0..VM_INSTRUCTION_SIZE {
let op_name = insn_name(op_idx);
let key_string = "exit_".to_owned() + &op_name;
let key = rust_str_to_sym(&key_string);
let value = VALUE::fixnum_from_usize(EXIT_OP_COUNT[op_idx] as usize);
rb_hash_aset(hash, key, value);
}
}
hash
}
/// Primitive called in yjit.rb. Zero out all the counters.
#[no_mangle]
pub extern "C" fn rb_yjit_reset_stats_bang(_ec: EcPtr, _ruby_self: VALUE) -> VALUE {
unsafe {
EXIT_OP_COUNT = [0; VM_INSTRUCTION_SIZE];
COUNTERS = Counters::default();
}
return Qnil;
}
/// Increment the number of instructions executed by the interpreter
#[no_mangle]
pub extern "C" fn rb_yjit_collect_vm_usage_insn() {
incr_counter!(vm_insns_count);
}
#[no_mangle]
pub extern "C" fn rb_yjit_collect_binding_alloc() {
incr_counter!(binding_allocations);
}
#[no_mangle]
pub extern "C" fn rb_yjit_collect_binding_set() {
incr_counter!(binding_set);
}
#[no_mangle]
pub extern "C" fn rb_yjit_count_side_exit_op(exit_pc: *const VALUE) -> *const VALUE {
#[cfg(not(test))]
unsafe {
// Get the opcode from the encoded insn handler at this PC
let opcode = rb_vm_insn_addr2opcode((*exit_pc).as_ptr());
// Increment the exit op count for this opcode
EXIT_OP_COUNT[opcode as usize] += 1;
};
// This function must return exit_pc!
return exit_pc;
}

205
yjit/src/utils.rs Normal file
Просмотреть файл

@ -0,0 +1,205 @@
use crate::asm::x86_64::*;
use crate::asm::*;
use crate::cruby::*;
use std::slice;
/// Trait for casting to [usize] that allows you to say `.as_usize()`.
/// Implementation conditional on the the cast preserving the numeric value on
/// all inputs and being inexpensive.
///
/// [usize] is only guaranteed to be more than 16-bit wide, so we can't use
/// `.into()` to cast an `u32` or an `u64` to a `usize` even though in all
/// the platforms YJIT supports these two casts are pretty much no-ops.
/// We could say `as usize` or `.try_convert().unwrap()` everywhere
/// for those casts but they both have undesirable consequences if and when
/// we decide to support 32-bit platforms. Unfortunately we can't implement
/// [::core::convert::From] for [usize] since both the trait and the type are
/// external. Naming the method `into()` also runs into naming conflicts.
pub(crate) trait IntoUsize {
/// Convert to usize. Implementation conditional on width of [usize].
fn as_usize(self) -> usize;
}
#[cfg(target_pointer_width = "64")]
impl IntoUsize for u64 {
fn as_usize(self) -> usize {
self as usize
}
}
#[cfg(target_pointer_width = "64")]
impl IntoUsize for u32 {
fn as_usize(self) -> usize {
self as usize
}
}
impl IntoUsize for u16 {
/// Alias for `.into()`. For convenience so you could use the trait for
/// all unsgined types.
fn as_usize(self) -> usize {
self.into()
}
}
impl IntoUsize for u8 {
/// Alias for `.into()`. For convenience so you could use the trait for
/// all unsgined types.
fn as_usize(self) -> usize {
self.into()
}
}
#[cfg(test)]
mod tests {
#[test]
fn min_max_preserved_after_cast_to_usize() {
use crate::utils::IntoUsize;
let min: usize = u64::MIN.as_usize();
assert_eq!(min, u64::MIN.try_into().unwrap());
let max: usize = u64::MAX.as_usize();
assert_eq!(max, u64::MAX.try_into().unwrap());
let min: usize = u32::MIN.as_usize();
assert_eq!(min, u32::MIN.try_into().unwrap());
let max: usize = u32::MAX.as_usize();
assert_eq!(max, u32::MAX.try_into().unwrap());
}
}
// TODO: we may want to move this function into yjit.c, maybe add a convenient Rust-side wrapper
/*
// For debugging. Print the bytecode for an iseq.
RBIMPL_ATTR_MAYBE_UNUSED()
static void
yjit_print_iseq(const rb_iseq_t *iseq)
{
char *ptr;
long len;
VALUE disassembly = rb_iseq_disasm(iseq);
RSTRING_GETMEM(disassembly, ptr, len);
fprintf(stderr, "%.*s\n", (int)len, ptr);
}
*/
// Save caller-save registers on the stack before a C call
fn push_regs(cb: &mut CodeBlock) {
push(cb, RAX);
push(cb, RCX);
push(cb, RDX);
push(cb, RSI);
push(cb, RDI);
push(cb, R8);
push(cb, R9);
push(cb, R10);
push(cb, R11);
pushfq(cb);
}
// Restore caller-save registers from the after a C call
fn pop_regs(cb: &mut CodeBlock) {
popfq(cb);
pop(cb, R11);
pop(cb, R10);
pop(cb, R9);
pop(cb, R8);
pop(cb, RDI);
pop(cb, RSI);
pop(cb, RDX);
pop(cb, RCX);
pop(cb, RAX);
}
pub fn print_int(cb: &mut CodeBlock, opnd: X86Opnd) {
extern "sysv64" fn print_int_fn(val: i64) {
println!("{}", val);
}
push_regs(cb);
match opnd {
X86Opnd::Mem(_) | X86Opnd::Reg(_) => {
// Sign-extend the value if necessary
if opnd.num_bits() < 64 {
movsx(cb, C_ARG_REGS[0], opnd);
} else {
mov(cb, C_ARG_REGS[0], opnd);
}
}
X86Opnd::Imm(_) | X86Opnd::UImm(_) => {
mov(cb, C_ARG_REGS[0], opnd);
}
_ => unreachable!(),
}
mov(cb, RAX, const_ptr_opnd(print_int_fn as *const u8));
call(cb, RAX);
pop_regs(cb);
}
/// Generate code to print a pointer
pub fn print_ptr(cb: &mut CodeBlock, opnd: X86Opnd) {
extern "sysv64" fn print_ptr_fn(ptr: *const u8) {
println!("{:p}", ptr);
}
assert!(opnd.num_bits() == 64);
push_regs(cb);
mov(cb, C_ARG_REGS[0], opnd);
mov(cb, RAX, const_ptr_opnd(print_ptr_fn as *const u8));
call(cb, RAX);
pop_regs(cb);
}
/// Generate code to print a value
pub fn print_value(cb: &mut CodeBlock, opnd: X86Opnd) {
extern "sysv64" fn print_value_fn(val: VALUE) {
unsafe { rb_obj_info_dump(val) }
}
assert!(opnd.num_bits() == 64);
push_regs(cb);
mov(cb, RDI, opnd);
mov(cb, RAX, const_ptr_opnd(print_value_fn as *const u8));
call(cb, RAX);
pop_regs(cb);
}
// Generate code to print constant string to stdout
pub fn print_str(cb: &mut CodeBlock, str: &str) {
extern "sysv64" fn print_str_cfun(ptr: *const u8, num_bytes: usize) {
unsafe {
let slice = slice::from_raw_parts(ptr, num_bytes);
let str = std::str::from_utf8(slice).unwrap();
println!("{}", str);
}
}
let bytes = str.as_ptr();
let num_bytes = str.len();
push_regs(cb);
// Load the string address and jump over the string data
lea(cb, C_ARG_REGS[0], mem_opnd(8, RIP, 5));
jmp32(cb, num_bytes as i32);
// Write the string chars and a null terminator
for i in 0..num_bytes {
cb.write_byte(unsafe { *bytes.add(i) });
}
// Pass the string length as an argument
mov(cb, C_ARG_REGS[1], uimm_opnd(num_bytes as u64));
// Call the print function
mov(cb, RAX, const_ptr_opnd(print_str_cfun as *const u8));
call(cb, RAX);
pop_regs(cb);
}

98
yjit/src/yjit.rs Normal file
Просмотреть файл

@ -0,0 +1,98 @@
use crate::codegen::*;
use crate::core::*;
use crate::cruby::*;
use crate::invariants::*;
use crate::options::*;
use std::os::raw;
use std::sync::atomic::{AtomicBool, Ordering};
/// For tracking whether the user enabled YJIT through command line arguments or environment
/// variables. AtomicBool to avoid `unsafe`. On x86 it compiles to simple movs.
/// See <https://doc.rust-lang.org/std/sync/atomic/enum.Ordering.html>
/// See [rb_yjit_enabled_p]
static YJIT_ENABLED: AtomicBool = AtomicBool::new(false);
/// Parse one command-line option.
/// This is called from ruby.c
#[no_mangle]
pub extern "C" fn rb_yjit_parse_option(str_ptr: *const raw::c_char) -> bool {
return parse_option(str_ptr).is_some();
}
/// Is YJIT on? The interpreter uses this function to decide whether to increment
/// ISEQ call counters. See mjit_exec().
/// This is used frequently since it's used on every method call in the interpreter.
#[no_mangle]
pub extern "C" fn rb_yjit_enabled_p() -> raw::c_int {
// Note that we might want to call this function from signal handlers so
// might need to ensure signal-safety(7).
YJIT_ENABLED.load(Ordering::Acquire).into()
}
/// Like rb_yjit_enabled_p, but for Rust code.
pub fn yjit_enabled_p() -> bool {
YJIT_ENABLED.load(Ordering::Acquire)
}
/// After how many calls YJIT starts compiling a method
#[no_mangle]
pub extern "C" fn rb_yjit_call_threshold() -> raw::c_uint {
get_option!(call_threshold) as raw::c_uint
}
/// This function is called from C code
#[no_mangle]
pub extern "C" fn rb_yjit_init_rust() {
// TODO: need to make sure that command-line options have been
// initialized by CRuby
// Catch panics to avoid UB for unwinding into C frames.
// See https://doc.rust-lang.org/nomicon/exception-safety.html
// TODO: set a panic handler so the we don't print a message
// everytime we panic.
let result = std::panic::catch_unwind(|| {
Invariants::init();
CodegenGlobals::init();
// YJIT enabled and initialized successfully
YJIT_ENABLED.store(true, Ordering::Release);
});
if let Err(_) = result {
println!("YJIT: rb_yjit_init_rust() panicked. Aborting.");
std::process::abort();
}
}
/// Called from C code to begin compiling a function
/// NOTE: this should be wrapped in RB_VM_LOCK_ENTER(), rb_vm_barrier() on the C side
#[no_mangle]
pub extern "C" fn rb_yjit_iseq_gen_entry_point(iseq: IseqPtr, ec: EcPtr) -> *const u8 {
let maybe_code_ptr = gen_entry_point(iseq, ec);
match maybe_code_ptr {
Some(ptr) => ptr.raw_ptr(),
None => std::ptr::null(),
}
}
/// Simulate a situation where we are out of executable memory
#[no_mangle]
pub extern "C" fn rb_yjit_simulate_oom_bang(_ec: EcPtr, _ruby_self: VALUE) -> VALUE {
// If YJIT is not enabled, do nothing
if !yjit_enabled_p() {
return Qnil;
}
// Enabled in debug mode only for security
#[cfg(debug_assertions)]
{
let cb = CodegenGlobals::get_inline_cb();
let ocb = CodegenGlobals::get_outlined_cb().unwrap();
cb.set_pos(cb.get_mem_size() - 1);
ocb.set_pos(ocb.get_mem_size() - 1);
}
return Qnil;
}

1834
yjit_asm.c

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,408 +0,0 @@
#ifndef YJIT_ASM_H
#define YJIT_ASM_H 1
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
// Maximum number of labels to link
#define MAX_LABELS 32
// Maximum number of label references
#define MAX_LABEL_REFS 32
// Reference to an ASM label
typedef struct LabelRef
{
// Position in the code block where the label reference exists
uint32_t pos;
// Label which this refers to
uint32_t label_idx;
} labelref_t;
// Block of executable memory into which instructions can be written
typedef struct CodeBlock
{
// Memory block
// Users are advised to not use this directly.
uint8_t *mem_block_;
// Memory block size
uint32_t mem_size;
// Current writing position
uint32_t write_pos;
// Table of registered label addresses
uint32_t label_addrs[MAX_LABELS];
// Table of registered label names
// Note that these should be constant strings only
const char *label_names[MAX_LABELS];
// References to labels
labelref_t label_refs[MAX_LABEL_REFS];
// Number of labels registeered
uint32_t num_labels;
// Number of references to labels
uint32_t num_refs;
// Keep track of the current aligned write position.
// Used for changing protection when writing to the JIT buffer
uint32_t current_aligned_write_pos;
// Set if the assembler is unable to output some instructions,
// for example, when there is not enough space or when a jump
// target is too far away.
bool dropped_bytes;
// Flag to enable or disable comments
bool has_asm;
} codeblock_t;
// 1 is not aligned so this won't match any pages
#define ALIGNED_WRITE_POSITION_NONE 1
enum OpndType
{
OPND_NONE,
OPND_REG,
OPND_IMM,
OPND_MEM
};
enum RegType
{
REG_GP,
REG_FP,
REG_XMM,
REG_IP
};
typedef struct X86Reg
{
// Register type
uint8_t reg_type;
// Register index number
uint8_t reg_no;
} x86reg_t;
typedef struct X86Mem
{
/// Base register number
uint8_t base_reg_no;
/// Index register number
uint8_t idx_reg_no;
/// SIB scale exponent value (power of two, two bits)
uint8_t scale_exp;
/// Has index register flag
bool has_idx;
// TODO: should this be here, or should we have an extra operand type?
/// IP-relative addressing flag
bool is_iprel;
/// Constant displacement from the base, not scaled
int32_t disp;
} x86mem_t;
typedef struct X86Opnd
{
// Operand type
uint8_t type;
// Size in bits
uint16_t num_bits;
union
{
// Register operand
x86reg_t reg;
// Memory operand
x86mem_t mem;
// Signed immediate value
int64_t imm;
// Unsigned immediate value
uint64_t unsig_imm;
} as;
} x86opnd_t;
// Dummy none/null operand
static const x86opnd_t NO_OPND = { OPND_NONE, 0, .as.imm = 0 };
// Instruction pointer
static const x86opnd_t RIP = { OPND_REG, 64, .as.reg = { REG_IP, 5 }};
// 64-bit GP registers
static const x86opnd_t RAX = { OPND_REG, 64, .as.reg = { REG_GP, 0 }};
static const x86opnd_t RCX = { OPND_REG, 64, .as.reg = { REG_GP, 1 }};
static const x86opnd_t RDX = { OPND_REG, 64, .as.reg = { REG_GP, 2 }};
static const x86opnd_t RBX = { OPND_REG, 64, .as.reg = { REG_GP, 3 }};
static const x86opnd_t RSP = { OPND_REG, 64, .as.reg = { REG_GP, 4 }};
static const x86opnd_t RBP = { OPND_REG, 64, .as.reg = { REG_GP, 5 }};
static const x86opnd_t RSI = { OPND_REG, 64, .as.reg = { REG_GP, 6 }};
static const x86opnd_t RDI = { OPND_REG, 64, .as.reg = { REG_GP, 7 }};
static const x86opnd_t R8 = { OPND_REG, 64, .as.reg = { REG_GP, 8 }};
static const x86opnd_t R9 = { OPND_REG, 64, .as.reg = { REG_GP, 9 }};
static const x86opnd_t R10 = { OPND_REG, 64, .as.reg = { REG_GP, 10 }};
static const x86opnd_t R11 = { OPND_REG, 64, .as.reg = { REG_GP, 11 }};
static const x86opnd_t R12 = { OPND_REG, 64, .as.reg = { REG_GP, 12 }};
static const x86opnd_t R13 = { OPND_REG, 64, .as.reg = { REG_GP, 13 }};
static const x86opnd_t R14 = { OPND_REG, 64, .as.reg = { REG_GP, 14 }};
static const x86opnd_t R15 = { OPND_REG, 64, .as.reg = { REG_GP, 15 }};
// 32-bit GP registers
static const x86opnd_t EAX = { OPND_REG, 32, .as.reg = { REG_GP, 0 }};
static const x86opnd_t ECX = { OPND_REG, 32, .as.reg = { REG_GP, 1 }};
static const x86opnd_t EDX = { OPND_REG, 32, .as.reg = { REG_GP, 2 }};
static const x86opnd_t EBX = { OPND_REG, 32, .as.reg = { REG_GP, 3 }};
static const x86opnd_t ESP = { OPND_REG, 32, .as.reg = { REG_GP, 4 }};
static const x86opnd_t EBP = { OPND_REG, 32, .as.reg = { REG_GP, 5 }};
static const x86opnd_t ESI = { OPND_REG, 32, .as.reg = { REG_GP, 6 }};
static const x86opnd_t EDI = { OPND_REG, 32, .as.reg = { REG_GP, 7 }};
static const x86opnd_t R8D = { OPND_REG, 32, .as.reg = { REG_GP, 8 }};
static const x86opnd_t R9D = { OPND_REG, 32, .as.reg = { REG_GP, 9 }};
static const x86opnd_t R10D = { OPND_REG, 32, .as.reg = { REG_GP, 10 }};
static const x86opnd_t R11D = { OPND_REG, 32, .as.reg = { REG_GP, 11 }};
static const x86opnd_t R12D = { OPND_REG, 32, .as.reg = { REG_GP, 12 }};
static const x86opnd_t R13D = { OPND_REG, 32, .as.reg = { REG_GP, 13 }};
static const x86opnd_t R14D = { OPND_REG, 32, .as.reg = { REG_GP, 14 }};
static const x86opnd_t R15D = { OPND_REG, 32, .as.reg = { REG_GP, 15 }};
// 16-bit GP registers
static const x86opnd_t AX = { OPND_REG, 16, .as.reg = { REG_GP, 0 }};
static const x86opnd_t CX = { OPND_REG, 16, .as.reg = { REG_GP, 1 }};
static const x86opnd_t DX = { OPND_REG, 16, .as.reg = { REG_GP, 2 }};
static const x86opnd_t BX = { OPND_REG, 16, .as.reg = { REG_GP, 3 }};
static const x86opnd_t SP = { OPND_REG, 16, .as.reg = { REG_GP, 4 }};
static const x86opnd_t BP = { OPND_REG, 16, .as.reg = { REG_GP, 5 }};
static const x86opnd_t SI = { OPND_REG, 16, .as.reg = { REG_GP, 6 }};
static const x86opnd_t DI = { OPND_REG, 16, .as.reg = { REG_GP, 7 }};
static const x86opnd_t R8W = { OPND_REG, 16, .as.reg = { REG_GP, 8 }};
static const x86opnd_t R9W = { OPND_REG, 16, .as.reg = { REG_GP, 9 }};
static const x86opnd_t R10W = { OPND_REG, 16, .as.reg = { REG_GP, 10 }};
static const x86opnd_t R11W = { OPND_REG, 16, .as.reg = { REG_GP, 11 }};
static const x86opnd_t R12W = { OPND_REG, 16, .as.reg = { REG_GP, 12 }};
static const x86opnd_t R13W = { OPND_REG, 16, .as.reg = { REG_GP, 13 }};
static const x86opnd_t R14W = { OPND_REG, 16, .as.reg = { REG_GP, 14 }};
static const x86opnd_t R15W = { OPND_REG, 16, .as.reg = { REG_GP, 15 }};
// 8-bit GP registers
static const x86opnd_t AL = { OPND_REG, 8, .as.reg = { REG_GP, 0 }};
static const x86opnd_t CL = { OPND_REG, 8, .as.reg = { REG_GP, 1 }};
static const x86opnd_t DL = { OPND_REG, 8, .as.reg = { REG_GP, 2 }};
static const x86opnd_t BL = { OPND_REG, 8, .as.reg = { REG_GP, 3 }};
static const x86opnd_t SPL = { OPND_REG, 8, .as.reg = { REG_GP, 4 }};
static const x86opnd_t BPL = { OPND_REG, 8, .as.reg = { REG_GP, 5 }};
static const x86opnd_t SIL = { OPND_REG, 8, .as.reg = { REG_GP, 6 }};
static const x86opnd_t DIL = { OPND_REG, 8, .as.reg = { REG_GP, 7 }};
static const x86opnd_t R8B = { OPND_REG, 8, .as.reg = { REG_GP, 8 }};
static const x86opnd_t R9B = { OPND_REG, 8, .as.reg = { REG_GP, 9 }};
static const x86opnd_t R10B = { OPND_REG, 8, .as.reg = { REG_GP, 10 }};
static const x86opnd_t R11B = { OPND_REG, 8, .as.reg = { REG_GP, 11 }};
static const x86opnd_t R12B = { OPND_REG, 8, .as.reg = { REG_GP, 12 }};
static const x86opnd_t R13B = { OPND_REG, 8, .as.reg = { REG_GP, 13 }};
static const x86opnd_t R14B = { OPND_REG, 8, .as.reg = { REG_GP, 14 }};
static const x86opnd_t R15B = { OPND_REG, 8, .as.reg = { REG_GP, 15 }};
// C argument registers
#define NUM_C_ARG_REGS 6
#define C_ARG_REGS ( (x86opnd_t[]){ RDI, RSI, RDX, RCX, R8, R9 } )
// Compute the number of bits needed to store a signed or unsigned value
static inline uint32_t sig_imm_size(int64_t imm);
static inline uint32_t unsig_imm_size(uint64_t imm);
// Memory operand with base register and displacement/offset
static inline x86opnd_t mem_opnd(uint32_t num_bits, x86opnd_t base_reg, int32_t disp);
// Scale-index-base memory operand
static inline x86opnd_t mem_opnd_sib(uint32_t num_bits, x86opnd_t base_reg, x86opnd_t index_reg, int32_t scale, int32_t disp);
// Immediate number operand
static inline x86opnd_t imm_opnd(int64_t val);
// Constant pointer operand
static inline x86opnd_t const_ptr_opnd(const void *ptr);
// Struct member operand
#define member_opnd(base_reg, struct_type, member_name) mem_opnd( \
8 * sizeof(((struct_type*)0)->member_name), \
base_reg, \
offsetof(struct_type, member_name) \
)
// Struct member operand with an array index
#define member_opnd_idx(base_reg, struct_type, member_name, idx) mem_opnd( \
8 * sizeof(((struct_type*)0)->member_name[0]), \
base_reg, \
(offsetof(struct_type, member_name) + \
sizeof(((struct_type*)0)->member_name[0]) * idx) \
)
// Allocate executable memory
static uint8_t *alloc_exec_mem(uint32_t mem_size);
// Code block functions
static inline void cb_init(codeblock_t *cb, uint8_t *mem_block, uint32_t mem_size);
static inline void cb_align_pos(codeblock_t *cb, uint32_t multiple);
static inline void cb_set_pos(codeblock_t *cb, uint32_t pos);
static inline void cb_set_write_ptr(codeblock_t *cb, uint8_t *code_ptr);
static inline uint8_t *cb_get_ptr(const codeblock_t *cb, uint32_t index);
static inline uint8_t *cb_get_write_ptr(const codeblock_t *cb);
static inline void cb_write_byte(codeblock_t *cb, uint8_t byte);
static inline void cb_write_bytes(codeblock_t *cb, uint32_t num_bytes, ...);
static inline void cb_write_int(codeblock_t *cb, uint64_t val, uint32_t num_bits);
static inline uint32_t cb_new_label(codeblock_t *cb, const char *name);
static inline void cb_write_label(codeblock_t *cb, uint32_t label_idx);
static inline void cb_label_ref(codeblock_t *cb, uint32_t label_idx);
static inline void cb_link_labels(codeblock_t *cb);
static inline void cb_mark_all_writeable(codeblock_t *cb);
static inline void cb_mark_position_writeable(codeblock_t *cb, uint32_t write_pos);
static inline void cb_mark_all_executable(codeblock_t *cb);
// Encode individual instructions into a code block
static inline void add(codeblock_t *cb, x86opnd_t opnd0, x86opnd_t opnd1);
static inline void and(codeblock_t *cb, x86opnd_t opnd0, x86opnd_t opnd1);
static inline void call_ptr(codeblock_t *cb, x86opnd_t scratch_reg, uint8_t *dst_ptr);
static inline void call_label(codeblock_t *cb, uint32_t label_idx);
static inline void call(codeblock_t *cb, x86opnd_t opnd);
static inline void cmova(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovae(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovb(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovbe(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovc(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmove(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovg(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovge(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovl(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovle(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovna(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovnae(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovnb(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovnbe(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovnc(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovne(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovng(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovnge(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovnl(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovnle(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovno(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovnp(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovns(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovnz(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovo(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovp(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovpe(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovpo(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovs(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmovz(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void cmp(codeblock_t *cb, x86opnd_t opnd0, x86opnd_t opnd1);
static inline void cdq(codeblock_t *cb);
static inline void cqo(codeblock_t *cb);
static inline void int3(codeblock_t *cb);
static inline void ja_label(codeblock_t *cb, uint32_t label_idx);
static inline void jae_label(codeblock_t *cb, uint32_t label_idx);
static inline void jb_label(codeblock_t *cb, uint32_t label_idx);
static inline void jbe_label(codeblock_t *cb, uint32_t label_idx);
static inline void jc_label(codeblock_t *cb, uint32_t label_idx);
static inline void je_label(codeblock_t *cb, uint32_t label_idx);
static inline void jg_label(codeblock_t *cb, uint32_t label_idx);
static inline void jge_label(codeblock_t *cb, uint32_t label_idx);
static inline void jl_label(codeblock_t *cb, uint32_t label_idx);
static inline void jle_label(codeblock_t *cb, uint32_t label_idx);
static inline void jna_label(codeblock_t *cb, uint32_t label_idx);
static inline void jnae_label(codeblock_t *cb, uint32_t label_idx);
static inline void jnb_label(codeblock_t *cb, uint32_t label_idx);
static inline void jnbe_label(codeblock_t *cb, uint32_t label_idx);
static inline void jnc_label(codeblock_t *cb, uint32_t label_idx);
static inline void jne_label(codeblock_t *cb, uint32_t label_idx);
static inline void jng_label(codeblock_t *cb, uint32_t label_idx);
static inline void jnge_label(codeblock_t *cb, uint32_t label_idx);
static inline void jnl_label(codeblock_t *cb, uint32_t label_idx);
static inline void jnle_label(codeblock_t *cb, uint32_t label_idx);
static inline void jno_label(codeblock_t *cb, uint32_t label_idx);
static inline void jnp_label(codeblock_t *cb, uint32_t label_idx);
static inline void jns_label(codeblock_t *cb, uint32_t label_idx);
static inline void jnz_label(codeblock_t *cb, uint32_t label_idx);
static inline void jo_label(codeblock_t *cb, uint32_t label_idx);
static inline void jp_label(codeblock_t *cb, uint32_t label_idx);
static inline void jpe_label(codeblock_t *cb, uint32_t label_idx);
static inline void jpo_label(codeblock_t *cb, uint32_t label_idx);
static inline void js_label(codeblock_t *cb, uint32_t label_idx);
static inline void jz_label(codeblock_t *cb, uint32_t label_idx);
static inline void ja_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jae_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jb_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jbe_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jc_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void je_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jg_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jge_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jl_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jle_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jna_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jnae_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jnb_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jnbe_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jnc_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jne_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jng_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jnge_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jnl_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jnle_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jno_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jnp_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jns_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jnz_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jo_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jp_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jpe_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jpo_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void js_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jz_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jmp_label(codeblock_t *cb, uint32_t label_idx);
static inline void jmp_ptr(codeblock_t *cb, uint8_t *ptr);
static inline void jmp_rm(codeblock_t *cb, x86opnd_t opnd);
static inline void jmp32(codeblock_t *cb, int32_t offset);
static inline void lea(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void mov(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void movsx(codeblock_t *cb, x86opnd_t dst, x86opnd_t src);
static inline void neg(codeblock_t *cb, x86opnd_t opnd);
static inline void nop(codeblock_t *cb, uint32_t length);
static inline void not(codeblock_t *cb, x86opnd_t opnd);
static inline void or(codeblock_t *cb, x86opnd_t opnd0, x86opnd_t opnd1);
static inline void pop(codeblock_t *cb, x86opnd_t reg);
static inline void popfq(codeblock_t *cb);
static inline void push(codeblock_t *cb, x86opnd_t opnd);
static inline void pushfq(codeblock_t *cb);
static inline void ret(codeblock_t *cb);
static inline void sal(codeblock_t *cb, x86opnd_t opnd0, x86opnd_t opnd1);
static inline void sar(codeblock_t *cb, x86opnd_t opnd0, x86opnd_t opnd1);
static inline void shl(codeblock_t *cb, x86opnd_t opnd0, x86opnd_t opnd1);
static inline void shr(codeblock_t *cb, x86opnd_t opnd0, x86opnd_t opnd1);
static inline void sub(codeblock_t *cb, x86opnd_t opnd0, x86opnd_t opnd1);
static inline void test(codeblock_t *cb, x86opnd_t rm_opnd, x86opnd_t test_opnd);
static inline void ud2(codeblock_t *cb);
static inline void xchg(codeblock_t *cb, x86opnd_t rm_opnd, x86opnd_t r_opnd);
static inline void xor(codeblock_t *cb, x86opnd_t opnd0, x86opnd_t opnd1);
static inline void cb_write_lock_prefix(codeblock_t *cb);
#endif

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,23 +0,0 @@
#ifndef YJIT_CODEGEN_H
#define YJIT_CODEGEN_H 1
typedef enum codegen_status {
YJIT_END_BLOCK,
YJIT_KEEP_COMPILING,
YJIT_CANT_COMPILE
} codegen_status_t;
// Code generation function signature
typedef codegen_status_t (*codegen_fn)(jitstate_t *jit, ctx_t *ctx, codeblock_t *cb);
static void jit_ensure_block_entry_exit(jitstate_t *jit);
static uint8_t *yjit_entry_prologue(codeblock_t *cb, const rb_iseq_t *iseq);
static block_t *gen_single_block(blockid_t blockid, const ctx_t *start_ctx, rb_execution_context_t *ec);
static void gen_code_for_exit_from_stub(void);
static void yjit_init_codegen(void);
#endif // #ifndef YJIT_CODEGEN_H

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,307 +0,0 @@
#ifndef YJIT_CORE_H
#define YJIT_CORE_H 1
#include <stddef.h>
#include <stdint.h>
#include "yjit_asm.h"
// Callee-saved regs
#define REG_CFP R13
#define REG_EC R12
#define REG_SP RBX
// Scratch registers used by YJIT
#define REG0 RAX
#define REG0_32 EAX
#define REG0_8 AL
#define REG1 RCX
#define REG1_32 ECX
// Maximum number of temp value types we keep track of
#define MAX_TEMP_TYPES 8
// Maximum number of local variable types we keep track of
#define MAX_LOCAL_TYPES 8
// Default versioning context (no type information)
#define DEFAULT_CTX ( (ctx_t){ 0 } )
enum yjit_type_enum
{
ETYPE_UNKNOWN = 0,
ETYPE_NIL,
ETYPE_TRUE,
ETYPE_FALSE,
ETYPE_FIXNUM,
ETYPE_FLONUM,
ETYPE_ARRAY,
ETYPE_HASH,
ETYPE_SYMBOL,
ETYPE_STRING
};
// Represent the type of a value (local/stack/self) in YJIT
typedef struct yjit_type_struct
{
// Value is definitely a heap object
uint8_t is_heap : 1;
// Value is definitely an immediate
uint8_t is_imm : 1;
// Specific value type, if known
uint8_t type : 4;
} val_type_t;
STATIC_ASSERT(val_type_size, sizeof(val_type_t) == 1);
// Unknown type, could be anything, all zeroes
#define TYPE_UNKNOWN ( (val_type_t){ 0 } )
// Could be any heap object
#define TYPE_HEAP ( (val_type_t){ .is_heap = 1 } )
// Could be any immediate
#define TYPE_IMM ( (val_type_t){ .is_imm = 1 } )
#define TYPE_NIL ( (val_type_t){ .is_imm = 1, .type = ETYPE_NIL } )
#define TYPE_TRUE ( (val_type_t){ .is_imm = 1, .type = ETYPE_TRUE } )
#define TYPE_FALSE ( (val_type_t){ .is_imm = 1, .type = ETYPE_FALSE } )
#define TYPE_FIXNUM ( (val_type_t){ .is_imm = 1, .type = ETYPE_FIXNUM } )
#define TYPE_FLONUM ( (val_type_t){ .is_imm = 1, .type = ETYPE_FLONUM } )
#define TYPE_STATIC_SYMBOL ( (val_type_t){ .is_imm = 1, .type = ETYPE_SYMBOL } )
#define TYPE_ARRAY ( (val_type_t){ .is_heap = 1, .type = ETYPE_ARRAY } )
#define TYPE_HASH ( (val_type_t){ .is_heap = 1, .type = ETYPE_HASH } )
#define TYPE_STRING ( (val_type_t){ .is_heap = 1, .type = ETYPE_STRING } )
enum yjit_temp_loc
{
TEMP_STACK = 0,
TEMP_SELF,
TEMP_LOCAL, // Local with index
//TEMP_CONST, // Small constant (0, 1, 2, Qnil, Qfalse, Qtrue)
};
// Potential mapping of a value on the temporary stack to
// self, a local variable or constant so that we can track its type
typedef struct yjit_temp_mapping
{
// Where/how is the value stored?
uint8_t kind: 2;
// Index of the local variale,
// or small non-negative constant in [0, 63]
uint8_t idx : 6;
} temp_mapping_t;
STATIC_ASSERT(temp_mapping_size, sizeof(temp_mapping_t) == 1);
// By default, temps are just temps on the stack.
// Name conflict with an mmap flag. This is a struct instance,
// so the compiler will check for wrong usage.
#undef MAP_STACK
#define MAP_STACK ( (temp_mapping_t) { 0 } )
// Temp value is actually self
#define MAP_SELF ( (temp_mapping_t) { .kind = TEMP_SELF } )
// Represents both the type and mapping
typedef struct {
temp_mapping_t mapping;
val_type_t type;
} temp_type_mapping_t;
STATIC_ASSERT(temp_type_mapping_size, sizeof(temp_type_mapping_t) == 2);
// Operand to a bytecode instruction
typedef struct yjit_insn_opnd
{
// Indicates if the value is self
bool is_self;
// Index on the temporary stack (for stack operands only)
uint16_t idx;
} insn_opnd_t;
#define OPND_SELF ( (insn_opnd_t){ .is_self = true } )
#define OPND_STACK(stack_idx) ( (insn_opnd_t){ .is_self = false, .idx = stack_idx } )
/**
Code generation context
Contains information we can use to optimize code
*/
typedef struct yjit_context
{
// Number of values currently on the temporary stack
uint16_t stack_size;
// Offset of the JIT SP relative to the interpreter SP
// This represents how far the JIT's SP is from the "real" SP
int16_t sp_offset;
// Depth of this block in the sidechain (eg: inline-cache chain)
uint8_t chain_depth;
// Local variable types we keepp track of
val_type_t local_types[MAX_LOCAL_TYPES];
// Temporary variable types we keep track of
val_type_t temp_types[MAX_TEMP_TYPES];
// Type we track for self
val_type_t self_type;
// Mapping of temp stack entries to types we track
temp_mapping_t temp_mapping[MAX_TEMP_TYPES];
} ctx_t;
STATIC_ASSERT(yjit_ctx_size, sizeof(ctx_t) <= 32);
// Tuple of (iseq, idx) used to identify basic blocks
typedef struct BlockId
{
// Instruction sequence
const rb_iseq_t *iseq;
// Index in the iseq where the block starts
uint32_t idx;
} blockid_t;
// Null block id constant
static const blockid_t BLOCKID_NULL = { 0, 0 };
/// Branch code shape enumeration
typedef enum branch_shape
{
SHAPE_NEXT0, // Target 0 is next
SHAPE_NEXT1, // Target 1 is next
SHAPE_DEFAULT // Neither target is next
} branch_shape_t;
// Branch code generation function signature
typedef void (*branchgen_fn)(codeblock_t* cb, uint8_t* target0, uint8_t* target1, uint8_t shape);
/**
Store info about an outgoing branch in a code segment
Note: care must be taken to minimize the size of branch_t objects
*/
typedef struct yjit_branch_entry
{
// Block this is attached to
struct yjit_block_version *block;
// Positions where the generated code starts and ends
uint8_t *start_addr;
uint8_t *end_addr;
// Context right after the branch instruction
// Unused for now.
// ctx_t src_ctx;
// Branch target blocks and their contexts
blockid_t targets[2];
ctx_t target_ctxs[2];
struct yjit_block_version *blocks[2];
// Jump target addresses
uint8_t *dst_addrs[2];
// Branch code generation function
branchgen_fn gen_fn;
// Shape of the branch
branch_shape_t shape : 2;
} branch_t;
// In case this block is invalidated, these two pieces of info
// help to remove all pointers to this block in the system.
typedef struct {
VALUE receiver_klass;
VALUE callee_cme;
} cme_dependency_t;
typedef rb_darray(cme_dependency_t) cme_dependency_array_t;
typedef rb_darray(branch_t*) branch_array_t;
typedef rb_darray(uint32_t) int32_array_t;
/**
Basic block version
Represents a portion of an iseq compiled with a given context
Note: care must be taken to minimize the size of block_t objects
*/
typedef struct yjit_block_version
{
// Bytecode sequence (iseq, idx) this is a version of
blockid_t blockid;
// Context at the start of the block
ctx_t ctx;
// Positions where the generated code starts and ends
uint8_t *start_addr;
uint8_t *end_addr;
// List of incoming branches (from predecessors)
branch_array_t incoming;
// List of outgoing branches (to successors)
// Note: these are owned by this block version
branch_array_t outgoing;
// Offsets for GC managed objects in the mainline code block
int32_array_t gc_object_offsets;
// CME dependencies of this block, to help to remove all pointers to this
// block in the system.
cme_dependency_array_t cme_dependencies;
// Code address of an exit for `ctx` and `blockid`. Used for block
// invalidation.
uint8_t *entry_exit;
// Index one past the last instruction in the iseq
uint32_t end_idx;
} block_t;
// Code generation state
typedef struct JITState
{
// Inline and outlined code blocks we are
// currently generating code into
codeblock_t* cb;
codeblock_t* ocb;
// Block version being compiled
block_t *block;
// Instruction sequence this is associated with
const rb_iseq_t *iseq;
// Index of the current instruction being compiled
uint32_t insn_idx;
// Opcode for the instruction being compiled
int opcode;
// PC of the instruction being compiled
VALUE *pc;
// Side exit to the instruction being compiled. See :side-exit:.
uint8_t *side_exit_for_pc;
// Execution context when compilation started
// This allows us to peek at run-time values
rb_execution_context_t *ec;
// Whether we need to record the code address at
// the end of this bytecode instruction for global invalidation
bool record_boundary_patch_point;
} jitstate_t;
#endif // #ifndef YJIT_CORE_H

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,38 +0,0 @@
//
// These are definitions YJIT uses to interface with the CRuby codebase,
// but which are only used internally by YJIT.
//
#ifndef YJIT_IFACE_H
#define YJIT_IFACE_H 1
#include "ruby/internal/config.h"
#include "ruby_assert.h" // for RUBY_DEBUG
#include "yjit.h" // for YJIT_STATS
#include "vm_core.h"
#include "yjit_core.h"
#ifndef YJIT_DEFAULT_CALL_THRESHOLD
# define YJIT_DEFAULT_CALL_THRESHOLD 10
#endif
RUBY_EXTERN struct rb_yjit_options rb_yjit_opts;
static VALUE *yjit_iseq_pc_at_idx(const rb_iseq_t *iseq, uint32_t insn_idx);
static int yjit_opcode_at_pc(const rb_iseq_t *iseq, const VALUE *pc);
static void yjit_print_iseq(const rb_iseq_t *iseq);
#if YJIT_STATS
// this function *must* return passed exit_pc
static const VALUE *yjit_count_side_exit_op(const VALUE *exit_pc);
#endif
static void yjit_unlink_method_lookup_dependency(block_t *block);
static void yjit_block_assumptions_free(block_t *block);
static VALUE yjit_get_code_page(uint32_t cb_bytes_needed, uint32_t ocb_bytes_needed);
//code_page_t *rb_yjit_code_page_unwrap(VALUE cp_obj);
//void rb_yjit_get_cb(codeblock_t* cb, uint8_t* code_ptr);
//void rb_yjit_get_ocb(codeblock_t* cb, uint8_t* code_ptr);
#endif // #ifndef YJIT_IFACE_H

Просмотреть файл

@ -1,109 +0,0 @@
// This file is a fragment of the yjit.o compilation unit. See yjit.c.
// Save caller-save registers on the stack before a C call
static void
push_regs(codeblock_t *cb)
{
push(cb, RAX);
push(cb, RCX);
push(cb, RDX);
push(cb, RSI);
push(cb, RDI);
push(cb, R8);
push(cb, R9);
push(cb, R10);
push(cb, R11);
pushfq(cb);
}
// Restore caller-save registers from the after a C call
static void
pop_regs(codeblock_t *cb)
{
popfq(cb);
pop(cb, R11);
pop(cb, R10);
pop(cb, R9);
pop(cb, R8);
pop(cb, RDI);
pop(cb, RSI);
pop(cb, RDX);
pop(cb, RCX);
pop(cb, RAX);
}
static void
print_int_cfun(int64_t val)
{
fprintf(stderr, "%lld\n", (long long int)val);
}
RBIMPL_ATTR_MAYBE_UNUSED()
static void
print_int(codeblock_t *cb, x86opnd_t opnd)
{
push_regs(cb);
if (opnd.num_bits < 64 && opnd.type != OPND_IMM)
movsx(cb, RDI, opnd);
else
mov(cb, RDI, opnd);
// Call the print function
mov(cb, RAX, const_ptr_opnd((void*)&print_int_cfun));
call(cb, RAX);
pop_regs(cb);
}
static void
print_ptr_cfun(void *val)
{
fprintf(stderr, "%p\n", val);
}
RBIMPL_ATTR_MAYBE_UNUSED()
static void
print_ptr(codeblock_t *cb, x86opnd_t opnd)
{
assert (opnd.num_bits == 64);
push_regs(cb);
mov(cb, RDI, opnd);
mov(cb, RAX, const_ptr_opnd((void*)&print_ptr_cfun));
call(cb, RAX);
pop_regs(cb);
}
static void
print_str_cfun(const char *str)
{
fprintf(stderr, "%s\n", str);
}
// Print a constant string to stdout
static void
print_str(codeblock_t *cb, const char *str)
{
//as.comment("printStr(\"" ~ str ~ "\")");
size_t len = strlen(str);
push_regs(cb);
// Load the string address and jump over the string data
lea(cb, RDI, mem_opnd(8, RIP, 5));
jmp32(cb, (int32_t)len + 1);
// Write the string chars and a null terminator
for (size_t i = 0; i < len; ++i)
cb_write_byte(cb, (uint8_t)str[i]);
cb_write_byte(cb, 0);
// Call the print function
mov(cb, RAX, const_ptr_opnd((void*)&print_str_cfun));
call(cb, RAX);
pop_regs(cb);
}