Next Generation WASM Microkernel Operating System
wasm os rust microkernel
at trap_handler 494 lines 17 kB view raw
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}