//! Definitions of runtime structures and metadata which are serialized into ELF //! with `bincode` as part of a module's compilation process. use crate::prelude::*; use crate::{ EntityRef, FilePos, FuncIndex, FuncKey, FuncKeyIndex, FuncKeyKind, FuncKeyNamespace, Module, PanicOnOom as _, }; use core::ops::Range; use core::{fmt, u32}; use core::{iter, str}; use serde_derive::{Deserialize, Serialize}; #[cfg(feature = "rr")] use sha2::{Digest, Sha256}; /// Description of where a function is located in the text section of a /// compiled image. #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct FunctionLoc { /// The byte offset from the start of the text section where this /// function starts. pub start: u32, /// The byte length of this function's function body. pub length: u32, } impl FunctionLoc { /// Is this an empty function location? #[inline] pub fn is_empty(&self) -> bool { self.length == 0 } } /// The checksum of a Wasm binary. /// /// Allows for features requiring the exact same Wasm Module (e.g. deterministic replay) /// to verify that the binary used matches the one originally compiled. #[derive(Copy, Clone, Default, PartialEq, Eq, Ord, PartialOrd, Debug, Serialize, Deserialize)] pub struct WasmChecksum([u8; 32]); impl WasmChecksum { /// Construct a [`WasmChecksum`] from the given wasm binary, used primarily for integrity /// checks on compiled modules when recording configs are enabled. The checksum is not /// computed when recording is disabled to prevent pessimization of non-recorded compilations. #[cfg(feature = "rr")] pub fn from_binary(bin: &[u8], recording: bool) -> WasmChecksum { if recording { WasmChecksum(Sha256::digest(bin).into()) } else { WasmChecksum::default() } } /// This method requires the `rr` feature to actual compute a checksum. Since the `rr` /// feature is disabled, this only returns a default checksum value of all zeros. #[cfg(not(feature = "rr"))] pub fn from_binary(_: &[u8], _: bool) -> WasmChecksum { WasmChecksum::default() } } impl core::ops::Deref for WasmChecksum { type Target = [u8; 32]; fn deref(&self) -> &Self::Target { &self.0 } } /// A builder for a `CompiledFunctionsTable`. pub struct CompiledFunctionsTableBuilder { inner: CompiledFunctionsTable, } impl CompiledFunctionsTableBuilder { /// Create a new builder. pub fn new() -> Self { Self { inner: CompiledFunctionsTable { namespaces: TryPrimaryMap::new(), func_loc_starts: TryPrimaryMap::new(), sparse_starts: TryPrimaryMap::new(), src_loc_starts: TryPrimaryMap::new(), sparse_indices: TryPrimaryMap::new(), func_locs: TryPrimaryMap::new(), src_locs: TryPrimaryMap::new(), }, } } fn last_namespace(&self) -> Option { let (_, &ns) = self.inner.namespaces.last()?; Some(ns) } fn last_key_index(&self) -> Option { let (ns_idx, ns) = self.inner.namespaces.last()?; let start = self.inner.func_loc_starts[ns_idx]; if CompiledFunctionsTable::is_dense(ns.kind()) { let len = self.inner.func_locs.len(); let len = u32::try_from(len).unwrap(); let key_index = len - start.as_u32(); let key_index = FuncKeyIndex::from_raw(key_index); Some(key_index) } else { let sparse_start = self.inner.sparse_starts[ns_idx]; if self.inner.sparse_indices.len() > sparse_start.index() { let (_, &key_index) = self.inner.sparse_indices.last().unwrap(); Some(key_index) } else { None } } } fn last_func_loc(&self) -> Option { let (_, &loc) = self.inner.func_locs.last()?; Some(loc) } /// Push a new entry into this builder. /// /// Panics if the key or function location is out of order. pub fn push_func( &mut self, key: FuncKey, func_loc: FunctionLoc, src_loc: FilePos, ) -> &mut Self { let (key_ns, key_index) = key.into_parts(); assert!( self.last_namespace().is_none_or(|ns| ns <= key_ns), "`FuncKey`s pushed out of order" ); assert!( self.last_key_index().is_none_or( |i| i <= key_index || self.last_namespace().is_some_and(|ns| ns != key_ns) ), "`FuncKey`s pushed out of order" ); assert!( self.last_func_loc() .is_none_or(|l| l.start + l.length <= func_loc.start), "`FunctionLoc`s pushed out of order" ); // Make sure that there is a `kind` entry for this key's kind. let kind_start_index = self .inner .namespaces .last() .and_then(|(ns_idx, ns)| { if *ns == key_ns { Some(self.inner.func_loc_starts[ns_idx]) } else { None } }) .unwrap_or_else(|| { let start = self.inner.func_locs.next_key(); let ns_idx = self.inner.namespaces.push(key_ns).panic_on_oom(); let ns_idx2 = self.inner.func_loc_starts.push(start).panic_on_oom(); let ns_idx3 = self .inner .sparse_starts .push(self.inner.sparse_indices.next_key()) .panic_on_oom(); let ns_idx4 = self .inner .src_loc_starts .push(self.inner.src_locs.next_key()) .panic_on_oom(); debug_assert_eq!(ns_idx, ns_idx2); debug_assert_eq!(ns_idx, ns_idx3); debug_assert_eq!(ns_idx, ns_idx4); start }); if CompiledFunctionsTable::is_dense(key.kind()) { // Figure out the index within `func_locs` for this key's entry. let index = kind_start_index.as_u32() + key_index.into_raw(); let index = FuncLocIndex::from_u32(index); debug_assert!(self.inner.func_locs.get(index).is_none()); // Fill in null entries for any key indices that have been omitted. // // Note that we need a null `FunctionLoc`, but we also need // `func_locs` to be sorted so that we support reverse // lookups. Therefore, we take care to create an empty function // location that starts at the text offset that the previous one (if // any) ends at, and use that as our null entry. let null_func_loc = FunctionLoc { start: self .last_func_loc() .map(|l| l.start + l.length) .unwrap_or_default(), length: 0, }; let gap = index.index() - self.inner.func_locs.len(); self.inner .func_locs .try_extend(iter::repeat(null_func_loc).take(gap)) .panic_on_oom(); debug_assert_eq!(index, self.inner.func_locs.next_key()); if CompiledFunctionsTable::has_src_locs(key_ns.kind()) { self.inner .src_locs .try_extend(iter::repeat(FilePos::none()).take(gap)) .panic_on_oom(); } } else { debug_assert!( src_loc.is_none(), "sparse keys do not have source locations" ); self.inner.sparse_indices.push(key_index).panic_on_oom(); } // And finally, we push this entry. self.inner.func_locs.push(func_loc).panic_on_oom(); if CompiledFunctionsTable::has_src_locs(key_ns.kind()) { self.inner.src_locs.push(src_loc).panic_on_oom(); } else { debug_assert!(src_loc.is_none()); } self } /// Finish construction of the `CompiledFunctionsTable`. pub fn finish(self) -> CompiledFunctionsTable { self.inner } } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] struct NamespaceIndex(u32); cranelift_entity::entity_impl!(NamespaceIndex); #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] struct FuncLocIndex(u32); cranelift_entity::entity_impl!(FuncLocIndex); #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] struct SparseIndex(u32); cranelift_entity::entity_impl!(SparseIndex); #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] struct SrcLocIndex(u32); cranelift_entity::entity_impl!(SrcLocIndex); /// A table describing the set of functions compiled into an artifact, their /// locations within the text section, and etc... /// /// Logically, this type is a map from a `FuncKey` to the associated function's /// /// * location within the associated text section, and /// * optional source location. /// /// How this map is *actually* implemented is with a series of lookup and binary /// search tables, split out in a data-oriented, struct-of-arrays style. We /// organize the data in this way is service of three goals: /// /// 1. Provide fast look ups: We need to look up the metadata for a function by /// its key at runtime. During instantiation, for example, we need to create /// `VMFuncRef`s for escaping functions and this requires looking up the /// locations of those Wasm functions and their associated array-to-Wasm /// trampolines. /// /// 2. Keep memory overheads low and code size small: This type is serialized /// into all of our ELF artifacts and deserialized into all `Module`s and /// `Component`s at runtime. /// /// 3. Be generic over any kind of function (whether defined Wasm function, /// trampoline, or etc...) that we compile: Adding a new kind of trampoline, /// for example, should not require updating this structure to add a new /// table of the function locations for just trampolines of that new kind. We /// should be able to store and query all kinds of functions uniformly. // // TODO: This structure could be directly encoded as raw ELF sections, instead // of a `struct` containing a bunch of `PrimaryMap`s, which would allow us to // avoid the serialize/deserialize runtime costs. #[derive(Debug, Serialize, Deserialize)] pub struct CompiledFunctionsTable { /// A binary-search index for this table, mapping raw `FuncKeyNamespace`s to /// their associated `NamespaceIndex`. That `NamespaceIndex` can then be /// used to find the range of other entity indices that are specific to that /// namespace. namespaces: TryPrimaryMap, /// `self.func_loc_starts[i]..self.func_loc_starts[i+1]` describes the range /// within `self.func_locs` whose entries are associated with the namespace /// `self.index[i]`. /// /// When `self.func_loc_starts[i+1]` is out of bounds, then the range is to /// the end of `self.func_locs`. func_loc_starts: TryPrimaryMap, /// `self.sparse_starts[i]..self.sparse_starts[i+1]` describes the range /// within `self.sparse_indices` whose entries are associated with the /// namespace `self.index[i]`. /// /// When `self.sparse_starts[i+1]` is out of bounds, then the range is to /// the end of `self.sparse_indices`. /// /// Entries are only valid for sparse, non-dense namespaces. sparse_starts: TryPrimaryMap, /// `self.src_loc_starts[i]..self.src_loc_starts[i+1]` describes the range /// within `self.src_loc_indices` whose entries are associated with the /// namespace `self.index[i]`. /// /// When `self.src_loc_starts[i+1]` is out of bounds, then the range is to /// the end of `self.src_locs`. /// /// Entries are only valid for namespaces whose functions have source /// locations. src_loc_starts: TryPrimaryMap, /// `self.sparse_indices[i]` contains the index part of /// `FuncKey::from_parts(ns, index)` where `ns` is determined by /// `self.sparse_starts` and is a sparse, non-dense key kind. (Note that for /// dense keys, this information is implicitly encoded in their offset from /// the namespace's start index.) /// /// This is sorted to allow for binary searches. sparse_indices: TryPrimaryMap, /// `self.func_locs[i]` contains the location within the text section of /// `FuncKey::from_parts(self.namespaces[ns], i - start)`'s function, where /// `ns` and `start` are determined by `self.func_loc_starts`. /// /// Values are sorted by function location to support reverse queries from /// function location back to `FuncKey`. /// /// The absence of a function location (for gaps in dense namespaces) is /// represented with `FunctionLoc::none()`. func_locs: TryPrimaryMap, /// `self.src_locs[i]` contains the initial source location of /// `FuncKey::from_parts(self.namespaces[ns], i - start)`'s function, where /// `ns` and `start` are determined by `self.src_loc_starts`. /// /// The absence of a source location is represented by `FilePos::none()`. src_locs: TryPrimaryMap, } impl CompiledFunctionsTable { #[inline] fn namespace_index(&self, namespace: FuncKeyNamespace) -> Option { const LINEAR_SEARCH_LIMIT: usize = 32; if self.namespaces.len() <= LINEAR_SEARCH_LIMIT { self.namespaces .iter() .find_map(|(idx, ns)| if *ns == namespace { Some(idx) } else { None }) } else { self.namespaces .binary_search_values_by_key(&namespace, |ns| *ns) .ok() } } #[inline] fn func_loc_range(&self, ns_idx: NamespaceIndex) -> Range { let start = self.func_loc_starts[ns_idx]; let next_ns_idx = NamespaceIndex::from_u32(ns_idx.as_u32() + 1); let end = self .func_loc_starts .get(next_ns_idx) .copied() .unwrap_or_else(|| self.func_locs.next_key()); start..end } fn sparse_range(&self, ns_idx: NamespaceIndex) -> Range { debug_assert!(!Self::is_dense(self.namespaces[ns_idx].kind())); let start = self.sparse_starts[ns_idx]; let next_ns_idx = NamespaceIndex::from_u32(ns_idx.as_u32() + 1); let end = self .sparse_starts .get(next_ns_idx) .copied() .unwrap_or_else(|| self.sparse_indices.next_key()); start..end } fn src_loc_range(&self, ns_idx: NamespaceIndex) -> Range { debug_assert!(Self::has_src_locs(self.namespaces[ns_idx].kind())); let start = self.src_loc_starts[ns_idx]; let next_ns_idx = NamespaceIndex::from_u32(ns_idx.as_u32() + 1); let end = self .src_loc_starts .get(next_ns_idx) .copied() .unwrap_or_else(|| self.src_locs.next_key()); start..end } /// Get the index within `self.{func_locs,src_locs}` that is associated with /// the given `key`, if any. #[inline] fn func_loc_index(&self, key: FuncKey) -> Option { let (key_ns, key_index) = key.into_parts(); let ns_idx = self.namespace_index(key_ns)?; let Range { start, end } = self.func_loc_range(ns_idx); let index = if Self::is_dense(key.kind()) { let index = start.as_u32().checked_add(key_index.into_raw())?; FuncLocIndex::from_u32(index) } else { let sparse_range = self.sparse_range(ns_idx); let sparse_subslice = self.sparse_indices.get_range(sparse_range).unwrap(); match sparse_subslice.binary_search(&key_index) { Ok(i) => FuncLocIndex::new(start.index() + i), Err(_) => return None, } }; if index < end { Some(index) } else { None } } /// Get the location of the function associated with the given `key` inside /// the text section, if any. #[inline] pub fn func_loc(&self, key: FuncKey) -> Option<&FunctionLoc> { let index = self.func_loc_index(key)?; let loc = &self.func_locs[index]; if loc.is_empty() { None } else { Some(loc) } } fn src_loc_index(&self, key: FuncKey) -> Option { let (key_ns, key_index) = key.into_parts(); if !Self::has_src_locs(key_ns.kind()) { return None; } let ns_idx = self.namespace_index(key_ns)?; let Range { start, end } = self.src_loc_range(ns_idx); debug_assert!(Self::is_dense(key_ns.kind())); let index = start.as_u32().checked_add(key_index.into_raw())?; let index = SrcLocIndex::from_u32(index); if index >= end { return None; } Some(index) } /// Get the initial source location of the function associated with the /// given `key`, if any. pub fn src_loc(&self, key: FuncKey) -> Option { let index = self.src_loc_index(key)?; let loc = self.src_locs[index]; if loc.is_none() { None } else { Some(loc) } } /// Given an offset into the text section, get the key for its associated /// function and its offset within that function. pub fn func_by_text_offset(&self, text_offset: u32) -> Option { let index = match self.func_locs.as_values_slice().binary_search_by(|loc| { if loc.is_empty() { loc.start .cmp(&text_offset) .then_with(|| core::cmp::Ordering::Less) } else { if loc.start > text_offset { core::cmp::Ordering::Greater } else if loc.start + loc.length <= text_offset { core::cmp::Ordering::Less } else { debug_assert!(loc.start <= text_offset); debug_assert!(text_offset < loc.start + loc.length); core::cmp::Ordering::Equal } } }) { // Exact match, the offset is at the end of this function. Ok(k) => k, // Not an exact match: `k` is where the offset would be // "inserted". Since we key based on the end, function `k` might // contain the offset, so we'll validate on the range check // below. Err(k) => k, }; let index = FuncLocIndex::new(index); // Make sure that the text offset is actually within this function. // Non-exact binary search results can either be because we have a text // offset within a function but not exactly at its inclusive end, or // because the text offset is not within any of our functions. We filter // that latter case out with this check. let loc = self.func_locs.get(index)?; let start = loc.start; let end = start + loc.length; if text_offset < start || end < text_offset { return None; } let ns_idx = match self .func_loc_starts .binary_search_values_by_key(&index, |s| *s) { // Exact match: `i` is the entry's index. Ok(i) => i, // Not an exact match: the index, if it were the start of a // namespace's range, would be at `i`. Therefore, our namespace // entry is actually at index `i - 1`. Err(i) => { let i = i.as_u32(); assert_ne!(i, 0); NamespaceIndex::from_u32(i - 1) } }; let key_ns = self.namespaces[ns_idx]; let start = self.func_loc_starts[ns_idx]; let key_index = if Self::is_dense(key_ns.kind()) { let key_index = index.as_u32() - start.as_u32(); FuncKeyIndex::from_raw(key_index) } else { let sparse_offset = index.as_u32() - start.as_u32(); let sparse_start = self.sparse_starts[ns_idx]; let sparse_index = SparseIndex::from_u32(sparse_start.as_u32() + sparse_offset); debug_assert!( { let range = self.sparse_range(ns_idx); range.start <= sparse_index && sparse_index < range.end }, "{sparse_index:?} is not within {:?}", self.sparse_range(ns_idx) ); self.sparse_indices[sparse_index] }; let key = FuncKey::from_parts(key_ns, key_index); Some(key) } /// Whether the given kind's index space is (generally) densely populated /// and therefore we should densely pack them in the table for `O(1)` /// lookups; otherwise, we should avoid code size bloat by using the sparse /// table indirection and `O(log n)` binary search lookups. fn is_dense(kind: FuncKeyKind) -> bool { match kind { FuncKeyKind::DefinedWasmFunction | FuncKeyKind::WasmToArrayTrampoline | FuncKeyKind::PulleyHostCall => true, FuncKeyKind::ArrayToWasmTrampoline | FuncKeyKind::WasmToBuiltinTrampoline | FuncKeyKind::PatchableToBuiltinTrampoline => false, #[cfg(feature = "component-model")] FuncKeyKind::ComponentTrampoline | FuncKeyKind::ResourceDropTrampoline | FuncKeyKind::UnsafeIntrinsic => true, } } /// Whether the given function kind has source locations or not. fn has_src_locs(kind: FuncKeyKind) -> bool { match kind { FuncKeyKind::DefinedWasmFunction => true, FuncKeyKind::ArrayToWasmTrampoline | FuncKeyKind::WasmToArrayTrampoline | FuncKeyKind::WasmToBuiltinTrampoline | FuncKeyKind::PatchableToBuiltinTrampoline | FuncKeyKind::PulleyHostCall => false, #[cfg(feature = "component-model")] FuncKeyKind::ComponentTrampoline | FuncKeyKind::ResourceDropTrampoline | FuncKeyKind::UnsafeIntrinsic => false, } } } /// Secondary in-memory results of module compilation. /// /// This opaque structure can be optionally passed back to /// `CompiledModule::from_artifacts` to avoid decoding extra information there. #[derive(Serialize, Deserialize)] pub struct CompiledModuleInfo { /// Type information about the compiled WebAssembly module. pub module: Module, /// General compilation metadata. pub meta: Metadata, /// Sorted list, by function index, of names we have for this module. pub func_names: Vec, /// Checksum of the source Wasm binary from which this module was compiled. pub checksum: WasmChecksum, } /// The name of a function stored in the /// [`ELF_NAME_DATA`](crate::obj::ELF_NAME_DATA) section. #[derive(Serialize, Deserialize)] pub struct FunctionName { /// The Wasm function index of this function. pub idx: FuncIndex, /// The offset of the name in the /// [`ELF_NAME_DATA`](crate::obj::ELF_NAME_DATA) section. pub offset: u32, /// The length of the name in bytes. pub len: u32, } /// Metadata associated with a compiled ELF artifact. #[derive(Serialize, Deserialize)] pub struct Metadata { /// Whether or not the original wasm module contained debug information that /// we skipped and did not parse. pub has_unparsed_debuginfo: bool, /// Offset in the original wasm file to the code section. pub code_section_offset: u64, /// Whether or not custom wasm-specific dwarf sections were inserted into /// the ELF image. /// /// Note that even if this flag is `true` sections may be missing if they /// weren't found in the original wasm module itself. pub has_wasm_debuginfo: bool, /// Dwarf sections and the offsets at which they're stored in the /// ELF_WASMTIME_DWARF pub dwarf: Vec<(u8, Range)>, } /// Value of a configured setting for a [`Compiler`](crate::Compiler) #[derive(Serialize, Deserialize, Hash, Eq, PartialEq, Debug)] pub enum FlagValue<'a> { /// Name of the value that has been configured for this setting. Enum(&'a str), /// The numerical value of the configured settings. Num(u8), /// Whether the setting is on or off. Bool(bool), } impl fmt::Display for FlagValue<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::Enum(v) => v.fmt(f), Self::Num(v) => v.fmt(f), Self::Bool(v) => v.fmt(f), } } } /// Types of objects that can be created by `Compiler::object` pub enum ObjectKind { /// A core wasm compilation artifact Module, /// A component compilation artifact Component, } #[cfg(test)] mod tests { use super::*; use crate::{DefinedFuncIndex, StaticModuleIndex}; fn func_loc(range: Range) -> FunctionLoc { FunctionLoc { start: range.start, length: range.end - range.start, } } fn def_func_key(m: u32, f: u32) -> FuncKey { FuncKey::DefinedWasmFunction( StaticModuleIndex::from_u32(m), DefinedFuncIndex::from_u32(f), ) } fn array_to_wasm_tramp_key(m: u32, f: u32) -> FuncKey { FuncKey::ArrayToWasmTrampoline( StaticModuleIndex::from_u32(m), DefinedFuncIndex::from_u32(f), ) } fn make_test_table() -> CompiledFunctionsTable { let mut builder = CompiledFunctionsTableBuilder::new(); builder // ========= Dense ========= .push_func(def_func_key(0, 0), func_loc(0..10), FilePos::new(111)) .push_func(def_func_key(0, 1), func_loc(10..20), FilePos::new(222)) .push_func(def_func_key(0, 2), func_loc(20..30), FilePos::none()) // Gap in dense keys! .push_func(def_func_key(0, 5), func_loc(30..40), FilePos::new(333)) // ========= Sparse ========= .push_func( array_to_wasm_tramp_key(0, 1), func_loc(100..110), FilePos::none(), ) .push_func( array_to_wasm_tramp_key(0, 2), func_loc(110..120), FilePos::none(), ) .push_func( array_to_wasm_tramp_key(0, 5), func_loc(120..130), FilePos::none(), ); builder.finish() } #[test] fn src_locs() { let table = make_test_table(); for (key, expected) in [ (def_func_key(0, 0), Some(FilePos::new(111))), (def_func_key(0, 1), Some(FilePos::new(222))), (def_func_key(0, 2), None), (def_func_key(0, 3), None), (def_func_key(0, 4), None), (def_func_key(0, 5), Some(FilePos::new(333))), (array_to_wasm_tramp_key(0, 0), None), (array_to_wasm_tramp_key(0, 1), None), (array_to_wasm_tramp_key(0, 2), None), (array_to_wasm_tramp_key(0, 3), None), (array_to_wasm_tramp_key(0, 4), None), (array_to_wasm_tramp_key(0, 5), None), ] { eprintln!("Checking key {key:?}"); let actual = table.src_loc(key); assert_eq!(expected, actual); } } #[test] fn func_locs() { let table = make_test_table(); for (key, expected) in [ (def_func_key(0, 0), Some(0)), (def_func_key(0, 1), Some(10)), (def_func_key(0, 2), Some(20)), (def_func_key(0, 3), None), (def_func_key(0, 4), None), (def_func_key(0, 5), Some(30)), (array_to_wasm_tramp_key(0, 0), None), (array_to_wasm_tramp_key(0, 1), Some(100)), (array_to_wasm_tramp_key(0, 2), Some(110)), (array_to_wasm_tramp_key(0, 3), None), (array_to_wasm_tramp_key(0, 4), None), (array_to_wasm_tramp_key(0, 5), Some(120)), ] { let actual = table.func_loc(key); match (expected, actual) { (None, None) => {} (Some(expected), Some(actual)) => assert_eq!(expected, actual.start), (None, Some(actual)) => { panic!("expected no function location for {key:?}, got {actual:?}") } (Some(_), None) => { panic!("expected a function location for {key:?}, but got nothing") } } } } #[test] fn reverse_func_locs() { let table = make_test_table(); for (range, expected) in [ (0..10, Some(def_func_key(0, 0))), (10..20, Some(def_func_key(0, 1))), (20..30, Some(def_func_key(0, 2))), (30..40, Some(def_func_key(0, 5))), (40..100, None), (100..110, Some(array_to_wasm_tramp_key(0, 1))), (110..120, Some(array_to_wasm_tramp_key(0, 2))), (120..130, Some(array_to_wasm_tramp_key(0, 5))), (140..150, None), ] { for i in range { eprintln!("Checking offset {i}"); let actual = table.func_by_text_offset(i); assert_eq!(expected, actual); } } } #[test] fn reverse_lookups() { use arbitrary::{Result, Unstructured}; arbtest::arbtest(|u| run(u)).budget_ms(1_000); fn run(u: &mut Unstructured<'_>) -> Result<()> { let mut funcs = Vec::new(); // Build up a random set of functions with random indices. for _ in 0..u.int_in_range(1..=200)? { let key = match u.int_in_range(0..=6)? { 0 => FuncKey::DefinedWasmFunction(idx(u, 10)?, idx(u, 200)?), 1 => FuncKey::ArrayToWasmTrampoline(idx(u, 10)?, idx(u, 200)?), 2 => FuncKey::WasmToArrayTrampoline(idx(u, 100)?), 3 => FuncKey::WasmToBuiltinTrampoline(u.arbitrary()?), 4 => FuncKey::PulleyHostCall(u.arbitrary()?), 5 => FuncKey::ComponentTrampoline(u.arbitrary()?, idx(u, 50)?), 6 => FuncKey::ResourceDropTrampoline, _ => unreachable!(), }; funcs.push(key); } // Sort/dedup our list of `funcs` to satisfy the requirement of // `CompiledFunctionsTableBuilder::push_func`. funcs.sort(); funcs.dedup(); let mut builder = CompiledFunctionsTableBuilder::new(); let mut size = 0; let mut expected = Vec::new(); for key in funcs { let length = u.int_in_range(1..=10)?; for _ in 0..length { expected.push(key); } // println!("push {key:?} - {length}"); builder.push_func( key, FunctionLoc { start: size, length, }, FilePos::none(), ); size += length; } let index = builder.finish(); let mut expected = expected.iter(); for i in 0..size { // println!("lookup {i}"); let actual = index.func_by_text_offset(i).unwrap(); assert_eq!(Some(&actual), expected.next()); } Ok(()) } fn idx(u: &mut Unstructured<'_>, max: usize) -> Result where T: EntityRef, { Ok(T::new(u.int_in_range(0..=max - 1)?)) } } }