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 @@ -939,6 +939,7 @@ objtostring
(VALUE recv)
(VALUE val)
// attr bool leaf = false;
// attr bool zjit_profile = true;
{
val = vm_objtostring(GET_ISEQ(), recv, cd);

Expand Down
8 changes: 5 additions & 3 deletions string.c
Original file line number Diff line number Diff line change
Expand Up @@ -10361,10 +10361,12 @@ lstrip_offset(VALUE str, const char *s, const char *e, rb_encoding *enc)
* call-seq:
* lstrip! -> self or nil
*
* Like String#lstrip, except that any modifications are made in +self+;
* returns +self+ if any modification are made, +nil+ otherwise.
* Like String#lstrip, except that:
*
* - Performs stripping in +self+ (not in a copy of +self+).
* - Returns +self+ if any characters are stripped, +nil+ otherwise.
*
* Related: String#rstrip!, String#strip!.
* Related: see {Modifying}[rdoc-ref:String@Modifying].
*/

static VALUE
Expand Down
64 changes: 64 additions & 0 deletions test/ruby/test_zjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,15 @@ def test_setlocal_on_eval
}
end

def test_getblockparamproxy
assert_compiles '1', %q{
def test(&block)
0.then(&block)
end
test { 1 }
}, insns: [:getblockparamproxy]
end

def test_call_a_forwardable_method
assert_runs '[]', %q{
def test_root = forwardable
Expand Down Expand Up @@ -2088,6 +2097,61 @@ def test(str)
}
end

def test_objtostring_profiled_string_fastpath
assert_compiles '"foo"', %q{
def test(str)
"#{str}"
end
test('foo'); test('foo') # profile as string
}, call_threshold: 2
end

def test_objtostring_profiled_string_subclass_fastpath
assert_compiles '"foo"', %q{
class MyString < String; end

def test(str)
"#{str}"
end

foo = MyString.new("foo")
test(foo); test(foo) # still profiles as string
}, call_threshold: 2
end

def test_objtostring_profiled_string_fastpath_exits_on_nonstring
assert_compiles '"1"', %q{
def test(str)
"#{str}"
end

test('foo') # profile as string
test(1)
}, call_threshold: 2
end

def test_objtostring_profiled_nonstring_calls_to_s
assert_compiles '"[1, 2, 3]"', %q{
def test(str)
"#{str}"
end

test([1,2,3]); # profile as nonstring
test([1,2,3]);
}, call_threshold: 2
end

def test_objtostring_profiled_nonstring_guard_exits_when_string
assert_compiles '"foo"', %q{
def test(str)
"#{str}"
end

test([1,2,3]); # profiles as nonstring
test('foo');
}, call_threshold: 2
end

def test_string_bytesize_with_guard
assert_compiles '5', %q{
def test(str)
Expand Down
6 changes: 2 additions & 4 deletions vm_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -261,10 +261,8 @@ union ic_serial_entry {
struct iseq_inline_constant_cache_entry {
VALUE flags;

VALUE value; // v0
VALUE _unused1; // v1
VALUE _unused2; // v2
const rb_cref_t *ic_cref; // v3
VALUE value;
const rb_cref_t *ic_cref;
};
STATIC_ASSERT(sizeof_iseq_inline_constant_cache_entry,
(offsetof(struct iseq_inline_constant_cache_entry, ic_cref) +
Expand Down
2 changes: 0 additions & 2 deletions yjit/src/cruby_bindings.inc.rs

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

2 changes: 1 addition & 1 deletion zjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def print_counters_with_prefix(buf:, stats:, prefix:, prompt:, limit: nil)
counters = stats.select { |key, value| key.start_with?(prefix) && value > 0 }
return if stats.empty?

counters.transform_keys! { |key| key.to_s.delete_prefix!(prefix) }
counters.transform_keys! { |key| key.to_s.delete_prefix(prefix) }
left_pad = counters.keys.map(&:size).max
right_pad = counters.values.map { |value| number_with_delimiter(value).size }.max
total = counters.values.sum
Expand Down
81 changes: 79 additions & 2 deletions zjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
&Insn::IsMethodCfunc { val, cd, cfunc } => gen_is_method_cfunc(jit, asm, opnd!(val), cd, cfunc),
Insn::Test { val } => gen_test(asm, opnd!(val)),
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, state } => gen_guard_bit_equals(jit, asm, opnd!(val), *expected, &function.frame_state(*state)),
Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, invariant, &function.frame_state(*state))),
Insn::CCall { cfun, args, name: _, return_type: _, elidable: _ } => gen_ccall(asm, *cfun, opnds!(args)),
Expand All @@ -397,6 +398,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
Insn::GetGlobal { id, state } => gen_getglobal(jit, asm, *id, &function.frame_state(*state)),
&Insn::GetLocal { ep_offset, level } => gen_getlocal_with_ep(asm, ep_offset, level),
&Insn::SetLocal { val, ep_offset, level } => no_output!(gen_setlocal_with_ep(asm, opnd!(val), function.type_of(val), ep_offset, level)),
&Insn::GetBlockParamProxy { level, state } => gen_get_block_param_proxy(jit, asm, level, &function.frame_state(state)),
Insn::GetConstantPath { ic, state } => gen_get_constant_path(jit, asm, *ic, &function.frame_state(*state)),
Insn::SetIvar { self_val, id, val, state: _ } => no_output!(gen_setivar(asm, opnd!(self_val), *id, opnd!(val))),
Insn::SideExit { state, reason } => no_output!(gen_side_exit(jit, asm, reason, &function.frame_state(*state))),
Expand Down Expand Up @@ -547,6 +549,29 @@ fn gen_setlocal_with_ep(asm: &mut Assembler, val: Opnd, val_type: Type, local_ep
}
}

fn gen_get_block_param_proxy(jit: &JITState, asm: &mut Assembler, level: u32, state: &FrameState) -> lir::Opnd {
// 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));

// Return the rb_block_param_proxy instance (GC root, so put as a number to avoid unnecessary GC tracing)
unsafe { rb_block_param_proxy }.as_u64().into()
}

fn gen_get_constant_path(jit: &JITState, asm: &mut Assembler, ic: *const iseq_inline_constant_cache, state: &FrameState) -> Opnd {
unsafe extern "C" {
fn rb_vm_opt_getconstant_path(ec: EcPtr, cfp: CfpPtr, ic: *const iseq_inline_constant_cache) -> VALUE;
Expand Down Expand Up @@ -1375,6 +1400,26 @@ fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, guard

asm.cmp(klass, Opnd::Value(expected_class));
asm.jne(side_exit);
} else if guard_type.is_subtype(types::String) {
let side = side_exit(jit, state, GuardType(guard_type));

// Check special constant
asm.test(val, Opnd::UImm(RUBY_IMMEDIATE_MASK as u64));
asm.jnz(side.clone());

// Check false
asm.cmp(val, Qfalse.into());
asm.je(side.clone());

let val = match val {
Opnd::Reg(_) | Opnd::VReg { .. } => val,
_ => asm.load(val),
};

let flags = asm.load(Opnd::mem(VALUE_BITS, val, RUBY_OFFSET_RBASIC_FLAGS));
let tag = asm.and(flags, Opnd::UImm(RUBY_T_MASK as u64));
asm.cmp(tag, Opnd::UImm(RUBY_T_STRING as u64));
asm.jne(side);
} else if guard_type.bit_equal(types::HeapObject) {
let side_exit = side_exit(jit, state, GuardType(guard_type));
asm.cmp(val, Opnd::Value(Qfalse));
Expand All @@ -1387,6 +1432,38 @@ fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, guard
val
}

fn gen_guard_type_not(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, guard_type: Type, state: &FrameState) -> lir::Opnd {
if guard_type.is_subtype(types::String) {
// We only exit if val *is* a String. Otherwise we fall through.
let cont = asm.new_label("guard_type_not_string_cont");
let side = side_exit(jit, state, GuardTypeNot(guard_type));

// Continue if special constant (not string)
asm.test(val, Opnd::UImm(RUBY_IMMEDIATE_MASK as u64));
asm.jnz(cont.clone());

// Continue if false (not string)
asm.cmp(val, Qfalse.into());
asm.je(cont.clone());

let val = match val {
Opnd::Reg(_) | Opnd::VReg { .. } => val,
_ => asm.load(val),
};

let flags = asm.load(Opnd::mem(VALUE_BITS, val, RUBY_OFFSET_RBASIC_FLAGS));
let tag = asm.and(flags, Opnd::UImm(RUBY_T_MASK as u64));
asm.cmp(tag, Opnd::UImm(RUBY_T_STRING as u64));
asm.je(side);

// Otherwise (non-string heap object), continue.
asm.write_label(cont);
} else {
unimplemented!("unsupported type: {guard_type}");
}
val
}

/// Compile an identity check with a side exit
fn gen_guard_bit_equals(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, expected: VALUE, state: &FrameState) -> lir::Opnd {
asm.cmp(val, Opnd::Value(expected));
Expand Down Expand Up @@ -1570,12 +1647,12 @@ fn compile_iseq(iseq: IseqPtr) -> Result<Function, CompileError> {
}

/// Build a Target::SideExit for non-PatchPoint instructions
fn side_exit(jit: &mut JITState, state: &FrameState, reason: SideExitReason) -> Target {
fn side_exit(jit: &JITState, state: &FrameState, reason: SideExitReason) -> Target {
build_side_exit(jit, state, reason, None)
}

/// Build a Target::SideExit out of a FrameState
fn build_side_exit(jit: &mut JITState, state: &FrameState, reason: SideExitReason, label: Option<Label>) -> Target {
fn build_side_exit(jit: &JITState, state: &FrameState, reason: SideExitReason, label: Option<Label>) -> Target {
let mut stack = Vec::new();
for &insn_id in state.stack() {
stack.push(jit.get_opnd(insn_id));
Expand Down
37 changes: 18 additions & 19 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