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
2 changes: 0 additions & 2 deletions .github/workflows/macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ jobs:
- test_task: check
os: macos-15
extra_checks: [capi]
- test_task: check
os: macos-13
fail-fast: false

env:
Expand Down
18 changes: 0 additions & 18 deletions .github/workflows/ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,6 @@ jobs:
${{ !endsWith(matrix.os, 'arm')
&& !endsWith(matrix.os, 'ppc64le') && !endsWith(matrix.os, 's390x') }}

# A temporary workaround: Set HOME env to pass the step
# ./.github/actions/setup/directories.
# https://github.com/IBM/actionspz/issues/30
- name: Set HOME env
run: |
echo "HOME: ${HOME}"
echo "HOME=$(ls -d ~)" >> $GITHUB_ENV
working-directory:
if: ${{ endsWith(matrix.os, 'ppc64le') || endsWith(matrix.os, 's390x') }}

- uses: ./.github/actions/setup/directories
with:
srcdir: src
Expand Down Expand Up @@ -142,14 +132,6 @@ jobs:
run: echo "DFLTCC=0" >> $GITHUB_ENV
if: ${{ endsWith(matrix.os, 's390x') }}

# A temporary workaround: Set the user's primary group to avoid a mismatch
# between the group IDs of "id -g" and C function getpwuid(uid_t uid)
# pw_gid.
# https://github.com/IBM/actionspz/issues/31
- name: Set user's group id
run: sudo usermod -g "$(id -g)" runner
if: ${{ endsWith(matrix.os, 'ppc64le') || endsWith(matrix.os, 's390x') }}

- name: make ${{ matrix.test_task }}
run: |
test -n "${LAUNCHABLE_STDOUT}" && exec 1> >(tee "${LAUNCHABLE_STDOUT}")
Expand Down
2 changes: 2 additions & 0 deletions class.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "ruby/st.h"
#include "vm_core.h"
#include "yjit.h"
#include "zjit.h"

/* Flags of T_CLASS
*
Expand Down Expand Up @@ -1309,6 +1310,7 @@ make_singleton_class(VALUE obj)
RBASIC_SET_CLASS(obj, klass);
rb_singleton_class_attached(klass, obj);
rb_yjit_invalidate_no_singleton_class(orig_class);
rb_zjit_invalidate_no_singleton_class(orig_class);

SET_METACLASS_OF(klass, METACLASS_OF(rb_class_real(orig_class)));
return klass;
Expand Down
1 change: 1 addition & 0 deletions depend
Original file line number Diff line number Diff line change
Expand Up @@ -1170,6 +1170,7 @@ class.$(OBJEXT): {$(VPATH)}vm_debug.h
class.$(OBJEXT): {$(VPATH)}vm_opts.h
class.$(OBJEXT): {$(VPATH)}vm_sync.h
class.$(OBJEXT): {$(VPATH)}yjit.h
class.$(OBJEXT): {$(VPATH)}zjit.h
compar.$(OBJEXT): $(hdrdir)/ruby/ruby.h
compar.$(OBJEXT): $(hdrdir)/ruby/version.h
compar.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
Expand Down
2 changes: 1 addition & 1 deletion doc/zjit.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ make -j

### Tracing side exits

Through [Stackprof](https://github.com/tmm1/stackprof), detailed information about the methods that the JIT side-exits from can be displayed after some execution of a program.
Through [Stackprof](https://github.com/tmm1/stackprof), detailed information about the methods that the JIT side-exits from can be displayed after some execution of a program. Optionally, you can use `--zjit-trace-exits-sample-rate=N` to sample every N-th occurrence. Enabling `--zjit-trace-exits-sample-rate=N` will automatically enable `--zjit-trace-exits`.

```bash
./miniruby --zjit-trace-exits script.rb
Expand Down
3 changes: 3 additions & 0 deletions gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1311,6 +1311,9 @@ rb_gc_obj_free(void *objspace, VALUE obj)
break;
case T_MODULE:
case T_CLASS:
#if USE_ZJIT
rb_zjit_klass_free(obj);
#endif
args.klass = obj;
rb_class_classext_foreach(obj, classext_free, (void *)&args);
if (RCLASS_CLASSEXT_TBL(obj)) {
Expand Down
1 change: 0 additions & 1 deletion ractor.c
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,6 @@ ractor_create(rb_execution_context_t *ec, VALUE self, VALUE loc, VALUE name, VAL
rb_ractor_t *r = RACTOR_PTR(rv);
ractor_init(r, name, loc);

// can block here
r->pub.id = ractor_next_id();
RUBY_DEBUG_LOG("r:%u", r->pub.id);

Expand Down
59 changes: 59 additions & 0 deletions test/ruby/test_zjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2912,6 +2912,65 @@ def self.new = :k
}, call_threshold: 2, insns: [:opt_new]
end

def test_singleton_class_invalidation_annotated_ccall
assert_compiles '[false, true]', %q{
def define_singleton(obj, define)
if define
# Wrap in C method frame to avoid exiting JIT on defineclass
[nil].reverse_each do
class << obj
def ==(_)
true
end
end
end
end
false
end

def test(define)
obj = BasicObject.new
# This == call gets compiled to a CCall
obj == define_singleton(obj, define)
end

result = []
result << test(false) # Compiles BasicObject#==
result << test(true) # Should use singleton#== now
result
}, call_threshold: 2
end

def test_singleton_class_invalidation_optimized_variadic_ccall
assert_compiles '[1, 1000]', %q{
def define_singleton(arr, define)
if define
# Wrap in C method frame to avoid exiting JIT on defineclass
[nil].reverse_each do
class << arr
def push(x)
super(x * 1000)
end
end
end
end
1
end

def test(define)
arr = []
val = define_singleton(arr, define)
arr.push(val) # This CCall should be invalidated if singleton was defined
arr[0]
end

result = []
result << test(false) # Compiles Array#push as CCall
result << test(true) # Singleton defined, CCall should be invalidated
result
}, call_threshold: 2
end

private

# Assert that every method call in `test_script` can be compiled by ZJIT
Expand Down
22 changes: 15 additions & 7 deletions yjit/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3591,6 +3591,13 @@ fn branch_stub_hit_body(branch_ptr: *const c_void, target_idx: u32, ec: EcPtr) -
return CodegenGlobals::get_stub_exit_code().raw_ptr(cb);
}

// Bail if this branch is housed in an invalidated (dead) block.
// This only happens in rare invalidation scenarios and we need
// to avoid linking a dead block to a live block with a branch.
if branch.block.get().as_ref().iseq.get().is_null() {
return CodegenGlobals::get_stub_exit_code().raw_ptr(cb);
}

(cfp, original_interp_sp)
};

Expand Down Expand Up @@ -4297,25 +4304,26 @@ pub fn invalidate_block_version(blockref: &BlockRef) {
incr_counter!(invalidation_count);
}

// We cannot deallocate blocks immediately after invalidation since there
// could be stubs waiting to access branch pointers. Return stubs can do
// this since patching the code for setting up return addresses does not
// affect old return addresses that are already set up to use potentially
// invalidated branch pointers. Example:
// We cannot deallocate blocks immediately after invalidation since patching the code for setting
// up return addresses does not affect outstanding return addresses that are on stack and will use
// invalidated branch pointers when hit. Example:
// def foo(n)
// if n == 2
// # 1.times.each to create a cfunc frame to preserve the JIT frame
// # which will return to a stub housed in an invalidated block
// return 1.times.each { Object.define_method(:foo) {} }
// end
//
// foo(n + 1)
// foo(n + 1) # The block for this call houses the return branch stub
// end
// p foo(1)
pub fn delayed_deallocation(blockref: BlockRef) {
block_assumptions_free(blockref);

let payload = get_iseq_payload(unsafe { blockref.as_ref() }.iseq.get()).unwrap();
let block = unsafe { blockref.as_ref() };
// Set null ISEQ on the block to signal that it's dead.
let iseq = block.iseq.replace(ptr::null());
let payload = get_iseq_payload(iseq).unwrap();
payload.dead_blocks.push(blockref);
}

Expand Down
3 changes: 3 additions & 0 deletions zjit.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ 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_cme_free(const rb_callable_method_entry_t *cme);
void rb_zjit_klass_free(VALUE klass);
void rb_zjit_invalidate_no_ep_escape(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_iseq_free(const rb_iseq_t *iseq);
void rb_zjit_before_ractor_spawn(void);
void rb_zjit_tracing_invalidate_all(void);
void rb_zjit_invalidate_no_singleton_class(VALUE klass);
#else
#define rb_zjit_enabled_p false
static inline void rb_zjit_compile_iseq(const rb_iseq_t *iseq, bool jit_exception) {}
Expand All @@ -37,6 +39,7 @@ static inline void rb_zjit_invalidate_no_ep_escape(const rb_iseq_t *iseq) {}
static inline void rb_zjit_constant_state_changed(ID id) {}
static inline void rb_zjit_before_ractor_spawn(void) {}
static inline void rb_zjit_tracing_invalidate_all(void) {}
static inline void rb_zjit_invalidate_no_singleton_class(VALUE klass) {}
#endif // #if USE_ZJIT

#endif // #ifndef ZJIT_H
7 changes: 4 additions & 3 deletions zjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ def dump_exit_locations(filename)

File.open(filename, "wb") do |file|
Marshal.dump(RubyVM::ZJIT.exit_locations, file)
file.size
end
end

Expand Down Expand Up @@ -275,9 +276,9 @@ def print_stats
def dump_locations # :nodoc:
return unless trace_exit_locations_enabled?

filename = "zjit_exit_locations.dump"
dump_exit_locations(filename)
filename = "zjit_exits_#{Time.now.to_i}.dump"
n_bytes = dump_exit_locations(filename)

$stderr.puts("ZJIT exit locations dumped to `#{filename}`.")
$stderr.puts("#{n_bytes} bytes written to #{filename}.")
end
end
8 changes: 7 additions & 1 deletion zjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ use std::slice;

use crate::asm::Label;
use crate::backend::current::{Reg, ALLOC_REGS};
use crate::invariants::{track_bop_assumption, track_cme_assumption, track_no_ep_escape_assumption, track_no_trace_point_assumption, track_single_ractor_assumption, track_stable_constant_names_assumption};
use crate::invariants::{
track_bop_assumption, track_cme_assumption, track_no_ep_escape_assumption, track_no_trace_point_assumption,
track_single_ractor_assumption, track_stable_constant_names_assumption, track_no_singleton_class_assumption
};
use crate::gc::{append_gc_offsets, get_or_create_iseq_payload, get_or_create_iseq_payload_ptr, IseqCodePtrs, IseqPayload, IseqStatus};
use crate::state::ZJITState;
use crate::stats::{send_fallback_counter, exit_counter_for_compile_error, incr_counter, incr_counter_by, send_fallback_counter_for_method_type, send_fallback_counter_ptr_for_opcode, CompileError};
Expand Down Expand Up @@ -654,6 +657,9 @@ fn gen_patch_point(jit: &mut JITState, asm: &mut Assembler, invariant: &Invarian
Invariant::SingleRactorMode => {
track_single_ractor_assumption(code_ptr, side_exit_ptr, payload_ptr);
}
Invariant::NoSingletonClass { klass } => {
track_no_singleton_class_assumption(klass, code_ptr, side_exit_ptr, payload_ptr);
}
}
});
}
Expand Down
10 changes: 10 additions & 0 deletions zjit/src/cruby.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,16 @@ impl VALUE {
self.static_sym_p() || self.dynamic_sym_p()
}

pub fn instance_can_have_singleton_class(self) -> bool {
if self == unsafe { rb_cInteger } || self == unsafe { rb_cFloat } ||
self == unsafe { rb_cSymbol } || self == unsafe { rb_cNilClass } ||
self == unsafe { rb_cTrueClass } || self == unsafe { rb_cFalseClass } {

return false
}
true
}

/// Return true for a static (non-heap) Ruby symbol (RB_STATIC_SYM_P)
pub fn static_sym_p(self) -> bool {
let VALUE(cval) = self;
Expand Down
10 changes: 10 additions & 0 deletions zjit/src/gc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,16 @@ pub extern "C" fn rb_zjit_cme_free(cme: *const rb_callable_method_entry_struct)
invariants.forget_cme(cme);
}

/// GC callback for finalizing a class
#[unsafe(no_mangle)]
pub extern "C" fn rb_zjit_klass_free(klass: VALUE) {
if !ZJITState::has_instance() {
return;
}
let invariants = ZJITState::get_invariants();
invariants.forget_klass(klass);
}

/// GC callback for updating object references after all object moves
#[unsafe(no_mangle)]
pub extern "C" fn rb_zjit_root_update_references() {
Expand Down
Loading