ruby/zjit/src/state.rs
Takashi Kokubun 9fb34f4f16
ZJIT: Add --zjit-exec-mem-size (#14175)
* ZJIT: Add --zjit-exec-mem-size

* Add a comment about the limit
2025-08-11 15:36:37 -07:00

210 lines
7.1 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use crate::codegen::gen_stub_exit;
use crate::cruby::{self, rb_bug_panic_hook, rb_vm_insns_count, EcPtr, Qnil, VALUE};
use crate::cruby_methods;
use crate::invariants::Invariants;
use crate::asm::CodeBlock;
use crate::options::get_option;
use crate::stats::Counters;
use crate::virtualmem::CodePtr;
#[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)
code_block: CodeBlock,
/// ZJIT statistics
counters: Counters,
/// Assumptions that require invalidation
invariants: Invariants,
/// Assert successful compilation if set to true
assert_compiles: bool,
/// Properties of core library methods
method_annotations: cruby_methods::Annotations,
/// Side-exit trampoline used when it fails to compile the ISEQ for a function stub
stub_exit: CodePtr,
}
/// Private singleton instance of the codegen globals
static mut ZJIT_STATE: Option<ZJITState> = None;
impl ZJITState {
/// Initialize the ZJIT globals
pub fn init() {
#[cfg(not(test))]
let mut cb = {
use crate::cruby::*;
use crate::options::*;
let exec_mem_bytes: usize = get_option!(exec_mem_bytes);
let virt_block: *mut u8 = unsafe { rb_zjit_reserve_addr_space(64 * 1024 * 1024) };
// Memory protection syscalls need page-aligned addresses, so check it here. Assuming
// `virt_block` is page-aligned, `second_half` should be page-aligned as long as the
// page size in bytes is a power of two 2¹⁹ or smaller. This is because the user
// requested size is half of mem_option × 2²⁰ as it's in MiB.
//
// Basically, we don't support x86-64 2MiB and 1GiB pages. ARMv8 can do up to 64KiB
// (2¹⁶ bytes) pages, which should be fine. 4KiB pages seem to be the most popular though.
let page_size = unsafe { rb_zjit_get_page_size() };
assert_eq!(
virt_block as usize % page_size as usize, 0,
"Start of virtual address block should be page-aligned",
);
use crate::virtualmem::*;
use std::ptr::NonNull;
use std::rc::Rc;
use std::cell::RefCell;
let mem_block = VirtualMem::new(
crate::virtualmem::sys::SystemAllocator {},
page_size,
NonNull::new(virt_block).unwrap(),
exec_mem_bytes,
exec_mem_bytes, // TODO: change this to --zjit-mem-size (Shopify/ruby#686)
);
let mem_block = Rc::new(RefCell::new(mem_block));
CodeBlock::new(mem_block.clone(), get_option!(dump_disasm))
};
#[cfg(test)]
let mut cb = CodeBlock::new_dummy();
let stub_exit = gen_stub_exit(&mut cb).unwrap();
// Initialize the codegen globals instance
let zjit_state = ZJITState {
code_block: cb,
counters: Counters::default(),
invariants: Invariants::default(),
assert_compiles: false,
method_annotations: cruby_methods::init(),
stub_exit,
};
unsafe { ZJIT_STATE = Some(zjit_state); }
}
/// Return true if zjit_state has been initialized
pub fn has_instance() -> bool {
unsafe { ZJIT_STATE.as_mut().is_some() }
}
/// Get a mutable reference to the codegen globals instance
fn get_instance() -> &'static mut ZJITState {
unsafe { ZJIT_STATE.as_mut().unwrap() }
}
/// Get a mutable reference to the inline code block
pub fn get_code_block() -> &'static mut CodeBlock {
&mut ZJITState::get_instance().code_block
}
/// Get a mutable reference to the invariants
pub fn get_invariants() -> &'static mut Invariants {
&mut ZJITState::get_instance().invariants
}
pub fn get_method_annotations() -> &'static cruby_methods::Annotations {
&ZJITState::get_instance().method_annotations
}
/// Return true if successful compilation should be asserted
pub fn assert_compiles_enabled() -> bool {
ZJITState::get_instance().assert_compiles
}
/// Start asserting successful compilation
pub fn enable_assert_compiles() {
let instance = ZJITState::get_instance();
instance.assert_compiles = true;
}
/// Get a mutable reference to counters for ZJIT stats
pub fn get_counters() -> &'static mut Counters {
&mut ZJITState::get_instance().counters
}
/// Was --zjit-save-compiled-iseqs specified?
pub fn should_log_compiled_iseqs() -> bool {
get_option!(log_compiled_iseqs).is_some()
}
/// Log the name of a compiled ISEQ to the file specified in options.log_compiled_iseqs
pub fn log_compile(iseq_name: String) {
assert!(ZJITState::should_log_compiled_iseqs());
let filename = get_option!(log_compiled_iseqs).as_ref().unwrap();
use std::io::Write;
let mut file = match std::fs::OpenOptions::new().create(true).append(true).open(filename) {
Ok(f) => f,
Err(e) => {
eprintln!("ZJIT: Failed to create file '{}': {}", filename, e);
return;
}
};
if let Err(e) = writeln!(file, "{}", iseq_name) {
eprintln!("ZJIT: Failed to write to file '{}': {}", filename, e);
}
}
/// Check if we are allowed to compile a given ISEQ based on --zjit-allowed-iseqs
pub fn can_compile_iseq(iseq: cruby::IseqPtr) -> bool {
if let Some(ref allowed_iseqs) = get_option!(allowed_iseqs) {
let name = cruby::iseq_get_location(iseq, 0);
allowed_iseqs.contains(&name)
} else {
true // If no restrictions, allow all ISEQs
}
}
/// Return a code pointer to the side-exit trampoline for function stubs
pub fn get_stub_exit() -> CodePtr {
ZJITState::get_instance().stub_exit
}
}
/// Initialize ZJIT
#[unsafe(no_mangle)]
pub extern "C" fn rb_zjit_init() {
// 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(|| {
// Initialize ZJIT states
cruby::ids::init();
ZJITState::init();
// Install a panic hook for ZJIT
rb_bug_panic_hook();
// Discard the instruction count for boot which we never compile
unsafe { rb_vm_insns_count = 0; }
// ZJIT enabled and initialized successfully
assert!(unsafe{ !rb_zjit_enabled_p });
unsafe { rb_zjit_enabled_p = true; }
});
if result.is_err() {
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
}