wasm/execution/store/mod.rs
1use core::mem;
2
3use crate::addrs::{
4 AddrVec, DataAddr, ElemAddr, FuncAddr, GlobalAddr, MemAddr, ModuleAddr, TableAddr,
5};
6use crate::config::Config;
7use crate::core::indices::TypeIdx;
8use crate::core::reader::span::Span;
9use crate::core::reader::types::data::{DataModeActive, DataSegment};
10use crate::core::reader::types::element::{ActiveElem, ElemItems, ElemMode, ElemType};
11use crate::core::reader::types::export::{Export, ExportDesc};
12use crate::core::reader::types::global::{Global, GlobalType};
13use crate::core::reader::types::{
14 ExternType, FuncType, ImportSubTypeRelation, MemType, ResultType, TableType,
15};
16use crate::core::reader::WasmReader;
17use crate::execution::interpreter_loop::{self, memory_init, table_init};
18use crate::execution::value::{Ref, Value};
19use crate::execution::{run_const_span, Stack};
20use crate::resumable::{
21 Dormitory, FreshResumableRef, InvokedResumableRef, Resumable, ResumableRef, RunState,
22};
23use crate::{RefType, RuntimeError, ValidationInfo};
24use alloc::collections::btree_map::BTreeMap;
25use alloc::string::String;
26use alloc::sync::Arc;
27use alloc::vec;
28use alloc::vec::Vec;
29use instances::{
30 DataInst, ElemInst, FuncInst, GlobalInst, HostFuncInst, MemInst, ModuleInst, TableInst,
31 WasmFuncInst,
32};
33use linear_memory::LinearMemory;
34
35use super::interop::InteropValueList;
36use super::interpreter_loop::{data_drop, elem_drop};
37use super::value::ValueTypeMismatchError;
38use super::UnwrapValidatedExt;
39
40pub mod addrs;
41pub(crate) mod instances;
42pub(crate) mod linear_memory;
43
44/// The store represents all global state that can be manipulated by WebAssembly programs. It
45/// consists of the runtime representation of all instances of functions, tables, memories, and
46/// globals, element segments, and data segments that have been allocated during the life time of
47/// the abstract machine.
48/// <https://webassembly.github.io/spec/core/exec/runtime.html#store>
49pub struct Store<'b, T: Config> {
50 pub(crate) functions: AddrVec<FuncAddr, FuncInst<T>>,
51 pub(crate) tables: AddrVec<TableAddr, TableInst>,
52 pub(crate) memories: AddrVec<MemAddr, MemInst>,
53 pub(crate) globals: AddrVec<GlobalAddr, GlobalInst>,
54 pub(crate) elements: AddrVec<ElemAddr, ElemInst>,
55 pub(crate) data: AddrVec<DataAddr, DataInst>,
56
57 // fields outside of the spec but are convenient are below
58 /// An address space of modules instantiated within the context of this [`Store`].
59 ///
60 /// Although the WebAssembly Specification 2.0 does not specify module instances
61 /// to be part of the [`Store`], in reality they can be managed very similar to
62 /// other instance types. Therefore, we extend the [`Store`] by a module address
63 /// space along with a `ModuleAddr` index type.
64 pub(crate) modules: AddrVec<ModuleAddr, ModuleInst<'b>>,
65
66 pub user_data: T,
67
68 // data structure holding all resumable objects that belong to this store
69 pub(crate) dormitory: Dormitory,
70}
71
72impl<'b, T: Config> Store<'b, T> {
73 /// Creates a new empty store with some user data
74 ///
75 /// See: WebAssembly Specification 2.0 - 7.1.4 - store_init
76 pub fn new(user_data: T) -> Self {
77 // 1. Return the empty store.
78 // For us the store is empty except for the user data, which we do not have control over.
79 Self {
80 functions: AddrVec::default(),
81 tables: AddrVec::default(),
82 memories: AddrVec::default(),
83 globals: AddrVec::default(),
84 elements: AddrVec::default(),
85 data: AddrVec::default(),
86 modules: AddrVec::default(),
87 dormitory: Dormitory::default(),
88 user_data,
89 }
90 }
91
92 /// Instantiate a new module instance from a [`ValidationInfo`] in this [`Store`].
93 ///
94 /// Note that if this returns an `Err(_)`, the store might be left in an ill-defined state. This might cause further
95 /// operations to have unexpected results.
96 ///
97 /// See: WebAssembly Specification 2.0 - 7.1.5 - module_instantiate
98 pub fn module_instantiate(
99 &mut self,
100 validation_info: &ValidationInfo<'b>,
101 extern_vals: Vec<ExternVal>,
102 maybe_fuel: Option<u32>,
103 ) -> Result<InstantiationOutcome, RuntimeError> {
104 // instantiation: step 1
105 // The module is guaranteed to be valid, because only validation can
106 // produce `ValidationInfo`s.
107
108 // instantiation: step 3
109 if validation_info.imports.len() != extern_vals.len() {
110 return Err(RuntimeError::ExternValsLenMismatch);
111 }
112
113 // instantiation: step 4
114 let imports_as_extern_types = validation_info
115 .imports
116 .iter()
117 .map(|import| import.desc.extern_type(validation_info));
118 for (extern_val, import_as_extern_type) in extern_vals.iter().zip(imports_as_extern_types) {
119 // instantiation: step 4a
120 // check that extern_val is valid in this Store, which should be guaranteed by the caller through a safety constraint in the future.
121 // TODO document this instantiation step properly
122
123 // instantiation: step 4b
124 let extern_type = extern_val.extern_type(self);
125
126 // instantiation: step 4c
127 if !extern_type.is_subtype_of(&import_as_extern_type) {
128 return Err(RuntimeError::InvalidImportType);
129 }
130 }
131
132 // instantiation: step 5
133 // module_inst_init is unfortunately circularly defined from parts of module_inst that would be defined in step 11, which uses module_inst_init again implicitly.
134 // therefore I am mimicking the reference interpreter code here, I will allocate functions in the store in this step instead of step 11.
135 // https://github.com/WebAssembly/spec/blob/8d6792e3d6709e8d3e90828f9c8468253287f7ed/interpreter/exec/eval.ml#L789
136 let module_inst = ModuleInst {
137 types: validation_info.types.clone(),
138 func_addrs: extern_vals.iter().funcs().collect(),
139 table_addrs: Vec::new(),
140 mem_addrs: Vec::new(),
141 global_addrs: extern_vals.iter().globals().collect(),
142 elem_addrs: Vec::new(),
143 data_addrs: Vec::new(),
144 exports: BTreeMap::new(),
145 wasm_bytecode: validation_info.wasm,
146 sidetable: validation_info.sidetable.clone(),
147 };
148 let module_addr = self.modules.insert(module_inst);
149
150 // TODO rewrite this part
151 // <https://webassembly.github.io/spec/core/exec/modules.html#functions>
152 let func_addrs: Vec<FuncAddr> = validation_info
153 .functions
154 .iter()
155 .zip(validation_info.func_blocks_stps.iter())
156 .map(|(ty_idx, (span, stp))| self.alloc_func((*ty_idx, (*span, *stp)), module_addr))
157 .collect();
158
159 self.modules
160 .get_mut(module_addr)
161 .func_addrs
162 .extend(func_addrs);
163
164 // instantiation: this roughly matches step 6,7,8
165 // validation guarantees these will evaluate without errors.
166 let maybe_global_init_vals: Result<Vec<Value>, _> = validation_info
167 .globals
168 .iter()
169 .map(|global| {
170 run_const_span(validation_info.wasm, &global.init_expr, module_addr, self)
171 .transpose()
172 .unwrap_validated()
173 })
174 .collect();
175 let global_init_vals = maybe_global_init_vals?;
176
177 // instantiation: this roughly matches step 9,10
178
179 let mut element_init_ref_lists: Vec<Vec<Ref>> =
180 Vec::with_capacity(validation_info.elements.len());
181
182 for elem in &validation_info.elements {
183 let mut new_list = Vec::new();
184 match &elem.init {
185 // shortcut of evaluation of "ref.func <func_idx>; end;"
186 // validation guarantees corresponding func_idx's existence
187 ElemItems::RefFuncs(ref_funcs) => {
188 for func_idx in ref_funcs {
189 let func_addr = *self
190 .modules
191 .get(module_addr)
192 .func_addrs
193 .get(*func_idx as usize)
194 .unwrap_validated();
195
196 new_list.push(Ref::Func(func_addr));
197 }
198 }
199 ElemItems::Exprs(_, exprs) => {
200 for expr in exprs {
201 new_list.push(
202 run_const_span(validation_info.wasm, expr, module_addr, self)?
203 .unwrap_validated() // there is a return value
204 .try_into()
205 .unwrap_validated(), // return value has the correct type
206 )
207 }
208 }
209 }
210 element_init_ref_lists.push(new_list);
211 }
212
213 // instantiation: step 11 - module allocation (except function allocation - which was made in step 5)
214 // https://webassembly.github.io/spec/core/exec/modules.html#alloc-module
215
216 // allocation: begin
217
218 // allocation: step 1
219 let module = validation_info;
220
221 let vals = global_init_vals;
222 let ref_lists = element_init_ref_lists;
223
224 // allocation: skip step 2 as it was done in instantiation step 5
225
226 // allocation: step 3-13
227 let table_addrs: Vec<TableAddr> = module
228 .tables
229 .iter()
230 .map(|table_type| self.alloc_table(*table_type, Ref::Null(table_type.et)))
231 .collect();
232 let mem_addrs: Vec<MemAddr> = module
233 .memories
234 .iter()
235 .map(|mem_type| self.alloc_mem(*mem_type))
236 .collect();
237 let global_addrs: Vec<GlobalAddr> = module
238 .globals
239 .iter()
240 .zip(vals)
241 .map(
242 |(
243 Global {
244 ty: global_type, ..
245 },
246 val,
247 )| self.alloc_global(*global_type, val),
248 )
249 .collect();
250 let elem_addrs = module
251 .elements
252 .iter()
253 .zip(ref_lists)
254 .map(|(elem, refs)| self.alloc_elem(elem.ty(), refs))
255 .collect();
256 let data_addrs = module
257 .data
258 .iter()
259 .map(|DataSegment { init: bytes, .. }| self.alloc_data(bytes))
260 .collect();
261
262 // allocation: skip step 14 as it was done in instantiation step 5
263
264 // allocation: step 15,16
265 let mut table_addrs_mod: Vec<TableAddr> = extern_vals.iter().tables().collect();
266 table_addrs_mod.extend(table_addrs);
267
268 let mut mem_addrs_mod: Vec<MemAddr> = extern_vals.iter().mems().collect();
269 mem_addrs_mod.extend(mem_addrs);
270
271 // skipping step 17 partially as it was partially done in instantiation step
272 self.modules
273 .get_mut(module_addr)
274 .global_addrs
275 .extend(global_addrs);
276
277 // allocation: step 18,19
278 let export_insts: BTreeMap<String, ExternVal> = module
279 .exports
280 .iter()
281 .map(|Export { name, desc }| {
282 let module_inst = self.modules.get(module_addr);
283 let value = match desc {
284 ExportDesc::FuncIdx(func_idx) => {
285 ExternVal::Func(module_inst.func_addrs[*func_idx])
286 }
287 ExportDesc::TableIdx(table_idx) => {
288 ExternVal::Table(table_addrs_mod[*table_idx])
289 }
290 ExportDesc::MemIdx(mem_idx) => ExternVal::Mem(mem_addrs_mod[*mem_idx]),
291 ExportDesc::GlobalIdx(global_idx) => {
292 ExternVal::Global(module_inst.global_addrs[*global_idx])
293 }
294 };
295 (String::from(name), value)
296 })
297 .collect();
298
299 // allocation: step 20,21 initialize module (except functions and globals due to instantiation step 5, allocation step 14,17)
300 let module_inst = self.modules.get_mut(module_addr);
301 module_inst.table_addrs = table_addrs_mod;
302 module_inst.mem_addrs = mem_addrs_mod;
303 module_inst.elem_addrs = elem_addrs;
304 module_inst.data_addrs = data_addrs;
305 module_inst.exports = export_insts;
306
307 // allocation: end
308
309 // instantiation step 11 end: module_inst properly allocated after this point.
310
311 // instantiation: step 12-15
312 // TODO have to stray away from the spec a bit since our codebase does not lend itself well to freely executing instructions by themselves
313 for (
314 i,
315 ElemType {
316 init: elem_items,
317 mode,
318 },
319 ) in validation_info.elements.iter().enumerate()
320 {
321 match mode {
322 ElemMode::Active(ActiveElem {
323 table_idx: table_idx_i,
324 init_expr: einstr_i,
325 }) => {
326 let n = elem_items.len() as u32;
327 // equivalent to init.len() in spec
328 // instantiation step 14:
329 // TODO (for now, we are doing hopefully what is equivalent to it)
330 // execute:
331 // einstr_i
332 // i32.const 0
333 // i32.const n
334 // table.init table_idx_i i
335 // elem.drop i
336 let d: i32 = run_const_span(validation_info.wasm, einstr_i, module_addr, self)?
337 .unwrap_validated() // there is a return value
338 .try_into()
339 .unwrap_validated(); // return value has correct type
340
341 let s = 0;
342 table_init(
343 &self.modules,
344 &mut self.tables,
345 &self.elements,
346 module_addr,
347 i,
348 *table_idx_i as usize,
349 n,
350 s,
351 d,
352 )?;
353 elem_drop(&self.modules, &mut self.elements, module_addr, i)?;
354 }
355 ElemMode::Declarative => {
356 // instantiation step 15:
357 // TODO (for now, we are doing hopefully what is equivalent to it)
358 // execute:
359 // elem.drop i
360 elem_drop(&self.modules, &mut self.elements, module_addr, i)?;
361 }
362 ElemMode::Passive => (),
363 }
364 }
365
366 // instantiation: step 16
367 // TODO have to stray away from the spec a bit since our codebase does not lend itself well to freely executing instructions by themselves
368 for (i, DataSegment { init, mode }) in validation_info.data.iter().enumerate() {
369 match mode {
370 crate::core::reader::types::data::DataMode::Active(DataModeActive {
371 memory_idx,
372 offset: dinstr_i,
373 }) => {
374 let n = init.len() as u32;
375 // assert: mem_idx is 0
376 if *memory_idx != 0 {
377 // TODO fix error
378 return Err(RuntimeError::MoreThanOneMemory);
379 }
380
381 // TODO (for now, we are doing hopefully what is equivalent to it)
382 // execute:
383 // dinstr_i
384 // i32.const 0
385 // i32.const n
386 // memory.init i
387 // data.drop i
388 let d: i32 = run_const_span(validation_info.wasm, dinstr_i, module_addr, self)?
389 .unwrap_validated() // there is a return value
390 .try_into()
391 .unwrap_validated(); // return value has the correct type
392
393 let s = 0;
394 memory_init(
395 &self.modules,
396 &mut self.memories,
397 &self.data,
398 module_addr,
399 i,
400 0,
401 n,
402 s,
403 d,
404 )?;
405 data_drop(&self.modules, &mut self.data, module_addr, i)?;
406 }
407 crate::core::reader::types::data::DataMode::Passive => (),
408 }
409 }
410
411 // instantiation: step 17
412 let maybe_remaining_fuel = if let Some(func_idx) = validation_info.start {
413 // TODO (for now, we are doing hopefully what is equivalent to it)
414 // execute
415 // call func_ifx
416 let func_addr = self.modules.get(module_addr).func_addrs[func_idx];
417 let RunState::Finished {
418 maybe_remaining_fuel,
419 ..
420 } = self.invoke(func_addr, Vec::new(), maybe_fuel)?
421 else {
422 return Err(RuntimeError::OutOfFuel);
423 };
424 maybe_remaining_fuel
425 } else {
426 maybe_fuel
427 };
428
429 Ok(InstantiationOutcome {
430 module_addr,
431 maybe_remaining_fuel,
432 })
433 }
434
435 /// Gets an export of a specific module instance by its name
436 ///
437 /// See: WebAssembly Specification 2.0 - 7.1.6 - instance_export
438 pub fn instance_export(
439 &self,
440 module_addr: ModuleAddr,
441 name: &str,
442 ) -> Result<ExternVal, RuntimeError> {
443 // Fetch the module instance because we store them in the [`Store`]
444 let module_inst = self.modules.get(module_addr);
445
446 // 1. Assert: due to validity of the module instance `moduleinst`, all its export names are different
447
448 // 2. If there exists an `exportinst_i` in `moduleinst.exports` such that name `exportinst_i.name` equals `name`, then:
449 // a. Return the external value `exportinst_i.value`.
450 // 3. Else return `error`.
451 module_inst
452 .exports
453 .get(name)
454 .copied()
455 .ok_or(RuntimeError::UnknownExport)
456 }
457
458 /// Allocates a new function with some host code.
459 ///
460 /// This type of function is also called a host function.
461 ///
462 /// # Panics & Unexpected Behavior
463 /// The specification states that:
464 ///
465 /// > This operation must make sure that the provided host function satisfies the pre-
466 /// > and post-conditions required for a function instance with type `functype`.
467 ///
468 /// Therefore, all "invalid" host functions (e.g. those which return incorrect return values)
469 /// can cause the interpreter to panic or behave unexpectedly.
470 ///
471 /// See: <https://webassembly.github.io/spec/core/exec/modules.html#host-functions>
472 /// See: WebAssembly Specification 2.0 - 7.1.7 - func_alloc
473 pub fn func_alloc(
474 &mut self,
475 func_type: FuncType,
476 host_func: fn(&mut T, Vec<Value>) -> Result<Vec<Value>, HaltExecutionError>,
477 ) -> FuncAddr {
478 // 1. Pre-condition: `functype` is valid.
479
480 // 2. Let `funcaddr` be the result of allocating a host function in `store` with
481 // function type `functype` and host function code `hostfunc`.
482 // 3. Return the new store paired with `funcaddr`.
483 //
484 // Note: Returning the new store is a noop for us because we mutate the store instead.
485 self.functions.insert(FuncInst::HostFunc(HostFuncInst {
486 function_type: func_type,
487 hostcode: host_func,
488 }))
489 }
490
491 /// Gets the type of a function by its addr.
492 ///
493 /// See: WebAssembly Specification 2.0 - 7.1.7 - func_type
494 pub fn func_type(&self, func_addr: FuncAddr) -> FuncType {
495 // 1. Return `S.funcs[a].type`.
496 self.functions.get(func_addr).ty()
497
498 // 2. Post-condition: the returned function type is valid.
499 }
500
501 /// See: WebAssembly Specification 2.0 - 7.1.7 - func_invoke
502 pub fn invoke(
503 &mut self,
504 func_addr: FuncAddr,
505 params: Vec<Value>,
506 maybe_fuel: Option<u32>,
507 ) -> Result<RunState, RuntimeError> {
508 self.resume(self.create_resumable(func_addr, params, maybe_fuel)?)
509 }
510
511 /// Allocates a new table with some table type and an initialization value `ref` and returns its table address.
512 ///
513 /// See: WebAssembly Specification 2.0 - 7.1.8 - table_alloc
514 pub fn table_alloc(
515 &mut self,
516 table_type: TableType,
517 r#ref: Ref,
518 ) -> Result<TableAddr, RuntimeError> {
519 // Check pre-condition: ref has correct type
520 if table_type.et != r#ref.ty() {
521 return Err(RuntimeError::TableTypeMismatch);
522 }
523
524 // 1. Pre-condition: `tabletype` is valid
525
526 // 2. Let `tableaddr` be the result of allocating a table in `store` with table type `tabletype`
527 // and initialization value `ref`.
528 let table_addr = self.alloc_table(table_type, r#ref);
529
530 // 3. Return the new store paired with `tableaddr`.
531 //
532 // Note: Returning the new store is a noop for us because we mutate the store instead.
533 Ok(table_addr)
534 }
535
536 /// Gets the type of some table by its addr.
537 ///
538 /// See: WebAssembly Specification 2.0 - 7.1.8 - table_type
539 pub fn table_type(&self, table_addr: TableAddr) -> TableType {
540 // 1. Return `S.tables[a].type`.
541 self.tables.get(table_addr).ty
542
543 // 2. Post-condition: the returned table type is valid.
544 }
545
546 /// Reads a single reference from a table by its table address and an index into the table.
547 ///
548 /// See: WebAssembly Specification 2.0 - 7.1.8 - table_read
549 pub fn table_read(&self, table_addr: TableAddr, i: u32) -> Result<Ref, RuntimeError> {
550 // Convert `i` to usize for indexing
551 let i = usize::try_from(i).expect("the architecture to be at least 32-bit");
552
553 // 1. Let `ti` be the table instance `store.tables[tableaddr]`
554 let ti = self.tables.get(table_addr);
555
556 // 2. If `i` is larger than or equal to the length of `ti.elem`, then return `error`.
557 // 3. Else, return the reference value `ti.elem[i]`.
558 ti.elem
559 .get(i)
560 .copied()
561 .ok_or(RuntimeError::TableAccessOutOfBounds)
562 }
563
564 /// Writes a single reference into a table by its table address and an index into the table.
565 ///
566 /// See: WebAssembly Specification 2.0 - 7.1.8 - table_write
567 pub fn table_write(
568 &mut self,
569 table_addr: TableAddr,
570 i: u32,
571 r#ref: Ref,
572 ) -> Result<(), RuntimeError> {
573 // Convert `i` to usize for indexing
574 let i = usize::try_from(i).expect("the architecture to be at least 32-bit");
575
576 // 1. Let `ti` be the table instance `store.tables[tableaddr]`.
577 let ti = self.tables.get_mut(table_addr);
578
579 // Check pre-condition: ref has correct type
580 if ti.ty.et != r#ref.ty() {
581 return Err(RuntimeError::TableTypeMismatch);
582 }
583
584 // 2. If `i` is larger than or equal to the length of `ti.elem`, then return `error`.
585 // 3. Replace `ti.elem[i]` with the reference value `ref`
586 *ti.elem
587 .get_mut(i)
588 .ok_or(RuntimeError::TableAccessOutOfBounds)? = r#ref;
589
590 // 4. Return the updated store.
591 //
592 // Note: Returning the new store is a noop for us because we mutate the store instead.
593 Ok(())
594 }
595
596 /// Gets the current size of a table by its table address.
597 ///
598 /// See: WebAssembly Specification 2.0 - 7.1.8 - table_size
599 pub fn table_size(&self, table_addr: TableAddr) -> u32 {
600 // 1. Return the length of `store.tables[tableaddr].elem`.
601 let len = self.tables.get(table_addr).elem.len();
602
603 // In addition we have to convert the length back to a `u32`
604 u32::try_from(len).expect(
605 "the maximum table length to be u32::MAX because thats what the specification allows for indexing",
606 )
607 }
608
609 /// Grows a table referenced by its table address by `n` elements.
610 ///
611 /// See: WebAssembly Specification 2.0 - 7.1.8 - table_grow
612 pub fn table_grow(
613 &mut self,
614 table_addr: TableAddr,
615 n: u32,
616 r#ref: Ref,
617 ) -> Result<(), RuntimeError> {
618 // 1. Try growing the table instance `store.tables[tableaddr] by `n` elements with initialization value `ref`:
619 // a. If it succeeds, return the updated store.
620 // b. Else, return `error`.
621 //
622 // Note: Returning the new store is a noop for us because we mutate the store instead.
623 self.tables.get_mut(table_addr).grow(n, r#ref)
624 }
625
626 /// Allocates a new linear memory and returns its memory address.
627 ///
628 /// See: WebAssembly Specification 2.0 - 7.1.9 - mem_alloc
629 pub fn mem_alloc(&mut self, mem_type: MemType) -> MemAddr {
630 // 1. Pre-condition: `memtype` is valid.
631
632 // 2. Let `memaddr` be the result of allocating a memory in `store` with memory type `memtype`.
633 // 3. Return the new store paired with `memaddr`.
634 //
635 // Note: Returning the new store is a noop for us because we mutate the store instead.
636 self.alloc_mem(mem_type)
637 }
638
639 /// Gets the memory type of some memory by its memory address
640 ///
641 /// See: WebAssemblySpecification 2.0 - 7.1.9 - mem_type
642 pub fn mem_type(&self, mem_addr: MemAddr) -> MemType {
643 // 1. Return `S.mems[a].type`.
644 self.memories.get(mem_addr).ty
645
646 // 2. Post-condition: the returned memory type is valid.
647 }
648
649 /// Reads a byte from some memory by its memory address and an index into the memory
650 ///
651 /// See: WebAssemblySpecification 2.0 - 7.1.9 - mem_read
652 pub fn mem_read(&self, mem_addr: MemAddr, i: u32) -> Result<u8, RuntimeError> {
653 // Convert the index type
654 let i = usize::try_from(i).expect("the architecture to be at least 32-bit");
655
656 // 1. Let `mi` be the memory instance `store.mems[memaddr]`.
657 let mi = self.memories.get(mem_addr);
658
659 // 2. If `i` is larger than or equal to the length of `mi.data`, then return `error`.
660 // 3. Else, return the byte `mi.data[i]`.
661 mi.mem.load(i)
662 }
663
664 /// Writes a byte into some memory by its memory address and an index into the memory
665 ///
666 /// See: WebAssemblySpecification 2.0 - 7.1.9 - mem_write
667 pub fn mem_write(&self, mem_addr: MemAddr, i: u32, byte: u8) -> Result<(), RuntimeError> {
668 // Convert the index type
669 let i = usize::try_from(i).expect("the architecture to be at least 32-bit");
670
671 // 1. Let `mi` be the memory instance `store.mems[memaddr]`.
672 let mi = self.memories.get(mem_addr);
673
674 mi.mem.store(i, byte)
675 }
676
677 /// Gets the size of some memory by its memory address in pages.
678 ///
679 /// See: WebAssemblySpecification 2.0 - 7.1.9 - mem_size
680 pub fn mem_size(&self, mem_addr: MemAddr) -> u32 {
681 // 1. Return the length of `store.mems[memaddr].data` divided by the page size.
682 let length = self.memories.get(mem_addr).size();
683
684 // In addition we have to convert the length back to a `u32`
685 length.try_into().expect(
686 "the maximum memory length to be smaller than u32::MAX because thats what the specification allows for indexing into the memory. Also the memory size is measured in pages, not bytes.")
687 }
688
689 /// Grows some memory by its memory address by `n` pages.
690 ///
691 /// See: WebAssemblySpecification 2.0 - 7.1.9 - mem_grow
692 pub fn mem_grow(&mut self, mem_addr: MemAddr, n: u32) -> Result<(), RuntimeError> {
693 // 1. Try growing the memory instance `store.mems[memaddr]` by `n` pages:
694 // a. If it succeeds, then return the updated store.
695 // b. Else, return `error`.
696 //
697 // Note: Returning the new store is a noop for us because we mutate the store instead.
698 self.memories.get_mut(mem_addr).grow(n)
699 }
700
701 /// Allocates a new global and returns its global address.
702 ///
703 /// See: WebAssemblySpecification 2.0 - 7.1.10 - global_alloc
704 pub fn global_alloc(
705 &mut self,
706 global_type: GlobalType,
707 val: Value,
708 ) -> Result<GlobalAddr, RuntimeError> {
709 // Check pre-condition: val has correct type
710 if global_type.ty != val.to_ty() {
711 return Err(RuntimeError::GlobalTypeMismatch);
712 }
713
714 // 1. Pre-condition: `globaltype` is valid.
715
716 // 2. Let `globaladdr` be the result of allocating a global with global type `globaltype` and initialization value `val`.
717 let global_addr = self.alloc_global(global_type, val);
718
719 // 3. Return the new store paired with `globaladdr`.
720 //
721 // Note: Returning the new store is a noop for us because we mutate the store instead.
722 Ok(global_addr)
723 }
724
725 /// Returns the global type of some global instance by its addr.
726 ///
727 /// See: WebAssembly Specification 2.0 - 7.1.10 - global_type
728 pub fn global_type(&self, global_addr: GlobalAddr) -> GlobalType {
729 // 1. Return `S.globals[a].type`.
730 self.globals.get(global_addr).ty
731 // 2. Post-condition: the returned global type is valid
732 }
733
734 /// Returns the current value of some global instance by its addr.
735 ///
736 /// See: WebAssembly Specification 2.0 - 7.1.10 - global_read
737 pub fn global_read(&self, global_addr: GlobalAddr) -> Value {
738 // 1. Let `gi` be the global instance `store.globals[globaladdr].
739 let gi = self.globals.get(global_addr);
740
741 // 2. Return the value `gi.value`.
742 gi.value
743 }
744
745 /// Sets a new value of some global instance by its addr.
746 ///
747 /// # Errors
748 /// - [` RuntimeError::WriteOnImmutableGlobal`]
749 /// - [` RuntimeError::GlobalTypeMismatch`]
750 ///
751 /// See: WebAssembly Specification 2.0 - 7.1.10 - global_write
752 pub fn global_write(
753 &mut self,
754 global_addr: GlobalAddr,
755 val: Value,
756 ) -> Result<(), RuntimeError> {
757 // 1. Let `gi` be the global instance `store.globals[globaladdr]`.
758 let gi = self.globals.get_mut(global_addr);
759
760 // 2. Let `mut t` be the structure of the global type `gi.type`.
761 let r#mut = gi.ty.is_mut;
762 let t = gi.ty.ty;
763
764 // 3. If `mut` is not `var`, then return error.
765 if !r#mut {
766 return Err(RuntimeError::WriteOnImmutableGlobal);
767 }
768
769 // Check invariant:
770 // It is an invariant of the semantics that the value has a type equal to the value type of `globaltype`.
771 // See: WebAssembly Specification 2.0 - 4.2.9
772 if t != val.to_ty() {
773 return Err(RuntimeError::GlobalTypeMismatch);
774 }
775
776 // 4. Replace `gi.value` with the value `val`.
777 gi.value = val;
778
779 // 5. Return the updated store.
780 // This is a noop for us, as our store `self` is mutable.
781
782 Ok(())
783 }
784
785 /// roughly matches <https://webassembly.github.io/spec/core/exec/modules.html#functions> with the addition of sidetable pointer to the input signature
786 // TODO refactor the type of func
787 // TODO module_addr
788 fn alloc_func(&mut self, func: (TypeIdx, (Span, usize)), module_addr: ModuleAddr) -> FuncAddr {
789 let (ty, (span, stp)) = func;
790
791 // TODO rewrite this huge chunk of parsing after generic way to re-parse(?) structs lands
792 let mut wasm_reader = WasmReader::new(self.modules.get(module_addr).wasm_bytecode);
793 wasm_reader.move_start_to(span).unwrap_validated();
794
795 let (locals, bytes_read) = wasm_reader
796 .measure_num_read_bytes(crate::code::read_declared_locals)
797 .unwrap_validated();
798
799 let code_expr = wasm_reader
800 .make_span(span.len() - bytes_read)
801 .unwrap_validated();
802
803 // core of the method below
804
805 // validation guarantees func_ty_idx exists within module_inst.types
806 // TODO fix clone
807 let func_inst = FuncInst::WasmFunc(WasmFuncInst {
808 function_type: self.modules.get(module_addr).types[ty].clone(),
809 _ty: ty,
810 locals,
811 code_expr,
812 stp,
813 module_addr,
814 });
815 self.functions.insert(func_inst)
816 }
817
818 /// <https://webassembly.github.io/spec/core/exec/modules.html#tables>
819 fn alloc_table(&mut self, table_type: TableType, reff: Ref) -> TableAddr {
820 let table_inst = TableInst {
821 ty: table_type,
822 elem: vec![reff; table_type.lim.min as usize],
823 };
824
825 self.tables.insert(table_inst)
826 }
827
828 /// <https://webassembly.github.io/spec/core/exec/modules.html#memories>
829 fn alloc_mem(&mut self, mem_type: MemType) -> MemAddr {
830 let mem_inst = MemInst {
831 ty: mem_type,
832 mem: LinearMemory::new_with_initial_pages(
833 mem_type.limits.min.try_into().unwrap_validated(),
834 ),
835 };
836
837 self.memories.insert(mem_inst)
838 }
839
840 /// <https://webassembly.github.io/spec/core/exec/modules.html#globals>
841 fn alloc_global(&mut self, global_type: GlobalType, val: Value) -> GlobalAddr {
842 let global_inst = GlobalInst {
843 ty: global_type,
844 value: val,
845 };
846
847 self.globals.insert(global_inst)
848 }
849
850 /// <https://webassembly.github.io/spec/core/exec/modules.html#element-segments>
851 fn alloc_elem(&mut self, ref_type: RefType, refs: Vec<Ref>) -> ElemAddr {
852 let elem_inst = ElemInst {
853 _ty: ref_type,
854 references: refs,
855 };
856
857 self.elements.insert(elem_inst)
858 }
859
860 /// <https://webassembly.github.io/spec/core/exec/modules.html#data-segments>
861 fn alloc_data(&mut self, bytes: &[u8]) -> DataAddr {
862 let data_inst = DataInst {
863 data: Vec::from(bytes),
864 };
865
866 self.data.insert(data_inst)
867 }
868
869 /// Creates a new resumable, which when resumed for the first time invokes the function `function_ref` is associated
870 /// to, with the arguments `params`. The newly created resumable initially stores `fuel` units of fuel. Returns a
871 /// `[ResumableRef]` associated to the newly created resumable on success.
872 pub fn create_resumable(
873 &self,
874 func_addr: FuncAddr,
875 params: Vec<Value>,
876 maybe_fuel: Option<u32>,
877 ) -> Result<ResumableRef, RuntimeError> {
878 let func_inst = self.functions.get(func_addr);
879
880 let func_ty = func_inst.ty();
881
882 // Verify that the given parameters match the function parameters
883 let param_types = params.iter().map(|v| v.to_ty()).collect::<Vec<_>>();
884
885 if func_ty.params.valtypes != param_types {
886 trace!(
887 "Func param types len: {}; Given args len: {}",
888 func_ty.params.valtypes.len(),
889 param_types.len()
890 );
891 panic!("Invalid parameters for function");
892 }
893
894 Ok(ResumableRef::Fresh(FreshResumableRef {
895 func_addr,
896 params,
897 maybe_fuel,
898 }))
899 }
900
901 /// resumes the resumable associated to `resumable_ref`. Returns a [`RunState`] associated to this resumable if the
902 /// resumable ran out of fuel or completely executed.
903 pub fn resume(&mut self, mut resumable_ref: ResumableRef) -> Result<RunState, RuntimeError> {
904 match resumable_ref {
905 ResumableRef::Fresh(FreshResumableRef {
906 func_addr,
907 params,
908 maybe_fuel,
909 }) => {
910 let func_inst = self.functions.get(func_addr);
911
912 match func_inst {
913 FuncInst::HostFunc(host_func_inst) => {
914 let returns = (host_func_inst.hostcode)(&mut self.user_data, params);
915
916 debug!("Successfully invoked function");
917
918 let returns = returns.map_err(|HaltExecutionError| {
919 RuntimeError::HostFunctionHaltedExecution
920 })?;
921
922 // Verify that the return parameters match the host function parameters
923 // since we have no validation guarantees for host functions
924
925 let return_types = returns.iter().map(|v| v.to_ty()).collect::<Vec<_>>();
926 if host_func_inst.function_type.returns.valtypes != return_types {
927 trace!(
928 "Func return types len: {}; returned args len: {}",
929 host_func_inst.function_type.returns.valtypes.len(),
930 return_types.len()
931 );
932 return Err(RuntimeError::HostFunctionSignatureMismatch);
933 }
934
935 Ok(RunState::Finished {
936 values: returns,
937 maybe_remaining_fuel: maybe_fuel,
938 })
939 }
940 FuncInst::WasmFunc(wasm_func_inst) => {
941 // Prepare a new stack with the locals for the entry function
942 let mut stack = Stack::new_with_values(params);
943
944 stack.push_call_frame::<T>(
945 FuncAddr::INVALID, // TODO using a default value like this is dangerous
946 &wasm_func_inst.function_type,
947 &wasm_func_inst.locals,
948 usize::MAX,
949 usize::MAX,
950 )?;
951
952 let mut resumable = Resumable {
953 current_func_addr: func_addr,
954 stack,
955 pc: wasm_func_inst.code_expr.from,
956 stp: wasm_func_inst.stp,
957 maybe_fuel,
958 };
959
960 // Run the interpreter
961 let result = interpreter_loop::run(&mut resumable, self)?;
962
963 match result {
964 None => {
965 debug!("Successfully invoked function");
966 let maybe_remaining_fuel = resumable.maybe_fuel;
967 let values = resumable.stack.into_values();
968 Ok(RunState::Finished {
969 values,
970 maybe_remaining_fuel,
971 })
972 }
973 Some(required_fuel) => {
974 debug!("Successfully invoked function, but ran out of fuel");
975 Ok(RunState::Resumable {
976 resumable_ref: ResumableRef::Invoked(
977 self.dormitory.insert(resumable),
978 ),
979 required_fuel,
980 })
981 }
982 }
983 }
984 }
985 }
986 ResumableRef::Invoked(InvokedResumableRef {
987 dormitory: ref mut dormitory_weak,
988 ref key,
989 }) => {
990 // Resuming requires `self`'s dormitory to still be alive
991 let Some(dormitory) = dormitory_weak.upgrade() else {
992 return Err(RuntimeError::ResumableNotFound);
993 };
994
995 // Check the given `RuntimeInstance` is the same one used to create `self`
996 if !Arc::ptr_eq(&dormitory, &self.dormitory.0) {
997 return Err(RuntimeError::ResumableNotFound);
998 }
999
1000 // Obtain a write lock to the `Dormitory`
1001 let mut dormitory = dormitory.write();
1002
1003 // TODO We might want to remove the `Resumable` here already and later reinsert it.
1004 // This would prevent holding the lock across the interpreter loop.
1005 let resumable = dormitory
1006 .get_mut(key)
1007 .expect("the key to always be valid as self was not dropped yet");
1008
1009 // Resume execution
1010 let result = interpreter_loop::run(resumable, self)?;
1011
1012 match result {
1013 None => {
1014 let resumable = dormitory.remove(key)
1015 .expect("that the resumable could not have been removed already, because then this self could not exist");
1016
1017 // Take the `Weak` pointing to the dormitory out of `self` and replace it with a default `Weak`.
1018 // This causes the `Drop` impl of `self` to directly quit preventing it from unnecessarily locking the dormitory.
1019 let _dormitory = mem::take(dormitory_weak);
1020 let maybe_remaining_fuel = resumable.maybe_fuel;
1021 let values = resumable.stack.into_values();
1022 Ok(RunState::Finished {
1023 values,
1024 maybe_remaining_fuel,
1025 })
1026 }
1027 Some(required_fuel) => Ok(RunState::Resumable {
1028 resumable_ref,
1029 required_fuel,
1030 }),
1031 }
1032 }
1033 }
1034 }
1035
1036 /// Calls its argument `f` with a mutable reference of the fuel of the
1037 /// respective [`ResumableRef`].
1038 ///
1039 /// Fuel is stored as an [`Option<u32>`], where `None` means that fuel is
1040 /// disabled and `Some(x)` means that `x` units of fuel is left. A
1041 /// ubiquitious use of this method would be using `f` to read or mutate the
1042 /// current fuel amount of the respective [`ResumableRef`].
1043 ///
1044 /// # Example
1045 ///
1046 /// ```
1047 /// use wasm::{resumable::RunState, validate, Store};
1048 /// // a simple module with a single function looping forever
1049 /// let wasm = [ 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
1050 /// 0x01, 0x04, 0x01, 0x60, 0x00, 0x00, 0x03, 0x02,
1051 /// 0x01, 0x00, 0x07, 0x09, 0x01, 0x05, 0x6c, 0x6f,
1052 /// 0x6f, 0x70, 0x73, 0x00, 0x00, 0x0a, 0x09, 0x01,
1053 /// 0x07, 0x00, 0x03, 0x40, 0x0c, 0x00, 0x0b, 0x0b ];
1054 /// let validation_info = validate(&wasm).unwrap();
1055 ///
1056 /// let mut store = Store::new(());
1057 /// let module = store.module_instantiate(&validation_info, Vec::new(), None).unwrap().module_addr;
1058 /// let func_addr = store.instance_export(module, "loops").unwrap().as_func().unwrap();
1059 /// let mut resumable_ref = store.create_resumable(func_addr, Vec::new(), Some(0)).unwrap();
1060 /// store.access_fuel_mut(&mut resumable_ref, |x| { assert_eq!(*x, Some(0)); *x = None; }).unwrap();
1061 /// ```
1062 pub fn access_fuel_mut<R>(
1063 &mut self,
1064 resumable_ref: &mut ResumableRef,
1065 f: impl FnOnce(&mut Option<u32>) -> R,
1066 ) -> Result<R, RuntimeError> {
1067 match resumable_ref {
1068 ResumableRef::Fresh(FreshResumableRef { maybe_fuel, .. }) => Ok(f(maybe_fuel)),
1069 ResumableRef::Invoked(resumable_ref) => {
1070 // Resuming requires `self`'s dormitory to still be alive
1071 let Some(dormitory) = resumable_ref.dormitory.upgrade() else {
1072 return Err(RuntimeError::ResumableNotFound);
1073 };
1074
1075 // Check the given `RuntimeInstance` is the same one used to create `self`
1076 if !Arc::ptr_eq(&dormitory, &self.dormitory.0) {
1077 return Err(RuntimeError::ResumableNotFound);
1078 }
1079
1080 let mut dormitory = dormitory.write();
1081
1082 let resumable = dormitory
1083 .get_mut(&resumable_ref.key)
1084 .expect("the key to always be valid as self was not dropped yet");
1085
1086 Ok(f(&mut resumable.maybe_fuel))
1087 }
1088 }
1089 }
1090
1091 /// Allocates a new function with a statically known type signature with some host code.
1092 ///
1093 /// This function is simply syntactic sugar for calling [`Store::func_alloc`].
1094 ///
1095 /// # Panics & Unexpected Behavior
1096 /// Same as [`Store::func_alloc`].
1097 pub fn func_alloc_typed<Params: InteropValueList, Returns: InteropValueList>(
1098 &mut self,
1099 host_func: fn(&mut T, Vec<Value>) -> Result<Vec<Value>, HaltExecutionError>,
1100 ) -> FuncAddr {
1101 let func_type = FuncType {
1102 params: ResultType {
1103 valtypes: Vec::from(Params::TYS),
1104 },
1105 returns: ResultType {
1106 valtypes: Vec::from(Returns::TYS),
1107 },
1108 };
1109 self.func_alloc(func_type, host_func)
1110 }
1111
1112 /// Invokes a function without fuel.
1113 ///
1114 /// This is a wrapper around [`Store::invoke`].
1115 pub fn invoke_without_fuel(
1116 &mut self,
1117 function: FuncAddr,
1118 params: Vec<Value>,
1119 ) -> Result<Vec<Value>, RuntimeError> {
1120 self.invoke(function, params, None)
1121 .map(|run_state| match run_state {
1122 RunState::Finished {
1123 values,
1124 maybe_remaining_fuel: _,
1125 } => values,
1126 RunState::Resumable { .. } => unreachable!("fuel is disabled"),
1127 })
1128 }
1129
1130 /// Invokes a function with a statically known type signature without fuel.
1131 ///
1132 /// This is a wrapper around [`Store::invoke`].
1133 pub fn invoke_typed_without_fuel<Params: InteropValueList, Returns: InteropValueList>(
1134 &mut self,
1135 function: FuncAddr,
1136 params: Params,
1137 ) -> Result<Returns, RuntimeError> {
1138 self.invoke_without_fuel(function, params.into_values())
1139 .and_then(|values| {
1140 Returns::try_from_values(values.into_iter())
1141 .map_err(|ValueTypeMismatchError| todo!("throw correct error here"))
1142 })
1143 }
1144}
1145
1146/// A marker error for host functions to return, in case they want execution to be halted.
1147pub struct HaltExecutionError;
1148
1149///<https://webassembly.github.io/spec/core/exec/runtime.html#external-values>
1150#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1151pub enum ExternVal {
1152 Func(FuncAddr),
1153 Table(TableAddr),
1154 Mem(MemAddr),
1155 Global(GlobalAddr),
1156}
1157
1158impl ExternVal {
1159 /// returns the external type of `self` according to typing relation,
1160 /// taking `store` as context S.
1161 ///
1162 /// Note: This method may panic if self does not come from the given [`Store`].
1163 ///<https://webassembly.github.io/spec/core/valid/modules.html#imports>
1164 pub fn extern_type<T: Config>(&self, store: &Store<T>) -> ExternType {
1165 match self {
1166 // TODO: fix ugly clone in function types
1167 ExternVal::Func(func_addr) => ExternType::Func(store.functions.get(*func_addr).ty()),
1168 ExternVal::Table(table_addr) => ExternType::Table(store.tables.get(*table_addr).ty),
1169 ExternVal::Mem(mem_addr) => ExternType::Mem(store.memories.get(*mem_addr).ty),
1170 ExternVal::Global(global_addr) => {
1171 ExternType::Global(store.globals.get(*global_addr).ty)
1172 }
1173 }
1174 }
1175}
1176
1177impl ExternVal {
1178 pub fn as_func(self) -> Option<FuncAddr> {
1179 match self {
1180 ExternVal::Func(func_addr) => Some(func_addr),
1181 _ => None,
1182 }
1183 }
1184
1185 pub fn as_table(self) -> Option<TableAddr> {
1186 match self {
1187 ExternVal::Table(table_addr) => Some(table_addr),
1188 _ => None,
1189 }
1190 }
1191
1192 pub fn as_mem(self) -> Option<MemAddr> {
1193 match self {
1194 ExternVal::Mem(mem_addr) => Some(mem_addr),
1195 _ => None,
1196 }
1197 }
1198
1199 pub fn as_global(self) -> Option<GlobalAddr> {
1200 match self {
1201 ExternVal::Global(global_addr) => Some(global_addr),
1202 _ => None,
1203 }
1204 }
1205}
1206
1207/// common convention functions defined for lists of ExternVals, ExternTypes, Exports
1208/// <https://webassembly.github.io/spec/core/exec/runtime.html#conventions>
1209/// <https://webassembly.github.io/spec/core/syntax/types.html#id3>
1210/// <https://webassembly.github.io/spec/core/syntax/modules.html?highlight=convention#id1>
1211// TODO implement this trait for ExternType lists Export lists
1212pub trait ExternFilterable {
1213 fn funcs(self) -> impl Iterator<Item = FuncAddr>;
1214 fn tables(self) -> impl Iterator<Item = TableAddr>;
1215 fn mems(self) -> impl Iterator<Item = MemAddr>;
1216 fn globals(self) -> impl Iterator<Item = GlobalAddr>;
1217}
1218
1219impl<'a, I> ExternFilterable for I
1220where
1221 I: Iterator<Item = &'a ExternVal>,
1222{
1223 fn funcs(self) -> impl Iterator<Item = FuncAddr> {
1224 self.filter_map(|extern_val| extern_val.as_func())
1225 }
1226
1227 fn tables(self) -> impl Iterator<Item = TableAddr> {
1228 self.filter_map(|extern_val| extern_val.as_table())
1229 }
1230
1231 fn mems(self) -> impl Iterator<Item = MemAddr> {
1232 self.filter_map(|extern_val| extern_val.as_mem())
1233 }
1234
1235 fn globals(self) -> impl Iterator<Item = GlobalAddr> {
1236 self.filter_map(|extern_val| extern_val.as_global())
1237 }
1238}
1239
1240/// Represents a successful, possibly fueled instantiation of a module.
1241pub struct InstantiationOutcome {
1242 /// contains the store address of the module that has successfully instantiated.
1243 pub module_addr: ModuleAddr,
1244 /// contains `Some(remaining_fuel)` if instantiation was fuel-metered and `None` otherwise.
1245 pub maybe_remaining_fuel: Option<u32>,
1246}