Skip to content

Commit 599d821

Browse files
Properly manage ownership of GC refs in passive element segments (#13179) (#13183)
* Properly manage ownership of GC refs in passive element segments We need to `clone_gc_ref` when pushing into the segment and `drop_gc_ref` when clearing the segment, similar to what we do for e.g. setting `VMGlobalDefinition`s. Fixes #13066 * fix cfg warnings * fix cfg_attr * remove cfg_attr Co-authored-by: Nick Fitzgerald <fitzgen@gmail.com>
1 parent a0319c1 commit 599d821

6 files changed

Lines changed: 185 additions & 30 deletions

File tree

crates/wasmtime/src/runtime/func.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,10 +1064,6 @@ impl Func {
10641064
self.vm_func_ref(store).as_ptr().cast()
10651065
}
10661066

1067-
pub(crate) fn to_val_raw(&self, store: &mut StoreOpaque) -> ValRaw {
1068-
ValRaw::funcref(self.to_raw_(store))
1069-
}
1070-
10711067
/// Invokes this function with the `params` given, returning the results
10721068
/// asynchronously.
10731069
///

crates/wasmtime/src/runtime/vm/instance.rs

Lines changed: 82 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::runtime::vm::vmcontext::{
1313
VMTableDefinition, VMTableImport, VMTagDefinition, VMTagImport,
1414
};
1515
use crate::runtime::vm::{
16-
GcStore, HostResult, Imports, ModuleRuntimeInfo, SendSyncPtr, VMGlobalKind, VMStore,
16+
GcStore, HostResult, Imports, ModuleRuntimeInfo, SendSyncPtr, VMGcRef, VMGlobalKind, VMStore,
1717
VMStoreRawPtr, VmPtr, VmSafe, WasmFault, catch_unwind_and_record_trap,
1818
};
1919
use crate::store::{
@@ -133,7 +133,7 @@ pub struct Instance {
133133
//
134134
// TODO(#12621): This should be a `TrySecondaryMap<PassiveElemIndex, _>`
135135
// but that type is currently footgun-y / isn't actually OOM-safe yet.
136-
passive_elements: TryVec<Option<(NeedsGcRooting, TryVec<ValRaw>)>>,
136+
passive_elements: TryVec<PassiveElementSegment>,
137137

138138
/// Stores the dropped passive data segments in this instantiation by index.
139139
/// If the index is present in the set, the segment has been dropped.
@@ -225,8 +225,8 @@ impl Instance {
225225
gc_roots: &mut crate::vm::GcRootsList,
226226
) {
227227
for segment in self.passive_elements_mut().iter_mut() {
228-
if let Some((wasmtime_environ::NeedsGcRooting::Yes, elems)) = segment {
229-
for e in elems {
228+
if segment.needs_gc_rooting() == NeedsGcRooting::Yes {
229+
for e in segment.elements() {
230230
let Some(root) = e.as_vmgc_ref_ptr() else {
231231
continue;
232232
};
@@ -912,16 +912,12 @@ impl Instance {
912912
return &[];
913913
};
914914

915-
let Some((_, seg)) = &self.passive_elements[passive.index()] else {
916-
return &[];
917-
};
918-
919-
&**seg
915+
self.passive_elements[passive.index()].elements()
920916
}
921917

922918
pub(crate) fn passive_elements_mut(
923919
self: Pin<&mut Self>,
924-
) -> Pin<&mut TryVec<Option<(NeedsGcRooting, TryVec<ValRaw>)>>> {
920+
) -> Pin<&mut TryVec<PassiveElementSegment>> {
925921
// SAFETY: Not moving data out of `self`.
926922
Pin::new(&mut unsafe { self.get_unchecked_mut() }.passive_elements)
927923
}
@@ -996,6 +992,7 @@ impl Instance {
996992
/// Drop an element.
997993
pub(crate) fn elem_drop(
998994
self: Pin<&mut Self>,
995+
gc_store: Option<&mut GcStore>,
999996
elem_index: ElemIndex,
1000997
) -> Result<(), OutOfMemory> {
1001998
// https://webassembly.github.io/reference-types/core/exec/instructions.html#exec-elem-drop
@@ -1010,7 +1007,7 @@ impl Instance {
10101007
return Ok(());
10111008
};
10121009

1013-
self.passive_elements_mut()[passive_index.index()] = None;
1010+
self.passive_elements_mut()[passive_index.index()].clear(gc_store);
10141011
Ok(())
10151012
}
10161013

@@ -1922,3 +1919,77 @@ impl<T: InstanceLayout> Drop for OwnedInstance<T> {
19221919
}
19231920
}
19241921
}
1922+
1923+
#[derive(Debug)]
1924+
pub(crate) struct PassiveElementSegment {
1925+
needs_gc_rooting: NeedsGcRooting,
1926+
elements: TryVec<ValRaw>,
1927+
}
1928+
1929+
impl PassiveElementSegment {
1930+
/// Create a new passive element segment with the given capacity.
1931+
pub(crate) fn new(
1932+
needs_gc_rooting: NeedsGcRooting,
1933+
capacity: usize,
1934+
) -> Result<Self, OutOfMemory> {
1935+
Ok(Self {
1936+
needs_gc_rooting,
1937+
elements: TryVec::with_capacity(capacity)?,
1938+
})
1939+
}
1940+
1941+
/// Push a value onto this passive element segment.
1942+
///
1943+
/// NB: Does not type check the value, relies on callers to ensure the value
1944+
/// is of the correct type (generally, due to validation).
1945+
pub(crate) fn push(&mut self, store: &mut StoreOpaque, val: Val) -> Result<()> {
1946+
let val = {
1947+
let mut store = AutoAssertNoGc::new(store);
1948+
val.to_raw_(&mut store)?
1949+
};
1950+
let val = self.clone_gc_ref(store, val);
1951+
self.elements.push(val)?;
1952+
Ok(())
1953+
}
1954+
1955+
fn clone_gc_ref(&mut self, store: &mut StoreOpaque, val: ValRaw) -> ValRaw {
1956+
if let NeedsGcRooting::Yes = self.needs_gc_rooting {
1957+
let gc_ref = val.get_anyref();
1958+
if let Some(gc_ref) = VMGcRef::from_raw_u32(gc_ref) {
1959+
if let Some(gc_store) = store.optional_gc_store_mut() {
1960+
return ValRaw::anyref(gc_store.clone_gc_ref(&gc_ref).as_raw_u32());
1961+
}
1962+
}
1963+
}
1964+
val
1965+
}
1966+
1967+
/// Clear this segment's elements.
1968+
pub(crate) fn clear(&mut self, mut gc_store: Option<&mut GcStore>) {
1969+
for val in mem::take(&mut self.elements) {
1970+
self.drop_gc_ref(&mut gc_store, val);
1971+
}
1972+
}
1973+
1974+
fn drop_gc_ref(&mut self, gc_store: &mut Option<&mut GcStore>, val: ValRaw) {
1975+
if let NeedsGcRooting::Yes = self.needs_gc_rooting {
1976+
let gc_ref = val.get_anyref();
1977+
if let Some(gc_ref) = VMGcRef::from_raw_u32(gc_ref) {
1978+
if let Some(gc_store) = gc_store.as_deref_mut() {
1979+
let _ = gc_store.drop_gc_ref(gc_ref);
1980+
}
1981+
}
1982+
}
1983+
}
1984+
1985+
/// Whether this segment needs GC rooting and tracing.
1986+
#[cfg(feature = "gc")]
1987+
pub(crate) fn needs_gc_rooting(&self) -> NeedsGcRooting {
1988+
self.needs_gc_rooting
1989+
}
1990+
1991+
/// The elements of this segment.
1992+
pub(crate) fn elements(&self) -> &[ValRaw] {
1993+
&self.elements
1994+
}
1995+
}

crates/wasmtime/src/runtime/vm/instance/allocator.rs

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ use crate::runtime::vm::memory::Memory;
66
use crate::runtime::vm::mpk::ProtectionKey;
77
use crate::runtime::vm::table::Table;
88
use crate::runtime::vm::{CompiledModuleId, ModuleRuntimeInfo};
9-
use crate::store::{Asyncness, AutoAssertNoGc, InstanceId, StoreOpaque, StoreResourceLimiter};
9+
use crate::store::{Asyncness, InstanceId, StoreOpaque, StoreResourceLimiter};
10+
use crate::vm::instance::PassiveElementSegment;
1011
use crate::{OpaqueRootScope, Val};
1112
use core::future::Future;
1213
use core::pin::Pin;
@@ -859,40 +860,34 @@ async fn initialize_passive_elements(
859860
for (idx, segment) in &module.passive_elements {
860861
match segment {
861862
TableSegmentElements::Functions(func_indices) => {
862-
let mut vals = TryVec::with_capacity(func_indices.len())?;
863+
let mut segment =
864+
PassiveElementSegment::new(NeedsGcRooting::No, func_indices.len())?;
863865
for func_idx in func_indices {
864866
let (instance, registry) =
865867
store.instance_and_module_registry_mut(context.instance);
866868
// SAFETY: `store_id` is for the store that owns this instance.
867869
let func = unsafe { instance.get_exported_func(registry, store_id, *func_idx) };
868-
vals.push(func.to_val_raw(store))?;
870+
segment.push(store, func.into())?;
869871
}
870872
let instance = store.instance_mut(context.instance);
871873
debug_assert_eq!(instance.passive_elements.len(), idx.index());
872-
instance
873-
.passive_elements_mut()
874-
.push(Some((NeedsGcRooting::No, vals)))?;
874+
instance.passive_elements_mut().push(segment)?;
875875
}
876876
TableSegmentElements::Expressions {
877877
needs_gc_rooting,
878878
exprs,
879879
} => {
880-
let mut vals = TryVec::with_capacity(exprs.len())?;
880+
let mut segment = PassiveElementSegment::new(*needs_gc_rooting, exprs.len())?;
881881
for expr in exprs {
882882
let mut store = OpaqueRootScope::new(&mut *store);
883-
884883
let val = const_evaluator
885884
.eval(&mut store, limiter.as_deref_mut(), context, expr)
886885
.await?;
887-
888-
let mut store = AutoAssertNoGc::new(&mut store);
889-
vals.push(val.to_raw_(&mut store)?)?;
886+
segment.push(&mut store, *val)?;
890887
}
891888
let instance = store.instance_mut(context.instance);
892889
debug_assert_eq!(instance.passive_elements.len(), idx.index());
893-
instance
894-
.passive_elements_mut()
895-
.push(Some((*needs_gc_rooting, vals)))?;
890+
instance.passive_elements_mut().push(segment)?;
896891
}
897892
}
898893
}

crates/wasmtime/src/runtime/vm/libcalls.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,8 @@ fn table_init(
528528
// Implementation of `elem.drop`.
529529
fn elem_drop(store: &mut dyn VMStore, instance: InstanceId, elem_index: u32) -> Result<()> {
530530
let elem_index = ElemIndex::from_u32(elem_index);
531-
store.instance_mut(instance).elem_drop(elem_index)?;
531+
let (gc_store, instance) = store.optional_gc_store_and_instance_mut(instance);
532+
instance.elem_drop(gc_store, elem_index)?;
532533
Ok(())
533534
}
534535

tests/all/gc.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1705,6 +1705,67 @@ fn select_gc_ref_stack_map() -> Result<()> {
17051705
Ok(())
17061706
}
17071707

1708+
#[test]
1709+
#[cfg_attr(miri, ignore)]
1710+
fn dropped_passive_elems_do_not_leak() -> Result<()> {
1711+
let _ = env_logger::try_init();
1712+
1713+
let mut config = Config::new();
1714+
config.wasm_function_references(true);
1715+
config.wasm_gc(true);
1716+
config.collector(Collector::DeferredReferenceCounting);
1717+
1718+
let engine = Engine::new(&config)?;
1719+
1720+
let module = Module::new(
1721+
&engine,
1722+
r#"
1723+
(module
1724+
(type $a (array (mut externref)))
1725+
(type $aa (array (ref $a)))
1726+
1727+
(elem $e (ref $a) (array.new_default $a (i32.const 1)))
1728+
1729+
(func (export "run") (param $x externref)
1730+
(local $l (ref null $aa))
1731+
1732+
;; Create an array from the passive element segment.
1733+
(local.set $l (array.new_elem $aa $e (i32.const 0) (i32.const 1)))
1734+
1735+
;; Put our externref into the passive element segment's array:
1736+
;; `l[0][0] = x`.
1737+
(array.set $a
1738+
(array.get $aa (local.get $l) (i32.const 0))
1739+
(i32.const 0)
1740+
(local.get $x))
1741+
1742+
;; Drop the passive element segment. This should make `x`
1743+
;; unreachable upon return, since we aren't keeping our
1744+
;; array live nor the passive element segment.
1745+
(elem.drop $e)
1746+
)
1747+
)
1748+
"#,
1749+
)?;
1750+
1751+
let mut store = Store::new(&engine, ());
1752+
let dropped = Arc::new(AtomicBool::new(false));
1753+
1754+
{
1755+
let mut store = RootScope::new(&mut store);
1756+
let e = ExternRef::new(&mut store, SetFlagOnDrop(dropped.clone()))?;
1757+
1758+
let instance = Instance::new(&mut store, &module, &[])?;
1759+
let run = instance.get_typed_func::<Rooted<ExternRef>, ()>(&mut store, "run")?;
1760+
run.call(&mut store, e)?;
1761+
}
1762+
1763+
store.gc(None)?;
1764+
assert!(dropped.load(SeqCst));
1765+
1766+
Ok(())
1767+
}
1768+
17081769
#[test]
17091770
#[cfg_attr(miri, ignore)]
17101771
fn gc_heap_does_not_grow_unboundedly() -> Result<()> {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
;;! gc = true
2+
;;! bulk_memory = true
3+
4+
(module
5+
(import "wasmtime" "gc" (func $gc))
6+
(type $s (struct (field i32)))
7+
(table $t 10 anyref)
8+
9+
;; Passive element segment with a GC struct
10+
(elem $e anyref (struct.new $s (i32.const 42)))
11+
12+
;; Copy passive element into table
13+
(func (export "init")
14+
(table.init $t $e (i32.const 0) (i32.const 0) (i32.const 1))
15+
)
16+
17+
;; Read the struct from the table and return its field value
18+
(func (export "get_field") (result i32)
19+
(struct.get $s 0
20+
(ref.cast (ref $s)
21+
(table.get $t (i32.const 0))
22+
)
23+
)
24+
)
25+
26+
(export "gc" (func $gc))
27+
)
28+
29+
(assert_return (invoke "gc"))
30+
(assert_return (invoke "init"))
31+
(assert_return (invoke "get_field") (i32.const 42))

0 commit comments

Comments
 (0)