diff options
-rw-r--r-- | zjit/src/codegen.rs | 46 | ||||
-rw-r--r-- | zjit/src/cruby.rs | 41 | ||||
-rw-r--r-- | zjit/src/hir.rs | 2 | ||||
-rw-r--r-- | zjit/src/hir_type.rs | 14 | ||||
-rw-r--r-- | zjit/src/invariants.rs | 2 | ||||
-rw-r--r-- | zjit/src/lib.rs | 112 | ||||
-rw-r--r-- | zjit/src/state.rs | 40 |
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 +} |