fix: GC sweep 复用 Vm.forwarding,消除每轮 HashMap 分配

This commit is contained in:
2026-06-24 15:59:55 +08:00
parent f0da49a54c
commit 4535f70f80
5 changed files with 39 additions and 3 deletions
Generated
+1
View File
@@ -1125,6 +1125,7 @@ dependencies = [
"oxide_types",
"proptest",
"regex",
"rustc-hash",
"ryu",
"serde_json",
"smallvec",
+1
View File
@@ -21,6 +21,7 @@ indexmap.workspace = true
smallvec.workspace = true
unicode-normalization.workspace = true
tracing.workspace = true
rustc-hash.workspace = true
[dev-dependencies]
oxide_compiler = { path = "../oxide_compiler" }
+30 -3
View File
@@ -5,6 +5,7 @@ use std::time::Instant;
use oxide_kernel::vm_debug;
use oxide_types::object::{JsObject, PropMetaEntry};
use oxide_types::value::JsValue;
use rustc_hash::FxBuildHasher;
use crate::builtins::{array_buffer, data_view, map, regexp, set, typed_array};
use crate::vm::Vm;
@@ -187,7 +188,7 @@ impl SessionGc {
pub(crate) fn sweep(&mut self, vm: &mut Vm) -> u64 {
let old_ptrs = std::mem::take(&mut vm.session_object_ptrs);
let mut forwarding: HashMap<*mut JsObject, *mut JsObject> = HashMap::new();
let mut forwarding = std::mem::take(&mut vm.forwarding);
let new_arena = bumpalo::Bump::new();
let mut survivors = 0u64;
let mut dead = 0u64;
@@ -251,6 +252,8 @@ impl SessionGc {
rewrite_vm_roots(vm, &forwarding);
vm.session_object_ptrs = forwarding.values().copied().collect();
forwarding.clear();
vm.forwarding = forwarding;
vm.session_epoch = new_arena;
vm.session_bytes_allocated = vm.session_object_ptrs.len() * size_of::<JsObject>();
@@ -342,7 +345,9 @@ impl Default for SessionGc {
}
}
fn rewrite_forwarded_value(value: JsValue, forwarding: &HashMap<*mut JsObject, *mut JsObject>) -> JsValue {
fn rewrite_forwarded_value(
value: JsValue, forwarding: &HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>,
) -> JsValue {
if !value.is_object() {
return value;
}
@@ -352,7 +357,7 @@ fn rewrite_forwarded_value(value: JsValue, forwarding: &HashMap<*mut JsObject, *
.unwrap_or(value)
}
fn rewrite_vm_roots(vm: &mut Vm, forwarding: &HashMap<*mut JsObject, *mut JsObject>) {
fn rewrite_vm_roots(vm: &mut Vm, forwarding: &HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>) {
for value in &mut vm.regs {
*value = rewrite_forwarded_value(*value, forwarding);
}
@@ -532,6 +537,28 @@ mod tests {
assert!(!vm.session_object_ptrs.iter().any(|ptr| unsafe { (*(*ptr)).is_gc_marked() }));
}
#[test]
fn forwarding_is_cleared_after_sweep() {
let mut vm = Vm::new();
let root = plain_object(&mut vm);
let child = plain_object(&mut vm);
unsafe {
(*root).set_prop_at(0, JsValue::from_js_object(child));
}
let root_session = vm.promote_object(root);
vm.regs[0] = JsValue::from_js_object(root_session);
let _ = vm.promote_object(child);
let roots = vm.gc_roots();
let mut gc = std::mem::take(&mut vm.session_gc);
gc.mark(&vm, &roots);
let _ = gc.sweep(&mut vm);
vm.session_gc = gc;
// The reused forwarding map MUST be cleared after sweep, otherwise
// promote would observe stale old->new entries pointing into the freed arena.
assert!(vm.forwarding.is_empty());
}
fn vm_with_low_threshold() -> Vm {
let mut cfg = KernelConfig::minimal();
cfg.set_session_gc_threshold(1);
+5
View File
@@ -155,6 +155,11 @@ pub struct Vm {
pub epoch: Epoch,
pub(crate) session_epoch: bumpalo::Bump,
pub(crate) session_gc: SessionGc,
/// Long-lived forwarding map reused by GC sweep and promote.
/// `mem::take`'d out for use, `clear()`'d and put back — capacity retained
/// across collections, so no per-event `HashMap` allocation. `FxBuildHasher`
/// because the keys are raw pointers (collision resistance is irrelevant).
pub(crate) forwarding: HashMap<*mut JsObject, *mut JsObject, rustc_hash::FxBuildHasher>,
pub(crate) epoch_object_ptrs: Vec<*mut JsObject>,
pub(crate) session_object_ptrs: Vec<*mut JsObject>,
pub(crate) session_bytes_allocated: usize,
+2
View File
@@ -32,6 +32,7 @@ impl Vm {
epoch: Epoch::new(),
session_epoch: bumpalo::Bump::new(),
session_gc: crate::session_gc::SessionGc::new(),
forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher),
epoch_object_ptrs: Vec::new(),
session_object_ptrs: Vec::new(),
session_bytes_allocated: 0,
@@ -77,6 +78,7 @@ impl Vm {
epoch: Epoch::new(),
session_epoch: bumpalo::Bump::new(),
session_gc: crate::session_gc::SessionGc::new(),
forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher),
epoch_object_ptrs: Vec::new(),
session_object_ptrs: Vec::new(),
session_bytes_allocated: 0,