mirror of
https://github.com/ruby/ruby.git
synced 2026-07-30 15:41:06 +08:00
This commit implements support in ZJIT for inlining GC fast path. The GC fast path is defined in gc_fastpath.rs for both the default GC and MMTk. For the default GC, the fast path uses the bump pointer allocator that was implemented in https://github.com/ruby/ruby/pull/17201. If the bump pointer region is full, it will bail out and fall back to the slow path. This GC fast path is used by the newarray instruction in ZJIT in the empty array case. We can see significant improvements in the following microbenchmark, which allocates 100 million empty arrays: def run(max) i = 0 while i < max a = [] i += 1 end end 30.times { run(2) } run(100_000_000) This microbenchmark runs 1.5x faster on my machine: Benchmark 1: master Time (mean ± σ): 583.3 ms ± 10.1 ms [User: 569.3 ms, System: 12.0 ms] Range (min … max): 571.9 ms … 600.0 ms 10 runs Benchmark 2: branch Time (mean ± σ): 389.5 ms ± 8.2 ms [User: 375.0 ms, System: 12.1 ms] Range (min … max): 380.2 ms … 405.3 ms 10 runs Summary branch ran 1.50 ± 0.04 times faster than master
273 lines
8.4 KiB
C
273 lines
8.4 KiB
C
#include "internal.h"
|
|
#include "internal/sanitizers.h"
|
|
#include "internal/string.h"
|
|
#include "internal/hash.h"
|
|
#include "internal/variable.h"
|
|
#include "internal/compile.h"
|
|
#include "internal/class.h"
|
|
#include "internal/fixnum.h"
|
|
#include "internal/numeric.h"
|
|
#include "internal/gc.h"
|
|
#include "internal/vm.h"
|
|
#include "yjit.h"
|
|
#include "vm_core.h"
|
|
#include "vm_callinfo.h"
|
|
#include "builtin.h"
|
|
#include "insns.inc"
|
|
#include "insns_info.inc"
|
|
#include "zjit.h"
|
|
#include "vm_insnhelper.h"
|
|
#include "probes.h"
|
|
#include "probes_helper.h"
|
|
#include "constant.h"
|
|
#include "iseq.h"
|
|
#include "ruby/debug.h"
|
|
#include "internal/cont.h"
|
|
#include "ractor_core.h"
|
|
|
|
// This build config impacts the pointer tagging scheme and we only want to
|
|
// support one scheme for simplicity.
|
|
STATIC_ASSERT(pointer_tagging_scheme, USE_FLONUM);
|
|
|
|
enum zjit_struct_offsets {
|
|
ISEQ_BODY_OFFSET_PARAM = offsetof(struct rb_iseq_constant_body, param),
|
|
ISEQ_BODY_OFFSET_OUTER_VARIABLES = offsetof(struct rb_iseq_constant_body, outer_variables),
|
|
RUBY_OFFSET_THREAD_RACTOR = offsetof(rb_thread_t, ractor),
|
|
};
|
|
|
|
// Special JITFrame used by all C method calls. We don't control the native
|
|
// stack layout for C frames, so cfp->jit_return points at this static frame
|
|
// via the ZJIT_JIT_RETURN_C_FRAME sentinel instead of a per-call allocation.
|
|
const zjit_jit_frame_t rb_zjit_c_frame = (zjit_jit_frame_t) {
|
|
.pc = 0,
|
|
.iseq = 0,
|
|
.materialize_block_code = false,
|
|
};
|
|
|
|
void rb_zjit_profile_disable(const rb_iseq_t *iseq);
|
|
int rb_zjit_insn_to_bare_insn(int insn);
|
|
|
|
void
|
|
rb_zjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception)
|
|
{
|
|
RB_VM_LOCKING() {
|
|
rb_vm_barrier();
|
|
|
|
// Compile a block version starting at the current instruction
|
|
uint8_t *rb_zjit_iseq_gen_entry_point(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception); // defined in Rust
|
|
uintptr_t code_ptr = (uintptr_t)rb_zjit_iseq_gen_entry_point(iseq, ec, jit_exception);
|
|
|
|
if (jit_exception) {
|
|
iseq->body->jit_exception = (rb_jit_func_t)code_ptr;
|
|
}
|
|
else {
|
|
iseq->body->jit_entry = (rb_jit_func_t)code_ptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
extern VALUE *rb_vm_base_ptr(struct rb_control_frame_struct *cfp);
|
|
|
|
bool
|
|
rb_zjit_constcache_shareable(const struct iseq_inline_constant_cache_entry *ice)
|
|
{
|
|
return (ice->flags & IMEMO_CONST_CACHE_SHAREABLE) != 0;
|
|
}
|
|
|
|
// Convert a given ISEQ's instructions to zjit_* instructions
|
|
void
|
|
rb_zjit_profile_enable(const rb_iseq_t *iseq)
|
|
{
|
|
// This table encodes an opcode into the instruction's address
|
|
const void *const *insn_table = rb_vm_get_insns_address_table();
|
|
|
|
unsigned int insn_idx = 0;
|
|
while (insn_idx < iseq->body->iseq_size) {
|
|
int insn = rb_vm_insn_addr2opcode((void *)iseq->body->iseq_encoded[insn_idx]);
|
|
int zjit_insn = vm_bare_insn_to_zjit_insn(insn);
|
|
if (insn != zjit_insn) {
|
|
iseq->body->iseq_encoded[insn_idx] = (VALUE)insn_table[zjit_insn];
|
|
}
|
|
insn_idx += insn_len(insn);
|
|
}
|
|
}
|
|
|
|
// Convert a given ISEQ's ZJIT instructions to bare instructions
|
|
void
|
|
rb_zjit_profile_disable(const rb_iseq_t *iseq)
|
|
{
|
|
// This table encodes an opcode into the instruction's address
|
|
const void *const *insn_table = rb_vm_get_insns_address_table();
|
|
|
|
unsigned int insn_idx = 0;
|
|
while (insn_idx < iseq->body->iseq_size) {
|
|
int insn = rb_vm_insn_addr2opcode((void *)iseq->body->iseq_encoded[insn_idx]);
|
|
int bare_insn = vm_zjit_insn_to_bare_insn(insn);
|
|
if (insn != bare_insn) {
|
|
iseq->body->iseq_encoded[insn_idx] = (VALUE)insn_table[bare_insn];
|
|
}
|
|
insn_idx += insn_len(insn);
|
|
}
|
|
}
|
|
|
|
// Map `zjit_* instructions back to their bare form. This is an identity function for all others.
|
|
int
|
|
rb_zjit_insn_to_bare_insn(int insn)
|
|
{
|
|
return vm_zjit_insn_to_bare_insn(insn);
|
|
}
|
|
|
|
// Update a YARV instruction to a given opcode (to disable ZJIT profiling).
|
|
void
|
|
rb_zjit_iseq_insn_set(const rb_iseq_t *iseq, unsigned int insn_idx, enum ruby_vminsn_type bare_insn)
|
|
{
|
|
#if RUBY_DEBUG
|
|
int insn = rb_vm_insn_addr2opcode((void *)iseq->body->iseq_encoded[insn_idx]);
|
|
RUBY_ASSERT(vm_zjit_insn_to_bare_insn(insn) == (int)bare_insn);
|
|
#endif
|
|
const void *const *insn_table = rb_vm_get_insns_address_table();
|
|
iseq->body->iseq_encoded[insn_idx] = (VALUE)insn_table[bare_insn];
|
|
}
|
|
|
|
// Get profiling information for ISEQ
|
|
void *
|
|
rb_iseq_get_zjit_payload(const rb_iseq_t *iseq)
|
|
{
|
|
RUBY_ASSERT_ALWAYS(IMEMO_TYPE_P(iseq, imemo_iseq));
|
|
if (iseq->body) {
|
|
return iseq->body->zjit_payload;
|
|
}
|
|
else {
|
|
// Body is NULL when constructing the iseq.
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// Set profiling information for ISEQ
|
|
void
|
|
rb_iseq_set_zjit_payload(const rb_iseq_t *iseq, void *payload)
|
|
{
|
|
RUBY_ASSERT_ALWAYS(IMEMO_TYPE_P(iseq, imemo_iseq));
|
|
RUBY_ASSERT_ALWAYS(iseq->body);
|
|
RUBY_ASSERT_ALWAYS(NULL == iseq->body->zjit_payload);
|
|
iseq->body->zjit_payload = payload;
|
|
}
|
|
|
|
void
|
|
rb_zjit_print_exception(void)
|
|
{
|
|
VALUE exception = rb_errinfo();
|
|
rb_set_errinfo(Qnil);
|
|
assert(RTEST(exception));
|
|
rb_warn("Ruby error: %"PRIsVALUE"", rb_funcall(exception, rb_intern("full_message"), 0));
|
|
}
|
|
|
|
bool
|
|
rb_zjit_singleton_class_p(VALUE klass)
|
|
{
|
|
return RCLASS_SINGLETON_P(klass);
|
|
}
|
|
|
|
size_t
|
|
rb_zjit_offset_ractor_newobj_cache(void)
|
|
{
|
|
return offsetof(rb_ractor_t, newobj_cache);
|
|
}
|
|
|
|
VALUE
|
|
rb_zjit_defined_ivar(VALUE obj, ID id, VALUE pushval)
|
|
{
|
|
VALUE result = rb_ivar_defined(obj, id);
|
|
return result ? pushval : Qnil;
|
|
}
|
|
|
|
bool
|
|
rb_zjit_method_tracing_currently_enabled(void)
|
|
{
|
|
rb_event_flag_t tracing_events;
|
|
if (rb_multi_ractor_p()) {
|
|
tracing_events = ruby_vm_event_enabled_global_flags;
|
|
}
|
|
else {
|
|
// At the time of writing, events are never removed from
|
|
// ruby_vm_event_enabled_global_flags so always checking using it would
|
|
// mean we don't compile even after tracing is disabled.
|
|
tracing_events = rb_ec_ractor_hooks(GET_EC())->events;
|
|
}
|
|
|
|
return tracing_events & (RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN);
|
|
}
|
|
|
|
// Check if any ISEQ trace events are currently enabled.
|
|
// Used to prevent ZJIT from compiling while tracing is active, since ZJIT's
|
|
// send fallback (rb_vm_opt_send_without_block) uses VM_EXEC which sets
|
|
// VM_FRAME_FLAG_FINISH on the callee frame, changing exception handling
|
|
// semantics for throw TAG_RETURN (e.g. return from rescue).
|
|
bool
|
|
rb_zjit_iseq_tracing_currently_enabled(void)
|
|
{
|
|
rb_event_flag_t tracing_events;
|
|
if (rb_multi_ractor_p()) {
|
|
tracing_events = ruby_vm_event_enabled_global_flags;
|
|
}
|
|
else {
|
|
tracing_events = rb_ec_ractor_hooks(GET_EC())->events;
|
|
}
|
|
|
|
return tracing_events & ISEQ_TRACE_EVENTS;
|
|
}
|
|
|
|
bool
|
|
rb_zjit_insn_leaf(int insn, const VALUE *opes)
|
|
{
|
|
return insn_leaf(insn, opes);
|
|
}
|
|
|
|
ID
|
|
rb_zjit_local_id(const rb_iseq_t *iseq, unsigned idx)
|
|
{
|
|
return ISEQ_BODY(iseq)->local_table[idx];
|
|
}
|
|
|
|
bool rb_zjit_cme_is_cfunc(const rb_callable_method_entry_t *me, const void *func);
|
|
|
|
const struct rb_callable_method_entry_struct *
|
|
rb_zjit_vm_search_method(VALUE cd_owner, struct rb_call_data *cd, VALUE recv);
|
|
|
|
bool
|
|
rb_zjit_class_initialized_p(VALUE klass)
|
|
{
|
|
return RCLASS_INITIALIZED_P(klass);
|
|
}
|
|
|
|
rb_alloc_func_t rb_zjit_class_get_alloc_func(VALUE klass);
|
|
|
|
VALUE rb_class_allocate_instance(VALUE klass);
|
|
|
|
bool
|
|
rb_zjit_class_has_default_allocator(VALUE klass)
|
|
{
|
|
assert(RCLASS_INITIALIZED_P(klass));
|
|
assert(!RCLASS_SINGLETON_P(klass));
|
|
rb_alloc_func_t alloc = rb_zjit_class_get_alloc_func(klass);
|
|
return alloc == rb_class_allocate_instance;
|
|
}
|
|
|
|
|
|
VALUE rb_vm_untag_block_handler(VALUE block_handler);
|
|
VALUE rb_vm_get_untagged_block_handler(rb_control_frame_t *reg_cfp);
|
|
|
|
// Primitives used by zjit.rb. Don't put other functions below, which wouldn't use them.
|
|
VALUE rb_zjit_enable(rb_execution_context_t *ec, VALUE self);
|
|
VALUE rb_zjit_assert_compiles(rb_execution_context_t *ec, VALUE self);
|
|
VALUE rb_zjit_stats(rb_execution_context_t *ec, VALUE self, VALUE target_key);
|
|
VALUE rb_zjit_reset_stats_bang(rb_execution_context_t *ec, VALUE self);
|
|
VALUE rb_zjit_stats_enabled_p(rb_execution_context_t *ec, VALUE self);
|
|
VALUE rb_zjit_print_stats_p(rb_execution_context_t *ec, VALUE self);
|
|
VALUE rb_zjit_get_stats_file_path_p(rb_execution_context_t *ec, VALUE self);
|
|
VALUE rb_zjit_trace_exit_locations_enabled_p(rb_execution_context_t *ec, VALUE self);
|
|
VALUE rb_zjit_get_exit_locations(rb_execution_context_t *ec, VALUE self);
|
|
|
|
// Preprocessed zjit.rb generated during build
|
|
#include "zjit.rbinc"
|