Skip to content

Commit a465eab

Browse files
authored
Introduce wasmtime::Store::try_new, which handles OOM (#12415)
* Introduce `wasmtime::Store::try_new`, which handles OOM `Store::new` is an infallible constructor, so there is not a direct way to make it return an error on OOM. Additionally, it is one of the most-used functions in the Wasmtime embedder API, so changing its signature to return a `Result` is a non-starter -- it would cause way too much pain. So instead we define `Store::try_new` which returns a `Result` and make `Store::new` call and unwrap that new constructor. Part of #12069 * update disas tests and fix winch * Disable concurrency support in `Store::try_new` OOM test * Add attributes that were lost in rebase
1 parent b34556f commit a465eab

File tree

11 files changed

+88
-45
lines changed

11 files changed

+88
-45
lines changed

crates/environ/src/builtin.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ macro_rules! foreach_builtin_function {
1111
// Returns an index for wasm's `table.init`.
1212
table_init(vmctx: vmctx, table: u32, elem: u32, dst: u64, src: u64, len: u64) -> bool;
1313
// Returns an index for wasm's `elem.drop`.
14-
elem_drop(vmctx: vmctx, elem: u32);
14+
elem_drop(vmctx: vmctx, elem: u32) -> bool;
1515
// Returns an index for wasm's `memory.copy`
1616
memory_copy(vmctx: vmctx, dst_index: u32, dst: u64, src_index: u32, src: u64, len: u64) -> bool;
1717
// Returns an index for wasm's `memory.fill` instruction.
@@ -21,7 +21,7 @@ macro_rules! foreach_builtin_function {
2121
// Returns a value for wasm's `ref.func` instruction.
2222
ref_func(vmctx: vmctx, func: u32) -> pointer;
2323
// Returns an index for wasm's `data.drop` instruction.
24-
data_drop(vmctx: vmctx, data: u32);
24+
data_drop(vmctx: vmctx, data: u32) -> bool;
2525
// Returns a table entry after lazily initializing it.
2626
table_get_lazy_init_func_ref(vmctx: vmctx, table: u32, index: u64) -> pointer;
2727
// Returns an index for Wasm's `table.grow` instruction for `funcref`s.

crates/fuzzing/tests/oom.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,19 @@ fn linker_new() -> Result<()> {
138138
})
139139
}
140140

141+
#[test]
142+
#[cfg(arc_try_new)]
143+
fn store_try_new() -> Result<()> {
144+
let mut config = Config::new();
145+
config.enable_compiler(false);
146+
config.concurrency_support(false);
147+
let engine = Engine::new(&config)?;
148+
OomTest::new().test(|| {
149+
let _ = Store::try_new(&engine, ())?;
150+
Ok(())
151+
})
152+
}
153+
141154
fn ok_if_not_oom(error: Error) -> Result<()> {
142155
if error.is::<OutOfMemory>() {
143156
Err(error)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -758,7 +758,7 @@ impl<'a> Instantiator<'a> {
758758
Arc::new(imported_resources),
759759
imports,
760760
store.traitobj(),
761-
);
761+
)?;
762762
let id = store.store_data_mut().push_component_instance(instance);
763763

764764
Ok(Instantiator {

crates/wasmtime/src/runtime/store.rs

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ use crate::ThrownException;
8585
use crate::component::ComponentStoreData;
8686
#[cfg(feature = "component-model")]
8787
use crate::component::concurrent;
88+
use crate::error::OutOfMemory;
8889
#[cfg(feature = "async")]
8990
use crate::fiber;
9091
use crate::module::RegisteredModuleId;
@@ -474,7 +475,7 @@ pub struct StoreOpaque {
474475
#[cfg(feature = "stack-switching")]
475476
continuations: Vec<Box<VMContRef>>,
476477

477-
instances: PrimaryMap<InstanceId, StoreInstance>,
478+
instances: wasmtime_environ::collections::PrimaryMap<InstanceId, StoreInstance>,
478479

479480
#[cfg(feature = "component-model")]
480481
num_component_instances: usize,
@@ -725,6 +726,13 @@ impl<T> Store<T> {
725726
/// tables created to 10,000. This can be overridden with the
726727
/// [`Store::limiter`] configuration method.
727728
pub fn new(engine: &Engine, data: T) -> Self {
729+
Self::try_new(engine, data).expect(
730+
"allocation failure during `Store::new` (use `Store::try_new` to handle such errors)",
731+
)
732+
}
733+
734+
/// Like `Store::new` but returns an error on allocation failure.
735+
pub fn try_new(engine: &Engine, data: T) -> Result<Self> {
728736
let store_data = StoreData::new();
729737
log::trace!("creating new store {:?}", store_data.id());
730738

@@ -736,7 +744,7 @@ impl<T> Store<T> {
736744
vm_store_context: Default::default(),
737745
#[cfg(feature = "stack-switching")]
738746
continuations: Vec::new(),
739-
instances: PrimaryMap::new(),
747+
instances: wasmtime_environ::collections::PrimaryMap::new(),
740748
#[cfg(feature = "component-model")]
741749
num_component_instances: 0,
742750
signal_handler: None,
@@ -790,7 +798,7 @@ impl<T> Store<T> {
790798
#[cfg(feature = "debug")]
791799
breakpoints: Default::default(),
792800
};
793-
let mut inner = Box::new(StoreInner {
801+
let mut inner = try_new::<Box<_>>(StoreInner {
794802
inner,
795803
limiter: None,
796804
call_hook: None,
@@ -799,7 +807,7 @@ impl<T> Store<T> {
799807
data_no_provenance: ManuallyDrop::new(data),
800808
#[cfg(feature = "debug")]
801809
debug_handler: None,
802-
});
810+
})?;
803811

804812
let store_data =
805813
<NonNull<ManuallyDrop<T>>>::from(&mut inner.data_no_provenance).cast::<()>();
@@ -825,22 +833,30 @@ impl<T> Store<T> {
825833
// (also no limiter is passed in) so it won't have an async await
826834
// point meaning that it should be ok to assert the future is
827835
// always ready.
828-
let id = vm::assert_ready(inner.allocate_instance(
836+
let result = vm::assert_ready(inner.allocate_instance(
829837
None,
830838
AllocateInstanceKind::Dummy {
831839
allocator: &allocator,
832840
},
833841
info,
834842
Default::default(),
835-
))
836-
.expect("failed to allocate default callee");
843+
));
844+
let id = match result {
845+
Ok(id) => id,
846+
Err(e) => {
847+
if e.is::<OutOfMemory>() {
848+
return Err(e);
849+
}
850+
panic!("instance allocator failed to allocate default callee")
851+
}
852+
};
837853
let default_caller_vmctx = inner.instance(id).vmctx();
838854
inner.default_caller_vmctx = default_caller_vmctx.into();
839855
}
840856

841-
Self {
857+
Ok(Self {
842858
inner: ManuallyDrop::new(inner),
843-
}
859+
})
844860
}
845861

846862
/// Access the underlying `T` data owned by this `Store`.
@@ -2712,7 +2728,7 @@ at https://bytecodealliance.org/security.
27122728
self.instances.push(StoreInstance {
27132729
handle,
27142730
kind: StoreInstanceKind::Real { module_id },
2715-
})
2731+
})?
27162732
}
27172733
AllocateInstanceKind::Dummy { .. } => {
27182734
log::trace!(
@@ -2722,7 +2738,7 @@ at https://bytecodealliance.org/security.
27222738
self.instances.push(StoreInstance {
27232739
handle,
27242740
kind: StoreInstanceKind::Dummy,
2725-
})
2741+
})?
27262742
}
27272743
};
27282744

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use core::mem::offset_of;
2828
use core::pin::Pin;
2929
use core::ptr::NonNull;
3030
use wasmtime_environ::component::*;
31+
use wasmtime_environ::error::OutOfMemory;
3132
use wasmtime_environ::{HostPtr, PrimaryMap, VMSharedTypeIndex};
3233

3334
#[allow(
@@ -317,7 +318,7 @@ impl ComponentInstance {
317318
resource_types: Arc<PrimaryMap<ResourceIndex, ResourceType>>,
318319
imports: &Arc<PrimaryMap<RuntimeImportIndex, RuntimeImport>>,
319320
store: NonNull<dyn VMStore>,
320-
) -> OwnedComponentInstance {
321+
) -> Result<OwnedComponentInstance, OutOfMemory> {
321322
let offsets = VMComponentOffsets::new(HostPtr, component.env_component());
322323
let num_instances = component.env_component().num_runtime_component_instances;
323324
let mut instance_states = PrimaryMap::with_capacity(num_instances.try_into().unwrap());
@@ -342,11 +343,11 @@ impl ComponentInstance {
342343
store: VMStoreRawPtr(store),
343344
post_return_arg: None,
344345
vmctx: OwnedVMContext::new(),
345-
});
346+
})?;
346347
unsafe {
347348
ret.get_mut().initialize_vmctx();
348349
}
349-
ret
350+
Ok(ret)
350351
}
351352

352353
#[inline]

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

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,12 @@ use core::sync::atomic::AtomicU64;
3434
use core::{mem, ptr};
3535
#[cfg(feature = "gc")]
3636
use wasmtime_environ::ModuleInternedTypeIndex;
37+
use wasmtime_environ::error::OutOfMemory;
3738
use wasmtime_environ::{
3839
DataIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, DefinedTagIndex,
39-
ElemIndex, EntityIndex, EntityRef, EntitySet, FuncIndex, GlobalIndex, HostPtr, MemoryIndex,
40-
PrimaryMap, PtrSize, TableIndex, TableInitialValue, TableSegmentElements, TagIndex, Trap,
41-
VMCONTEXT_MAGIC, VMOffsets, VMSharedTypeIndex, packed_option::ReservedValue,
40+
ElemIndex, EntityIndex, EntityRef, FuncIndex, GlobalIndex, HostPtr, MemoryIndex, PrimaryMap,
41+
PtrSize, TableIndex, TableInitialValue, TableSegmentElements, TagIndex, Trap, VMCONTEXT_MAGIC,
42+
VMOffsets, VMSharedTypeIndex, packed_option::ReservedValue,
4243
};
4344
#[cfg(feature = "wmemcheck")]
4445
use wasmtime_wmemcheck::Wmemcheck;
@@ -167,11 +168,11 @@ impl Instance {
167168
req: InstanceAllocationRequest,
168169
memories: PrimaryMap<DefinedMemoryIndex, (MemoryAllocationIndex, Memory)>,
169170
tables: PrimaryMap<DefinedTableIndex, (TableAllocationIndex, Table)>,
170-
) -> InstanceHandle {
171+
) -> Result<InstanceHandle, OutOfMemory> {
171172
let module = req.runtime_info.env_module();
172173
let memory_tys = &module.memories;
173-
let dropped_elements = EntitySet::with_capacity(module.passive_elements.len());
174-
let dropped_data = EntitySet::with_capacity(module.passive_data_map.len());
174+
let dropped_elements = EntitySet::with_capacity(module.passive_elements.len())?;
175+
let dropped_data = EntitySet::with_capacity(module.passive_data_map.len())?;
175176

176177
#[cfg(feature = "wmemcheck")]
177178
let wmemcheck_state = if req.store.engine().config().wmemcheck {
@@ -200,14 +201,14 @@ impl Instance {
200201
wmemcheck_state,
201202
store: None,
202203
vmctx: OwnedVMContext::new(),
203-
});
204+
})?;
204205

205206
// SAFETY: this vmctx was allocated with the same layout above, so it
206207
// should be safe to initialize with the same values here.
207208
unsafe {
208209
ret.get_mut().initialize_vmctx(req.store, req.imports);
209210
}
210-
ret
211+
Ok(ret)
211212
}
212213

213214
/// Converts a raw `VMContext` pointer into a raw `Instance` pointer.
@@ -1050,13 +1051,18 @@ impl Instance {
10501051
}
10511052

10521053
/// Drop an element.
1053-
pub(crate) fn elem_drop(self: Pin<&mut Self>, elem_index: ElemIndex) {
1054+
pub(crate) fn elem_drop(
1055+
self: Pin<&mut Self>,
1056+
elem_index: ElemIndex,
1057+
) -> Result<(), OutOfMemory> {
10541058
// https://webassembly.github.io/reference-types/core/exec/instructions.html#exec-elem-drop
10551059

1056-
self.dropped_elements_mut().insert(elem_index);
1060+
self.dropped_elements_mut().insert(elem_index)?;
10571061

10581062
// Note that we don't check that we actually removed a segment because
10591063
// dropping a non-passive segment is a no-op (not a trap).
1064+
1065+
Ok(())
10601066
}
10611067

10621068
/// Get a locally-defined memory.
@@ -1218,11 +1224,16 @@ impl Instance {
12181224
}
12191225

12201226
/// Drop the given data segment, truncating its length to zero.
1221-
pub(crate) fn data_drop(self: Pin<&mut Self>, data_index: DataIndex) {
1222-
self.dropped_data_mut().insert(data_index);
1227+
pub(crate) fn data_drop(
1228+
self: Pin<&mut Self>,
1229+
data_index: DataIndex,
1230+
) -> Result<(), OutOfMemory> {
1231+
self.dropped_data_mut().insert(data_index)?;
12231232

12241233
// Note that we don't check that we actually removed a segment because
12251234
// dropping a non-passive segment is a no-op (not a trap).
1235+
1236+
Ok(())
12261237
}
12271238

12281239
/// Get a table by index regardless of whether it is locally-defined
@@ -1891,7 +1902,7 @@ impl<T: InstanceLayout> OwnedInstance<T> {
18911902
/// Allocates a new `OwnedInstance` and places `instance` inside of it.
18921903
///
18931904
/// This will `instance`
1894-
pub(super) fn new(mut instance: T) -> OwnedInstance<T> {
1905+
pub(super) fn new(mut instance: T) -> Result<OwnedInstance<T>, OutOfMemory> {
18951906
let layout = instance.layout();
18961907
debug_assert!(layout.size() >= size_of_val(&instance));
18971908
debug_assert!(layout.align() >= align_of_val(&instance));
@@ -1906,10 +1917,9 @@ impl<T: InstanceLayout> OwnedInstance<T> {
19061917
alloc::alloc::alloc(layout)
19071918
}
19081919
};
1909-
if ptr.is_null() {
1910-
alloc::alloc::handle_alloc_error(layout);
1911-
}
1912-
let instance_ptr = NonNull::new(ptr.cast::<T>()).unwrap();
1920+
let Some(instance_ptr) = NonNull::new(ptr.cast::<T>()) else {
1921+
return Err(OutOfMemory::new(layout.size()));
1922+
};
19131923

19141924
// SAFETY: it's part of the unsafe contract of `InstanceLayout` that the
19151925
// `add` here is appropriate for the layout allocated.
@@ -1938,7 +1948,7 @@ impl<T: InstanceLayout> OwnedInstance<T> {
19381948
);
19391949
debug_assert_eq!(vmctx_self_reference.addr(), ret.get().vmctx().addr());
19401950

1941-
ret
1951+
Ok(ret)
19421952
}
19431953

19441954
/// Gets the raw underlying `&Instance` from this handle.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ impl dyn InstanceAllocator + '_ {
344344
request,
345345
mem::take(&mut guard.memories),
346346
mem::take(&mut guard.tables),
347-
))
347+
)?)
348348
};
349349

350350
struct DeallocateOnDrop<'a> {

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -541,9 +541,10 @@ fn table_init(
541541
}
542542

543543
// Implementation of `elem.drop`.
544-
fn elem_drop(store: &mut dyn VMStore, instance: InstanceId, elem_index: u32) {
544+
fn elem_drop(store: &mut dyn VMStore, instance: InstanceId, elem_index: u32) -> Result<()> {
545545
let elem_index = ElemIndex::from_u32(elem_index);
546-
store.instance_mut(instance).elem_drop(elem_index)
546+
store.instance_mut(instance).elem_drop(elem_index)?;
547+
Ok(())
547548
}
548549

549550
// Implementation of `memory.copy`.
@@ -606,9 +607,10 @@ fn ref_func(store: &mut dyn VMStore, instance: InstanceId, func_index: u32) -> N
606607
}
607608

608609
// Implementation of `data.drop`.
609-
fn data_drop(store: &mut dyn VMStore, instance: InstanceId, data_index: u32) {
610+
fn data_drop(store: &mut dyn VMStore, instance: InstanceId, data_index: u32) -> Result<()> {
610611
let data_index = DataIndex::from_u32(data_index);
611-
store.instance_mut(instance).data_drop(data_index)
612+
store.instance_mut(instance).data_drop(data_index)?;
613+
Ok(())
612614
}
613615

614616
// Returns a table entry after lazily initializing it.

tests/disas/passive-data.wat

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,13 @@
4040
;; gv1 = load.i64 notrap aligned readonly gv0+8
4141
;; gv2 = load.i64 notrap aligned gv1+16
4242
;; gv3 = vmctx
43-
;; sig0 = (i64 vmctx, i32) tail
43+
;; sig0 = (i64 vmctx, i32) -> i8 tail
4444
;; fn0 = colocated u805306368:8 sig0
4545
;; stack_limit = gv2
4646
;;
4747
;; block0(v0: i64, v1: i64):
4848
;; @0044 v2 = iconst.i32 0
49-
;; @0044 call fn0(v0, v2) ; v2 = 0
49+
;; @0044 v4 = call fn0(v0, v2) ; v2 = 0
5050
;; @0047 jump block1
5151
;;
5252
;; block1:

tests/disas/winch/x64/table/init_copy_drop.wat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@
243243
;; movq %r14, %rdi
244244
;; movl $0, %esi
245245
;; movl 0xc(%rsp), %edx
246-
;; callq 0x9c0
246+
;; callq 0x9df
247247
;; addq $0xc, %rsp
248248
;; addq $4, %rsp
249249
;; movq 0x18(%rsp), %r14

0 commit comments

Comments
 (0)