From 0252ce1cd439ec2cacd9486b5c0cf2ee50ecc13e Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 10 Feb 2025 13:31:15 -0800 Subject: [PATCH] Implement Options as part of ZJITState --- internal/cmdlineopt.h | 4 +++- ruby.c | 33 +++++++++++++++++++------ zjit/src/codegen.rs | 35 +++++++++++++++++---------- zjit/src/lib.rs | 12 ++++------ zjit/src/options.rs | 56 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 112 insertions(+), 28 deletions(-) create mode 100644 zjit/src/options.rs diff --git a/internal/cmdlineopt.h b/internal/cmdlineopt.h index e156ba10e3..667fd6df2e 100644 --- a/internal/cmdlineopt.h +++ b/internal/cmdlineopt.h @@ -23,6 +23,9 @@ typedef struct ruby_cmdline_options { ruby_features_t warn; unsigned int dump; long backtrace_length_limit; +#if USE_ZJIT + void *zjit; +#endif const char *crash_report; @@ -39,7 +42,6 @@ typedef struct ruby_cmdline_options { #if USE_YJIT unsigned int yjit: 1; #endif - unsigned int zjit: 1; } ruby_cmdline_options_t; struct ruby_opt_message { diff --git a/ruby.c b/ruby.c index daaf9c14ae..ca2fa8ab1c 100644 --- a/ruby.c +++ b/ruby.c @@ -1184,6 +1184,21 @@ setup_yjit_options(const char *s) } #endif +#if USE_ZJIT +static void +setup_zjit_options(ruby_cmdline_options_t *opt, const char *s) +{ + // The option parsing is done in zjit/src/options.rs + extern void *rb_zjit_init_options(void); + extern bool rb_zjit_parse_option(void *options, const char *s); + + if (!opt->zjit) opt->zjit = rb_zjit_init_options(); + if (!rb_zjit_parse_option(opt->zjit, s)) { + rb_raise(rb_eRuntimeError, "invalid ZJIT option '%s' (--help will show valid zjit options)", s); + } +} +#endif + /* * Following proc_*_option functions are tree kinds: * @@ -1454,9 +1469,13 @@ proc_long_options(ruby_cmdline_options_t *opt, const char *s, long argc, char ** #endif } else if (is_option_with_optarg("zjit", '-', true, false, false)) { +#if USE_ZJIT FEATURE_SET(opt->features, FEATURE_BIT(zjit)); - extern bool rb_zjit_parse_option(); - rb_zjit_parse_option(); + setup_zjit_options(opt, s); +#else + rb_warn("Ruby was built without ZJIT support." + " You may need to install rustc to build Ruby with ZJIT."); +#endif } else if (strcmp("yydebug", s) == 0) { if (envopt) goto noenvopt_long; @@ -1799,9 +1818,8 @@ ruby_opt_init(ruby_cmdline_options_t *opt) #endif #if USE_ZJIT if (opt->zjit) { - fprintf(stderr, "test test\n"); - extern void rb_zjit_init(); - rb_zjit_init(); + extern void rb_zjit_init(void *options); + rb_zjit_init(opt->zjit); } #endif @@ -2339,8 +2357,9 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) } #endif #if USE_ZJIT - if (FEATURE_SET_P(opt->features, zjit)) { - opt->zjit = true; + if (FEATURE_SET_P(opt->features, zjit) && !opt->zjit) { + extern void *rb_zjit_init_options(void); + opt->zjit = rb_zjit_init_options(); } #endif diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index e0f9e2e43e..827a770c6d 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -1,7 +1,9 @@ +use std::mem; use std::rc::Rc; use std::cell::RefCell; use crate::cruby::*; use crate::virtualmem::*; +use crate::options::Options; /// Block of memory into which instructions can be assembled pub struct CodeBlock { @@ -111,18 +113,20 @@ impl crate::virtualmem::CodePtrBase for CodeBlock { pub struct ZJITState { /// Inline code block (fast path) code_block: CodeBlock, + + /// ZJIT command-line options + options: Options, } /// Private singleton instance of the codegen globals static mut ZJIT_STATE: Option = None; impl ZJITState { - /// Initialize the ZJIT globals - pub fn init() { - let exec_mem_size: usize = 64 * 1024 * 1024; // TODO: support the option - + /// Initialize the ZJIT globals, given options allocated by rb_zjit_init_options() + pub fn init(options: *const u8) { #[cfg(not(test))] let cb = { + let exec_mem_size: usize = 64 * 1024 * 1024; // TODO: implement the option 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 @@ -153,16 +157,18 @@ impl ZJITState { CodeBlock::new(mem_block.clone()) }; - #[cfg(not(test))] - let zjit_state = ZJITState { - code_block: cb, - }; + let options = unsafe { Box::from_raw(options as *mut Options) }; + #[cfg(not(test))] // TODO: can we get rid of this #[cfg]? + { + let zjit_state = ZJITState { + code_block: cb, + options: *options, + }; - // Initialize the codegen globals instance - #[cfg(not(test))] - unsafe { - ZJIT_STATE = Some(zjit_state); + // Initialize the codegen globals instance + unsafe { ZJIT_STATE = Some(zjit_state); } } + mem::drop(options); } /// Get a mutable reference to the codegen globals instance @@ -174,4 +180,9 @@ impl ZJITState { pub fn get_code_block() -> &'static mut CodeBlock { &mut ZJITState::get_instance().code_block } + + // Get a mutable reference to the options + pub fn get_options() -> &'static mut Options { + &mut ZJITState::get_instance().options + } } diff --git a/zjit/src/lib.rs b/zjit/src/lib.rs index 438a94ab0e..56422a37f7 100644 --- a/zjit/src/lib.rs +++ b/zjit/src/lib.rs @@ -10,6 +10,7 @@ mod virtualmem; mod asm; mod backend; mod disasm; +mod options; use backend::x86_emit; use codegen::ZJITState; @@ -19,12 +20,13 @@ use crate::cruby::*; #[no_mangle] pub static mut rb_zjit_enabled_p: bool = false; +/// Initialize ZJIT, given options allocated by rb_zjit_init_options() #[no_mangle] -pub extern "C" fn rb_zjit_init() { +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(|| { - ZJITState::init(); + ZJITState::init(options); rb_bug_panic_hook(); @@ -71,12 +73,6 @@ fn rb_bug_panic_hook() { })); } -#[no_mangle] -pub extern "C" fn rb_zjit_parse_option() -> bool { - println!("parsing zjit options"); - false -} - #[no_mangle] pub extern "C" fn rb_zjit_iseq_gen_entry_point(iseq: IseqPtr, _ec: EcPtr) -> *const u8 { ir::iseq_to_ssa(iseq).unwrap(); diff --git a/zjit/src/options.rs b/zjit/src/options.rs new file mode 100644 index 0000000000..e0af04d4c9 --- /dev/null +++ b/zjit/src/options.rs @@ -0,0 +1,56 @@ +use std::{ffi::CStr, os::raw::c_char}; + +#[derive(Clone, Copy, Debug)] +pub struct Options { + /// Dump all compiled instructions of target cb. + pub dump_disasm: bool, +} + +/// Allocate Options on the heap, initialize it, and return the address of it. +/// The return value will be modified by rb_zjit_parse_option() and then +/// passed to rb_zjit_init() for initialization. +#[no_mangle] +pub extern "C" fn rb_zjit_init_options() -> *const u8 { + let options = Options { + dump_disasm: false, + }; + Box::into_raw(Box::new(options)) as *const u8 +} + +/// Parse a --zjit* command-line flag +#[no_mangle] +pub extern "C" fn rb_zjit_parse_option(options: *const u8, str_ptr: *const c_char) -> bool { + let options = unsafe { &mut *(options as *mut Options) }; + parse_option(options, str_ptr).is_some() +} + +/// Expected to receive what comes after the third dash in "--zjit-*". +/// Empty string means user passed only "--zjit". C code rejects when +/// they pass exact "--zjit-". +fn parse_option(options: &mut Options, str_ptr: *const std::os::raw::c_char) -> Option<()> { + let c_str: &CStr = unsafe { CStr::from_ptr(str_ptr) }; + let opt_str: &str = c_str.to_str().ok()?; + + // Split the option name and value strings + // Note that some options do not contain an assignment + let parts = opt_str.split_once('='); + let (opt_name, opt_val) = match parts { + Some((before_eq, after_eq)) => (before_eq, after_eq), + None => (opt_str, ""), + }; + + // Match on the option name and value strings + match (opt_name, opt_val) { + ("", "") => {}, // Simply --zjit + + ("dump-disasm", "") => options.dump_disasm = true, + + // Option name not recognized + _ => { + return None; + } + } + + // Option successfully parsed + return Some(()); +}