Skip to main content

numcodecs_wasm_host_reproducible/
engine.rs

1use wasm_runtime_layer::{
2    ExportType, ExternType, FuncType, GlobalType, ImportType, MemoryType, TableType,
3    backend::{
4        AsContext, AsContextMut, Export, Extern, Imports, Value, WasmEngine, WasmExternRef,
5        WasmFunc, WasmGlobal, WasmInstance, WasmMemory, WasmModule, WasmStore, WasmStoreContext,
6        WasmStoreContextMut, WasmTable,
7    },
8};
9
10use crate::transform::{
11    instcnt::{InstructionCounterInjecter, PerfWitInterfaces},
12    nan::NaNCanonicaliser,
13};
14
15#[derive(Clone)]
16#[repr(transparent)]
17pub struct ReproducibleEngine<E: WasmEngine>(E);
18
19impl<E: WasmEngine> WasmEngine for ReproducibleEngine<E> {
20    type ExternRef = ReproducibleExternRef<E>;
21    type Func = ReproducibleFunc<E>;
22    type Global = ReproducibleGlobal<E>;
23    type Instance = ReproducibleInstance<E>;
24    type Memory = ReproducibleMemory<E>;
25    type Module = ReproducibleModule<E>;
26    type Store<T: 'static> = ReproducibleStore<T, E>;
27    type StoreContext<'a, T: 'static> = ReproducibleStoreContext<'a, T, E>;
28    type StoreContextMut<'a, T: 'static> = ReproducibleStoreContextMut<'a, T, E>;
29    type Table = ReproducibleTable<E>;
30}
31
32impl<E: WasmEngine> ReproducibleEngine<E> {
33    pub const fn new(engine: E) -> Self {
34        Self(engine)
35    }
36
37    const fn as_ref(&self) -> &E {
38        &self.0
39    }
40
41    const fn from_ref(engine: &E) -> &Self {
42        // Safety: Self is a transparent newtype around E
43        #[expect(unsafe_code)]
44        unsafe {
45            &*std::ptr::from_ref(engine).cast()
46        }
47    }
48}
49
50#[derive(Clone)]
51#[repr(transparent)]
52pub struct ReproducibleExternRef<E: WasmEngine>(E::ExternRef);
53
54impl<E: WasmEngine> WasmExternRef<ReproducibleEngine<E>> for ReproducibleExternRef<E> {
55    fn new<T: 'static + Send + Sync>(
56        mut ctx: impl AsContextMut<ReproducibleEngine<E>>,
57        object: T,
58    ) -> Self {
59        Self(<E::ExternRef as WasmExternRef<E>>::new(
60            ctx.as_context_mut().as_inner_context_mut(),
61            object,
62        ))
63    }
64
65    fn downcast<'a, 's: 'a, T: 'static, S: 'static>(
66        &'a self,
67        store: ReproducibleStoreContext<'s, S, E>,
68    ) -> anyhow::Result<&'a T> {
69        WasmExternRef::downcast(&self.0, store.0)
70    }
71}
72
73#[derive(Clone)]
74#[repr(transparent)]
75pub struct ReproducibleFunc<E: WasmEngine>(E::Func);
76
77impl<E: WasmEngine> WasmFunc<ReproducibleEngine<E>> for ReproducibleFunc<E> {
78    fn new<T: 'static>(
79        mut ctx: impl AsContextMut<ReproducibleEngine<E>, UserState = T>,
80        ty: FuncType,
81        func: impl 'static
82        + Send
83        + Sync
84        + Fn(
85            ReproducibleStoreContextMut<T, E>,
86            &[Value<ReproducibleEngine<E>>],
87            &mut [Value<ReproducibleEngine<E>>],
88        ) -> anyhow::Result<()>,
89    ) -> Self {
90        Self(<E::Func as WasmFunc<E>>::new(
91            ctx.as_context_mut().as_inner_context_mut(),
92            ty,
93            move |ctx, args, results| {
94                func(
95                    ReproducibleStoreContextMut(ctx),
96                    from_values(args),
97                    from_values_mut(results),
98                )
99            },
100        ))
101    }
102
103    fn ty(&self, ctx: impl AsContext<ReproducibleEngine<E>>) -> FuncType {
104        WasmFunc::ty(&self.0, ctx.as_context().as_inner_context())
105    }
106
107    fn call<T>(
108        &self,
109        mut ctx: impl AsContextMut<ReproducibleEngine<E>>,
110        args: &[Value<ReproducibleEngine<E>>],
111        results: &mut [Value<ReproducibleEngine<E>>],
112    ) -> anyhow::Result<()> {
113        WasmFunc::call::<T>(
114            &self.0,
115            ctx.as_context_mut().as_inner_context_mut(),
116            as_values(args),
117            as_values_mut(results),
118        )
119    }
120}
121
122#[derive(Clone)]
123#[repr(transparent)]
124pub struct ReproducibleGlobal<E: WasmEngine>(E::Global);
125
126impl<E: WasmEngine> WasmGlobal<ReproducibleEngine<E>> for ReproducibleGlobal<E> {
127    fn new(
128        mut ctx: impl AsContextMut<ReproducibleEngine<E>>,
129        value: Value<ReproducibleEngine<E>>,
130        mutable: bool,
131    ) -> Self {
132        Self(<E::Global as WasmGlobal<E>>::new(
133            ctx.as_context_mut().as_inner_context_mut(),
134            into_value(value),
135            mutable,
136        ))
137    }
138
139    fn ty(&self, ctx: impl AsContext<ReproducibleEngine<E>>) -> GlobalType {
140        WasmGlobal::ty(&self.0, ctx.as_context().as_inner_context())
141    }
142
143    fn set(
144        &self,
145        mut ctx: impl AsContextMut<ReproducibleEngine<E>>,
146        new_value: Value<ReproducibleEngine<E>>,
147    ) -> anyhow::Result<()> {
148        WasmGlobal::set(
149            &self.0,
150            ctx.as_context_mut().as_inner_context_mut(),
151            into_value(new_value),
152        )
153    }
154
155    fn get(
156        &self,
157        mut ctx: impl AsContextMut<ReproducibleEngine<E>>,
158    ) -> Value<ReproducibleEngine<E>> {
159        from_value(WasmGlobal::get(
160            &self.0,
161            ctx.as_context_mut().as_inner_context_mut(),
162        ))
163    }
164}
165
166#[derive(Clone)]
167#[repr(transparent)]
168pub struct ReproducibleInstance<E: WasmEngine>(E::Instance);
169
170impl<E: WasmEngine> WasmInstance<ReproducibleEngine<E>> for ReproducibleInstance<E> {
171    fn new(
172        mut store: impl AsContextMut<ReproducibleEngine<E>>,
173        module: &ReproducibleModule<E>,
174        imports: &Imports<ReproducibleEngine<E>>,
175    ) -> anyhow::Result<Self> {
176        let mut new_imports = Imports::new();
177        new_imports.extend(
178            imports
179                .into_iter()
180                .map(|((module, name), value)| ((module, name), into_extern(value))),
181        );
182
183        let PerfWitInterfaces {
184            perf: perf_interface,
185            instruction_counter,
186        } = PerfWitInterfaces::get();
187        new_imports.define(
188            &format!("{perf_interface}"),
189            instruction_counter,
190            Extern::Global(
191                store
192                    .as_context_mut()
193                    .get_instruction_counter_global()
194                    .0
195                    .clone(),
196            ),
197        );
198
199        Ok(Self(<E::Instance as WasmInstance<E>>::new(
200            store.as_context_mut().as_inner_context_mut(),
201            &module.0,
202            &new_imports,
203        )?))
204    }
205
206    fn exports(
207        &self,
208        store: impl AsContext<ReproducibleEngine<E>>,
209    ) -> Box<dyn Iterator<Item = Export<ReproducibleEngine<E>>>> {
210        Box::new(
211            WasmInstance::exports(&self.0, store.as_context().as_inner_context()).map(
212                |Export { name, value }| Export {
213                    name,
214                    value: from_extern(value),
215                },
216            ),
217        )
218    }
219
220    fn get_export(
221        &self,
222        store: impl AsContext<ReproducibleEngine<E>>,
223        name: &str,
224    ) -> Option<Extern<ReproducibleEngine<E>>> {
225        WasmInstance::get_export(&self.0, store.as_context().as_inner_context(), name)
226            .map(from_extern)
227    }
228}
229
230#[derive(Clone)]
231#[repr(transparent)]
232pub struct ReproducibleMemory<E: WasmEngine>(E::Memory);
233
234impl<E: WasmEngine> WasmMemory<ReproducibleEngine<E>> for ReproducibleMemory<E> {
235    fn new(
236        mut ctx: impl AsContextMut<ReproducibleEngine<E>>,
237        ty: MemoryType,
238    ) -> anyhow::Result<Self> {
239        Ok(Self(<E::Memory as WasmMemory<E>>::new(
240            ctx.as_context_mut().as_inner_context_mut(),
241            ty,
242        )?))
243    }
244
245    fn ty(&self, ctx: impl AsContext<ReproducibleEngine<E>>) -> MemoryType {
246        WasmMemory::ty(&self.0, ctx.as_context().as_inner_context())
247    }
248
249    fn grow(
250        &self,
251        mut ctx: impl AsContextMut<ReproducibleEngine<E>>,
252        additional: u32,
253    ) -> anyhow::Result<u32> {
254        WasmMemory::grow(
255            &self.0,
256            ctx.as_context_mut().as_inner_context_mut(),
257            additional,
258        )
259    }
260
261    fn current_pages(&self, ctx: impl AsContext<ReproducibleEngine<E>>) -> u32 {
262        WasmMemory::current_pages(&self.0, ctx.as_context().as_inner_context())
263    }
264
265    fn read(
266        &self,
267        ctx: impl AsContext<ReproducibleEngine<E>>,
268        offset: usize,
269        buffer: &mut [u8],
270    ) -> anyhow::Result<()> {
271        WasmMemory::read(&self.0, ctx.as_context().as_inner_context(), offset, buffer)
272    }
273
274    fn write(
275        &self,
276        mut ctx: impl AsContextMut<ReproducibleEngine<E>>,
277        offset: usize,
278        buffer: &[u8],
279    ) -> anyhow::Result<()> {
280        WasmMemory::write(
281            &self.0,
282            ctx.as_context_mut().as_inner_context_mut(),
283            offset,
284            buffer,
285        )
286    }
287}
288
289pub const DETERMINISTIC_WASM_MODULE_FEATURES: wasmparser::WasmFeaturesInflated =
290    wasmparser::WasmFeaturesInflated {
291        // MUST: mutable globals do not introduce non-determinism, as long
292        //       as the host does not change their value to be non-
293        //       deterministic
294        mutable_global: true,
295        // OK: saturating float -> int conversions only produce finite values
296        saturating_float_to_int: true,
297        // MUST: arithmetic sign extension operators are deterministic
298        sign_extension: true,
299        // OK: references types themselves are fully determinstic (since they
300        //     are opaque), and the new instructions for growing a table and
301        //     checking its size would only be non-deterministic if the host is
302        reference_types: true,
303        // OK: returning multiple values does not interact with determinism
304        multi_value: true,
305        // MUST: operations like memcpy and memset are deterministic
306        bulk_memory: true,
307        // (ok): fixed-width SIMD replicates scalar float semantics
308        simd: true,
309        // BAD: exposes platform-dependent behaviour and non-determinism
310        relaxed_simd: false,
311        // BAD: allows non-deterministic concurrency and race conditions
312        threads: false,
313        // BAD: allows non-deterministic concurrency and race conditions
314        shared_everything_threads: false,
315        // (ok): using tail calls does not interact with determinism
316        //       but support is not universal yet:
317        //       https://webassembly.org/features/
318        tail_call: false,
319        // BAD: float operations can introduce non-deterministic NaNs
320        floats: false,
321        // MUST: using multiple memories does not interact with determinism
322        multi_memory: true,
323        // OK: exception handling is fully deterministic
324        exceptions: true,
325        // (nope): using a 64bit memory space does not interact with
326        //         determinism but encourages large memory usage
327        memory64: false,
328        // (ok): const i[32|64] add, sub, and mul are deterministic
329        //       but support is not universal yet:
330        //       https://webassembly.org/features/
331        extended_const: false,
332        // NO-CORE: components must have been translated into core WASM
333        //          modules by now
334        component_model: false,
335        // (unsure): disabled for now, needs further research
336        function_references: false,
337        // (unsure): disabled for now, needs further research
338        memory_control: false,
339        // (unsure): disabled for now, needs further research
340        gc: false,
341        // (ok): statically declaring a custom page size is deterministic
342        //       and could reduce resource consumption
343        //       but there is no support yet
344        custom_page_sizes: false,
345        // (unsure): disabled for now, needs further research
346        legacy_exceptions: false,
347        // (unsure): disabled for now, depends on reference types and gc,
348        //           needs further research
349        gc_types: false,
350        // (unsure): disabled for now, not needed since codecs are sync for now
351        stack_switching: false,
352        // OK: wide integer add, sub, and mul are deterministic
353        wide_arithmetic: true,
354        // NO-CORE: components must have been translated into core WASM
355        //          modules by now
356        cm_values: false,
357        // NO-CORE: components must have been translated into core WASM
358        //          modules by now
359        cm_nested_names: false,
360        // NO-CORE: components must have been translated into core WASM
361        //          modules by now
362        cm_async: false,
363        // NO-CORE: components must have been translated into core WASM
364        //          modules by now
365        cm_async_stackful: false,
366        // NO-CORE: components must have been translated into core WASM
367        //          modules by now
368        cm_async_builtins: false,
369        // NO-CORE: components must have been translated into core WASM
370        //          modules by now
371        cm_threading: false,
372        // NO-CORE: components must have been translated into core WASM
373        //          modules by now
374        cm_error_context: false,
375        // NO-CORE: components must have been translated into core WASM
376        //          modules by now
377        cm_fixed_size_list: false,
378        // NO-CORE: components must have been translated into core WASM
379        //          modules by now
380        cm_gc: false,
381        // OK: part of the reference types proposal, only enables leb-encoding
382        //     of the table immediate
383        call_indirect_overlong: true,
384        // MUST: part of bulk memory, operations like memcpy and memset are
385        //       deterministic
386        bulk_memory_opt: true,
387    };
388
389#[derive(Clone)]
390#[repr(transparent)]
391pub struct ReproducibleModule<E: WasmEngine>(E::Module);
392
393impl<E: WasmEngine> WasmModule<ReproducibleEngine<E>> for ReproducibleModule<E> {
394    fn new(engine: &ReproducibleEngine<E>, bytes: &[u8]) -> anyhow::Result<Self> {
395        let features = wasmparser::WasmFeatures::from(wasmparser::WasmFeaturesInflated {
396            // MUST: floats are required and we are running the NaN
397            //       canonicalisation transform to make them deterministic
398            floats: true,
399            ..DETERMINISTIC_WASM_MODULE_FEATURES
400        });
401
402        wasmparser::Validator::new_with_features(features).validate_all(bytes)?;
403
404        // Inject an instruction counter into the WASM module
405        let bytes = InstructionCounterInjecter::apply_to_module(bytes, features)?;
406
407        // Normalise NaNs to ensure floating point operations are deterministic
408        let bytes = NaNCanonicaliser::apply_to_module(&bytes, features)?;
409
410        Ok(Self(<E::Module as WasmModule<E>>::new(
411            engine.as_ref(),
412            bytes.as_slice(),
413        )?))
414    }
415
416    fn exports(&self) -> Box<dyn '_ + Iterator<Item = ExportType<'_>>> {
417        WasmModule::exports(&self.0)
418    }
419
420    fn get_export(&self, name: &str) -> Option<ExternType> {
421        WasmModule::get_export(&self.0, name)
422    }
423
424    fn imports(&self) -> Box<dyn '_ + Iterator<Item = ImportType<'_>>> {
425        WasmModule::imports(&self.0)
426    }
427}
428
429struct StoreData<T, E: WasmEngine> {
430    data: T,
431    instruction_counter: Option<ReproducibleGlobal<E>>,
432}
433
434#[derive(Clone)]
435#[repr(transparent)]
436pub struct ReproducibleStore<T: 'static, E: WasmEngine>(E::Store<StoreData<T, E>>);
437
438impl<T: 'static, E: WasmEngine> WasmStore<T, ReproducibleEngine<E>> for ReproducibleStore<T, E> {
439    fn new(engine: &ReproducibleEngine<E>, data: T) -> Self {
440        Self(<E::Store<StoreData<T, E>> as WasmStore<
441            StoreData<T, E>,
442            E,
443        >>::new(
444            engine.as_ref(),
445            StoreData {
446                data,
447                instruction_counter: None,
448            },
449        ))
450    }
451
452    fn engine(&self) -> &ReproducibleEngine<E> {
453        ReproducibleEngine::from_ref(WasmStore::engine(&self.0))
454    }
455
456    fn data(&self) -> &T {
457        &WasmStore::data(&self.0).data
458    }
459
460    fn data_mut(&mut self) -> &mut T {
461        &mut WasmStore::data_mut(&mut self.0).data
462    }
463
464    fn into_data(self) -> T {
465        WasmStore::into_data(self.0).data
466    }
467}
468
469impl<T: 'static, E: WasmEngine> AsContext<ReproducibleEngine<E>> for ReproducibleStore<T, E> {
470    type UserState = T;
471
472    fn as_context(&self) -> ReproducibleStoreContext<'_, Self::UserState, E> {
473        ReproducibleStoreContext(AsContext::as_context(&self.0))
474    }
475}
476
477impl<T: 'static, E: WasmEngine> AsContextMut<ReproducibleEngine<E>> for ReproducibleStore<T, E> {
478    fn as_context_mut(&mut self) -> ReproducibleStoreContextMut<'_, Self::UserState, E> {
479        ReproducibleStoreContextMut(AsContextMut::as_context_mut(&mut self.0))
480    }
481}
482
483#[repr(transparent)]
484pub struct ReproducibleStoreContext<'a, T: 'static, E: WasmEngine>(
485    E::StoreContext<'a, StoreData<T, E>>,
486);
487
488impl<'a, T: 'static, E: WasmEngine> WasmStoreContext<'a, T, ReproducibleEngine<E>>
489    for ReproducibleStoreContext<'a, T, E>
490{
491    fn engine(&self) -> &ReproducibleEngine<E> {
492        ReproducibleEngine::from_ref(WasmStoreContext::engine(&self.0))
493    }
494
495    fn data(&self) -> &T {
496        &WasmStoreContext::data(&self.0).data
497    }
498}
499
500impl<T: 'static, E: WasmEngine> AsContext<ReproducibleEngine<E>>
501    for ReproducibleStoreContext<'_, T, E>
502{
503    type UserState = T;
504
505    fn as_context(&self) -> ReproducibleStoreContext<'_, Self::UserState, E> {
506        ReproducibleStoreContext(AsContext::as_context(&self.0))
507    }
508}
509
510impl<T: 'static, E: WasmEngine> ReproducibleStoreContext<'_, T, E> {
511    fn as_inner_context(&self) -> E::StoreContext<'_, StoreData<T, E>> {
512        self.0.as_context()
513    }
514}
515
516#[repr(transparent)]
517pub struct ReproducibleStoreContextMut<'a, T: 'static, E: WasmEngine>(
518    E::StoreContextMut<'a, StoreData<T, E>>,
519);
520
521impl<'a, T: 'static, E: WasmEngine> WasmStoreContext<'a, T, ReproducibleEngine<E>>
522    for ReproducibleStoreContextMut<'a, T, E>
523{
524    fn engine(&self) -> &ReproducibleEngine<E> {
525        ReproducibleEngine::from_ref(WasmStoreContext::engine(&self.0))
526    }
527
528    fn data(&self) -> &T {
529        &WasmStoreContext::data(&self.0).data
530    }
531}
532
533impl<'a, T: 'static, E: WasmEngine> WasmStoreContextMut<'a, T, ReproducibleEngine<E>>
534    for ReproducibleStoreContextMut<'a, T, E>
535{
536    fn data_mut(&mut self) -> &mut T {
537        &mut WasmStoreContextMut::data_mut(&mut self.0).data
538    }
539}
540
541impl<T: 'static, E: WasmEngine> AsContext<ReproducibleEngine<E>>
542    for ReproducibleStoreContextMut<'_, T, E>
543{
544    type UserState = T;
545
546    fn as_context(&self) -> ReproducibleStoreContext<'_, Self::UserState, E> {
547        ReproducibleStoreContext(AsContext::as_context(&self.0))
548    }
549}
550
551impl<T: 'static, E: WasmEngine> AsContextMut<ReproducibleEngine<E>>
552    for ReproducibleStoreContextMut<'_, T, E>
553{
554    fn as_context_mut(&mut self) -> ReproducibleStoreContextMut<'_, Self::UserState, E> {
555        ReproducibleStoreContextMut(AsContextMut::as_context_mut(&mut self.0))
556    }
557}
558
559impl<T: 'static, E: WasmEngine> ReproducibleStoreContextMut<'_, T, E> {
560    fn as_inner_context_mut(&mut self) -> E::StoreContextMut<'_, StoreData<T, E>> {
561        self.0.as_context_mut()
562    }
563
564    fn get_instruction_counter_global(&mut self) -> &ReproducibleGlobal<E> {
565        let mut this = self;
566
567        // NLL cannot prove this to be safe, but Polonius can
568        polonius_the_crab::polonius!(|this| -> &'polonius ReproducibleGlobal<E> {
569            let data: &mut StoreData<T, E> = WasmStoreContextMut::data_mut(&mut this.0);
570            if let Some(global) = &data.instruction_counter {
571                polonius_the_crab::polonius_return!(global);
572            }
573        });
574
575        let global = WasmGlobal::new(AsContextMut::as_context_mut(this), Value::I64(0), true);
576
577        let data: &mut StoreData<T, E> = WasmStoreContextMut::data_mut(&mut this.0);
578        data.instruction_counter.insert(global)
579    }
580}
581
582#[derive(Clone)]
583#[repr(transparent)]
584pub struct ReproducibleTable<E: WasmEngine>(E::Table);
585
586impl<E: WasmEngine> WasmTable<ReproducibleEngine<E>> for ReproducibleTable<E> {
587    fn new(
588        mut ctx: impl AsContextMut<ReproducibleEngine<E>>,
589        ty: TableType,
590        init: Value<ReproducibleEngine<E>>,
591    ) -> anyhow::Result<Self> {
592        Ok(Self(<E::Table as WasmTable<E>>::new(
593            ctx.as_context_mut().as_inner_context_mut(),
594            ty,
595            into_value(init),
596        )?))
597    }
598
599    fn ty(&self, ctx: impl AsContext<ReproducibleEngine<E>>) -> TableType {
600        WasmTable::ty(&self.0, ctx.as_context().as_inner_context())
601    }
602
603    fn size(&self, ctx: impl AsContext<ReproducibleEngine<E>>) -> u32 {
604        WasmTable::size(&self.0, ctx.as_context().as_inner_context())
605    }
606
607    fn grow(
608        &self,
609        mut ctx: impl AsContextMut<ReproducibleEngine<E>>,
610        delta: u32,
611        init: Value<ReproducibleEngine<E>>,
612    ) -> anyhow::Result<u32> {
613        WasmTable::grow(
614            &self.0,
615            ctx.as_context_mut().as_inner_context_mut(),
616            delta,
617            into_value(init),
618        )
619    }
620
621    fn get(
622        &self,
623        mut ctx: impl AsContextMut<ReproducibleEngine<E>>,
624        index: u32,
625    ) -> Option<Value<ReproducibleEngine<E>>> {
626        WasmTable::get(&self.0, ctx.as_context_mut().as_inner_context_mut(), index).map(from_value)
627    }
628
629    fn set(
630        &self,
631        mut ctx: impl AsContextMut<ReproducibleEngine<E>>,
632        index: u32,
633        value: Value<ReproducibleEngine<E>>,
634    ) -> anyhow::Result<()> {
635        WasmTable::set(
636            &self.0,
637            ctx.as_context_mut().as_inner_context_mut(),
638            index,
639            into_value(value),
640        )
641    }
642}
643
644const fn as_values<E: WasmEngine>(values: &[Value<ReproducibleEngine<E>>]) -> &[Value<E>] {
645    // Safety: all of our WASM runtime type wrappers are transparent newtypes
646    #[expect(unsafe_code)]
647    unsafe {
648        std::slice::from_raw_parts(values.as_ptr().cast(), values.len())
649    }
650}
651
652const fn as_values_mut<E: WasmEngine>(
653    values: &mut [Value<ReproducibleEngine<E>>],
654) -> &mut [Value<E>] {
655    // Safety: all of our WASM runtime type wrappers are transparent newtypes
656    #[expect(unsafe_code)]
657    unsafe {
658        std::slice::from_raw_parts_mut(values.as_mut_ptr().cast(), values.len())
659    }
660}
661
662const fn from_values<E: WasmEngine>(values: &[Value<E>]) -> &[Value<ReproducibleEngine<E>>] {
663    // Safety: all of our WASM runtime type wrappers are transparent newtypes
664    #[expect(unsafe_code)]
665    unsafe {
666        std::slice::from_raw_parts(values.as_ptr().cast(), values.len())
667    }
668}
669
670const fn from_values_mut<E: WasmEngine>(
671    values: &mut [Value<E>],
672) -> &mut [Value<ReproducibleEngine<E>>] {
673    // Safety: all of our WASM runtime type wrappers are transparent newtypes
674    #[expect(unsafe_code)]
675    unsafe {
676        std::slice::from_raw_parts_mut(values.as_mut_ptr().cast(), values.len())
677    }
678}
679
680fn into_value<E: WasmEngine>(value: Value<ReproducibleEngine<E>>) -> Value<E> {
681    match value {
682        Value::I32(v) => Value::I32(v),
683        Value::I64(v) => Value::I64(v),
684        Value::F32(v) => Value::F32(v),
685        Value::F64(v) => Value::F64(v),
686        Value::FuncRef(v) => Value::FuncRef(v.map(|v| v.0)),
687        Value::ExternRef(v) => Value::ExternRef(v.map(|v| v.0)),
688    }
689}
690
691fn from_value<E: WasmEngine>(value: Value<E>) -> Value<ReproducibleEngine<E>> {
692    match value {
693        Value::I32(v) => Value::I32(v),
694        Value::I64(v) => Value::I64(v),
695        Value::F32(v) => Value::F32(v),
696        Value::F64(v) => Value::F64(v),
697        Value::FuncRef(v) => Value::FuncRef(v.map(ReproducibleFunc)),
698        Value::ExternRef(v) => Value::ExternRef(v.map(ReproducibleExternRef)),
699    }
700}
701
702fn into_extern<E: WasmEngine>(value: Extern<ReproducibleEngine<E>>) -> Extern<E> {
703    match value {
704        Extern::Global(v) => Extern::Global(v.0),
705        Extern::Table(v) => Extern::Table(v.0),
706        Extern::Memory(v) => Extern::Memory(v.0),
707        Extern::Func(v) => Extern::Func(v.0),
708    }
709}
710
711fn from_extern<E: WasmEngine>(value: Extern<E>) -> Extern<ReproducibleEngine<E>> {
712    match value {
713        Extern::Global(v) => Extern::Global(ReproducibleGlobal(v)),
714        Extern::Table(v) => Extern::Table(ReproducibleTable(v)),
715        Extern::Memory(v) => Extern::Memory(ReproducibleMemory(v)),
716        Extern::Func(v) => Extern::Func(ReproducibleFunc(v)),
717    }
718}