Next Generation WASM Microkernel Operating System
at trap_handler 424 lines 14 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 8mod compile_key; 9mod compiled_function; 10 11use crate::wasm::Engine; 12use crate::wasm::builtins::BuiltinFunctionIndex; 13use crate::wasm::compile::compiled_function::{RelocationTarget, TrapInfo}; 14use crate::wasm::indices::{DefinedFuncIndex, ModuleInternedTypeIndex}; 15use crate::wasm::translate::{ 16 FunctionBodyData, ModuleTranslation, ModuleTypes, TranslatedModule, WasmFuncType, 17}; 18use crate::wasm::trap::TrapKind; 19use crate::wasm::vm::{CodeObject, MmapVec}; 20use alloc::boxed::Box; 21use alloc::collections::BTreeMap; 22use alloc::format; 23use alloc::string::String; 24use alloc::vec::Vec; 25use compile_key::CompileKey; 26pub use compiled_function::CompiledFunction; 27use cranelift_codegen::control::ControlPlane; 28use cranelift_entity::{EntitySet, PrimaryMap}; 29use hashbrown::HashSet; 30 31/// Namespace corresponding to wasm functions, the index is the index of the 32/// defined function that's being referenced. 33pub const NS_WASM_FUNC: u32 = 0; 34 35/// Namespace for builtin function trampolines. The index is the index of the 36/// builtin that's being referenced. 37pub const NS_BUILTIN: u32 = 1; 38 39pub trait Compiler { 40 /// Returns the target triple this compiler is configured for 41 fn triple(&self) -> &target_lexicon::Triple; 42 43 fn text_section_builder( 44 &self, 45 capacity: usize, 46 ) -> Box<dyn cranelift_codegen::TextSectionBuilder>; 47 48 /// Compile the translated WASM function `index` within `translation`. 49 fn compile_function( 50 &self, 51 translation: &ModuleTranslation<'_>, 52 index: DefinedFuncIndex, 53 data: FunctionBodyData<'_>, 54 types: &ModuleTypes, 55 ) -> crate::Result<CompiledFunction>; 56 57 /// Compile a trampoline for calling the `index` WASM function through the 58 /// array-calling convention used by host code to call into WASM. 59 fn compile_array_to_wasm_trampoline( 60 &self, 61 translation: &ModuleTranslation<'_>, 62 types: &ModuleTypes, 63 index: DefinedFuncIndex, 64 ) -> crate::Result<CompiledFunction>; 65 66 // Compile a trampoline for calling the a(host-defined) function through the array 67 /// calling convention used to by WASM to call into host code. 68 fn compile_wasm_to_array_trampoline( 69 &self, 70 wasm_func_ty: &WasmFuncType, 71 ) -> crate::Result<CompiledFunction>; 72 73 /// Compile a trampoline for calling the `index` builtin function from WASM. 74 fn compile_wasm_to_builtin( 75 &self, 76 index: BuiltinFunctionIndex, 77 ) -> crate::Result<CompiledFunction>; 78} 79 80/// A position within an original source file, 81#[derive(Clone, Copy, Debug, PartialEq, Eq)] 82pub struct FilePos(u32); 83 84impl Default for FilePos { 85 fn default() -> Self { 86 Self(u32::MAX) 87 } 88} 89 90impl FilePos { 91 pub(crate) fn new(pos: u32) -> Self { 92 Self(pos) 93 } 94 95 pub fn file_offset(self) -> Option<u32> { 96 if self.0 == u32::MAX { 97 None 98 } else { 99 Some(self.0) 100 } 101 } 102} 103 104#[derive(Debug)] 105pub struct CompiledFunctionInfo { 106 /// The [`FunctionLoc`] indicating the location of this function in the text 107 /// section of the compilation artifact. 108 pub wasm_func_loc: FunctionLoc, 109 /// A trampoline for host callers (e.g. `Func::wrap`) calling into this function (if needed). 110 pub array_to_wasm_trampoline: Option<FunctionLoc>, 111 pub start_srcloc: FilePos, 112} 113 114/// Description of where a function is located in the text section of a 115/// compiled image. 116#[derive(Debug, Copy, Clone)] 117pub struct FunctionLoc { 118 /// The byte offset from the start of the text section where this 119 /// function starts. 120 pub start: u32, 121 /// The byte length of this function's function body. 122 pub length: u32, 123} 124 125pub type CompileInput<'a> = 126 Box<dyn FnOnce(&dyn Compiler) -> crate::Result<CompileOutput> + Send + 'a>; 127 128pub struct CompileInputs<'a>(Vec<CompileInput<'a>>); 129 130impl<'a> CompileInputs<'a> { 131 pub fn from_module( 132 translation: &'a ModuleTranslation, 133 types: &'a ModuleTypes, 134 function_body_data: PrimaryMap<DefinedFuncIndex, FunctionBodyData<'a>>, 135 ) -> Self { 136 let mut inputs: Vec<CompileInput> = Vec::new(); 137 138 for (def_func_index, function_body_data) in function_body_data { 139 // push the "main" function compilation job 140 inputs.push(Box::new(move |compiler| { 141 let symbol = format!("wasm[0]::function[{}]", def_func_index.as_u32()); 142 tracing::debug!("compiling {symbol}..."); 143 144 let function = compiler.compile_function( 145 translation, 146 def_func_index, 147 function_body_data, 148 types, 149 )?; 150 151 Ok(CompileOutput { 152 key: CompileKey::wasm_function(def_func_index), 153 function, 154 symbol, 155 }) 156 })); 157 158 // Compile a host->wasm trampoline for every function that are flags as "escaping" 159 // and could therefore theoretically be called by native code. 160 let func_index = translation.module.func_index(def_func_index); 161 if translation.module.functions[func_index].is_escaping() { 162 inputs.push(Box::new(move |compiler| { 163 let symbol = 164 format!("wasm[0]::array_to_wasm_trampoline[{}]", func_index.as_u32()); 165 tracing::debug!("compiling {symbol}..."); 166 167 let function = compiler.compile_array_to_wasm_trampoline( 168 translation, 169 types, 170 def_func_index, 171 )?; 172 173 Ok(CompileOutput { 174 key: CompileKey::array_to_wasm_trampoline(def_func_index), 175 function, 176 symbol, 177 }) 178 })); 179 } 180 } 181 182 let mut trampoline_types_seen = HashSet::new(); 183 for (_func_type_index, trampoline_type_index) in types.trampoline_types() { 184 let is_new = trampoline_types_seen.insert(trampoline_type_index); 185 if !is_new { 186 continue; 187 } 188 189 let trampoline_func_ty = types 190 .get_wasm_type(trampoline_type_index) 191 .unwrap() 192 .unwrap_func(); 193 inputs.push(Box::new(move |compiler| { 194 let symbol = format!( 195 "signatures[{}]::wasm_to_array_trampoline", 196 trampoline_type_index.as_u32() 197 ); 198 tracing::debug!("compiling {symbol}..."); 199 200 let function = compiler.compile_wasm_to_array_trampoline(trampoline_func_ty)?; 201 202 Ok(CompileOutput { 203 key: CompileKey::wasm_to_array_trampoline(trampoline_type_index), 204 function, 205 symbol, 206 }) 207 })); 208 } 209 210 Self(inputs) 211 } 212 213 pub fn compile(self, compiler: &dyn Compiler) -> crate::Result<UnlinkedCompileOutputs> { 214 let mut outputs = self 215 .0 216 .into_iter() 217 .map(|f| f(compiler)) 218 .collect::<Result<Vec<_>, _>>()?; 219 220 compile_required_builtin_trampolines(compiler, &mut outputs)?; 221 222 let mut indices: BTreeMap<u32, BTreeMap<CompileKey, usize>> = BTreeMap::new(); 223 for (index, output) in outputs.iter().enumerate() { 224 indices 225 .entry(output.key.kind()) 226 .or_default() 227 .insert(output.key, index); 228 } 229 230 Ok(UnlinkedCompileOutputs { indices, outputs }) 231 } 232} 233 234fn compile_required_builtin_trampolines( 235 compiler: &dyn Compiler, 236 outputs: &mut Vec<CompileOutput>, 237) -> crate::Result<()> { 238 let mut builtins = EntitySet::new(); 239 let mut new_jobs: Vec<CompileInput<'_>> = Vec::new(); 240 241 let builtin_indices = outputs 242 .iter() 243 .flat_map(|output| output.function.relocations()) 244 .filter_map(|reloc| match reloc.target { 245 RelocationTarget::Wasm(_) => None, 246 RelocationTarget::Builtin(index) => Some(index), 247 }); 248 249 let compile_builtin = |builtin: BuiltinFunctionIndex| -> CompileInput { 250 Box::new(move |compiler: &dyn Compiler| { 251 let symbol = format!("wasm_builtin_{}", builtin.name()); 252 tracing::debug!("compiling {symbol}..."); 253 Ok(CompileOutput { 254 key: CompileKey::wasm_to_builtin_trampoline(builtin), 255 symbol, 256 function: compiler.compile_wasm_to_builtin(builtin)?, 257 }) 258 }) 259 }; 260 261 for index in builtin_indices { 262 if builtins.insert(index) { 263 new_jobs.push(compile_builtin(index)); 264 } 265 } 266 267 outputs.extend( 268 new_jobs 269 .into_iter() 270 .map(|f| f(compiler)) 271 .collect::<Result<Vec<_>, _>>()?, 272 ); 273 274 Ok(()) 275} 276 277#[derive(Debug)] 278pub struct CompileOutput { 279 pub key: CompileKey, 280 pub function: CompiledFunction, 281 pub symbol: String, 282} 283 284#[derive(Debug)] 285pub struct UnlinkedCompileOutputs { 286 indices: BTreeMap<u32, BTreeMap<CompileKey, usize>>, 287 outputs: Vec<CompileOutput>, 288} 289 290impl UnlinkedCompileOutputs { 291 pub fn link_and_finish( 292 mut self, 293 engine: &Engine, 294 module: &TranslatedModule, 295 do_mmap: impl FnOnce(Vec<u8>) -> crate::Result<MmapVec<u8>>, 296 ) -> crate::Result<CodeObject> { 297 let mut text_builder = engine.compiler().text_section_builder(self.outputs.len()); 298 let mut ctrl_plane = ControlPlane::default(); 299 let mut locs = Vec::new(); // TODO get a capacity value for this 300 let mut traps = TrapsBuilder::default(); 301 302 for output in &self.outputs { 303 let body = output.function.buffer(); 304 let alignment = output.function.alignment(); 305 let body_len = body.len() as u64; 306 let off = text_builder.append(true, body, alignment, &mut ctrl_plane); 307 308 tracing::debug!( 309 "Function {}: {off:#x}..{:#x} align: {alignment}", 310 output.symbol, 311 off + body_len 312 ); 313 314 for r in output.function.relocations() { 315 let target = match r.target { 316 RelocationTarget::Wasm(callee_index) => { 317 let def_func_index = module.defined_func_index(callee_index).unwrap(); 318 319 self.indices[&CompileKey::WASM_FUNCTION_KIND] 320 [&CompileKey::wasm_function(def_func_index)] 321 } 322 RelocationTarget::Builtin(index) => { 323 self.indices[&CompileKey::WASM_TO_BUILTIN_TRAMPOLINE_KIND] 324 [&CompileKey::wasm_to_builtin_trampoline(index)] 325 } 326 }; 327 328 // Ensure that we actually resolved the relocation 329 let resolved = 330 text_builder.resolve_reloc(off + u64::from(r.offset), r.kind, r.addend, target); 331 debug_assert!(resolved); 332 } 333 334 let loc = FunctionLoc { 335 start: u32::try_from(off).unwrap(), 336 length: u32::try_from(body_len).unwrap(), 337 }; 338 339 traps.push_traps(loc, output.function.traps()); 340 locs.push(loc); 341 } 342 343 let wasm_functions = self 344 .indices 345 .remove(&CompileKey::WASM_FUNCTION_KIND) 346 .unwrap_or_default() 347 .into_iter(); 348 349 let mut host_to_wasm_trampolines = self 350 .indices 351 .remove(&CompileKey::ARRAY_TO_WASM_TRAMPOLINE_KIND) 352 .unwrap_or_default(); 353 354 let funcs = wasm_functions 355 .map(|(key, index)| { 356 let host_to_wasm_trampoline_key = 357 CompileKey::array_to_wasm_trampoline(DefinedFuncIndex::from_u32(key.index)); 358 let host_to_wasm_trampoline = host_to_wasm_trampolines 359 .remove(&host_to_wasm_trampoline_key) 360 .map(|index| locs[index]); 361 362 CompiledFunctionInfo { 363 start_srcloc: self.outputs[index].function.metadata().start_srcloc, 364 wasm_func_loc: locs[index], 365 array_to_wasm_trampoline: host_to_wasm_trampoline, 366 } 367 }) 368 .collect(); 369 370 // collect up all the WASM->host trampolines we compiled 371 let wasm_to_host_trampolines: Vec<_> = self 372 .indices 373 .remove(&CompileKey::WASM_TO_ARRAY_TRAMPOLINE_KIND) 374 .unwrap_or_default() 375 .into_iter() 376 .map(|(key, index)| (ModuleInternedTypeIndex::from_u32(key.index), locs[index])) 377 .collect(); 378 379 let (trap_offsets, traps) = traps.finish(); 380 let mmap_vec = do_mmap(text_builder.finish(&mut ctrl_plane))?; 381 382 Ok(CodeObject::new( 383 mmap_vec, 384 trap_offsets, 385 traps, 386 wasm_to_host_trampolines, 387 funcs, 388 )) 389 } 390} 391 392#[derive(Default)] 393struct TrapsBuilder { 394 offsets: Vec<u32>, 395 traps: Vec<TrapKind>, 396 last_offset: u32, 397} 398 399impl TrapsBuilder { 400 pub fn push_traps( 401 &mut self, 402 func: FunctionLoc, 403 traps: impl ExactSizeIterator<Item = TrapInfo>, 404 ) { 405 self.offsets.reserve_exact(traps.len()); 406 self.traps.reserve_exact(traps.len()); 407 408 for trap in traps { 409 let pos = func.start + trap.offset; 410 debug_assert!(pos >= self.last_offset); 411 // sanity check to make sure everything is sorted. 412 // otherwise we won't be able to use lookup later. 413 self.offsets.push(pos); 414 self.traps.push(trap.trap); 415 self.last_offset = pos; 416 } 417 418 self.last_offset = func.start + func.length; 419 } 420 421 pub fn finish(self) -> (Vec<u32>, Vec<TrapKind>) { 422 (self.offsets, self.traps) 423 } 424}