1 //! Definitions of runtime structures and metadata which are serialized into ELF 2 //! with `bincode` as part of a module's compilation process. 3 4 use crate::prelude::*; 5 use crate::{ 6 EntityRef, FilePos, FuncIndex, FuncKey, FuncKeyIndex, FuncKeyKind, FuncKeyNamespace, Module, 7 PanicOnOom as _, 8 }; 9 use core::ops::Range; 10 use core::{fmt, u32}; 11 use core::{iter, str}; 12 use serde_derive::{Deserialize, Serialize}; 13 #[cfg(feature = "rr")] 14 use sha2::{Digest, Sha256}; 15 16 /// Description of where a function is located in the text section of a 17 /// compiled image. 18 #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 19 pub struct FunctionLoc { 20 /// The byte offset from the start of the text section where this 21 /// function starts. 22 pub start: u32, 23 /// The byte length of this function's function body. 24 pub length: u32, 25 } 26 27 impl FunctionLoc { 28 /// Is this an empty function location? 29 #[inline] is_empty(&self) -> bool30 pub fn is_empty(&self) -> bool { 31 self.length == 0 32 } 33 } 34 35 /// The checksum of a Wasm binary. 36 /// 37 /// Allows for features requiring the exact same Wasm Module (e.g. deterministic replay) 38 /// to verify that the binary used matches the one originally compiled. 39 #[derive(Copy, Clone, Default, PartialEq, Eq, Ord, PartialOrd, Debug, Serialize, Deserialize)] 40 pub struct WasmChecksum([u8; 32]); 41 42 impl WasmChecksum { 43 /// Construct a [`WasmChecksum`] from the given wasm binary, used primarily for integrity 44 /// checks on compiled modules when recording configs are enabled. The checksum is not 45 /// computed when recording is disabled to prevent pessimization of non-recorded compilations. 46 #[cfg(feature = "rr")] from_binary(bin: &[u8], recording: bool) -> WasmChecksum47 pub fn from_binary(bin: &[u8], recording: bool) -> WasmChecksum { 48 if recording { 49 WasmChecksum(Sha256::digest(bin).into()) 50 } else { 51 WasmChecksum::default() 52 } 53 } 54 55 /// This method requires the `rr` feature to actual compute a checksum. Since the `rr` 56 /// feature is disabled, this only returns a default checksum value of all zeros. 57 #[cfg(not(feature = "rr"))] from_binary(_: &[u8], _: bool) -> WasmChecksum58 pub fn from_binary(_: &[u8], _: bool) -> WasmChecksum { 59 WasmChecksum::default() 60 } 61 } 62 63 impl core::ops::Deref for WasmChecksum { 64 type Target = [u8; 32]; 65 deref(&self) -> &Self::Target66 fn deref(&self) -> &Self::Target { 67 &self.0 68 } 69 } 70 71 /// A builder for a `CompiledFunctionsTable`. 72 pub struct CompiledFunctionsTableBuilder { 73 inner: CompiledFunctionsTable, 74 } 75 76 impl CompiledFunctionsTableBuilder { 77 /// Create a new builder. new() -> Self78 pub fn new() -> Self { 79 Self { 80 inner: CompiledFunctionsTable { 81 namespaces: TryPrimaryMap::new(), 82 func_loc_starts: TryPrimaryMap::new(), 83 sparse_starts: TryPrimaryMap::new(), 84 src_loc_starts: TryPrimaryMap::new(), 85 sparse_indices: TryPrimaryMap::new(), 86 func_locs: TryPrimaryMap::new(), 87 src_locs: TryPrimaryMap::new(), 88 }, 89 } 90 } 91 last_namespace(&self) -> Option<FuncKeyNamespace>92 fn last_namespace(&self) -> Option<FuncKeyNamespace> { 93 let (_, &ns) = self.inner.namespaces.last()?; 94 Some(ns) 95 } 96 last_key_index(&self) -> Option<FuncKeyIndex>97 fn last_key_index(&self) -> Option<FuncKeyIndex> { 98 let (ns_idx, ns) = self.inner.namespaces.last()?; 99 let start = self.inner.func_loc_starts[ns_idx]; 100 if CompiledFunctionsTable::is_dense(ns.kind()) { 101 let len = self.inner.func_locs.len(); 102 let len = u32::try_from(len).unwrap(); 103 let key_index = len - start.as_u32(); 104 let key_index = FuncKeyIndex::from_raw(key_index); 105 Some(key_index) 106 } else { 107 let sparse_start = self.inner.sparse_starts[ns_idx]; 108 if self.inner.sparse_indices.len() > sparse_start.index() { 109 let (_, &key_index) = self.inner.sparse_indices.last().unwrap(); 110 Some(key_index) 111 } else { 112 None 113 } 114 } 115 } 116 last_func_loc(&self) -> Option<FunctionLoc>117 fn last_func_loc(&self) -> Option<FunctionLoc> { 118 let (_, &loc) = self.inner.func_locs.last()?; 119 Some(loc) 120 } 121 122 /// Push a new entry into this builder. 123 /// 124 /// Panics if the key or function location is out of order. push_func( &mut self, key: FuncKey, func_loc: FunctionLoc, src_loc: FilePos, ) -> &mut Self125 pub fn push_func( 126 &mut self, 127 key: FuncKey, 128 func_loc: FunctionLoc, 129 src_loc: FilePos, 130 ) -> &mut Self { 131 let (key_ns, key_index) = key.into_parts(); 132 133 assert!( 134 self.last_namespace().is_none_or(|ns| ns <= key_ns), 135 "`FuncKey`s pushed out of order" 136 ); 137 assert!( 138 self.last_key_index().is_none_or( 139 |i| i <= key_index || self.last_namespace().is_some_and(|ns| ns != key_ns) 140 ), 141 "`FuncKey`s pushed out of order" 142 ); 143 assert!( 144 self.last_func_loc() 145 .is_none_or(|l| l.start + l.length <= func_loc.start), 146 "`FunctionLoc`s pushed out of order" 147 ); 148 149 // Make sure that there is a `kind` entry for this key's kind. 150 let kind_start_index = self 151 .inner 152 .namespaces 153 .last() 154 .and_then(|(ns_idx, ns)| { 155 if *ns == key_ns { 156 Some(self.inner.func_loc_starts[ns_idx]) 157 } else { 158 None 159 } 160 }) 161 .unwrap_or_else(|| { 162 let start = self.inner.func_locs.next_key(); 163 let ns_idx = self.inner.namespaces.push(key_ns).panic_on_oom(); 164 let ns_idx2 = self.inner.func_loc_starts.push(start).panic_on_oom(); 165 let ns_idx3 = self 166 .inner 167 .sparse_starts 168 .push(self.inner.sparse_indices.next_key()) 169 .panic_on_oom(); 170 let ns_idx4 = self 171 .inner 172 .src_loc_starts 173 .push(self.inner.src_locs.next_key()) 174 .panic_on_oom(); 175 debug_assert_eq!(ns_idx, ns_idx2); 176 debug_assert_eq!(ns_idx, ns_idx3); 177 debug_assert_eq!(ns_idx, ns_idx4); 178 start 179 }); 180 181 if CompiledFunctionsTable::is_dense(key.kind()) { 182 // Figure out the index within `func_locs` for this key's entry. 183 let index = kind_start_index.as_u32() + key_index.into_raw(); 184 let index = FuncLocIndex::from_u32(index); 185 debug_assert!(self.inner.func_locs.get(index).is_none()); 186 187 // Fill in null entries for any key indices that have been omitted. 188 // 189 // Note that we need a null `FunctionLoc`, but we also need 190 // `func_locs` to be sorted so that we support reverse 191 // lookups. Therefore, we take care to create an empty function 192 // location that starts at the text offset that the previous one (if 193 // any) ends at, and use that as our null entry. 194 let null_func_loc = FunctionLoc { 195 start: self 196 .last_func_loc() 197 .map(|l| l.start + l.length) 198 .unwrap_or_default(), 199 length: 0, 200 }; 201 let gap = index.index() - self.inner.func_locs.len(); 202 self.inner 203 .func_locs 204 .try_extend(iter::repeat(null_func_loc).take(gap)) 205 .panic_on_oom(); 206 debug_assert_eq!(index, self.inner.func_locs.next_key()); 207 208 if CompiledFunctionsTable::has_src_locs(key_ns.kind()) { 209 self.inner 210 .src_locs 211 .try_extend(iter::repeat(FilePos::none()).take(gap)) 212 .panic_on_oom(); 213 } 214 } else { 215 debug_assert!( 216 src_loc.is_none(), 217 "sparse keys do not have source locations" 218 ); 219 self.inner.sparse_indices.push(key_index).panic_on_oom(); 220 } 221 222 // And finally, we push this entry. 223 self.inner.func_locs.push(func_loc).panic_on_oom(); 224 if CompiledFunctionsTable::has_src_locs(key_ns.kind()) { 225 self.inner.src_locs.push(src_loc).panic_on_oom(); 226 } else { 227 debug_assert!(src_loc.is_none()); 228 } 229 230 self 231 } 232 233 /// Finish construction of the `CompiledFunctionsTable`. finish(self) -> CompiledFunctionsTable234 pub fn finish(self) -> CompiledFunctionsTable { 235 self.inner 236 } 237 } 238 239 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] 240 struct NamespaceIndex(u32); 241 cranelift_entity::entity_impl!(NamespaceIndex); 242 243 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] 244 struct FuncLocIndex(u32); 245 cranelift_entity::entity_impl!(FuncLocIndex); 246 247 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] 248 struct SparseIndex(u32); 249 cranelift_entity::entity_impl!(SparseIndex); 250 251 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] 252 struct SrcLocIndex(u32); 253 cranelift_entity::entity_impl!(SrcLocIndex); 254 255 /// A table describing the set of functions compiled into an artifact, their 256 /// locations within the text section, and etc... 257 /// 258 /// Logically, this type is a map from a `FuncKey` to the associated function's 259 /// 260 /// * location within the associated text section, and 261 /// * optional source location. 262 /// 263 /// How this map is *actually* implemented is with a series of lookup and binary 264 /// search tables, split out in a data-oriented, struct-of-arrays style. We 265 /// organize the data in this way is service of three goals: 266 /// 267 /// 1. Provide fast look ups: We need to look up the metadata for a function by 268 /// its key at runtime. During instantiation, for example, we need to create 269 /// `VMFuncRef`s for escaping functions and this requires looking up the 270 /// locations of those Wasm functions and their associated array-to-Wasm 271 /// trampolines. 272 /// 273 /// 2. Keep memory overheads low and code size small: This type is serialized 274 /// into all of our ELF artifacts and deserialized into all `Module`s and 275 /// `Component`s at runtime. 276 /// 277 /// 3. Be generic over any kind of function (whether defined Wasm function, 278 /// trampoline, or etc...) that we compile: Adding a new kind of trampoline, 279 /// for example, should not require updating this structure to add a new 280 /// table of the function locations for just trampolines of that new kind. We 281 /// should be able to store and query all kinds of functions uniformly. 282 // 283 // TODO: This structure could be directly encoded as raw ELF sections, instead 284 // of a `struct` containing a bunch of `PrimaryMap`s, which would allow us to 285 // avoid the serialize/deserialize runtime costs. 286 #[derive(Debug, Serialize, Deserialize)] 287 pub struct CompiledFunctionsTable { 288 /// A binary-search index for this table, mapping raw `FuncKeyNamespace`s to 289 /// their associated `NamespaceIndex`. That `NamespaceIndex` can then be 290 /// used to find the range of other entity indices that are specific to that 291 /// namespace. 292 namespaces: TryPrimaryMap<NamespaceIndex, FuncKeyNamespace>, 293 294 /// `self.func_loc_starts[i]..self.func_loc_starts[i+1]` describes the range 295 /// within `self.func_locs` whose entries are associated with the namespace 296 /// `self.index[i]`. 297 /// 298 /// When `self.func_loc_starts[i+1]` is out of bounds, then the range is to 299 /// the end of `self.func_locs`. 300 func_loc_starts: TryPrimaryMap<NamespaceIndex, FuncLocIndex>, 301 302 /// `self.sparse_starts[i]..self.sparse_starts[i+1]` describes the range 303 /// within `self.sparse_indices` whose entries are associated with the 304 /// namespace `self.index[i]`. 305 /// 306 /// When `self.sparse_starts[i+1]` is out of bounds, then the range is to 307 /// the end of `self.sparse_indices`. 308 /// 309 /// Entries are only valid for sparse, non-dense namespaces. 310 sparse_starts: TryPrimaryMap<NamespaceIndex, SparseIndex>, 311 312 /// `self.src_loc_starts[i]..self.src_loc_starts[i+1]` describes the range 313 /// within `self.src_loc_indices` whose entries are associated with the 314 /// namespace `self.index[i]`. 315 /// 316 /// When `self.src_loc_starts[i+1]` is out of bounds, then the range is to 317 /// the end of `self.src_locs`. 318 /// 319 /// Entries are only valid for namespaces whose functions have source 320 /// locations. 321 src_loc_starts: TryPrimaryMap<NamespaceIndex, SrcLocIndex>, 322 323 /// `self.sparse_indices[i]` contains the index part of 324 /// `FuncKey::from_parts(ns, index)` where `ns` is determined by 325 /// `self.sparse_starts` and is a sparse, non-dense key kind. (Note that for 326 /// dense keys, this information is implicitly encoded in their offset from 327 /// the namespace's start index.) 328 /// 329 /// This is sorted to allow for binary searches. 330 sparse_indices: TryPrimaryMap<SparseIndex, FuncKeyIndex>, 331 332 /// `self.func_locs[i]` contains the location within the text section of 333 /// `FuncKey::from_parts(self.namespaces[ns], i - start)`'s function, where 334 /// `ns` and `start` are determined by `self.func_loc_starts`. 335 /// 336 /// Values are sorted by function location to support reverse queries from 337 /// function location back to `FuncKey`. 338 /// 339 /// The absence of a function location (for gaps in dense namespaces) is 340 /// represented with `FunctionLoc::none()`. 341 func_locs: TryPrimaryMap<FuncLocIndex, FunctionLoc>, 342 343 /// `self.src_locs[i]` contains the initial source location of 344 /// `FuncKey::from_parts(self.namespaces[ns], i - start)`'s function, where 345 /// `ns` and `start` are determined by `self.src_loc_starts`. 346 /// 347 /// The absence of a source location is represented by `FilePos::none()`. 348 src_locs: TryPrimaryMap<SrcLocIndex, FilePos>, 349 } 350 351 impl CompiledFunctionsTable { 352 #[inline] namespace_index(&self, namespace: FuncKeyNamespace) -> Option<NamespaceIndex>353 fn namespace_index(&self, namespace: FuncKeyNamespace) -> Option<NamespaceIndex> { 354 const LINEAR_SEARCH_LIMIT: usize = 32; 355 if self.namespaces.len() <= LINEAR_SEARCH_LIMIT { 356 self.namespaces 357 .iter() 358 .find_map(|(idx, ns)| if *ns == namespace { Some(idx) } else { None }) 359 } else { 360 self.namespaces 361 .binary_search_values_by_key(&namespace, |ns| *ns) 362 .ok() 363 } 364 } 365 366 #[inline] func_loc_range(&self, ns_idx: NamespaceIndex) -> Range<FuncLocIndex>367 fn func_loc_range(&self, ns_idx: NamespaceIndex) -> Range<FuncLocIndex> { 368 let start = self.func_loc_starts[ns_idx]; 369 let next_ns_idx = NamespaceIndex::from_u32(ns_idx.as_u32() + 1); 370 let end = self 371 .func_loc_starts 372 .get(next_ns_idx) 373 .copied() 374 .unwrap_or_else(|| self.func_locs.next_key()); 375 start..end 376 } 377 sparse_range(&self, ns_idx: NamespaceIndex) -> Range<SparseIndex>378 fn sparse_range(&self, ns_idx: NamespaceIndex) -> Range<SparseIndex> { 379 debug_assert!(!Self::is_dense(self.namespaces[ns_idx].kind())); 380 let start = self.sparse_starts[ns_idx]; 381 let next_ns_idx = NamespaceIndex::from_u32(ns_idx.as_u32() + 1); 382 let end = self 383 .sparse_starts 384 .get(next_ns_idx) 385 .copied() 386 .unwrap_or_else(|| self.sparse_indices.next_key()); 387 start..end 388 } 389 src_loc_range(&self, ns_idx: NamespaceIndex) -> Range<SrcLocIndex>390 fn src_loc_range(&self, ns_idx: NamespaceIndex) -> Range<SrcLocIndex> { 391 debug_assert!(Self::has_src_locs(self.namespaces[ns_idx].kind())); 392 let start = self.src_loc_starts[ns_idx]; 393 let next_ns_idx = NamespaceIndex::from_u32(ns_idx.as_u32() + 1); 394 let end = self 395 .src_loc_starts 396 .get(next_ns_idx) 397 .copied() 398 .unwrap_or_else(|| self.src_locs.next_key()); 399 start..end 400 } 401 402 /// Get the index within `self.{func_locs,src_locs}` that is associated with 403 /// the given `key`, if any. 404 #[inline] func_loc_index(&self, key: FuncKey) -> Option<FuncLocIndex>405 fn func_loc_index(&self, key: FuncKey) -> Option<FuncLocIndex> { 406 let (key_ns, key_index) = key.into_parts(); 407 let ns_idx = self.namespace_index(key_ns)?; 408 let Range { start, end } = self.func_loc_range(ns_idx); 409 410 let index = if Self::is_dense(key.kind()) { 411 let index = start.as_u32().checked_add(key_index.into_raw())?; 412 FuncLocIndex::from_u32(index) 413 } else { 414 let sparse_range = self.sparse_range(ns_idx); 415 let sparse_subslice = self.sparse_indices.get_range(sparse_range).unwrap(); 416 match sparse_subslice.binary_search(&key_index) { 417 Ok(i) => FuncLocIndex::new(start.index() + i), 418 Err(_) => return None, 419 } 420 }; 421 422 if index < end { Some(index) } else { None } 423 } 424 425 /// Get the location of the function associated with the given `key` inside 426 /// the text section, if any. 427 #[inline] func_loc(&self, key: FuncKey) -> Option<&FunctionLoc>428 pub fn func_loc(&self, key: FuncKey) -> Option<&FunctionLoc> { 429 let index = self.func_loc_index(key)?; 430 let loc = &self.func_locs[index]; 431 if loc.is_empty() { None } else { Some(loc) } 432 } 433 src_loc_index(&self, key: FuncKey) -> Option<SrcLocIndex>434 fn src_loc_index(&self, key: FuncKey) -> Option<SrcLocIndex> { 435 let (key_ns, key_index) = key.into_parts(); 436 if !Self::has_src_locs(key_ns.kind()) { 437 return None; 438 } 439 440 let ns_idx = self.namespace_index(key_ns)?; 441 let Range { start, end } = self.src_loc_range(ns_idx); 442 443 debug_assert!(Self::is_dense(key_ns.kind())); 444 let index = start.as_u32().checked_add(key_index.into_raw())?; 445 let index = SrcLocIndex::from_u32(index); 446 if index >= end { 447 return None; 448 } 449 450 Some(index) 451 } 452 453 /// Get the initial source location of the function associated with the 454 /// given `key`, if any. src_loc(&self, key: FuncKey) -> Option<FilePos>455 pub fn src_loc(&self, key: FuncKey) -> Option<FilePos> { 456 let index = self.src_loc_index(key)?; 457 let loc = self.src_locs[index]; 458 if loc.is_none() { None } else { Some(loc) } 459 } 460 461 /// Given an offset into the text section, get the key for its associated 462 /// function and its offset within that function. func_by_text_offset(&self, text_offset: u32) -> Option<FuncKey>463 pub fn func_by_text_offset(&self, text_offset: u32) -> Option<FuncKey> { 464 let index = match self.func_locs.as_values_slice().binary_search_by(|loc| { 465 if loc.is_empty() { 466 loc.start 467 .cmp(&text_offset) 468 .then_with(|| core::cmp::Ordering::Less) 469 } else { 470 if loc.start > text_offset { 471 core::cmp::Ordering::Greater 472 } else if loc.start + loc.length <= text_offset { 473 core::cmp::Ordering::Less 474 } else { 475 debug_assert!(loc.start <= text_offset); 476 debug_assert!(text_offset < loc.start + loc.length); 477 core::cmp::Ordering::Equal 478 } 479 } 480 }) { 481 // Exact match, the offset is at the end of this function. 482 Ok(k) => k, 483 // Not an exact match: `k` is where the offset would be 484 // "inserted". Since we key based on the end, function `k` might 485 // contain the offset, so we'll validate on the range check 486 // below. 487 Err(k) => k, 488 }; 489 let index = FuncLocIndex::new(index); 490 491 // Make sure that the text offset is actually within this function. 492 // Non-exact binary search results can either be because we have a text 493 // offset within a function but not exactly at its inclusive end, or 494 // because the text offset is not within any of our functions. We filter 495 // that latter case out with this check. 496 let loc = self.func_locs.get(index)?; 497 let start = loc.start; 498 let end = start + loc.length; 499 if text_offset < start || end < text_offset { 500 return None; 501 } 502 503 let ns_idx = match self 504 .func_loc_starts 505 .binary_search_values_by_key(&index, |s| *s) 506 { 507 // Exact match: `i` is the entry's index. 508 Ok(i) => i, 509 // Not an exact match: the index, if it were the start of a 510 // namespace's range, would be at `i`. Therefore, our namespace 511 // entry is actually at index `i - 1`. 512 Err(i) => { 513 let i = i.as_u32(); 514 assert_ne!(i, 0); 515 NamespaceIndex::from_u32(i - 1) 516 } 517 }; 518 let key_ns = self.namespaces[ns_idx]; 519 let start = self.func_loc_starts[ns_idx]; 520 521 let key_index = if Self::is_dense(key_ns.kind()) { 522 let key_index = index.as_u32() - start.as_u32(); 523 FuncKeyIndex::from_raw(key_index) 524 } else { 525 let sparse_offset = index.as_u32() - start.as_u32(); 526 let sparse_start = self.sparse_starts[ns_idx]; 527 let sparse_index = SparseIndex::from_u32(sparse_start.as_u32() + sparse_offset); 528 debug_assert!( 529 { 530 let range = self.sparse_range(ns_idx); 531 range.start <= sparse_index && sparse_index < range.end 532 }, 533 "{sparse_index:?} is not within {:?}", 534 self.sparse_range(ns_idx) 535 ); 536 self.sparse_indices[sparse_index] 537 }; 538 let key = FuncKey::from_parts(key_ns, key_index); 539 540 Some(key) 541 } 542 543 /// Whether the given kind's index space is (generally) densely populated 544 /// and therefore we should densely pack them in the table for `O(1)` 545 /// lookups; otherwise, we should avoid code size bloat by using the sparse 546 /// table indirection and `O(log n)` binary search lookups. is_dense(kind: FuncKeyKind) -> bool547 fn is_dense(kind: FuncKeyKind) -> bool { 548 match kind { 549 FuncKeyKind::DefinedWasmFunction 550 | FuncKeyKind::WasmToArrayTrampoline 551 | FuncKeyKind::PulleyHostCall => true, 552 553 FuncKeyKind::ArrayToWasmTrampoline 554 | FuncKeyKind::WasmToBuiltinTrampoline 555 | FuncKeyKind::PatchableToBuiltinTrampoline => false, 556 557 #[cfg(feature = "component-model")] 558 FuncKeyKind::ComponentTrampoline 559 | FuncKeyKind::ResourceDropTrampoline 560 | FuncKeyKind::UnsafeIntrinsic => true, 561 } 562 } 563 564 /// Whether the given function kind has source locations or not. has_src_locs(kind: FuncKeyKind) -> bool565 fn has_src_locs(kind: FuncKeyKind) -> bool { 566 match kind { 567 FuncKeyKind::DefinedWasmFunction => true, 568 FuncKeyKind::ArrayToWasmTrampoline 569 | FuncKeyKind::WasmToArrayTrampoline 570 | FuncKeyKind::WasmToBuiltinTrampoline 571 | FuncKeyKind::PatchableToBuiltinTrampoline 572 | FuncKeyKind::PulleyHostCall => false, 573 #[cfg(feature = "component-model")] 574 FuncKeyKind::ComponentTrampoline 575 | FuncKeyKind::ResourceDropTrampoline 576 | FuncKeyKind::UnsafeIntrinsic => false, 577 } 578 } 579 } 580 581 /// Secondary in-memory results of module compilation. 582 /// 583 /// This opaque structure can be optionally passed back to 584 /// `CompiledModule::from_artifacts` to avoid decoding extra information there. 585 #[derive(Serialize, Deserialize)] 586 pub struct CompiledModuleInfo { 587 /// Type information about the compiled WebAssembly module. 588 pub module: Module, 589 590 /// General compilation metadata. 591 pub meta: Metadata, 592 593 /// Sorted list, by function index, of names we have for this module. 594 pub func_names: Vec<FunctionName>, 595 596 /// Checksum of the source Wasm binary from which this module was compiled. 597 pub checksum: WasmChecksum, 598 } 599 600 /// The name of a function stored in the 601 /// [`ELF_NAME_DATA`](crate::obj::ELF_NAME_DATA) section. 602 #[derive(Serialize, Deserialize)] 603 pub struct FunctionName { 604 /// The Wasm function index of this function. 605 pub idx: FuncIndex, 606 /// The offset of the name in the 607 /// [`ELF_NAME_DATA`](crate::obj::ELF_NAME_DATA) section. 608 pub offset: u32, 609 /// The length of the name in bytes. 610 pub len: u32, 611 } 612 613 /// Metadata associated with a compiled ELF artifact. 614 #[derive(Serialize, Deserialize)] 615 pub struct Metadata { 616 /// Whether or not the original wasm module contained debug information that 617 /// we skipped and did not parse. 618 pub has_unparsed_debuginfo: bool, 619 620 /// Offset in the original wasm file to the code section. 621 pub code_section_offset: u64, 622 623 /// Whether or not custom wasm-specific dwarf sections were inserted into 624 /// the ELF image. 625 /// 626 /// Note that even if this flag is `true` sections may be missing if they 627 /// weren't found in the original wasm module itself. 628 pub has_wasm_debuginfo: bool, 629 630 /// Dwarf sections and the offsets at which they're stored in the 631 /// ELF_WASMTIME_DWARF 632 pub dwarf: Vec<(u8, Range<u64>)>, 633 } 634 635 /// Value of a configured setting for a [`Compiler`](crate::Compiler) 636 #[derive(Serialize, Deserialize, Hash, Eq, PartialEq, Debug)] 637 pub enum FlagValue<'a> { 638 /// Name of the value that has been configured for this setting. 639 Enum(&'a str), 640 /// The numerical value of the configured settings. 641 Num(u8), 642 /// Whether the setting is on or off. 643 Bool(bool), 644 } 645 646 impl fmt::Display for FlagValue<'_> { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result647 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 648 match self { 649 Self::Enum(v) => v.fmt(f), 650 Self::Num(v) => v.fmt(f), 651 Self::Bool(v) => v.fmt(f), 652 } 653 } 654 } 655 656 /// Types of objects that can be created by `Compiler::object` 657 pub enum ObjectKind { 658 /// A core wasm compilation artifact 659 Module, 660 /// A component compilation artifact 661 Component, 662 } 663 664 #[cfg(test)] 665 mod tests { 666 use super::*; 667 use crate::{DefinedFuncIndex, StaticModuleIndex}; 668 func_loc(range: Range<u32>) -> FunctionLoc669 fn func_loc(range: Range<u32>) -> FunctionLoc { 670 FunctionLoc { 671 start: range.start, 672 length: range.end - range.start, 673 } 674 } 675 def_func_key(m: u32, f: u32) -> FuncKey676 fn def_func_key(m: u32, f: u32) -> FuncKey { 677 FuncKey::DefinedWasmFunction( 678 StaticModuleIndex::from_u32(m), 679 DefinedFuncIndex::from_u32(f), 680 ) 681 } 682 array_to_wasm_tramp_key(m: u32, f: u32) -> FuncKey683 fn array_to_wasm_tramp_key(m: u32, f: u32) -> FuncKey { 684 FuncKey::ArrayToWasmTrampoline( 685 StaticModuleIndex::from_u32(m), 686 DefinedFuncIndex::from_u32(f), 687 ) 688 } 689 make_test_table() -> CompiledFunctionsTable690 fn make_test_table() -> CompiledFunctionsTable { 691 let mut builder = CompiledFunctionsTableBuilder::new(); 692 693 builder 694 // ========= Dense ========= 695 .push_func(def_func_key(0, 0), func_loc(0..10), FilePos::new(111)) 696 .push_func(def_func_key(0, 1), func_loc(10..20), FilePos::new(222)) 697 .push_func(def_func_key(0, 2), func_loc(20..30), FilePos::none()) 698 // Gap in dense keys! 699 .push_func(def_func_key(0, 5), func_loc(30..40), FilePos::new(333)) 700 // ========= Sparse ========= 701 .push_func( 702 array_to_wasm_tramp_key(0, 1), 703 func_loc(100..110), 704 FilePos::none(), 705 ) 706 .push_func( 707 array_to_wasm_tramp_key(0, 2), 708 func_loc(110..120), 709 FilePos::none(), 710 ) 711 .push_func( 712 array_to_wasm_tramp_key(0, 5), 713 func_loc(120..130), 714 FilePos::none(), 715 ); 716 717 builder.finish() 718 } 719 720 #[test] src_locs()721 fn src_locs() { 722 let table = make_test_table(); 723 724 for (key, expected) in [ 725 (def_func_key(0, 0), Some(FilePos::new(111))), 726 (def_func_key(0, 1), Some(FilePos::new(222))), 727 (def_func_key(0, 2), None), 728 (def_func_key(0, 3), None), 729 (def_func_key(0, 4), None), 730 (def_func_key(0, 5), Some(FilePos::new(333))), 731 (array_to_wasm_tramp_key(0, 0), None), 732 (array_to_wasm_tramp_key(0, 1), None), 733 (array_to_wasm_tramp_key(0, 2), None), 734 (array_to_wasm_tramp_key(0, 3), None), 735 (array_to_wasm_tramp_key(0, 4), None), 736 (array_to_wasm_tramp_key(0, 5), None), 737 ] { 738 eprintln!("Checking key {key:?}"); 739 let actual = table.src_loc(key); 740 assert_eq!(expected, actual); 741 } 742 } 743 744 #[test] func_locs()745 fn func_locs() { 746 let table = make_test_table(); 747 748 for (key, expected) in [ 749 (def_func_key(0, 0), Some(0)), 750 (def_func_key(0, 1), Some(10)), 751 (def_func_key(0, 2), Some(20)), 752 (def_func_key(0, 3), None), 753 (def_func_key(0, 4), None), 754 (def_func_key(0, 5), Some(30)), 755 (array_to_wasm_tramp_key(0, 0), None), 756 (array_to_wasm_tramp_key(0, 1), Some(100)), 757 (array_to_wasm_tramp_key(0, 2), Some(110)), 758 (array_to_wasm_tramp_key(0, 3), None), 759 (array_to_wasm_tramp_key(0, 4), None), 760 (array_to_wasm_tramp_key(0, 5), Some(120)), 761 ] { 762 let actual = table.func_loc(key); 763 match (expected, actual) { 764 (None, None) => {} 765 (Some(expected), Some(actual)) => assert_eq!(expected, actual.start), 766 (None, Some(actual)) => { 767 panic!("expected no function location for {key:?}, got {actual:?}") 768 } 769 (Some(_), None) => { 770 panic!("expected a function location for {key:?}, but got nothing") 771 } 772 } 773 } 774 } 775 776 #[test] reverse_func_locs()777 fn reverse_func_locs() { 778 let table = make_test_table(); 779 780 for (range, expected) in [ 781 (0..10, Some(def_func_key(0, 0))), 782 (10..20, Some(def_func_key(0, 1))), 783 (20..30, Some(def_func_key(0, 2))), 784 (30..40, Some(def_func_key(0, 5))), 785 (40..100, None), 786 (100..110, Some(array_to_wasm_tramp_key(0, 1))), 787 (110..120, Some(array_to_wasm_tramp_key(0, 2))), 788 (120..130, Some(array_to_wasm_tramp_key(0, 5))), 789 (140..150, None), 790 ] { 791 for i in range { 792 eprintln!("Checking offset {i}"); 793 let actual = table.func_by_text_offset(i); 794 assert_eq!(expected, actual); 795 } 796 } 797 } 798 799 #[test] reverse_lookups()800 fn reverse_lookups() { 801 use arbitrary::{Result, Unstructured}; 802 803 arbtest::arbtest(|u| run(u)).budget_ms(1_000); 804 805 fn run(u: &mut Unstructured<'_>) -> Result<()> { 806 let mut funcs = Vec::new(); 807 808 // Build up a random set of functions with random indices. 809 for _ in 0..u.int_in_range(1..=200)? { 810 let key = match u.int_in_range(0..=6)? { 811 0 => FuncKey::DefinedWasmFunction(idx(u, 10)?, idx(u, 200)?), 812 1 => FuncKey::ArrayToWasmTrampoline(idx(u, 10)?, idx(u, 200)?), 813 2 => FuncKey::WasmToArrayTrampoline(idx(u, 100)?), 814 3 => FuncKey::WasmToBuiltinTrampoline(u.arbitrary()?), 815 4 => FuncKey::PulleyHostCall(u.arbitrary()?), 816 5 => FuncKey::ComponentTrampoline(u.arbitrary()?, idx(u, 50)?), 817 6 => FuncKey::ResourceDropTrampoline, 818 _ => unreachable!(), 819 }; 820 funcs.push(key); 821 } 822 823 // Sort/dedup our list of `funcs` to satisfy the requirement of 824 // `CompiledFunctionsTableBuilder::push_func`. 825 funcs.sort(); 826 funcs.dedup(); 827 828 let mut builder = CompiledFunctionsTableBuilder::new(); 829 let mut size = 0; 830 let mut expected = Vec::new(); 831 for key in funcs { 832 let length = u.int_in_range(1..=10)?; 833 for _ in 0..length { 834 expected.push(key); 835 } 836 // println!("push {key:?} - {length}"); 837 builder.push_func( 838 key, 839 FunctionLoc { 840 start: size, 841 length, 842 }, 843 FilePos::none(), 844 ); 845 size += length; 846 } 847 let index = builder.finish(); 848 849 let mut expected = expected.iter(); 850 for i in 0..size { 851 // println!("lookup {i}"); 852 let actual = index.func_by_text_offset(i).unwrap(); 853 assert_eq!(Some(&actual), expected.next()); 854 } 855 856 Ok(()) 857 } 858 859 fn idx<T>(u: &mut Unstructured<'_>, max: usize) -> Result<T> 860 where 861 T: EntityRef, 862 { 863 Ok(T::new(u.int_in_range(0..=max - 1)?)) 864 } 865 } 866 } 867