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
37 changes: 26 additions & 11 deletions array.c
Original file line number Diff line number Diff line change
Expand Up @@ -2694,18 +2694,33 @@ ary_enum_length(VALUE ary, VALUE args, VALUE eobj)
return rb_ary_length(ary);
}

// Primitive to avoid a race condition in Array#each.
// Return `true` and write `value` and `index` if the element exists.
static VALUE
ary_fetch_next(VALUE self, VALUE *index, VALUE *value)
// Return true if the index is at or past the end of the array.
VALUE
rb_jit_ary_at_end(rb_execution_context_t *ec, VALUE self, VALUE index)
{
long i = NUM2LONG(*index);
if (i >= RARRAY_LEN(self)) {
return Qfalse;
}
*value = RARRAY_AREF(self, i);
*index = LONG2NUM(i + 1);
return Qtrue;
return FIX2LONG(index) >= RARRAY_LEN(self) ? Qtrue : Qfalse;
}

// Return the element at the given fixnum index.
VALUE
rb_jit_ary_at(rb_execution_context_t *ec, VALUE self, VALUE index)
{
return RARRAY_AREF(self, FIX2LONG(index));
}

// Increment a fixnum by 1.
VALUE
rb_jit_fixnum_inc(rb_execution_context_t *ec, VALUE self, VALUE num)
{
return LONG2FIX(FIX2LONG(num) + 1);
}

// Push a value onto an array and return the value.
VALUE
rb_jit_ary_push(rb_execution_context_t *ec, VALUE self, VALUE ary, VALUE val)
{
rb_ary_push(ary, val);
return val;
}

/*
Expand Down
42 changes: 22 additions & 20 deletions array.rb
Original file line number Diff line number Diff line change
Expand Up @@ -217,15 +217,15 @@ def fetch_values(*indexes, &block)
undef :each

def each # :nodoc:
Primitive.attr! :inline_block, :c_trace
Primitive.attr! :inline_block, :c_trace, :without_interrupts

unless defined?(yield)
return Primitive.cexpr! 'SIZED_ENUMERATOR(self, 0, 0, ary_enum_length)'
end
_i = 0
value = nil
while Primitive.cexpr!(%q{ ary_fetch_next(self, LOCAL_PTR(_i), LOCAL_PTR(value)) })
yield value
i = 0
until Primitive.rb_jit_ary_at_end(i)
yield Primitive.rb_jit_ary_at(i)
i = Primitive.rb_jit_fixnum_inc(i)
end
self
end
Expand All @@ -235,18 +235,18 @@ def each # :nodoc:
undef :map

def map # :nodoc:
Primitive.attr! :inline_block, :c_trace
Primitive.attr! :inline_block, :c_trace, :without_interrupts

unless defined?(yield)
return Primitive.cexpr! 'SIZED_ENUMERATOR(self, 0, 0, ary_enum_length)'
end

_i = 0
value = nil
i = 0
result = Primitive.ary_sized_alloc
while Primitive.cexpr!(%q{ ary_fetch_next(self, LOCAL_PTR(_i), LOCAL_PTR(value)) })
value = yield(value)
Primitive.cexpr!(%q{ rb_ary_push(result, value) })
until Primitive.rb_jit_ary_at_end(i)
value = yield(Primitive.rb_jit_ary_at(i))
Primitive.rb_jit_ary_push(result, value)
i = Primitive.rb_jit_fixnum_inc(i)
end
result
end
Expand All @@ -261,19 +261,20 @@ def map # :nodoc:
undef :select

def select # :nodoc:
Primitive.attr! :inline_block, :c_trace
Primitive.attr! :inline_block, :c_trace, :without_interrupts

unless defined?(yield)
return Primitive.cexpr! 'SIZED_ENUMERATOR(self, 0, 0, ary_enum_length)'
end

_i = 0
value = nil
i = 0
result = Primitive.ary_sized_alloc
while Primitive.cexpr!(%q{ ary_fetch_next(self, LOCAL_PTR(_i), LOCAL_PTR(value)) })
until Primitive.rb_jit_ary_at_end(i)
value = Primitive.rb_jit_ary_at(i)
if yield value
Primitive.cexpr!(%q{ rb_ary_push(result, value) })
Primitive.rb_jit_ary_push(result, value)
end
i = Primitive.rb_jit_fixnum_inc(i)
end
result
end
Expand All @@ -288,15 +289,16 @@ def select # :nodoc:
undef :find

def find(if_none_proc = nil) # :nodoc:
Primitive.attr! :inline_block, :c_trace
Primitive.attr! :inline_block, :c_trace, :without_interrupts

unless defined?(yield)
return Primitive.cexpr! 'SIZED_ENUMERATOR(self, 0, 0, ary_enum_length)'
end
_i = 0
value = nil
while Primitive.cexpr!(%q{ ary_fetch_next(self, LOCAL_PTR(_i), LOCAL_PTR(value)) })
i = 0
until Primitive.rb_jit_ary_at_end(i)
value = Primitive.rb_jit_ary_at(i)
return value if yield(value)
i = Primitive.rb_jit_fixnum_inc(i)
end
if_none_proc&.call
end
Expand Down
20 changes: 20 additions & 0 deletions compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -4433,6 +4433,7 @@ iseq_optimize(rb_iseq_t *iseq, LINK_ANCHOR *const anchor)
ISEQ_COMPILE_DATA(iseq)->option->tailcall_optimization;
const int do_si = ISEQ_COMPILE_DATA(iseq)->option->specialized_instruction;
const int do_ou = ISEQ_COMPILE_DATA(iseq)->option->operands_unification;
const int do_without_ints = ISEQ_BODY(iseq)->builtin_attrs & BUILTIN_ATTR_WITHOUT_INTERRUPTS;
int rescue_level = 0;
int tailcallopt = do_tailcallopt;

Expand Down Expand Up @@ -4465,6 +4466,22 @@ iseq_optimize(rb_iseq_t *iseq, LINK_ANCHOR *const anchor)
insn_operands_unification((INSN *)list);
}

if (do_without_ints) {
INSN *item = (INSN *)list;
if (IS_INSN_ID(item, jump)) {
item->insn_id = BIN(jump_without_ints);
}
else if (IS_INSN_ID(item, branchif)) {
item->insn_id = BIN(branchif_without_ints);
}
else if (IS_INSN_ID(item, branchunless)) {
item->insn_id = BIN(branchunless_without_ints);
}
else if (IS_INSN_ID(item, branchnil)) {
item->insn_id = BIN(branchnil_without_ints);
}
}

if (do_block_optimization) {
INSN * item = (INSN *)list;
// Give up if there is a throw
Expand Down Expand Up @@ -9223,6 +9240,9 @@ compile_builtin_attr(rb_iseq_t *iseq, const NODE *node)
// Let the iseq act like a C method in backtraces
ISEQ_BODY(iseq)->builtin_attrs |= BUILTIN_ATTR_C_TRACE;
}
else if (strcmp(RSTRING_PTR(string), "without_interrupts") == 0) {
ISEQ_BODY(iseq)->builtin_attrs |= BUILTIN_ATTR_WITHOUT_INTERRUPTS;
}
else {
goto unknown_arg;
}
Expand Down
50 changes: 50 additions & 0 deletions insns.def
Original file line number Diff line number Diff line change
Expand Up @@ -1265,6 +1265,56 @@ branchnil
}
}

/* same as jump, but without interrupt check */
DEFINE_INSN
jump_without_ints
(OFFSET dst)
()
()
// attr bool leaf = true;
{
JUMP(dst);
}

/* same as branchif, but without interrupt check */
DEFINE_INSN
branchif_without_ints
(OFFSET dst)
(VALUE val)
()
// attr bool leaf = true;
{
if (RTEST(val)) {
JUMP(dst);
}
}

/* same as branchunless, but without interrupt check */
DEFINE_INSN
branchunless_without_ints
(OFFSET dst)
(VALUE val)
()
// attr bool leaf = true;
{
if (!RTEST(val)) {
JUMP(dst);
}
}

/* same as branchnil, but without interrupt check */
DEFINE_INSN
branchnil_without_ints
(OFFSET dst)
(VALUE val)
()
// attr bool leaf = true;
{
if (NIL_P(val)) {
JUMP(dst);
}
}

/**********************************************************/
/* for optimize */
/**********************************************************/
Expand Down
6 changes: 3 additions & 3 deletions jit_hook.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ class Module
# Internal helper for built-in initializations to define methods only when JIT is enabled.
# This method is removed in jit_undef.rb.
private def with_jit(&block) # :nodoc:
# ZJIT currently doesn't compile Array#each properly, so it's disabled for now.
if defined?(RubyVM::ZJIT) && false # TODO: remove `&& false` (Shopify/ruby#667)
if defined?(RubyVM::ZJIT)
RubyVM::ZJIT.send(:add_jit_hook, block)
elsif defined?(RubyVM::YJIT)
end
if defined?(RubyVM::YJIT)
RubyVM::YJIT.send(:add_jit_hook, block)
end
end
Expand Down
9 changes: 9 additions & 0 deletions lib/rubygems/specification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@
# Starting in RubyGems 2.0, a Specification can hold arbitrary
# metadata. See #metadata for restrictions on the format and size of metadata
# items you may add to a specification.
#
# Specifications must be deterministic, as in the example above. For instance,
# you cannot define attributes conditionally:
#
# # INVALID: do not do this.
# unless RUBY_ENGINE == "jruby"
# s.extensions << "ext/example/extconf.rb"
# end
#

class Gem::Specification < Gem::BasicSpecification
# REFACTOR: Consider breaking out this version stuff into a separate
Expand Down
3 changes: 3 additions & 0 deletions prism_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -3441,6 +3441,9 @@ pm_compile_builtin_attr(rb_iseq_t *iseq, const pm_scope_node_t *scope_node, cons
// Let the iseq act like a C method in backtraces
ISEQ_BODY(iseq)->builtin_attrs |= BUILTIN_ATTR_C_TRACE;
}
else if (strcmp(RSTRING_PTR(string), "without_interrupts") == 0) {
ISEQ_BODY(iseq)->builtin_attrs |= BUILTIN_ATTR_WITHOUT_INTERRUPTS;
}
else {
COMPILE_ERROR(iseq, node_location->line, "unknown argument to attr!: %s", RSTRING_PTR(string));
return COMPILE_NG;
Expand Down
2 changes: 2 additions & 0 deletions proc.c
Original file line number Diff line number Diff line change
Expand Up @@ -2006,6 +2006,8 @@ method_eq(VALUE method, VALUE other)

klass1 = method_entry_defined_class(m1->me);
klass2 = method_entry_defined_class(m2->me);
if (RB_TYPE_P(klass1, T_ICLASS)) klass1 = RBASIC_CLASS(klass1);
if (RB_TYPE_P(klass2, T_ICLASS)) klass2 = RBASIC_CLASS(klass2);

if (!rb_method_entry_eq(m1->me, m2->me) ||
klass1 != klass2 ||
Expand Down
14 changes: 14 additions & 0 deletions test/ruby/test_method.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,20 @@ def foo() :derived; end
end
end

def test_unbound_method_equality_with_extended_module
m = Module.new { def hello; "hello"; end }
base = Class.new { extend m }
sub = Class.new(base)

from_module = m.instance_method(:hello)
from_base = base.method(:hello).unbind
from_sub = sub.method(:hello).unbind

assert_equal(from_module, from_base)
assert_equal(from_module, from_sub)
assert_equal(from_base, from_sub)
end

def test_callee
assert_equal(:test_callee, __method__)
assert_equal(:m, Class.new {def m; __method__; end}.new.m)
Expand Down
15 changes: 15 additions & 0 deletions test/ruby/test_zjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3380,6 +3380,21 @@ def test = RUBY_COPYRIGHT
}, call_threshold: 1, insns: [:opt_getconstant_path]
end

def test_getconstant
assert_compiles '1', %q{
class Foo
CONST = 1
end
def test(klass)
klass::CONST
end
test(Foo)
test(Foo)
}, call_threshold: 2, insns: [:getconstant]
end

def test_expandarray_no_splat
assert_compiles '[3, 4]', %q{
def test(o)
Expand Down
2 changes: 1 addition & 1 deletion tool/mk_builtin_loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

SUBLIBS = {}
REQUIRED = {}
BUILTIN_ATTRS = %w[leaf inline_block use_block c_trace]
BUILTIN_ATTRS = %w[leaf inline_block use_block c_trace without_interrupts]

module CompileWarning
@@warnings = 0
Expand Down
2 changes: 2 additions & 0 deletions vm_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,8 @@ enum rb_builtin_attr {
BUILTIN_ATTR_INLINE_BLOCK = 0x04,
// The iseq acts like a C method in backtraces.
BUILTIN_ATTR_C_TRACE = 0x08,
// The iseq uses noint branch/jump opcodes that skip interrupt checking.
BUILTIN_ATTR_WITHOUT_INTERRUPTS = 0x10,
};

typedef VALUE (*rb_jit_func_t)(struct rb_execution_context_struct *, struct rb_control_frame_struct *);
Expand Down
12 changes: 8 additions & 4 deletions yjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4641,7 +4641,7 @@ fn gen_branchif(
let jump_offset = jit.get_arg(0).as_i32();

// Check for interrupts, but only on backward branches that may create loops
if jump_offset < 0 {
if jump_offset < 0 && jit.get_opcode() != YARVINSN_branchif_without_ints as usize {
gen_check_ints(asm, Counter::branchif_interrupted);
}

Expand Down Expand Up @@ -4693,7 +4693,7 @@ fn gen_branchunless(
let jump_offset = jit.get_arg(0).as_i32();

// Check for interrupts, but only on backward branches that may create loops
if jump_offset < 0 {
if jump_offset < 0 && jit.get_opcode() != YARVINSN_branchunless_without_ints as usize {
gen_check_ints(asm, Counter::branchunless_interrupted);
}

Expand Down Expand Up @@ -4746,7 +4746,7 @@ fn gen_branchnil(
let jump_offset = jit.get_arg(0).as_i32();

// Check for interrupts, but only on backward branches that may create loops
if jump_offset < 0 {
if jump_offset < 0 && jit.get_opcode() != YARVINSN_branchnil_without_ints as usize {
gen_check_ints(asm, Counter::branchnil_interrupted);
}

Expand Down Expand Up @@ -4901,7 +4901,7 @@ fn gen_jump(
let jump_offset = jit.get_arg(0).as_i32();

// Check for interrupts, but only on backward branches that may create loops
if jump_offset < 0 {
if jump_offset < 0 && jit.get_opcode() != YARVINSN_jump_without_ints as usize {
gen_check_ints(asm, Counter::jump_interrupted);
}

Expand Down Expand Up @@ -10777,6 +10777,10 @@ fn get_gen_fn(opcode: VALUE) -> Option<InsnGenFn> {
YARVINSN_branchnil => Some(gen_branchnil),
YARVINSN_throw => Some(gen_throw),
YARVINSN_jump => Some(gen_jump),
YARVINSN_branchif_without_ints => Some(gen_branchif),
YARVINSN_branchunless_without_ints => Some(gen_branchunless),
YARVINSN_branchnil_without_ints => Some(gen_branchnil),
YARVINSN_jump_without_ints => Some(gen_jump),
YARVINSN_opt_new => Some(gen_opt_new),

YARVINSN_getblockparamproxy => Some(gen_getblockparamproxy),
Expand Down
Loading