Next Generation WASM Microkernel Operating System
1// Copyright 2025 Jonas Kruckenberg
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8use crate::wasm::func::{HostFunc, IntoFunc, WasmParams, WasmResults};
9use crate::wasm::indices::VMSharedTypeIndex;
10use crate::wasm::store::StoreOpaque;
11use crate::wasm::translate::EntityType;
12use crate::wasm::types::{GlobalType, MemoryType, TableType, TagType};
13use crate::wasm::vm::{ConstExprEvaluator, Imports};
14use crate::wasm::{Engine, Extern, Func, Global, Instance, Memory, Module, Store, Table, Tag};
15use alloc::sync::Arc;
16use alloc::vec::Vec;
17use anyhow::{Context, bail, format_err};
18use core::marker::PhantomData;
19use hashbrown::HashMap;
20use hashbrown::hash_map::Entry;
21
22/// A dynamic linker for WebAssembly modules.
23#[derive(Debug)]
24pub struct Linker<T> {
25 engine: Engine,
26 string2idx: HashMap<Arc<str>, usize>,
27 strings: Vec<Arc<str>>,
28 map: HashMap<ImportKey, Definition>,
29 _m: PhantomData<T>,
30}
31
32#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
33struct ImportKey {
34 name: usize,
35 module: usize,
36}
37
38#[derive(Debug, Clone)]
39pub(super) enum Definition {
40 Func(Func, VMSharedTypeIndex),
41 HostFunc(Arc<HostFunc>, VMSharedTypeIndex),
42 Global(Global, GlobalType),
43 // Note that tables and memories store not only the original type
44 // information but additionally the current size of the table/memory, as
45 // this is used during linking since the min size specified in the type may
46 // no longer be the current size of the table/memory.
47 Table(Table, TableType),
48 Memory(Memory, MemoryType),
49 Tag(Tag, TagType),
50}
51
52impl<T> Linker<T> {
53 /// Create a new `Linker`.
54 ///
55 /// This linker is scoped to the provided engine and cannot be used to link modules from other engines.
56 pub fn new(engine: &Engine) -> Self {
57 Self {
58 engine: engine.clone(),
59 string2idx: HashMap::new(),
60 strings: Vec::new(),
61 map: HashMap::new(),
62 _m: PhantomData,
63 }
64 }
65
66 pub fn engine(&self) -> &Engine {
67 &self.engine
68 }
69
70 pub fn define(
71 &mut self,
72 store: &mut StoreOpaque,
73 module: &str,
74 name: &str,
75 def: impl Into<Extern>,
76 ) -> crate::Result<&mut Self> {
77 let key = self.import_key(module, Some(name));
78 self.insert(key, Definition::new(store, def.into()))?;
79 Ok(self)
80 }
81
82 /// Attempt to retrieve a definition from this linker.
83 pub fn get(&self, store: &mut StoreOpaque, module: &str, name: &str) -> Option<Extern> {
84 // Safety: TODO
85 Some(unsafe { self._get(module, name)?.to_extern(store) })
86 }
87
88 fn _get(&self, module: &str, name: &str) -> Option<&Definition> {
89 let key = ImportKey {
90 module: *self.string2idx.get(module)?,
91 name: *self.string2idx.get(name)?,
92 };
93 self.map.get(&key)
94 }
95
96 /// Alias all exports of `module` under the name `as_module`.
97 ///
98 /// # Errors
99 ///
100 /// TODO
101 pub fn alias_module(&mut self, module: &str, as_module: &str) -> crate::Result<&mut Self> {
102 let module = self.intern_str(module);
103 let as_module = self.intern_str(as_module);
104 let items = self
105 .map
106 .iter()
107 .filter(|(key, _def)| key.module == module)
108 .map(|(key, def)| (key.name, def.clone()))
109 .collect::<Vec<_>>();
110 for (name, item) in items {
111 self.insert(
112 ImportKey {
113 module: as_module,
114 name,
115 },
116 item,
117 )?;
118 }
119 Ok(self)
120 }
121
122 /// Define all exports of the provided `instance` under the module name `module_name`.
123 ///
124 /// # Errors
125 ///
126 /// TODO
127 pub fn define_instance(
128 &mut self,
129 store: &mut StoreOpaque,
130 module_name: &str,
131 instance: Instance,
132 ) -> crate::Result<&mut Self> {
133 let exports = instance
134 .exports(store)
135 .map(|e| (self.import_key(module_name, Some(e.name)), e.definition))
136 .collect::<Vec<_>>(); // TODO can we somehow get rid of this?
137
138 for (key, ext) in exports {
139 self.insert(key, Definition::new(store, ext))?;
140 }
141
142 Ok(self)
143 }
144
145 /// Instantiate the provided `module`.
146 ///
147 /// This step resolve the modules imports using definitions from this linker, then pass them
148 /// on to `Instance::new_unchecked` for instantiation.
149 ///
150 /// Each import of module will be looked up in this Linker and must have previously been defined.
151 ///
152 /// # Errors
153 ///
154 /// TODO
155 ///
156 /// # Panics
157 ///
158 /// TODO
159 pub fn instantiate(
160 &self,
161 store: &mut Store<T>,
162 const_eval: &mut ConstExprEvaluator,
163 module: &Module,
164 ) -> crate::Result<Instance> {
165 let mut imports = Imports::with_capacity_for(module.translated());
166
167 for import in module.imports() {
168 let def = self._get(&import.module, &import.name).with_context(|| {
169 let type_ = match import.ty {
170 EntityType::Function(_) => "function",
171 EntityType::Table(_) => "table",
172 EntityType::Memory(_) => "memory",
173 EntityType::Global(_) => "global",
174 EntityType::Tag(_) => "tag",
175 };
176
177 format_err!("Missing {type_} import {}::{}", import.module, import.name)
178 })?;
179
180 match (def, &import.ty) {
181 (Definition::Func(func, _actual), EntityType::Function(_expected)) => {
182 imports
183 .functions
184 .push(func.as_vmfunction_import(store, module));
185 }
186 (Definition::HostFunc(func, _actual), EntityType::Function(_expected)) => {
187 let func = func.clone().to_func(store);
188 imports
189 .functions
190 .push(func.as_vmfunction_import(store, module));
191 }
192 (Definition::Table(table, _actual), EntityType::Table(_expected)) => {
193 imports.tables.push(table.as_vmtable_import(store));
194 }
195 (Definition::Memory(memory, _actual), EntityType::Memory(_expected)) => {
196 imports.memories.push(memory.as_vmmemory_import(store));
197 }
198 (Definition::Global(global, _actual), EntityType::Global(_expected)) => {
199 imports.globals.push(global.as_vmglobal_import(store));
200 }
201 (Definition::Tag(tag, _actual), EntityType::Tag(_expected)) => {
202 imports.tags.push(tag.as_vmtag_import(store));
203 }
204 _ => panic!("mismatched import type"),
205 }
206 }
207
208 // Safety: we have typechecked the imports above.
209 unsafe { Instance::new_unchecked(store, const_eval, module.clone(), imports) }
210 }
211
212 pub fn func_wrap<Params, Results>(
213 &mut self,
214 module: &str,
215 name: &str,
216 func: impl IntoFunc<T, Params, Results>,
217 ) -> crate::Result<&mut Self>
218 where
219 Params: WasmParams,
220 Results: WasmResults,
221 {
222 let (func, ty) = HostFunc::wrap(self.engine(), func);
223
224 let key = self.import_key(module, Some(name));
225 self.insert(key, Definition::HostFunc(Arc::new(func), ty.type_index()))?;
226
227 Ok(self)
228 }
229
230 fn insert(&mut self, key: ImportKey, item: Definition) -> crate::Result<()> {
231 match self.map.entry(key) {
232 Entry::Occupied(_) => {
233 bail!(
234 "Name {}::{} is already defined",
235 self.strings[key.module],
236 self.strings[key.name]
237 );
238 }
239 Entry::Vacant(v) => {
240 v.insert(item);
241 }
242 }
243
244 Ok(())
245 }
246
247 fn import_key(&mut self, module: &str, name: Option<&str>) -> ImportKey {
248 ImportKey {
249 module: self.intern_str(module),
250 name: name.map_or(usize::MAX, |name| self.intern_str(name)),
251 }
252 }
253
254 fn intern_str(&mut self, string: &str) -> usize {
255 if let Some(idx) = self.string2idx.get(string) {
256 return *idx;
257 }
258 let string: Arc<str> = string.into();
259 let idx = self.strings.len();
260 self.strings.push(string.clone());
261 self.string2idx.insert(string, idx);
262 idx
263 }
264}
265
266impl Definition {
267 fn new(store: &StoreOpaque, item: Extern) -> Definition {
268 match item {
269 Extern::Func(f) => Definition::Func(f, f.type_index(store)),
270 Extern::Table(t) => Definition::Table(t, t.ty(store)),
271 Extern::Memory(m) => Definition::Memory(m, m.ty(store)),
272 Extern::Global(g) => Definition::Global(g, g.ty(store)),
273 Extern::Tag(t) => Definition::Tag(t, t.ty(store)),
274 }
275 }
276
277 unsafe fn to_extern(&self, store: &mut StoreOpaque) -> Extern {
278 match self {
279 Definition::Func(f, _) => Extern::Func(*f),
280 Definition::HostFunc(f, _) => Extern::Func(f.clone().to_func(store)),
281 Definition::Global(g, _) => Extern::Global(*g),
282 Definition::Table(t, _) => Extern::Table(*t),
283 Definition::Memory(m, _) => Extern::Memory(*m),
284 Definition::Tag(t, _) => Extern::Tag(*t),
285 }
286 }
287}