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
26 changes: 26 additions & 0 deletions bootstraptest/test_yjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2483,6 +2483,32 @@ def foo
B.new.foo
}

# invokesuper in a weird block
assert_equal '["block->A#itself", "block->singleton#itself"]', %q{
# This test runs the same block as first as a block and then as a method,
# testing the routine that finds the currently running method, which is
# relevant for `super`.
class BlockIseqDuality
prepend(Module.new do
def itself
nested = -> { "block->" + super() }
@singleton_itself.define_singleton_method(:itself, &nested)
nested
end
end)

attr_reader :singleton_itself
def initialize = (@singleton_itself = "singleton#itself")

def itself = "A#itself"
end

tester = BlockIseqDuality.new
super_lambda = tester.itself
super_lambda.call # warmup
[super_lambda.call, tester.singleton_itself.itself]
}

# invokesuper zsuper in a bmethod
assert_equal 'ok', %q{
class Foo
Expand Down
3 changes: 3 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -4342,6 +4342,9 @@ AS_IF([test -n "${LIBS}"], [
MAINFLAGS=`echo " $MAINLIBS " | sed "s|$libspat"'||;s/^ *//;s/ *$//'`
])
LIBRUBYARG_STATIC="${LIBRUBYARG_STATIC} \$(MAINLIBS)"
AS_IF([test "$enable_shared" = yes], [
LIBRUBYARG_SHARED="${LIBRUBYARG_SHARED} \$(MAINLIBS)"
])
CPPFLAGS="$CPPFLAGS "'$(DEFS) ${cppflags}'
AS_IF([test -n "${cflags+set}"], [
cflagspat=`eval echo '"'"${cflags}"'"' | sed 's/[[][|.*]]/\\&/g;s/^ */ /;s/^ *$/ /'`
Expand Down
31 changes: 15 additions & 16 deletions set.c
Original file line number Diff line number Diff line change
Expand Up @@ -362,21 +362,20 @@ set_compact_after_delete(VALUE set)
}

static int
set_table_insert_wb(set_table *tab, VALUE set, VALUE key, VALUE *key_addr)
set_table_insert_wb(set_table *tab, VALUE set, VALUE key)
{
if (tab->type != &identhash && rb_obj_class(key) == rb_cString && !RB_OBJ_FROZEN(key)) {
key = rb_hash_key_str(key);
if (key_addr) *key_addr = key;
}
int ret = set_insert(tab, (st_data_t)key);
if (ret == 0) RB_OBJ_WRITTEN(set, Qundef, key);
return ret;
}

static int
set_insert_wb(VALUE set, VALUE key, VALUE *key_addr)
set_insert_wb(VALUE set, VALUE key)
{
return set_table_insert_wb(RSET_TABLE(set), set, key, key_addr);
return set_table_insert_wb(RSET_TABLE(set), set, key);
}

static VALUE
Expand Down Expand Up @@ -413,7 +412,7 @@ set_s_create(int argc, VALUE *argv, VALUE klass)
int i;

for (i=0; i < argc; i++) {
set_table_insert_wb(table, set, argv[i], NULL);
set_table_insert_wb(table, set, argv[i]);
}

return set;
Expand Down Expand Up @@ -464,15 +463,15 @@ static VALUE
set_initialize_without_block(RB_BLOCK_CALL_FUNC_ARGLIST(i, set))
{
VALUE element = i;
set_insert_wb(set, element, &element);
set_insert_wb(set, element);
return element;
}

static VALUE
set_initialize_with_block(RB_BLOCK_CALL_FUNC_ARGLIST(i, set))
{
VALUE element = rb_yield(i);
set_insert_wb(set, element, &element);
set_insert_wb(set, element);
return element;
}

Expand Down Expand Up @@ -513,7 +512,7 @@ set_i_initialize(int argc, VALUE *argv, VALUE set)
for (i=0; i<RARRAY_LEN(other); i++) {
VALUE key = RARRAY_AREF(other, i);
if (block_given) key = rb_yield(key);
set_table_insert_wb(into, set, key, NULL);
set_table_insert_wb(into, set, key);
}
}
else {
Expand Down Expand Up @@ -693,7 +692,7 @@ set_i_add(VALUE set, VALUE item)
}
}
else {
set_insert_wb(set, item, NULL);
set_insert_wb(set, item);
}
return set;
}
Expand All @@ -720,7 +719,7 @@ set_i_add_p(VALUE set, VALUE item)
return Qnil;
}
else {
return set_insert_wb(set, item, NULL) ? Qnil : set;
return set_insert_wb(set, item) ? Qnil : set;
}
}

Expand Down Expand Up @@ -1002,7 +1001,7 @@ set_intersection_i(st_data_t key, st_data_t tmp)
{
struct set_intersection_data *data = (struct set_intersection_data *)tmp;
if (set_table_lookup(data->other, key)) {
set_table_insert_wb(data->into, data->set, key, NULL);
set_table_insert_wb(data->into, data->set, key);
}

return ST_CONTINUE;
Expand Down Expand Up @@ -1098,15 +1097,15 @@ static int
set_merge_i(st_data_t key, st_data_t data)
{
struct set_merge_args *args = (struct set_merge_args *)data;
set_table_insert_wb(args->into, args->set, key, NULL);
set_table_insert_wb(args->into, args->set, key);
return ST_CONTINUE;
}

static VALUE
set_merge_block(RB_BLOCK_CALL_FUNC_ARGLIST(key, set))
{
VALUE element = key;
set_insert_wb(set, element, &element);
set_insert_wb(set, element);
return element;
}

Expand All @@ -1124,7 +1123,7 @@ set_merge_enum_into(VALUE set, VALUE arg)
long i;
set_table *into = RSET_TABLE(set);
for (i=0; i<RARRAY_LEN(arg); i++) {
set_table_insert_wb(into, set, RARRAY_AREF(arg, i), NULL);
set_table_insert_wb(into, set, RARRAY_AREF(arg, i));
}
}
else {
Expand Down Expand Up @@ -1250,7 +1249,7 @@ set_xor_i(st_data_t key, st_data_t data)
VALUE element = (VALUE)key;
VALUE set = (VALUE)data;
set_table *table = RSET_TABLE(set);
if (set_table_insert_wb(table, set, element, &element)) {
if (set_table_insert_wb(table, set, element)) {
set_table_delete(table, &element);
}
return ST_CONTINUE;
Expand Down Expand Up @@ -1386,7 +1385,7 @@ set_i_each(VALUE set)
static int
set_collect_i(st_data_t key, st_data_t data)
{
set_insert_wb((VALUE)data, rb_yield((VALUE)key), NULL);
set_insert_wb((VALUE)data, rb_yield((VALUE)key));
return ST_CONTINUE;
}

Expand Down
2 changes: 1 addition & 1 deletion template/Makefile.in
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ miniruby$(EXEEXT):
$(PROGRAM):
@$(RM) $@
$(ECHO) linking $@
$(Q) $(PURIFY) $(CC) $(EXE_LDFLAGS) $(XLDFLAGS) $(MAINOBJ) $(EXTOBJS) $(LIBRUBYARG) $(MAINLIBS) $(EXTLIBS) $(OUTFLAG)$@
$(Q) $(PURIFY) $(CC) $(EXE_LDFLAGS) $(XLDFLAGS) $(MAINOBJ) $(EXTOBJS) $(LIBRUBYARG) $(EXTLIBS) $(OUTFLAG)$@
$(Q) $(POSTLINK)

$(PROGRAM): @XRUBY_LIBPATHENV_WRAPPER@
Expand Down
34 changes: 34 additions & 0 deletions test/ruby/test_yjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,40 @@ def to_s
RUBY
end

def test_super_bmethod
# Bmethod defined at class scope
assert_compiles(<<~'RUBY', insns: %i[invokesuper], result: true, exits: {})
class SuperItself
define_method(:itself) { super() }
end

obj = SuperItself.new
obj.itself
obj.itself == obj
RUBY

# Bmethod defined inside a method (the block's local_iseq is ISEQ_TYPE_METHOD
# but the CME is at the bmethod frame, not the enclosing method's frame)
assert_compiles(<<~'RUBY', insns: %i[invokesuper], result: "Base#foo via bmethod", exits: {})
class Base
def foo = "Base#foo"
end

class SetupHelper
def add_bmethod_to(klass)
klass.define_method(:foo) { super() + " via bmethod" }
end
end

class Target < Base; end

SetupHelper.new.add_bmethod_to(Target)
obj = Target.new
obj.foo
obj.foo
RUBY
end

# Tests calling a variadic cfunc with many args
def test_build_large_struct
assert_compiles(<<~RUBY, insns: %i[opt_send_without_block], call_threshold: 2)
Expand Down
113 changes: 85 additions & 28 deletions yjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2425,22 +2425,83 @@ fn gen_get_ep(asm: &mut Assembler, level: u32) -> Opnd {
ep_opnd
}

// Gets the EP of the ISeq of the containing method, or "local level".
// Equivalent of GET_LEP() macro.
/// Get the EP of the ISeq of the containing method, or "local level EP".
/// Equivalent to `GET_LEP()` with a constraint:
/// `ISEQ_TYPE_METHOD == iseq_under_compilation->body->local_iseq->body->type`.
/// No bad memory access happens when this condition is not met, just that the
/// EP returned may not be `VM_ENV_FLAG_LOCAL`. Practically, ISeqs that don't
/// meet this condition, such as `ISEQ_TYPE_TOP`, also don't need to use this operation.
fn gen_get_lep(jit: &JITState, asm: &mut Assembler) -> Opnd {
// Equivalent of get_lvar_level() in compile.c
fn get_lvar_level(iseq: IseqPtr) -> u32 {
if iseq == unsafe { rb_get_iseq_body_local_iseq(iseq) } {
0
} else {
1 + get_lvar_level(unsafe { rb_get_iseq_body_parent_iseq(iseq) })
}
}

let level = get_lvar_level(jit.get_iseq());
// GET_LEP() chases the parent environment pointer to reach the local environment. Inside
// a descendant of ISEQ_TYPE_METHOD, it chases the same number of times as chasing
// the parent iseq pointer to reach the local iseq. See get_lvar_level() in compile.c
let mut iseq = jit.get_iseq();
let local_iseq = unsafe { rb_get_iseq_body_local_iseq(iseq) };
let mut level = 0;
while iseq != local_iseq {
iseq = unsafe { rb_get_iseq_body_parent_iseq(iseq) };
level += 1;
}
asm_comment!(asm, "get_lep(level: {level})");
gen_get_ep(asm, level)
}

/// Load the value in the ME_CREF slot of the EP that contains the running CME.
/// Returns the slot value directly, which is only useful for guarding that the
/// CME has not changed. Unlike rb_vm_frame_method_entry(), we never dereference
/// `ep[VM_ENV_DATA_INDEX_ME_CREF]` but rather rely on `ep[VM_ENV_DATA_INDEX_FLAGS]`
/// to terminate the search, since we load the flags anyways for EP hopping.
/// When `ep[VM_ENV_DATA_INDEX_ME_CREF]` is not a CME, it can't match the expected
/// CME, and the guard fails.
fn gen_get_running_cme_or_sentinal(jit: &JITState, asm: &mut Assembler) -> Opnd {
// When not in a block, the running CME is at `cfp->ep`.
if jit.iseq == unsafe { rb_get_iseq_body_local_iseq(jit.iseq) } {
asm_comment!(asm, "cfp->ep[VM_ENV_DATA_INDEX_ME_CREF]");
let lep_opnd = gen_get_ep(asm, 0);
Opnd::mem(
VALUE_BITS,
lep_opnd,
SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_ME_CREF,
)
} else {
// Loop through EPs to find the one containing the CME.
// Stop when we find an EP with VM_FRAME_FLAG_BMETHOD (bmethod frame with CME)
// or VM_ENV_FLAG_LOCAL (local frame which is METHOD/CFUNC/IFUNC with CME).
//
// We cannot unroll to a static hop count like gen_get_lep() because bmethods
// defined inside methods may have a CME that lives at a EP cloers to the
// starting EP than at the local and final EP level. Each level of nesting can
// dynamically run with and without VM_FRAME_FLAG_BMETHOD set.
asm_comment!(asm, "search for running cme");
let loop_label = asm.new_label("cme_loop");
let done_label = asm.new_label("cme_done");

let ep_opnd = Opnd::mem(VALUE_BITS, CFP, RUBY_OFFSET_CFP_EP);
let ep_opnd = asm.load(ep_opnd);

asm.write_label(loop_label);
// Load flags from ep[VM_ENV_DATA_INDEX_FLAGS]
let flags = asm.load(Opnd::mem(VALUE_BITS, ep_opnd, SIZEOF_VALUE_I32 * (VM_ENV_DATA_INDEX_FLAGS as i32)));
// Check if VM_FRAME_FLAG_BMETHOD or VM_ENV_FLAG_LOCAL is set.
// If either is set, this EP contains the CME.
let check_flags = (VM_FRAME_FLAG_BMETHOD | VM_ENV_FLAG_LOCAL).as_usize();
asm.test(flags, check_flags.into());
asm.jnz(done_label);
// Get the previous EP from the current EP
// See GET_PREV_EP(ep) macro
// VALUE *prev_ep = ((VALUE *)((ep)[VM_ENV_DATA_INDEX_SPECVAL] & ~0x03))
let offs = SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL;
let next_ep_opnd = asm.load(Opnd::mem(VALUE_BITS, ep_opnd, offs));
let next_ep_opnd = asm.and(next_ep_opnd, (!0x03_i64).into());
asm.load_into(ep_opnd, next_ep_opnd);
asm.jmp(loop_label);
asm.write_label(done_label);

// Load and return the CME from ep[VM_ENV_DATA_INDEX_ME_CREF]
asm.load(Opnd::mem(VALUE_BITS, ep_opnd, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_ME_CREF))
}
}

fn gen_getlocal_generic(
jit: &mut JITState,
asm: &mut Assembler,
Expand Down Expand Up @@ -9832,6 +9893,15 @@ fn gen_invokesuper_specialized(
return None;
}

let ci = unsafe { get_call_data_ci(cd) };
let ci_flags = unsafe { vm_ci_flag(ci) };

// Bail on ZSUPER inside a block method. They always raise.
if ci_flags & VM_CALL_ZSUPER != 0 && VM_METHOD_TYPE_BMETHOD == unsafe { get_cme_def_type(me) } {
gen_counter_incr(jit, asm, Counter::invokesuper_bmethod_zsuper);
return None;
}

// FIXME: We should track and invalidate this block when this cme is invalidated
let current_defined_class = unsafe { (*me).defined_class };
let mid = unsafe { get_def_original_id((*me).def) };
Expand All @@ -9847,14 +9917,8 @@ fn gen_invokesuper_specialized(
let comptime_superclass =
unsafe { rb_class_get_superclass(RCLASS_ORIGIN(current_defined_class)) };

let ci = unsafe { get_call_data_ci(cd) };
let argc: i32 = unsafe { vm_ci_argc(ci) }.try_into().unwrap();

let ci_flags = unsafe { vm_ci_flag(ci) };

// Don't JIT calls that aren't simple
// Note, not using VM_CALL_ARGS_SIMPLE because sometimes we pass a block.

if ci_flags & VM_CALL_KWARG != 0 {
gen_counter_incr(jit, asm, Counter::invokesuper_kwarg);
return None;
Expand All @@ -9873,6 +9937,7 @@ fn gen_invokesuper_specialized(
// cheaper calculations first, but since we specialize on the method entry
// and so only have to do this once at compile time this is fine to always
// check and side exit.
let argc: i32 = unsafe { vm_ci_argc(ci) }.try_into().unwrap();
let comptime_recv = jit.peek_at_stack(&asm.ctx, argc as isize);
if unsafe { rb_obj_is_kind_of(comptime_recv, current_defined_class) } == VALUE(0) {
gen_counter_incr(jit, asm, Counter::invokesuper_defined_class_mismatch);
Expand Down Expand Up @@ -9900,16 +9965,8 @@ fn gen_invokesuper_specialized(
return None;
}

asm_comment!(asm, "guard known me");
let lep_opnd = gen_get_lep(jit, asm);
let ep_me_opnd = Opnd::mem(
64,
lep_opnd,
SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_ME_CREF,
);

let me_as_value = VALUE(me as usize);
asm.cmp(ep_me_opnd, me_as_value.into());
let cme_opnd = gen_get_running_cme_or_sentinal(jit, asm);
asm.cmp(cme_opnd, VALUE::from(me).into());
jit_chain_guard(
JCC_JNE,
jit,
Expand Down
1 change: 1 addition & 0 deletions yjit/src/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ make_counters! {
invokesuper_megamorphic,
invokesuper_no_cme,
invokesuper_no_me,
invokesuper_bmethod_zsuper,
invokesuper_not_iseq_or_cfunc,
invokesuper_refinement,
invokesuper_singleton_class,
Expand Down
Loading