Allow enabling YJIT and RJIT independently (#7474)

We used to require MJIT is supported when YJIT is supported. However,
now that RJIT dropped some platforms that YJIT supports, it no longer
makes sense. We should be able to enable only YJIT, and vice versa.
This commit is contained in:
Takashi Kokubun 2023-03-07 22:43:37 -08:00 коммит произвёл GitHub
Родитель 0bf4cd8e1c
Коммит 6d91df08b5
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
9 изменённых файлов: 742 добавлений и 504 удалений

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

@ -197,8 +197,10 @@ jobs:
# - { name: VM_DEBUG_BP_CHECK, env: { cppflags: '-DVM_DEBUG_BP_CHECK' } }
# - { name: VM_DEBUG_VERIFY_METHOD_CACHE, env: { cppflags: '-DVM_DEBUG_VERIFY_METHOD_CACHE' } }
- { name: enable-yjit, env: { append_configure: '--enable-yjit --disable-rjit' }, rust: true }
- { name: enable-rjit, env: { append_configure: '--enable-rjit --disable-yjit' } }
- { name: YJIT_FORCE_ENABLE, env: { cppflags: '-DYJIT_FORCE_ENABLE' }, rust: true }
- { name: RJIT_FORCE_ENABLE, env: { cppflags: '-DRJIT_FORCE_ENABLE' } }
- { name: YJIT_FORCE_ENABLE, env: { cppflags: '-DYJIT_FORCE_ENABLE' } }
name: ${{ matrix.entry.name }}
runs-on: ubuntu-latest
@ -222,6 +224,9 @@ jobs:
with:
path: src/.downloaded-cache
key: downloaded-cache
- name: Install Rust
if: ${{ matrix.entry.rust }}
run: sudo apt-get update && sudo apt install -y rustc
- name: autogen
run: |
if [ ! -f ./autogen.sh ]; then

925
common.mk

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

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

@ -3725,16 +3725,6 @@ AC_SUBST(CAPITARGET)
AS_CASE(["$RDOCTARGET:$CAPITARGET"],[nodoc:nodoc],[INSTALLDOC=nodoc],[INSTALLDOC=all])
AC_SUBST(INSTALLDOC)
AC_ARG_ENABLE(jit-support,
AS_HELP_STRING([--disable-jit-support], [disable JIT features]),
[RJIT_SUPPORT=$enableval],
[AS_CASE(["$target_os"],
[wasi | mingw* | solaris*], [RJIT_SUPPORT=no],
[RJIT_SUPPORT=yes]
)])
AC_SUBST(RJIT_SUPPORT)
AC_CHECK_PROG(RUSTC, [rustc], [rustc], [no]) dnl no ac_tool_prefix
dnl check if rustc is recent enough to build YJIT (rustc >= 1.58.0)
@ -3780,8 +3770,8 @@ AC_ARG_ENABLE(yjit,
AS_HELP_STRING([--enable-yjit],
[enable in-process JIT compiler that requires Rust build tools. enabled by default on supported platforms if rustc 1.58.0+ is available]),
[YJIT_SUPPORT=$enableval],
[AS_CASE(["$enable_jit_support:$YJIT_TARGET_OK:$YJIT_RUSTC_OK"],
[yes:yes:yes|:yes:yes], [
[AS_CASE(["$YJIT_TARGET_OK:$YJIT_RUSTC_OK"],
[yes:yes], [
YJIT_SUPPORT=yes
],
[YJIT_SUPPORT=no]
@ -3793,9 +3783,6 @@ CARGO_BUILD_ARGS=
YJIT_LIBS=
AS_CASE(["${YJIT_SUPPORT}"],
[yes|dev|stats|dev_nodebug], [
AS_IF([test x"$enable_jit_support" = "xno"],
AC_MSG_ERROR([--disable-jit-support but --enable-yjit. YJIT requires JIT support])
)
AS_IF([test x"$RUSTC" = "xno"],
AC_MSG_ERROR([rustc is required. Installation instructions available at https://www.rust-lang.org/tools/install])
)
@ -3835,16 +3822,8 @@ AS_CASE(["${YJIT_SUPPORT}"],
AC_DEFINE_UNQUOTED(YJIT_SUPPORT, [$YJIT_SUPPORT])
])
AC_DEFINE(USE_YJIT, 1)
AC_DEFINE(USE_RJIT, 1)
], [
AC_DEFINE(USE_YJIT, 0)
AC_DEFINE(USE_RJIT, 0)
])
# If YJIT links capstone, libcapstone stops working on the C side.
# capstone should be linked for RJIT only when YJIT doesn't.
AS_IF([test x"$RJIT_SUPPORT" = "xyes" -a -z "$CARGO_BUILD_ARGS" ], [
AC_CHECK_LIB([capstone], [cs_disasm])
])
dnl These variables end up in ::RbConfig::CONFIG
@ -3855,6 +3834,58 @@ 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
dnl Currently, RJIT only supports Unix x86_64 platforms.
RJIT_TARGET_OK=no
AS_IF([test "$cross_compiling" = no],
AS_CASE(["$target_cpu-$target_os"],
[*android*], [
RJIT_TARGET_OK=no
],
[x86_64-darwin*], [
RJIT_TARGET_OK=yes
],
[x86_64-*linux*], [
RJIT_TARGET_OK=yes
],
[x86_64-*bsd*], [
RJIT_TARGET_OK=yes
]
)
)
dnl Build RJIT on Unix x86_64 platforms or if --enable-rjit is specified.
AC_ARG_ENABLE(rjit,
AS_HELP_STRING([--enable-rjit],
[enable pure-Ruby JIT compiler. enabled by default on Unix x86_64 platforms]),
[RJIT_SUPPORT=$enableval],
[AS_CASE(["$YJIT_TARGET_OK"],
[yes], [RJIT_SUPPORT=yes],
[RJIT_SUPPORT=no]
)]
)
AS_CASE(["${RJIT_SUPPORT}"],
[yes|dev], [
AS_CASE(["${RJIT_SUPPORT}"],
[dev], [
# Link libcapstone for --rjit-dump-disasm. If YJIT links libcapstone
# with cargo, linking libcapstone here doesn't work. It should be
# linked for RJIT only when YJIT doesn't.
AS_IF([test -z "$CARGO_BUILD_ARGS"], [
AC_CHECK_LIB([capstone], [cs_disasm])
])
# Enable RJIT_STATS (vm_insns_count of --rjit-stats)
AC_DEFINE(RUBY_DEBUG, 1)
])
AC_DEFINE(USE_RJIT, 1)
], [
AC_DEFINE(USE_RJIT, 0)
])
AC_SUBST(RJIT_SUPPORT)
AC_ARG_ENABLE(install-static-library,
AS_HELP_STRING([--disable-install-static-library], [do not install static ruby library]),
[INSTALL_STATIC_LIBRARY=$enableval
@ -4541,8 +4572,8 @@ config_summary "debugflags" "$debugflags"
config_summary "warnflags" "$warnflags"
config_summary "strip command" "$STRIP"
config_summary "install doc" "$DOCTARGETS"
config_summary "RJIT support" "$RJIT_SUPPORT"
config_summary "YJIT support" "$YJIT_SUPPORT"
config_summary "RJIT support" "$RJIT_SUPPORT"
config_summary "man page type" "$MANTYPE"
config_summary "search path" "$search_path"
config_summary "static-linked-ext" ${EXTSTATIC:+"yes"}

108
rjit.c
Просмотреть файл

@ -56,6 +56,12 @@ void rb_rjit(void) {}
#endif
#include "dln.h"
// For mmapp(), sysconf()
#ifndef _WIN32
#include <unistd.h>
#include <sys/mman.h>
#endif
#include "ruby/util.h"
// A copy of RJIT portion of MRI options since RJIT initialization. We
@ -261,6 +267,105 @@ rjit_compile(FILE *f, const rb_iseq_t *iseq, const char *funcname, int id)
// New stuff from here
//
#if defined(MAP_FIXED_NOREPLACE) && defined(_SC_PAGESIZE)
// Align the current write position to a multiple of bytes
static uint8_t *
align_ptr(uint8_t *ptr, uint32_t multiple)
{
// Compute the pointer modulo the given alignment boundary
uint32_t rem = ((uint32_t)(uintptr_t)ptr) % multiple;
// If the pointer is already aligned, stop
if (rem == 0)
return ptr;
// Pad the pointer by the necessary amount to align it
uint32_t pad = multiple - rem;
return ptr + pad;
}
#endif
// Address space reservation. Memory pages are mapped on an as needed basis.
// See the Rust mm module for details.
static uint8_t *
rb_mjit_reserve_addr_space(uint32_t mem_size)
{
#ifndef _WIN32
uint8_t *mem_block;
// On Linux
#if defined(MAP_FIXED_NOREPLACE) && defined(_SC_PAGESIZE)
uint32_t const page_size = (uint32_t)sysconf(_SC_PAGESIZE);
uint8_t *const cfunc_sample_addr = (void *)&rb_mjit_reserve_addr_space;
uint8_t *const probe_region_end = cfunc_sample_addr + INT32_MAX;
// Align the requested address to page size
uint8_t *req_addr = align_ptr(cfunc_sample_addr, page_size);
// Probe for addresses close to this function using MAP_FIXED_NOREPLACE
// to improve odds of being in range for 32-bit relative call instructions.
do {
mem_block = mmap(
req_addr,
mem_size,
PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE,
-1,
0
);
// If we succeeded, stop
if (mem_block != MAP_FAILED) {
break;
}
// +4MB
req_addr += 4 * 1024 * 1024;
} while (req_addr < probe_region_end);
// On MacOS and other platforms
#else
// Try to map a chunk of memory as executable
mem_block = mmap(
(void *)rb_mjit_reserve_addr_space,
mem_size,
PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0
);
#endif
// Fallback
if (mem_block == MAP_FAILED) {
// Try again without the address hint (e.g., valgrind)
mem_block = mmap(
NULL,
mem_size,
PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0
);
}
// Check that the memory mapping was successful
if (mem_block == MAP_FAILED) {
perror("ruby: yjit: mmap:");
if(errno == ENOMEM) {
// No crash report if it's only insufficient memory
exit(EXIT_FAILURE);
}
rb_bug("mmap failed");
}
return mem_block;
#else
// Windows not supported for now
return NULL;
#endif
}
// JIT buffer
uint8_t *rb_rjit_mem_block = NULL;
@ -485,8 +590,7 @@ rjit_init(const struct rjit_options *opts)
VM_ASSERT(rjit_enabled);
rjit_opts = *opts;
extern uint8_t* rb_yjit_reserve_addr_space(uint32_t mem_size);
rb_rjit_mem_block = rb_yjit_reserve_addr_space(RJIT_CODE_SIZE);
rb_rjit_mem_block = rb_mjit_reserve_addr_space(RJIT_CODE_SIZE);
// RJIT doesn't support miniruby, but it might reach here by RJIT_FORCE_ENABLE.
rb_mRJIT = rb_const_get(rb_cRubyVM, rb_intern("RJIT"));

11
rjit.h
Просмотреть файл

@ -86,12 +86,12 @@ struct rb_rjit_compile_info {
bool disable_const_cache;
};
typedef VALUE (*jit_func_t)(rb_execution_context_t *, rb_control_frame_t *);
RUBY_SYMBOL_EXPORT_BEGIN
RUBY_EXTERN struct rjit_options rjit_opts;
RUBY_EXTERN bool rjit_call_p;
#define rb_rjit_call_threshold() rjit_opts.call_threshold
extern void rb_rjit_compile(const rb_iseq_t *iseq);
extern struct rb_rjit_compile_info* rb_rjit_iseq_compile_info(const struct rb_iseq_constant_body *body);
extern void rb_rjit_recompile_send(const rb_iseq_t *iseq);
@ -132,10 +132,11 @@ void rjit_finish(bool close_handle_p);
# else // USE_RJIT
static inline void rb_rjit_compile(const rb_iseq_t *iseq){}
static inline void rjit_cancel_all(const char *reason){}
static inline void rjit_free_iseq(const rb_iseq_t *iseq){}
static inline void rjit_mark(void){}
static inline VALUE jit_exec(rb_execution_context_t *ec) { return Qundef; /* unreachable */ }
static inline void rjit_child_after_fork(void){}
static inline void rb_rjit_bop_redefined(int redefined_flag, enum ruby_basic_operators bop) {}
@ -146,7 +147,11 @@ static inline void rb_rjit_constant_ic_update(const rb_iseq_t *const iseq, IC ic
static inline void rb_rjit_tracing_invalidate_all(rb_event_flag_t new_iseq_events) {}
#define rjit_enabled false
#define rjit_call_p false
#define rjit_stats_enabled false
#define rb_rjit_call_threshold() UINT_MAX
static inline VALUE rjit_pause(bool wait_p){ return Qnil; } // unreachable
static inline VALUE rjit_resume(void){ return Qnil; } // unreachable
static inline void rjit_finish(bool close_handle_p){}

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

@ -23,10 +23,92 @@ void rb_rjit_c(void) {}
#include "internal/gc.h"
#include "yjit.h"
#include "vm_insnhelper.h"
#include "probes.h"
#include "probes_helper.h"
#include "insns.inc"
#include "insns_info.inc"
// For mmapp(), sysconf()
#ifndef _WIN32
#include <unistd.h>
#include <sys/mman.h>
#endif
#include <errno.h>
bool
rb_rjit_mark_writable(void *mem_block, uint32_t mem_size)
{
return mprotect(mem_block, mem_size, PROT_READ | PROT_WRITE) == 0;
}
void
rb_rjit_mark_executable(void *mem_block, uint32_t mem_size)
{
// Do not call mprotect when mem_size is zero. Some platforms may return
// an error for it. https://github.com/Shopify/ruby/issues/450
if (mem_size == 0) {
return;
}
if (mprotect(mem_block, mem_size, PROT_READ | PROT_EXEC)) {
rb_bug("Couldn't make JIT page (%p, %lu bytes) executable, errno: %s\n",
mem_block, (unsigned long)mem_size, strerror(errno));
}
}
VALUE
rb_rjit_optimized_call(VALUE *recv, rb_execution_context_t *ec, int argc, VALUE *argv, int kw_splat, VALUE block_handler)
{
rb_proc_t *proc;
GetProcPtr(recv, proc);
return rb_vm_invoke_proc(ec, proc, argc, argv, kw_splat, block_handler);
}
VALUE
rb_rjit_str_neq_internal(VALUE str1, VALUE str2)
{
return rb_str_eql_internal(str1, str2) == Qtrue ? Qfalse : Qtrue;
}
// The code we generate in gen_send_cfunc() doesn't fire the c_return TracePoint event
// like the interpreter. When tracing for c_return is enabled, we patch the code after
// the C method return to call into this to fire the event.
void
rb_rjit_full_cfunc_return(rb_execution_context_t *ec, VALUE return_value)
{
rb_control_frame_t *cfp = ec->cfp;
RUBY_ASSERT_ALWAYS(cfp == GET_EC()->cfp);
const rb_callable_method_entry_t *me = rb_vm_frame_method_entry(cfp);
RUBY_ASSERT_ALWAYS(RUBYVM_CFUNC_FRAME_P(cfp));
RUBY_ASSERT_ALWAYS(me->def->type == VM_METHOD_TYPE_CFUNC);
// CHECK_CFP_CONSISTENCY("full_cfunc_return"); TODO revive this
// Pop the C func's frame and fire the c_return TracePoint event
// Note that this is the same order as vm_call_cfunc_with_frame().
rb_vm_pop_frame(ec);
EXEC_EVENT_HOOK(ec, RUBY_EVENT_C_RETURN, cfp->self, me->def->original_id, me->called_id, me->owner, return_value);
// Note, this deviates from the interpreter in that users need to enable
// a c_return TracePoint for this DTrace hook to work. A reasonable change
// since the Ruby return event works this way as well.
RUBY_DTRACE_CMETHOD_RETURN_HOOK(ec, me->owner, me->def->original_id);
// Push return value into the caller's stack. We know that it's a frame that
// uses cfp->sp because we are patching a call done with gen_send_cfunc().
ec->cfp->sp[0] = return_value;
ec->cfp->sp++;
}
rb_proc_t *
rb_rjit_get_proc_ptr(VALUE procv)
{
rb_proc_t *proc;
GetProcPtr(procv, proc);
return proc;
}
#if SIZEOF_LONG == SIZEOF_VOIDP
#define NUM2PTR(x) NUM2ULONG(x)
#define PTR2NUM(x) ULONG2NUM(x)

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

@ -11,16 +11,16 @@ module RubyVM::RJIT # :nodoc: all
#
def rjit_mark_writable
Primitive.cstmt! %{
extern bool rb_yjit_mark_writable(void *mem_block, uint32_t mem_size);
rb_yjit_mark_writable(rb_rjit_mem_block, RJIT_CODE_SIZE);
extern bool rb_rjit_mark_writable(void *mem_block, uint32_t mem_size);
rb_rjit_mark_writable(rb_rjit_mem_block, RJIT_CODE_SIZE);
return Qnil;
}
end
def rjit_mark_executable
Primitive.cstmt! %{
extern bool rb_yjit_mark_executable(void *mem_block, uint32_t mem_size);
rb_yjit_mark_executable(rb_rjit_mem_block, RJIT_CODE_SIZE);
extern void rb_rjit_mark_executable(void *mem_block, uint32_t mem_size);
rb_rjit_mark_executable(rb_rjit_mem_block, RJIT_CODE_SIZE);
return Qnil;
}
end
@ -147,8 +147,8 @@ module RubyVM::RJIT # :nodoc: all
def rb_full_cfunc_return
Primitive.cstmt! %{
extern void rb_full_cfunc_return(rb_execution_context_t *ec, VALUE return_value);
return SIZET2NUM((size_t)rb_full_cfunc_return);
extern void rb_rjit_full_cfunc_return(rb_execution_context_t *ec, VALUE return_value);
return SIZET2NUM((size_t)rb_rjit_full_cfunc_return);
}
end
@ -177,8 +177,8 @@ module RubyVM::RJIT # :nodoc: all
def rb_str_neq_internal
Primitive.cstmt! %{
extern VALUE rb_str_neq_internal(VALUE str1, VALUE str2);
return SIZET2NUM((size_t)rb_str_neq_internal);
extern VALUE rb_rjit_str_neq_internal(VALUE str1, VALUE str2);
return SIZET2NUM((size_t)rb_rjit_str_neq_internal);
}
end
@ -398,8 +398,8 @@ module RubyVM::RJIT # :nodoc: all
def rb_optimized_call
Primitive.cstmt! %{
extern VALUE rb_optimized_call(VALUE *recv, rb_execution_context_t *ec, int argc, VALUE *argv, int kw_splat, VALUE block_handler);
return SIZET2NUM((size_t)rb_optimized_call);
extern VALUE rb_rjit_optimized_call(VALUE *recv, rb_execution_context_t *ec, int argc, VALUE *argv, int kw_splat, VALUE block_handler);
return SIZET2NUM((size_t)rb_rjit_optimized_call);
}
end
@ -414,8 +414,8 @@ module RubyVM::RJIT # :nodoc: all
def rb_yjit_get_proc_ptr(proc_addr)
proc_t_addr = Primitive.cstmt! %{
extern rb_proc_t * rb_yjit_get_proc_ptr(VALUE procv);
return SIZET2NUM((size_t)rb_yjit_get_proc_ptr((VALUE)NUM2SIZET(proc_addr)));
extern rb_proc_t * rb_rjit_get_proc_ptr(VALUE procv);
return SIZET2NUM((size_t)rb_rjit_get_proc_ptr((VALUE)NUM2SIZET(proc_addr)));
}
rb_proc_t.new(proc_t_addr)
end

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

@ -403,7 +403,7 @@ jit_exec(rb_execution_context_t *ec)
}
}
else { // rjit_call_p
if (body->total_calls == rjit_opts.call_threshold) {
if (body->total_calls == rb_rjit_call_threshold()) {
rb_rjit_compile(iseq);
}
if ((func = body->jit_func) == 0) {
@ -414,6 +414,12 @@ jit_exec(rb_execution_context_t *ec)
// Call the JIT code
return func(ec, ec->cfp); // SystemV x64 calling convention: ec -> RDI, cfp -> RSI
}
#else
static inline VALUE
jit_exec(rb_execution_context_t *ec)
{
return Qundef;
}
#endif
#include "vm_insnhelper.c"

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

@ -521,6 +521,8 @@ struct rb_iseq_constant_body {
#endif
};
typedef VALUE (*jit_func_t)(struct rb_execution_context_struct *, struct rb_control_frame_struct *);
/* T_IMEMO/iseq */
/* typedef rb_iseq_t is in method.h */
struct rb_iseq_struct {