YJIT: ZJIT: Allow both JITs in the same build

This commit allows building YJIT and ZJIT simultaneously, a "combo
build". Previously, `./configure --enable-yjit --enable-zjit` failed. At
runtime, though, only one of the two can be enabled at a time.

Add a root Cargo workspace that contains both the yjit and zjit crate.
The common Rust build integration mechanisms are factored out into
defs/jit.mk.

Combo YJIT+ZJIT dev builds are supported; if either JIT uses
`--enable-*=dev`, both of them are built in dev mode.

The combo build requires Cargo, but building one JIT at a time with only
rustc in release build remains supported.
This commit is contained in:
Alan Wu 2025-05-07 00:19:36 +09:00
parent b5575a80bc
commit 92b218fbc3
Notes: git 2025-05-14 15:39:18 +00:00
20 changed files with 297 additions and 197 deletions

View File

@ -10,6 +10,7 @@ files:
'zjit/src/cruby_bindings.inc.rs': [] 'zjit/src/cruby_bindings.inc.rs': []
'doc/zjit*': [team:jit] 'doc/zjit*': [team:jit]
'test/ruby/test_zjit*': [team:jit] 'test/ruby/test_zjit*': [team:jit]
'defs/jit.mk': [team:jit]
options: options:
ignore_draft: true ignore_draft: true
# This currently doesn't work as intended. We want to skip reviews when only # This currently doesn't work as intended. We want to skip reviews when only

3
.gitignore vendored
View File

@ -246,6 +246,9 @@ lcov*.info
/yjit-bench /yjit-bench
/yjit_exit_locations.dump /yjit_exit_locations.dump
# Rust
/target
# /wasm/ # /wasm/
/wasm/tests/*.wasm /wasm/tests/*.wasm

89
Cargo.lock generated Normal file
View File

@ -0,0 +1,89 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "capstone"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "015ef5d5ca1743e3f94af9509ba6bd2886523cfee46e48d15c2ef5216fd4ac9a"
dependencies = [
"capstone-sys",
"libc",
]
[[package]]
name = "capstone-sys"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2267cb8d16a1e4197863ec4284ffd1aec26fe7e57c58af46b02590a0235809a0"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "cc"
version = "1.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c"
dependencies = [
"shlex",
]
[[package]]
name = "dissimilar"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921"
[[package]]
name = "expect-test"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63af43ff4431e848fb47472a920f14fa71c24de13255a5692e93d4e90302acb0"
dependencies = [
"dissimilar",
"once_cell",
]
[[package]]
name = "jit"
version = "0.0.0"
dependencies = [
"yjit",
"zjit",
]
[[package]]
name = "libc"
version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "yjit"
version = "0.1.0"
dependencies = [
"capstone",
]
[[package]]
name = "zjit"
version = "0.0.1"
dependencies = [
"capstone",
"expect-test",
]

51
Cargo.toml Normal file
View File

@ -0,0 +1,51 @@
# Using Cargo's workspace feature to build all the Rust code in
# into a single package.
# TODO(alan) notes about rust version requirements. Undecided yet.
[workspace]
members = ["zjit", "yjit"]
[package]
name = "jit"
version = "0.0.0"
edition = "2024"
rust-version = "1.85.0"
publish = false # Don't publish to crates.io
[dependencies]
yjit = { path = "yjit", optional = true }
zjit = { path = "zjit", optional = true }
[lib]
crate-type = ["staticlib"]
path = "jit.rs"
[features]
disasm = []
runtime_checks = []
yjit = [ "dep:yjit" ]
zjit = [ "dep:zjit" ]
[profile.dev]
opt-level = 0
debug = true
debug-assertions = true
overflow-checks = true
[profile.dev_nodebug]
inherits = "dev"
[profile.stats]
inherits = "release"
[profile.release]
# NOTE: --enable-yjit and zjit 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
# Generate debug info
debug = true
# Use ThinLTO. Much smaller output for a small amount of build time increase.
lto = "thin"

View File

@ -187,10 +187,9 @@ COMMONOBJS = array.$(OBJEXT) \
weakmap.$(OBJEXT) \ weakmap.$(OBJEXT) \
$(PRISM_FILES) \ $(PRISM_FILES) \
$(YJIT_OBJ) \ $(YJIT_OBJ) \
$(YJIT_LIBOBJ) \
$(ZJIT_OBJ) \ $(ZJIT_OBJ) \
$(ZJIT_LIBOBJ) \
$(JIT_OBJ) \ $(JIT_OBJ) \
$(RUST_LIBOBJ) \
$(COROUTINE_OBJ) \ $(COROUTINE_OBJ) \
$(DTRACE_OBJ) \ $(DTRACE_OBJ) \
$(BUILTIN_ENCOBJS) \ $(BUILTIN_ENCOBJS) \
@ -346,7 +345,7 @@ YJIT_RUSTC_ARGS = --crate-name=yjit \
-C opt-level=3 \ -C opt-level=3 \
-C overflow-checks=on \ -C overflow-checks=on \
'--out-dir=$(CARGO_TARGET_DIR)/release/' \ '--out-dir=$(CARGO_TARGET_DIR)/release/' \
$(top_srcdir)/yjit/src/lib.rs '$(top_srcdir)/yjit/src/lib.rs'
ZJIT_RUSTC_ARGS = --crate-name=zjit \ ZJIT_RUSTC_ARGS = --crate-name=zjit \
--crate-type=staticlib \ --crate-type=staticlib \
@ -355,8 +354,8 @@ ZJIT_RUSTC_ARGS = --crate-name=zjit \
-C lto=thin \ -C lto=thin \
-C opt-level=3 \ -C opt-level=3 \
-C overflow-checks=on \ -C overflow-checks=on \
'--out-dir=$(ZJIT_CARGO_TARGET_DIR)/release/' \ '--out-dir=$(CARGO_TARGET_DIR)/release/' \
$(top_srcdir)/zjit/src/lib.rs '$(top_srcdir)/zjit/src/lib.rs'
all: $(SHOWFLAGS) main all: $(SHOWFLAGS) main
@ -736,8 +735,8 @@ clean-local:: clean-runnable
$(Q)$(RM) probes.h probes.$(OBJEXT) probes.stamp ruby-glommed.$(OBJEXT) ruby.imp ChangeLog $(STATIC_RUBY)$(EXEEXT) $(Q)$(RM) probes.h probes.$(OBJEXT) probes.stamp ruby-glommed.$(OBJEXT) ruby.imp ChangeLog $(STATIC_RUBY)$(EXEEXT)
$(Q)$(RM) GNUmakefile.old Makefile.old $(arch)-fake.rb bisect.sh $(ENC_TRANS_D) builtin_binary.inc $(Q)$(RM) GNUmakefile.old Makefile.old $(arch)-fake.rb bisect.sh $(ENC_TRANS_D) builtin_binary.inc
$(Q)$(RM) $(PRISM_BUILD_DIR)/.time $(PRISM_BUILD_DIR)/*/.time yjit_exit_locations.dump $(Q)$(RM) $(PRISM_BUILD_DIR)/.time $(PRISM_BUILD_DIR)/*/.time yjit_exit_locations.dump
-$(Q)$(RMALL) yjit/target -$(Q)$(RMALL) target
-$(Q) $(RMDIR) enc/jis enc/trans enc $(COROUTINE_H:/Context.h=) coroutine yjit \ -$(Q) $(RMDIR) enc/jis enc/trans enc $(COROUTINE_H:/Context.h=) coroutine target \
$(PRISM_BUILD_DIR)/*/ $(PRISM_BUILD_DIR) tmp \ $(PRISM_BUILD_DIR)/*/ $(PRISM_BUILD_DIR) tmp \
2> $(NULL) || $(NULLCMD) 2> $(NULL) || $(NULLCMD)

View File

@ -3924,46 +3924,33 @@ AC_ARG_ENABLE(yjit,
CARGO= CARGO=
CARGO_BUILD_ARGS= CARGO_BUILD_ARGS=
YJIT_LIBS= YJIT_LIBS=
JIT_CARGO_SUPPORT=no
AS_CASE(["${YJIT_SUPPORT}"], AS_CASE(["${YJIT_SUPPORT}"],
[yes|dev|stats|dev_nodebug], [ [yes|dev|stats|dev_nodebug], [
AS_IF([test x"$RUSTC" = "xno"], AS_IF([test x"$RUSTC" = "xno"],
AC_MSG_ERROR([rustc is required. Installation instructions available at https://www.rust-lang.org/tools/install]) AC_MSG_ERROR([rustc is required. Installation instructions available at https://www.rust-lang.org/tools/install])
) )
AS_IF([test x"$ZJIT_SUPPORT" != "xno"],
AC_MSG_ERROR([YJIT cannot be enabled when ZJIT is enabled])
)
AS_CASE(["${YJIT_SUPPORT}"], AS_CASE(["${YJIT_SUPPORT}"],
[yes], [ [yes], [
rb_rust_target_subdir=release
], ],
[dev], [ [dev], [
rb_rust_target_subdir=debug rb_cargo_features='disasm,runtime_checks'
CARGO_BUILD_ARGS='--features disasm,runtime_checks' JIT_CARGO_SUPPORT=dev
AC_DEFINE(RUBY_DEBUG, 1) AC_DEFINE(RUBY_DEBUG, 1)
], ],
[dev_nodebug], [ [dev_nodebug], [
rb_rust_target_subdir=dev_nodebug rb_cargo_features='disasm'
CARGO_BUILD_ARGS='--profile dev_nodebug --features disasm' JIT_CARGO_SUPPORT=dev_nodebug
AC_DEFINE(YJIT_STATS, 1) AC_DEFINE(YJIT_STATS, 1)
], ],
[stats], [ [stats], [
rb_rust_target_subdir=stats JIT_CARGO_SUPPORT=stats
CARGO_BUILD_ARGS='--profile stats'
AC_DEFINE(YJIT_STATS, 1) AC_DEFINE(YJIT_STATS, 1)
]) ])
AS_IF([test -n "${CARGO_BUILD_ARGS}"], [ YJIT_LIBS="target/release/libyjit.a"
AC_CHECK_TOOL(CARGO, [cargo], [no]) RUST_LIB='$(YJIT_LIBS)'
AS_IF([test x"$CARGO" = "xno"],
AC_MSG_ERROR([cargo is required. Installation instructions available at https://www.rust-lang.org/tools/install])
]))
YJIT_LIBS="yjit/target/${rb_rust_target_subdir}/libyjit.a"
AS_CASE(["$target_os"],[openbsd*],[
# Link libc++abi (which requires libpthread) for _Unwind_* functions needed by yjit
LDFLAGS="$LDFLAGS -lpthread -lc++abi"
])
YJIT_OBJ='yjit.$(OBJEXT)' YJIT_OBJ='yjit.$(OBJEXT)'
JIT_OBJ='jit.$(OBJEXT)' JIT_OBJ='jit.$(OBJEXT)'
AS_IF([test x"$YJIT_SUPPORT" != "xyes" ], [ AS_IF([test x"$YJIT_SUPPORT" != "xyes" ], [
@ -3974,38 +3961,23 @@ AS_CASE(["${YJIT_SUPPORT}"],
AC_DEFINE(USE_YJIT, 0) AC_DEFINE(USE_YJIT, 0)
]) ])
ZJIT_CARGO_BUILD_ARGS=
ZJIT_LIBS= ZJIT_LIBS=
AS_CASE(["${ZJIT_SUPPORT}"], AS_CASE(["${ZJIT_SUPPORT}"],
[yes|dev], [ [yes|dev], [
AS_IF([test x"$RUSTC" = "xno"], AS_IF([test x"$RUSTC" = "xno"],
AC_MSG_ERROR([rustc is required. Installation instructions available at https://www.rust-lang.org/tools/install]) AC_MSG_ERROR([rustc is required. Installation instructions available at https://www.rust-lang.org/tools/install])
) )
AS_IF([test x"$YJIT_SUPPORT" != "xno"],
AC_MSG_ERROR([ZJIT cannot be enabled when YJIT is enabled])
)
AS_CASE(["${ZJIT_SUPPORT}"], AS_CASE(["${ZJIT_SUPPORT}"],
[yes], [ [yes], [
rb_rust_target_subdir=release
], ],
[dev], [ [dev], [
rb_rust_target_subdir=debug JIT_CARGO_SUPPORT=dev
ZJIT_CARGO_BUILD_ARGS='--profile dev --features disasm'
AC_DEFINE(RUBY_DEBUG, 1) AC_DEFINE(RUBY_DEBUG, 1)
]) ])
AS_IF([test -n "${ZJIT_CARGO_BUILD_ARGS}"], [ ZJIT_LIBS="target/release/libzjit.a"
AC_CHECK_TOOL(CARGO, [cargo], [no]) RUST_LIB='$(ZJIT_LIBS)'
AS_IF([test x"$CARGO" = "xno"],
AC_MSG_ERROR([cargo is required. Installation instructions available at https://www.rust-lang.org/tools/install])
]))
ZJIT_LIBS="zjit/target/${rb_rust_target_subdir}/libzjit.a"
AS_CASE(["$target_os"],[openbsd*],[
# Link libc++abi (which requires libpthread) for _Unwind_* functions needed by yjit
LDFLAGS="$LDFLAGS -lpthread -lc++abi"
])
ZJIT_OBJ='zjit.$(OBJEXT)' ZJIT_OBJ='zjit.$(OBJEXT)'
JIT_OBJ='jit.$(OBJEXT)' JIT_OBJ='jit.$(OBJEXT)'
AS_IF([test x"$ZJIT_SUPPORT" != "xyes" ], [ AS_IF([test x"$ZJIT_SUPPORT" != "xyes" ], [
@ -4016,18 +3988,57 @@ AS_CASE(["${ZJIT_SUPPORT}"],
AC_DEFINE(USE_ZJIT, 0) AC_DEFINE(USE_ZJIT, 0)
]) ])
# if YJIT+ZJIT release build, or any build that requires Cargo
AS_IF([test x"$JIT_CARGO_SUPPORT" != "xno" -o \( x"$YJIT_SUPPORT" != "xno" -a x"$ZJIT_SUPPORT" != "xno" \)], [
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]))
YJIT_LIBS=
ZJIT_LIBS=
AS_IF([test x"${YJIT_SUPPORT}" != x"no"], [
rb_cargo_features="$rb_cargo_features,yjit"
])
AS_IF([test x"${ZJIT_SUPPORT}" != x"no"], [
rb_cargo_features="$rb_cargo_features,zjit"
])
# if YJIT and ZJIT release mode
AS_IF([test "${YJIT_SUPPORT}:${ZJIT_SUPPORT}" = "yes:yes"], [
JIT_CARGO_SUPPORT=release
])
CARGO_BUILD_ARGS="--profile ${JIT_CARGO_SUPPORT} --features ${rb_cargo_features}"
AS_IF([test "${JIT_CARGO_SUPPORT}" = "dev"], [
RUST_LIB="target/debug/libjit.a"
], [
RUST_LIB="target/${JIT_CARGO_SUPPORT}/libjit.a"
])
])
# In case either we're linking rust code
AS_IF([test -n "$RUST_LIB"], [
AS_CASE(["$target_os"],[openbsd*],[
# Link libc++abi (which requires libpthread) for _Unwind_* functions needed by rust stdlib
LDFLAGS="$LDFLAGS -lpthread -lc++abi"
])
# absolute path to stop the "target" dir in src dir from interfering through VPATH
RUST_LIB="$(pwd)/${RUST_LIB}"
])
dnl These variables end up in ::RbConfig::CONFIG 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(RUSTC)dnl Rust compiler command
AC_SUBST(CARGO)dnl Cargo command for Rust builds AC_SUBST(CARGO)dnl Cargo command for Rust builds
AC_SUBST(CARGO_BUILD_ARGS)dnl for selecting Rust build profiles AC_SUBST(CARGO_BUILD_ARGS)dnl for selecting Rust build profiles
AC_SUBST(ZJIT_CARGO_BUILD_ARGS)dnl for selecting Rust build profiles AC_SUBST(YJIT_SUPPORT)dnl what flavor of YJIT the Ruby build includes
AC_SUBST(YJIT_LIBS)dnl for optionally building the Rust parts of YJIT AC_SUBST(YJIT_LIBS)dnl the .a library of YJIT
AC_SUBST(YJIT_OBJ)dnl for optionally building the C parts of YJIT AC_SUBST(YJIT_OBJ)dnl for optionally building the C parts of YJIT
AC_SUBST(ZJIT_SUPPORT)dnl what flavor of ZJIT the Ruby build includes AC_SUBST(ZJIT_SUPPORT)dnl what flavor of ZJIT the Ruby build includes
AC_SUBST(ZJIT_LIBS)dnl for optionally building the Rust parts of ZJIT AC_SUBST(ZJIT_LIBS)dnl path to the .a library of ZJIT
AC_SUBST(ZJIT_OBJ)dnl for optionally building the C parts of ZJIT AC_SUBST(ZJIT_OBJ)dnl for optionally building the C parts of ZJIT
AC_SUBST(JIT_OBJ)dnl for optionally building C glue code for Rust FFI AC_SUBST(JIT_OBJ)dnl for optionally building C glue code for Rust FFI
AC_SUBST(RUST_LIB)dnl path to the rust .a library that contains either or both JITs
AC_SUBST(JIT_CARGO_SUPPORT)dnl "no" or the cargo profile of the rust code
} }
[begin]_group "build section" && { [begin]_group "build section" && {

View File

@ -443,6 +443,7 @@ endif
include $(top_srcdir)/yjit/yjit.mk include $(top_srcdir)/yjit/yjit.mk
include $(top_srcdir)/zjit/zjit.mk include $(top_srcdir)/zjit/zjit.mk
include $(top_srcdir)/defs/jit.mk
# Query on the generated rdoc # Query on the generated rdoc
# #

53
defs/jit.mk Normal file
View File

@ -0,0 +1,53 @@
# Make recipes that deal with the rust code of YJIT and ZJIT.
# Because of Cargo cache, if the actual binary is not changed from the
# previous build, the mtime is preserved as the cached file.
# This means the target is not updated actually, and it will need to
# rebuild at the next build.
RUST_LIB_TOUCH = touch $@
ifneq ($(JIT_CARGO_SUPPORT),no)
$(RUST_LIB):
$(Q)if [ '$(ZJIT_SUPPORT)' != no -a '$(YJIT_SUPPORT)' != no ]; then \
echo 'building YJIT and ZJIT ($(JIT_CARGO_SUPPORT:yes=release) mode)'; \
elif [ '$(ZJIT_SUPPORT)' != no ]; then \
echo 'building ZJIT ($(JIT_CARGO_SUPPORT) mode)'; \
elif [ '$(YJIT_SUPPORT)' != no ]; then \
echo 'building YJIT ($(JIT_CARGO_SUPPORT) mode)'; \
fi
+$(Q)CARGO_TARGET_DIR='$(CARGO_TARGET_DIR)' \
CARGO_TERM_PROGRESS_WHEN='never' \
$(CARGO) $(CARGO_VERBOSE) build --manifest-path '$(top_srcdir)/Cargo.toml' $(CARGO_BUILD_ARGS)
$(RUST_LIB_TOUCH)
endif
RUST_LIB_SYMBOLS = $(RUST_LIB:.a=).symbols
$(RUST_LIBOBJ): $(RUST_LIB)
$(ECHO) 'partial linking $(RUST_LIB) into $@'
ifneq ($(findstring darwin,$(target_os)),)
$(Q) $(CC) -nodefaultlibs -r -o $@ -exported_symbols_list $(RUST_LIB_SYMBOLS) $(RUST_LIB)
else
$(Q) $(LD) -r -o $@ --whole-archive $(RUST_LIB)
-$(Q) $(OBJCOPY) --wildcard --keep-global-symbol='$(SYMBOL_PREFIX)rb_*' $(@)
endif
rust-libobj: $(RUST_LIBOBJ)
rust-lib: $(RUST_LIB)
# For Darwin only: a list of symbols that we want the glommed Rust static lib to export.
# Unfortunately, using wildcard like '_rb_*' with -exported-symbol does not work, at least
# not on version 820.1. Assume llvm-nm, so XCode 8.0 (from 2016) or newer.
#
# The -exported_symbols_list pulls out the right archive members. Symbols not listed
# in the list are made private extern, which are in turn made local as we're using `ld -r`.
# Note, section about -keep_private_externs in ld's man page hints at this behavior on which
# we rely.
ifneq ($(findstring darwin,$(target_os)),)
$(RUST_LIB_SYMBOLS): $(RUST_LIB)
$(Q) $(tooldir)/darwin-ar $(NM) --defined-only --extern-only $(RUST_LIB) | \
sed -n -e 's/.* //' -e '/^$(SYMBOL_PREFIX)rb_/p' \
-e '/^$(SYMBOL_PREFIX)rust_eh_personality/p' \
> $@
$(RUST_LIBOBJ): $(RUST_LIB_SYMBOLS)
endif

View File

@ -35,3 +35,5 @@ default = []
# When moving an object, clear its original copy. # When moving an object, clear its original copy.
clear_old_copy = [] clear_old_copy = []
[workspace]

4
jit.rs Normal file
View File

@ -0,0 +1,4 @@
#[cfg(feature = "yjit")]
pub use yjit::*;
#[cfg(feature = "zjit")]
pub use zjit::*;

View File

@ -107,15 +107,14 @@ JIT_OBJ=@JIT_OBJ@
YJIT_SUPPORT=@YJIT_SUPPORT@ YJIT_SUPPORT=@YJIT_SUPPORT@
YJIT_LIBS=@YJIT_LIBS@ YJIT_LIBS=@YJIT_LIBS@
YJIT_OBJ=@YJIT_OBJ@ YJIT_OBJ=@YJIT_OBJ@
YJIT_LIBOBJ = $(YJIT_LIBS:.a=.@OBJEXT@)
ZJIT_SUPPORT=@ZJIT_SUPPORT@ ZJIT_SUPPORT=@ZJIT_SUPPORT@
ZJIT_LIBS=@ZJIT_LIBS@ ZJIT_LIBS=@ZJIT_LIBS@
ZJIT_OBJ=@ZJIT_OBJ@ ZJIT_OBJ=@ZJIT_OBJ@
ZJIT_LIBOBJ = $(ZJIT_LIBS:.a=.@OBJEXT@) JIT_CARGO_SUPPORT=@JIT_CARGO_SUPPORT@
CARGO_TARGET_DIR=@abs_top_builddir@/yjit/target CARGO_TARGET_DIR=@abs_top_builddir@/target
CARGO_BUILD_ARGS=@CARGO_BUILD_ARGS@ CARGO_BUILD_ARGS=@CARGO_BUILD_ARGS@
ZJIT_CARGO_BUILD_ARGS=@ZJIT_CARGO_BUILD_ARGS@ RUST_LIB=@RUST_LIB@
ZJIT_CARGO_TARGET_DIR=@abs_top_builddir@/zjit/target RUST_LIBOBJ = $(RUST_LIB:.a=.@OBJEXT@)
LDFLAGS = @STATIC@ $(CFLAGS) @LDFLAGS@ LDFLAGS = @STATIC@ $(CFLAGS) @LDFLAGS@
EXE_LDFLAGS = $(LDFLAGS) EXE_LDFLAGS = $(LDFLAGS)
EXTLDFLAGS = @EXTLDFLAGS@ EXTLDFLAGS = @EXTLDFLAGS@

4
vm.c
View File

@ -452,7 +452,9 @@ jit_compile(rb_execution_context_t *ec)
rb_zjit_compile_iseq(iseq, ec, false); rb_zjit_compile_iseq(iseq, ec, false);
} }
} }
#elif USE_YJIT #endif
#if USE_YJIT
// Increment the ISEQ's call counter and trigger JIT compilation if not compiled // Increment the ISEQ's call counter and trigger JIT compilation if not compiled
if (body->jit_entry == NULL && rb_yjit_enabled_p) { if (body->jit_entry == NULL && rb_yjit_enabled_p) {
body->jit_entry_calls++; body->jit_entry_calls++;

View File

@ -9,9 +9,6 @@ edition = "2021" # Rust 2021 edition to compile with
rust-version = "1.58.0" # Minimally supported rust version rust-version = "1.58.0" # Minimally supported rust version
publish = false # Don't publish to crates.io publish = false # Don't publish to crates.io
[lib]
crate-type = ["staticlib"]
[dependencies] [dependencies]
# No required dependencies to simplify build process. TODO: Link to yet to be # No required dependencies to simplify build process. TODO: Link to yet to be
# written rationale. Optional For development and testing purposes # written rationale. Optional For development and testing purposes
@ -27,27 +24,3 @@ disasm = ["capstone"]
# from cfg!(debug_assertions) so that we can see disasm of the code # from cfg!(debug_assertions) so that we can see disasm of the code
# that would run in the release mode. # that would run in the release mode.
runtime_checks = [] runtime_checks = []
[profile.dev]
opt-level = 0
debug = true
debug-assertions = true
overflow-checks = true
[profile.dev_nodebug]
inherits = "dev"
[profile.stats]
inherits = "release"
[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
# Generate debug info
debug = true
# Use ThinLTO. Much smaller output for a small amount of build time increase.
lto = "thin"

View File

@ -8,3 +8,5 @@ edition = "2021"
[dependencies] [dependencies]
bindgen = "0.70.1" bindgen = "0.70.1"
env_logger = "0.11.5" env_logger = "0.11.5"
[workspace]

View File

@ -19,60 +19,21 @@ YJIT_SRC_FILES = $(wildcard \
# rebuild at the next build. # rebuild at the next build.
YJIT_LIB_TOUCH = touch $@ YJIT_LIB_TOUCH = touch $@
# Absolute path to match RUST_LIB rules to avoid picking
# the "target" dir in the source directory through VPATH.
BUILD_YJIT_LIBS = $(TOP_BUILD_DIR)/$(YJIT_LIBS)
# YJIT_SUPPORT=yes when `configure` gets `--enable-yjit` # YJIT_SUPPORT=yes when `configure` gets `--enable-yjit`
ifeq ($(YJIT_SUPPORT),yes) ifeq ($(YJIT_SUPPORT),yes)
$(YJIT_LIBS): $(YJIT_SRC_FILES) yjit-libs: $(BUILD_YJIT_LIBS)
$(BUILD_YJIT_LIBS): $(YJIT_SRC_FILES)
$(ECHO) 'building Rust YJIT (release mode)' $(ECHO) 'building Rust YJIT (release mode)'
+$(Q) $(RUSTC) $(YJIT_RUSTC_ARGS) +$(Q) $(RUSTC) $(YJIT_RUSTC_ARGS)
$(YJIT_LIB_TOUCH) $(YJIT_LIB_TOUCH)
else ifeq ($(YJIT_SUPPORT),no)
$(YJIT_LIBS):
$(ECHO) 'Error: Tried to build YJIT without configuring it first. Check `make showconfig`?'
@false
else ifeq ($(YJIT_SUPPORT),$(filter dev dev_nodebug stats,$(YJIT_SUPPORT)))
# NOTE: MACOSX_DEPLOYMENT_TARGET to match `rustc --print deployment-target` to avoid the warning below.
# ld: warning: object file (yjit/target/debug/libyjit.a(<libcapstone object>)) was built for
# newer macOS version (15.2) than being linked (15.0)
# We don't use newer macOS feature as of yet.
$(YJIT_LIBS): $(YJIT_SRC_FILES)
$(ECHO) 'building Rust YJIT ($(YJIT_SUPPORT) mode)'
+$(Q)$(CHDIR) $(top_srcdir)/yjit && \
CARGO_TARGET_DIR='$(CARGO_TARGET_DIR)' \
CARGO_TERM_PROGRESS_WHEN='never' \
MACOSX_DEPLOYMENT_TARGET=11.0 \
$(CARGO) $(CARGO_VERBOSE) build $(CARGO_BUILD_ARGS)
$(YJIT_LIB_TOUCH)
else
endif endif
yjit-libobj: $(YJIT_LIBOBJ) ifneq ($(YJIT_SUPPORT),no)
$(RUST_LIB): $(YJIT_SRC_FILES)
YJIT_LIB_SYMBOLS = $(YJIT_LIBS:.a=).symbols
$(YJIT_LIBOBJ): $(YJIT_LIBS)
$(ECHO) 'partial linking $(YJIT_LIBS) into $@'
ifneq ($(findstring darwin,$(target_os)),)
$(Q) $(CC) -nodefaultlibs -r -o $@ -exported_symbols_list $(YJIT_LIB_SYMBOLS) $(YJIT_LIBS)
else
$(Q) $(LD) -r -o $@ --whole-archive $(YJIT_LIBS)
-$(Q) $(OBJCOPY) --wildcard --keep-global-symbol='$(SYMBOL_PREFIX)rb_*' $(@)
endif
# For Darwin only: a list of symbols that we want the glommed Rust static lib to export.
# Unfortunately, using wildcard like '_rb_*' with -exported-symbol does not work, at least
# not on version 820.1. Assume llvm-nm, so XCode 8.0 (from 2016) or newer.
#
# The -exported_symbols_list pulls out the right archive members. Symbols not listed
# in the list are made private extern, which are in turn made local as we're using `ld -r`.
# Note, section about -keep_private_externs in ld's man page hints at this behavior on which
# we rely.
ifneq ($(findstring darwin,$(target_os)),)
$(YJIT_LIB_SYMBOLS): $(YJIT_LIBS)
$(Q) $(tooldir)/darwin-ar $(NM) --defined-only --extern-only $(YJIT_LIBS) | \
sed -n -e 's/.* //' -e '/^$(SYMBOL_PREFIX)rb_/p' \
-e '/^$(SYMBOL_PREFIX)rust_eh_personality/p' \
> $@
$(YJIT_LIBOBJ): $(YJIT_LIB_SYMBOLS)
endif endif
# By using YJIT_BENCH_OPTS instead of RUN_OPTS, you can skip passing the options to `make install` # By using YJIT_BENCH_OPTS instead of RUN_OPTS, you can skip passing the options to `make install`
@ -94,7 +55,7 @@ RUST_VERSION = +1.58.0
.PHONY: yjit-smoke-test .PHONY: yjit-smoke-test
yjit-smoke-test: yjit-smoke-test:
ifneq ($(strip $(CARGO)),) ifneq ($(strip $(CARGO)),)
$(CARGO) $(RUST_VERSION) test --all-features -q --manifest-path='$(top_srcdir)/yjit/Cargo.toml' $(CARGO) test --all-features -q --manifest-path='$(top_srcdir)/yjit/Cargo.toml'
endif endif
$(MAKE) btest RUN_OPTS='--yjit-call-threshold=1' BTESTS=-j $(MAKE) btest RUN_OPTS='--yjit-call-threshold=1' BTESTS=-j
$(MAKE) test-all TESTS='$(top_srcdir)/test/ruby/test_yjit.rb' $(MAKE) test-all TESTS='$(top_srcdir)/test/ruby/test_yjit.rb'

1
zjit.c
View File

@ -9,6 +9,7 @@
#include "internal/numeric.h" #include "internal/numeric.h"
#include "internal/gc.h" #include "internal/gc.h"
#include "internal/vm.h" #include "internal/vm.h"
#include "yjit.h"
#include "vm_core.h" #include "vm_core.h"
#include "vm_callinfo.h" #include "vm_callinfo.h"
#include "builtin.h" #include "builtin.h"

View File

@ -1,25 +1,10 @@
[package] [package]
name = "zjit" name = "zjit"
version = "0.0.1" version = "0.0.1"
edition = "2024" # Rust 2021 edition to compile with edition = "2024"
rust-version = "1.85.0" # Minimally supported rust version rust-version = "1.85.0" # Minimally supported rust version
publish = false # Don't publish to crates.io publish = false # Don't publish to crates.io
[lib]
crate-type = ["staticlib"]
[profile.release]
# NOTE: --enable-zjit 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
# Generate debug info
debug = true
# Use ThinLTO. Much smaller output for a small amount of build time increase.
lto = "thin"
[dependencies] [dependencies]
# No required dependencies to simplify build process. TODO: Link to yet to be # No required dependencies to simplify build process. TODO: Link to yet to be
# written rationale. Optional For development and testing purposes # written rationale. Optional For development and testing purposes

View File

@ -8,3 +8,5 @@ edition = "2024"
[dependencies] [dependencies]
bindgen = "0.71.1" bindgen = "0.71.1"
env_logger = "0.11.5" env_logger = "0.11.5"
[workspace]

View File

@ -1,7 +1,8 @@
fn main() { fn main() {
use std::env; use std::env;
if let Ok(ruby_build_dir) = env::var("RUBY_BUILD_DIR") { // option_env! automatically registers a rerun-if-env-changed
if let Some(ruby_build_dir) = option_env!("RUBY_BUILD_DIR") {
// Link against libminiruby // Link against libminiruby
println!("cargo:rustc-link-search=native={ruby_build_dir}"); println!("cargo:rustc-link-search=native={ruby_build_dir}");
println!("cargo:rustc-link-lib=static:-bundle=miniruby"); println!("cargo:rustc-link-lib=static:-bundle=miniruby");

View File

@ -19,60 +19,20 @@ ZJIT_SRC_FILES = $(wildcard \
# rebuild at the next build. # rebuild at the next build.
ZJIT_LIB_TOUCH = touch $@ ZJIT_LIB_TOUCH = touch $@
# Absolute path to match RUST_LIB rules to avoid picking
# the "target" dir in the source directory through VPATH.
BUILD_ZJIT_LIBS = $(TOP_BUILD_DIR)/$(ZJIT_LIBS)
# ZJIT_SUPPORT=yes when `configure` gets `--enable-zjit` # ZJIT_SUPPORT=yes when `configure` gets `--enable-zjit`
ifeq ($(ZJIT_SUPPORT),yes) ifeq ($(ZJIT_SUPPORT),yes)
$(ZJIT_LIBS): $(ZJIT_SRC_FILES) $(BUILD_ZJIT_LIBS): $(ZJIT_SRC_FILES)
$(ECHO) 'building Rust ZJIT (release mode)' $(ECHO) 'building Rust ZJIT (release mode)'
+$(Q) $(RUSTC) $(ZJIT_RUSTC_ARGS) +$(Q) $(RUSTC) $(ZJIT_RUSTC_ARGS)
$(ZJIT_LIB_TOUCH) $(ZJIT_LIB_TOUCH)
else ifeq ($(ZJIT_SUPPORT),no)
$(ZJIT_LIBS):
$(ECHO) 'Error: Tried to build ZJIT without configuring it first. Check `make showconfig`?'
@false
else ifeq ($(ZJIT_SUPPORT),$(filter dev dev_nodebug stats,$(ZJIT_SUPPORT)))
# NOTE: MACOSX_DEPLOYMENT_TARGET to match `rustc --print deployment-target` to avoid the warning below.
# ld: warning: object file (zjit/target/debug/libzjit.a(<libcapstone object>)) was built for
# newer macOS version (15.2) than being linked (15.0)
# We don't use newer macOS feature as of yet.
$(ZJIT_LIBS): $(ZJIT_SRC_FILES)
$(ECHO) 'building Rust ZJIT ($(ZJIT_SUPPORT) mode)'
+$(Q)$(CHDIR) $(top_srcdir)/zjit && \
CARGO_TARGET_DIR='$(ZJIT_CARGO_TARGET_DIR)' \
CARGO_TERM_PROGRESS_WHEN='never' \
MACOSX_DEPLOYMENT_TARGET=11.0 \
$(CARGO) $(CARGO_VERBOSE) build $(ZJIT_CARGO_BUILD_ARGS)
$(ZJIT_LIB_TOUCH)
else
endif endif
zjit-libobj: $(ZJIT_LIBOBJ) ifneq ($(ZJIT_SUPPORT),no)
$(RUST_LIB): $(ZJIT_SRC_FILES)
ZJIT_LIB_SYMBOLS = $(ZJIT_LIBS:.a=).symbols
$(ZJIT_LIBOBJ): $(ZJIT_LIBS)
$(ECHO) 'partial linking $(ZJIT_LIBS) into $@'
ifneq ($(findstring darwin,$(target_os)),)
$(Q) $(CC) -nodefaultlibs -r -o $@ -exported_symbols_list $(ZJIT_LIB_SYMBOLS) $(ZJIT_LIBS)
else
$(Q) $(LD) -r -o $@ --whole-archive $(ZJIT_LIBS)
-$(Q) $(OBJCOPY) --wildcard --keep-global-symbol='$(SYMBOL_PREFIX)rb_*' $(@)
endif
# For Darwin only: a list of symbols that we want the glommed Rust static lib to export.
# Unfortunately, using wildcard like '_rb_*' with -exported-symbol does not work, at least
# not on version 820.1. Assume llvm-nm, so XCode 8.0 (from 2016) or newer.
#
# The -exported_symbols_list pulls out the right archive members. Symbols not listed
# in the list are made private extern, which are in turn made local as we're using `ld -r`.
# Note, section about -keep_private_externs in ld's man page hints at this behavior on which
# we rely.
ifneq ($(findstring darwin,$(target_os)),)
$(ZJIT_LIB_SYMBOLS): $(ZJIT_LIBS)
$(Q) $(tooldir)/darwin-ar $(NM) --defined-only --extern-only $(ZJIT_LIBS) | \
sed -n -e 's/.* //' -e '/^$(SYMBOL_PREFIX)rb_/p' \
-e '/^$(SYMBOL_PREFIX)rust_eh_personality/p' \
> $@
$(ZJIT_LIBOBJ): $(ZJIT_LIB_SYMBOLS)
endif endif
# By using ZJIT_BENCH_OPTS instead of RUN_OPTS, you can skip passing the options to `make install` # By using ZJIT_BENCH_OPTS instead of RUN_OPTS, you can skip passing the options to `make install`
@ -113,7 +73,7 @@ zjit-bindgen: zjit.$(OBJEXT)
zjit-test: libminiruby.a zjit-test: libminiruby.a
RUBY_BUILD_DIR='$(TOP_BUILD_DIR)' \ RUBY_BUILD_DIR='$(TOP_BUILD_DIR)' \
RUBY_LD_FLAGS='$(LDFLAGS) $(XLDFLAGS) $(MAINLIBS)' \ RUBY_LD_FLAGS='$(LDFLAGS) $(XLDFLAGS) $(MAINLIBS)' \
CARGO_TARGET_DIR='$(ZJIT_CARGO_TARGET_DIR)' \ CARGO_TARGET_DIR='$(CARGO_TARGET_DIR)' \
$(CARGO) nextest run --manifest-path '$(top_srcdir)/zjit/Cargo.toml' $(ZJIT_TESTS) $(CARGO) nextest run --manifest-path '$(top_srcdir)/zjit/Cargo.toml' $(ZJIT_TESTS)
# Run a ZJIT test written with Rust #[test] under LLDB # Run a ZJIT test written with Rust #[test] under LLDB
@ -126,7 +86,7 @@ zjit-test-lldb: libminiruby.a
fi; \ fi; \
exe_path=`RUBY_BUILD_DIR='$(TOP_BUILD_DIR)' \ exe_path=`RUBY_BUILD_DIR='$(TOP_BUILD_DIR)' \
RUBY_LD_FLAGS='$(LDFLAGS) $(XLDFLAGS) $(MAINLIBS)' \ RUBY_LD_FLAGS='$(LDFLAGS) $(XLDFLAGS) $(MAINLIBS)' \
CARGO_TARGET_DIR='$(ZJIT_CARGO_TARGET_DIR)' \ CARGO_TARGET_DIR='$(CARGO_TARGET_DIR)' \
$(CARGO) nextest list --manifest-path '$(top_srcdir)/zjit/Cargo.toml' --message-format json --list-type=binaries-only | \ $(CARGO) nextest list --manifest-path '$(top_srcdir)/zjit/Cargo.toml' --message-format json --list-type=binaries-only | \
$(BASERUBY) -rjson -e 'puts JSON.load(STDIN.read).dig("rust-binaries", "zjit", "binary-path")'`; \ $(BASERUBY) -rjson -e 'puts JSON.load(STDIN.read).dig("rust-binaries", "zjit", "binary-path")'`; \
exec lldb $$exe_path -- --test-threads=1 $(ZJIT_TESTS) exec lldb $$exe_path -- --test-threads=1 $(ZJIT_TESTS)