summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--depend6
-rw-r--r--eval_intern.h14
-rw-r--r--tool/lib/bundled_gem.rb22
-rw-r--r--vm.c31
-rw-r--r--vm_core.h4
-rw-r--r--vm_exec.h2
-rw-r--r--zjit.h21
-rw-r--r--zjit/src/backend/lir.rs19
-rw-r--r--zjit/src/codegen.rs32
9 files changed, 103 insertions, 48 deletions
diff --git a/depend b/depend
index 746e62075a..e61b685670 100644
--- a/depend
+++ b/depend
@@ -2839,6 +2839,7 @@ debug.$(OBJEXT): {$(VPATH)}vm_core.h
debug.$(OBJEXT): {$(VPATH)}vm_debug.h
debug.$(OBJEXT): {$(VPATH)}vm_opts.h
debug.$(OBJEXT): {$(VPATH)}vm_sync.h
+debug.$(OBJEXT): {$(VPATH)}zjit.h
debug_counter.$(OBJEXT): $(hdrdir)/ruby/ruby.h
debug_counter.$(OBJEXT): {$(VPATH)}assert.h
debug_counter.$(OBJEXT): {$(VPATH)}backward/2/assume.h
@@ -8352,6 +8353,7 @@ load.$(OBJEXT): {$(VPATH)}util.h
load.$(OBJEXT): {$(VPATH)}vm_core.h
load.$(OBJEXT): {$(VPATH)}vm_debug.h
load.$(OBJEXT): {$(VPATH)}vm_opts.h
+load.$(OBJEXT): {$(VPATH)}zjit.h
loadpath.$(OBJEXT): $(hdrdir)/ruby/ruby.h
loadpath.$(OBJEXT): $(hdrdir)/ruby/version.h
loadpath.$(OBJEXT): $(top_srcdir)/version.h
@@ -13275,6 +13277,7 @@ proc.$(OBJEXT): {$(VPATH)}vm_debug.h
proc.$(OBJEXT): {$(VPATH)}vm_opts.h
proc.$(OBJEXT): {$(VPATH)}vm_sync.h
proc.$(OBJEXT): {$(VPATH)}yjit.h
+proc.$(OBJEXT): {$(VPATH)}zjit.h
process.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h
process.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
process.$(OBJEXT): $(CCAN_DIR)/list/list.h
@@ -15864,6 +15867,7 @@ ruby.$(OBJEXT): {$(VPATH)}util.h
ruby.$(OBJEXT): {$(VPATH)}vm_core.h
ruby.$(OBJEXT): {$(VPATH)}vm_opts.h
ruby.$(OBJEXT): {$(VPATH)}yjit.h
+ruby.$(OBJEXT): {$(VPATH)}zjit.h
ruby_parser.$(OBJEXT): $(hdrdir)/ruby/ruby.h
ruby_parser.$(OBJEXT): $(top_srcdir)/internal/array.h
ruby_parser.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
@@ -16271,6 +16275,7 @@ scheduler.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h
scheduler.$(OBJEXT): {$(VPATH)}thread_native.h
scheduler.$(OBJEXT): {$(VPATH)}vm_core.h
scheduler.$(OBJEXT): {$(VPATH)}vm_opts.h
+scheduler.$(OBJEXT): {$(VPATH)}zjit.h
set.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h
set.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
set.$(OBJEXT): $(CCAN_DIR)/list/list.h
@@ -17075,6 +17080,7 @@ signal.$(OBJEXT): {$(VPATH)}thread_native.h
signal.$(OBJEXT): {$(VPATH)}vm_core.h
signal.$(OBJEXT): {$(VPATH)}vm_debug.h
signal.$(OBJEXT): {$(VPATH)}vm_opts.h
+signal.$(OBJEXT): {$(VPATH)}zjit.h
sprintf.$(OBJEXT): $(hdrdir)/ruby/ruby.h
sprintf.$(OBJEXT): $(hdrdir)/ruby/version.h
sprintf.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
diff --git a/eval_intern.h b/eval_intern.h
index 2090fbfcab..954ba6a184 100644
--- a/eval_intern.h
+++ b/eval_intern.h
@@ -3,6 +3,7 @@
#include "ruby/ruby.h"
#include "vm_core.h"
+#include "zjit.h"
static inline void
vm_passed_block_handler_set(rb_execution_context_t *ec, VALUE block_handler)
@@ -102,8 +103,18 @@ extern int select_large_fdset(int, fd_set *, fd_set *, fd_set *, struct timeval
_tag.tag = Qundef; \
_tag.prev = _ec->tag; \
_tag.lock_rec = rb_ec_vm_lock_rec(_ec); \
+ EC_SAVE_TAG_CFP(_tag, _ec); \
rb_vm_tag_jmpbuf_init(&_tag.buf); \
+// Remember the CFP as of EC_PUSH_TAG so that ZJIT can materialize frames
+// only up to longjmp's target CFP. When a C method does longjmp inside it,
+// the target CFP may not be equal to the VM_FRAME_FLAG_FINISH frame.
+#if USE_ZJIT
+# define EC_SAVE_TAG_CFP(_tag, _ec) _tag.cfp = _ec->cfp
+#else
+# define EC_SAVE_TAG_CFP(_tag, _ec)
+#endif
+
#define EC_POP_TAG() \
_ec->tag = _tag.prev; \
rb_vm_tag_jmpbuf_deinit(&_tag.buf); \
@@ -155,6 +166,9 @@ static inline void
rb_ec_tag_jump(const rb_execution_context_t *ec, enum ruby_tag_type st)
{
RUBY_ASSERT(st > TAG_NONE && st <= TAG_FATAL, ": Invalid tag jump: %d", (int)st);
+#if USE_ZJIT
+ rb_zjit_materialize_frames(ec, ec->cfp);
+#endif
ec->tag->state = st;
ruby_longjmp(RB_VM_TAG_JMPBUF_GET(ec->tag->buf), 1);
}
diff --git a/tool/lib/bundled_gem.rb b/tool/lib/bundled_gem.rb
index ca9b40875c..ad103825bc 100644
--- a/tool/lib/bundled_gem.rb
+++ b/tool/lib/bundled_gem.rb
@@ -131,10 +131,26 @@ module BundledGem
system(command, chdir: gemdir) or raise "failed: #{command}"
end
+ class GemspecLoader
+ module NoPipe
+ refine IO.singleton_class do
+ def popen(...) ""; end
+ end
+ end
+ using NoPipe
+
+ def `(command) ""; end
+
+ def load_gemspec(file)
+ code = File.read(file, encoding: "utf-8:-")
+ eval(code, binding, file)
+ rescue
+ nil
+ end
+ end
+
def load_gemspec(g)
- dir, base = File.split(g)
- spec = Dir.chdir(dir) {Gem::Specification.load(base)} || Gem::Specification.load(g) or
- return false
+ spec = GemspecLoader.new.load_gemspec(g)
spec.files.clear
spec.extensions.clear
src = spec.to_ruby
diff --git a/vm.c b/vm.c
index ccc4f4d02a..de8628454e 100644
--- a/vm.c
+++ b/vm.c
@@ -553,8 +553,6 @@ zjit_compile(rb_execution_context_t *ec)
# define zjit_compile(ec) ((rb_jit_func_t)0)
#endif
-static inline void zjit_materialize_frames(rb_control_frame_t *cfp);
-
#if USE_YJIT || USE_ZJIT
// Execute JIT code compiled by yjit_compile() or zjit_compile()
static inline VALUE
@@ -580,8 +578,8 @@ jit_exec(rb_execution_context_t *ec)
// This is done here (once per JIT entry) instead of in each side exit
// to reduce generated code size.
if (UNDEF_P(result)) {
- ec->cfp->jit_return = 0;
- zjit_materialize_frames(ec->cfp);
+ ec->cfp->jit_return = 0; // exit code already cleared most fields except jit_return
+ rb_zjit_materialize_frames(ec, ec->cfp);
}
return result;
}
@@ -2841,10 +2839,14 @@ vm_exec_loop(rb_execution_context_t *ec, enum ruby_tag_type state,
return result;
}
-static inline void
-zjit_materialize_frames(rb_control_frame_t *cfp)
+#if USE_ZJIT
+// Materialize JITFrame-enabled CFP into interpreter-compatible CFP
+void
+rb_zjit_materialize_frames(const rb_execution_context_t *ec, rb_control_frame_t *cfp)
{
if (!rb_zjit_enabled_p) return;
+ const rb_control_frame_t *end_cfp = ec->tag->cfp;
+ VM_ASSERT(cfp <= end_cfp);
while (true) {
if (CFP_ZJIT_FRAME_P(cfp)) {
@@ -2856,16 +2858,11 @@ zjit_materialize_frames(rb_control_frame_t *cfp)
}
cfp->jit_return = 0;
}
- if (VM_FRAME_FINISHED_P(cfp)) break;
+ if (end_cfp == cfp) break;
cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
}
}
-
-void
-rb_zjit_materialize_frames(rb_control_frame_t *cfp)
-{
- zjit_materialize_frames(cfp);
-}
+#endif
static inline VALUE
vm_exec_handle_exception(rb_execution_context_t *ec, enum ruby_tag_type state, VALUE errinfo)
@@ -2938,7 +2935,7 @@ vm_exec_handle_exception(rb_execution_context_t *ec, enum ruby_tag_type state, V
/* TAG_BREAK */
*cfp->sp++ = THROW_DATA_VAL(err);
ec->errinfo = Qnil;
- zjit_materialize_frames(cfp);
+ rb_zjit_materialize_frames(ec, cfp);
return Qundef;
}
}
@@ -2976,7 +2973,7 @@ vm_exec_handle_exception(rb_execution_context_t *ec, enum ruby_tag_type state, V
const rb_control_frame_t *escape_cfp;
escape_cfp = THROW_DATA_CATCH_FRAME(err);
if (cfp == escape_cfp) {
- zjit_materialize_frames(cfp);
+ rb_zjit_materialize_frames(ec, cfp);
cfp->pc = ISEQ_BODY(CFP_ISEQ(cfp))->iseq_encoded + entry->cont;
ec->errinfo = Qnil;
return Qundef;
@@ -3007,7 +3004,7 @@ vm_exec_handle_exception(rb_execution_context_t *ec, enum ruby_tag_type state, V
break;
}
else if (entry->type == type) {
- zjit_materialize_frames(cfp);
+ rb_zjit_materialize_frames(ec, cfp);
cfp->pc = ISEQ_BODY(CFP_ISEQ(cfp))->iseq_encoded + entry->cont;
cfp->sp = vm_base_ptr(cfp) + entry->sp;
@@ -3042,7 +3039,7 @@ vm_exec_handle_exception(rb_execution_context_t *ec, enum ruby_tag_type state, V
const int arg_size = 1;
rb_iseq_check(catch_iseq);
- zjit_materialize_frames(cfp); // vm_base_ptr looks at cfp->_iseq
+ rb_zjit_materialize_frames(ec, cfp); // vm_base_ptr looks at cfp->_iseq
cfp->sp = vm_base_ptr(cfp) + cont_sp;
cfp->pc = ISEQ_BODY(CFP_ISEQ(cfp))->iseq_encoded + cont_pc;
diff --git a/vm_core.h b/vm_core.h
index be5b5781f6..3b0fe0a811 100644
--- a/vm_core.h
+++ b/vm_core.h
@@ -1015,6 +1015,10 @@ struct rb_vm_tag {
struct rb_vm_tag *prev;
enum ruby_tag_type state;
unsigned int lock_rec;
+#if USE_ZJIT
+ // ec->cfp as of EC_PUSH_TAG, which is saved for materializing JITFrame.
+ rb_control_frame_t *cfp;
+#endif
};
STATIC_ASSERT(rb_vm_tag_buf_offset, offsetof(struct rb_vm_tag, buf) > 0);
diff --git a/vm_exec.h b/vm_exec.h
index f0796e93ab..d7c7752110 100644
--- a/vm_exec.h
+++ b/vm_exec.h
@@ -191,7 +191,7 @@ default: \
val = zjit_entry(ec, ec->cfp, func); \
if (UNDEF_P(val)) { \
ec->cfp->jit_return = 0; \
- zjit_materialize_frames(ec->cfp); \
+ rb_zjit_materialize_frames(ec, ec->cfp); \
} \
} \
} \
diff --git a/zjit.h b/zjit.h
index f8bffb19ca..5a56297a4e 100644
--- a/zjit.h
+++ b/zjit.h
@@ -49,6 +49,7 @@ void rb_zjit_tracing_invalidate_all(void);
void rb_zjit_invalidate_no_singleton_class(VALUE klass);
void rb_zjit_invalidate_root_box(void);
void rb_zjit_jit_frame_update_references(zjit_jit_frame_t *jit_frame);
+void rb_zjit_materialize_frames(const rb_execution_context_t *ec, rb_control_frame_t *cfp);
// Special value for cfp->jit_return that means "this is a C method frame, use
// rb_zjit_c_frame as the JITFrame". We don't control the native stack layout
@@ -56,13 +57,24 @@ void rb_zjit_jit_frame_update_references(zjit_jit_frame_t *jit_frame);
// instead of a heap-allocated JITFrame pointer.
#define ZJIT_JIT_RETURN_C_FRAME 0x1
+// BADFrame. The high bit is set, so likely SEGV on linux and darwin if dereferenced.
+#define ZJIT_JIT_RETURN_POISON 0xbadfbadfbadfbadfULL
+
static inline const zjit_jit_frame_t *
CFP_ZJIT_FRAME(const rb_control_frame_t *cfp)
{
if ((VALUE)cfp->jit_return == ZJIT_JIT_RETURN_C_FRAME) {
return &rb_zjit_c_frame;
}
- return (const zjit_jit_frame_t *)cfp->jit_return;
+ else {
+#if USE_ZJIT
+ RUBY_ASSERT((unsigned long long)((VALUE *)cfp->jit_return)[-1] != ZJIT_JIT_RETURN_POISON);
+#endif
+ // Read JITFrame from the stack slot. gen_entry_point() writes an initial
+ // frame describing the entry PC + iseq; subsequent gen_save_pc_for_gc()
+ // calls update it with a more accurate PC before any non-leaf C call.
+ return (const zjit_jit_frame_t *)((VALUE *)cfp->jit_return)[-1];
+ }
}
#else
#define rb_zjit_entry 0
@@ -78,22 +90,17 @@ static inline void rb_zjit_tracing_invalidate_all(void) {}
static inline void rb_zjit_invalidate_no_singleton_class(VALUE klass) {}
static inline void rb_zjit_invalidate_root_box(void) {}
static inline void rb_zjit_jit_frame_update_references(zjit_jit_frame_t *jit_frame) {}
+static inline void rb_zjit_materialize_frames(const rb_execution_context_t *ec, rb_control_frame_t *cfp) {}
static inline const zjit_jit_frame_t *CFP_ZJIT_FRAME(const rb_control_frame_t *cfp) { return NULL; }
#endif // #if USE_ZJIT
#define rb_zjit_enabled_p (rb_zjit_entry != 0)
-// BADFrame. The high bit is set, so likely SEGV on linux and darwin if dereferenced.
-#define ZJIT_JIT_RETURN_POISON 0xbadfbadfbadfbadfULL
-
// Return true if a given CFP has ZJIT's JITFrame.
static inline bool
CFP_ZJIT_FRAME_P(const rb_control_frame_t *cfp)
{
if (!rb_zjit_enabled_p) return false;
-#if USE_ZJIT
- RUBY_ASSERT((unsigned long long)cfp->jit_return != ZJIT_JIT_RETURN_POISON);
-#endif
return cfp->jit_return != NULL;
}
diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs
index 975c46529a..a8d03ad69a 100644
--- a/zjit/src/backend/lir.rs
+++ b/zjit/src/backend/lir.rs
@@ -1812,8 +1812,9 @@ impl Assembler
Opnd::Reg(ALLOC_REGS[idx])
} else {
// With FrameSetup, the address that NATIVE_BASE_PTR points to stores an old value in the register.
- // To avoid clobbering it, we need to start from the next slot, hence `+ 1` for the index.
- Opnd::mem(64, NATIVE_BASE_PTR, (idx - ALLOC_REGS.len() + 1) as i32 * -SIZEOF_VALUE_I32)
+ // To avoid clobbering it, we need to start from the next slot, and we also reserve one space for
+ // JITFrame, hence `+ 2` for the index.
+ Opnd::mem(64, NATIVE_BASE_PTR, (idx - ALLOC_REGS.len() + 2) as i32 * -SIZEOF_VALUE_I32)
}
}
@@ -2690,15 +2691,11 @@ impl Assembler
// holding stack/local operands.
compile_exit_save_state(asm, exit);
if trace_reason.is_some() || exit.recompile.is_some() {
- if cfg!(feature = "runtime_checks") {
- // Clear jit_return to fully materialize the frame. This must happen
- // before any C call in the exit path because that C call can trigger
- // GC, which walks the stack and would hit the CFP_JIT_RETURN assertion
- // if jit_return still holds the runtime_checks poison value
- // (JIT_RETURN_POISON).
- asm_comment!(asm, "clear cfp->jit_return");
- asm.store(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_JIT_RETURN), 0.into());
- }
+ // Clear cfp->jit_return to prepare for a C call. Normally, cfp->jit_return
+ // is cleared by the caller jit_exec() or JIT_EXEC(), but if we're about to
+ // make a C call, we need to clear any stale JITFrame.
+ asm_comment!(asm, "clear cfp->jit_return");
+ asm.store(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_JIT_RETURN), 0.into());
}
if let Some(reason) = trace_reason {
// Leak a CString with the reason so it's available at runtime
diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs
index 7d7aca1c83..f1ef17d794 100644
--- a/zjit/src/codegen.rs
+++ b/zjit/src/codegen.rs
@@ -19,7 +19,7 @@ use crate::state::ZJITState;
use crate::stats::{CompileError, exit_counter_for_compile_error, exit_counter_for_unhandled_hir_insn, incr_counter, incr_counter_by, send_fallback_counter, send_fallback_counter_for_method_type, send_fallback_counter_for_super_method_type, send_fallback_counter_ptr_for_opcode, send_without_block_fallback_counter_for_method_type, send_without_block_fallback_counter_for_optimized_method_type};
use crate::stats::{counter_ptr, with_time_stat, trace_compile_phase, Counter, Counter::{compile_time_ns, exit_compile_error}};
use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr};
-use crate::backend::lir::{self, Assembler, C_ARG_OPNDS, C_RET_OPND, CFP, EC, NATIVE_STACK_PTR, Opnd, SP, SideExit, SideExitRecompile, Target, asm_ccall, asm_comment};
+use crate::backend::lir::{self, Assembler, C_ARG_OPNDS, C_RET_OPND, CFP, EC, NATIVE_BASE_PTR, NATIVE_STACK_PTR, Opnd, SP, SideExit, SideExitRecompile, Target, asm_ccall, asm_comment};
use crate::hir::{iseq_to_hir, BlockId, Invariant, RangeType, SideExitReason::{self, *}, SpecialBackrefSymbol, SpecialObjectType};
use crate::hir::{BlockHandler, Const, FieldName, FrameState, Function, Insn, InsnId, Recompile, SendFallbackReason};
use crate::hir_type::{types, Type};
@@ -376,7 +376,7 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, version: IseqVersionRef, func
let (mut jit, asm) = trace_compile_phase("codegen", || {
let num_spilled_params = max_num_params(function).saturating_sub(ALLOC_REGS.len());
let mut jit = JITState::new(version, function.num_insns(), function.num_blocks());
- let mut asm = Assembler::new_with_stack_slots(num_spilled_params);
+ let mut asm = Assembler::new_with_stack_slots(num_spilled_params + 1); // +1 for JITFrame
// Mapping from HIR block IDs to LIR block IDs.
// This is is a one-to-one mapping from HIR to LIR blocks used for finding
@@ -2186,6 +2186,17 @@ fn gen_entry_point(jit: &mut JITState, asm: &mut Assembler, jit_entry_idx: Optio
});
}
asm.frame_setup(&[]);
+
+ // Publish the JITFrame slot's location via cfp->jit_return. The slot at
+ // [NATIVE_BASE_PTR - 8] is left uninitialized here; the JIT design relies on
+ // gen_save_pc_for_gc() to populate it before any C call, and on cross-ractor
+ // barriers ensuring that no other ractor scans this CFP before such a call.
+ asm.mov(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_JIT_RETURN), NATIVE_BASE_PTR);
+
+ // Poison the JITFrame slot. It should be read only after gen_save_pc_for_gc().
+ if let Some(jit_return_poison) = JIT_RETURN_POISON {
+ asm.mov(Opnd::mem(64, NATIVE_BASE_PTR, -SIZEOF_VALUE_I32), jit_return_poison.into());
+ }
}
/// Compile code that exits from JIT code with a return value
@@ -2710,11 +2721,16 @@ fn gen_save_pc_for_gc(asm: &mut Assembler, state: &FrameState) {
gen_incr_counter(asm, Counter::vm_write_jit_frame_count);
asm_comment!(asm, "save JITFrame to CFP");
- if let Some(pc) = PC_POISON {
- asm.mov(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_PC), Opnd::const_ptr(pc));
- }
let jit_frame = JITFrame::new_iseq(next_pc, state.iseq, !iseq_may_write_block_code(state.iseq));
- asm.mov(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_JIT_RETURN), Opnd::const_ptr(jit_frame));
+ asm.mov(Opnd::mem(64, NATIVE_BASE_PTR, -SIZEOF_VALUE_I32), Opnd::const_ptr(jit_frame));
+
+ // CFP_PC for a live JIT frame routes through the JITFrame on the native
+ // stack (cfp->jit_return points to NATIVE_BASE_PTR), so we don't need to
+ // touch cfp->pc here. Poisoning cfp->pc with PC_POISON would actively
+ // break the case where rb_zjit_materialize_frames() previously copied
+ // jit_frame->pc into cfp->pc and cleared cfp->jit_return: the JIT keeps
+ // running, lands on this routine again, and the poison would replace
+ // the valid materialized pc behind the GC's back.
}
/// Save the current PC on the CFP as a preparation for calling a C function
@@ -2843,9 +2859,7 @@ fn gen_push_frame(asm: &mut Assembler, argc: usize, state: &FrameState, frame: C
if frame.iseq.is_some() {
// PC, SP, and ISEQ are written lazily by the callee on side-exits, non-leaf calls, or GC.
- if let Some(jit_return_poison) = JIT_RETURN_POISON {
- asm.mov(cfp_opnd(RUBY_OFFSET_CFP_JIT_RETURN), jit_return_poison.into());
- }
+ // cfp->jit_return will be written by gen_entry_point() on the callee after this frame push.
if frame.write_block_code {
asm_comment!(asm, "write block_code for iseq that may use it");
asm.mov(cfp_opnd(RUBY_OFFSET_CFP_BLOCK_CODE), 0.into());