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
2 changes: 1 addition & 1 deletion .github/workflows/zjit-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ jobs:
rustup install ${{ matrix.rust_version }} --profile minimal
rustup default ${{ matrix.rust_version }}

- uses: taiki-e/install-action@3235f8901fd37ffed0052b276cec25a362fb82e9 # v2.77.7
- uses: taiki-e/install-action@e1c4cd42111751368541a7cb5db3522bd1f846a4 # v2.78.0
with:
tool: nextest@0.9
if: ${{ matrix.test_task == 'zjit-check' }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/zjit-ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ jobs:
ruby-version: '3.1'
bundler: none

- uses: taiki-e/install-action@3235f8901fd37ffed0052b276cec25a362fb82e9 # v2.77.7
- uses: taiki-e/install-action@e1c4cd42111751368541a7cb5db3522bd1f846a4 # v2.78.0
with:
tool: nextest@0.9
if: ${{ matrix.test_task == 'zjit-check' }}
Expand Down
30 changes: 19 additions & 11 deletions io_buffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -3378,14 +3378,14 @@ io_buffer_pwrite(int argc, VALUE *argv, VALUE self)
}

static inline void
io_buffer_check_mask(const struct rb_io_buffer *buffer)
io_buffer_check_mask_size(size_t size)
{
if (buffer->size == 0)
if (size == 0)
rb_raise(rb_eIOBufferMaskError, "Zero-length mask given!");
}

static void
memory_and(unsigned char * restrict output, unsigned char * restrict base, size_t size, unsigned char * restrict mask, size_t mask_size)
memory_and(unsigned char * restrict output, const unsigned char * restrict base, size_t size, const unsigned char * restrict mask, size_t mask_size)
{
for (size_t offset = 0; offset < size; offset += 1) {
output[offset] = base[offset] & mask[offset % mask_size];
Expand Down Expand Up @@ -3413,13 +3413,21 @@ io_buffer_and(VALUE self, VALUE mask)
struct rb_io_buffer *mask_buffer = NULL;
TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer);

io_buffer_check_mask(mask_buffer);
const void *base;
size_t size;
io_buffer_get_bytes_for_reading(buffer, &base, &size);

VALUE output = rb_io_buffer_new(NULL, buffer->size, io_flags_for_size(buffer->size));
const void *mask_base;
size_t mask_size;
io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size);

io_buffer_check_mask_size(mask_size);

VALUE output = rb_io_buffer_new(NULL, size, io_flags_for_size(size));
struct rb_io_buffer *output_buffer = NULL;
TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_buffer);

memory_and(output_buffer->base, buffer->base, buffer->size, mask_buffer->base, mask_buffer->size);
memory_and(output_buffer->base, base, size, mask_base, mask_size);

return output;
}
Expand Down Expand Up @@ -3453,7 +3461,7 @@ io_buffer_or(VALUE self, VALUE mask)
struct rb_io_buffer *mask_buffer = NULL;
TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer);

io_buffer_check_mask(mask_buffer);
io_buffer_check_mask_size(mask_buffer->size);

VALUE output = rb_io_buffer_new(NULL, buffer->size, io_flags_for_size(buffer->size));
struct rb_io_buffer *output_buffer = NULL;
Expand Down Expand Up @@ -3493,7 +3501,7 @@ io_buffer_xor(VALUE self, VALUE mask)
struct rb_io_buffer *mask_buffer = NULL;
TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer);

io_buffer_check_mask(mask_buffer);
io_buffer_check_mask_size(mask_buffer->size);

VALUE output = rb_io_buffer_new(NULL, buffer->size, io_flags_for_size(buffer->size));
struct rb_io_buffer *output_buffer = NULL;
Expand Down Expand Up @@ -3590,7 +3598,7 @@ io_buffer_and_inplace(VALUE self, VALUE mask)
struct rb_io_buffer *mask_buffer = NULL;
TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer);

io_buffer_check_mask(mask_buffer);
io_buffer_check_mask_size(mask_buffer->size);
io_buffer_check_overlaps(buffer, mask_buffer);

void *base;
Expand Down Expand Up @@ -3636,7 +3644,7 @@ io_buffer_or_inplace(VALUE self, VALUE mask)
struct rb_io_buffer *mask_buffer = NULL;
TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer);

io_buffer_check_mask(mask_buffer);
io_buffer_check_mask_size(mask_buffer->size);
io_buffer_check_overlaps(buffer, mask_buffer);

void *base;
Expand Down Expand Up @@ -3682,7 +3690,7 @@ io_buffer_xor_inplace(VALUE self, VALUE mask)
struct rb_io_buffer *mask_buffer = NULL;
TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer);

io_buffer_check_mask(mask_buffer);
io_buffer_check_mask_size(mask_buffer->size);
io_buffer_check_overlaps(buffer, mask_buffer);

void *base;
Expand Down
2 changes: 1 addition & 1 deletion test/ruby/test_box.rb
Original file line number Diff line number Diff line change
Expand Up @@ -899,7 +899,7 @@ def test_prelude_gems_and_loaded_features_with_disable_gems
end

def test_calling_root_box_methods_does_not_change_user_boxes_newly_created
assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true, timeout: 60)
begin;
assert_not_include Object.constants.sort, :Find # required by Pathname#find
assert_not_include Ruby::Box.root.eval("Object.constants.sort"), :Find
Expand Down
18 changes: 18 additions & 0 deletions test/ruby/test_io_buffer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,24 @@ def test_inplace_operators
assert_equal IO::Buffer.for("\xce\xcd\xcc\xcb\xce\xcd\xcc\xcb\xce\xcd"), source.dup.not!
end

def test_and_raises_on_freed_self
inner = IO::Buffer.new(IO::Buffer::PAGE_SIZE)
slice = inner.slice(0, 8)
inner.free

mask = IO::Buffer.for("ABCDEFGH")
assert_raise(IO::Buffer::InvalidatedError) { slice & mask }
end

def test_and_raises_on_freed_mask
inner = IO::Buffer.new(IO::Buffer::PAGE_SIZE)
mask_slice = inner.slice(0, 8)
inner.free

source = IO::Buffer.for("ABCDEFGH")
assert_raise(IO::Buffer::InvalidatedError) { source & mask_slice }
end

def test_bit_count
# All ones: 8 bits set per byte
assert_equal 8, IO::Buffer.for("\xFF").bit_count
Expand Down
1 change: 0 additions & 1 deletion tool/rdoc-srcdir
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

require 'rubygems'
require 'rdoc/rdoc'
require 'rbs'

# Make only the output directory relative to the invoked directory.
invoked = Dir.pwd
Expand Down
26 changes: 18 additions & 8 deletions zjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -707,11 +707,11 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
Insn::SetIvar { self_val, id, ic, val, state } => no_output!(gen_setivar(jit, asm, opnd!(self_val), *id, *ic, opnd!(val), &function.frame_state(*state))),
Insn::FixnumBitCheck { val, index } => gen_fixnum_bit_check(asm, opnd!(val), *index),
Insn::SideExit { state, reason, recompile } => no_output!(gen_side_exit(jit, asm, reason, *recompile, &function.frame_state(*state))),
Insn::PutSpecialObject { value_type } => gen_putspecialobject(asm, *value_type),
Insn::PutSpecialObject { value_type, state } => gen_putspecialobject(jit, asm, *value_type, &function.frame_state(*state)),
Insn::AnyToString { val, str, state } => gen_anytostring(asm, opnd!(val), opnd!(str), &function.frame_state(*state)),
Insn::Defined { op_type, obj, pushval, v, lep_level, state } => gen_defined(jit, asm, *op_type, *obj, *pushval, opnd!(v), *lep_level, &function.frame_state(*state)),
Insn::CheckMatch { target, pattern, flag, state } => gen_checkmatch(jit, asm, opnd!(target), opnd!(pattern), *flag, &function.frame_state(*state)),
Insn::GetSpecialSymbol { symbol_type, state: _ } => gen_getspecial_symbol(asm, *symbol_type),
Insn::GetSpecialSymbol { symbol_type, state } => gen_getspecial_symbol(asm, *symbol_type, &function.frame_state(*state)),
Insn::GetSpecialNumber { nth, state } => gen_getspecial_number(asm, *nth, &function.frame_state(*state)),
&Insn::IncrCounter(counter) => no_output!(gen_incr_counter(asm, counter)),
Insn::IncrCounterPtr { counter_ptr } => no_output!(gen_incr_counter_ptr(asm, *counter_ptr)),
Expand Down Expand Up @@ -1219,17 +1219,26 @@ fn gen_side_exit(jit: &mut JITState, asm: &mut Assembler, reason: &SideExitReaso
}

/// Emit a special object lookup
fn gen_putspecialobject(asm: &mut Assembler, value_type: SpecialObjectType) -> Opnd {
fn gen_putspecialobject(jit: &JITState, asm: &mut Assembler, value_type: SpecialObjectType, state: &FrameState) -> Opnd {
// rb_vm_get_special_object for CBASE/CONST_BASE can call rb_singleton_class,
// which allocates (may trigger GC) and can raise TypeError on non-class
// receivers (e.g. `123.instance_eval { Const = 1 }`). Treat as non-leaf so
// the PC is saved for GC and stack/locals are spilled for rescue.
gen_prepare_non_leaf_call(jit, asm, state);

// Get the EP of the current CFP and load it into a register
let ep_opnd = Opnd::mem(64, CFP, RUBY_OFFSET_CFP_EP);
let ep_reg = asm.load(ep_opnd);

asm_ccall!(asm, rb_vm_get_special_object, ep_reg, Opnd::UImm(u64::from(value_type)))
}

fn gen_getspecial_symbol(asm: &mut Assembler, symbol_type: SpecialBackrefSymbol) -> Opnd {
// Fetch a "special" backref based on the symbol type
fn gen_getspecial_symbol(asm: &mut Assembler, symbol_type: SpecialBackrefSymbol, state: &FrameState) -> Opnd {
// rb_backref_get reaches rb_vm_svar_lep, which calls CFP_PC/CFP_ISEQ on the
// current frame, so the PC must be saved before the call.
gen_prepare_leaf_call_with_gc(asm, state);

// Fetch a "special" backref based on the symbol type
let backref = asm_ccall!(asm, rb_backref_get,);

match symbol_type {
Expand All @@ -1249,12 +1258,13 @@ fn gen_getspecial_symbol(asm: &mut Assembler, symbol_type: SpecialBackrefSymbol)
}

fn gen_getspecial_number(asm: &mut Assembler, nth: u64, state: &FrameState) -> Opnd {
// Fetch the N-th match from the last backref based on type shifted by 1
// rb_backref_get reaches rb_vm_svar_lep, which calls CFP_PC/CFP_ISEQ on the
// current frame, so the PC must be saved before the call.
gen_prepare_leaf_call_with_gc(asm, state);

// Fetch the N-th match from the last backref based on type shifted by 1
let backref = asm_ccall!(asm, rb_backref_get,);

gen_prepare_leaf_call_with_gc(asm, state);

asm_ccall!(asm, rb_reg_nth_match, Opnd::Imm((nth >> 1).try_into().unwrap()), backref)
}

Expand Down
46 changes: 46 additions & 0 deletions zjit/src/codegen_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4205,6 +4205,52 @@ fn test_getspecial_multiple_groups() {
assert_snapshot!(assert_compiles(r#"test("123-456")"#), @r#""456""#);
}

// In a JIT-to-JIT call, gen_push_frame writes JIT_RETURN_POISON to the
// callee's cfp->jit_return (runtime_checks builds). On the *first* such
// call the function stub trampoline clears jit_return to NULL, so the
// crash only manifests on the second JIT-to-JIT hit when the stub has
// been patched to jump directly to the callee's JIT entry. Putting $& as
// the first C call in the callee keeps the poison live until
// gen_getspecial_symbol calls rb_backref_get → rb_vm_svar_lep → CFP_PC →
// CFP_ZJIT_FRAME, which dereferences the poison without the prep fix.
#[test]
fn test_getspecial_symbol_in_jit_to_jit_callee() {
eval(r#"
def callee = $&
def caller_method = callee

# Warm up callee so it JITs
callee
callee

# First call to caller_method profiles; second JITs caller_method
# and runs through the function-stub-hit path which clears
# jit_return. The third call goes through the patched stub with
# POISON intact, hitting the bug.
caller_method
caller_method
"#);
assert_contains_opcode("callee", YARVINSN_getspecial);
assert_snapshot!(assert_compiles("caller_method"), @"nil");
}

// Same JIT-to-JIT setup, exercising gen_getspecial_number ($N).
#[test]
fn test_getspecial_number_in_jit_to_jit_callee() {
eval(r#"
def callee = $1
def caller_method = callee

callee
callee

caller_method
caller_method
"#);
assert_contains_opcode("callee", YARVINSN_getspecial);
assert_snapshot!(assert_compiles("caller_method"), @"nil");
}

#[test]
fn test_profile_under_nested_jit_call() {
assert_snapshot!(inspect("
Expand Down
8 changes: 4 additions & 4 deletions zjit/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -856,7 +856,7 @@ pub enum Insn {
ToRegexp { opt: usize, values: Vec<InsnId>, state: InsnId },

/// Put special object (VMCORE, CBASE, etc.) based on value_type
PutSpecialObject { value_type: SpecialObjectType },
PutSpecialObject { value_type: SpecialObjectType, state: InsnId },

/// Call `to_a` on `val` if the method is defined, or make a new array `[val]` otherwise.
ToArray { val: InsnId, state: InsnId },
Expand Down Expand Up @@ -1205,7 +1205,6 @@ macro_rules! for_each_operand_impl {
| Insn::GetEP { .. }
| Insn::LoadSelf
| Insn::BreakPoint | Insn::Unreachable
| Insn::PutSpecialObject { .. }
| Insn::IncrCounter(_)
| Insn::IncrCounterPtr { .. } => {}

Expand All @@ -1222,6 +1221,7 @@ macro_rules! for_each_operand_impl {
}
Insn::PatchPoint { state, .. }
| Insn::CheckInterrupts { state }
| Insn::PutSpecialObject { state, .. }
| Insn::GetBlockParam { state, .. }
| Insn::GetConstantPath { state, .. } => {
$visit_one!(state);
Expand Down Expand Up @@ -2224,7 +2224,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
write!(f, "SideExit {reason}")
}
}
Insn::PutSpecialObject { value_type } => write!(f, "PutSpecialObject {value_type}"),
Insn::PutSpecialObject { value_type, .. } => write!(f, "PutSpecialObject {value_type}"),
Insn::Throw { throw_state, val, .. } => {
write!(f, "Throw ")?;
match throw_state & VM_THROW_STATE_MASK {
Expand Down Expand Up @@ -6830,7 +6830,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
let insn = if value_type == SpecialObjectType::VMCore {
Insn::Const { val: Const::Value(unsafe { rb_mRubyVMFrozenCore }) }
} else {
Insn::PutSpecialObject { value_type }
Insn::PutSpecialObject { value_type, state: exit_id }
};
state.stack_push(fun.push_insn(block, insn));
}
Expand Down