summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--zjit/src/codegen.rs46
-rw-r--r--zjit/src/cruby.rs41
-rw-r--r--zjit/src/hir.rs2
-rw-r--r--zjit/src/hir_type.rs14
-rw-r--r--zjit/src/invariants.rs2
-rw-r--r--zjit/src/lib.rs112
-rw-r--r--zjit/src/state.rs40
7 files changed, 133 insertions, 124 deletions
diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs
index ed8836e052..e6029e70e0 100644
--- a/zjit/src/codegen.rs
+++ b/zjit/src/codegen.rs
@@ -1,6 +1,8 @@
-use crate::{asm::CodeBlock, cruby::*, debug, virtualmem::CodePtr};
+use crate::state::ZJITState;
+use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr};
use crate::invariants::{iseq_escapes_ep, track_no_ep_escape_assumption};
use crate::backend::lir::{self, asm_comment, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, EC, SP};
+use crate::hir;
use crate::hir::{Const, FrameState, Function, Insn, InsnId};
use crate::hir_type::{types::Fixnum, Type};
@@ -41,8 +43,48 @@ impl JITState {
}
}
+/// Generate JIT code for a given ISEQ, which takes EC and CFP as its arguments.
+#[unsafe(no_mangle)]
+pub extern "C" fn rb_zjit_iseq_gen_entry_point(iseq: IseqPtr, _ec: EcPtr) -> *const u8 {
+ let code_ptr = iseq_gen_entry_point(iseq);
+ if ZJITState::assert_compiles_enabled() && code_ptr == std::ptr::null() {
+ let iseq_location = iseq_get_location(iseq, 0);
+ panic!("Failed to compile: {iseq_location}");
+ }
+ code_ptr
+}
+
+fn iseq_gen_entry_point(iseq: IseqPtr) -> *const u8 {
+ // Do not test the JIT code in HIR tests
+ if cfg!(test) {
+ return std::ptr::null();
+ }
+
+ // Take a lock to avoid writing to ISEQ in parallel with Ractors.
+ // with_vm_lock() does nothing if the program doesn't use Ractors.
+ with_vm_lock(src_loc!(), || {
+ // Compile ISEQ into High-level IR
+ let ssa = match hir::iseq_to_hir(iseq) {
+ Ok(ssa) => ssa,
+ Err(err) => {
+ debug!("ZJIT: iseq_to_hir: {:?}", err);
+ return std::ptr::null();
+ }
+ };
+
+ // Compile High-level IR into machine code
+ let cb = ZJITState::get_code_block();
+ match gen_function(cb, &ssa, iseq) {
+ Some(start_ptr) => start_ptr.raw_ptr(cb),
+
+ // Compilation failed, continue executing in the interpreter only
+ None => std::ptr::null(),
+ }
+ })
+}
+
/// Compile High-level IR into machine code
-pub fn gen_function(cb: &mut CodeBlock, function: &Function, iseq: IseqPtr) -> Option<CodePtr> {
+fn gen_function(cb: &mut CodeBlock, function: &Function, iseq: IseqPtr) -> Option<CodePtr> {
// Set up special registers
let mut jit = JITState::new(iseq, function.insns.len());
let mut asm = Assembler::new();
diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs
index 9ffe4ff7e6..46c91a474f 100644
--- a/zjit/src/cruby.rs
+++ b/zjit/src/cruby.rs
@@ -770,6 +770,45 @@ where
ret
}
+/// At the moment, we abort in all cases we panic.
+/// To aid with getting diagnostics in the wild without requiring people to set
+/// RUST_BACKTRACE=1, register a panic hook that crash using rb_bug() for release builds.
+/// rb_bug() might not be as good at printing a call trace as Rust's stdlib, but
+/// it dumps some other info that might be relevant.
+///
+/// In case we want to start doing fancier exception handling with panic=unwind,
+/// we can revisit this later. For now, this helps to get us good bug reports.
+pub fn rb_bug_panic_hook() {
+ use std::env;
+ use std::panic;
+ use std::io::{stderr, Write};
+
+ // Probably the default hook. We do this very early during process boot.
+ let previous_hook = panic::take_hook();
+
+ panic::set_hook(Box::new(move |panic_info| {
+ // Not using `eprintln` to avoid double panic.
+ let _ = stderr().write_all(b"ruby: ZJIT has panicked. More info to follow...\n");
+
+ // Always show a Rust backtrace for release builds.
+ // You should set RUST_BACKTRACE=1 for dev builds.
+ let release_build = cfg!(not(debug_assertions));
+ if release_build {
+ unsafe { env::set_var("RUST_BACKTRACE", "1"); }
+ }
+ previous_hook(panic_info);
+
+ // Dump information about the interpreter for release builds.
+ // You may also use ZJIT_RB_BUG=1 to trigger this on dev builds.
+ if release_build || env::var("ZJIT_RB_BUG").is_ok() {
+ // Abort with rb_bug(). It has a length limit on the message.
+ let panic_message = &format!("{}", panic_info)[..];
+ let len = std::cmp::min(0x100, panic_message.len()) as c_int;
+ unsafe { rb_bug(b"ZJIT: %*s\0".as_ref().as_ptr() as *const c_char, len, panic_message.as_ptr()); }
+ }
+ }));
+}
+
// Non-idiomatic capitalization for consistency with CRuby code
#[allow(non_upper_case_globals)]
pub const Qfalse: VALUE = VALUE(RUBY_Qfalse as usize);
@@ -855,7 +894,7 @@ pub use manual_defs::*;
pub mod test_utils {
use std::{ptr::null, sync::Once};
- use crate::{options::init_options, rb_zjit_enabled_p, state::ZJITState};
+ use crate::{options::init_options, state::rb_zjit_enabled_p, state::ZJITState};
use super::*;
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs
index 1100b3c343..f86aac169d 100644
--- a/zjit/src/hir.rs
+++ b/zjit/src/hir.rs
@@ -2,7 +2,7 @@
#![allow(non_upper_case_globals)]
use crate::{
- cruby::*, get_option, hir_type::types::Fixnum, options::DumpHIR, profile::get_or_create_iseq_payload
+ cruby::*, options::get_option, hir_type::types::Fixnum, options::DumpHIR, profile::get_or_create_iseq_payload
};
use std::collections::{HashMap, HashSet};
diff --git a/zjit/src/hir_type.rs b/zjit/src/hir_type.rs
index 4f7e3c86fd..4b6b2cbb23 100644
--- a/zjit/src/hir_type.rs
+++ b/zjit/src/hir_type.rs
@@ -64,8 +64,8 @@ include!("hir_type.inc.rs");
/// Get class name from a class pointer.
fn get_class_name(class: Option<VALUE>) -> String {
- use crate::{RB_TYPE_P, RUBY_T_MODULE, RUBY_T_CLASS};
- use crate::{cstr_to_rust_string, rb_class2name};
+ use crate::cruby::{RB_TYPE_P, RUBY_T_MODULE, RUBY_T_CLASS};
+ use crate::cruby::{cstr_to_rust_string, rb_class2name};
class.filter(|&class| {
// type checks for rb_class2name()
unsafe { RB_TYPE_P(class, RUBY_T_MODULE) || RB_TYPE_P(class, RUBY_T_CLASS) }
@@ -324,11 +324,11 @@ impl Type {
#[cfg(test)]
mod tests {
use super::*;
- use crate::rust_str_to_ruby;
- use crate::rust_str_to_sym;
- use crate::rb_ary_new_capa;
- use crate::rb_hash_new;
- use crate::rb_float_new;
+ use crate::cruby::rust_str_to_ruby;
+ use crate::cruby::rust_str_to_sym;
+ use crate::cruby::rb_ary_new_capa;
+ use crate::cruby::rb_hash_new;
+ use crate::cruby::rb_float_new;
use crate::cruby::define_class;
#[track_caller]
diff --git a/zjit/src/invariants.rs b/zjit/src/invariants.rs
index 87ae1facdf..77fd78d95e 100644
--- a/zjit/src/invariants.rs
+++ b/zjit/src/invariants.rs
@@ -1,6 +1,6 @@
use std::collections::HashSet;
-use crate::{cruby::{ruby_basic_operators, IseqPtr, RedefinitionFlag}, state::ZJITState, zjit_enabled_p};
+use crate::{cruby::{ruby_basic_operators, IseqPtr, RedefinitionFlag}, state::ZJITState, state::zjit_enabled_p};
/// Used to track all of the various block references that contain assumptions
/// about the state of the virtual machine.
diff --git a/zjit/src/lib.rs b/zjit/src/lib.rs
index e68ac93fa5..221a3a60a2 100644
--- a/zjit/src/lib.rs
+++ b/zjit/src/lib.rs
@@ -20,115 +20,3 @@ mod disasm;
mod options;
mod profile;
mod invariants;
-
-use codegen::gen_function;
-use options::{debug, get_option, Options};
-use state::ZJITState;
-use crate::cruby::*;
-
-#[allow(non_upper_case_globals)]
-#[unsafe(no_mangle)]
-pub static mut rb_zjit_enabled_p: bool = false;
-
-/// Like rb_zjit_enabled_p, but for Rust code.
-pub fn zjit_enabled_p() -> bool {
- unsafe { rb_zjit_enabled_p }
-}
-
-/// Initialize ZJIT, given options allocated by rb_zjit_init_options()
-#[unsafe(no_mangle)]
-pub extern "C" fn rb_zjit_init(options: *const u8) {
- // Catch panics to avoid UB for unwinding into C frames.
- // See https://doc.rust-lang.org/nomicon/exception-safety.html
- let result = std::panic::catch_unwind(|| {
- let options = unsafe { Box::from_raw(options as *mut Options) };
- ZJITState::init(*options);
- std::mem::drop(options);
-
- rb_bug_panic_hook();
-
- // YJIT enabled and initialized successfully
- assert!(unsafe{ !rb_zjit_enabled_p });
- unsafe { rb_zjit_enabled_p = true; }
- });
-
- if let Err(_) = result {
- println!("ZJIT: zjit_init() panicked. Aborting.");
- std::process::abort();
- }
-}
-
-/// At the moment, we abort in all cases we panic.
-/// To aid with getting diagnostics in the wild without requiring
-/// people to set RUST_BACKTRACE=1, register a panic hook that crash using rb_bug().
-/// rb_bug() might not be as good at printing a call trace as Rust's stdlib, but
-/// it dumps some other info that might be relevant.
-///
-/// In case we want to start doing fancier exception handling with panic=unwind,
-/// we can revisit this later. For now, this helps to get us good bug reports.
-fn rb_bug_panic_hook() {
- use std::panic;
- use std::io::{stderr, Write};
-
- panic::set_hook(Box::new(move |panic_info| {
- // Not using `eprintln` to avoid double panic.
- _ = write!(stderr(),
-"ruby: ZJIT has panicked. More info to follow...
-{panic_info}
-{}",
- std::backtrace::Backtrace::force_capture());
-
- // TODO: enable CRuby's SEGV handler
- // Abort with rb_bug(). It has a length limit on the message.
- //let panic_message = &format!("{}", panic_info)[..];
- //let len = std::cmp::min(0x100, panic_message.len()) as c_int;
- //unsafe { rb_bug(b"ZJIT: %*s\0".as_ref().as_ptr() as *const c_char, len, panic_message.as_ptr()); }
- }));
-}
-
-/// Generate JIT code for a given ISEQ, which takes EC and CFP as its arguments.
-#[unsafe(no_mangle)]
-pub extern "C" fn rb_zjit_iseq_gen_entry_point(iseq: IseqPtr, _ec: EcPtr) -> *const u8 {
- let code_ptr = iseq_gen_entry_point(iseq);
- if ZJITState::assert_compiles_enabled() && code_ptr == std::ptr::null() {
- let iseq_location = iseq_get_location(iseq, 0);
- panic!("Failed to compile: {iseq_location}");
- }
- code_ptr
-}
-
-fn iseq_gen_entry_point(iseq: IseqPtr) -> *const u8 {
- // Do not test the JIT code in HIR tests
- if cfg!(test) {
- return std::ptr::null();
- }
-
- // Take a lock to avoid writing to ISEQ in parallel with Ractors.
- // with_vm_lock() does nothing if the program doesn't use Ractors.
- with_vm_lock(src_loc!(), || {
- // Compile ISEQ into High-level IR
- let ssa = match hir::iseq_to_hir(iseq) {
- Ok(ssa) => ssa,
- Err(err) => {
- debug!("ZJIT: iseq_to_hir: {:?}", err);
- return std::ptr::null();
- }
- };
-
- // Compile High-level IR into machine code
- let cb = ZJITState::get_code_block();
- match gen_function(cb, &ssa, iseq) {
- Some(start_ptr) => start_ptr.raw_ptr(cb),
-
- // Compilation failed, continue executing in the interpreter only
- None => std::ptr::null(),
- }
- })
-}
-
-/// Assert that any future ZJIT compilation will return a function pointer (not fail to compile)
-#[unsafe(no_mangle)]
-pub extern "C" fn rb_zjit_assert_compiles(_ec: EcPtr, _self: VALUE) -> VALUE {
- ZJITState::enable_assert_compiles();
- Qnil
-}
diff --git a/zjit/src/state.rs b/zjit/src/state.rs
index c176d58b20..51e0fef898 100644
--- a/zjit/src/state.rs
+++ b/zjit/src/state.rs
@@ -1,7 +1,17 @@
+use crate::cruby::{rb_bug_panic_hook, EcPtr, Qnil, VALUE};
use crate::invariants::Invariants;
use crate::options::Options;
use crate::asm::CodeBlock;
+#[allow(non_upper_case_globals)]
+#[unsafe(no_mangle)]
+pub static mut rb_zjit_enabled_p: bool = false;
+
+/// Like rb_zjit_enabled_p, but for Rust code.
+pub fn zjit_enabled_p() -> bool {
+ unsafe { rb_zjit_enabled_p }
+}
+
/// Global state needed for code generation
pub struct ZJITState {
/// Inline code block (fast path)
@@ -108,3 +118,33 @@ impl ZJITState {
instance.assert_compiles = true;
}
}
+
+/// Initialize ZJIT, given options allocated by rb_zjit_init_options()
+#[unsafe(no_mangle)]
+pub extern "C" fn rb_zjit_init(options: *const u8) {
+ // Catch panics to avoid UB for unwinding into C frames.
+ // See https://doc.rust-lang.org/nomicon/exception-safety.html
+ let result = std::panic::catch_unwind(|| {
+ let options = unsafe { Box::from_raw(options as *mut Options) };
+ ZJITState::init(*options);
+ std::mem::drop(options);
+
+ rb_bug_panic_hook();
+
+ // YJIT enabled and initialized successfully
+ assert!(unsafe{ !rb_zjit_enabled_p });
+ unsafe { rb_zjit_enabled_p = true; }
+ });
+
+ if let Err(_) = result {
+ println!("ZJIT: zjit_init() panicked. Aborting.");
+ std::process::abort();
+ }
+}
+
+/// Assert that any future ZJIT compilation will return a function pointer (not fail to compile)
+#[unsafe(no_mangle)]
+pub extern "C" fn rb_zjit_assert_compiles(_ec: EcPtr, _self: VALUE) -> VALUE {
+ ZJITState::enable_assert_compiles();
+ Qnil
+}