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 array.c
Original file line number Diff line number Diff line change
Expand Up @@ -1178,6 +1178,7 @@ rb_ary_initialize(int argc, VALUE *argv, VALUE ary)
}
/* recheck after argument conversion */
rb_ary_modify(ary);
ARY_SET_LEN(ary, 0);
ary_resize_capa(ary, len);
if (rb_block_given_p()) {
long i;
Expand Down
6 changes: 5 additions & 1 deletion gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -4211,7 +4211,11 @@ rb_gc_vm_refresh_zombie_pages(void)
void
rb_gc_rest(void)
{
rb_gc_impl_gc_rest(rb_gc_get_objspace());
// Lock to keep assertions happy. This runs right after single-ractor mode is
// cancelled, but we can still free shareables like fstrings because ractor.cnt is 1.
RB_VM_LOCKING() {
rb_gc_impl_gc_rest(rb_gc_get_objspace());
}
}

/* True while a zombie is being absorbed. The zombie's count is decremented before the
Expand Down
12 changes: 0 additions & 12 deletions gc/default/default.c
Original file line number Diff line number Diff line change
Expand Up @@ -5202,16 +5202,6 @@ gc_sweep_page(rb_objspace_t *objspace, rb_heap_t *heap, struct gc_sweep_context
}
}

/* main's local GC is lock-free, but freeing a dead object can mutate VM-global state
* that other Ractors rewrite under the VM lock: weak tables (rb_gc_obj_free_vm_weak_
* references: ci_table, fstring, symbol, cme). (JIT iseq frees are not reached here:
* iseqs are born shareable and a local GC never frees shareable objects.) Wrap the
* page's free loop in a no-barrier VM lock (FIXME). */
const bool sweep_needs_vm_lock =
objspace == global_objspace->main_objspace && rb_gc_multi_ractor_p() && !objspace->flags.during_global_gc;
unsigned int sweep_lock_lev = 0;
if (sweep_needs_vm_lock) sweep_lock_lev = RB_GC_VM_LOCK_NO_BARRIER();

for (int i = 0; i < bitmap_plane_count; i++) {
bitset = ~bits[i];
if (bitset) {
Expand All @@ -5220,8 +5210,6 @@ gc_sweep_page(rb_objspace_t *objspace, rb_heap_t *heap, struct gc_sweep_context
p += BITS_BITLENGTH * slot_size;
}

if (sweep_needs_vm_lock) RB_GC_VM_UNLOCK_NO_BARRIER(sweep_lock_lev);

/* Bulk-clear the freed slots' shareable and shref bits before the freelist is
* published, so a reused slot is clean. Freed slots are exactly the unmarked ones,
* so `bits &= mark_bits` keeps live shareable objects (which must stay pinned) and
Expand Down
2 changes: 1 addition & 1 deletion include/ruby/internal/attr/noalias.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
# /*
# * `::llvm::Attribute::ArgMemOnly` was buggy before. Maybe because nobody
# * actually seriously used it. It seems they somehow mitigated the situation
# * in LLVM 12. Still not found the exact changeset which fiexed the
# * in LLVM 12. Still not found the exact changeset which fixed the
# * attribute, though.
# *
# * :FIXME: others (armclang, xlclang, ...) can also be affected?
Expand Down
1 change: 0 additions & 1 deletion insns.def
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,6 @@ 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
35 changes: 24 additions & 11 deletions jit.c
Original file line number Diff line number Diff line change
Expand Up @@ -736,13 +736,32 @@ rb_jit_reserve_addr_space(uint32_t mem_size)
#if defined(MAP_FIXED_NOREPLACE) && defined(_SC_PAGESIZE)
uint32_t const page_size = (uint32_t)sysconf(_SC_PAGESIZE);
uint8_t *const cfunc_sample_addr = (void *)(uintptr_t)&rb_jit_reserve_addr_space;
uint8_t *const probe_region_end = cfunc_sample_addr + INT32_MAX;
// Align the requested address to page size
uint8_t *req_addr = rb_jit_align_ptr(cfunc_sample_addr, page_size);
// 64MiB: balancing space probed and time spent probing.
const uintptr_t probe_stride = 64 * 1024 * 1024;
// Related to the stride. Any successful trial will be within INT32_MAX
// range with slack for the binary size.
const int max_probe_trials = 30;

// Probe for addresses close to this function using MAP_FIXED_NOREPLACE
// to improve odds of being in range for 32-bit relative call instructions.
do {
uint8_t *req_addr = cfunc_sample_addr;
for (int i = 0; i < max_probe_trials; i++) {
// The address space on x86-64/A64 Linux tends to look like:
//
// high addr +---------------+
// | | [stack] |
// | | DSO text |
// | | [heap] |
// | | main exe text |
// v | 0 |
// low addr +---------------+
//
// We always probe downwards from one of the program text areas
// to avoid getting in the way of the stack's downwards growth.
// If we happen to start from the main text, we also avoid the heap.
req_addr -= probe_stride;
req_addr = rb_jit_align_ptr(req_addr, page_size);

mem_block = mmap(
req_addr,
mem_size,
Expand All @@ -757,13 +776,7 @@ rb_jit_reserve_addr_space(uint32_t mem_size)
ruby_annotate_mmap(mem_block, mem_size, "Ruby:rb_jit_reserve_addr_space");
break;
}

// -4MiB. Downwards to probe away from the heap. (On x86/A64 Linux
// main_code_addr < heap_addr, and in case we are in a shared
// library mapped higher than the heap, downwards is still better
// since it's towards the end of the heap rather than the stack.)
req_addr -= 4 * 1024 * 1024;
} while (req_addr < probe_region_end);
}

// On MacOS and other platforms
#else
Expand Down
10 changes: 6 additions & 4 deletions spec/ruby/core/module/alias_method_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,13 @@ def uno_refined_method
-> { ModuleSpecs::ReopeningModule.foo2 }.should_not.raise(NoMethodError)
end

it "accesses a method defined on Object from Kernel" do
Kernel.public_instance_methods(true).should_not.include?(:module_specs_public_method_on_object)
ruby_version_is ""..."4.2" do
it "accesses a method defined on Object from Kernel" do
Kernel.public_instance_methods(true).should_not.include?(:module_specs_public_method_on_object)

Kernel.public_instance_methods(false).should.include?(:module_specs_alias_on_kernel)
Object.public_instance_methods(true).should.include?(:module_specs_alias_on_kernel)
Kernel.public_instance_methods(false).should.include?(:module_specs_alias_on_kernel)
Object.public_instance_methods(true).should.include?(:module_specs_alias_on_kernel)
end
end

it "can call a method with super aliased twice" do
Expand Down
7 changes: 6 additions & 1 deletion spec/ruby/core/module/fixtures/classes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,12 @@ def module_specs_public_method_on_object_for_kernel_private; end
module Kernel
def module_specs_public_method_on_kernel; end

alias_method :module_specs_alias_on_kernel, :module_specs_public_method_on_object
ruby_version_is ""..."4.2" do
deprecated, Warning[:deprecated] = Warning[:deprecated], false
alias_method :module_specs_alias_on_kernel, :module_specs_public_method_on_object
ensure
Warning[:deprecated] = deprecated
end

public :module_specs_private_method_on_object_for_kernel_public
protected :module_specs_public_method_on_object_for_kernel_protected
Expand Down
1 change: 1 addition & 0 deletions test/ruby/test_array.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2753,6 +2753,7 @@ def test_initialize
assert_equal([1, 1, 1], Array.new(3, 1))
assert_equal([1, 1, 1], Array.new(3) { 1 })
assert_equal([1, 1, 1], assert_warning(/block supersedes default value argument/) {Array.new(3, 1) { 1 }})
assert_equal([], [1].instance_eval { initialize(0) })
end

def test_aset_error
Expand Down
67 changes: 33 additions & 34 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: 0 additions & 1 deletion zjit.c
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,6 @@ 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);
bool rb_vm_once_done_value(ISE is, VALUE *result);

Expand Down
1 change: 0 additions & 1 deletion zjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ 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)
print_counters_with_prefix(prefix: 'inline_reject_', prompt: 'HIR-level inlining rejection reasons', buf:, stats:, limit: 10)

# Show most popular unsupported call features. Because each call can
Expand Down
3 changes: 0 additions & 3 deletions zjit/bindgen/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,6 @@ fn main() {
.allowlist_function("rb_get_cfp_sp")
.allowlist_function("rb_get_cfp_self")
.allowlist_function("rb_get_cfp_ep")
.allowlist_function("rb_get_cfp_ep_level")
.allowlist_function("rb_get_cme_def_type")
.allowlist_function("rb_zjit_vm_search_method")
.allowlist_function("rb_zjit_cme_is_cfunc")
Expand Down Expand Up @@ -460,7 +459,6 @@ 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 All @@ -473,7 +471,6 @@ fn main() {
.allowlist_function("rb_RCLASS_ORIGIN")
.allowlist_function("rb_method_basic_definition_p")
.allowlist_function("rb_obj_class")
.allowlist_function("rb_obj_is_proc")
.allowlist_function("rb_vm_base_ptr")
.allowlist_function("rb_ec_stack_check")
.allowlist_function("rb_vm_top_self")
Expand Down
34 changes: 25 additions & 9 deletions zjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,8 @@ fn gen_iseq(cb: &mut CodeBlock, iseq: IseqPtr, function: Option<&Function>) -> R
Ok(code_ptrs) => {
unsafe { version.as_mut() }.status = IseqStatus::Compiled(code_ptrs.clone());
incr_counter!(compiled_iseq_count);
// Give the new version a fresh budget of recompile exits. See exit_recompile().
payload.num_exits_until_invalidate = get_option!(num_exits_until_invalidate);
}
Err(err) => {
unsafe { version.as_mut() }.status = IseqStatus::CantCompile(err.clone());
Expand Down Expand Up @@ -756,7 +758,8 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
Insn::SetGlobal { id, val, state } => no_output!(gen_setglobal(jit, asm, function, *id, opnd!(val), &function.frame_state(*state))),
Insn::GetGlobal { id, state } => gen_getglobal(jit, asm, function, *id, &function.frame_state(*state)),
&Insn::IsBlockParamModified { flags } => gen_is_block_param_modified(asm, opnd!(flags)),
&Insn::GetBlockParam { ep_offset, level, state } => gen_getblockparam(jit, asm, function, ep_offset, level, &function.frame_state(state)),
&Insn::GetBlockParam { ep_offset, level, state } => gen_getblockparam(jit, asm, function, ep_offset, level, &function.frame_state(state), false),
&Insn::SymToProc { ep_offset, level, state } => gen_getblockparam(jit, asm, function, ep_offset, level, &function.frame_state(state), true),
&Insn::SetLocal { val, ep_offset, level, .. } => no_output!(gen_setlocal(asm, opnd!(val), function.type_of(val), ep_offset, level)),
Insn::GetConstant { klass, id, allow_nil, state } => gen_getconstant(jit, asm, function, opnd!(klass), *id, opnd!(allow_nil), &function.frame_state(*state)),
Insn::GetConstantPath { ic, state } => gen_get_constant_path(jit, asm, function, *ic, &function.frame_state(*state)),
Expand Down Expand Up @@ -898,17 +901,26 @@ fn gen_is_block_param_modified(asm: &mut Assembler, flags: Opnd) -> Opnd {

/// Get the block parameter as a Proc, write it to the environment,
/// and mark the flag as modified.
fn gen_getblockparam(jit: &mut JITState, asm: &mut Assembler, function: &Function, ep_offset: u32, level: u32, state: &FrameState) -> Opnd {
fn gen_getblockparam(jit: &mut JITState, asm: &mut Assembler, function: &Function, ep_offset: u32, level: u32, state: &FrameState, known_symbol: bool) -> Opnd {
unsafe extern "C" {
fn rb_sym_to_proc(sym: VALUE) -> VALUE;
}

gen_prepare_leaf_call_with_gc(asm, state);
// Bail out if write barrier is required.
let ep = gen_get_ep(asm, level);
let flags = Opnd::mem(VALUE_BITS, ep, SIZEOF_VALUE_I32 * (VM_ENV_DATA_INDEX_FLAGS as i32));
asm.test(flags, VM_ENV_FLAG_WB_REQUIRED.into());
asm.jnz(jit, side_exit(jit, function, state, SideExitReason::BlockParamWbRequired));

// Convert block handler to Proc.
// Convert block handler to Proc. When the caller proved the block handler is a symbol,
// VM_BH_TO_SYMBOL() is an identity cast, so call rb_sym_to_proc() on it directly.
let block_handler = asm.load(Opnd::mem(VALUE_BITS, ep, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL));
let proc = asm_ccall!(asm, rb_vm_bh_to_procval, EC, block_handler);
let proc = if known_symbol {
asm_ccall!(asm, rb_sym_to_proc, block_handler)
} else {
asm_ccall!(asm, rb_vm_bh_to_procval, EC, block_handler)
};

let local_ep_offset = c_int::try_from(ep_offset).unwrap_or_else(|_| {
panic!("Could not convert local_ep_offset {ep_offset} to i32")
Expand Down Expand Up @@ -3755,11 +3767,9 @@ c_callable! {
/// the outer ISEQ's version holds the failing guard and must be invalidated to
/// force a recompile. For non-inlined code, it is the same as the frame ISEQ.
///
/// The first exit invalidates the version right away. Invalidation resets the
/// ISEQ's call counter and re-stubs incoming JIT-to-JIT calls, so every entry
/// runs the profiling window in the interpreter before the next compile.
///
/// TODO: Allow waiting for a configured number of exits before invalidating the ISEQ.
/// The version gets invalidated after `--zjit-num-exits-until-invalidate` recompile exits.
/// Invalidation resets the ISEQ's call counter and re-stubs incoming JIT-to-JIT calls,
/// so every entry runs the profiling window in the interpreter before the next compile.
pub(crate) fn exit_recompile(compiled_iseq_raw: VALUE) {
// Fast check before taking the VM lock: skip if the compiled unit is already
// invalidated or at the version limit. This avoids expensive lock acquisition
Expand All @@ -3774,6 +3784,12 @@ c_callable! {
if already_done {
return;
}

// Wait for the configured number of recompile exits before invalidation.
payload.num_exits_until_invalidate = payload.num_exits_until_invalidate.saturating_sub(1);
if payload.num_exits_until_invalidate > 0 {
return;
}
}

with_vm_lock(src_loc!(), || {
Expand Down
Loading