diff --git a/Cargo.toml b/Cargo.toml index ec2ce880ca..521129d92d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,15 @@ -# 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. +# This is the root Cargo [workspace](https://doc.rust-lang.org/cargo/reference/workspaces.html) +# and the root package for all the rust code that are statically linked into ruby. Rust tooling +# limitations means all Rust code need to share a single archive library (staticlib) at the +# integration point with non-rust code. (See rustlang/rust#44322 and #104707 for a taste of +# the linking challenges.) +# +# Do not add required dependencies. This is a policy that helps downstream consumers and give +# us tight control over what we ship. All of the optional dependencies are used exclusively +# during development. +# +# Release builds avoid Cargo entirely because offline builds can fail even when none of the +# optional dependencies are built (rust-lang/cargo#10352). [workspace] members = ["zjit", "yjit", "jit"] diff --git a/common.mk b/common.mk index 35c2315980..50abfeab8a 100644 --- a/common.mk +++ b/common.mk @@ -268,9 +268,8 @@ MAKE_LINK = $(MINIRUBY) -rfileutils -e "include FileUtils::Verbose" \ # For release builds YJIT_RUSTC_ARGS = --crate-name=yjit \ - --crate-type=staticlib \ + $(JIT_RUST_FLAGS) \ --edition=2021 \ - --cfg 'feature="stats_allocator"' \ -g \ -C lto=thin \ -C opt-level=3 \ @@ -279,9 +278,8 @@ YJIT_RUSTC_ARGS = --crate-name=yjit \ '$(top_srcdir)/yjit/src/lib.rs' ZJIT_RUSTC_ARGS = --crate-name=zjit \ - --crate-type=staticlib \ + $(JIT_RUST_FLAGS) \ --edition=2024 \ - --cfg 'feature="stats_allocator"' \ -g \ -C lto=thin \ -C opt-level=3 \ diff --git a/configure.ac b/configure.ac index 14b0234ef0..ef4b76e537 100644 --- a/configure.ac +++ b/configure.ac @@ -3896,7 +3896,6 @@ AC_SUBST(INSTALL_STATIC_LIBRARY) [begin]_group "JIT section" && { AC_CHECK_PROG(RUSTC, [rustc], [rustc], [no]) dnl no ac_tool_prefix -AC_CHECK_TOOL(CARGO, [cargo], [no]) dnl check if rustc is recent enough to build YJIT (rustc >= 1.58.0) JIT_RUSTC_OK=no @@ -3963,11 +3962,7 @@ AC_ARG_ENABLE(zjit, # 1.85.0 is the first stable version that supports the 2024 edition. AS_IF([test "$RUSTC" != "no" && echo "#[cfg(target_arch = \"$JIT_TARGET_ARCH\")] fn main() {}" | $RUSTC - --edition=2024 --emit asm=/dev/null 2>/dev/null], - AS_IF([test "$gnumake" = "yes" -a \( "$YJIT_SUPPORT" = "no" -o "$CARGO" != "no" \)], [ - # When only building ZJIT, we don't need cargo; it's required for YJIT+ZJIT build. - # Assume that if rustc is new enough, then cargo is also. - # TODO(alan): Get rid of dependency on cargo in YJIT+ZJIT build. Cargo's offline mode - # still too unreliable: https://github.com/rust-lang/cargo/issues/10352 + AS_IF([test "$gnumake" = "yes"], [ rb_zjit_build_possible=yes ]) ) @@ -4053,36 +4048,49 @@ AS_CASE(["${ZJIT_SUPPORT}"], 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" \)], [ - AS_IF([test x"$CARGO" = "xno"], - AC_MSG_ERROR([this build configuration requires cargo. Installation instructions available at https://www.rust-lang.org/tools/install])) +JIT_RUST_FLAGS='--crate-type=staticlib --cfg feature=\"stats_allocator\"' +RLIB_DIR= +AS_CASE(["$JIT_CARGO_SUPPORT:$YJIT_SUPPORT:$ZJIT_SUPPORT"], + [no:yes:yes], [ # release build of YJIT+ZJIT + YJIT_LIBS= + ZJIT_LIBS= + JIT_RUST_FLAGS="--crate-type=rlib" + RLIB_DIR="target/release" + RUST_LIB="target/release/libruby.a" + ], + [no:*], [], + [*], [ # JIT_CARGO_SUPPORT not "no" -- cargo required. + AC_CHECK_TOOL(CARGO, [cargo], [no]) + AS_IF([test x"$CARGO" = "xno"], + AC_MSG_ERROR([this build configuration requires cargo. Installation instructions available at https://www.rust-lang.org/tools/install])) - YJIT_LIBS= - ZJIT_LIBS= + YJIT_LIBS= + ZJIT_LIBS= - # There's more processing below to get the feature set for the - # top-level crate, so capture at this point for feature set of - # just the zjit crate. - ZJIT_TEST_FEATURES="${rb_cargo_features}" + # There's more processing below to get the feature set for the + # top-level crate, so capture at this point for feature set of + # just the zjit crate. + ZJIT_TEST_FEATURES="${rb_cargo_features}" + + AS_IF([test x"${YJIT_SUPPORT}" != x"no"], [ + rb_cargo_features="$rb_cargo_features,yjit" + ]) + AS_IF([test x"${ZJIT_SUPPORT}" != x"no"], [ + AC_SUBST(ZJIT_TEST_FEATURES) + 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/libruby.a" + ], [ + RUST_LIB="target/${JIT_CARGO_SUPPORT}/libruby.a" + ]) + ], - AS_IF([test x"${YJIT_SUPPORT}" != x"no"], [ - rb_cargo_features="$rb_cargo_features,yjit" - ]) - AS_IF([test x"${ZJIT_SUPPORT}" != x"no"], [ - AC_SUBST(ZJIT_TEST_FEATURES) - 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/libruby.a" - ], [ - RUST_LIB="target/${JIT_CARGO_SUPPORT}/libruby.a" - ]) ]) # In case either we're linking rust code @@ -4098,6 +4106,7 @@ AS_IF([test -n "$RUST_LIB"], [ dnl These variables end up in ::RbConfig::CONFIG AC_SUBST(RUSTC)dnl Rust compiler command +AC_SUBST(JIT_RUST_FLAGS)dnl the common rustc flags for JIT crates such as zjit AC_SUBST(CARGO)dnl Cargo command for Rust builds AC_SUBST(CARGO_BUILD_ARGS)dnl for selecting Rust build profiles AC_SUBST(YJIT_SUPPORT)dnl what flavor of YJIT the Ruby build includes @@ -4108,6 +4117,7 @@ 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(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(RLIB_DIR)dnl subpath of build directory for .rlib files AC_SUBST(JIT_CARGO_SUPPORT)dnl "no" or the cargo profile of the rust code } diff --git a/defs/jit.mk b/defs/jit.mk index 67cc6fdce7..e893098ca2 100644 --- a/defs/jit.mk +++ b/defs/jit.mk @@ -30,6 +30,31 @@ $(RUST_LIB): $(srcdir)/ruby.rs MACOSX_DEPLOYMENT_TARGET=11.0 \ $(CARGO) $(CARGO_VERBOSE) build --manifest-path '$(top_srcdir)/Cargo.toml' $(CARGO_BUILD_ARGS) $(RUST_LIB_TOUCH) +else ifneq ($(strip $(RLIB_DIR)),) # combo build + +$(RUST_LIB): $(srcdir)/ruby.rs + $(ECHO) 'building $(@F)' + $(Q) $(RUSTC) --edition=2024 \ + '-L$(@D)' \ + --extern=yjit \ + --extern=zjit \ + --crate-type=staticlib \ + --cfg 'feature="yjit"' \ + --cfg 'feature="zjit"' \ + '--out-dir=$(@D)' \ + '$(top_srcdir)/ruby.rs' + +# Absolute path to avoid VPATH ambiguity +JIT_RLIB = $(TOP_BUILD_DIR)/$(RLIB_DIR)/libjit.rlib +$(YJIT_RLIB): $(JIT_RLIB) +$(ZJIT_RLIB): $(JIT_RLIB) +$(JIT_RLIB): + $(ECHO) 'building $(@F)' + $(Q) $(RUSTC) --crate-name=jit \ + --edition=2024 \ + $(JIT_RUST_FLAGS) \ + '--out-dir=$(@D)' \ + '$(top_srcdir)/jit/src/lib.rs' endif # ifneq ($(JIT_CARGO_SUPPORT),no) RUST_LIB_SYMBOLS = $(RUST_LIB:.a=).symbols diff --git a/template/Makefile.in b/template/Makefile.in index 7bc40b9d01..fd41ddca9d 100644 --- a/template/Makefile.in +++ b/template/Makefile.in @@ -114,6 +114,8 @@ JIT_CARGO_SUPPORT=@JIT_CARGO_SUPPORT@ CARGO_TARGET_DIR=@abs_top_builddir@/target CARGO_BUILD_ARGS=@CARGO_BUILD_ARGS@ ZJIT_TEST_FEATURES=@ZJIT_TEST_FEATURES@ +JIT_RUST_FLAGS=@JIT_RUST_FLAGS@ +RLIB_DIR=@RLIB_DIR@ RUST_LIB=@RUST_LIB@ RUST_LIBOBJ = $(RUST_LIB:.a=.@OBJEXT@) LDFLAGS = @STATIC@ $(CFLAGS) @LDFLAGS@ diff --git a/yjit/Cargo.toml b/yjit/Cargo.toml index e2f1d84ffd..d3124e608c 100644 --- a/yjit/Cargo.toml +++ b/yjit/Cargo.toml @@ -10,8 +10,8 @@ rust-version = "1.58.0" # Minimally supported rust version publish = false # Don't publish to crates.io [dependencies] -# No required dependencies to simplify build process. TODO: Link to yet to be -# written rationale. Optional For development and testing purposes +# No required dependencies to simplify build process. +# Optional For development and testing purposes. capstone = { version = "0.13.0", optional = true } jit = { version = "0.1.0", path = "../jit" } diff --git a/yjit/yjit.mk b/yjit/yjit.mk index 777dcdd43e..22d256fee7 100644 --- a/yjit/yjit.mk +++ b/yjit/yjit.mk @@ -19,7 +19,16 @@ yjit-libs: $(BUILD_YJIT_LIBS) $(BUILD_YJIT_LIBS): $(YJIT_SRC_FILES) $(ECHO) 'building Rust YJIT (release mode)' $(Q) $(RUSTC) $(YJIT_RUSTC_ARGS) -endif +else ifneq ($(strip $(RLIB_DIR)),) # combo build +# Absolute path to avoid VPATH ambiguity +YJIT_RLIB = $(TOP_BUILD_DIR)/$(RLIB_DIR)/libyjit.rlib + +$(YJIT_RLIB): $(YJIT_SRC_FILES) + $(ECHO) 'building $(@F)' + $(Q) $(RUSTC) '-L$(@D)' --extern=jit $(YJIT_RUSTC_ARGS) + +$(RUST_LIB): $(YJIT_RLIB) +endif # ifneq ($(strip $(YJIT_LIBS)),) ifneq ($(YJIT_SUPPORT),no) $(RUST_LIB): $(YJIT_SRC_FILES) diff --git a/zjit/Cargo.toml b/zjit/Cargo.toml index c97c845a6e..ef656c5252 100644 --- a/zjit/Cargo.toml +++ b/zjit/Cargo.toml @@ -6,8 +6,8 @@ rust-version = "1.85.0" # Minimally supported rust version publish = false # Don't publish to crates.io [dependencies] -# No required dependencies to simplify build process. TODO: Link to yet to be -# written rationale. Optional For development and testing purposes +# No required dependencies to simplify build process. +# Optional For development and testing purposes. capstone = { version = "0.13.0", optional = true } jit = { version = "0.1.0", path = "../jit" } diff --git a/zjit/zjit.mk b/zjit/zjit.mk index 68fc5778ec..2116775a91 100644 --- a/zjit/zjit.mk +++ b/zjit/zjit.mk @@ -23,7 +23,16 @@ ifneq ($(strip $(ZJIT_LIBS)),) $(BUILD_ZJIT_LIBS): $(ZJIT_SRC_FILES) $(ECHO) 'building Rust ZJIT (release mode)' $(Q) $(RUSTC) $(ZJIT_RUSTC_ARGS) -endif +else ifneq ($(strip $(RLIB_DIR)),) # combo build +# Absolute path to avoid VPATH ambiguity +ZJIT_RLIB = $(TOP_BUILD_DIR)/$(RLIB_DIR)/libzjit.rlib + +$(ZJIT_RLIB): $(ZJIT_SRC_FILES) + $(ECHO) 'building $(@F)' + $(Q) $(RUSTC) '-L$(@D)' --extern=jit $(ZJIT_RUSTC_ARGS) + +$(RUST_LIB): $(ZJIT_RLIB) +endif # ifneq ($(strip $(ZJIT_LIBS)),) # By using ZJIT_BENCH_OPTS instead of RUN_OPTS, you can skip passing the options to `make install` ZJIT_BENCH_OPTS = $(RUN_OPTS) --enable-gems