Skip to content

Commit 479cdab

Browse files
authored
ZJIT: Allow testing JIT code on zjit-test (ruby#14639)
* ZJIT: Allow testing JIT code on zjit-test * Resurrect TestingAllocator tests
1 parent 06b7a70 commit 479cdab

File tree

8 files changed

+54
-65
lines changed

8 files changed

+54
-65
lines changed

zjit/src/asm/mod.rs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -317,19 +317,13 @@ impl fmt::LowerHex for CodeBlock {
317317
impl CodeBlock {
318318
/// Stubbed CodeBlock for testing. Can't execute generated code.
319319
pub fn new_dummy() -> Self {
320-
const DEFAULT_MEM_SIZE: usize = 1024;
320+
const DEFAULT_MEM_SIZE: usize = 1024 * 1024;
321321
CodeBlock::new_dummy_sized(DEFAULT_MEM_SIZE)
322322
}
323323

324324
pub fn new_dummy_sized(mem_size: usize) -> Self {
325-
use std::ptr::NonNull;
326325
use crate::virtualmem::*;
327-
use crate::virtualmem::tests::TestingAllocator;
328-
329-
let alloc = TestingAllocator::new(mem_size);
330-
let mem_start: *const u8 = alloc.mem_start();
331-
let virt_mem = VirtualMem::new(alloc, 1, NonNull::new(mem_start as *mut u8).unwrap(), mem_size, 128 * 1024 * 1024);
332-
326+
let virt_mem = VirtualMem::alloc(mem_size, None);
333327
Self::new(Rc::new(RefCell::new(virt_mem)), false)
334328
}
335329
}

zjit/src/backend/arm64/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2058,10 +2058,12 @@ mod tests {
20582058

20592059
// `IMMEDIATE_MAX_VALUE` number of dummy instructions will be generated
20602060
// plus a compare, a jump instruction, and a label.
2061-
const MEMORY_REQUIRED: usize = (IMMEDIATE_MAX_VALUE + 8)*4;
2061+
// Adding page_size to avoid OOM on the last page.
2062+
let page_size = unsafe { rb_jit_get_page_size() } as usize;
2063+
let memory_required = (IMMEDIATE_MAX_VALUE + 8) * 4 + page_size;
20622064

20632065
let mut asm = Assembler::new();
2064-
let mut cb = CodeBlock::new_dummy_sized(MEMORY_REQUIRED);
2066+
let mut cb = CodeBlock::new_dummy_sized(memory_required);
20652067

20662068
let far_label = asm.new_label("far");
20672069

zjit/src/codegen.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,6 @@ impl JITState {
7575
/// See jit_compile_exception() for details.
7676
#[unsafe(no_mangle)]
7777
pub extern "C" fn rb_zjit_iseq_gen_entry_point(iseq: IseqPtr, jit_exception: bool) -> *const u8 {
78-
// Do not test the JIT code in HIR tests
79-
if cfg!(test) {
80-
return std::ptr::null();
81-
}
82-
8378
// Take a lock to avoid writing to ISEQ in parallel with Ractors.
8479
// with_vm_lock() does nothing if the program doesn't use Ractors.
8580
with_vm_lock(src_loc!(), || {

zjit/src/cruby.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -999,7 +999,7 @@ pub use manual_defs::*;
999999
pub mod test_utils {
10001000
use std::{ptr::null, sync::Once};
10011001

1002-
use crate::{options::{internal_set_num_profiles, rb_zjit_call_threshold, rb_zjit_prepare_options, DEFAULT_CALL_THRESHOLD}, state::{rb_zjit_enabled_p, ZJITState}};
1002+
use crate::{options::{rb_zjit_call_threshold, rb_zjit_prepare_options, set_call_threshold, DEFAULT_CALL_THRESHOLD}, state::{rb_zjit_enabled_p, ZJITState}};
10031003

10041004
use super::*;
10051005

@@ -1026,7 +1026,7 @@ pub mod test_utils {
10261026

10271027
// The default rb_zjit_profile_threshold is too high, so lower it for HIR tests.
10281028
if rb_zjit_call_threshold == DEFAULT_CALL_THRESHOLD {
1029-
internal_set_num_profiles(1);
1029+
set_call_threshold(2);
10301030
}
10311031

10321032
// Pass command line options so the VM loads core library methods defined in
@@ -1099,6 +1099,12 @@ pub mod test_utils {
10991099
})
11001100
}
11011101

1102+
/// Get the #inspect of a given Ruby program in Rust string
1103+
pub fn inspect(program: &str) -> String {
1104+
let inspect = format!("({program}).inspect");
1105+
ruby_str_to_rust_string(eval(&inspect))
1106+
}
1107+
11021108
/// Get the ISeq of a specified method
11031109
pub fn get_method_iseq(recv: &str, name: &str) -> *const rb_iseq_t {
11041110
let wrapped_iseq = eval(&format!("RubyVM::InstructionSequence.of({}.method(:{}))", recv, name));

zjit/src/hir.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6545,6 +6545,7 @@ mod graphviz_tests {
65456545
#[cfg(test)]
65466546
mod opt_tests {
65476547
use super::*;
6548+
use crate::options::*;
65486549
use insta::assert_snapshot;
65496550

65506551
#[track_caller]
@@ -8481,6 +8482,7 @@ mod opt_tests {
84818482
CheckInterrupts
84828483
Return v37
84838484
");
8485+
assert_snapshot!(inspect("test"), @"{}");
84848486
}
84858487

84868488
#[test]
@@ -9580,8 +9582,7 @@ mod opt_tests {
95809582

95819583
#[test]
95829584
fn test_dont_optimize_getivar_polymorphic() {
9583-
crate::options::rb_zjit_prepare_options();
9584-
crate::options::internal_set_num_profiles(3);
9585+
set_call_threshold(3);
95859586
eval("
95869587
class C
95879588
attr_reader :foo, :bar

zjit/src/options.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -301,12 +301,11 @@ fn update_profile_threshold() {
301301
}
302302
}
303303

304+
/// Update --zjit-call-threshold for testing
304305
#[cfg(test)]
305-
pub fn internal_set_num_profiles(n: u8) {
306-
let options = unsafe { OPTIONS.as_mut().unwrap() };
307-
options.num_profiles = n;
308-
let call_threshold = n.saturating_add(1);
306+
pub fn set_call_threshold(call_threshold: u64) {
309307
unsafe { rb_zjit_call_threshold = call_threshold as u64; }
308+
rb_zjit_prepare_options();
310309
update_profile_threshold();
311310
}
312311

zjit/src/state.rs

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -54,45 +54,17 @@ static mut ZJIT_STATE: Option<ZJITState> = None;
5454
impl ZJITState {
5555
/// Initialize the ZJIT globals
5656
pub fn init() {
57-
#[cfg(not(test))]
5857
let mut cb = {
59-
use crate::cruby::*;
6058
use crate::options::*;
61-
62-
let exec_mem_bytes: usize = get_option!(exec_mem_bytes);
63-
let virt_block: *mut u8 = unsafe { rb_jit_reserve_addr_space(64 * 1024 * 1024) };
64-
65-
// Memory protection syscalls need page-aligned addresses, so check it here. Assuming
66-
// `virt_block` is page-aligned, `second_half` should be page-aligned as long as the
67-
// page size in bytes is a power of two 2¹⁹ or smaller. This is because the user
68-
// requested size is half of mem_option × 2²⁰ as it's in MiB.
69-
//
70-
// Basically, we don't support x86-64 2MiB and 1GiB pages. ARMv8 can do up to 64KiB
71-
// (2¹⁶ bytes) pages, which should be fine. 4KiB pages seem to be the most popular though.
72-
let page_size = unsafe { rb_jit_get_page_size() };
73-
assert_eq!(
74-
virt_block as usize % page_size as usize, 0,
75-
"Start of virtual address block should be page-aligned",
76-
);
77-
7859
use crate::virtualmem::*;
79-
use std::ptr::NonNull;
8060
use std::rc::Rc;
8161
use std::cell::RefCell;
8262

83-
let mem_block = VirtualMem::new(
84-
crate::virtualmem::sys::SystemAllocator {},
85-
page_size,
86-
NonNull::new(virt_block).unwrap(),
87-
exec_mem_bytes,
88-
get_option!(mem_bytes)
89-
);
63+
let mem_block = VirtualMem::alloc(get_option!(exec_mem_bytes), Some(get_option!(mem_bytes)));
9064
let mem_block = Rc::new(RefCell::new(mem_block));
9165

9266
CodeBlock::new(mem_block.clone(), get_option!(dump_disasm))
9367
};
94-
#[cfg(test)]
95-
let mut cb = CodeBlock::new_dummy();
9668

9769
let exit_trampoline = gen_exit_trampoline(&mut cb).unwrap();
9870
let function_stub_hit_trampoline = gen_function_stub_hit_trampoline(&mut cb).unwrap();

zjit/src/virtualmem.rs

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,11 @@
44
// benefit.
55

66
use std::ptr::NonNull;
7-
7+
use crate::cruby::*;
88
use crate::stats::zjit_alloc_size;
99

10-
#[cfg(test)]
11-
use crate::options::get_option;
12-
13-
#[cfg(not(test))]
1410
pub type VirtualMem = VirtualMemory<sys::SystemAllocator>;
1511

16-
#[cfg(test)]
17-
pub type VirtualMem = VirtualMemory<tests::TestingAllocator>;
18-
1912
/// Memory for generated executable machine code. When not testing, we reserve address space for
2013
/// the entire region upfront and map physical memory into the reserved address space as needed. On
2114
/// Linux, this is basically done using an `mmap` with `PROT_NONE` upfront and gradually using
@@ -33,7 +26,7 @@ pub struct VirtualMemory<A: Allocator> {
3326
region_size_bytes: usize,
3427

3528
/// mapped_region_bytes + zjit_alloc_size may not increase beyond this limit.
36-
memory_limit_bytes: usize,
29+
memory_limit_bytes: Option<usize>,
3730

3831
/// Number of bytes per "page", memory protection permission can only be controlled at this
3932
/// granularity.
@@ -113,14 +106,36 @@ pub enum WriteError {
113106

114107
use WriteError::*;
115108

109+
impl VirtualMem {
110+
/// Allocate a VirtualMem insntace with a requested size
111+
pub fn alloc(exec_mem_bytes: usize, mem_bytes: Option<usize>) -> Self {
112+
let virt_block: *mut u8 = unsafe { rb_jit_reserve_addr_space(exec_mem_bytes as u32) };
113+
114+
// Memory protection syscalls need page-aligned addresses, so check it here. Assuming
115+
// `virt_block` is page-aligned, `second_half` should be page-aligned as long as the
116+
// page size in bytes is a power of two 2¹⁹ or smaller. This is because the user
117+
// requested size is half of mem_option × 2²⁰ as it's in MiB.
118+
//
119+
// Basically, we don't support x86-64 2MiB and 1GiB pages. ARMv8 can do up to 64KiB
120+
// (2¹⁶ bytes) pages, which should be fine. 4KiB pages seem to be the most popular though.
121+
let page_size = unsafe { rb_jit_get_page_size() };
122+
assert_eq!(
123+
virt_block as usize % page_size as usize, 0,
124+
"Start of virtual address block should be page-aligned",
125+
);
126+
127+
Self::new(sys::SystemAllocator {}, page_size, NonNull::new(virt_block).unwrap(), exec_mem_bytes, mem_bytes)
128+
}
129+
}
130+
116131
impl<A: Allocator> VirtualMemory<A> {
117132
/// Bring a part of the address space under management.
118133
pub fn new(
119134
allocator: A,
120135
page_size: u32,
121136
virt_region_start: NonNull<u8>,
122137
region_size_bytes: usize,
123-
memory_limit_bytes: usize,
138+
memory_limit_bytes: Option<usize>,
124139
) -> Self {
125140
assert_ne!(0, page_size);
126141
let page_size_bytes = page_size as usize;
@@ -181,6 +196,12 @@ impl<A: Allocator> VirtualMemory<A> {
181196
let whole_region_end = start.wrapping_add(self.region_size_bytes);
182197
let alloc = &mut self.allocator;
183198

199+
// Ignore zjit_alloc_size() if self.memory_limit_bytes is None for testing
200+
let mut required_region_bytes = page_addr + page_size - start as usize;
201+
if self.memory_limit_bytes.is_some() {
202+
required_region_bytes += zjit_alloc_size();
203+
}
204+
184205
assert!((start..=whole_region_end).contains(&mapped_region_end));
185206

186207
if (start..mapped_region_end).contains(&raw) {
@@ -193,7 +214,7 @@ impl<A: Allocator> VirtualMemory<A> {
193214

194215
self.current_write_page = Some(page_addr);
195216
} else if (start..whole_region_end).contains(&raw) &&
196-
(page_addr + page_size - start as usize) + zjit_alloc_size() < self.memory_limit_bytes {
217+
required_region_bytes < self.memory_limit_bytes.unwrap_or(self.region_size_bytes) {
197218
// Writing to a brand new page
198219
let mapped_region_end_addr = mapped_region_end as usize;
199220
let alloc_size = page_addr - mapped_region_end_addr + page_size;
@@ -279,7 +300,6 @@ impl<A: Allocator> CodePtrBase for VirtualMemory<A> {
279300
}
280301

281302
/// Requires linking with CRuby to work
282-
#[cfg(not(test))]
283303
pub mod sys {
284304
use crate::cruby::*;
285305

@@ -387,7 +407,7 @@ pub mod tests {
387407
PAGE_SIZE.try_into().unwrap(),
388408
NonNull::new(mem_start as *mut u8).unwrap(),
389409
mem_size,
390-
get_option!(mem_bytes),
410+
None,
391411
)
392412
}
393413

0 commit comments

Comments
 (0)