Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions insns.def
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ getblockparamproxy
(lindex_t idx, rb_num_t level)
()
(VALUE val)
// attr bool zjit_profile = true;
{
const VALUE *ep = vm_get_ep(GET_EP(), level);
VM_ASSERT(VM_ENV_LOCAL_P(ep));
Expand Down
2 changes: 1 addition & 1 deletion vm_insnhelper.c
Original file line number Diff line number Diff line change
Expand Up @@ -6050,7 +6050,7 @@ vm_define_method(const rb_execution_context_t *ec, VALUE obj, ID id, VALUE iseqv
// * If it's VM_BLOCK_HANDLER_NONE, return nil
// * If it's an ISEQ or an IFUNC, fetch it from its rb_captured_block
// * If it's a PROC or SYMBOL, return it as is
static VALUE
VALUE
rb_vm_untag_block_handler(VALUE block_handler)
{
if (VM_BLOCK_HANDLER_NONE == block_handler) return Qnil;
Expand Down
65 changes: 33 additions & 32 deletions yjit/src/cruby_bindings.inc.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions zjit.c
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ rb_zjit_class_has_default_allocator(VALUE klass)
}


VALUE rb_vm_untag_block_handler(VALUE block_handler);
VALUE rb_vm_get_untagged_block_handler(rb_control_frame_t *reg_cfp);

void
Expand Down
1 change: 1 addition & 0 deletions zjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ def stats_string
print_counters_with_prefix(prefix: 'getivar_fallback_', prompt: 'getivar fallback reasons', buf:, stats:, limit: 5)
print_counters_with_prefix(prefix: 'definedivar_fallback_', prompt: 'definedivar fallback reasons', buf:, stats:, limit: 5)
print_counters_with_prefix(prefix: 'invokeblock_handler_', prompt: 'invokeblock handler', buf:, stats:, limit: 10)
print_counters_with_prefix(prefix: 'getblockparamproxy_handler_', prompt: 'getblockparamproxy handler', buf:, stats:, limit: 10)

# Show most popular unsupported call features. Because each call can
# use multiple complex features, a decrease in this number does not
Expand Down
1 change: 1 addition & 0 deletions zjit/bindgen/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@ fn main() {
.allowlist_function("rb_str_neq_internal")
.allowlist_function("rb_yarv_ary_entry_internal")
.allowlist_function("rb_vm_get_untagged_block_handler")
.allowlist_function("rb_vm_untag_block_handler")
.allowlist_function("rb_FL_TEST")
.allowlist_function("rb_FL_TEST_RAW")
.allowlist_function("rb_RB_TYPE_P")
Expand Down
114 changes: 93 additions & 21 deletions zjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -526,10 +526,12 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
&Insn::UnboxFixnum { val } => gen_unbox_fixnum(asm, opnd!(val)),
Insn::Test { val } => gen_test(asm, opnd!(val)),
Insn::RefineType { val, .. } => opnd!(val),
Insn::HasType { val, expected } => gen_has_type(asm, opnd!(val), *expected),
Insn::GuardType { val, guard_type, state } => gen_guard_type(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)),
Insn::GuardTypeNot { val, guard_type, state } => gen_guard_type_not(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)),
&Insn::GuardBitEquals { val, expected, reason, state } => gen_guard_bit_equals(jit, asm, opnd!(val), expected, reason, &function.frame_state(state)),
&Insn::GuardBlockParamProxy { level, state } => no_output!(gen_guard_block_param_proxy(jit, asm, level, &function.frame_state(state))),
&Insn::GuardAnyBitSet { val, mask, reason, state } => gen_guard_any_bit_set(jit, asm, opnd!(val), mask, reason, &function.frame_state(state)),
&Insn::GuardNoBitsSet { val, mask, reason, state } => gen_guard_no_bits_set(jit, asm, opnd!(val), mask, reason, &function.frame_state(state)),
Insn::GuardNotFrozen { recv, state } => gen_guard_not_frozen(jit, asm, opnd!(recv), &function.frame_state(*state)),
Insn::GuardNotShared { recv, state } => gen_guard_not_shared(jit, asm, opnd!(recv), &function.frame_state(*state)),
&Insn::GuardLess { left, right, state } => gen_guard_less(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)),
Expand Down Expand Up @@ -580,6 +582,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
&Insn::GuardShape { val, shape, state } => gen_guard_shape(jit, asm, opnd!(val), shape, &function.frame_state(state)),
Insn::LoadPC => gen_load_pc(asm),
Insn::LoadEC => gen_load_ec(),
&Insn::GetEP { level } => gen_get_ep(asm, level),
Insn::GetLEP => gen_get_lep(jit, asm),
Insn::LoadSelf => gen_load_self(),
&Insn::LoadField { recv, id, offset, return_type } => gen_load_field(asm, opnd!(recv), id, offset, return_type),
Expand Down Expand Up @@ -786,26 +789,6 @@ fn gen_getblockparam(jit: &mut JITState, asm: &mut Assembler, ep_offset: u32, le
asm.load(Opnd::mem(VALUE_BITS, ep, offset))
}

fn gen_guard_block_param_proxy(jit: &JITState, asm: &mut Assembler, level: u32, state: &FrameState) {
// Bail out if the `&block` local variable has been modified
let ep = gen_get_ep(asm, level);
let flags = Opnd::mem(64, ep, SIZEOF_VALUE_I32 * (VM_ENV_DATA_INDEX_FLAGS as i32));
asm.test(flags, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM.into());
asm.jnz(side_exit(jit, state, SideExitReason::BlockParamProxyModified));

// This handles two cases which are nearly identical
// Block handler is a tagged pointer. Look at the tag.
// VM_BH_ISEQ_BLOCK_P(): block_handler & 0x03 == 0x01
// VM_BH_IFUNC_P(): block_handler & 0x03 == 0x03
// So to check for either of those cases we can use: val & 0x1 == 0x1
const _: () = assert!(RUBY_SYMBOL_FLAG & 1 == 0, "guard below rejects symbol block handlers");

// Bail ouf if the block handler is neither ISEQ nor ifunc
let block_handler = asm.load(Opnd::mem(64, ep, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL));
asm.test(block_handler, 0x1.into());
asm.jz(side_exit(jit, state, SideExitReason::BlockParamProxyNotIseqOrIfunc));
}

fn gen_guard_not_frozen(jit: &JITState, asm: &mut Assembler, recv: Opnd, state: &FrameState) -> Opnd {
let recv = asm.load(recv);
// It's a heap object, so check the frozen flag
Expand Down Expand Up @@ -2205,6 +2188,69 @@ fn gen_test(asm: &mut Assembler, val: lir::Opnd) -> lir::Opnd {
asm.csel_e(0.into(), 1.into())
}

fn gen_has_type(asm: &mut Assembler, val: lir::Opnd, ty: Type) -> lir::Opnd {
if ty.is_subtype(types::Fixnum) {
asm.test(val, Opnd::UImm(RUBY_FIXNUM_FLAG as u64));
asm.csel_nz(Opnd::Imm(1), Opnd::Imm(0))
} else if ty.is_subtype(types::Flonum) {
// Flonum: (val & RUBY_FLONUM_MASK) == RUBY_FLONUM_FLAG
let masked = asm.and(val, Opnd::UImm(RUBY_FLONUM_MASK as u64));
asm.cmp(masked, Opnd::UImm(RUBY_FLONUM_FLAG as u64));
asm.csel_e(Opnd::Imm(1), Opnd::Imm(0))
} else if ty.is_subtype(types::StaticSymbol) {
// Static symbols have (val & 0xff) == RUBY_SYMBOL_FLAG
// Use 8-bit comparison like YJIT does. GuardType should not be used
// for a known VALUE, which with_num_bits() does not support.
asm.cmp(val.with_num_bits(8), Opnd::UImm(RUBY_SYMBOL_FLAG as u64));
asm.csel_e(Opnd::Imm(1), Opnd::Imm(0))
} else if ty.is_subtype(types::NilClass) {
asm.cmp(val, Qnil.into());
asm.csel_e(Opnd::Imm(1), Opnd::Imm(0))
} else if ty.is_subtype(types::TrueClass) {
asm.cmp(val, Qtrue.into());
asm.csel_e(Opnd::Imm(1), Opnd::Imm(0))
} else if ty.is_subtype(types::FalseClass) {
asm.cmp(val, Qfalse.into());
asm.csel_e(Opnd::Imm(1), Opnd::Imm(0))
} else if ty.is_immediate() {
// All immediate types' guard should have been handled above
panic!("unexpected immediate guard type: {ty}");
} else if let Some(expected_class) = ty.runtime_exact_ruby_class() {
// If val isn't in a register, load it to use it as the base of Opnd::mem later.
// TODO: Max thinks codegen should not care about the shapes of the operands except to create them. (Shopify/ruby#685)
let val = match val {
Opnd::Reg(_) | Opnd::VReg { .. } => val,
_ => asm.load(val),
};

let ret_label = asm.new_label("true");
let false_label = asm.new_label("false");

// Check if it's a special constant
asm.test(val, (RUBY_IMMEDIATE_MASK as u64).into());
asm.jnz(false_label.clone());

// Check if it's false
asm.cmp(val, Qfalse.into());
asm.je(false_label.clone());

// Load the class from the object's klass field
let klass = asm.load(Opnd::mem(64, val, RUBY_OFFSET_RBASIC_KLASS));
asm.cmp(klass, Opnd::Value(expected_class));
asm.jmp(ret_label.clone());

// If we get here then the value was false, unset the Z flag
// so that csel_e will select false instead of true
asm.write_label(false_label);
asm.test(Opnd::UImm(1), Opnd::UImm(1));

asm.write_label(ret_label);
asm.csel_e(Opnd::UImm(1), Opnd::Imm(0))
} else {
unimplemented!("unsupported type: {ty}");
}
}

/// Compile a type check with a side exit
fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, guard_type: Type, state: &FrameState) -> lir::Opnd {
gen_incr_counter(asm, Counter::guard_type_count);
Expand Down Expand Up @@ -2338,6 +2384,32 @@ fn gen_guard_bit_equals(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd,
val
}

fn mask_to_opnd(mask: crate::hir::Const) -> Option<Opnd> {
match mask {
crate::hir::Const::CUInt8(v) => Some(Opnd::UImm(v as u64)),
crate::hir::Const::CUInt16(v) => Some(Opnd::UImm(v as u64)),
crate::hir::Const::CUInt32(v) => Some(Opnd::UImm(v as u64)),
crate::hir::Const::CUInt64(v) => Some(Opnd::UImm(v)),
_ => None
}
}

/// Compile a bitmask check with a side exit if none of the masked bits are not set
fn gen_guard_any_bit_set(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, mask: crate::hir::Const, reason: SideExitReason, state: &FrameState) -> lir::Opnd {
let mask_opnd = mask_to_opnd(mask).unwrap_or_else(|| panic!("gen_guard_any_bit_set: unexpected hir::Const {mask:?}"));
asm.test(val, mask_opnd);
asm.jz(side_exit(jit, state, reason));
val
}

/// Compile a bitmask check with a side exit if any of the masked bits are set
fn gen_guard_no_bits_set(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, mask: crate::hir::Const, reason: SideExitReason, state: &FrameState) -> lir::Opnd {
let mask_opnd = mask_to_opnd(mask).unwrap_or_else(|| panic!("gen_guard_no_bits_set: unexpected hir::Const {mask:?}"));
asm.test(val, mask_opnd);
asm.jnz(side_exit(jit, state, reason));
val
}

/// Generate code that records unoptimized C functions if --zjit-stats is enabled
fn gen_incr_counter_ptr(asm: &mut Assembler, counter_ptr: *mut u64) {
if get_option!(stats) {
Expand Down
2 changes: 2 additions & 0 deletions zjit/src/cruby.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1396,6 +1396,8 @@ pub(crate) mod ids {
name: self_ content: b"self"
name: rb_ivar_get_at_no_ractor_check
name: _shape_id
name: _env_data_index_flags
name: _env_data_index_specval
}

/// Get an CRuby `ID` to an interned string, e.g. a particular method name.
Expand Down
66 changes: 34 additions & 32 deletions zjit/src/cruby_bindings.inc.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading