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
8 changes: 1 addition & 7 deletions parse.y
Original file line number Diff line number Diff line change
Expand Up @@ -9174,12 +9174,7 @@ static int
arg_ambiguous(struct parser_params *p, char c)
{
#ifndef RIPPER
if (c == '/') {
rb_warning1("ambiguity between regexp and two divisions: wrap regexp in parentheses or add a space after '%c' operator", WARN_I(c));
}
else {
rb_warning1("ambiguous first argument; put parentheses or a space even after '%c' operator", WARN_I(c));
}
rb_warning1("ambiguous first argument; put parentheses or a space even after '%c' operator", WARN_I(c));
#else
dispatch1(arg_ambiguous, rb_usascii_str_new(&c, 1));
#endif
Expand Down Expand Up @@ -11019,7 +11014,6 @@ parser_yylex(struct parser_params *p)
}
pushback(p, c);
if (IS_SPCARG(c)) {
arg_ambiguous(p, '/');
p->lex.strterm = NEW_STRTERM(str_regexp, '/', 0);
return tREGEXP_BEG;
}
Expand Down
5 changes: 4 additions & 1 deletion prism/prism.c
Original file line number Diff line number Diff line change
Expand Up @@ -11043,7 +11043,10 @@ parser_lex(pm_parser_t *parser) {
}

if (lex_state_spcarg_p(parser, space_seen)) {
pm_parser_warn_token(parser, &parser->current, PM_WARN_AMBIGUOUS_SLASH);
// https://bugs.ruby-lang.org/issues/21994
if (parser->version <= PM_OPTIONS_VERSION_CRUBY_4_0) {
pm_parser_warn_token(parser, &parser->current, PM_WARN_AMBIGUOUS_SLASH);
}
lex_mode_push_regexp(parser, '\0', '/');
LEX(PM_TOKEN_REGEXP_BEGIN);
}
Expand Down
7 changes: 4 additions & 3 deletions test/prism/result/warnings_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ def test_ambiguous_ustar
end

def test_ambiguous_regexp
assert_warning("a /b/", "wrap regexp in parentheses")
assert_warning("a /b/", "wrap regexp in parentheses", compare: false, version: "4.0")
refute_warning("a /b/", compare: false, version: "4.1")
end

def test_ambiguous_ampersand
Expand Down Expand Up @@ -408,8 +409,8 @@ def test_warnings_verbosity
assert_equal "END in method; use at_exit", warning.message
assert_equal :default, warning.level

warning = Prism.parse("foo /regexp/").warnings.first
assert_equal "ambiguous `/`; wrap regexp in parentheses or add a space after `/` operator", warning.message
warning = Prism.parse("foo +1").warnings.first
assert_equal "ambiguous first argument; put parentheses or a space even after `+` operator", warning.message
assert_equal :verbose, warning.level
end

Expand Down
2 changes: 1 addition & 1 deletion test/ripper/test_parser_events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ def test_aref_field

def test_arg_ambiguous
thru_arg_ambiguous = false
parse('m //', :on_arg_ambiguous) {thru_arg_ambiguous = true}
parse('m +1', :on_arg_ambiguous) {thru_arg_ambiguous = true}
assert_equal true, thru_arg_ambiguous
end

Expand Down
7 changes: 0 additions & 7 deletions thread_sync.c
Original file line number Diff line number Diff line change
Expand Up @@ -504,13 +504,6 @@ do_mutex_unlock_safe(VALUE args)
return Qnil;
}

/*
* call-seq:
* mutex.unlock -> self
*
* Releases the lock.
* Raises +ThreadError+ if +mutex+ wasn't locked by the current thread.
*/
VALUE
rb_mutex_unlock(VALUE self)
{
Expand Down
6 changes: 3 additions & 3 deletions thread_sync.rb
Original file line number Diff line number Diff line change
Expand Up @@ -371,10 +371,10 @@ def try_lock
end

# call-seq:
# mutex.lock -> self
# mutex.unlock -> self
#
# Attempts to grab the lock and waits if it isn't available.
# Raises +ThreadError+ if +mutex+ was locked by the current thread.
# Releases the lock.
# Raises +ThreadError+ if +mutex+ wasn't locked by the current thread.
def unlock
Primitive.rb_mut_unlock
end
Expand Down
114 changes: 106 additions & 8 deletions yjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7486,7 +7486,7 @@ fn gen_send_bmethod(
}

let frame_type = VM_FRAME_MAGIC_BLOCK | VM_FRAME_FLAG_BMETHOD | VM_FRAME_FLAG_LAMBDA;
perf_call! { gen_send_iseq(jit, asm, iseq, ci, frame_type, Some(capture.ep), cme, block, flags, argc, None) }
perf_call! { gen_send_iseq(jit, asm, iseq, ci, frame_type, Some(capture.ep), cme, block, flags, argc, None, false) }
}

/// The kind of a value an ISEQ returns
Expand Down Expand Up @@ -7582,6 +7582,10 @@ fn gen_send_iseq(
flags: u32,
argc: i32,
captured_opnd: Option<Opnd>,
// The block param proxy receiver is on the stack below the args. self/EP come from
// the captured block (as for invokeblock); the proxy is shifted off after the
// overflow check so the rest of the frame setup is a plain invokeblock.
proxy_recv: bool,
) -> Option<CodegenStatus> {
// Argument count. We will change this as we gather values from
// sources to satisfy the callee's parameters. To help make sense
Expand Down Expand Up @@ -7747,6 +7751,13 @@ fn gen_send_iseq(
&& !get_iseq_flags_ambiguous_param0(iseq)
};
if block_arg0_splat {
// The block param proxy removes its receiver from the stack only after the
// overflow check (see proxy_recv below). arg0 auto-splat needs a side exit past
// that point, which can't reconstruct the proxy at the opt_send_without_block PC.
if proxy_recv {
gen_counter_incr(jit, asm, Counter::send_bpp_arg0_splat);
return None;
}
// If block_arg0_splat, we still need side exits after splat, but
// the splat modifies the stack which breaks side exits. So bail out.
if splat_call {
Expand Down Expand Up @@ -7876,7 +7887,7 @@ fn gen_send_iseq(
}
IseqReturn::Value(value) => {
// Pop receiver and arguments
asm.stack_pop(argc as usize + if captured_opnd.is_some() { 0 } else { 1 });
asm.stack_pop(argc as usize + if captured_opnd.is_some() && !proxy_recv { 0 } else { 1 });

// Push the return value
let stack_ret = asm.stack_push(Type::from(value));
Expand All @@ -7903,6 +7914,11 @@ fn gen_send_iseq(
asm.cmp(CFP, stack_limit);
asm.jbe(Target::side_exit(Counter::guard_send_se_cf_overflow));

// Remove the block param proxy receiver from the stack, mirroring vm_invoke_block_opt_call
if proxy_recv {
handle_opt_send_shift_stack(asm, argc);
}

if iseq_has_rest && splat_call {
// Insert length guard for a call to copy_splat_args_for_rest_callee()
// that will come later. We will have made changes to
Expand Down Expand Up @@ -9155,9 +9171,11 @@ fn gen_send_general(

// Don't compile calls through singleton classes to avoid retaining the receiver.
// Make an exception for class methods since classes tend to be retained anyways.
// Also compile calls on top_self to help tests.
// Also compile calls on top_self to help tests. The block param proxy is an
// immortal global root, so retaining it is a non-issue.
if VALUE(0) != unsafe { FL_TEST(comptime_recv_klass, VALUE(RUBY_FL_SINGLETON as usize)) }
&& comptime_recv != unsafe { rb_vm_top_self() }
&& comptime_recv != unsafe { rb_block_param_proxy }
&& !unsafe { RB_TYPE_P(comptime_recv, RUBY_T_CLASS) }
&& !unsafe { RB_TYPE_P(comptime_recv, RUBY_T_MODULE) } {
gen_counter_incr(jit, asm, Counter::send_singleton_class);
Expand Down Expand Up @@ -9244,7 +9262,7 @@ fn gen_send_general(
VM_METHOD_TYPE_ISEQ => {
let iseq = unsafe { get_def_iseq_ptr((*cme).def) };
let frame_type = VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL;
return perf_call! { gen_send_iseq(jit, asm, iseq, ci, frame_type, None, cme, block, flags, argc, None) };
return perf_call! { gen_send_iseq(jit, asm, iseq, ci, frame_type, None, cme, block, flags, argc, None, false) };
}
VM_METHOD_TYPE_CFUNC => {
return perf_call! { gen_send_cfunc(
Expand Down Expand Up @@ -9489,8 +9507,7 @@ fn gen_send_general(
return jump_to_next_insn(jit, asm);
}
OPTIMIZED_METHOD_TYPE_BLOCK_CALL => {
gen_counter_incr(jit, asm, Counter::send_optimized_method_block_call);
return None;
return gen_send_block_param_proxy(jit, asm, ci, block, flags, argc);
}
OPTIMIZED_METHOD_TYPE_STRUCT_AREF => {
if flags & VM_CALL_ARGS_SPLAT != 0 {
Expand Down Expand Up @@ -9770,7 +9787,7 @@ fn gen_invokeblock_specialized(
Counter::guard_invokeblock_iseq_block_changed,
);

perf_call! { gen_send_iseq(jit, asm, comptime_iseq, ci, VM_FRAME_MAGIC_BLOCK, None, 0 as _, None, flags, argc, Some(captured_opnd)) }
perf_call! { gen_send_iseq(jit, asm, comptime_iseq, ci, VM_FRAME_MAGIC_BLOCK, None, 0 as _, None, flags, argc, Some(captured_opnd), false) }
} else if comptime_handler.0 & 0x3 == 0x3 { // VM_BH_IFUNC_P
// We aren't handling CALLER_SETUP_ARG and CALLER_REMOVE_EMPTY_KW_SPLAT yet.
if flags & VM_CALL_ARGS_SPLAT != 0 {
Expand Down Expand Up @@ -9831,6 +9848,87 @@ fn gen_invokeblock_specialized(
}
}

// blk.call where blk is the block param proxy. Inline the block invocation the way invokeblock does
// instead of dispatching through the proxy's singleton method. The receiver's singleton class was already
// guarded by the caller, which is enough to prove the receiver is the (unique) block param proxy.
fn gen_send_block_param_proxy(
jit: &mut JITState,
asm: &mut Assembler,
ci: *const rb_callinfo,
block: Option<BlockHandler>,
flags: u32,
argc: i32,
) -> Option<CodegenStatus> {
// Anything fancier than a plain call falls back to dynamic dispatch, which
// materializes a Proc and dispatches normally (also handling redefinition).
if block.is_some() {
gen_counter_incr(jit, asm, Counter::send_bpp_not_simple);
return None;
}
if flags & (VM_CALL_ARGS_SPLAT | VM_CALL_KWARG | VM_CALL_KW_SPLAT | VM_CALL_ARGS_BLOCKARG | VM_CALL_OPT_SEND) != 0 {
gen_counter_incr(jit, asm, Counter::send_bpp_not_simple);
return None;
}

// Fall back to dynamic dispatch if this callsite is megamorphic
if asm.ctx.get_chain_depth() >= SEND_MAX_DEPTH {
gen_counter_incr(jit, asm, Counter::send_bpp_megamorphic);
return None;
}

// The block handler lives in the local EP: the same source both the interpreter's
// vm_call_opt_block_call and the getblockparamproxy instruction read from.
let cfp = jit.get_cfp();
let lep = unsafe { rb_vm_ep_local_ep(get_cfp_ep(cfp)) };
let comptime_handler = unsafe { *lep.offset(VM_ENV_DATA_INDEX_SPECVAL as isize) };

// Only specialize an ISEQ block; other block handler types fall back to dynamic dispatch.
if comptime_handler.0 & 0x3 != 0x1 { // VM_BH_ISEQ_BLOCK_P
gen_counter_incr(jit, asm, Counter::send_bpp_not_iseq_block);
return None;
}

if !assume_bop_not_redefined(jit, asm, PROC_REDEFINED_OP_FLAG, BOP_CALL) {
return None;
}

gen_counter_incr(jit, asm, Counter::send_bpp_dispatch);

asm_comment!(asm, "get local EP");
let ep_opnd = gen_get_lep(jit, asm);
let block_handler_opnd = asm.load(
Opnd::mem(64, ep_opnd, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL)
);

asm_comment!(asm, "guard block_handler type");
let tag_opnd = asm.and(block_handler_opnd, 0x3.into());
asm.cmp(tag_opnd, 0x1.into()); // VM_BH_ISEQ_BLOCK_P
jit_chain_guard(
JCC_JNE,
jit,
asm,
SEND_MAX_DEPTH,
Counter::guard_invokeblock_tag_changed,
);

let comptime_captured = unsafe { ((comptime_handler.0 & !0x3) as *const rb_captured_block).as_ref().unwrap() };
let comptime_iseq = unsafe { *comptime_captured.code.iseq.as_ref() };

asm_comment!(asm, "guard known ISEQ");
let captured_opnd = asm.and(block_handler_opnd, Opnd::Imm(!0x3));
let iseq_opnd = asm.load(Opnd::mem(64, captured_opnd, SIZEOF_VALUE_I32 * 2));
asm.cmp(iseq_opnd, VALUE::from(comptime_iseq).into());
jit_chain_guard(
JCC_JNE,
jit,
asm,
SEND_MAX_DEPTH,
Counter::guard_invokeblock_iseq_block_changed,
);

perf_call! { gen_send_iseq(jit, asm, comptime_iseq, ci, VM_FRAME_MAGIC_BLOCK, None, 0 as _, None, flags, argc, Some(captured_opnd), true) }
}

fn gen_invokesuper(
jit: &mut JITState,
asm: &mut Assembler,
Expand Down Expand Up @@ -10000,7 +10098,7 @@ fn gen_invokesuper_specialized(
VM_METHOD_TYPE_ISEQ => {
let iseq = unsafe { get_def_iseq_ptr((*cme).def) };
let frame_type = VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL;
perf_call! { gen_send_iseq(jit, asm, iseq, ci, frame_type, None, cme, Some(block), ci_flags, argc, None) }
perf_call! { gen_send_iseq(jit, asm, iseq, ci, frame_type, None, cme, Some(block), ci_flags, argc, None, false) }
}
VM_METHOD_TYPE_CFUNC => {
perf_call! { gen_send_cfunc(jit, asm, ci, cme, Some(block), None, ci_flags, argc) }
Expand Down
6 changes: 5 additions & 1 deletion yjit/src/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,11 @@ make_counters! {
send_ivar_set_method,
send_zsuper_method,
send_undef_method,
send_optimized_method_block_call,
send_bpp_dispatch,
send_bpp_not_simple,
send_bpp_megamorphic,
send_bpp_not_iseq_block,
send_bpp_arg0_splat,
send_call_block,
send_call_kwarg,
send_call_multi_ractor,
Expand Down
7 changes: 4 additions & 3 deletions zjit/src/backend/lir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use std::fmt;
use std::mem::take;
use std::rc::Rc;
use crate::bitset::BitSet;
use crate::codegen::{local_size_and_idx_to_ep_offset, perf_symbol_range_start, perf_symbol_range_end, register_with_perf};
use crate::cruby::{IseqPtr, RUBY_OFFSET_CFP_ISEQ, RUBY_OFFSET_CFP_JIT_RETURN, RUBY_OFFSET_CFP_PC, RUBY_OFFSET_CFP_SP, SIZEOF_VALUE_I32, VALUE, ZJIT_STACK_MAP_SHIFT, ZJIT_STACK_MAP_VREG_TAG, vm_stack_canary, YarvInsnIdx, zjit_jit_frame};
use crate::codegen::{perf_symbol_range_start, perf_symbol_range_end, register_with_perf};
use crate::cruby::{IseqPtr, RUBY_OFFSET_CFP_ISEQ, RUBY_OFFSET_CFP_JIT_RETURN, RUBY_OFFSET_CFP_PC, RUBY_OFFSET_CFP_SP, SIZEOF_VALUE_I32, VALUE, ZJIT_STACK_MAP_SHIFT, ZJIT_STACK_MAP_VREG_TAG, vm_stack_canary, YarvInsnIdx, zjit_jit_frame, local_size_and_idx_to_ep_offset};
use crate::hir::{Invariant, SideExitReason};
use crate::hir;
use crate::options::{TraceExits, PerfMap, get_option};
Expand Down Expand Up @@ -2073,8 +2073,9 @@ impl Assembler
} else {
if let Some(allocation) = assignment[active_interval.id] {
if let Some(reg) = allocation.alloc_pool_index(num_registers) {
let was_not_there_before = free_registers.insert(reg);
assert!(
free_registers.insert(reg),
was_not_there_before,
"attempted to return allocator register {:?} to the free pool more than once",
allocation.assigned_reg().unwrap(),
);
Expand Down
Loading