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 depend
Original file line number Diff line number Diff line change
Expand Up @@ -12702,6 +12702,7 @@ ractor.$(OBJEXT): {$(VPATH)}vm_debug.h
ractor.$(OBJEXT): {$(VPATH)}vm_opts.h
ractor.$(OBJEXT): {$(VPATH)}vm_sync.h
ractor.$(OBJEXT): {$(VPATH)}yjit.h
ractor.$(OBJEXT): {$(VPATH)}zjit.h
random.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h
random.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
random.$(OBJEXT): $(CCAN_DIR)/list/list.h
Expand Down
15 changes: 9 additions & 6 deletions ext/objspace/objspace_dump.c
Original file line number Diff line number Diff line change
Expand Up @@ -451,13 +451,16 @@ dump_object(VALUE obj, struct dump_config *dc)
break;

case imemo_callcache:
mid = vm_cc_cme((const struct rb_callcache *)obj)->called_id;
if (mid != 0) {
dump_append(dc, ", \"called_id\":");
dump_append_id(dc, mid);

{
VALUE klass = ((const struct rb_callcache *)obj)->klass;
if (klass != 0) {
if (klass != Qundef) {
mid = vm_cc_cme((const struct rb_callcache *)obj)->called_id;
if (mid != 0) {
dump_append(dc, ", \"called_id\":");
dump_append_id(dc, mid);

}

dump_append(dc, ", \"receiver_class\":");
dump_append_ref(dc, klass);
}
Expand Down
33 changes: 21 additions & 12 deletions imemo.c
Original file line number Diff line number Diff line change
Expand Up @@ -337,28 +337,37 @@ rb_imemo_mark_and_move(VALUE obj, bool reference_updating)
* cc->klass (klass) should not be marked because if the klass is
* free'ed, the cc->klass will be cleared by `vm_cc_invalidate()`.
*
* cc->cme (cme) should not be marked because if cc is invalidated
* when cme is free'ed.
* For "normal" CCs cc->cme (cme) should not be marked because the cc is
* invalidated through the klass when the cme is free'd.
* - klass marks cme if klass uses cme.
* - caller classe's ccs->cme marks cc->cme.
* - if cc is invalidated (klass doesn't refer the cc),
* cc is invalidated by `vm_cc_invalidate()` and cc->cme is
* not be accessed.
* - On the multi-Ractors, cme will be collected with global GC
* - caller class's ccs->cme marks cc->cme.
* - if cc is invalidated (klass doesn't refer the cc), cc is
* invalidated by `vm_cc_invalidate()` after which cc->cme must not
* be accessed.
* - With multi-Ractors, cme will be collected with global GC
* so that it is safe if GC is not interleaving while accessing
* cc and cme.
* - However, cc_type_super and cc_type_refinement are not chained
* from ccs so cc->cme should be marked; the cme might be
* reachable only through cc in these cases.
*
* However cc_type_super and cc_type_refinement are not chained
* from ccs so cc->cme should be marked as long as the cc is valid;
* the cme might be reachable only through cc in these cases.
*/
struct rb_callcache *cc = (struct rb_callcache *)obj;
if (reference_updating) {
if (UNDEF_P(cc->klass)) {
/* If it's invalidated, we must not mark anything.
* All fields should are considered invalid
*/
}
else if (reference_updating) {
if (moved_or_living_object_strictly_p((VALUE)cc->cme_)) {
*((VALUE *)&cc->klass) = rb_gc_location(cc->klass);
*((struct rb_callable_method_entry_struct **)&cc->cme_) =
(struct rb_callable_method_entry_struct *)rb_gc_location((VALUE)cc->cme_);

RUBY_ASSERT(RB_TYPE_P(cc->klass, T_CLASS) || RB_TYPE_P(cc->klass, T_ICLASS));
RUBY_ASSERT(IMEMO_TYPE_P((VALUE)cc->cme_, imemo_ment));
}
else if (vm_cc_valid(cc)) {
else {
vm_cc_invalidate(cc);
}
}
Expand Down
2 changes: 2 additions & 0 deletions ractor.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "internal/thread.h"
#include "variable.h"
#include "yjit.h"
#include "zjit.h"

VALUE rb_cRactor;
static VALUE rb_cRactorSelector;
Expand Down Expand Up @@ -511,6 +512,7 @@ ractor_create(rb_execution_context_t *ec, VALUE self, VALUE loc, VALUE name, VAL
r->debug = cr->debug;

rb_yjit_before_ractor_spawn();
rb_zjit_before_ractor_spawn();
rb_thread_create_ractor(r, args, block);

RB_GC_GUARD(rv);
Expand Down
2 changes: 0 additions & 2 deletions test/.excludes-zjit/TestFixnum.rb

This file was deleted.

52 changes: 52 additions & 0 deletions test/ruby/test_zjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,38 @@ def test() = @foo = 1
}
end

def test_attr_reader
assert_compiles '[4, 4]', %q{
class C
attr_reader :foo

def initialize
@foo = 4
end
end

def test(c) = c.foo
c = C.new
[test(c), test(c)]
}, call_threshold: 2, insns: [:opt_send_without_block]
end

def test_attr_accessor
assert_compiles '[4, 4]', %q{
class C
attr_accessor :foo

def initialize
@foo = 4
end
end

def test(c) = c.foo
c = C.new
[test(c), test(c)]
}, call_threshold: 2, insns: [:opt_send_without_block]
end

def test_uncached_getconstant_path
assert_compiles RUBY_COPYRIGHT.dump, %q{
def test = RUBY_COPYRIGHT
Expand Down Expand Up @@ -950,6 +982,26 @@ def test = A::B::C
RUBY
end

def test_single_ractor_mode_invalidation
# Without invalidating the single-ractor mode, the test would crash
assert_compiles '"errored but not crashed"', <<~RUBY, call_threshold: 2, insns: [:opt_getconstant_path]
C = Object.new

def test
C
rescue Ractor::IsolationError
"errored but not crashed"
end

test
test

Ractor.new {
test
}.value
RUBY
end

def test_dupn
assert_compiles '[[1], [1, 1], :rhs, [nil, :rhs]]', <<~RUBY, insns: [:dupn]
def test(array) = (array[1, 2] ||= :rhs)
Expand Down
4 changes: 4 additions & 0 deletions vm_callinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,8 @@ static inline const struct rb_callable_method_entry_struct *
vm_cc_cme(const struct rb_callcache *cc)
{
VM_ASSERT(IMEMO_TYPE_P(cc, imemo_callcache));
VM_ASSERT(cc->klass != Qundef || !vm_cc_markable(cc));
VM_ASSERT(cc_check_class(cc->klass));
VM_ASSERT(cc->call_ == NULL || // not initialized yet
!vm_cc_markable(cc) ||
cc->cme_ != NULL);
Expand All @@ -430,6 +432,8 @@ vm_cc_call(const struct rb_callcache *cc)
{
VM_ASSERT(IMEMO_TYPE_P(cc, imemo_callcache));
VM_ASSERT(cc->call_ != NULL);
VM_ASSERT(cc->klass != Qundef || !vm_cc_markable(cc));
VM_ASSERT(cc_check_class(cc->klass));
return cc->call_;
}

Expand Down
2 changes: 1 addition & 1 deletion yjit/src/invariants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ pub extern "C" fn rb_yjit_cme_invalidate(callee_cme: *const rb_callable_method_e
});
}

/// Callback for then Ruby is about to spawn a ractor. In that case we need to
/// Callback for when Ruby is about to spawn a ractor. In that case we need to
/// invalidate every block that is assuming single ractor mode.
#[no_mangle]
pub extern "C" fn rb_yjit_before_ractor_spawn() {
Expand Down
8 changes: 5 additions & 3 deletions zjit.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,25 @@ extern bool rb_zjit_enabled_p;
extern uint64_t rb_zjit_call_threshold;
extern uint64_t rb_zjit_profile_threshold;
void rb_zjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception);
void rb_zjit_profile_insn(enum ruby_vminsn_type insn, rb_execution_context_t *ec);
void rb_zjit_profile_insn(uint32_t insn, rb_execution_context_t *ec);
void rb_zjit_profile_enable(const rb_iseq_t *iseq);
void rb_zjit_bop_redefined(int redefined_flag, enum ruby_basic_operators bop);
void rb_zjit_cme_invalidate(const rb_callable_method_entry_t *cme);
void rb_zjit_invalidate_ep_is_bp(const rb_iseq_t *iseq);
void rb_zjit_constant_state_changed(ID id);
void rb_zjit_iseq_mark(void *payload);
void rb_zjit_iseq_update_references(void *payload);
void rb_zjit_before_ractor_spawn(void);
#else
#define rb_zjit_enabled_p false
static inline void rb_zjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception) {}
static inline void rb_zjit_profile_insn(enum ruby_vminsn_type insn, rb_execution_context_t *ec) {}
static inline void rb_zjit_profile_insn(uint32_t insn, rb_execution_context_t *ec) {}
static inline void rb_zjit_profile_enable(const rb_iseq_t *iseq) {}
static inline void rb_zjit_bop_redefined(int redefined_flag, enum ruby_basic_operators bop) {}
static inline void rb_zjit_cme_invalidate(const rb_callable_method_entry_t *cme) {}
static inline void rb_zjit_invalidate_ep_is_bp(const rb_iseq_t *iseq) {}
static inline void rb_zjit_constant_state_changed(ID id) {}
#endif // #if USE_YJIT
static inline void rb_zjit_before_ractor_spawn(void) {}
#endif // #if USE_ZJIT

#endif // #ifndef ZJIT_H
17 changes: 13 additions & 4 deletions zjit/src/backend/x86_64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,15 @@ impl Assembler
};
}

// When we split an operand, we can create a new VReg not in `live_ranges`.
// So when we see a VReg with out-of-range index, it's created from splitting
// from the loop above and we know it doesn't outlive the current instruction.
let vreg_outlives_insn = |vreg_idx| {
live_ranges
.get(vreg_idx)
.map_or(false, |live_range: &LiveRange| live_range.end() > index)
};

// We are replacing instructions here so we know they are already
// being used. It is okay not to use their output here.
#[allow(unused_must_use)]
Expand Down Expand Up @@ -183,7 +192,7 @@ impl Assembler
},
// Instruction output whose live range spans beyond this instruction
(Opnd::VReg { idx, .. }, _) => {
if live_ranges[idx].end() > index {
if vreg_outlives_insn(idx) {
*left = asm.load(*left);
}
},
Expand Down Expand Up @@ -248,7 +257,7 @@ impl Assembler
match opnd {
// Instruction output whose live range spans beyond this instruction
Opnd::VReg { idx, .. } => {
if live_ranges[*idx].end() > index {
if vreg_outlives_insn(*idx) {
*opnd = asm.load(*opnd);
}
},
Expand All @@ -272,7 +281,7 @@ impl Assembler
// If we have an instruction output whose live range
// spans beyond this instruction, we have to load it.
Opnd::VReg { idx, .. } => {
if live_ranges[idx].end() > index {
if vreg_outlives_insn(idx) {
*truthy = asm.load(*truthy);
}
},
Expand Down Expand Up @@ -307,7 +316,7 @@ impl Assembler
// If we have an instruction output whose live range
// spans beyond this instruction, we have to load it.
Opnd::VReg { idx, .. } => {
if live_ranges[idx].end() > index {
if vreg_outlives_insn(idx) {
*opnd = asm.load(*opnd);
}
},
Expand Down
8 changes: 4 additions & 4 deletions zjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::ffi::{c_int, c_void};

use crate::asm::Label;
use crate::backend::current::{Reg, ALLOC_REGS};
use crate::invariants::{track_bop_assumption, track_cme_assumption, track_stable_constant_names_assumption};
use crate::invariants::{track_bop_assumption, track_cme_assumption, track_single_ractor_assumption, track_stable_constant_names_assumption};
use crate::gc::{get_or_create_iseq_payload, append_gc_offsets};
use crate::state::ZJITState;
use crate::stats::{counter_ptr, Counter};
Expand Down Expand Up @@ -542,9 +542,9 @@ fn gen_patch_point(jit: &mut JITState, asm: &mut Assembler, invariant: &Invarian
let side_exit_ptr = cb.resolve_label(label);
track_stable_constant_names_assumption(idlist, code_ptr, side_exit_ptr);
}
_ => {
debug!("ZJIT: gen_patch_point: unimplemented invariant {invariant:?}");
return;
Invariant::SingleRactorMode => {
let side_exit_ptr = cb.resolve_label(label);
track_single_ractor_assumption(code_ptr, side_exit_ptr);
}
}
});
Expand Down
Loading