Next Generation WASM Microkernel Operating System
wasm
os
rust
microkernel
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
8//! Currently the `VMContext` allocation by field looks like this:
9//!
10//! ```
11//! struct VMContext {
12//! // Fixed-width data comes first so the calculation of the offset of
13//! // these fields is a compile-time constant when using `HostPtr`.
14//! magic: u32,
15//! _padding: u32, //! (On 64-bit systems)
16//! vm_store_context: *const VMStoreContext,
17//! builtin_functions: VmPtr<VMBuiltinFunctionsArray>,
18//! callee: VmPtr<VMFunctionBody>,
19//! epoch_ptr: *mut AtomicU64,
20//! gc_heap_base: *mut u8,
21//! gc_heap_bound: *mut u8,
22//! gc_heap_data: *mut T, //! Collector-specific pointer
23//! type_ids: *const VMSharedTypeIndex,
24//!
25//! // Variable-width fields come after the fixed-width fields above. Place
26//! // memory-related items first as they're some of the most frequently
27//! // accessed items and minimizing their offset in this structure can
28//! // shrink the size of load/store instruction offset immediates on
29//! // platforms like x64 (e.g. fit in an 8-bit offset instead
30//! // of needing a 32-bit offset)
31//! imported_memories: [VMMemoryImport; module.num_imported_memories],
32//! memories: [VmPtr<VMMemoryDefinition>; module.num_defined_memories],
33//! owned_memories: [VMMemoryDefinition; module.num_owned_memories],
34//! imported_functions: [VMFunctionImport; module.num_imported_functions],
35//! imported_tables: [VMTable; module.num_imported_tables],
36//! imported_globals: [VMGlobalImport; module.num_imported_globals],
37//! imported_tags: [VMTagImport; module.num_imported_tags],
38//! tables: [VMTableDefinition; module.num_defined_tables],
39//! globals: [VMGlobalDefinition; module.num_defined_globals],
40//! tags: [VMTagDefinition; module.num_defined_tags],
41//! func_refs: [VMFuncRef; module.num_escaped_funcs],
42//! }
43//! ```
44
45use crate::wasm::indices::{
46 DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, DefinedTagIndex, FuncIndex,
47 FuncRefIndex, GlobalIndex, MemoryIndex, OwnedMemoryIndex, TableIndex, TagIndex,
48};
49use crate::wasm::translate::TranslatedModule;
50use crate::wasm::utils::u8_size_of;
51use crate::wasm::vm::provenance::VmPtr;
52use crate::wasm::vm::vmcontext::{
53 VMFuncRef, VMFunctionImport, VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition,
54 VMMemoryImport, VMTableDefinition, VMTableImport, VMTagDefinition, VMTagImport,
55};
56
57pub struct StaticVMShape;
58
59#[derive(Debug, Clone)]
60pub struct VMShape {
61 /// The number of imported functions in the module.
62 pub num_imported_functions: u32,
63 /// The number of imported tables in the module.
64 pub num_imported_tables: u32,
65 /// The number of imported memories in the module.
66 pub num_imported_memories: u32,
67 /// The number of imported globals in the module.
68 pub num_imported_globals: u32,
69 /// The number of imported tags in the module.
70 pub num_imported_tags: u32,
71 /// The number of defined tables in the module.
72 pub num_defined_tables: u32,
73 /// The number of defined memories in the module.
74 pub num_defined_memories: u32,
75 /// The number of memories owned by the module instance.
76 pub num_owned_memories: u32,
77 /// The number of defined globals in the module.
78 pub num_defined_globals: u32,
79 /// The number of defined tags in the module.
80 pub num_defined_tags: u32,
81 /// The number of escaped functions in the module, the size of the func_refs
82 /// array.
83 pub num_escaped_funcs: u32,
84
85 // precalculated offsets of various member fields
86 imported_functions: u32,
87 imported_tables: u32,
88 imported_memories: u32,
89 imported_globals: u32,
90 imported_tags: u32,
91 defined_tables: u32,
92 defined_memories: u32,
93 owned_memories: u32,
94 defined_globals: u32,
95 defined_tags: u32,
96 defined_func_refs: u32,
97 size: u32,
98}
99
100impl StaticVMShape {
101 #[expect(
102 clippy::cast_possible_truncation,
103 reason = "pointers larger than 255 bytes dont exist"
104 )]
105 const fn ptr_size(&self) -> u8 {
106 size_of::<usize>() as u8
107 }
108
109 /// Return the offset to the `magic` value in this `VMContext`.
110 #[inline]
111 pub const fn vmctx_magic(&self) -> u8 {
112 // This is required by the implementation of `VMContext::instance` and
113 // `VMContext::instance_mut`. If this value changes then those locations
114 // need to be updated.
115 0
116 }
117
118 /// Return the offset to the `VMStoreContext` structure
119 #[inline]
120 pub const fn vmctx_store_context(&self) -> u8 {
121 self.vmctx_magic() + self.ptr_size()
122 }
123
124 /// Return the offset to the `VMBuiltinFunctionsArray` structure
125 #[inline]
126 pub const fn vmctx_builtin_functions(&self) -> u8 {
127 self.vmctx_store_context() + self.ptr_size()
128 }
129
130 /// Return the offset to the `callee` member in this `VMContext`.
131 #[inline]
132 pub const fn vmctx_callee(&self) -> u8 {
133 self.vmctx_builtin_functions() + self.ptr_size()
134 }
135
136 /// Return the offset to the `*const AtomicU64` epoch-counter
137 /// pointer.
138 #[inline]
139 pub const fn vmctx_epoch_ptr(&self) -> u8 {
140 self.vmctx_callee() + self.ptr_size()
141 }
142
143 /// Return the offset to the GC heap base in this `VMContext`.
144 #[inline]
145 pub const fn vmctx_gc_heap_base(&self) -> u8 {
146 self.vmctx_epoch_ptr() + self.ptr_size()
147 }
148
149 /// Return the offset to the GC heap bound in this `VMContext`.
150 #[inline]
151 pub const fn vmctx_gc_heap_bound(&self) -> u8 {
152 self.vmctx_gc_heap_base() + self.ptr_size()
153 }
154
155 /// Return the offset to the `*mut T` collector-specific data.
156 ///
157 /// This is a pointer that different collectors can use however they see
158 /// fit.
159 #[inline]
160 pub const fn vmctx_gc_heap_data(&self) -> u8 {
161 self.vmctx_gc_heap_bound() + self.ptr_size()
162 }
163
164 /// The offset of the `type_ids` array pointer.
165 #[inline]
166 pub const fn vmctx_type_ids_array(&self) -> u8 {
167 self.vmctx_gc_heap_data() + self.ptr_size()
168 }
169
170 /// The end of statically known offsets in `VMContext`.
171 ///
172 /// Data after this is dynamically sized.
173 #[inline]
174 pub const fn vmctx_dynamic_data_start(&self) -> u8 {
175 self.vmctx_type_ids_array() + self.ptr_size()
176 }
177}
178
179impl VMShape {
180 pub fn for_module(ptr_size: u8, module: &TranslatedModule) -> Self {
181 assert_eq!(ptr_size, StaticVMShape.ptr_size());
182
183 let num_owned_memories = module
184 .memories
185 .iter()
186 .skip(module.num_imported_memories as usize)
187 .filter(|p| !p.1.shared)
188 .count()
189 .try_into()
190 .unwrap();
191
192 let mut ret = Self {
193 num_imported_functions: module.num_imported_functions,
194 num_imported_tables: module.num_imported_tables,
195 num_imported_memories: module.num_imported_memories,
196 num_imported_globals: module.num_imported_globals,
197 num_imported_tags: module.num_imported_tags,
198 num_defined_tables: module.num_defined_tables(),
199 num_defined_memories: module.num_defined_memories(),
200 num_owned_memories,
201 num_defined_globals: module.num_defined_globals(),
202 num_defined_tags: module.num_defined_tags(),
203 num_escaped_funcs: module.num_escaped_functions,
204 imported_functions: 0,
205 imported_tables: 0,
206 imported_memories: 0,
207 imported_globals: 0,
208 imported_tags: 0,
209 defined_tables: 0,
210 defined_memories: 0,
211 owned_memories: 0,
212 defined_globals: 0,
213 defined_tags: 0,
214 defined_func_refs: 0,
215 size: 0,
216 };
217
218 // Convenience functions for checked addition and multiplication.
219 // As side effect this reduces binary size by using only a single
220 // `#[track_caller]` location for each function instead of one for
221 // each individual invocation.
222 #[inline]
223 fn cadd(count: u32, size: u32) -> u32 {
224 count.checked_add(size).unwrap()
225 }
226
227 #[inline]
228 fn cmul(count: u32, size: u8) -> u32 {
229 count.checked_mul(u32::from(size)).unwrap()
230 }
231
232 /// Align an offset used in this module to a specific byte-width by rounding up
233 #[inline]
234 fn align(offset: u32, width: u32) -> u32 {
235 offset.div_ceil(width) * width
236 }
237
238 let mut next_field_offset = u32::from(StaticVMShape.vmctx_dynamic_data_start());
239
240 macro_rules! fields {
241 (size($field:ident) = $size:expr, $($rest:tt)*) => {
242 ret.$field = next_field_offset;
243 next_field_offset = cadd(next_field_offset, u32::from($size));
244 fields!($($rest)*);
245 };
246 (align($align:expr), $($rest:tt)*) => {
247 next_field_offset = align(next_field_offset, $align);
248 fields!($($rest)*);
249 };
250 () => {};
251 }
252
253 fields! {
254 size(imported_memories)
255 = cmul(ret.num_imported_memories, u8_size_of::<VMMemoryImport>()),
256 size(defined_memories)
257 = cmul(ret.num_defined_memories, u8_size_of::<VmPtr<VMMemoryDefinition>>()),
258 size(owned_memories)
259 = cmul(ret.num_owned_memories, u8_size_of::<VMMemoryDefinition>()),
260 size(imported_functions)
261 = cmul(ret.num_imported_functions, u8_size_of::<VMFunctionImport>()),
262 size(imported_tables)
263 = cmul(ret.num_imported_tables, u8_size_of::<VMTableImport>()),
264 size(imported_globals)
265 = cmul(ret.num_imported_globals, u8_size_of::<VMGlobalImport>()),
266 size(imported_tags)
267 = cmul(ret.num_imported_tags, u8_size_of::<VMTagImport>()),
268 size(defined_tables)
269 = cmul(ret.num_defined_tables, u8_size_of::<VMTableDefinition>()),
270 align(16),
271 size(defined_globals)
272 = cmul(ret.num_defined_globals, u8_size_of::<VMGlobalDefinition>()),
273 size(defined_tags)
274 = cmul(ret.num_defined_tags, u8_size_of::<VMTagDefinition>()),
275 size(defined_func_refs) = cmul(
276 ret.num_escaped_funcs,
277 u8_size_of::<VMFuncRef>()
278 ),
279 }
280
281 ret.size = next_field_offset;
282
283 ret
284 }
285
286 /// Return the offset to the `magic` value in this `VMContext`.
287 #[inline]
288 pub const fn vmctx_magic(&self) -> u8 {
289 StaticVMShape.vmctx_magic()
290 }
291
292 /// Return the offset to the `VMStoreContext` structure
293 #[inline]
294 pub const fn vmctx_store_context(&self) -> u8 {
295 StaticVMShape.vmctx_store_context()
296 }
297
298 /// Return the offset to the `VMBuiltinFunctionsArray` structure
299 #[inline]
300 pub const fn vmctx_builtin_functions(&self) -> u8 {
301 StaticVMShape.vmctx_builtin_functions()
302 }
303
304 /// Return the offset to the `callee` member in this `VMContext`.
305 #[inline]
306 pub const fn vmctx_callee(&self) -> u8 {
307 StaticVMShape.vmctx_callee()
308 }
309
310 /// Return the offset to the `*const AtomicU64` epoch-counter
311 /// pointer.
312 #[inline]
313 pub const fn vmctx_epoch_ptr(&self) -> u8 {
314 StaticVMShape.vmctx_epoch_ptr()
315 }
316
317 /// Return the offset to the GC heap base in this `VMContext`.
318 #[inline]
319 pub const fn vmctx_gc_heap_base(&self) -> u8 {
320 StaticVMShape.vmctx_gc_heap_base()
321 }
322
323 /// Return the offset to the GC heap bound in this `VMContext`.
324 #[inline]
325 pub const fn vmctx_gc_heap_bound(&self) -> u8 {
326 StaticVMShape.vmctx_gc_heap_bound()
327 }
328
329 /// Return the offset to the `*mut T` collector-specific data.
330 ///
331 /// This is a pointer that different collectors can use however they see
332 /// fit.
333 #[inline]
334 pub const fn vmctx_gc_heap_data(&self) -> u8 {
335 StaticVMShape.vmctx_gc_heap_data()
336 }
337
338 /// The offset of the `type_ids` array pointer.
339 #[inline]
340 pub const fn vmctx_type_ids_array(&self) -> u8 {
341 StaticVMShape.vmctx_type_ids_array()
342 }
343
344 /// The end of statically known offsets in `VMContext`.
345 ///
346 /// Data after this is dynamically sized.
347 #[inline]
348 pub const fn vmctx_dynamic_data_start(&self) -> u8 {
349 StaticVMShape.vmctx_dynamic_data_start()
350 }
351
352 /// The offset of the `imported_functions` array.
353 #[inline]
354 pub fn vmctx_imported_functions_begin(&self) -> u32 {
355 self.imported_functions
356 }
357
358 /// The offset of the `imported_tables` array.
359 #[inline]
360 pub fn vmctx_imported_tables_begin(&self) -> u32 {
361 self.imported_tables
362 }
363
364 /// The offset of the `imported_memories` array.
365 #[inline]
366 pub fn vmctx_imported_memories_begin(&self) -> u32 {
367 self.imported_memories
368 }
369
370 /// The offset of the `imported_globals` array.
371 #[inline]
372 pub fn vmctx_imported_globals_begin(&self) -> u32 {
373 self.imported_globals
374 }
375
376 /// The offset of the `imported_tags` array.
377 #[inline]
378 pub fn vmctx_imported_tags_begin(&self) -> u32 {
379 self.imported_tags
380 }
381
382 /// The offset of the `tables` array.
383 #[inline]
384 pub fn vmctx_tables_begin(&self) -> u32 {
385 self.defined_tables
386 }
387
388 /// The offset of the `memories` array.
389 #[inline]
390 pub fn vmctx_memories_begin(&self) -> u32 {
391 self.defined_memories
392 }
393
394 /// The offset of the `owned_memories` array.
395 #[inline]
396 pub fn vmctx_owned_memories_begin(&self) -> u32 {
397 self.owned_memories
398 }
399
400 /// The offset of the `globals` array.
401 #[inline]
402 pub fn vmctx_globals_begin(&self) -> u32 {
403 self.defined_globals
404 }
405
406 /// The offset of the `tags` array.
407 #[inline]
408 pub fn vmctx_tags_begin(&self) -> u32 {
409 self.defined_tags
410 }
411
412 /// The offset of the `func_refs` array.
413 #[inline]
414 pub fn vmctx_func_refs_begin(&self) -> u32 {
415 self.defined_func_refs
416 }
417
418 #[inline]
419 pub fn vmctx_vmfunction_import(&self, index: FuncIndex) -> u32 {
420 assert!(index.as_u32() < self.num_imported_functions);
421 self.vmctx_imported_functions_begin()
422 + index.as_u32() * u32::from(u8_size_of::<VMFunctionImport>())
423 }
424
425 #[inline]
426 pub fn vmctx_vmtable_import(&self, index: TableIndex) -> u32 {
427 assert!(index.as_u32() < self.num_imported_tables);
428 self.vmctx_imported_tables_begin()
429 + index.as_u32() * u32::from(u8_size_of::<VMTableImport>())
430 }
431
432 #[inline]
433 pub fn vmctx_vmmemory_import(&self, index: MemoryIndex) -> u32 {
434 assert!(index.as_u32() < self.num_imported_memories);
435 self.vmctx_imported_memories_begin()
436 + index.as_u32() * u32::from(u8_size_of::<VMMemoryImport>())
437 }
438
439 #[inline]
440 pub fn vmctx_vmglobal_import(&self, index: GlobalIndex) -> u32 {
441 assert!(index.as_u32() < self.num_defined_globals);
442 self.vmctx_imported_globals_begin()
443 + index.as_u32() * u32::from(u8_size_of::<VMGlobalImport>())
444 }
445
446 #[inline]
447 pub fn vmctx_vmtag_import(&self, index: TagIndex) -> u32 {
448 assert!(index.as_u32() < self.num_imported_tags);
449 self.vmctx_imported_tags_begin() + index.as_u32() * u32::from(u8_size_of::<VMTagImport>())
450 }
451
452 #[inline]
453 pub fn vmctx_vmtable_definition(&self, index: DefinedTableIndex) -> u32 {
454 assert!(index.as_u32() < self.num_defined_tables);
455 self.vmctx_tables_begin() + index.as_u32() * u32::from(u8_size_of::<VMTableDefinition>())
456 }
457
458 #[inline]
459 pub fn vmctx_vmmemory_pointer(&self, index: DefinedMemoryIndex) -> u32 {
460 assert!(index.as_u32() < self.num_defined_memories);
461 self.vmctx_memories_begin()
462 + index.as_u32() * u32::from(u8_size_of::<VmPtr<VMMemoryDefinition>>())
463 }
464
465 #[inline]
466 pub fn vmctx_vmmemory_definition(&self, index: OwnedMemoryIndex) -> u32 {
467 assert!(index.as_u32() < self.owned_memories);
468 self.vmctx_owned_memories_begin()
469 + index.as_u32() * u32::from(u8_size_of::<VMMemoryDefinition>())
470 }
471
472 #[inline]
473 pub fn vmctx_vmglobal_definition(&self, index: DefinedGlobalIndex) -> u32 {
474 assert!(index.as_u32() < self.defined_globals);
475 self.vmctx_globals_begin() + index.as_u32() * u32::from(u8_size_of::<VMGlobalDefinition>())
476 }
477
478 #[inline]
479 pub fn vmctx_vmtag_definition(&self, index: DefinedTagIndex) -> u32 {
480 assert!(index.as_u32() < self.defined_tags);
481 self.vmctx_tags_begin() + index.as_u32() * u32::from(u8_size_of::<VMTagDefinition>())
482 }
483
484 #[inline]
485 pub fn vmctx_vmfunc_ref(&self, index: FuncRefIndex) -> u32 {
486 assert!(index.as_u32() < self.defined_func_refs);
487 self.vmctx_func_refs_begin() + index.as_u32() * u32::from(u8_size_of::<VMFuncRef>())
488 }
489
490 #[inline]
491 pub fn size_of_vmctx(&self) -> u32 {
492 self.size
493 }
494}