summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlan Wu <[email protected]>2022-11-29 16:14:13 -0500
committerMaxime Chevalier-Boisvert <[email protected]>2022-11-30 12:23:50 -0500
commita0b0365e905e1ac51998ace7e6fc723406a2f157 ()
tree5523f45099f3a5e2c8cf3112f4acc4cf42d5defe
parentb30248f74a8f6ce37a78f07597c7c5452ff50abd (diff)
YJIT: Deallocate `struct Block` to plug memory s
Previously we essentially never freed block even after invalidation. Their reference count never reached zero for a couple of reasons: 1. `Branch::block` formed a cycle with the block holding the branch 2. Strong count on a branch that has ever contained a stub never reached 0 because we increment the `.clone()` call for `BranchRef::into_raw()` didn't have a matching decrement. It's not safe to immediately deallocate blocks during invalidation since `branch_stub_hit()` can end up running with a branch pointer from an invalidated branch. To plug the s, we wait until code GC or global invalidation and deallocate the blocks for iseqs that are definitely not running.
Notes: Merged: https://.com/ruby/ruby/pull/6833
-rw-r--r--bootstraptest/test_yjit.rb13
-rw-r--r--yjit/src/core.rs67
-rw-r--r--yjit/src/invariants.rs31
3 files changed, 84 insertions, 27 deletions
@@ -3423,3 +3423,16 @@ assert_equal '1', %q{
bar { }
bar { }
}
@@ -1,4 +1,3 @@
-//use crate::asm::x86_64::*;
use crate::asm::*;
use crate::backend::ir::*;
use crate::codegen::*;
@@ -17,6 +16,7 @@ use std::mem;
use std::rc::{Rc};
use YARVOpnd::*;
use TempMapping::*;
// Maximum number of temp value types we keep track of
pub const MAX_TEMP_TYPES: usize = 8;
@@ -492,6 +492,10 @@ pub struct IseqPayload {
// Indexes of code pages used by this this ISEQ
pub pages: HashSet<usize>,
}
impl IseqPayload {
@@ -599,8 +603,6 @@ pub extern "C" fn rb_yjit_iseq_free(payload: *mut c_void) {
}
};
- use crate::invariants;
-
// Take ownership of the payload with Box::from_raw().
// It drops right before this function returns.
// SAFETY: We got the pointer from Box::into_raw().
@@ -609,10 +611,10 @@ pub extern "C" fn rb_yjit_iseq_free(payload: *mut c_void) {
// Increment the freed iseq count
incr_counter!(freed_iseq_count);
- // Remove all blocks in the payload from global invariants table.
for versions in &payload.version_map {
for block in versions {
- invariants::block_assumptions_free(&block);
}
}
}
@@ -1896,10 +1898,12 @@ fn set_branch_target(
// Generate an outlined stub that will call branch_stub_hit()
let stub_addr = ocb.get_write_ptr();
- // Get a raw pointer to the branch while keeping the reference count alive
- // Here clone increments the strong count by 1
- // This means the branch stub owns its own reference to the branch
let branch_ptr: *const RefCell<Branch> = BranchRef::into_raw(branchref.clone());
let mut asm = Assembler::new();
asm.comment("branch stub hit");
@@ -2087,12 +2091,7 @@ pub fn defer_compilation(
asm.mark_branch_end(&branch_rc);
}
-// Remove all references to a block then free it.
-pub fn free_block(blockref: &BlockRef) {
- use crate::invariants::*;
-
- block_assumptions_free(blockref);
-
let block = blockref.borrow();
// Remove this block from the predecessor's targets
@@ -2123,6 +2122,19 @@ pub fn free_block(blockref: &BlockRef) {
}
}
}
// No explicit deallocation here as blocks are ref-counted.
}
@@ -2293,7 +2305,7 @@ pub fn invalidate_block_version(blockref: &BlockRef) {
// FIXME:
// Call continuation addresses on the stack can also be atomically replaced by jumps going to the stub.
- free_block(blockref);
ocb.unwrap().mark_all_executable();
cb.mark_all_executable();
@@ -2301,6 +2313,31 @@ pub fn invalidate_block_version(blockref: &BlockRef) {
incr_counter!(invalidation_count);
}
#[cfg(test)]
mod tests {
use crate::core::*;
@@ -497,21 +497,28 @@ pub extern "C" fn rb_yjit_tracing_invalidate_all() {
// Stop other ractors since we are going to machine code.
with_vm_lock(src_loc!(), || {
// Make it so all live block versions are no longer valid branch targets
for_each_iseq(|iseq| {
if let Some(payload) = get_iseq_payload(iseq) {
- // C comment:
- // ing the blocks for now since we might have situations where
- // a different ractor is waiting for the VM lock in branch_stub_hit().
- // If we free the block that ractor can wake up with a dangling block.
- //
- // Deviation: since we ref count the the blocks now, we might be deallocating and
- // not the block.
- //
- // Empty all blocks on the iseq so we don't compile new blocks that jump to the
- // invalidated region.
let blocks = payload.take_all_blocks();
- for blockref in blocks {
- block_assumptions_free(&blockref);
}
}