Skip to content

Commit f0971c9

Browse files
cemonemwucke13
authored andcommitted
refactor: export registry as a stand-in for linker
entities such as host functions and globals, tables, and memories without a native wasm module where they are defined are still referred within native wasm modules as imports with a module name, even though that module might not exist. Previous implementation necessitated existence of owning modules for these external entities. This implementation inspired by reference wasm interpreter removes that requirement. Signed-off-by: Cem Onem <cem.oenem@dlr.de>
1 parent f927e5c commit f0971c9

File tree

4 files changed

+131
-119
lines changed

4 files changed

+131
-119
lines changed

src/execution/function_ref.rs

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1+
use alloc::borrow::ToOwned;
12
use alloc::vec::Vec;
23

34
use crate::execution::{hooks::HookSet, value::InteropValueList, RuntimeInstance};
4-
use crate::{
5-
Error, ExportInst, ExternVal, Result as CustomResult, RuntimeError, Store, ValType, Value,
6-
};
5+
use crate::{Error, ExternVal, Result as CustomResult, RuntimeError, Store, ValType, Value};
76

87
pub struct FunctionRef {
98
pub func_addr: usize,
@@ -17,26 +16,19 @@ impl FunctionRef {
1716
) -> CustomResult<Self> {
1817
// https://webassembly.github.io/spec/core/appendix/embedding.html#module-instances
1918
// inspired by instance_export
20-
let module_addr = *store
21-
.module_names
22-
.get(module_name)
23-
.ok_or(Error::RuntimeError(RuntimeError::ModuleNotFound))?;
24-
Ok(Self {
25-
func_addr: store.modules[module_addr]
26-
.exports
27-
.iter()
28-
.find_map(|ExportInst { name, value }| {
29-
if *name == function_name {
30-
match value {
31-
ExternVal::Func(func_addr) => Some(*func_addr),
32-
_ => None,
33-
}
34-
} else {
35-
None
36-
}
37-
})
38-
.ok_or(Error::RuntimeError(RuntimeError::FunctionNotFound))?,
39-
})
19+
let extern_val = store
20+
.registry
21+
.lookup(
22+
module_name.to_owned().into(),
23+
function_name.to_owned().into(),
24+
)
25+
.map_err(|_| Error::RuntimeError(RuntimeError::FunctionNotFound))?;
26+
match extern_val {
27+
ExternVal::Func(func_addr) => Ok(Self {
28+
func_addr: *func_addr,
29+
}),
30+
_ => Err(Error::RuntimeError(RuntimeError::FunctionNotFound)),
31+
}
4032
}
4133

4234
pub fn invoke<

src/execution/store.rs

Lines changed: 85 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::execution::value::{Ref, Value};
1616
use crate::execution::{run_const_span, Stack};
1717
use crate::value::FuncAddr;
1818
use crate::{Error, Limits, RefType, RuntimeError, ValidationInfo};
19-
use alloc::borrow::ToOwned;
19+
use alloc::borrow::{Cow, ToOwned};
2020
use alloc::collections::btree_map::BTreeMap;
2121
use alloc::string::String;
2222
use alloc::vec;
@@ -44,7 +44,15 @@ pub struct Store<'b> {
4444
pub tables: Vec<TableInst>,
4545
pub elements: Vec<ElemInst>,
4646
pub modules: Vec<ModuleInst<'b>>,
47+
48+
// fields outside of the spec but are convenient are below
49+
50+
// kv pair for module names and module "addresses" (that make sense with `Self.modules[module_addr]`)
4751
pub module_names: BTreeMap<String, usize>,
52+
53+
// all visible exports and entities added by hand or module instantiation by the interpreter
54+
// currently, all of the exports of an instantiated module is made visible (this is outside of spec)
55+
pub registry: Registry,
4856
}
4957

5058
impl<'b> Store<'b> {
@@ -76,28 +84,10 @@ impl<'b> Store<'b> {
7684
import_desc
7785
);
7886
let import_extern_type = import_desc.extern_type(validation_info)?;
79-
let exporting_module = self
80-
.modules
81-
.get(
82-
*self
83-
.module_names
84-
.get(exporting_module_name)
85-
.ok_or(Error::RuntimeError(RuntimeError::ModuleNotFound))?,
86-
)
87-
.ok_or(Error::RuntimeError(RuntimeError::ModuleNotFound))?;
88-
89-
let export_extern_val_candidate = *exporting_module
90-
.exports
91-
.iter()
92-
.find_map(
93-
|ExportInst {
94-
name: export_name,
95-
value: export_extern_val,
96-
}| {
97-
(import_name == export_name).then_some(export_extern_val)
98-
},
99-
)
100-
.ok_or(Error::UnknownImport)?;
87+
let export_extern_val_candidate = *self.registry.lookup(
88+
exporting_module_name.clone().into(),
89+
import_name.clone().into(),
90+
)?;
10191
trace!("export candidate found: {:?}", export_extern_val_candidate);
10292
if !export_extern_val_candidate
10393
.extern_type(self)?
@@ -121,7 +111,7 @@ impl<'b> Store<'b> {
121111
global_addrs: extern_vals.iter().globals().collect(),
122112
elem_addrs: Vec::new(),
123113
data_addrs: Vec::new(),
124-
exports: Vec::new(),
114+
exports: BTreeMap::new(),
125115
wasm_bytecode: validation_info.wasm,
126116
sidetable: validation_info.sidetable.clone(),
127117
name: name.to_owned(),
@@ -252,7 +242,7 @@ impl<'b> Store<'b> {
252242
module_inst.global_addrs.extend(global_addrs);
253243

254244
// allocation: step 18,19
255-
let export_insts = module
245+
let export_insts: BTreeMap<String, ExternVal> = module
256246
.exports
257247
.iter()
258248
.map(|Export { name, desc }| {
@@ -268,10 +258,7 @@ impl<'b> Store<'b> {
268258
ExternVal::Global(module_inst.global_addrs[*global_idx])
269259
}
270260
};
271-
ExportInst {
272-
name: String::from(name),
273-
value,
274-
}
261+
(String::from(name), value)
275262
})
276263
.collect();
277264

@@ -284,10 +271,16 @@ impl<'b> Store<'b> {
284271

285272
// allocation: end
286273

274+
// register module exports, this is outside of the spec
275+
self.registry
276+
.register_module(name.to_owned().into(), &module_inst)?;
277+
287278
// instantiation step 11 end: module_inst properly allocated after this point.
288279
// TODO: it is too hard with our codebase to do the following steps without adding the module to the store
289280
let current_module_idx = &self.modules.len();
290281
self.modules.push(module_inst);
282+
283+
// keep module names, this is outside of the spec
291284
self.module_names
292285
.insert(String::from(name), *current_module_idx);
293286

@@ -764,15 +757,10 @@ impl<'b> Store<'b> {
764757
self.modules
765758
.get(module_addr)?
766759
.exports
767-
.iter()
768-
.find_map(|ExportInst { name, value }| {
769-
if name != function_name {
770-
return None;
771-
};
772-
match value {
773-
ExternVal::Func(func_addr) => Some(*func_addr),
774-
_ => None,
775-
}
760+
.get(function_name)
761+
.and_then(|value| match value {
762+
ExternVal::Func(func_addr) => Some(*func_addr),
763+
_ => None,
776764
})
777765
}
778766

@@ -1060,13 +1048,6 @@ where
10601048
}
10611049
}
10621050

1063-
///<https://webassembly.github.io/spec/core/exec/runtime.html#export-instances>
1064-
#[derive(Debug)]
1065-
pub struct ExportInst {
1066-
pub name: String,
1067-
pub value: ExternVal,
1068-
}
1069-
10701051
///<https://webassembly.github.io/spec/core/exec/runtime.html#module-instances>
10711052
#[derive(Debug)]
10721053
pub struct ModuleInst<'b> {
@@ -1077,15 +1058,69 @@ pub struct ModuleInst<'b> {
10771058
pub global_addrs: Vec<usize>,
10781059
pub elem_addrs: Vec<usize>,
10791060
pub data_addrs: Vec<usize>,
1080-
pub exports: Vec<ExportInst>,
1061+
///<https://webassembly.github.io/spec/core/exec/runtime.html#export-instances>
1062+
/// matches the list of ExportInst structs in the spec, however the spec never uses the name attribute
1063+
/// except during linking, which is up to the embedder to implement.
1064+
/// therefore this is a map data structure instead.
1065+
pub exports: BTreeMap<String, ExternVal>,
10811066

10821067
// TODO the bytecode is not in the spec, but required for re-parsing
10831068
pub wasm_bytecode: &'b [u8],
10841069

10851070
// sidetable is not in the spec, but required for control flow
1086-
pub sidetable: Sidetable,
1071+
pub sidetable: Sidetable
1072+
}
1073+
1074+
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord)]
1075+
struct ImportKey {
1076+
module_name: Cow<'static, str>,
1077+
name: Cow<'static, str>,
1078+
}
1079+
#[derive(Default, Debug)]
1080+
pub struct Registry(BTreeMap<ImportKey, ExternVal>);
1081+
1082+
impl Registry {
1083+
pub fn register(
1084+
&mut self,
1085+
module_name: Cow<'static, str>,
1086+
name: Cow<'static, str>,
1087+
extern_val: ExternVal,
1088+
) -> Result<(), Error> {
1089+
if self
1090+
.0
1091+
.insert(ImportKey { module_name, name }, extern_val)
1092+
.is_some()
1093+
{
1094+
return Err(Error::InvalidImportType);
1095+
}
1096+
1097+
Ok(())
1098+
}
10871099

1088-
// TODO name field is not in the spec but used by the testsuite crate, might need to be refactored out
1089-
// this data is unfortunately duplicated within store.module_names kv store.
1090-
pub name: String,
1100+
pub fn lookup(
1101+
&self,
1102+
module_name: Cow<'static, str>,
1103+
name: Cow<'static, str>,
1104+
) -> Result<&ExternVal, Error> {
1105+
// Note: We cannot do a &str lookup on a [`String`] map key. Thus we have to use `Cow<'static, str> as a key (at least this prevents allocations with static names).
1106+
self.0
1107+
.get(&ImportKey { module_name, name })
1108+
.ok_or(Error::UnknownImport)
1109+
}
1110+
1111+
pub fn register_module(
1112+
&mut self,
1113+
module_name: Cow<'static, str>,
1114+
module_inst: &ModuleInst,
1115+
) -> Result<(), Error> {
1116+
for (entity_name, extern_val) in &module_inst.exports {
1117+
// FIXME this clones module_name. Maybe prevent by using `Cow<'static, Arc<str>>`.
1118+
self.register(
1119+
module_name.clone(),
1120+
Cow::Owned(entity_name.clone()),
1121+
*extern_val,
1122+
)?;
1123+
}
1124+
Ok(())
1125+
}
10911126
}

tests/linker.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ pub fn compile_simple_import() {
4141
// let mut instance =
4242
// RuntimeInstance::new_named("base", &validation_info_base).expect("instantiation failed");
4343

44-
let func_idx = store.lookup_function("base", "get_three").unwrap();
44+
let func_addr = match store.registry.lookup("base".into(), "get_three".into()).unwrap() {
45+
ExternVal::Func(func_addr) => *func_addr,
46+
_ => panic!("this entity is not a function"),
47+
};
4548

4649
println!("{:#?}", store.invoke::<(), i32>(func_idx, ()).unwrap());
4750

tests/specification/run.rs

Lines changed: 27 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use std::panic::UnwindSafe;
77
use itertools::enumerate;
88
use log::debug;
99
use wasm::function_ref::FunctionRef;
10-
use wasm::ExportInst;
1110
use wasm::RuntimeError;
1211
use wasm::Value;
1312
use wasm::{validate, RuntimeInstance};
@@ -349,11 +348,14 @@ pub fn run_spec_test(filepath: &str) -> WastTestReport {
349348
let module_addr = match modulee {
350349
None => store.modules.len() - 1,
351350
Some(id) => {
352-
log::error!("looking for {:?}\n{:?}", id.name(), store.module_names);
351+
log::error!("looking for {:?} in \n{:?}", id.name(), visible_modules);
353352
visible_modules[id.name()]
354353
}
355354
};
356-
store.module_names.insert(String::from(name), module_addr);
355+
store
356+
.registry
357+
.register_module(name.to_owned().into(), &store.modules[module_addr])
358+
.unwrap();
357359
}
358360
wast::WastDirective::AssertUnlinkable {
359361
span,
@@ -464,17 +466,12 @@ pub fn run_spec_test(filepath: &str) -> WastTestReport {
464466

465467
module_inst
466468
.exports
467-
.iter()
468-
.find_map(|ExportInst { name, value }| {
469-
if name != invoke.name {
470-
return None;
471-
}
472-
match value {
473-
wasm::ExternVal::Func(func_addr) => Some(FunctionRef {
474-
func_addr: *func_addr,
475-
}),
476-
_ => None,
477-
}
469+
.get(invoke.name)
470+
.and_then(|value| match value {
471+
wasm::ExternVal::Func(func_addr) => Some(FunctionRef {
472+
func_addr: *func_addr,
473+
}),
474+
_ => None,
478475
})
479476
.ok_or(RuntimeError::FunctionNotFound)
480477
}))
@@ -580,17 +577,12 @@ fn execute_assert_return(
580577

581578
module_inst
582579
.exports
583-
.iter()
584-
.find_map(|ExportInst { name, value }| {
585-
if name != invoke_info.name {
586-
return None;
587-
}
588-
match value {
589-
wasm::ExternVal::Func(func_addr) => Some(FunctionRef {
590-
func_addr: *func_addr,
591-
}),
592-
_ => None,
593-
}
580+
.get(invoke_info.name)
581+
.and_then(|value| match value {
582+
wasm::ExternVal::Func(func_addr) => Some(FunctionRef {
583+
func_addr: *func_addr,
584+
}),
585+
_ => None,
594586
})
595587
.ok_or(RuntimeError::FunctionNotFound)
596588
}))
@@ -631,15 +623,10 @@ fn execute_assert_return(
631623
};
632624
let global_addr = module_inst
633625
.exports
634-
.iter()
635-
.find_map(|ExportInst { name, value }| {
636-
if name != global {
637-
return None;
638-
}
639-
match value {
640-
wasm::ExternVal::Global(global_addr) => Some(*global_addr),
641-
_ => None,
642-
}
626+
.get(global)
627+
.and_then(|value| match value {
628+
wasm::ExternVal::Global(global_addr) => Some(*global_addr),
629+
_ => None,
643630
})
644631
.ok_or(RuntimeError::FunctionNotFound)?; // TODO fix error
645632
Ok(store.globals[global_addr].value)
@@ -688,17 +675,12 @@ fn execute_assert_trap<'a>(
688675

689676
module_inst
690677
.exports
691-
.iter()
692-
.find_map(|ExportInst { name, value }| {
693-
if name != invoke_info.name {
694-
return None;
695-
}
696-
match value {
697-
wasm::ExternVal::Func(func_addr) => Some(FunctionRef {
698-
func_addr: *func_addr,
699-
}),
700-
_ => None,
701-
}
678+
.get(invoke_info.name)
679+
.and_then(|value| match value {
680+
wasm::ExternVal::Func(func_addr) => Some(FunctionRef {
681+
func_addr: *func_addr,
682+
}),
683+
_ => None,
702684
})
703685
.ok_or(RuntimeError::FunctionNotFound)
704686
}))

0 commit comments

Comments
 (0)