Disable ZJIT profiling at call-threshold (https://github.com/Shopify/zjit/pull/99)

* Disable ZJIT profiling at call-threshold

* Stop referencing ZJIT instructions in codegen
This commit is contained in:
Takashi Kokubun 2025-04-04 09:06:56 -07:00
parent 2915806820
commit 8b72e07359
Notes: git 2025-04-18 13:47:42 +00:00
7 changed files with 60 additions and 20 deletions

View File

@ -1,7 +1,6 @@
MAYBE_UNUSED(static int vm_insn_to_zjit_insn(int insn));
MAYBE_UNUSED(static int vm_bare_insn_to_zjit_insn(int insn));
static int
vm_insn_to_zjit_insn(int insn)
vm_bare_insn_to_zjit_insn(int insn)
{
switch (insn) {
% RubyVM::ZJITInstructions.to_a.each do |insn|
@ -12,3 +11,17 @@ vm_insn_to_zjit_insn(int insn)
return insn;
}
}
MAYBE_UNUSED(static int vm_zjit_insn_to_bare_insn(int insn));
static int
vm_zjit_insn_to_bare_insn(int insn)
{
switch (insn) {
% RubyVM::ZJITInstructions.to_a.each do |insn|
case <%= insn.bin %>:
return BIN(<%= insn.jump_destination %>);
% end
default:
return insn;
}
}

2
vm.c
View File

@ -443,7 +443,7 @@ jit_compile(rb_execution_context_t *ec)
// At profile-threshold, rewrite some of the YARV instructions
// to zjit_* instructions to profile these instructions.
if (body->jit_entry_calls == rb_zjit_profile_threshold) {
rb_zjit_profile_iseq(iseq);
rb_zjit_profile_enable(iseq);
}
// At call-threshold, compile the ISEQ with ZJIT.

27
zjit.c
View File

@ -168,12 +168,17 @@ rb_RSTRING_PTR(VALUE str)
return RSTRING_PTR(str);
}
void rb_zjit_profile_disable(const rb_iseq_t *iseq);
void
rb_zjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception)
{
RB_VM_LOCK_ENTER();
rb_vm_barrier();
// Convert ZJIT instructions back to bare instructions
rb_zjit_profile_disable(iseq);
// 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); // defined in Rust
uintptr_t code_ptr = (uintptr_t)rb_zjit_iseq_gen_entry_point(iseq, ec);
@ -652,7 +657,7 @@ rb_RCLASS_ORIGIN(VALUE c)
// Convert a given ISEQ's instructions to zjit_* instructions
void
rb_zjit_profile_iseq(const rb_iseq_t *iseq)
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();
@ -660,7 +665,7 @@ rb_zjit_profile_iseq(const rb_iseq_t *iseq)
unsigned int insn_idx = 0;
while (insn_idx < iseq->body->iseq_size) {
int insn = rb_vm_insn_decode(iseq->body->iseq_encoded[insn_idx]);
int zjit_insn = vm_insn_to_zjit_insn(insn);
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];
}
@ -668,6 +673,24 @@ rb_zjit_profile_iseq(const rb_iseq_t *iseq)
}
}
// 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_decode(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);
}
}
// Get profiling information for ISEQ
void *
rb_iseq_get_zjit_payload(const rb_iseq_t *iseq)

4
zjit.h
View File

@ -10,13 +10,13 @@ extern uint64_t rb_zjit_call_threshold;
extern uint64_t rb_zjit_profile_threshold;
void rb_zjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception);
void rb_zjit_profile_insn(enum ruby_vminsn_type insn, rb_execution_context_t *ec);
void rb_zjit_profile_iseq(const rb_iseq_t *iseq);
void rb_zjit_profile_enable(const rb_iseq_t *iseq);
void rb_zjit_bop_redefined(int redefined_flag, enum ruby_basic_operators bop);
void rb_zjit_invalidate_ep_is_bp(const rb_iseq_t *iseq);
#else
static inline void rb_zjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception) {}
static inline void rb_zjit_profile_insn(enum ruby_vminsn_type insn, rb_execution_context_t *ec) {}
static inline void rb_zjit_profile_iseq(const rb_iseq_t *iseq) {}
static inline void rb_zjit_profile_enable(const rb_iseq_t *iseq) {}
static inline void rb_zjit_bop_redefined(int redefined_flag, enum ruby_basic_operators bop) {}
static inline void rb_zjit_invalidate_ep_is_bp(const rb_iseq_t *iseq) {}
#endif // #if USE_YJIT

View File

@ -100,6 +100,7 @@ fn main() {
.allowlist_function("ruby_executable_node")
.allowlist_function("rb_funcallv")
.allowlist_function("rb_protect")
.allowlist_function("rb_zjit_profile_disable")
// For crashing
.allowlist_function("rb_bug")

View File

@ -945,6 +945,7 @@ unsafe extern "C" {
pub fn rb_zjit_reserve_addr_space(mem_size: u32) -> *mut u8;
pub fn rb_RSTRING_LEN(str_: VALUE) -> ::std::os::raw::c_ulong;
pub fn rb_RSTRING_PTR(str_: VALUE) -> *mut ::std::os::raw::c_char;
pub fn rb_zjit_profile_disable(iseq: *const rb_iseq_t);
pub fn rb_iseq_encoded_size(iseq: *const rb_iseq_t) -> ::std::os::raw::c_uint;
pub fn rb_iseq_opcode_at_pc(iseq: *const rb_iseq_t, pc: *const VALUE) -> ::std::os::raw::c_int;
pub fn rb_iseq_pc_at_idx(iseq: *const rb_iseq_t, insn_idx: u32) -> *mut VALUE;

View File

@ -1598,38 +1598,38 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
}
}
YARVINSN_opt_plus | YARVINSN_zjit_opt_plus => {
YARVINSN_opt_plus => {
push_fixnum_insn!(FixnumAdd, "+", BOP_PLUS, state);
}
YARVINSN_opt_minus | YARVINSN_zjit_opt_minus => {
YARVINSN_opt_minus => {
push_fixnum_insn!(FixnumSub, "-", BOP_MINUS, state);
}
YARVINSN_opt_mult | YARVINSN_zjit_opt_mult => {
YARVINSN_opt_mult => {
push_fixnum_insn!(FixnumMult, "*", BOP_MULT, state);
}
YARVINSN_opt_div | YARVINSN_zjit_opt_div => {
YARVINSN_opt_div => {
push_fixnum_insn!(FixnumDiv, "/", BOP_DIV, state);
}
YARVINSN_opt_mod | YARVINSN_zjit_opt_mod => {
YARVINSN_opt_mod => {
push_fixnum_insn!(FixnumMod, "%", BOP_MOD, state);
}
YARVINSN_opt_eq | YARVINSN_zjit_opt_eq => {
YARVINSN_opt_eq => {
push_fixnum_insn!(FixnumEq, "==", BOP_EQ);
}
YARVINSN_opt_neq | YARVINSN_zjit_opt_neq => {
YARVINSN_opt_neq => {
push_fixnum_insn!(FixnumNeq, "!=", BOP_NEQ);
}
YARVINSN_opt_lt | YARVINSN_zjit_opt_lt => {
YARVINSN_opt_lt => {
push_fixnum_insn!(FixnumLt, "<", BOP_LT);
}
YARVINSN_opt_le | YARVINSN_zjit_opt_le => {
YARVINSN_opt_le => {
push_fixnum_insn!(FixnumLe, "<=", BOP_LE);
}
YARVINSN_opt_gt | YARVINSN_zjit_opt_gt => {
YARVINSN_opt_gt => {
push_fixnum_insn!(FixnumGt, ">", BOP_GT);
}
YARVINSN_opt_ge | YARVINSN_zjit_opt_ge => {
YARVINSN_opt_ge => {
push_fixnum_insn!(FixnumGe, ">==", BOP_GE);
}
YARVINSN_opt_ltlt => {
@ -1654,7 +1654,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
break; // Don't enqueue the next block as a successor
}
YARVINSN_opt_send_without_block | YARVINSN_zjit_opt_send_without_block => {
YARVINSN_opt_send_without_block => {
let cd: *const rb_call_data = get_arg(pc, 0).as_ptr();
let call_info = unsafe { rb_get_call_data_ci(cd) };
let argc = unsafe { vm_ci_argc((*cd).ci) };
@ -1991,6 +1991,7 @@ mod tests {
#[track_caller]
fn assert_method_hir(method: &str, hir: Expect) {
let iseq = crate::cruby::with_rubyvm(|| get_method_iseq(method));
unsafe { crate::cruby::rb_zjit_profile_disable(iseq) };
let function = iseq_to_hir(iseq).unwrap();
assert_function_hir(function, hir);
}
@ -2531,6 +2532,7 @@ mod opt_tests {
#[track_caller]
fn assert_optimized_method_hir(method: &str, hir: Expect) {
let iseq = crate::cruby::with_rubyvm(|| get_method_iseq(method));
unsafe { crate::cruby::rb_zjit_profile_disable(iseq) };
let mut function = iseq_to_hir(iseq).unwrap();
function.optimize();
assert_function_hir(function, hir);