mirror of
https://github.com/ruby/ruby.git
synced 2025-08-15 13:39:04 +02:00
ZJIT: Prepare for sharing JIT hooks with ZJIT (#14044)
This commit is contained in:
parent
4263c49d1c
commit
2cd10de330
20 changed files with 142 additions and 118 deletions
2
array.rb
2
array.rb
|
@ -212,7 +212,7 @@ class Array
|
||||||
indexes
|
indexes
|
||||||
end
|
end
|
||||||
|
|
||||||
with_yjit do
|
with_jit do
|
||||||
if Primitive.rb_builtin_basic_definition_p(:each)
|
if Primitive.rb_builtin_basic_definition_p(:each)
|
||||||
undef :each
|
undef :each
|
||||||
|
|
||||||
|
|
|
@ -1236,8 +1236,9 @@ BUILTIN_RB_SRCS = \
|
||||||
$(srcdir)/nilclass.rb \
|
$(srcdir)/nilclass.rb \
|
||||||
$(srcdir)/prelude.rb \
|
$(srcdir)/prelude.rb \
|
||||||
$(srcdir)/gem_prelude.rb \
|
$(srcdir)/gem_prelude.rb \
|
||||||
|
$(srcdir)/jit_hook.rb \
|
||||||
|
$(srcdir)/jit_undef.rb \
|
||||||
$(srcdir)/yjit.rb \
|
$(srcdir)/yjit.rb \
|
||||||
$(srcdir)/yjit_hook.rb \
|
|
||||||
$(srcdir)/zjit.rb \
|
$(srcdir)/zjit.rb \
|
||||||
$(empty)
|
$(empty)
|
||||||
BUILTIN_RB_INCS = $(BUILTIN_RB_SRCS:.rb=.rbinc)
|
BUILTIN_RB_INCS = $(BUILTIN_RB_SRCS:.rb=.rbinc)
|
||||||
|
|
6
depend
6
depend
|
@ -9196,6 +9196,8 @@ miniinit.$(OBJEXT): {$(VPATH)}internal/warning_push.h
|
||||||
miniinit.$(OBJEXT): {$(VPATH)}internal/xmalloc.h
|
miniinit.$(OBJEXT): {$(VPATH)}internal/xmalloc.h
|
||||||
miniinit.$(OBJEXT): {$(VPATH)}io.rb
|
miniinit.$(OBJEXT): {$(VPATH)}io.rb
|
||||||
miniinit.$(OBJEXT): {$(VPATH)}iseq.h
|
miniinit.$(OBJEXT): {$(VPATH)}iseq.h
|
||||||
|
miniinit.$(OBJEXT): {$(VPATH)}jit_hook.rb
|
||||||
|
miniinit.$(OBJEXT): {$(VPATH)}jit_undef.rb
|
||||||
miniinit.$(OBJEXT): {$(VPATH)}kernel.rb
|
miniinit.$(OBJEXT): {$(VPATH)}kernel.rb
|
||||||
miniinit.$(OBJEXT): {$(VPATH)}marshal.rb
|
miniinit.$(OBJEXT): {$(VPATH)}marshal.rb
|
||||||
miniinit.$(OBJEXT): {$(VPATH)}method.h
|
miniinit.$(OBJEXT): {$(VPATH)}method.h
|
||||||
|
@ -9232,7 +9234,6 @@ miniinit.$(OBJEXT): {$(VPATH)}vm_core.h
|
||||||
miniinit.$(OBJEXT): {$(VPATH)}vm_opts.h
|
miniinit.$(OBJEXT): {$(VPATH)}vm_opts.h
|
||||||
miniinit.$(OBJEXT): {$(VPATH)}warning.rb
|
miniinit.$(OBJEXT): {$(VPATH)}warning.rb
|
||||||
miniinit.$(OBJEXT): {$(VPATH)}yjit.rb
|
miniinit.$(OBJEXT): {$(VPATH)}yjit.rb
|
||||||
miniinit.$(OBJEXT): {$(VPATH)}yjit_hook.rb
|
|
||||||
miniinit.$(OBJEXT): {$(VPATH)}zjit.rb
|
miniinit.$(OBJEXT): {$(VPATH)}zjit.rb
|
||||||
namespace.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h
|
namespace.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h
|
||||||
namespace.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
|
namespace.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
|
||||||
|
@ -18755,6 +18756,8 @@ vm.$(OBJEXT): {$(VPATH)}internal/variable.h
|
||||||
vm.$(OBJEXT): {$(VPATH)}internal/warning_push.h
|
vm.$(OBJEXT): {$(VPATH)}internal/warning_push.h
|
||||||
vm.$(OBJEXT): {$(VPATH)}internal/xmalloc.h
|
vm.$(OBJEXT): {$(VPATH)}internal/xmalloc.h
|
||||||
vm.$(OBJEXT): {$(VPATH)}iseq.h
|
vm.$(OBJEXT): {$(VPATH)}iseq.h
|
||||||
|
vm.$(OBJEXT): {$(VPATH)}jit_hook.rbinc
|
||||||
|
vm.$(OBJEXT): {$(VPATH)}jit_undef.rbinc
|
||||||
vm.$(OBJEXT): {$(VPATH)}method.h
|
vm.$(OBJEXT): {$(VPATH)}method.h
|
||||||
vm.$(OBJEXT): {$(VPATH)}missing.h
|
vm.$(OBJEXT): {$(VPATH)}missing.h
|
||||||
vm.$(OBJEXT): {$(VPATH)}node.h
|
vm.$(OBJEXT): {$(VPATH)}node.h
|
||||||
|
@ -18797,7 +18800,6 @@ vm.$(OBJEXT): {$(VPATH)}vm_opts.h
|
||||||
vm.$(OBJEXT): {$(VPATH)}vm_sync.h
|
vm.$(OBJEXT): {$(VPATH)}vm_sync.h
|
||||||
vm.$(OBJEXT): {$(VPATH)}vmtc.inc
|
vm.$(OBJEXT): {$(VPATH)}vmtc.inc
|
||||||
vm.$(OBJEXT): {$(VPATH)}yjit.h
|
vm.$(OBJEXT): {$(VPATH)}yjit.h
|
||||||
vm.$(OBJEXT): {$(VPATH)}yjit_hook.rbinc
|
|
||||||
vm.$(OBJEXT): {$(VPATH)}zjit.h
|
vm.$(OBJEXT): {$(VPATH)}zjit.h
|
||||||
vm_backtrace.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h
|
vm_backtrace.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h
|
||||||
vm_backtrace.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
|
vm_backtrace.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
|
||||||
|
|
7
inits.c
7
inits.c
|
@ -88,8 +88,10 @@ void
|
||||||
rb_call_builtin_inits(void)
|
rb_call_builtin_inits(void)
|
||||||
{
|
{
|
||||||
#define BUILTIN(n) CALL(builtin_##n)
|
#define BUILTIN(n) CALL(builtin_##n)
|
||||||
BUILTIN(kernel);
|
BUILTIN(jit_hook);
|
||||||
BUILTIN(yjit);
|
BUILTIN(yjit);
|
||||||
|
BUILTIN(zjit);
|
||||||
|
BUILTIN(kernel);
|
||||||
BUILTIN(gc);
|
BUILTIN(gc);
|
||||||
BUILTIN(ractor);
|
BUILTIN(ractor);
|
||||||
BUILTIN(numeric);
|
BUILTIN(numeric);
|
||||||
|
@ -107,8 +109,7 @@ rb_call_builtin_inits(void)
|
||||||
BUILTIN(thread_sync);
|
BUILTIN(thread_sync);
|
||||||
BUILTIN(nilclass);
|
BUILTIN(nilclass);
|
||||||
BUILTIN(marshal);
|
BUILTIN(marshal);
|
||||||
BUILTIN(zjit);
|
BUILTIN(jit_undef);
|
||||||
BUILTIN(yjit_hook);
|
|
||||||
Init_builtin_prelude();
|
Init_builtin_prelude();
|
||||||
}
|
}
|
||||||
#undef CALL
|
#undef CALL
|
||||||
|
|
|
@ -23,9 +23,6 @@ typedef struct ruby_cmdline_options {
|
||||||
ruby_features_t warn;
|
ruby_features_t warn;
|
||||||
unsigned int dump;
|
unsigned int dump;
|
||||||
long backtrace_length_limit;
|
long backtrace_length_limit;
|
||||||
#if USE_ZJIT
|
|
||||||
void *zjit;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const char *crash_report;
|
const char *crash_report;
|
||||||
|
|
||||||
|
@ -42,6 +39,9 @@ typedef struct ruby_cmdline_options {
|
||||||
#if USE_YJIT
|
#if USE_YJIT
|
||||||
unsigned int yjit: 1;
|
unsigned int yjit: 1;
|
||||||
#endif
|
#endif
|
||||||
|
#if USE_ZJIT
|
||||||
|
unsigned int zjit: 1;
|
||||||
|
#endif
|
||||||
} ruby_cmdline_options_t;
|
} ruby_cmdline_options_t;
|
||||||
|
|
||||||
struct ruby_opt_message {
|
struct ruby_opt_message {
|
||||||
|
|
13
jit_hook.rb
Normal file
13
jit_hook.rb
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
class Module
|
||||||
|
# Internal helper for built-in initializations to define methods only when JIT is enabled.
|
||||||
|
# This method is removed in jit_undef.rb.
|
||||||
|
private def with_jit(&block) # :nodoc:
|
||||||
|
# ZJIT currently doesn't compile Array#each properly, so it's disabled for now.
|
||||||
|
if defined?(RubyVM::ZJIT) && Primitive.rb_zjit_option_enabled_p && false # TODO: remove `&& false` (Shopify/ruby#667)
|
||||||
|
# We don't support lazily enabling ZJIT yet, so we can call the block right away.
|
||||||
|
block.call
|
||||||
|
elsif defined?(RubyVM::YJIT)
|
||||||
|
RubyVM::YJIT.send(:add_jit_hook, block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
4
jit_undef.rb
Normal file
4
jit_undef.rb
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# Remove the helper defined in jit_hook.rb
|
||||||
|
class Module
|
||||||
|
undef :with_jit
|
||||||
|
end
|
10
kernel.rb
10
kernel.rb
|
@ -291,13 +291,3 @@ module Kernel
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class Module
|
|
||||||
# Internal helper for built-in initializations to define methods only when YJIT is enabled.
|
|
||||||
# This method is removed in yjit_hook.rb.
|
|
||||||
private def with_yjit(&block) # :nodoc:
|
|
||||||
if defined?(RubyVM::YJIT)
|
|
||||||
RubyVM::YJIT.send(:add_yjit_hook, block)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
|
@ -322,7 +322,7 @@ class Integer
|
||||||
1
|
1
|
||||||
end
|
end
|
||||||
|
|
||||||
with_yjit do
|
with_jit do
|
||||||
if Primitive.rb_builtin_basic_definition_p(:downto)
|
if Primitive.rb_builtin_basic_definition_p(:downto)
|
||||||
undef :downto
|
undef :downto
|
||||||
|
|
||||||
|
|
19
ruby.c
19
ruby.c
|
@ -1196,14 +1196,12 @@ setup_yjit_options(const char *s)
|
||||||
|
|
||||||
#if USE_ZJIT
|
#if USE_ZJIT
|
||||||
static void
|
static void
|
||||||
setup_zjit_options(ruby_cmdline_options_t *opt, const char *s)
|
setup_zjit_options(const char *s)
|
||||||
{
|
{
|
||||||
// The option parsing is done in zjit/src/options.rs
|
// The option parsing is done in zjit/src/options.rs
|
||||||
extern void *rb_zjit_init_options(void);
|
extern bool rb_zjit_parse_option(const char *s);
|
||||||
extern bool rb_zjit_parse_option(void *options, const char *s);
|
|
||||||
|
|
||||||
if (!opt->zjit) opt->zjit = rb_zjit_init_options();
|
if (!rb_zjit_parse_option(s)) {
|
||||||
if (!rb_zjit_parse_option(opt->zjit, s)) {
|
|
||||||
rb_raise(rb_eRuntimeError, "invalid ZJIT option '%s' (--help will show valid zjit options)", s);
|
rb_raise(rb_eRuntimeError, "invalid ZJIT option '%s' (--help will show valid zjit options)", s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1481,7 +1479,7 @@ proc_long_options(ruby_cmdline_options_t *opt, const char *s, long argc, char **
|
||||||
else if (is_option_with_optarg("zjit", '-', true, false, false)) {
|
else if (is_option_with_optarg("zjit", '-', true, false, false)) {
|
||||||
#if USE_ZJIT
|
#if USE_ZJIT
|
||||||
FEATURE_SET(opt->features, FEATURE_BIT(zjit));
|
FEATURE_SET(opt->features, FEATURE_BIT(zjit));
|
||||||
setup_zjit_options(opt, s);
|
setup_zjit_options(s);
|
||||||
#else
|
#else
|
||||||
rb_warn("Ruby was built without ZJIT support."
|
rb_warn("Ruby was built without ZJIT support."
|
||||||
" You may need to install rustc to build Ruby with ZJIT.");
|
" You may need to install rustc to build Ruby with ZJIT.");
|
||||||
|
@ -1828,8 +1826,8 @@ ruby_opt_init(ruby_cmdline_options_t *opt)
|
||||||
#endif
|
#endif
|
||||||
#if USE_ZJIT
|
#if USE_ZJIT
|
||||||
if (opt->zjit) {
|
if (opt->zjit) {
|
||||||
extern void rb_zjit_init(void *options);
|
extern void rb_zjit_init(void);
|
||||||
rb_zjit_init(opt->zjit);
|
rb_zjit_init();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -2370,8 +2368,9 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt)
|
||||||
#endif
|
#endif
|
||||||
#if USE_ZJIT
|
#if USE_ZJIT
|
||||||
if (FEATURE_SET_P(opt->features, zjit) && !opt->zjit) {
|
if (FEATURE_SET_P(opt->features, zjit) && !opt->zjit) {
|
||||||
extern void *rb_zjit_init_options(void);
|
extern void rb_zjit_prepare_options(void);
|
||||||
opt->zjit = rb_zjit_init_options();
|
rb_zjit_prepare_options();
|
||||||
|
opt->zjit = true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -1084,6 +1084,13 @@ class TestZJIT < Test::Unit::TestCase
|
||||||
}, call_threshold: 2
|
}, call_threshold: 2
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_zjit_option_uses_array_each_in_ruby
|
||||||
|
omit 'ZJIT wrongly compiles Array#each, so it is disabled for now'
|
||||||
|
assert_runs '"<internal:array>"', %q{
|
||||||
|
Array.instance_method(:each).source_location&.first
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def test_profile_under_nested_jit_call
|
def test_profile_under_nested_jit_call
|
||||||
assert_compiles '[nil, nil, 3]', %q{
|
assert_compiles '[nil, nil, 3]', %q{
|
||||||
def profile
|
def profile
|
||||||
|
|
13
vm.c
13
vm.c
|
@ -4509,14 +4509,21 @@ Init_vm_objects(void)
|
||||||
vm->cc_refinement_table = rb_set_init_numtable();
|
vm->cc_refinement_table = rb_set_init_numtable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if USE_ZJIT
|
||||||
|
extern VALUE rb_zjit_option_enabled_p(rb_execution_context_t *ec, VALUE self);
|
||||||
|
#else
|
||||||
|
static VALUE rb_zjit_option_enabled_p(rb_execution_context_t *ec, VALUE self) { return Qfalse; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Whether JIT is enabled or not, we need to load/undef `#with_jit` for other builtins.
|
||||||
|
#include "jit_hook.rbinc"
|
||||||
|
#include "jit_undef.rbinc"
|
||||||
|
|
||||||
// Stub for builtin function when not building YJIT units
|
// Stub for builtin function when not building YJIT units
|
||||||
#if !USE_YJIT
|
#if !USE_YJIT
|
||||||
void Init_builtin_yjit(void) {}
|
void Init_builtin_yjit(void) {}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Whether YJIT is enabled or not, we load yjit_hook.rb to remove Kernel#with_yjit.
|
|
||||||
#include "yjit_hook.rbinc"
|
|
||||||
|
|
||||||
// Stub for builtin function when not building ZJIT units
|
// Stub for builtin function when not building ZJIT units
|
||||||
#if !USE_ZJIT
|
#if !USE_ZJIT
|
||||||
void Init_builtin_zjit(void) {}
|
void Init_builtin_zjit(void) {}
|
||||||
|
|
|
@ -775,7 +775,7 @@ rb_method_definition_set(const rb_method_entry_t *me, rb_method_definition_t *de
|
||||||
/* setup iseq first (before invoking GC) */
|
/* setup iseq first (before invoking GC) */
|
||||||
RB_OBJ_WRITE(me, &def->body.iseq.iseqptr, iseq);
|
RB_OBJ_WRITE(me, &def->body.iseq.iseqptr, iseq);
|
||||||
|
|
||||||
// Methods defined in `with_yjit` should be considered METHOD_ENTRY_BASIC
|
// Methods defined in `with_jit` should be considered METHOD_ENTRY_BASIC
|
||||||
if (rb_iseq_attr_p(iseq, BUILTIN_ATTR_C_TRACE)) {
|
if (rb_iseq_attr_p(iseq, BUILTIN_ATTR_C_TRACE)) {
|
||||||
METHOD_ENTRY_BASIC_SET((rb_method_entry_t *)me, TRUE);
|
METHOD_ENTRY_BASIC_SET((rb_method_entry_t *)me, TRUE);
|
||||||
}
|
}
|
||||||
|
|
14
yjit.rb
14
yjit.rb
|
@ -264,23 +264,23 @@ module RubyVM::YJIT
|
||||||
end
|
end
|
||||||
|
|
||||||
# Blocks that are called when YJIT is enabled
|
# Blocks that are called when YJIT is enabled
|
||||||
@yjit_hooks = []
|
@jit_hooks = []
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
# :stopdoc:
|
# :stopdoc:
|
||||||
private
|
private
|
||||||
|
|
||||||
# Register a block to be called when YJIT is enabled
|
# Register a block to be called when YJIT is enabled
|
||||||
def add_yjit_hook(hook)
|
def add_jit_hook(hook)
|
||||||
@yjit_hooks << hook
|
@jit_hooks << hook
|
||||||
end
|
end
|
||||||
|
|
||||||
# Run YJIT hooks registered by RubyVM::YJIT.with_yjit
|
# Run YJIT hooks registered by `#with_jit`
|
||||||
def call_yjit_hooks
|
def call_jit_hooks
|
||||||
# Skip using builtin methods in Ruby if --yjit-c-builtin is given
|
# Skip using builtin methods in Ruby if --yjit-c-builtin is given
|
||||||
return if Primitive.yjit_c_builtin_p
|
return if Primitive.yjit_c_builtin_p
|
||||||
@yjit_hooks.each(&:call)
|
@jit_hooks.each(&:call)
|
||||||
@yjit_hooks.clear
|
@jit_hooks.clear
|
||||||
end
|
end
|
||||||
|
|
||||||
# Print stats and dump exit locations
|
# Print stats and dump exit locations
|
||||||
|
|
|
@ -46,7 +46,7 @@ pub struct Options {
|
||||||
// The number of registers allocated for stack temps
|
// The number of registers allocated for stack temps
|
||||||
pub num_temp_regs: usize,
|
pub num_temp_regs: usize,
|
||||||
|
|
||||||
// Disable Ruby builtin methods defined by `with_yjit` hooks, e.g. Array#each in Ruby
|
// Disable Ruby builtin methods defined by `with_jit` hooks, e.g. Array#each in Ruby
|
||||||
pub c_builtin: bool,
|
pub c_builtin: bool,
|
||||||
|
|
||||||
// Capture stats
|
// Capture stats
|
||||||
|
|
|
@ -57,7 +57,7 @@ fn yjit_init() {
|
||||||
// Call YJIT hooks before enabling YJIT to avoid compiling the hooks themselves
|
// Call YJIT hooks before enabling YJIT to avoid compiling the hooks themselves
|
||||||
unsafe {
|
unsafe {
|
||||||
let yjit = rb_const_get(rb_cRubyVM, rust_str_to_id("YJIT"));
|
let yjit = rb_const_get(rb_cRubyVM, rust_str_to_id("YJIT"));
|
||||||
rb_funcall(yjit, rust_str_to_id("call_yjit_hooks"), 0);
|
rb_funcall(yjit, rust_str_to_id("call_jit_hooks"), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Catch panics to avoid UB for unwinding into C frames.
|
// Catch panics to avoid UB for unwinding into C frames.
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
# Remove the helper defined in kernel.rb
|
|
||||||
class Module
|
|
||||||
undef :with_yjit
|
|
||||||
end
|
|
|
@ -957,7 +957,7 @@ pub use manual_defs::*;
|
||||||
pub mod test_utils {
|
pub mod test_utils {
|
||||||
use std::{ptr::null, sync::Once};
|
use std::{ptr::null, sync::Once};
|
||||||
|
|
||||||
use crate::{options::init_options, state::rb_zjit_enabled_p, state::ZJITState};
|
use crate::{options::rb_zjit_prepare_options, state::rb_zjit_enabled_p, state::ZJITState};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
@ -979,6 +979,7 @@ pub mod test_utils {
|
||||||
// <https://github.com/Shopify/zjit/pull/37>, though
|
// <https://github.com/Shopify/zjit/pull/37>, though
|
||||||
let mut var: VALUE = Qnil;
|
let mut var: VALUE = Qnil;
|
||||||
ruby_init_stack(&mut var as *mut VALUE as *mut _);
|
ruby_init_stack(&mut var as *mut VALUE as *mut _);
|
||||||
|
rb_zjit_prepare_options(); // enable `#with_jit` on builtins
|
||||||
ruby_init();
|
ruby_init();
|
||||||
|
|
||||||
// Pass command line options so the VM loads core library methods defined in
|
// Pass command line options so the VM loads core library methods defined in
|
||||||
|
@ -994,7 +995,7 @@ pub mod test_utils {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up globals for convenience
|
// Set up globals for convenience
|
||||||
ZJITState::init(init_options());
|
ZJITState::init();
|
||||||
|
|
||||||
// Enable zjit_* instructions
|
// Enable zjit_* instructions
|
||||||
unsafe { rb_zjit_enabled_p = true; }
|
unsafe { rb_zjit_enabled_p = true; }
|
||||||
|
|
|
@ -16,9 +16,9 @@ pub static mut rb_zjit_profile_threshold: u64 = 1;
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
pub static mut rb_zjit_call_threshold: u64 = 2;
|
pub static mut rb_zjit_call_threshold: u64 = 2;
|
||||||
|
|
||||||
/// True if --zjit-stats is enabled.
|
/// ZJIT command-line options. This is set before rb_zjit_init() sets
|
||||||
#[allow(non_upper_case_globals)]
|
/// ZJITState so that we can query some options while loading builtins.
|
||||||
static mut zjit_stats_enabled_p: bool = false;
|
pub static mut OPTIONS: Option<Options> = None;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
|
@ -53,8 +53,8 @@ pub struct Options {
|
||||||
pub log_compiled_iseqs: Option<String>,
|
pub log_compiled_iseqs: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return an Options with default values
|
impl Default for Options {
|
||||||
pub fn init_options() -> Options {
|
fn default() -> Self {
|
||||||
Options {
|
Options {
|
||||||
num_profiles: 1,
|
num_profiles: 1,
|
||||||
stats: false,
|
stats: false,
|
||||||
|
@ -67,6 +67,7 @@ pub fn init_options() -> Options {
|
||||||
allowed_iseqs: None,
|
allowed_iseqs: None,
|
||||||
log_compiled_iseqs: None,
|
log_compiled_iseqs: None,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `ruby --help` descriptions for user-facing options. Do not add options for ZJIT developers.
|
/// `ruby --help` descriptions for user-facing options. Do not add options for ZJIT developers.
|
||||||
|
@ -95,28 +96,26 @@ macro_rules! get_option {
|
||||||
// Unsafe is ok here because options are initialized
|
// Unsafe is ok here because options are initialized
|
||||||
// once before any Ruby code executes
|
// once before any Ruby code executes
|
||||||
($option_name:ident) => {
|
($option_name:ident) => {
|
||||||
{
|
unsafe { crate::options::OPTIONS.as_ref() }.unwrap().$option_name
|
||||||
use crate::state::ZJITState;
|
|
||||||
ZJITState::get_options().$option_name
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub(crate) use get_option;
|
pub(crate) use get_option;
|
||||||
|
|
||||||
/// Allocate Options on the heap, initialize it, and return the address of it.
|
/// Set default values to ZJIT options. Setting Some to OPTIONS will make `#with_jit`
|
||||||
/// The return value will be modified by rb_zjit_parse_option() and then
|
/// enable the JIT hook while not enabling compilation yet.
|
||||||
/// passed to rb_zjit_init() for initialization.
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn rb_zjit_init_options() -> *const u8 {
|
pub extern "C" fn rb_zjit_prepare_options() {
|
||||||
let options = init_options();
|
// rb_zjit_prepare_options() could be called for feature flags or $RUBY_ZJIT_ENABLE
|
||||||
Box::into_raw(Box::new(options)) as *const u8
|
// after rb_zjit_parse_option() is called, so we need to handle the already-initialized case.
|
||||||
|
if unsafe { OPTIONS.is_none() } {
|
||||||
|
unsafe { OPTIONS = Some(Options::default()); }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a --zjit* command-line flag
|
/// Parse a --zjit* command-line flag
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn rb_zjit_parse_option(options: *const u8, str_ptr: *const c_char) -> bool {
|
pub extern "C" fn rb_zjit_parse_option(str_ptr: *const c_char) -> bool {
|
||||||
let options = unsafe { &mut *(options as *mut Options) };
|
parse_option(str_ptr).is_some()
|
||||||
parse_option(options, str_ptr).is_some()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_jit_list(path_like: &str) -> HashSet<String> {
|
fn parse_jit_list(path_like: &str) -> HashSet<String> {
|
||||||
|
@ -142,7 +141,10 @@ fn parse_jit_list(path_like: &str) -> HashSet<String> {
|
||||||
/// Expected to receive what comes after the third dash in "--zjit-*".
|
/// Expected to receive what comes after the third dash in "--zjit-*".
|
||||||
/// Empty string means user passed only "--zjit". C code rejects when
|
/// Empty string means user passed only "--zjit". C code rejects when
|
||||||
/// they pass exact "--zjit-".
|
/// they pass exact "--zjit-".
|
||||||
fn parse_option(options: &mut Options, str_ptr: *const std::os::raw::c_char) -> Option<()> {
|
fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> {
|
||||||
|
rb_zjit_prepare_options();
|
||||||
|
let options = unsafe { OPTIONS.as_mut().unwrap() };
|
||||||
|
|
||||||
let c_str: &CStr = unsafe { CStr::from_ptr(str_ptr) };
|
let c_str: &CStr = unsafe { CStr::from_ptr(str_ptr) };
|
||||||
let opt_str: &str = c_str.to_str().ok()?;
|
let opt_str: &str = c_str.to_str().ok()?;
|
||||||
|
|
||||||
|
@ -161,7 +163,7 @@ fn parse_option(options: &mut Options, str_ptr: *const std::os::raw::c_char) ->
|
||||||
("call-threshold", _) => match opt_val.parse() {
|
("call-threshold", _) => match opt_val.parse() {
|
||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
unsafe { rb_zjit_call_threshold = n; }
|
unsafe { rb_zjit_call_threshold = n; }
|
||||||
update_profile_threshold(options);
|
update_profile_threshold();
|
||||||
},
|
},
|
||||||
Err(_) => return None,
|
Err(_) => return None,
|
||||||
},
|
},
|
||||||
|
@ -169,13 +171,12 @@ fn parse_option(options: &mut Options, str_ptr: *const std::os::raw::c_char) ->
|
||||||
("num-profiles", _) => match opt_val.parse() {
|
("num-profiles", _) => match opt_val.parse() {
|
||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
options.num_profiles = n;
|
options.num_profiles = n;
|
||||||
update_profile_threshold(options);
|
update_profile_threshold();
|
||||||
},
|
},
|
||||||
Err(_) => return None,
|
Err(_) => return None,
|
||||||
},
|
},
|
||||||
|
|
||||||
("stats", "") => {
|
("stats", "") => {
|
||||||
unsafe { zjit_stats_enabled_p = true; }
|
|
||||||
options.stats = true;
|
options.stats = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,15 +218,14 @@ fn parse_option(options: &mut Options, str_ptr: *const std::os::raw::c_char) ->
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update rb_zjit_profile_threshold based on rb_zjit_call_threshold and options.num_profiles
|
/// Update rb_zjit_profile_threshold based on rb_zjit_call_threshold and options.num_profiles
|
||||||
fn update_profile_threshold(options: &Options) {
|
fn update_profile_threshold() {
|
||||||
unsafe {
|
if unsafe { rb_zjit_call_threshold == 1 } {
|
||||||
if rb_zjit_call_threshold == 1 {
|
|
||||||
// If --zjit-call-threshold=1, never rewrite ISEQs to profile instructions.
|
// If --zjit-call-threshold=1, never rewrite ISEQs to profile instructions.
|
||||||
rb_zjit_profile_threshold = 0;
|
unsafe { rb_zjit_profile_threshold = 0; }
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, profile instructions at least once.
|
// Otherwise, profile instructions at least once.
|
||||||
rb_zjit_profile_threshold = rb_zjit_call_threshold.saturating_sub(options.num_profiles as u64).max(1);
|
let num_profiles = get_option!(num_profiles) as u64;
|
||||||
}
|
unsafe { rb_zjit_profile_threshold = rb_zjit_call_threshold.saturating_sub(num_profiles).max(1) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,12 +254,23 @@ macro_rules! debug {
|
||||||
}
|
}
|
||||||
pub(crate) use debug;
|
pub(crate) use debug;
|
||||||
|
|
||||||
/// Return Qtrue if --zjit-stats has been enabled
|
/// Return Qtrue if --zjit* has been specified. For the `#with_jit` hook,
|
||||||
|
/// this becomes Qtrue before ZJIT is actually initialized and enabled.
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn rb_zjit_stats_enabled_p(_ec: EcPtr, _self: VALUE) -> VALUE {
|
pub extern "C" fn rb_zjit_option_enabled_p(_ec: EcPtr, _self: VALUE) -> VALUE {
|
||||||
// ZJITState is not initialized yet when loading builtins, so this relies
|
// If any --zjit* option is specified, OPTIONS becomes Some.
|
||||||
// on a separate global variable.
|
if unsafe { OPTIONS.is_some() } {
|
||||||
if unsafe { zjit_stats_enabled_p } {
|
Qtrue
|
||||||
|
} else {
|
||||||
|
Qfalse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return Qtrue if --zjit-stats has been specified.
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn rb_zjit_stats_enabled_p(_ec: EcPtr, _self: VALUE) -> VALUE {
|
||||||
|
// Builtin zjit.rb calls this even if ZJIT is disabled, so OPTIONS may not be set.
|
||||||
|
if unsafe { OPTIONS.as_ref() }.map_or(false, |opts| opts.stats) {
|
||||||
Qtrue
|
Qtrue
|
||||||
} else {
|
} else {
|
||||||
Qfalse
|
Qfalse
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::cruby::{self, rb_bug_panic_hook, rb_vm_insns_count, EcPtr, Qnil, VALUE};
|
use crate::cruby::{self, rb_bug_panic_hook, rb_vm_insns_count, EcPtr, Qnil, VALUE};
|
||||||
use crate::cruby_methods;
|
use crate::cruby_methods;
|
||||||
use crate::invariants::Invariants;
|
use crate::invariants::Invariants;
|
||||||
use crate::options::Options;
|
|
||||||
use crate::asm::CodeBlock;
|
use crate::asm::CodeBlock;
|
||||||
|
use crate::options::get_option;
|
||||||
use crate::stats::Counters;
|
use crate::stats::Counters;
|
||||||
|
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
|
@ -19,9 +19,6 @@ pub struct ZJITState {
|
||||||
/// Inline code block (fast path)
|
/// Inline code block (fast path)
|
||||||
code_block: CodeBlock,
|
code_block: CodeBlock,
|
||||||
|
|
||||||
/// ZJIT command-line options
|
|
||||||
options: Options,
|
|
||||||
|
|
||||||
/// ZJIT statistics
|
/// ZJIT statistics
|
||||||
counters: Counters,
|
counters: Counters,
|
||||||
|
|
||||||
|
@ -39,11 +36,12 @@ pub struct ZJITState {
|
||||||
static mut ZJIT_STATE: Option<ZJITState> = None;
|
static mut ZJIT_STATE: Option<ZJITState> = None;
|
||||||
|
|
||||||
impl ZJITState {
|
impl ZJITState {
|
||||||
/// Initialize the ZJIT globals, given options allocated by rb_zjit_init_options()
|
/// Initialize the ZJIT globals
|
||||||
pub fn init(options: Options) {
|
pub fn init() {
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
let cb = {
|
let cb = {
|
||||||
use crate::cruby::*;
|
use crate::cruby::*;
|
||||||
|
use crate::options::*;
|
||||||
|
|
||||||
let exec_mem_size: usize = 64 * 1024 * 1024; // TODO: implement the option
|
let exec_mem_size: usize = 64 * 1024 * 1024; // TODO: implement the option
|
||||||
let virt_block: *mut u8 = unsafe { rb_zjit_reserve_addr_space(64 * 1024 * 1024) };
|
let virt_block: *mut u8 = unsafe { rb_zjit_reserve_addr_space(64 * 1024 * 1024) };
|
||||||
|
@ -75,7 +73,7 @@ impl ZJITState {
|
||||||
);
|
);
|
||||||
let mem_block = Rc::new(RefCell::new(mem_block));
|
let mem_block = Rc::new(RefCell::new(mem_block));
|
||||||
|
|
||||||
CodeBlock::new(mem_block.clone(), options.dump_disasm)
|
CodeBlock::new(mem_block.clone(), get_option!(dump_disasm))
|
||||||
};
|
};
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
let cb = CodeBlock::new_dummy();
|
let cb = CodeBlock::new_dummy();
|
||||||
|
@ -83,7 +81,6 @@ impl ZJITState {
|
||||||
// Initialize the codegen globals instance
|
// Initialize the codegen globals instance
|
||||||
let zjit_state = ZJITState {
|
let zjit_state = ZJITState {
|
||||||
code_block: cb,
|
code_block: cb,
|
||||||
options,
|
|
||||||
counters: Counters::default(),
|
counters: Counters::default(),
|
||||||
invariants: Invariants::default(),
|
invariants: Invariants::default(),
|
||||||
assert_compiles: false,
|
assert_compiles: false,
|
||||||
|
@ -107,11 +104,6 @@ impl ZJITState {
|
||||||
&mut ZJITState::get_instance().code_block
|
&mut ZJITState::get_instance().code_block
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a mutable reference to the options
|
|
||||||
pub fn get_options() -> &'static mut Options {
|
|
||||||
&mut ZJITState::get_instance().options
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a mutable reference to the invariants
|
/// Get a mutable reference to the invariants
|
||||||
pub fn get_invariants() -> &'static mut Invariants {
|
pub fn get_invariants() -> &'static mut Invariants {
|
||||||
&mut ZJITState::get_instance().invariants
|
&mut ZJITState::get_instance().invariants
|
||||||
|
@ -139,13 +131,13 @@ impl ZJITState {
|
||||||
|
|
||||||
/// Was --zjit-save-compiled-iseqs specified?
|
/// Was --zjit-save-compiled-iseqs specified?
|
||||||
pub fn should_log_compiled_iseqs() -> bool {
|
pub fn should_log_compiled_iseqs() -> bool {
|
||||||
ZJITState::get_instance().options.log_compiled_iseqs.is_some()
|
get_option!(log_compiled_iseqs).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Log the name of a compiled ISEQ to the file specified in options.log_compiled_iseqs
|
/// Log the name of a compiled ISEQ to the file specified in options.log_compiled_iseqs
|
||||||
pub fn log_compile(iseq_name: String) {
|
pub fn log_compile(iseq_name: String) {
|
||||||
assert!(ZJITState::should_log_compiled_iseqs());
|
assert!(ZJITState::should_log_compiled_iseqs());
|
||||||
let filename = ZJITState::get_instance().options.log_compiled_iseqs.as_ref().unwrap();
|
let filename = get_option!(log_compiled_iseqs).as_ref().unwrap();
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
let mut file = match std::fs::OpenOptions::new().create(true).append(true).open(filename) {
|
let mut file = match std::fs::OpenOptions::new().create(true).append(true).open(filename) {
|
||||||
Ok(f) => f,
|
Ok(f) => f,
|
||||||
|
@ -161,7 +153,7 @@ impl ZJITState {
|
||||||
|
|
||||||
/// Check if we are allowed to compile a given ISEQ based on --zjit-allowed-iseqs
|
/// Check if we are allowed to compile a given ISEQ based on --zjit-allowed-iseqs
|
||||||
pub fn can_compile_iseq(iseq: cruby::IseqPtr) -> bool {
|
pub fn can_compile_iseq(iseq: cruby::IseqPtr) -> bool {
|
||||||
if let Some(ref allowed_iseqs) = ZJITState::get_instance().options.allowed_iseqs {
|
if let Some(ref allowed_iseqs) = get_option!(allowed_iseqs) {
|
||||||
let name = cruby::iseq_get_location(iseq, 0);
|
let name = cruby::iseq_get_location(iseq, 0);
|
||||||
allowed_iseqs.contains(&name)
|
allowed_iseqs.contains(&name)
|
||||||
} else {
|
} else {
|
||||||
|
@ -170,17 +162,17 @@ impl ZJITState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize ZJIT, given options allocated by rb_zjit_init_options()
|
/// Initialize ZJIT
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn rb_zjit_init(options: *const u8) {
|
pub extern "C" fn rb_zjit_init() {
|
||||||
// Catch panics to avoid UB for unwinding into C frames.
|
// Catch panics to avoid UB for unwinding into C frames.
|
||||||
// See https://doc.rust-lang.org/nomicon/exception-safety.html
|
// See https://doc.rust-lang.org/nomicon/exception-safety.html
|
||||||
let result = std::panic::catch_unwind(|| {
|
let result = std::panic::catch_unwind(|| {
|
||||||
|
// Initialize ZJIT states
|
||||||
cruby::ids::init();
|
cruby::ids::init();
|
||||||
|
ZJITState::init();
|
||||||
|
|
||||||
let options = unsafe { Box::from_raw(options as *mut Options) };
|
// Install a panic hook for ZJIT
|
||||||
ZJITState::init(*options);
|
|
||||||
|
|
||||||
rb_bug_panic_hook();
|
rb_bug_panic_hook();
|
||||||
|
|
||||||
// Discard the instruction count for boot which we never compile
|
// Discard the instruction count for boot which we never compile
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue