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
6 changes: 6 additions & 0 deletions .github/workflows/cygwin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,9 @@ jobs:
timeout-minutes: 30
run: make -j4 V=1
shell: C:\cygwin\bin\bash.EXE --noprofile --norc -e -o igncr -o pipefail {0}

- uses: ./.github/actions/slack
with:
label: Cygwin
SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot
if: ${{ failure() }}
5 changes: 4 additions & 1 deletion load.c
Original file line number Diff line number Diff line change
Expand Up @@ -1207,7 +1207,10 @@ load_ext(VALUE path, VALUE fname)
loaded = rb_box_local_extension(box->box_object, fname, path);
}
rb_scope_visibility_set(METHOD_VISI_PUBLIC);
return (VALUE)dln_load_feature(RSTRING_PTR(loaded), RSTRING_PTR(fname));
void *handle = dln_load_feature(RSTRING_PTR(loaded), RSTRING_PTR(fname));
RB_GC_GUARD(loaded);
RB_GC_GUARD(fname);
return (VALUE)handle;
}

static VALUE
Expand Down
6 changes: 5 additions & 1 deletion string.c
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,10 @@ rb_gc_free_fstring(VALUE obj)
{
ASSERT_vm_locking_with_barrier();

RUBY_ASSERT(FL_TEST(obj, RSTRING_FSTR));
RUBY_ASSERT(OBJ_FROZEN(obj));
RUBY_ASSERT(!FL_TEST(obj, STR_SHARED));

rb_concurrent_set_delete_by_identity(fstring_table_obj, obj);

RB_DEBUG_COUNTER_INC(obj_str_fstr);
Expand Down Expand Up @@ -1554,7 +1558,7 @@ rb_str_tmp_frozen_no_embed_acquire(VALUE orig)
* allocated. If the string is shared then the shared root must be
* embedded, so we want to create a copy. If the string is a shared root
* then it must be embedded, so we want to create a copy. */
if (STR_EMBED_P(orig) || FL_TEST_RAW(orig, STR_SHARED | STR_SHARED_ROOT)) {
if (STR_EMBED_P(orig) || FL_TEST_RAW(orig, STR_SHARED | STR_SHARED_ROOT | RSTRING_FSTR)) {
RSTRING(str)->as.heap.ptr = rb_xmalloc_mul_add_mul(sizeof(char), capa, sizeof(char), TERM_LEN(orig));
memcpy(RSTRING(str)->as.heap.ptr, RSTRING_PTR(orig), capa);
}
Expand Down
9 changes: 9 additions & 0 deletions test/ruby/test_string.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3441,6 +3441,15 @@ def test_uminus_no_freeze_not_bare
assert_equal(false, str.frozen?)
end

def test_uminus_no_embed_gc
pad = "a"*2048
("aa".."zz").each do |c|
fstr = -(c + pad).freeze
File.open(IO::NULL, "w").write(fstr)
end
GC.start
end

def test_ord
assert_equal(97, S("a").ord)
assert_equal(97, S("abc").ord)
Expand Down
30 changes: 30 additions & 0 deletions zjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
let out_opnd = match insn {
&Insn::Const { val: Const::Value(val) } => gen_const_value(val),
&Insn::Const { val: Const::CPtr(val) } => gen_const_cptr(val),
&Insn::Const { val: Const::CInt64(val) } => gen_const_long(val),
Insn::Const { .. } => panic!("Unexpected Const in gen_insn: {insn}"),
Insn::NewArray { elements, state } => gen_new_array(asm, opnds!(elements), &function.frame_state(*state)),
Insn::NewHash { elements, state } => gen_new_hash(jit, asm, opnds!(elements), &function.frame_state(*state)),
Expand All @@ -365,6 +366,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
Insn::StringConcat { strings, state, .. } if strings.is_empty() => return Err(*state),
Insn::StringConcat { strings, state } => gen_string_concat(jit, asm, opnds!(strings), &function.frame_state(*state)),
&Insn::StringGetbyteFixnum { string, index } => gen_string_getbyte_fixnum(asm, opnd!(string), opnd!(index)),
Insn::StringSetbyteFixnum { string, index, value } => gen_string_setbyte_fixnum(asm, opnd!(string), opnd!(index), opnd!(value)),
Insn::StringAppend { recv, other, state } => gen_string_append(jit, asm, opnd!(recv), opnd!(other), &function.frame_state(*state)),
Insn::StringIntern { val, state } => gen_intern(asm, opnd!(val), &function.frame_state(*state)),
Insn::ToRegexp { opt, values, state } => gen_toregexp(jit, asm, *opt, opnds!(values), &function.frame_state(*state)),
Expand Down Expand Up @@ -407,12 +409,15 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
&Insn::IsBitNotEqual { left, right } => gen_is_bit_not_equal(asm, opnd!(left), opnd!(right)),
&Insn::BoxBool { val } => gen_box_bool(asm, opnd!(val)),
&Insn::BoxFixnum { val, state } => gen_box_fixnum(jit, asm, opnd!(val), &function.frame_state(state)),
&Insn::UnboxFixnum { val } => gen_unbox_fixnum(asm, opnd!(val)),
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::GuardBlockParamProxy { level, state } => no_output!(gen_guard_block_param_proxy(jit, asm, level, &function.frame_state(state))),
Insn::GuardNotFrozen { val, state } => gen_guard_not_frozen(jit, asm, opnd!(val), &function.frame_state(*state)),
&Insn::GuardLess { left, right, state } => gen_guard_less(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)),
&Insn::GuardGreaterEq { left, right, state } => gen_guard_greater_eq(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)),
Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, invariant, &function.frame_state(*state))),
Insn::CCall { cfunc, args, name: _, return_type: _, elidable: _ } => gen_ccall(asm, *cfunc, opnds!(args)),
// Give up CCallWithFrame for 7+ args since asm.ccall() doesn't support it.
Expand Down Expand Up @@ -571,6 +576,10 @@ fn gen_is_block_given(jit: &JITState, asm: &mut Assembler) -> Opnd {
}
}

fn gen_unbox_fixnum(asm: &mut Assembler, val: Opnd) -> Opnd {
asm.rshift(val, Opnd::UImm(1))
}

/// Get a local variable from a higher scope or the heap. `local_ep_offset` is in number of VALUEs.
/// We generate this instruction with level=0 only when the local variable is on the heap, so we
/// can't optimize the level=0 case using the SP register.
Expand Down Expand Up @@ -642,6 +651,18 @@ fn gen_guard_not_frozen(jit: &JITState, asm: &mut Assembler, val: Opnd, state: &
val
}

fn gen_guard_less(jit: &JITState, asm: &mut Assembler, left: Opnd, right: Opnd, state: &FrameState) -> Opnd {
asm.cmp(left, right);
asm.jge(side_exit(jit, state, SideExitReason::GuardLess));
left
}

fn gen_guard_greater_eq(jit: &JITState, asm: &mut Assembler, left: Opnd, right: Opnd, state: &FrameState) -> Opnd {
asm.cmp(left, right);
asm.jl(side_exit(jit, state, SideExitReason::GuardGreaterEq));
left
}

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 @@ -1047,6 +1068,10 @@ fn gen_const_cptr(val: *const u8) -> lir::Opnd {
Opnd::const_ptr(val)
}

fn gen_const_long(val: i64) -> lir::Opnd {
Opnd::Imm(val)
}

/// Compile a basic block argument
fn gen_param(asm: &mut Assembler, idx: usize) -> lir::Opnd {
// Allocate a register or a stack slot
Expand Down Expand Up @@ -2302,6 +2327,11 @@ fn gen_string_getbyte_fixnum(asm: &mut Assembler, string: Opnd, index: Opnd) ->
asm_ccall!(asm, rb_str_getbyte, string, index)
}

fn gen_string_setbyte_fixnum(asm: &mut Assembler, string: Opnd, index: Opnd, value: Opnd) -> Opnd {
// rb_str_setbyte is not leaf, but we guard types and index ranges in HIR
asm_ccall!(asm, rb_str_setbyte, string, index, value)
}

fn gen_string_append(jit: &mut JITState, asm: &mut Assembler, string: Opnd, val: Opnd, state: &FrameState) -> Opnd {
gen_prepare_non_leaf_call(jit, asm, state);
asm_ccall!(asm, rb_str_buf_append, string, val)
Expand Down
26 changes: 26 additions & 0 deletions zjit/src/cruby_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ pub fn init() -> Annotations {
annotate!(rb_cString, "size", types::Fixnum, no_gc, leaf, elidable);
annotate!(rb_cString, "length", types::Fixnum, no_gc, leaf, elidable);
annotate!(rb_cString, "getbyte", inline_string_getbyte);
annotate!(rb_cString, "setbyte", inline_string_setbyte);
annotate!(rb_cString, "empty?", types::BoolExact, no_gc, leaf, elidable);
annotate!(rb_cString, "<<", inline_string_append);
annotate!(rb_cString, "==", inline_string_eq);
Expand Down Expand Up @@ -338,6 +339,31 @@ fn inline_string_getbyte(fun: &mut hir::Function, block: hir::BlockId, recv: hir
None
}

fn inline_string_setbyte(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option<hir::InsnId> {
let &[index, value] = args else { return None; };
if fun.likely_a(index, types::Fixnum, state) && fun.likely_a(value, types::Fixnum, state) {
let index = fun.coerce_to(block, index, types::Fixnum, state);
let value = fun.coerce_to(block, value, types::Fixnum, state);

let unboxed_index = fun.push_insn(block, hir::Insn::UnboxFixnum { val: index });
let len = fun.push_insn(block, hir::Insn::LoadField {
recv,
id: ID!(len),
offset: RUBY_OFFSET_RSTRING_LEN as i32,
return_type: types::CInt64,
});
let unboxed_index = fun.push_insn(block, hir::Insn::GuardLess { left: unboxed_index, right: len, state });
let zero = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(0) });
let _ = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: unboxed_index, right: zero, state });
let recv = fun.push_insn(block, hir::Insn::GuardNotFrozen { val: recv, state });
let _ = fun.push_insn(block, hir::Insn::StringSetbyteFixnum { string: recv, index, value });
// String#setbyte returns the fixnum provided as its `value` argument back to the caller.
Some(value)
} else {
None
}
}

fn inline_string_append(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option<hir::InsnId> {
let &[other] = args else { return None; };
// Inline only StringExact << String, which matches original type check from
Expand Down
Loading