Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2629505

Browse files
committed
YJIT: Non-incremental code GC
1 parent b54c8ba commit 2629505

File tree

10 files changed

+178
-8
lines changed

10 files changed

+178
-8
lines changed

yjit.c

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,7 @@ STATIC_ASSERT(pointer_tagging_scheme, USE_FLONUM);
6565
bool
6666
rb_yjit_mark_writable(void *mem_block, uint32_t mem_size)
6767
{
68-
if (mprotect(mem_block, mem_size, PROT_READ | PROT_WRITE)) {
69-
return false;
70-
}
71-
return true;
68+
return mprotect(mem_block, mem_size, PROT_READ | PROT_WRITE) == 0;
7269
}
7370

7471
void
@@ -85,6 +82,21 @@ rb_yjit_mark_executable(void *mem_block, uint32_t mem_size)
8582
}
8683
}
8784

85+
// Free the specified memory block.
86+
bool
87+
rb_yjit_mark_unused(void *mem_block, uint32_t mem_size)
88+
{
89+
// It seems like there's no way to free a memory block that works for all platforms.
90+
// So this implementation is OS-dependent.
91+
#ifdef __APPLE__
92+
// On macOS, mprotect PROT_NONE seems to reduce RSS immediately.
93+
return mprotect(mem_block, mem_size, PROT_NONE) == 0;
94+
#else
95+
// On Linux, however, you need to use madvise MADV_DONTNEED to do the same thing.
96+
return madvise(mem_block, mem_size, MADV_DONTNEED) == 0;
97+
#endif
98+
}
99+
88100
// `start` is inclusive and `end` is exclusive.
89101
void
90102
rb_yjit_icache_invalidate(void *start, void *end)
@@ -1018,6 +1030,19 @@ rb_yjit_invalidate_all_method_lookup_assumptions(void)
10181030
// method caches, so we do nothing here for now.
10191031
}
10201032

1033+
static void
1034+
update_in_use_flag(const rb_iseq_t *iseq)
1035+
{
1036+
VALUE rb_yjit_update_payload_in_use(const rb_iseq_t *iseq, bool flag);
1037+
rb_yjit_update_payload_in_use(iseq, false);
1038+
}
1039+
1040+
void
1041+
rb_yjit_scan_in_use_payloads(void)
1042+
{
1043+
rb_yjit_for_each_iseq(update_in_use_flag);
1044+
}
1045+
10211046
// Primitives used by yjit.rb
10221047
VALUE rb_yjit_stats_enabled_p(rb_execution_context_t *ec, VALUE self);
10231048
VALUE rb_yjit_trace_exit_locations_enabled_p(rb_execution_context_t *ec, VALUE self);

yjit.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,9 @@ def _print_stats
218218
$stderr.puts "invalidation_count: " + ("%10d" % stats[:invalidation_count])
219219
$stderr.puts "constant_state_bumps: " + ("%10d" % stats[:constant_state_bumps])
220220
$stderr.puts "inline_code_size: " + ("%10d" % stats[:inline_code_size])
221+
$stderr.puts "inline_freed_size: " + ("%10d" % stats[:inline_freed_size])
221222
$stderr.puts "outlined_code_size: " + ("%10d" % stats[:outlined_code_size])
223+
$stderr.puts "outlined_freed_size: " + ("%10d" % stats[:outlined_freed_size])
222224
$stderr.puts "num_gc_obj_refs: " + ("%10d" % stats[:num_gc_obj_refs])
223225

224226
$stderr.puts "total_exit_count: " + ("%10d" % total_exits)

yjit/bindgen/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ fn main() {
260260
.allowlist_function("rb_yjit_reserve_addr_space")
261261
.allowlist_function("rb_yjit_mark_writable")
262262
.allowlist_function("rb_yjit_mark_executable")
263+
.allowlist_function("rb_yjit_mark_unused")
263264
.allowlist_function("rb_yjit_get_page_size")
264265
.allowlist_function("rb_leaf_invokebuiltin_iseq_p")
265266
.allowlist_function("rb_leaf_builtin_function")

yjit/src/asm/mod.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ pub struct CodeBlock {
8282
// for example, when there is not enough space or when a jump
8383
// target is too far away.
8484
dropped_bytes: bool,
85+
86+
// The number of bytes that have been freed by code GC
87+
pub freed_bytes: usize,
8588
}
8689

8790
/// Set of CodeBlock label states. Used for recovering the previous state.
@@ -108,6 +111,7 @@ impl CodeBlock {
108111
asm_comments: BTreeMap::new(),
109112
outlined,
110113
dropped_bytes: false,
114+
freed_bytes: 0,
111115
};
112116
cb.write_pos = cb.page_start();
113117
cb
@@ -124,6 +128,7 @@ impl CodeBlock {
124128
let next_page_idx = self.write_pos / self.page_size + 1;
125129
if !self.set_page(next_page_idx, &jmp_ptr) {
126130
self.set_write_ptr(old_write_ptr); // rollback if there are no more pages
131+
self.code_gc(); // TODO: return true if there's a freed page once we implement freed page reuse
127132
return false;
128133
}
129134

@@ -293,6 +298,11 @@ impl CodeBlock {
293298
self.mem_block.borrow().start_ptr().add_bytes(offset)
294299
}
295300

301+
pub fn addrs_to_pages(&self, start_addr: CodePtr, end_addr: CodePtr) -> Vec<usize> {
302+
// TODO: move page management to codeblock
303+
self.mem_block.borrow().addrs_to_pages(start_addr, end_addr)
304+
}
305+
296306
/// Get a (possibly dangling) direct pointer to the current write position
297307
pub fn get_write_ptr(&self) -> CodePtr {
298308
self.get_ptr(self.write_pos)
@@ -431,6 +441,44 @@ impl CodeBlock {
431441
self.mem_block.borrow_mut().mark_all_executable();
432442
}
433443

444+
/// Free unused code pages
445+
fn code_gc(&mut self) {
446+
//println!("code_gc");
447+
let iseq_payloads = CodegenGlobals::get_iseq_payloads();
448+
449+
// Check which pages are still in use
450+
let num_pages = self.mem_block.borrow().num_pages();
451+
let mut alive_pages = vec![false; num_pages]; // TODO: consider using a bitmap
452+
for iseq_payload in iseq_payloads.iter() {
453+
let iseq_payload = unsafe { iseq_payload.as_ref() }.unwrap();
454+
for page in &iseq_payload.pages {
455+
alive_pages[*page] = true;
456+
}
457+
}
458+
459+
// Ask VirtualMem to free unused pages
460+
for (page, alive) in alive_pages.iter().enumerate() {
461+
if !alive {
462+
// TODO: reduce the number of syscalls by merging them for consecutive pages
463+
let freed_bytes = self.mem_block.borrow_mut().free_page(page);
464+
//println!("free_page: {}", page);
465+
self.freed_bytes += freed_bytes;
466+
}
467+
}
468+
469+
let mut cb = CodegenGlobals::get_inline_cb();
470+
cb.dropped_bytes = true;
471+
let mut ocb = CodegenGlobals::get_outlined_cb().unwrap();
472+
cb.dropped_bytes = true;
473+
474+
// TODO: Implement start-over; allow reusing freed pages
475+
// 1. scan cfp stack of all Fibers and list up which JIT payloads are still in use
476+
// 2. invalidate existing in-use code
477+
// 3. list up pages used by invalidation code
478+
// 4. make vector of freed pages + free existing pages
479+
// 5. move write_pos back to first freed page
480+
}
481+
434482
pub fn inline(&self) -> bool {
435483
!self.outlined
436484
}

yjit/src/codegen.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use std::cell::RefCell;
1717
use std::cell::RefMut;
1818
use std::cmp;
1919
use std::collections::HashMap;
20+
use std::collections::HashSet;
2021
use std::ffi::CStr;
2122
use std::mem::{self, size_of};
2223
use std::os::raw::c_uint;
@@ -653,6 +654,10 @@ pub fn gen_entry_prologue(cb: &mut CodeBlock, iseq: IseqPtr, insn_idx: u32) -> O
653654
if (cb.has_dropped_bytes()) {
654655
None
655656
} else {
657+
let mut iseq_payload = unsafe { get_or_create_iseq_payload(iseq) };
658+
for page in cb.addrs_to_pages(code_ptr, cb.get_write_ptr()) {
659+
iseq_payload.pages.insert(page);
660+
}
656661
Some(code_ptr)
657662
}
658663
}
@@ -6519,6 +6524,9 @@ pub struct CodegenGlobals {
65196524

65206525
// Methods for generating code for hardcoded (usually C) methods
65216526
method_codegen_table: HashMap<usize, MethodGenFn>,
6527+
6528+
/// List of the payload of ISEQs that are JITed and not GCed yet
6529+
iseq_payloads: HashSet<*const IseqPayload>,
65226530
}
65236531

65246532
/// For implementing global code invalidation. A position in the inline
@@ -6600,6 +6608,7 @@ impl CodegenGlobals {
66006608
global_inval_patches: Vec::new(),
66016609
inline_frozen_bytes: 0,
66026610
method_codegen_table: HashMap::new(),
6611+
iseq_payloads: HashSet::new(),
66036612
};
66046613

66056614
// Register the method codegen functions
@@ -6735,6 +6744,10 @@ impl CodegenGlobals {
67356744
Some(&mgf) => Some(mgf), // Deref
67366745
}
67376746
}
6747+
6748+
pub fn get_iseq_payloads() -> &'static mut HashSet<*const IseqPayload> {
6749+
&mut CodegenGlobals::get_instance().iseq_payloads
6750+
}
67386751
}
67396752

67406753
#[cfg(test)]

yjit/src/core.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::utils::*;
1111
use crate::disasm::*;
1212
use core::ffi::c_void;
1313
use std::cell::*;
14+
use std::collections::HashSet;
1415
use std::hash::{Hash, Hasher};
1516
use std::mem;
1617
use std::rc::{Rc};
@@ -321,7 +322,7 @@ struct Branch {
321322

322323
// Positions where the generated code starts and ends
323324
start_addr: Option<CodePtr>,
324-
end_addr: Option<CodePtr>,
325+
end_addr: Option<CodePtr>, // exclusive
325326

326327
// Context right after the branch instruction
327328
#[allow(unused)] // set but not read at the moment
@@ -475,7 +476,9 @@ impl Eq for BlockRef {}
475476
/// when calling into YJIT
476477
#[derive(Default)]
477478
pub struct IseqPayload {
479+
pub pages: HashSet<usize>,
478480
version_map: VersionMap,
481+
pub in_use: bool,
479482
}
480483

481484
impl IseqPayload {
@@ -498,7 +501,7 @@ pub fn get_iseq_payload(iseq: IseqPtr) -> Option<&'static mut IseqPayload> {
498501
}
499502

500503
/// Get the payload object associated with an iseq. Create one if none exists.
501-
fn get_or_create_iseq_payload(iseq: IseqPtr) -> &'static mut IseqPayload {
504+
pub fn get_or_create_iseq_payload(iseq: IseqPtr) -> &'static mut IseqPayload {
502505
type VoidPtr = *mut c_void;
503506

504507
let payload_non_null = unsafe {
@@ -513,6 +516,7 @@ fn get_or_create_iseq_payload(iseq: IseqPtr) -> &'static mut IseqPayload {
513516
// We allocate in those cases anyways.
514517
let new_payload = Box::into_raw(Box::new(IseqPayload::default()));
515518
rb_iseq_set_yjit_payload(iseq, new_payload as VoidPtr);
519+
CodegenGlobals::get_iseq_payloads().insert(new_payload);
516520

517521
new_payload
518522
} else {
@@ -539,17 +543,20 @@ pub extern "C" fn rb_yjit_iseq_free(payload: *mut c_void) {
539543
}
540544
};
541545

542-
use crate::invariants;
546+
// Allow code GC to free this ISEQ's JIT code
547+
CodegenGlobals::get_iseq_payloads().remove(&(payload as *const IseqPayload));
543548

544549
// Take ownership of the payload with Box::from_raw().
545550
// It drops right before this function returns.
546551
// SAFETY: We got the pointer from Box::into_raw().
547552
let payload = unsafe { Box::from_raw(payload) };
553+
//println!("pages:{:?}, versions:{:?}", payload.pages, payload.version_map.len());
548554

549555
// Increment the freed iseq count
550556
incr_counter!(freed_iseq_count);
551557

552558
// Remove all blocks in the payload from global invariants table.
559+
use crate::invariants;
553560
for versions in &payload.version_map {
554561
for block in versions {
555562
invariants::block_assumptions_free(&block);
@@ -844,6 +851,11 @@ fn add_block_version(blockref: &BlockRef, cb: &CodeBlock) {
844851
}
845852

846853
incr_counter!(compiled_block_count);
854+
855+
let mut iseq_payload = unsafe { get_iseq_payload(block.blockid.iseq) }.unwrap();
856+
for page in cb.addrs_to_pages(block.start_addr.unwrap(), block.end_addr.unwrap()) {
857+
iseq_payload.pages.insert(page);
858+
}
847859
}
848860

849861
/// Remove a block version from the version map of its parent ISEQ

yjit/src/cruby_bindings.inc.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1271,6 +1271,9 @@ extern "C" {
12711271
extern "C" {
12721272
pub fn rb_yjit_mark_executable(mem_block: *mut ::std::os::raw::c_void, mem_size: u32);
12731273
}
1274+
extern "C" {
1275+
pub fn rb_yjit_mark_unused(mem_block: *mut ::std::os::raw::c_void, mem_size: u32) -> bool;
1276+
}
12741277
extern "C" {
12751278
pub fn rb_yjit_icache_invalidate(
12761279
start: *mut ::std::os::raw::c_void,

yjit/src/stats.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,10 +364,20 @@ fn rb_yjit_gen_stats_dict() -> VALUE {
364364
let value = VALUE::fixnum_from_usize(cb.get_write_pos());
365365
rb_hash_aset(hash, key, value);
366366

367+
// GCed inline code size
368+
let key = rust_str_to_sym("inline_freed_size");
369+
let value = VALUE::fixnum_from_usize(cb.freed_bytes);
370+
rb_hash_aset(hash, key, value);
371+
367372
// Outlined code size
368373
let key = rust_str_to_sym("outlined_code_size");
369374
let value = VALUE::fixnum_from_usize(ocb.unwrap().get_write_pos());
370375
rb_hash_aset(hash, key, value);
376+
377+
// GCed outlined code size
378+
let key = rust_str_to_sym("outlined_freed_size");
379+
let value = VALUE::fixnum_from_usize(ocb.unwrap().freed_bytes);
380+
rb_hash_aset(hash, key, value);
371381
}
372382

373383
// If we're not generating stats, the hash is done

0 commit comments

Comments
 (0)