Skip to content

Commit 3a47f4e

Browse files
wksXrXr
authored andcommitted
YJIT: Move RefCell one level down
This is the second part of making YJIT work with parallel GC. During GC, `rb_yjit_iseq_mark` and `rb_yjit_iseq_update_references` need to resolve offsets in `Block::gc_obj_offsets` into absolute addresses before reading or updating the fields. This needs the base address stored in `VirtualMemory::region_start` which was previously behind a `RefCell`. When multiple GC threads scan multiple iseq simultaneously (which is possible for some GC modules such as MMTk), it will panic because the `RefCell` is already borrowed. We notice that some fields of `VirtualMemory`, such as `region_start`, are never modified once `VirtualMemory` is constructed. We change the type of the field `CodeBlock::mem_block` from `Rc<RefCell<T>>` to `Rc<T>`, and push the `RefCell` into `VirtualMemory`. We extract mutable fields of `VirtualMemory` into a dedicated struct `VirtualMemoryMut`, and store them in a field `VirtualMemory::mutable` which is a `RefCell<VirtualMemoryMut>`. After this change, methods that access immutable fields in `VirtualMemory`, particularly `base_ptr()` which reads `region_start`, will no longer need to borrow any `RefCell`. Methods that access mutable fields will need to borrow `VirtualMemory::mutable`, but the number of borrowing operations becomes strictly fewer than before because borrowing operations previously done in callers (such as `CodeBlock::write_mem`) are moved into methods of `VirtualMemory` (such as `VirtualMemory::write_bytes`).
1 parent 51a3ea5 commit 3a47f4e

File tree

3 files changed

+68
-54
lines changed

3 files changed

+68
-54
lines changed

yjit/src/asm/mod.rs

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use std::cell::RefCell;
21
use std::fmt;
32
use std::mem;
43
use std::rc::Rc;
@@ -44,7 +43,7 @@ pub struct LabelRef {
4443
/// Block of memory into which instructions can be assembled
4544
pub struct CodeBlock {
4645
// Memory for storing the encoded instructions
47-
mem_block: Rc<RefCell<VirtualMem>>,
46+
mem_block: Rc<VirtualMem>,
4847

4948
// Size of a code page in bytes. Each code page is split into an inlined and an outlined portion.
5049
// Code GC collects code memory at this granularity.
@@ -107,16 +106,16 @@ impl CodeBlock {
107106
const PREFERRED_CODE_PAGE_SIZE: usize = 16 * 1024;
108107

109108
/// Make a new CodeBlock
110-
pub fn new(mem_block: Rc<RefCell<VirtualMem>>, outlined: bool, freed_pages: Rc<Option<Vec<usize>>>, keep_comments: bool) -> Self {
109+
pub fn new(mem_block: Rc<VirtualMem>, outlined: bool, freed_pages: Rc<Option<Vec<usize>>>, keep_comments: bool) -> Self {
111110
// Pick the code page size
112-
let system_page_size = mem_block.borrow().system_page_size();
111+
let system_page_size = mem_block.system_page_size();
113112
let page_size = if 0 == Self::PREFERRED_CODE_PAGE_SIZE % system_page_size {
114113
Self::PREFERRED_CODE_PAGE_SIZE
115114
} else {
116115
system_page_size
117116
};
118117

119-
let mem_size = mem_block.borrow().virtual_region_size();
118+
let mem_size = mem_block.virtual_region_size();
120119
let mut cb = Self {
121120
mem_block,
122121
mem_size,
@@ -238,9 +237,9 @@ impl CodeBlock {
238237
}
239238

240239
// Free the grouped pages at once
241-
let start_ptr = self.mem_block.borrow().start_ptr().add_bytes(page_idx * self.page_size);
240+
let start_ptr = self.mem_block.start_ptr().add_bytes(page_idx * self.page_size);
242241
let batch_size = self.page_size * batch_idxs.len();
243-
self.mem_block.borrow_mut().free_bytes(start_ptr, batch_size as u32);
242+
self.mem_block.free_bytes(start_ptr, batch_size as u32);
244243
}
245244
}
246245

@@ -249,13 +248,13 @@ impl CodeBlock {
249248
}
250249

251250
pub fn mapped_region_size(&self) -> usize {
252-
self.mem_block.borrow().mapped_region_size()
251+
self.mem_block.mapped_region_size()
253252
}
254253

255254
/// Size of the region in bytes where writes could be attempted.
256255
#[cfg(target_arch = "aarch64")]
257256
pub fn virtual_region_size(&self) -> usize {
258-
self.mem_block.borrow().virtual_region_size()
257+
self.mem_block.virtual_region_size()
259258
}
260259

261260
/// Return the number of code pages that have been mapped by the VirtualMemory.
@@ -267,7 +266,7 @@ impl CodeBlock {
267266

268267
/// Return the number of code pages that have been reserved by the VirtualMemory.
269268
pub fn num_virtual_pages(&self) -> usize {
270-
let virtual_region_size = self.mem_block.borrow().virtual_region_size();
269+
let virtual_region_size = self.mem_block.virtual_region_size();
271270
// CodeBlock's page size != VirtualMem's page size on Linux,
272271
// so mapped_region_size % self.page_size may not be 0
273272
((virtual_region_size - 1) / self.page_size) + 1
@@ -409,7 +408,7 @@ impl CodeBlock {
409408
}
410409

411410
pub fn write_mem(&self, write_ptr: CodePtr, byte: u8) -> Result<(), WriteError> {
412-
self.mem_block.borrow_mut().write_byte(write_ptr, byte)
411+
self.mem_block.write_byte(write_ptr, byte)
413412
}
414413

415414
// Set the current write position
@@ -423,19 +422,19 @@ impl CodeBlock {
423422

424423
// Set the current write position from a pointer
425424
pub fn set_write_ptr(&mut self, code_ptr: CodePtr) {
426-
let pos = code_ptr.as_offset() - self.mem_block.borrow().start_ptr().as_offset();
425+
let pos = code_ptr.as_offset() - self.mem_block.start_ptr().as_offset();
427426
self.set_pos(pos.try_into().unwrap());
428427
}
429428

430429
/// Get a (possibly dangling) direct pointer into the executable memory block
431430
pub fn get_ptr(&self, offset: usize) -> CodePtr {
432-
self.mem_block.borrow().start_ptr().add_bytes(offset)
431+
self.mem_block.start_ptr().add_bytes(offset)
433432
}
434433

435434
/// Convert an address range to memory page indexes against a num_pages()-sized array.
436435
pub fn addrs_to_pages(&self, start_addr: CodePtr, end_addr: CodePtr) -> impl Iterator<Item = usize> {
437-
let mem_start = self.mem_block.borrow().start_ptr().raw_addr(self);
438-
let mem_end = self.mem_block.borrow().mapped_end_ptr().raw_addr(self);
436+
let mem_start = self.mem_block.start_ptr().raw_addr(self);
437+
let mem_end = self.mem_block.mapped_end_ptr().raw_addr(self);
439438
assert!(mem_start <= start_addr.raw_addr(self));
440439
assert!(start_addr.raw_addr(self) <= end_addr.raw_addr(self));
441440
assert!(end_addr.raw_addr(self) <= mem_end);
@@ -458,7 +457,7 @@ impl CodeBlock {
458457
/// Write a single byte at the current position.
459458
pub fn write_byte(&mut self, byte: u8) {
460459
let write_ptr = self.get_write_ptr();
461-
if self.has_capacity(1) && self.mem_block.borrow_mut().write_byte(write_ptr, byte).is_ok() {
460+
if self.has_capacity(1) && self.mem_block.write_byte(write_ptr, byte).is_ok() {
462461
self.write_pos += 1;
463462
} else {
464463
self.dropped_bytes = true;
@@ -591,11 +590,11 @@ impl CodeBlock {
591590
}
592591

593592
pub fn mark_all_writeable(&mut self) {
594-
self.mem_block.borrow_mut().mark_all_writeable();
593+
self.mem_block.mark_all_writeable();
595594
}
596595

597596
pub fn mark_all_executable(&mut self) {
598-
self.mem_block.borrow_mut().mark_all_executable();
597+
self.mem_block.mark_all_executable();
599598
}
600599

601600
/// Code GC. Free code pages that are not on stack and reuse them.
@@ -693,7 +692,7 @@ impl CodeBlock {
693692
let mem_start: *const u8 = alloc.mem_start();
694693
let virt_mem = VirtualMem::new(alloc, 1, NonNull::new(mem_start as *mut u8).unwrap(), mem_size, 128 * 1024 * 1024);
695694

696-
Self::new(Rc::new(RefCell::new(virt_mem)), false, Rc::new(None), true)
695+
Self::new(Rc::new(virt_mem), false, Rc::new(None), true)
697696
}
698697

699698
/// Stubbed CodeBlock for testing conditions that can arise due to code GC. Can't execute generated code.
@@ -711,15 +710,15 @@ impl CodeBlock {
711710
let mem_start: *const u8 = alloc.mem_start();
712711
let virt_mem = VirtualMem::new(alloc, 1, NonNull::new(mem_start as *mut u8).unwrap(), mem_size, 128 * 1024 * 1024);
713712

714-
Self::new(Rc::new(RefCell::new(virt_mem)), false, Rc::new(Some(freed_pages)), true)
713+
Self::new(Rc::new(virt_mem), false, Rc::new(Some(freed_pages)), true)
715714
}
716715
}
717716

718717
/// Produce hex string output from the bytes in a code block
719718
impl fmt::LowerHex for CodeBlock {
720719
fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result {
721720
for pos in 0..self.write_pos {
722-
let mem_block = &*self.mem_block.borrow();
721+
let mem_block = &*self.mem_block;
723722
let byte = unsafe { mem_block.start_ptr().raw_ptr(mem_block).add(pos).read() };
724723
fmtr.write_fmt(format_args!("{:02x}", byte))?;
725724
}
@@ -729,7 +728,7 @@ impl fmt::LowerHex for CodeBlock {
729728

730729
impl crate::virtualmem::CodePtrBase for CodeBlock {
731730
fn base_ptr(&self) -> std::ptr::NonNull<u8> {
732-
self.mem_block.borrow().base_ptr()
731+
self.mem_block.base_ptr()
733732
}
734733
}
735734

yjit/src/codegen.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11037,7 +11037,7 @@ impl CodegenGlobals {
1103711037
exec_mem_size,
1103811038
get_option!(mem_size),
1103911039
);
11040-
let mem_block = Rc::new(RefCell::new(mem_block));
11040+
let mem_block = Rc::new(mem_block);
1104111041

1104211042
let freed_pages = Rc::new(None);
1104311043

yjit/src/virtualmem.rs

Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// usize->pointer casts is viable. It seems like a lot of work for us to participate for not much
44
// benefit.
55

6-
use std::ptr::NonNull;
6+
use std::{cell::RefCell, ptr::NonNull};
77

88
use crate::{backend::ir::Target, stats::yjit_alloc_size, utils::IntoUsize};
99

@@ -36,6 +36,12 @@ pub struct VirtualMemory<A: Allocator> {
3636
/// granularity.
3737
page_size_bytes: usize,
3838

39+
/// Mutable parts.
40+
mutable: RefCell<VirtualMemoryMut<A>>,
41+
}
42+
43+
/// Mutable parts of [`VirtualMemory`].
44+
pub struct VirtualMemoryMut<A: Allocator> {
3945
/// Number of bytes that have we have allocated physical memory for starting at
4046
/// [Self::region_start].
4147
mapped_region_bytes: usize,
@@ -124,9 +130,11 @@ impl<A: Allocator> VirtualMemory<A> {
124130
region_size_bytes,
125131
memory_limit_bytes,
126132
page_size_bytes,
127-
mapped_region_bytes: 0,
128-
current_write_page: None,
129-
allocator,
133+
mutable: RefCell::new(VirtualMemoryMut {
134+
mapped_region_bytes: 0,
135+
current_write_page: None,
136+
allocator,
137+
}),
130138
}
131139
}
132140

@@ -137,7 +145,7 @@ impl<A: Allocator> VirtualMemory<A> {
137145
}
138146

139147
pub fn mapped_end_ptr(&self) -> CodePtr {
140-
self.start_ptr().add_bytes(self.mapped_region_bytes)
148+
self.start_ptr().add_bytes(self.mutable.borrow().mapped_region_bytes)
141149
}
142150

143151
pub fn virtual_end_ptr(&self) -> CodePtr {
@@ -146,7 +154,7 @@ impl<A: Allocator> VirtualMemory<A> {
146154

147155
/// Size of the region in bytes that we have allocated physical memory for.
148156
pub fn mapped_region_size(&self) -> usize {
149-
self.mapped_region_bytes
157+
self.mutable.borrow().mapped_region_bytes
150158
}
151159

152160
/// Size of the region in bytes where writes could be attempted.
@@ -161,19 +169,21 @@ impl<A: Allocator> VirtualMemory<A> {
161169
}
162170

163171
/// Write a single byte. The first write to a page makes it readable.
164-
pub fn write_byte(&mut self, write_ptr: CodePtr, byte: u8) -> Result<(), WriteError> {
172+
pub fn write_byte(&self, write_ptr: CodePtr, byte: u8) -> Result<(), WriteError> {
173+
let mut mutable = self.mutable.borrow_mut();
174+
165175
let page_size = self.page_size_bytes;
166176
let raw: *mut u8 = write_ptr.raw_ptr(self) as *mut u8;
167177
let page_addr = (raw as usize / page_size) * page_size;
168178

169-
if self.current_write_page == Some(page_addr) {
179+
if mutable.current_write_page == Some(page_addr) {
170180
// Writing within the last written to page, nothing to do
171181
} else {
172182
// Switching to a different and potentially new page
173183
let start = self.region_start.as_ptr();
174-
let mapped_region_end = start.wrapping_add(self.mapped_region_bytes);
184+
let mapped_region_end = start.wrapping_add(mutable.mapped_region_bytes);
175185
let whole_region_end = start.wrapping_add(self.region_size_bytes);
176-
let alloc = &mut self.allocator;
186+
let alloc = &mut mutable.allocator;
177187

178188
assert!((start..=whole_region_end).contains(&mapped_region_end));
179189

@@ -185,7 +195,7 @@ impl<A: Allocator> VirtualMemory<A> {
185195
return Err(FailedPageMapping);
186196
}
187197

188-
self.current_write_page = Some(page_addr);
198+
mutable.current_write_page = Some(page_addr);
189199
} else if (start..whole_region_end).contains(&raw) &&
190200
(page_addr + page_size - start as usize) + yjit_alloc_size() < self.memory_limit_bytes {
191201
// Writing to a brand new page
@@ -217,9 +227,9 @@ impl<A: Allocator> VirtualMemory<A> {
217227
unreachable!("unknown arch");
218228
}
219229
}
220-
self.mapped_region_bytes = self.mapped_region_bytes + alloc_size;
230+
mutable.mapped_region_bytes = mutable.mapped_region_bytes + alloc_size;
221231

222-
self.current_write_page = Some(page_addr);
232+
mutable.current_write_page = Some(page_addr);
223233
} else {
224234
return Err(OutOfBounds);
225235
}
@@ -233,14 +243,16 @@ impl<A: Allocator> VirtualMemory<A> {
233243

234244
/// Make all the code in the region writeable.
235245
/// Call this during GC before the phase of updating reference fields.
236-
pub fn mark_all_writeable(&mut self) {
237-
self.current_write_page = None;
246+
pub fn mark_all_writeable(&self) {
247+
let mut mutable = self.mutable.borrow_mut();
248+
249+
mutable.current_write_page = None;
238250

239251
let region_start = self.region_start;
240-
let mapped_region_bytes: u32 = self.mapped_region_bytes.try_into().unwrap();
252+
let mapped_region_bytes: u32 = mutable.mapped_region_bytes.try_into().unwrap();
241253

242254
// Make mapped region executable
243-
if !self.allocator.mark_writable(region_start.as_ptr(), mapped_region_bytes) {
255+
if !mutable.allocator.mark_writable(region_start.as_ptr(), mapped_region_bytes) {
244256
panic!("Cannot make memory region writable: {:?}-{:?}",
245257
region_start.as_ptr(),
246258
unsafe { region_start.as_ptr().add(mapped_region_bytes as usize)}
@@ -250,18 +262,20 @@ impl<A: Allocator> VirtualMemory<A> {
250262

251263
/// Make all the code in the region executable. Call this at the end of a write session.
252264
/// See [Self] for usual usage flow.
253-
pub fn mark_all_executable(&mut self) {
254-
self.current_write_page = None;
265+
pub fn mark_all_executable(&self) {
266+
let mut mutable = self.mutable.borrow_mut();
267+
268+
mutable.current_write_page = None;
255269

256270
let region_start = self.region_start;
257-
let mapped_region_bytes: u32 = self.mapped_region_bytes.try_into().unwrap();
271+
let mapped_region_bytes: u32 = mutable.mapped_region_bytes.try_into().unwrap();
258272

259273
// Make mapped region executable
260-
self.allocator.mark_executable(region_start.as_ptr(), mapped_region_bytes);
274+
mutable.allocator.mark_executable(region_start.as_ptr(), mapped_region_bytes);
261275
}
262276

263277
/// Free a range of bytes. start_ptr must be memory page-aligned.
264-
pub fn free_bytes(&mut self, start_ptr: CodePtr, size: u32) {
278+
pub fn free_bytes(&self, start_ptr: CodePtr, size: u32) {
265279
assert_eq!(start_ptr.raw_ptr(self) as usize % self.page_size_bytes, 0);
266280

267281
// Bounds check the request. We should only free memory we manage.
@@ -274,7 +288,8 @@ impl<A: Allocator> VirtualMemory<A> {
274288
// code page, it's more appropriate to check the last byte against the virtual region.
275289
assert!(virtual_region.contains(&last_byte_to_free));
276290

277-
self.allocator.mark_unused(start_ptr.raw_ptr(self), size);
291+
let mut mutable = self.mutable.borrow_mut();
292+
mutable.allocator.mark_unused(start_ptr.raw_ptr(self), size);
278293
}
279294
}
280295

@@ -403,33 +418,33 @@ pub mod tests {
403418
#[test]
404419
#[cfg(target_arch = "x86_64")]
405420
fn new_memory_is_initialized() {
406-
let mut virt = new_dummy_virt_mem();
421+
let virt = new_dummy_virt_mem();
407422

408423
virt.write_byte(virt.start_ptr(), 1).unwrap();
409424
assert!(
410-
virt.allocator.memory[..PAGE_SIZE].iter().all(|&byte| byte != 0),
425+
virt.mutable.borrow().allocator.memory[..PAGE_SIZE].iter().all(|&byte| byte != 0),
411426
"Entire page should be initialized",
412427
);
413428

414429
// Skip a few page
415430
let three_pages = 3 * PAGE_SIZE;
416431
virt.write_byte(virt.start_ptr().add_bytes(three_pages), 1).unwrap();
417432
assert!(
418-
virt.allocator.memory[..three_pages].iter().all(|&byte| byte != 0),
433+
virt.mutable.borrow().allocator.memory[..three_pages].iter().all(|&byte| byte != 0),
419434
"Gaps between write requests should be filled",
420435
);
421436
}
422437

423438
#[test]
424439
fn no_redundant_syscalls_when_writing_to_the_same_page() {
425-
let mut virt = new_dummy_virt_mem();
440+
let virt = new_dummy_virt_mem();
426441

427442
virt.write_byte(virt.start_ptr(), 1).unwrap();
428443
virt.write_byte(virt.start_ptr(), 0).unwrap();
429444

430445
assert!(
431446
matches!(
432-
virt.allocator.requests[..],
447+
virt.mutable.borrow().allocator.requests[..],
433448
[MarkWritable { start_idx: 0, length: PAGE_SIZE }],
434449
)
435450
);
@@ -438,7 +453,7 @@ pub mod tests {
438453
#[test]
439454
fn bounds_checking() {
440455
use super::WriteError::*;
441-
let mut virt = new_dummy_virt_mem();
456+
let virt = new_dummy_virt_mem();
442457

443458
let one_past_end = virt.start_ptr().add_bytes(virt.virtual_region_size());
444459
assert_eq!(Err(OutOfBounds), virt.write_byte(one_past_end, 0));
@@ -451,15 +466,15 @@ pub mod tests {
451466
fn only_written_to_regions_become_executable() {
452467
// ... so we catch attempts to read/write/execute never-written-to regions
453468
const THREE_PAGES: usize = PAGE_SIZE * 3;
454-
let mut virt = new_dummy_virt_mem();
469+
let virt = new_dummy_virt_mem();
455470
let page_two_start = virt.start_ptr().add_bytes(PAGE_SIZE * 2);
456471
virt.write_byte(page_two_start, 1).unwrap();
457472
virt.mark_all_executable();
458473

459474
assert!(virt.virtual_region_size() > THREE_PAGES);
460475
assert!(
461476
matches!(
462-
virt.allocator.requests[..],
477+
virt.mutable.borrow().allocator.requests[..],
463478
[
464479
MarkWritable { start_idx: 0, length: THREE_PAGES },
465480
MarkExecutable { start_idx: 0, length: THREE_PAGES },

0 commit comments

Comments
 (0)