1 //! Runtime library calls.
2 //!
3 //! Note that Wasm compilers may sometimes perform these inline rather than
4 //! calling them, particularly when CPUs have special instructions which compute
5 //! them directly.
6 //!
7 //! These functions are called by compiled Wasm code, and therefore must take
8 //! certain care about some things:
9 //!
10 //! * They must only contain basic, raw i32/i64/f32/f64/pointer parameters that
11 //! are safe to pass across the system ABI.
12 //!
13 //! * If any nested function propagates an `Err(trap)` out to the library
14 //! function frame, we need to raise it. This involves some nasty and quite
15 //! unsafe code under the covers! Notably, after raising the trap, drops
16 //! **will not** be run for local variables! This can lead to things like
17 //! leaking `InstanceHandle`s which leads to never deallocating JIT code,
18 //! instances, and modules if we are not careful!
19 //!
20 //! * The libcall must be entered via a Wasm-to-libcall trampoline that saves
21 //! the last Wasm FP and PC for stack walking purposes. (For more details, see
22 //! `crates/wasmtime/src/runtime/vm/backtrace.rs`.)
23 //!
24 //! To make it easier to correctly handle all these things, **all** libcalls
25 //! must be defined via the `libcall!` helper macro! See its doc comments below
26 //! for an example, or just look at the rest of the file.
27 //!
28 //! ## Dealing with `externref`s
29 //!
30 //! When receiving a raw `*mut u8` that is actually a `VMExternRef` reference,
31 //! convert it into a proper `VMExternRef` with `VMExternRef::clone_from_raw` as
32 //! soon as apossible. Any GC before raw pointer is converted into a reference
33 //! can potentially collect the referenced object, which could lead to use after
34 //! free.
35 //!
36 //! Avoid this by eagerly converting into a proper `VMExternRef`! (Unfortunately
37 //! there is no macro to help us automatically get this correct, so stay
38 //! vigilant!)
39 //!
40 //! ```ignore
41 //! pub unsafe extern "C" my_libcall_takes_ref(raw_extern_ref: *mut u8) {
42 //! // Before `clone_from_raw`, `raw_extern_ref` is potentially unrooted,
43 //! // and doing GC here could lead to use after free!
44 //!
45 //! let my_extern_ref = if raw_extern_ref.is_null() {
46 //! None
47 //! } else {
48 //! Some(VMExternRef::clone_from_raw(raw_extern_ref))
49 //! };
50 //!
51 //! // Now that we did `clone_from_raw`, it is safe to do a GC (or do
52 //! // anything else that might transitively GC, like call back into
53 //! // Wasm!)
54 //! }
55 //! ```
56
57 #[cfg(feature = "stack-switching")]
58 use super::stack_switching::VMContObj;
59 use crate::prelude::*;
60 use crate::runtime::store::{Asyncness, InstanceId, StoreInstanceId, StoreOpaque};
61 #[cfg(feature = "gc")]
62 use crate::runtime::vm::VMGcRef;
63 use crate::runtime::vm::table::TableElementType;
64 use crate::runtime::vm::vmcontext::VMFuncRef;
65 use crate::runtime::vm::{
66 self, HostResultHasUnwindSentinel, SendSyncPtr, TrapReason, VMStore, f32x4, f64x2, i8x16,
67 };
68 use core::convert::Infallible;
69 use core::ptr::NonNull;
70 #[cfg(feature = "threads")]
71 use core::time::Duration;
72 use wasmtime_core::math::WasmFloat;
73 use wasmtime_environ::{
74 DataIndex, DefinedMemoryIndex, DefinedTableIndex, ElemIndex, FuncIndex, MemoryIndex,
75 TableIndex, Trap,
76 };
77 #[cfg(feature = "wmemcheck")]
78 use wasmtime_wmemcheck::AccessError::{
79 DoubleMalloc, InvalidFree, InvalidRead, InvalidWrite, OutOfBounds,
80 };
81
82 /// Raw functions which are actually called from compiled code.
83 ///
84 /// Invocation of a builtin currently looks like:
85 ///
86 /// * A wasm function calls a cranelift-compiled trampoline that's generated
87 /// once-per-builtin.
88 /// * The cranelift-compiled trampoline performs any necessary actions to exit
89 /// wasm, such as dealing with fp/pc/etc.
90 /// * The cranelift-compiled trampoline loads a function pointer from an array
91 /// stored in `VMContext` That function pointer is defined in this module.
92 /// * This module runs, handling things like `catch_unwind` and `Result` and
93 /// such.
94 /// * This module delegates to the outer module (this file) which has the actual
95 /// implementation.
96 ///
97 /// For more information on converting from host-defined values to Cranelift ABI
98 /// values see the `catch_unwind_and_record_trap` function.
99 pub mod raw {
100 use crate::runtime::vm::{Instance, VMContext, f32x4, f64x2, i8x16};
101 use core::ptr::NonNull;
102
103 macro_rules! libcall {
104 (
105 $(
106 $( #[cfg($attr:meta)] )?
107 $name:ident( vmctx: vmctx $(, $pname:ident: $param:ident )* ) $(-> $result:ident)?;
108 )*
109 ) => {
110 $(
111 // This is the direct entrypoint from the compiled module which
112 // still has the raw signature.
113 //
114 // This will delegate to the outer module to the actual
115 // implementation and automatically perform `catch_unwind` along
116 // with conversion of the return value in the face of traps.
117 #[allow(improper_ctypes_definitions, reason = "__m128i known not FFI-safe")]
118 #[allow(unused_variables, reason = "macro-generated")]
119 #[allow(unreachable_code, reason = "some types uninhabited on some platforms")]
120 pub unsafe extern "C" fn $name(
121 vmctx: NonNull<VMContext>,
122 $( $pname : libcall!(@ty $param), )*
123 ) $(-> libcall!(@ty $result))? {
124 $(#[cfg($attr)])?
125 unsafe {
126 Instance::enter_host_from_wasm(vmctx, |store, instance| {
127 super::$name(store, instance, $($pname),*)
128 })
129 }
130 $(
131 #[cfg(not($attr))]
132 {
133 let _ = vmctx;
134 unreachable!();
135 }
136 )?
137 }
138
139 // This works around a `rustc` bug where compiling with LTO
140 // will sometimes strip out some of these symbols resulting
141 // in a linking failure.
142 #[allow(improper_ctypes_definitions, reason = "__m128i known not FFI-safe")]
143 const _: () = {
144 #[used]
145 static I_AM_USED: unsafe extern "C" fn(
146 NonNull<VMContext>,
147 $( $pname : libcall!(@ty $param), )*
148 ) $( -> libcall!(@ty $result))? = $name;
149 };
150 )*
151 };
152
153 (@ty u32) => (u32);
154 (@ty u64) => (u64);
155 (@ty f32) => (f32);
156 (@ty f64) => (f64);
157 (@ty u8) => (u8);
158 (@ty i8x16) => (i8x16);
159 (@ty f32x4) => (f32x4);
160 (@ty f64x2) => (f64x2);
161 (@ty bool) => (bool);
162 (@ty pointer) => (*mut u8);
163 (@ty size) => (usize);
164 }
165
166 wasmtime_environ::foreach_builtin_function!(libcall);
167 }
168
169 /// Uses the `$store` provided to invoke the async closure `$f` and block on the
170 /// result.
171 ///
172 /// This will internally multiplex on `$store.with_blocking(...)` vs simply
173 /// asserting the closure is ready depending on whether a store's
174 /// `can_block` flag is set or not.
175 ///
176 /// FIXME: ideally this would be a function, not a macro. If this is a function
177 /// though it would require placing a bound on the async closure $f where the
178 /// returned future is itself `Send`. That's not possible in Rust right now,
179 /// unfortunately.
180 ///
181 /// As a workaround this takes advantage of the fact that we can assume that the
182 /// compiler can infer that the future returned by `$f` is indeed `Send` so long
183 /// as we don't try to name the type or place it behind a generic. In the future
184 /// when we can bound the return future of async functions with `Send` this
185 /// macro should be replaced with an equivalent function.
186 macro_rules! block_on {
187 ($store:expr, $f:expr) => {{
188 let store: &mut StoreOpaque = $store;
189 let closure = assert_async_fn_closure($f);
190
191 if store.can_block() {
192 // If the store can block then that means it's on a fiber. We can
193 // forward to `block_on` and everything should be fine and dandy.
194 #[cfg(feature = "async")]
195 {
196 store.with_blocking(|store, cx| cx.block_on(closure(store, Asyncness::Yes)))
197 }
198 #[cfg(not(feature = "async"))]
199 {
200 unreachable!()
201 }
202 } else {
203 // If the store cannot block it's not on a fiber. That means that we get
204 // at most one poll of `closure(store)` here. In the typical case
205 // what this means is that nothing async is configured in the store
206 // and one poll should be all we need. There are niche cases where
207 // one poll is not sufficient though, for example:
208 //
209 // * Store is created.
210 // * Wasm is called.
211 // * Wasm calls host.
212 // * Host configures an async resource limiter, returns back to
213 // wasm.
214 // * Wasm grows memory.
215 // * Limiter wants to block asynchronously.
216 //
217 // Technically there's nothing wrong with this, but it means that
218 // we're in wasm and one poll is not enough here. Given the niche
219 // nature of this scenario and how it's not really expected to work
220 // this translates failures in `closure` to a trap. This trap is
221 // only expected to show up in niche-ish scenarios, not for actual
222 // blocking work, as that would otherwise be too surprising.
223 vm::one_poll(closure(store, Asyncness::No)).ok_or_else(|| {
224 crate::format_err!(
225 "
226
227 A synchronously called wasm function invoked an async-defined libcall which
228 failed to complete synchronously and is thus raising a trap. It's expected
229 that this indicates that the store was configured to do async things after the
230 original synchronous entrypoint to wasm was called. That's generally not
231 supported in Wasmtime and async entrypoint should be used instead. If you're
232 seeing this message in error please file an issue on Wasmtime.
233
234 "
235 )
236 })
237 }
238 }};
239 }
240
assert_async_fn_closure<F, R>(f: F) -> F where F: AsyncFnOnce(&mut StoreOpaque, Asyncness) -> R,241 fn assert_async_fn_closure<F, R>(f: F) -> F
242 where
243 F: AsyncFnOnce(&mut StoreOpaque, Asyncness) -> R,
244 {
245 f
246 }
247
memory_grow( store: &mut dyn VMStore, instance: InstanceId, delta: u64, memory_index: u32, ) -> Result<Option<AllocationSize>>248 fn memory_grow(
249 store: &mut dyn VMStore,
250 instance: InstanceId,
251 delta: u64,
252 memory_index: u32,
253 ) -> Result<Option<AllocationSize>> {
254 let memory_index = DefinedMemoryIndex::from_u32(memory_index);
255 let (mut limiter, store) = store.resource_limiter_and_store_opaque();
256 let limiter = limiter.as_mut();
257 block_on!(store, async |store, _| {
258 let instance = store.instance_mut(instance);
259 let module = instance.env_module();
260 let page_size_log2 = module.memories[module.memory_index(memory_index)].page_size_log2;
261
262 let result = instance
263 .memory_grow(limiter, memory_index, delta)
264 .await?
265 .map(|size_in_bytes| AllocationSize(size_in_bytes >> page_size_log2));
266
267 Ok(result)
268 })?
269 }
270
271 /// A helper structure to represent the return value of a memory or table growth
272 /// call.
273 ///
274 /// This represents a byte or element-based count of the size of an item on the
275 /// host. For example a memory is how many bytes large the memory is, or a table
276 /// is how many elements large it is. It's assumed that the value here is never
277 /// -1 or -2 as that would mean the entire host address space is allocated which
278 /// is not possible.
279 struct AllocationSize(usize);
280
281 /// Special implementation for growth-related libcalls.
282 ///
283 /// Here the optional return value means:
284 ///
285 /// * `Some(val)` - the growth succeeded and the previous size of the item was
286 /// `val`.
287 /// * `None` - the growth failed.
288 ///
289 /// The failure case returns -1 (or `usize::MAX` as an unsigned integer) and the
290 /// successful case returns the `val` itself. Note that -2 (`usize::MAX - 1`
291 /// when unsigned) is unwind as a sentinel to indicate an unwind as no valid
292 /// allocation can be that large.
293 unsafe impl HostResultHasUnwindSentinel for Option<AllocationSize> {
294 type Abi = *mut u8;
295 const SENTINEL: *mut u8 = (usize::MAX - 1) as *mut u8;
296
into_abi(self) -> *mut u8297 fn into_abi(self) -> *mut u8 {
298 match self {
299 Some(size) => {
300 debug_assert!(size.0 < (usize::MAX - 1));
301 size.0 as *mut u8
302 }
303 None => usize::MAX as *mut u8,
304 }
305 }
306 }
307
308 /// Implementation of `table.grow` for `funcref` tables.
table_grow_func_ref( store: &mut dyn VMStore, instance: InstanceId, defined_table_index: u32, delta: u64, init_value: *mut u8, ) -> Result<Option<AllocationSize>>309 unsafe fn table_grow_func_ref(
310 store: &mut dyn VMStore,
311 instance: InstanceId,
312 defined_table_index: u32,
313 delta: u64,
314 init_value: *mut u8,
315 ) -> Result<Option<AllocationSize>> {
316 let defined_table_index = DefinedTableIndex::from_u32(defined_table_index);
317 let element = NonNull::new(init_value.cast::<VMFuncRef>()).map(SendSyncPtr::new);
318 let (mut limiter, store) = store.resource_limiter_and_store_opaque();
319 let limiter = limiter.as_mut();
320 block_on!(store, async |store, _| {
321 let mut instance = store.instance_mut(instance);
322 let table_index = instance.env_module().table_index(defined_table_index);
323 debug_assert!(matches!(
324 instance.as_mut().table_element_type(table_index),
325 TableElementType::Func,
326 ));
327 let result = instance
328 .defined_table_grow(defined_table_index, async |table| unsafe {
329 table.grow_func(limiter, delta, element).await
330 })
331 .await?
332 .map(AllocationSize);
333 Ok(result)
334 })?
335 }
336
337 /// Implementation of `table.grow` for GC-reference tables.
338 #[cfg(feature = "gc")]
table_grow_gc_ref( store: &mut dyn VMStore, instance: InstanceId, defined_table_index: u32, delta: u64, init_value: u32, ) -> Result<Option<AllocationSize>>339 fn table_grow_gc_ref(
340 store: &mut dyn VMStore,
341 instance: InstanceId,
342 defined_table_index: u32,
343 delta: u64,
344 init_value: u32,
345 ) -> Result<Option<AllocationSize>> {
346 let defined_table_index = DefinedTableIndex::from_u32(defined_table_index);
347 let element = VMGcRef::from_raw_u32(init_value);
348 let (mut limiter, store) = store.resource_limiter_and_store_opaque();
349 let limiter = limiter.as_mut();
350 block_on!(store, async |store, _| {
351 let (gc_store, mut instance) = store.optional_gc_store_and_instance_mut(instance);
352 let table_index = instance.env_module().table_index(defined_table_index);
353 debug_assert!(matches!(
354 instance.as_mut().table_element_type(table_index),
355 TableElementType::GcRef,
356 ));
357
358 let result = instance
359 .defined_table_grow(defined_table_index, async |table| unsafe {
360 table
361 .grow_gc_ref(limiter, gc_store, delta, element.as_ref())
362 .await
363 })
364 .await?
365 .map(AllocationSize);
366 Ok(result)
367 })?
368 }
369
370 #[cfg(feature = "stack-switching")]
table_grow_cont_obj( store: &mut dyn VMStore, instance: InstanceId, defined_table_index: u32, delta: u64, init_value_contref: *mut u8, init_value_revision: usize, ) -> Result<Option<AllocationSize>>371 unsafe fn table_grow_cont_obj(
372 store: &mut dyn VMStore,
373 instance: InstanceId,
374 defined_table_index: u32,
375 delta: u64,
376 // The following two values together form the initial Option<VMContObj>.
377 // A None value is indicated by the pointer being null.
378 init_value_contref: *mut u8,
379 init_value_revision: usize,
380 ) -> Result<Option<AllocationSize>> {
381 let defined_table_index = DefinedTableIndex::from_u32(defined_table_index);
382 let element = unsafe { VMContObj::from_raw_parts(init_value_contref, init_value_revision) };
383 let (mut limiter, store) = store.resource_limiter_and_store_opaque();
384 let limiter = limiter.as_mut();
385 block_on!(store, async |store, _| {
386 let mut instance = store.instance_mut(instance);
387 let table_index = instance.env_module().table_index(defined_table_index);
388 debug_assert!(matches!(
389 instance.as_mut().table_element_type(table_index),
390 TableElementType::Cont,
391 ));
392 let result = instance
393 .defined_table_grow(defined_table_index, async |table| unsafe {
394 table.grow_cont(limiter, delta, element).await
395 })
396 .await?
397 .map(AllocationSize);
398 Ok(result)
399 })?
400 }
401
402 /// Implementation of `table.fill` for `funcref`s.
table_fill_func_ref( store: &mut dyn VMStore, instance: InstanceId, table_index: u32, dst: u64, val: *mut u8, len: u64, ) -> Result<()>403 unsafe fn table_fill_func_ref(
404 store: &mut dyn VMStore,
405 instance: InstanceId,
406 table_index: u32,
407 dst: u64,
408 val: *mut u8,
409 len: u64,
410 ) -> Result<()> {
411 let instance = store.instance_mut(instance);
412 let table_index = DefinedTableIndex::from_u32(table_index);
413 let table = instance.get_defined_table(table_index);
414 match table.element_type() {
415 TableElementType::Func => {
416 let val = NonNull::new(val.cast::<VMFuncRef>());
417 table.fill_func(dst, val, len)?;
418 Ok(())
419 }
420 TableElementType::GcRef => unreachable!(),
421 TableElementType::Cont => unreachable!(),
422 }
423 }
424
425 #[cfg(feature = "gc")]
table_fill_gc_ref( store: &mut dyn VMStore, instance: InstanceId, table_index: u32, dst: u64, val: u32, len: u64, ) -> Result<()>426 fn table_fill_gc_ref(
427 store: &mut dyn VMStore,
428 instance: InstanceId,
429 table_index: u32,
430 dst: u64,
431 val: u32,
432 len: u64,
433 ) -> Result<()> {
434 let (gc_store, instance) = store.optional_gc_store_and_instance_mut(instance);
435 let table_index = DefinedTableIndex::from_u32(table_index);
436 let table = instance.get_defined_table(table_index);
437 match table.element_type() {
438 TableElementType::Func => unreachable!(),
439 TableElementType::GcRef => {
440 let gc_ref = VMGcRef::from_raw_u32(val);
441 table.fill_gc_ref(gc_store, dst, gc_ref.as_ref(), len)?;
442 Ok(())
443 }
444
445 TableElementType::Cont => unreachable!(),
446 }
447 }
448
449 #[cfg(feature = "stack-switching")]
table_fill_cont_obj( store: &mut dyn VMStore, instance: InstanceId, table_index: u32, dst: u64, value_contref: *mut u8, value_revision: usize, len: u64, ) -> Result<()>450 unsafe fn table_fill_cont_obj(
451 store: &mut dyn VMStore,
452 instance: InstanceId,
453 table_index: u32,
454 dst: u64,
455 value_contref: *mut u8,
456 value_revision: usize,
457 len: u64,
458 ) -> Result<()> {
459 let instance = store.instance_mut(instance);
460 let table_index = DefinedTableIndex::from_u32(table_index);
461 let table = instance.get_defined_table(table_index);
462 match table.element_type() {
463 TableElementType::Cont => {
464 let contobj = unsafe { VMContObj::from_raw_parts(value_contref, value_revision) };
465 table.fill_cont(dst, contobj, len)?;
466 Ok(())
467 }
468 _ => panic!("Wrong table filling function"),
469 }
470 }
471
472 // Implementation of `table.copy`.
table_copy( store: &mut dyn VMStore, instance: InstanceId, dst_table_index: u32, src_table_index: u32, dst: u64, src: u64, len: u64, ) -> Result<(), Trap>473 fn table_copy(
474 store: &mut dyn VMStore,
475 instance: InstanceId,
476 dst_table_index: u32,
477 src_table_index: u32,
478 dst: u64,
479 src: u64,
480 len: u64,
481 ) -> Result<(), Trap> {
482 let dst_table_index = TableIndex::from_u32(dst_table_index);
483 let src_table_index = TableIndex::from_u32(src_table_index);
484 let store = store.store_opaque_mut();
485 let mut instance = store.instance_mut(instance);
486
487 // Convert the two table indices relative to `instance` into two
488 // defining instances and the defined table index within that instance.
489 let (dst_def_index, dst_instance) = instance
490 .as_mut()
491 .defined_table_index_and_instance(dst_table_index);
492 let dst_instance_id = dst_instance.id();
493 let (src_def_index, src_instance) = instance
494 .as_mut()
495 .defined_table_index_and_instance(src_table_index);
496 let src_instance_id = src_instance.id();
497
498 let src_table = crate::Table::from_raw(
499 StoreInstanceId::new(store.id(), src_instance_id),
500 src_def_index,
501 );
502 let dst_table = crate::Table::from_raw(
503 StoreInstanceId::new(store.id(), dst_instance_id),
504 dst_def_index,
505 );
506
507 // SAFETY: this is only safe if the two tables have the same type, and that
508 // was validated during wasm-validation time.
509 unsafe { crate::Table::copy_raw(store, &dst_table, dst, &src_table, src, len) }
510 }
511
512 // Implementation of `table.init`.
table_init( store: &mut dyn VMStore, instance: InstanceId, table_index: u32, elem_index: u32, dst: u64, src: u64, len: u64, ) -> Result<()>513 fn table_init(
514 store: &mut dyn VMStore,
515 instance: InstanceId,
516 table_index: u32,
517 elem_index: u32,
518 dst: u64,
519 src: u64,
520 len: u64,
521 ) -> Result<()> {
522 let table_index = TableIndex::from_u32(table_index);
523 let elem_index = ElemIndex::from_u32(elem_index);
524
525 let (mut limiter, store) = store.resource_limiter_and_store_opaque();
526 block_on!(store, async |store, asyncness| {
527 vm::Instance::table_init(
528 store,
529 limiter.as_mut(),
530 asyncness,
531 instance,
532 table_index,
533 elem_index,
534 dst,
535 src,
536 len,
537 )
538 .await
539 })??;
540 Ok(())
541 }
542
543 // Implementation of `elem.drop`.
elem_drop(store: &mut dyn VMStore, instance: InstanceId, elem_index: u32) -> Result<()>544 fn elem_drop(store: &mut dyn VMStore, instance: InstanceId, elem_index: u32) -> Result<()> {
545 let elem_index = ElemIndex::from_u32(elem_index);
546 store.instance_mut(instance).elem_drop(elem_index)?;
547 Ok(())
548 }
549
550 // Implementation of `memory.copy`.
memory_copy( store: &mut dyn VMStore, instance: InstanceId, dst_index: u32, dst: u64, src_index: u32, src: u64, len: u64, ) -> Result<(), Trap>551 fn memory_copy(
552 store: &mut dyn VMStore,
553 instance: InstanceId,
554 dst_index: u32,
555 dst: u64,
556 src_index: u32,
557 src: u64,
558 len: u64,
559 ) -> Result<(), Trap> {
560 let src_index = MemoryIndex::from_u32(src_index);
561 let dst_index = MemoryIndex::from_u32(dst_index);
562 store
563 .instance_mut(instance)
564 .memory_copy(dst_index, dst, src_index, src, len)
565 }
566
567 // Implementation of `memory.fill` for locally defined memories.
memory_fill( store: &mut dyn VMStore, instance: InstanceId, memory_index: u32, dst: u64, val: u32, len: u64, ) -> Result<(), Trap>568 fn memory_fill(
569 store: &mut dyn VMStore,
570 instance: InstanceId,
571 memory_index: u32,
572 dst: u64,
573 val: u32,
574 len: u64,
575 ) -> Result<(), Trap> {
576 let memory_index = DefinedMemoryIndex::from_u32(memory_index);
577 #[expect(clippy::cast_possible_truncation, reason = "known to truncate here")]
578 store
579 .instance_mut(instance)
580 .memory_fill(memory_index, dst, val as u8, len)
581 }
582
583 // Implementation of `memory.init`.
memory_init( store: &mut dyn VMStore, instance: InstanceId, memory_index: u32, data_index: u32, dst: u64, src: u32, len: u32, ) -> Result<(), Trap>584 fn memory_init(
585 store: &mut dyn VMStore,
586 instance: InstanceId,
587 memory_index: u32,
588 data_index: u32,
589 dst: u64,
590 src: u32,
591 len: u32,
592 ) -> Result<(), Trap> {
593 let memory_index = MemoryIndex::from_u32(memory_index);
594 let data_index = DataIndex::from_u32(data_index);
595 store
596 .instance_mut(instance)
597 .memory_init(memory_index, data_index, dst, src, len)
598 }
599
600 // Implementation of `ref.func`.
ref_func(store: &mut dyn VMStore, instance: InstanceId, func_index: u32) -> NonNull<u8>601 fn ref_func(store: &mut dyn VMStore, instance: InstanceId, func_index: u32) -> NonNull<u8> {
602 let (instance, registry) = store.instance_and_module_registry_mut(instance);
603 instance
604 .get_func_ref(registry, FuncIndex::from_u32(func_index))
605 .expect("ref_func: funcref should always be available for given func index")
606 .cast()
607 }
608
609 // Implementation of `data.drop`.
data_drop(store: &mut dyn VMStore, instance: InstanceId, data_index: u32) -> Result<()>610 fn data_drop(store: &mut dyn VMStore, instance: InstanceId, data_index: u32) -> Result<()> {
611 let data_index = DataIndex::from_u32(data_index);
612 store.instance_mut(instance).data_drop(data_index)?;
613 Ok(())
614 }
615
616 // Returns a table entry after lazily initializing it.
table_get_lazy_init_func_ref( store: &mut dyn VMStore, instance: InstanceId, table_index: u32, index: u64, ) -> *mut u8617 fn table_get_lazy_init_func_ref(
618 store: &mut dyn VMStore,
619 instance: InstanceId,
620 table_index: u32,
621 index: u64,
622 ) -> *mut u8 {
623 let table_index = TableIndex::from_u32(table_index);
624 let (instance, registry) = store.instance_and_module_registry_mut(instance);
625 let table = instance.get_table_with_lazy_init(registry, table_index, core::iter::once(index));
626 let elem = table
627 .get_func(index)
628 .expect("table access already bounds-checked");
629
630 match elem {
631 Some(ptr) => ptr.as_ptr().cast(),
632 None => core::ptr::null_mut(),
633 }
634 }
635
636 /// Drop a GC reference.
637 #[cfg(feature = "gc-drc")]
drop_gc_ref(store: &mut dyn VMStore, _instance: InstanceId, gc_ref: u32)638 fn drop_gc_ref(store: &mut dyn VMStore, _instance: InstanceId, gc_ref: u32) {
639 log::trace!("libcalls::drop_gc_ref({gc_ref:#x})");
640 let gc_ref = VMGcRef::from_raw_u32(gc_ref).expect("non-null VMGcRef");
641 store
642 .store_opaque_mut()
643 .unwrap_gc_store_mut()
644 .drop_gc_ref(gc_ref);
645 }
646
647 /// Grow the GC heap.
648 #[cfg(feature = "gc-null")]
grow_gc_heap(store: &mut dyn VMStore, _instance: InstanceId, bytes_needed: u64) -> Result<()>649 fn grow_gc_heap(store: &mut dyn VMStore, _instance: InstanceId, bytes_needed: u64) -> Result<()> {
650 let orig_len = u64::try_from(
651 store
652 .require_gc_store()?
653 .gc_heap
654 .vmmemory()
655 .current_length(),
656 )
657 .unwrap();
658
659 let (mut limiter, store) = store.resource_limiter_and_store_opaque();
660 block_on!(store, async |store, asyncness| {
661 store
662 .gc(limiter.as_mut(), None, Some(bytes_needed), asyncness)
663 .await;
664 })?;
665
666 // JIT code relies on the memory having grown by `bytes_needed` bytes if
667 // this libcall returns successfully, so trap if we didn't grow that much.
668 let new_len = u64::try_from(
669 store
670 .require_gc_store()?
671 .gc_heap
672 .vmmemory()
673 .current_length(),
674 )
675 .unwrap();
676 if orig_len
677 .checked_add(bytes_needed)
678 .is_none_or(|expected_len| new_len < expected_len)
679 {
680 return Err(crate::Trap::AllocationTooLarge.into());
681 }
682
683 Ok(())
684 }
685
686 /// Allocate a raw, unininitialized GC object for Wasm code.
687 ///
688 /// The Wasm code is responsible for initializing the object.
689 #[cfg(feature = "gc-drc")]
gc_alloc_raw( store: &mut dyn VMStore, instance: InstanceId, kind_and_reserved: u32, module_interned_type_index: u32, size: u32, align: u32, ) -> Result<core::num::NonZeroU32>690 fn gc_alloc_raw(
691 store: &mut dyn VMStore,
692 instance: InstanceId,
693 kind_and_reserved: u32,
694 module_interned_type_index: u32,
695 size: u32,
696 align: u32,
697 ) -> Result<core::num::NonZeroU32> {
698 use crate::vm::VMGcHeader;
699 use core::alloc::Layout;
700 use wasmtime_environ::{ModuleInternedTypeIndex, VMGcKind};
701
702 let kind = VMGcKind::from_high_bits_of_u32(kind_and_reserved);
703 log::trace!("gc_alloc_raw(kind={kind:?}, size={size}, align={align})");
704
705 let module = store
706 .instance(instance)
707 .runtime_module()
708 .expect("should never allocate GC types defined in a dummy module");
709
710 let module_interned_type_index = ModuleInternedTypeIndex::from_u32(module_interned_type_index);
711 let shared_type_index = module
712 .signatures()
713 .shared_type(module_interned_type_index)
714 .expect("should have engine type index for module type index");
715
716 let mut header = VMGcHeader::from_kind_and_index(kind, shared_type_index);
717 header.set_reserved_u26(kind_and_reserved & VMGcKind::UNUSED_MASK);
718
719 let size = usize::try_from(size).unwrap();
720 let align = usize::try_from(align).unwrap();
721 assert!(align.is_power_of_two());
722 let layout = Layout::from_size_align(size, align).map_err(|e| {
723 let err = Error::from(crate::Trap::AllocationTooLarge);
724 err.context(e)
725 })?;
726
727 let (mut limiter, store) = store.resource_limiter_and_store_opaque();
728 block_on!(store, async |store, asyncness| {
729 let gc_ref = store
730 .retry_after_gc_async(limiter.as_mut(), (), asyncness, |store, ()| {
731 store
732 .unwrap_gc_store_mut()
733 .alloc_raw(header, layout)?
734 .map_err(|bytes_needed| crate::GcHeapOutOfMemory::new((), bytes_needed).into())
735 })
736 .await?;
737
738 let raw = store.unwrap_gc_store_mut().expose_gc_ref_to_wasm(gc_ref);
739 Ok(raw)
740 })?
741 }
742
743 // Intern a `funcref` into the GC heap, returning its `FuncRefTableId`.
744 //
745 // This libcall may not GC.
746 #[cfg(feature = "gc")]
intern_func_ref_for_gc_heap( store: &mut dyn VMStore, _instance: InstanceId, func_ref: *mut u8, ) -> Result<u32>747 unsafe fn intern_func_ref_for_gc_heap(
748 store: &mut dyn VMStore,
749 _instance: InstanceId,
750 func_ref: *mut u8,
751 ) -> Result<u32> {
752 use crate::{store::AutoAssertNoGc, vm::SendSyncPtr};
753 use core::ptr::NonNull;
754
755 let mut store = AutoAssertNoGc::new(store.store_opaque_mut());
756
757 let func_ref = func_ref.cast::<VMFuncRef>();
758 let func_ref = NonNull::new(func_ref).map(SendSyncPtr::new);
759
760 let func_ref_id = unsafe {
761 store
762 .require_gc_store_mut()?
763 .func_ref_table
764 .intern(func_ref)
765 };
766 Ok(func_ref_id.into_raw())
767 }
768
769 // Get the raw `VMFuncRef` pointer associated with a `FuncRefTableId` from an
770 // earlier `intern_func_ref_for_gc_heap` call.
771 //
772 // This libcall may not GC.
773 #[cfg(feature = "gc")]
get_interned_func_ref( store: &mut dyn VMStore, instance: InstanceId, func_ref_id: u32, module_interned_type_index: u32, ) -> *mut u8774 fn get_interned_func_ref(
775 store: &mut dyn VMStore,
776 instance: InstanceId,
777 func_ref_id: u32,
778 module_interned_type_index: u32,
779 ) -> *mut u8 {
780 use super::FuncRefTableId;
781 use crate::store::AutoAssertNoGc;
782 use wasmtime_environ::{ModuleInternedTypeIndex, packed_option::ReservedValue};
783
784 let store = AutoAssertNoGc::new(store.store_opaque_mut());
785
786 let func_ref_id = FuncRefTableId::from_raw(func_ref_id);
787 let module_interned_type_index = ModuleInternedTypeIndex::from_bits(module_interned_type_index);
788
789 let func_ref = if module_interned_type_index.is_reserved_value() {
790 store
791 .unwrap_gc_store()
792 .func_ref_table
793 .get_untyped(func_ref_id)
794 } else {
795 let types = store.engine().signatures();
796 let engine_ty = store
797 .instance(instance)
798 .engine_type_index(module_interned_type_index);
799 store
800 .unwrap_gc_store()
801 .func_ref_table
802 .get_typed(types, func_ref_id, engine_ty)
803 };
804
805 func_ref.map_or(core::ptr::null_mut(), |f| f.as_ptr().cast())
806 }
807
808 /// Implementation of the `array.new_data` instruction.
809 #[cfg(feature = "gc")]
array_new_data( store: &mut dyn VMStore, instance_id: InstanceId, array_type_index: u32, data_index: u32, src: u32, len: u32, ) -> Result<core::num::NonZeroU32>810 fn array_new_data(
811 store: &mut dyn VMStore,
812 instance_id: InstanceId,
813 array_type_index: u32,
814 data_index: u32,
815 src: u32,
816 len: u32,
817 ) -> Result<core::num::NonZeroU32> {
818 use crate::ArrayType;
819 use wasmtime_environ::ModuleInternedTypeIndex;
820
821 let (mut limiter, store) = store.resource_limiter_and_store_opaque();
822 block_on!(store, async |store, asyncness| {
823 let array_type_index = ModuleInternedTypeIndex::from_u32(array_type_index);
824 let data_index = DataIndex::from_u32(data_index);
825 let instance = store.instance(instance_id);
826
827 // Calculate the byte-length of the data (as opposed to the element-length
828 // of the array).
829 let data_range = instance.wasm_data_range(data_index);
830 let shared_ty = instance.engine_type_index(array_type_index);
831 let array_ty = ArrayType::from_shared_type_index(store.engine(), shared_ty);
832 let one_elem_size = array_ty
833 .element_type()
834 .data_byte_size()
835 .expect("Wasm validation ensures that this type have a defined byte size");
836 let byte_len = len
837 .checked_mul(one_elem_size)
838 .and_then(|x| usize::try_from(x).ok())
839 .ok_or_else(|| Trap::MemoryOutOfBounds)?;
840
841 // Get the data from the segment, checking bounds.
842 let src = usize::try_from(src).map_err(|_| Trap::MemoryOutOfBounds)?;
843 instance
844 .wasm_data(data_range.clone())
845 .get(src..)
846 .and_then(|d| d.get(..byte_len))
847 .ok_or_else(|| Trap::MemoryOutOfBounds)?;
848
849 // Allocate the (uninitialized) array.
850 let gc_layout = store
851 .engine()
852 .signatures()
853 .layout(shared_ty)
854 .expect("array types have GC layouts");
855 let array_layout = gc_layout.unwrap_array();
856 let array_ref = store
857 .retry_after_gc_async(limiter.as_mut(), (), asyncness, |store, ()| {
858 store
859 .unwrap_gc_store_mut()
860 .alloc_uninit_array(shared_ty, len, &array_layout)?
861 .map_err(|bytes_needed| crate::GcHeapOutOfMemory::new((), bytes_needed).into())
862 })
863 .await?;
864
865 let (gc_store, instance) = store.optional_gc_store_and_instance_mut(instance_id);
866 let gc_store = gc_store.unwrap();
867 let data = &instance.wasm_data(data_range)[src..][..byte_len];
868
869 // Copy the data into the array, initializing it.
870 gc_store
871 .gc_object_data(array_ref.as_gc_ref())
872 .copy_from_slice(array_layout.base_size, data);
873
874 // Return the array to Wasm!
875 let raw = gc_store.expose_gc_ref_to_wasm(array_ref.into());
876 Ok(raw)
877 })?
878 }
879
880 /// Implementation of the `array.init_data` instruction.
881 #[cfg(feature = "gc")]
array_init_data( store: &mut dyn VMStore, instance_id: InstanceId, array_type_index: u32, array: u32, dst: u32, data_index: u32, src: u32, len: u32, ) -> Result<()>882 fn array_init_data(
883 store: &mut dyn VMStore,
884 instance_id: InstanceId,
885 array_type_index: u32,
886 array: u32,
887 dst: u32,
888 data_index: u32,
889 src: u32,
890 len: u32,
891 ) -> Result<()> {
892 use crate::ArrayType;
893 use wasmtime_environ::ModuleInternedTypeIndex;
894
895 let array_type_index = ModuleInternedTypeIndex::from_u32(array_type_index);
896 let data_index = DataIndex::from_u32(data_index);
897 let instance = store.instance(instance_id);
898
899 log::trace!(
900 "array.init_data(array={array:#x}, dst={dst}, data_index={data_index:?}, src={src}, len={len})",
901 );
902
903 // Null check the array.
904 let gc_ref = VMGcRef::from_raw_u32(array).ok_or_else(|| Trap::NullReference)?;
905 let array = gc_ref
906 .into_arrayref(&*store.unwrap_gc_store().gc_heap)
907 .expect("gc ref should be an array");
908
909 let dst = usize::try_from(dst).map_err(|_| Trap::MemoryOutOfBounds)?;
910 let src = usize::try_from(src).map_err(|_| Trap::MemoryOutOfBounds)?;
911 let len = usize::try_from(len).map_err(|_| Trap::MemoryOutOfBounds)?;
912
913 // Bounds check the array.
914 let array_len = array.len(store.store_opaque());
915 let array_len = usize::try_from(array_len).map_err(|_| Trap::ArrayOutOfBounds)?;
916 if dst.checked_add(len).ok_or_else(|| Trap::ArrayOutOfBounds)? > array_len {
917 return Err(Trap::ArrayOutOfBounds.into());
918 }
919
920 // Calculate the byte length from the array length.
921 let shared_ty = instance.engine_type_index(array_type_index);
922 let array_ty = ArrayType::from_shared_type_index(store.engine(), shared_ty);
923 let one_elem_size = array_ty
924 .element_type()
925 .data_byte_size()
926 .expect("Wasm validation ensures that this type have a defined byte size");
927 let data_len = len
928 .checked_mul(usize::try_from(one_elem_size).unwrap())
929 .ok_or_else(|| Trap::MemoryOutOfBounds)?;
930
931 // Get the data from the segment, checking its bounds.
932 let data_range = instance.wasm_data_range(data_index);
933 instance
934 .wasm_data(data_range.clone())
935 .get(src..)
936 .and_then(|d| d.get(..data_len))
937 .ok_or_else(|| Trap::MemoryOutOfBounds)?;
938
939 // Copy the data into the array.
940
941 let dst_offset = u32::try_from(dst)
942 .unwrap()
943 .checked_mul(one_elem_size)
944 .unwrap();
945
946 let array_layout = store
947 .engine()
948 .signatures()
949 .layout(shared_ty)
950 .expect("array types have GC layouts");
951 let array_layout = array_layout.unwrap_array();
952
953 let obj_offset = array_layout.base_size.checked_add(dst_offset).unwrap();
954
955 let (gc_store, instance) = store.optional_gc_store_and_instance_mut(instance_id);
956 let gc_store = gc_store.unwrap();
957 let data = &instance.wasm_data(data_range)[src..][..data_len];
958 gc_store
959 .gc_object_data(array.as_gc_ref())
960 .copy_from_slice(obj_offset, data);
961
962 Ok(())
963 }
964
965 #[cfg(feature = "gc")]
array_new_elem( store: &mut dyn VMStore, instance_id: InstanceId, array_type_index: u32, elem_index: u32, src: u32, len: u32, ) -> Result<core::num::NonZeroU32>966 fn array_new_elem(
967 store: &mut dyn VMStore,
968 instance_id: InstanceId,
969 array_type_index: u32,
970 elem_index: u32,
971 src: u32,
972 len: u32,
973 ) -> Result<core::num::NonZeroU32> {
974 use crate::{
975 ArrayRef, ArrayRefPre, ArrayType, Func, OpaqueRootScope, RootedGcRefImpl, Val,
976 store::AutoAssertNoGc,
977 vm::const_expr::{ConstEvalContext, ConstExprEvaluator},
978 };
979 use wasmtime_environ::{ModuleInternedTypeIndex, TableSegmentElements};
980
981 // Convert indices to their typed forms.
982 let array_type_index = ModuleInternedTypeIndex::from_u32(array_type_index);
983 let elem_index = ElemIndex::from_u32(elem_index);
984 let instance = store.instance(instance_id);
985
986 let mut storage = None;
987 let elements = instance.passive_element_segment(&mut storage, elem_index);
988
989 let src = usize::try_from(src).map_err(|_| Trap::TableOutOfBounds)?;
990 let len = usize::try_from(len).map_err(|_| Trap::TableOutOfBounds)?;
991
992 let shared_ty = instance.engine_type_index(array_type_index);
993 let array_ty = ArrayType::from_shared_type_index(store.engine(), shared_ty);
994 let pre = ArrayRefPre::_new(store, array_ty);
995
996 let (mut limiter, store) = store.resource_limiter_and_store_opaque();
997 block_on!(store, async |store, asyncness| {
998 let mut store = OpaqueRootScope::new(store);
999 // Turn the elements into `Val`s.
1000 let mut vals = Vec::with_capacity(usize::try_from(elements.len()).unwrap());
1001 match elements {
1002 TableSegmentElements::Functions(fs) => {
1003 let store_id = store.id();
1004 let (mut instance, registry) = store.instance_and_module_registry_mut(instance_id);
1005 vals.extend(
1006 fs.get(src..)
1007 .and_then(|s| s.get(..len))
1008 .ok_or_else(|| Trap::TableOutOfBounds)?
1009 .iter()
1010 .map(|f| {
1011 let raw_func_ref = instance.as_mut().get_func_ref(registry, *f);
1012 let func = unsafe {
1013 raw_func_ref.map(|p| Func::from_vm_func_ref(store_id, p))
1014 };
1015 Val::FuncRef(func)
1016 }),
1017 );
1018 }
1019 TableSegmentElements::Expressions(xs) => {
1020 let xs = xs
1021 .get(src..)
1022 .and_then(|s| s.get(..len))
1023 .ok_or_else(|| Trap::TableOutOfBounds)?;
1024
1025 let mut const_context = ConstEvalContext::new(instance_id, asyncness);
1026 let mut const_evaluator = ConstExprEvaluator::default();
1027
1028 for x in xs.iter() {
1029 let val = *const_evaluator
1030 .eval(&mut store, limiter.as_mut(), &mut const_context, x)
1031 .await?;
1032 vals.push(val);
1033 }
1034 }
1035 }
1036
1037 let array =
1038 ArrayRef::_new_fixed_async(&mut store, limiter.as_mut(), &pre, &vals, asyncness)
1039 .await?;
1040
1041 let mut store = AutoAssertNoGc::new(&mut store);
1042 let gc_ref = array.try_clone_gc_ref(&mut store)?;
1043 let raw = store.unwrap_gc_store_mut().expose_gc_ref_to_wasm(gc_ref);
1044 Ok(raw)
1045 })?
1046 }
1047
1048 #[cfg(feature = "gc")]
array_init_elem( store: &mut dyn VMStore, instance: InstanceId, array_type_index: u32, array: u32, dst: u32, elem_index: u32, src: u32, len: u32, ) -> Result<()>1049 fn array_init_elem(
1050 store: &mut dyn VMStore,
1051 instance: InstanceId,
1052 array_type_index: u32,
1053 array: u32,
1054 dst: u32,
1055 elem_index: u32,
1056 src: u32,
1057 len: u32,
1058 ) -> Result<()> {
1059 use crate::{
1060 ArrayRef, Func, OpaqueRootScope, Val,
1061 store::AutoAssertNoGc,
1062 vm::const_expr::{ConstEvalContext, ConstExprEvaluator},
1063 };
1064 use wasmtime_environ::{ModuleInternedTypeIndex, TableSegmentElements};
1065
1066 let (mut limiter, store) = store.resource_limiter_and_store_opaque();
1067 block_on!(store, async |store, asyncness| {
1068 let mut store = OpaqueRootScope::new(store);
1069
1070 // Convert the indices into their typed forms.
1071 let _array_type_index = ModuleInternedTypeIndex::from_u32(array_type_index);
1072 let elem_index = ElemIndex::from_u32(elem_index);
1073
1074 log::trace!(
1075 "array.init_elem(array={array:#x}, dst={dst}, elem_index={elem_index:?}, src={src}, len={len})",
1076 );
1077
1078 // Convert the raw GC ref into a `Rooted<ArrayRef>`.
1079 let array = VMGcRef::from_raw_u32(array).ok_or_else(|| Trap::NullReference)?;
1080 let array = store.unwrap_gc_store_mut().clone_gc_ref(&array);
1081 let array = {
1082 let mut no_gc = AutoAssertNoGc::new(&mut store);
1083 ArrayRef::from_cloned_gc_ref(&mut no_gc, array)
1084 };
1085
1086 // Bounds check the destination within the array.
1087 let array_len = array._len(&store)?;
1088 log::trace!("array_len = {array_len}");
1089 if dst.checked_add(len).ok_or_else(|| Trap::ArrayOutOfBounds)? > array_len {
1090 return Err(Trap::ArrayOutOfBounds.into());
1091 }
1092
1093 // Get the passive element segment.
1094 let mut storage = None;
1095 let store_id = store.id();
1096 let (mut instance, registry) = store.instance_and_module_registry_mut(instance);
1097 let elements = instance.passive_element_segment(&mut storage, elem_index);
1098
1099 // Convert array offsets into `usize`s.
1100 let src = usize::try_from(src).map_err(|_| Trap::TableOutOfBounds)?;
1101 let len = usize::try_from(len).map_err(|_| Trap::TableOutOfBounds)?;
1102
1103 // Turn the elements into `Val`s.
1104 let vals = match elements {
1105 TableSegmentElements::Functions(fs) => fs
1106 .get(src..)
1107 .and_then(|s| s.get(..len))
1108 .ok_or_else(|| Trap::TableOutOfBounds)?
1109 .iter()
1110 .map(|f| {
1111 let raw_func_ref = instance.as_mut().get_func_ref(registry, *f);
1112 let func = unsafe { raw_func_ref.map(|p| Func::from_vm_func_ref(store_id, p)) };
1113 Val::FuncRef(func)
1114 })
1115 .collect::<Vec<_>>(),
1116 TableSegmentElements::Expressions(xs) => {
1117 let mut const_context = ConstEvalContext::new(instance.id(), asyncness);
1118 let mut const_evaluator = ConstExprEvaluator::default();
1119
1120 let mut vals = Vec::new();
1121 for x in xs
1122 .get(src..)
1123 .and_then(|s| s.get(..len))
1124 .ok_or_else(|| Trap::TableOutOfBounds)?
1125 {
1126 let val = *const_evaluator
1127 .eval(&mut store, limiter.as_mut(), &mut const_context, x)
1128 .await?;
1129 vals.push(val);
1130 }
1131 vals
1132 }
1133 };
1134
1135 // Copy the values into the array.
1136 for (i, val) in vals.into_iter().enumerate() {
1137 let i = u32::try_from(i).unwrap();
1138 let j = dst.checked_add(i).unwrap();
1139 array._set(&mut store, j, val)?;
1140 }
1141
1142 Ok(())
1143 })?
1144 }
1145
1146 // TODO: Specialize this libcall for only non-GC array elements, so we never
1147 // have to do GC barriers and their associated indirect calls through the `dyn
1148 // GcHeap`. Instead, implement those copies inline in Wasm code. Then, use bulk
1149 // `memcpy`-style APIs to do the actual copies here.
1150 #[cfg(feature = "gc")]
array_copy( store: &mut dyn VMStore, _instance: InstanceId, dst_array: u32, dst: u32, src_array: u32, src: u32, len: u32, ) -> Result<()>1151 fn array_copy(
1152 store: &mut dyn VMStore,
1153 _instance: InstanceId,
1154 dst_array: u32,
1155 dst: u32,
1156 src_array: u32,
1157 src: u32,
1158 len: u32,
1159 ) -> Result<()> {
1160 use crate::{ArrayRef, OpaqueRootScope, store::AutoAssertNoGc};
1161
1162 log::trace!(
1163 "array.copy(dst_array={dst_array:#x}, dst_index={dst}, src_array={src_array:#x}, src_index={src}, len={len})",
1164 );
1165
1166 let mut store = OpaqueRootScope::new(store.store_opaque_mut());
1167 let mut store = AutoAssertNoGc::new(&mut store);
1168
1169 // Convert the raw GC refs into `Rooted<ArrayRef>`s.
1170 let dst_array = VMGcRef::from_raw_u32(dst_array).ok_or_else(|| Trap::NullReference)?;
1171 let dst_array = store.unwrap_gc_store_mut().clone_gc_ref(&dst_array);
1172 let dst_array = ArrayRef::from_cloned_gc_ref(&mut store, dst_array);
1173 let src_array = VMGcRef::from_raw_u32(src_array).ok_or_else(|| Trap::NullReference)?;
1174 let src_array = store.unwrap_gc_store_mut().clone_gc_ref(&src_array);
1175 let src_array = ArrayRef::from_cloned_gc_ref(&mut store, src_array);
1176
1177 // Bounds check the destination array's elements.
1178 let dst_array_len = dst_array._len(&store)?;
1179 if dst.checked_add(len).ok_or_else(|| Trap::ArrayOutOfBounds)? > dst_array_len {
1180 return Err(Trap::ArrayOutOfBounds.into());
1181 }
1182
1183 // Bounds check the source array's elements.
1184 let src_array_len = src_array._len(&store)?;
1185 if src.checked_add(len).ok_or_else(|| Trap::ArrayOutOfBounds)? > src_array_len {
1186 return Err(Trap::ArrayOutOfBounds.into());
1187 }
1188
1189 let mut store = AutoAssertNoGc::new(&mut store);
1190 // If `src_array` and `dst_array` are the same array, then we are
1191 // potentially doing an overlapping copy, so make sure to copy elements in
1192 // the order that doesn't clobber the source elements before they are
1193 // copied. If they are different arrays, the order doesn't matter, but we
1194 // simply don't bother checking.
1195 if src > dst {
1196 for i in 0..len {
1197 let src_elem = src_array._get(&mut store, src + i)?;
1198 let dst_i = dst + i;
1199 dst_array._set(&mut store, dst_i, src_elem)?;
1200 }
1201 } else {
1202 for i in (0..len).rev() {
1203 let src_elem = src_array._get(&mut store, src + i)?;
1204 let dst_i = dst + i;
1205 dst_array._set(&mut store, dst_i, src_elem)?;
1206 }
1207 }
1208 Ok(())
1209 }
1210
1211 #[cfg(feature = "gc")]
is_subtype( store: &mut dyn VMStore, _instance: InstanceId, actual_engine_type: u32, expected_engine_type: u32, ) -> u321212 fn is_subtype(
1213 store: &mut dyn VMStore,
1214 _instance: InstanceId,
1215 actual_engine_type: u32,
1216 expected_engine_type: u32,
1217 ) -> u32 {
1218 use wasmtime_environ::VMSharedTypeIndex;
1219
1220 let actual = VMSharedTypeIndex::from_u32(actual_engine_type);
1221 let expected = VMSharedTypeIndex::from_u32(expected_engine_type);
1222
1223 let is_subtype: bool = store.engine().signatures().is_subtype(actual, expected);
1224
1225 log::trace!("is_subtype(actual={actual:?}, expected={expected:?}) -> {is_subtype}",);
1226 is_subtype as u32
1227 }
1228
1229 // Implementation of `memory.atomic.notify` for locally defined memories.
1230 #[cfg(feature = "threads")]
memory_atomic_notify( store: &mut dyn VMStore, instance: InstanceId, memory_index: u32, addr_index: u64, count: u32, ) -> Result<u32, Trap>1231 fn memory_atomic_notify(
1232 store: &mut dyn VMStore,
1233 instance: InstanceId,
1234 memory_index: u32,
1235 addr_index: u64,
1236 count: u32,
1237 ) -> Result<u32, Trap> {
1238 let memory = DefinedMemoryIndex::from_u32(memory_index);
1239 store
1240 .instance_mut(instance)
1241 .get_defined_memory_mut(memory)
1242 .atomic_notify(addr_index, count)
1243 }
1244
1245 // Implementation of `memory.atomic.wait32` for locally defined memories.
1246 #[cfg(feature = "threads")]
memory_atomic_wait32( store: &mut dyn VMStore, instance: InstanceId, memory_index: u32, addr_index: u64, expected: u32, timeout: u64, ) -> Result<u32, Trap>1247 fn memory_atomic_wait32(
1248 store: &mut dyn VMStore,
1249 instance: InstanceId,
1250 memory_index: u32,
1251 addr_index: u64,
1252 expected: u32,
1253 timeout: u64,
1254 ) -> Result<u32, Trap> {
1255 let timeout = (timeout as i64 >= 0).then(|| Duration::from_nanos(timeout));
1256 let memory = DefinedMemoryIndex::from_u32(memory_index);
1257 Ok(store
1258 .instance_mut(instance)
1259 .get_defined_memory_mut(memory)
1260 .atomic_wait32(addr_index, expected, timeout)? as u32)
1261 }
1262
1263 // Implementation of `memory.atomic.wait64` for locally defined memories.
1264 #[cfg(feature = "threads")]
memory_atomic_wait64( store: &mut dyn VMStore, instance: InstanceId, memory_index: u32, addr_index: u64, expected: u64, timeout: u64, ) -> Result<u32, Trap>1265 fn memory_atomic_wait64(
1266 store: &mut dyn VMStore,
1267 instance: InstanceId,
1268 memory_index: u32,
1269 addr_index: u64,
1270 expected: u64,
1271 timeout: u64,
1272 ) -> Result<u32, Trap> {
1273 let timeout = (timeout as i64 >= 0).then(|| Duration::from_nanos(timeout));
1274 let memory = DefinedMemoryIndex::from_u32(memory_index);
1275 Ok(store
1276 .instance_mut(instance)
1277 .get_defined_memory_mut(memory)
1278 .atomic_wait64(addr_index, expected, timeout)? as u32)
1279 }
1280
1281 // Hook for when an instance runs out of fuel.
out_of_gas(store: &mut dyn VMStore, _instance: InstanceId) -> Result<()>1282 fn out_of_gas(store: &mut dyn VMStore, _instance: InstanceId) -> Result<()> {
1283 block_on!(store, async |store, _| {
1284 if !store.refuel() {
1285 return Err(Trap::OutOfFuel.into());
1286 }
1287 #[cfg(feature = "async")]
1288 if store.fuel_yield_interval.is_some() {
1289 crate::runtime::vm::Yield::new().await;
1290 }
1291 Ok(())
1292 })?
1293 }
1294
1295 // Hook for when an instance observes that the epoch has changed.
1296 #[cfg(target_has_atomic = "64")]
new_epoch(store: &mut dyn VMStore, _instance: InstanceId) -> Result<NextEpoch>1297 fn new_epoch(store: &mut dyn VMStore, _instance: InstanceId) -> Result<NextEpoch> {
1298 use crate::UpdateDeadline;
1299
1300 #[cfg(feature = "debug")]
1301 {
1302 store.block_on_debug_handler(crate::DebugEvent::EpochYield)?;
1303 }
1304
1305 let update_deadline = store.new_epoch_updated_deadline()?;
1306 block_on!(store, async move |store, asyncness| {
1307 #[cfg(not(feature = "async"))]
1308 let _ = asyncness;
1309
1310 let delta = match update_deadline {
1311 UpdateDeadline::Interrupt => return Err(Trap::Interrupt.into()),
1312 UpdateDeadline::Continue(delta) => delta,
1313
1314 // Note that custom errors are used here to avoid tripping up on the
1315 // `block_on!` message that otherwise assumes
1316 // async-configuration-after-the-fact.
1317 #[cfg(feature = "async")]
1318 UpdateDeadline::Yield(delta) => {
1319 if asyncness != Asyncness::Yes {
1320 bail!(
1321 "cannot use `UpdateDeadline::Yield` without using \
1322 an async wasm entrypoint",
1323 );
1324 }
1325 crate::runtime::vm::Yield::new().await;
1326 delta
1327 }
1328 #[cfg(feature = "async")]
1329 UpdateDeadline::YieldCustom(delta, future) => {
1330 if asyncness != Asyncness::Yes {
1331 bail!(
1332 "cannot use `UpdateDeadline::YieldCustom` without using \
1333 an async wasm entrypoint",
1334 );
1335 }
1336 future.await;
1337 delta
1338 }
1339 };
1340
1341 // Set a new deadline and return the new epoch deadline so
1342 // the Wasm code doesn't have to reload it.
1343 store.set_epoch_deadline(delta);
1344 Ok(NextEpoch(store.get_epoch_deadline()))
1345 })?
1346 }
1347
1348 struct NextEpoch(u64);
1349
1350 unsafe impl HostResultHasUnwindSentinel for NextEpoch {
1351 type Abi = u64;
1352 const SENTINEL: u64 = u64::MAX;
into_abi(self) -> u641353 fn into_abi(self) -> u64 {
1354 self.0
1355 }
1356 }
1357
1358 // Hook for validating malloc using wmemcheck_state.
1359 #[cfg(feature = "wmemcheck")]
check_malloc(store: &mut dyn VMStore, instance: InstanceId, addr: u32, len: u32) -> Result<()>1360 fn check_malloc(store: &mut dyn VMStore, instance: InstanceId, addr: u32, len: u32) -> Result<()> {
1361 let instance = store.instance_mut(instance);
1362 if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() {
1363 let result = wmemcheck_state.malloc(addr as usize, len as usize);
1364 wmemcheck_state.memcheck_on();
1365 match result {
1366 Ok(()) => {}
1367 Err(DoubleMalloc { addr, len }) => {
1368 bail!("Double malloc at addr {:#x} of size {}", addr, len)
1369 }
1370 Err(OutOfBounds { addr, len }) => {
1371 bail!("Malloc out of bounds at addr {:#x} of size {}", addr, len);
1372 }
1373 _ => {
1374 panic!("unreachable")
1375 }
1376 }
1377 }
1378 Ok(())
1379 }
1380
1381 // Hook for validating free using wmemcheck_state.
1382 #[cfg(feature = "wmemcheck")]
check_free(store: &mut dyn VMStore, instance: InstanceId, addr: u32) -> Result<()>1383 fn check_free(store: &mut dyn VMStore, instance: InstanceId, addr: u32) -> Result<()> {
1384 let instance = store.instance_mut(instance);
1385 if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() {
1386 let result = wmemcheck_state.free(addr as usize);
1387 wmemcheck_state.memcheck_on();
1388 match result {
1389 Ok(()) => {}
1390 Err(InvalidFree { addr }) => {
1391 bail!("Invalid free at addr {:#x}", addr)
1392 }
1393 _ => {
1394 panic!("unreachable")
1395 }
1396 }
1397 }
1398 Ok(())
1399 }
1400
1401 // Hook for validating load using wmemcheck_state.
1402 #[cfg(feature = "wmemcheck")]
check_load( store: &mut dyn VMStore, instance: InstanceId, num_bytes: u32, addr: u32, offset: u32, ) -> Result<()>1403 fn check_load(
1404 store: &mut dyn VMStore,
1405 instance: InstanceId,
1406 num_bytes: u32,
1407 addr: u32,
1408 offset: u32,
1409 ) -> Result<()> {
1410 let instance = store.instance_mut(instance);
1411 if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() {
1412 let result = wmemcheck_state.read(addr as usize + offset as usize, num_bytes as usize);
1413 match result {
1414 Ok(()) => {}
1415 Err(InvalidRead { addr, len }) => {
1416 bail!("Invalid load at addr {:#x} of size {}", addr, len);
1417 }
1418 Err(OutOfBounds { addr, len }) => {
1419 bail!("Load out of bounds at addr {:#x} of size {}", addr, len);
1420 }
1421 _ => {
1422 panic!("unreachable")
1423 }
1424 }
1425 }
1426 Ok(())
1427 }
1428
1429 // Hook for validating store using wmemcheck_state.
1430 #[cfg(feature = "wmemcheck")]
check_store( store: &mut dyn VMStore, instance: InstanceId, num_bytes: u32, addr: u32, offset: u32, ) -> Result<()>1431 fn check_store(
1432 store: &mut dyn VMStore,
1433 instance: InstanceId,
1434 num_bytes: u32,
1435 addr: u32,
1436 offset: u32,
1437 ) -> Result<()> {
1438 let instance = store.instance_mut(instance);
1439 if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() {
1440 let result = wmemcheck_state.write(addr as usize + offset as usize, num_bytes as usize);
1441 match result {
1442 Ok(()) => {}
1443 Err(InvalidWrite { addr, len }) => {
1444 bail!("Invalid store at addr {:#x} of size {}", addr, len)
1445 }
1446 Err(OutOfBounds { addr, len }) => {
1447 bail!("Store out of bounds at addr {:#x} of size {}", addr, len)
1448 }
1449 _ => {
1450 panic!("unreachable")
1451 }
1452 }
1453 }
1454 Ok(())
1455 }
1456
1457 // Hook for turning wmemcheck load/store validation off when entering a malloc function.
1458 #[cfg(feature = "wmemcheck")]
malloc_start(store: &mut dyn VMStore, instance: InstanceId)1459 fn malloc_start(store: &mut dyn VMStore, instance: InstanceId) {
1460 let instance = store.instance_mut(instance);
1461 if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() {
1462 wmemcheck_state.memcheck_off();
1463 }
1464 }
1465
1466 // Hook for turning wmemcheck load/store validation off when entering a free function.
1467 #[cfg(feature = "wmemcheck")]
free_start(store: &mut dyn VMStore, instance: InstanceId)1468 fn free_start(store: &mut dyn VMStore, instance: InstanceId) {
1469 let instance = store.instance_mut(instance);
1470 if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() {
1471 wmemcheck_state.memcheck_off();
1472 }
1473 }
1474
1475 // Hook for tracking wasm stack updates using wmemcheck_state.
1476 #[cfg(feature = "wmemcheck")]
update_stack_pointer(_store: &mut dyn VMStore, _instance: InstanceId, _value: u32)1477 fn update_stack_pointer(_store: &mut dyn VMStore, _instance: InstanceId, _value: u32) {
1478 // TODO: stack-tracing has yet to be finalized. All memory below
1479 // the address of the top of the stack is marked as valid for
1480 // loads and stores.
1481 // if let Some(wmemcheck_state) = &mut instance.wmemcheck_state {
1482 // instance.wmemcheck_state.update_stack_pointer(value as usize);
1483 // }
1484 }
1485
1486 // Hook updating wmemcheck_state memory state vector every time memory.grow is called.
1487 #[cfg(feature = "wmemcheck")]
update_mem_size(store: &mut dyn VMStore, instance: InstanceId, num_pages: u32)1488 fn update_mem_size(store: &mut dyn VMStore, instance: InstanceId, num_pages: u32) {
1489 let instance = store.instance_mut(instance);
1490 if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() {
1491 const KIB: usize = 1024;
1492 let num_bytes = num_pages as usize * 64 * KIB;
1493 wmemcheck_state.update_mem_size(num_bytes);
1494 }
1495 }
1496
floor_f32(_store: &mut dyn VMStore, _instance: InstanceId, val: f32) -> f321497 fn floor_f32(_store: &mut dyn VMStore, _instance: InstanceId, val: f32) -> f32 {
1498 val.wasm_floor()
1499 }
1500
floor_f64(_store: &mut dyn VMStore, _instance: InstanceId, val: f64) -> f641501 fn floor_f64(_store: &mut dyn VMStore, _instance: InstanceId, val: f64) -> f64 {
1502 val.wasm_floor()
1503 }
1504
ceil_f32(_store: &mut dyn VMStore, _instance: InstanceId, val: f32) -> f321505 fn ceil_f32(_store: &mut dyn VMStore, _instance: InstanceId, val: f32) -> f32 {
1506 val.wasm_ceil()
1507 }
1508
ceil_f64(_store: &mut dyn VMStore, _instance: InstanceId, val: f64) -> f641509 fn ceil_f64(_store: &mut dyn VMStore, _instance: InstanceId, val: f64) -> f64 {
1510 val.wasm_ceil()
1511 }
1512
trunc_f32(_store: &mut dyn VMStore, _instance: InstanceId, val: f32) -> f321513 fn trunc_f32(_store: &mut dyn VMStore, _instance: InstanceId, val: f32) -> f32 {
1514 val.wasm_trunc()
1515 }
1516
trunc_f64(_store: &mut dyn VMStore, _instance: InstanceId, val: f64) -> f641517 fn trunc_f64(_store: &mut dyn VMStore, _instance: InstanceId, val: f64) -> f64 {
1518 val.wasm_trunc()
1519 }
1520
nearest_f32(_store: &mut dyn VMStore, _instance: InstanceId, val: f32) -> f321521 fn nearest_f32(_store: &mut dyn VMStore, _instance: InstanceId, val: f32) -> f32 {
1522 val.wasm_nearest()
1523 }
1524
nearest_f64(_store: &mut dyn VMStore, _instance: InstanceId, val: f64) -> f641525 fn nearest_f64(_store: &mut dyn VMStore, _instance: InstanceId, val: f64) -> f64 {
1526 val.wasm_nearest()
1527 }
1528
1529 // This intrinsic is only used on x86_64 platforms as an implementation of
1530 // the `i8x16.swizzle` instruction when `pshufb` in SSSE3 is not available.
1531 #[cfg(all(target_arch = "x86_64", target_feature = "sse"))]
i8x16_swizzle(_store: &mut dyn VMStore, _instance: InstanceId, a: i8x16, b: i8x16) -> i8x161532 fn i8x16_swizzle(_store: &mut dyn VMStore, _instance: InstanceId, a: i8x16, b: i8x16) -> i8x16 {
1533 union U {
1534 reg: i8x16,
1535 mem: [u8; 16],
1536 }
1537
1538 unsafe {
1539 let a = U { reg: a }.mem;
1540 let b = U { reg: b }.mem;
1541
1542 // Use the `swizzle` semantics of returning 0 on any out-of-bounds
1543 // index, rather than the x86 pshufb semantics, since Wasmtime uses
1544 // this to implement `i8x16.swizzle`.
1545 let select = |arr: &[u8; 16], byte: u8| {
1546 if byte >= 16 { 0x00 } else { arr[byte as usize] }
1547 };
1548
1549 U {
1550 mem: [
1551 select(&a, b[0]),
1552 select(&a, b[1]),
1553 select(&a, b[2]),
1554 select(&a, b[3]),
1555 select(&a, b[4]),
1556 select(&a, b[5]),
1557 select(&a, b[6]),
1558 select(&a, b[7]),
1559 select(&a, b[8]),
1560 select(&a, b[9]),
1561 select(&a, b[10]),
1562 select(&a, b[11]),
1563 select(&a, b[12]),
1564 select(&a, b[13]),
1565 select(&a, b[14]),
1566 select(&a, b[15]),
1567 ],
1568 }
1569 .reg
1570 }
1571 }
1572
1573 #[cfg(not(all(target_arch = "x86_64", target_feature = "sse")))]
i8x16_swizzle(_store: &mut dyn VMStore, _instance: InstanceId, _a: i8x16, _b: i8x16) -> i8x161574 fn i8x16_swizzle(_store: &mut dyn VMStore, _instance: InstanceId, _a: i8x16, _b: i8x16) -> i8x16 {
1575 unreachable!()
1576 }
1577
1578 // This intrinsic is only used on x86_64 platforms as an implementation of
1579 // the `i8x16.shuffle` instruction when `pshufb` in SSSE3 is not available.
1580 #[cfg(all(target_arch = "x86_64", target_feature = "sse"))]
i8x16_shuffle( _store: &mut dyn VMStore, _instance: InstanceId, a: i8x16, b: i8x16, c: i8x16, ) -> i8x161581 fn i8x16_shuffle(
1582 _store: &mut dyn VMStore,
1583 _instance: InstanceId,
1584 a: i8x16,
1585 b: i8x16,
1586 c: i8x16,
1587 ) -> i8x16 {
1588 union U {
1589 reg: i8x16,
1590 mem: [u8; 16],
1591 }
1592
1593 unsafe {
1594 let ab = [U { reg: a }.mem, U { reg: b }.mem];
1595 let c = U { reg: c }.mem;
1596
1597 // Use the `shuffle` semantics of returning 0 on any out-of-bounds
1598 // index, rather than the x86 pshufb semantics, since Wasmtime uses
1599 // this to implement `i8x16.shuffle`.
1600 let select = |arr: &[[u8; 16]; 2], byte: u8| {
1601 if byte >= 32 {
1602 0x00
1603 } else if byte >= 16 {
1604 arr[1][byte as usize - 16]
1605 } else {
1606 arr[0][byte as usize]
1607 }
1608 };
1609
1610 U {
1611 mem: [
1612 select(&ab, c[0]),
1613 select(&ab, c[1]),
1614 select(&ab, c[2]),
1615 select(&ab, c[3]),
1616 select(&ab, c[4]),
1617 select(&ab, c[5]),
1618 select(&ab, c[6]),
1619 select(&ab, c[7]),
1620 select(&ab, c[8]),
1621 select(&ab, c[9]),
1622 select(&ab, c[10]),
1623 select(&ab, c[11]),
1624 select(&ab, c[12]),
1625 select(&ab, c[13]),
1626 select(&ab, c[14]),
1627 select(&ab, c[15]),
1628 ],
1629 }
1630 .reg
1631 }
1632 }
1633
1634 #[cfg(not(all(target_arch = "x86_64", target_feature = "sse")))]
i8x16_shuffle( _store: &mut dyn VMStore, _instance: InstanceId, _a: i8x16, _b: i8x16, _c: i8x16, ) -> i8x161635 fn i8x16_shuffle(
1636 _store: &mut dyn VMStore,
1637 _instance: InstanceId,
1638 _a: i8x16,
1639 _b: i8x16,
1640 _c: i8x16,
1641 ) -> i8x16 {
1642 unreachable!()
1643 }
1644
fma_f32x4( _store: &mut dyn VMStore, _instance: InstanceId, x: f32x4, y: f32x4, z: f32x4, ) -> f32x41645 fn fma_f32x4(
1646 _store: &mut dyn VMStore,
1647 _instance: InstanceId,
1648 x: f32x4,
1649 y: f32x4,
1650 z: f32x4,
1651 ) -> f32x4 {
1652 union U {
1653 reg: f32x4,
1654 mem: [f32; 4],
1655 }
1656
1657 unsafe {
1658 let x = U { reg: x }.mem;
1659 let y = U { reg: y }.mem;
1660 let z = U { reg: z }.mem;
1661
1662 U {
1663 mem: [
1664 x[0].wasm_mul_add(y[0], z[0]),
1665 x[1].wasm_mul_add(y[1], z[1]),
1666 x[2].wasm_mul_add(y[2], z[2]),
1667 x[3].wasm_mul_add(y[3], z[3]),
1668 ],
1669 }
1670 .reg
1671 }
1672 }
1673
fma_f64x2( _store: &mut dyn VMStore, _instance: InstanceId, x: f64x2, y: f64x2, z: f64x2, ) -> f64x21674 fn fma_f64x2(
1675 _store: &mut dyn VMStore,
1676 _instance: InstanceId,
1677 x: f64x2,
1678 y: f64x2,
1679 z: f64x2,
1680 ) -> f64x2 {
1681 union U {
1682 reg: f64x2,
1683 mem: [f64; 2],
1684 }
1685
1686 unsafe {
1687 let x = U { reg: x }.mem;
1688 let y = U { reg: y }.mem;
1689 let z = U { reg: z }.mem;
1690
1691 U {
1692 mem: [x[0].wasm_mul_add(y[0], z[0]), x[1].wasm_mul_add(y[1], z[1])],
1693 }
1694 .reg
1695 }
1696 }
1697
1698 /// This intrinsic is just used to record trap information.
1699 ///
1700 /// The `Infallible` "ok" type here means that this never returns success, it
1701 /// only ever returns an error, and this hooks into the machinery to handle
1702 /// `Result` values to record such trap information.
trap( _store: &mut dyn VMStore, _instance: InstanceId, code: u8, ) -> Result<Infallible, TrapReason>1703 fn trap(
1704 _store: &mut dyn VMStore,
1705 _instance: InstanceId,
1706 code: u8,
1707 ) -> Result<Infallible, TrapReason> {
1708 Err(TrapReason::Wasm(
1709 wasmtime_environ::Trap::from_u8(code).unwrap(),
1710 ))
1711 }
1712
raise(store: &mut dyn VMStore, _instance: InstanceId)1713 fn raise(store: &mut dyn VMStore, _instance: InstanceId) {
1714 // SAFETY: this is only called from compiled wasm so we know that wasm has
1715 // already been entered. It's a dynamic safety precondition that the trap
1716 // information has already been arranged to be present.
1717 unsafe { crate::runtime::vm::traphandlers::raise_preexisting_trap(store) }
1718 }
1719
1720 // Builtins for continuations. These are thin wrappers around the
1721 // respective definitions in stack_switching.rs.
1722 #[cfg(feature = "stack-switching")]
cont_new( store: &mut dyn VMStore, instance: InstanceId, func: *mut u8, param_count: u32, result_count: u32, ) -> Result<Option<AllocationSize>>1723 fn cont_new(
1724 store: &mut dyn VMStore,
1725 instance: InstanceId,
1726 func: *mut u8,
1727 param_count: u32,
1728 result_count: u32,
1729 ) -> Result<Option<AllocationSize>> {
1730 let ans =
1731 crate::vm::stack_switching::cont_new(store, instance, func, param_count, result_count)?;
1732 Ok(Some(AllocationSize(ans.cast::<u8>() as usize)))
1733 }
1734
1735 #[cfg(feature = "gc")]
get_instance_id(_store: &mut dyn VMStore, instance: InstanceId) -> u321736 fn get_instance_id(_store: &mut dyn VMStore, instance: InstanceId) -> u32 {
1737 instance.as_u32()
1738 }
1739
1740 #[cfg(feature = "gc")]
throw_ref( store: &mut dyn VMStore, _instance: InstanceId, exnref: u32, ) -> Result<(), TrapReason>1741 fn throw_ref(
1742 store: &mut dyn VMStore,
1743 _instance: InstanceId,
1744 exnref: u32,
1745 ) -> Result<(), TrapReason> {
1746 let exnref = VMGcRef::from_raw_u32(exnref).ok_or_else(|| Trap::NullReference)?;
1747 let exnref = store.unwrap_gc_store_mut().clone_gc_ref(&exnref);
1748 let exnref = exnref
1749 .into_exnref(&*store.unwrap_gc_store().gc_heap)
1750 .expect("gc ref should be an exception object");
1751 store.set_pending_exception(exnref);
1752 Err(TrapReason::Exception)
1753 }
1754
breakpoint(store: &mut dyn VMStore, _instance: InstanceId) -> Result<()>1755 fn breakpoint(store: &mut dyn VMStore, _instance: InstanceId) -> Result<()> {
1756 #[cfg(feature = "debug")]
1757 {
1758 store.block_on_debug_handler(crate::DebugEvent::Breakpoint)?;
1759 }
1760 // Avoid unused-argument warning in no-debugger builds.
1761 let _ = store;
1762 Ok(())
1763 }
1764