Path: blob/main/crates/environ/src/module_artifacts.rs
3073 views
//! Definitions of runtime structures and metadata which are serialized into ELF1//! with `bincode` as part of a module's compilation process.23use crate::prelude::*;4use crate::{FilePos, FuncIndex, FuncKey, FuncKeyIndex, FuncKeyKind, FuncKeyNamespace, Module};5use core::ops::Range;6use core::{fmt, u32};7use core::{iter, str};8use cranelift_entity::{EntityRef, PrimaryMap};9use serde_derive::{Deserialize, Serialize};1011/// Description of where a function is located in the text section of a12/// compiled image.13#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]14pub struct FunctionLoc {15/// The byte offset from the start of the text section where this16/// function starts.17pub start: u32,18/// The byte length of this function's function body.19pub length: u32,20}2122impl FunctionLoc {23/// Is this an empty function location?24#[inline]25pub fn is_empty(&self) -> bool {26self.length == 027}28}2930/// A builder for a `CompiledFunctionsTable`.31pub struct CompiledFunctionsTableBuilder {32inner: CompiledFunctionsTable,33}3435impl CompiledFunctionsTableBuilder {36/// Create a new builder.37pub fn new() -> Self {38Self {39inner: CompiledFunctionsTable {40namespaces: PrimaryMap::new(),41func_loc_starts: PrimaryMap::new(),42sparse_starts: PrimaryMap::new(),43src_loc_starts: PrimaryMap::new(),44sparse_indices: PrimaryMap::new(),45func_locs: PrimaryMap::new(),46src_locs: PrimaryMap::new(),47},48}49}5051fn last_namespace(&self) -> Option<FuncKeyNamespace> {52let (_, &ns) = self.inner.namespaces.last()?;53Some(ns)54}5556fn last_key_index(&self) -> Option<FuncKeyIndex> {57let (ns_idx, ns) = self.inner.namespaces.last()?;58let start = self.inner.func_loc_starts[ns_idx];59if CompiledFunctionsTable::is_dense(ns.kind()) {60let len = self.inner.func_locs.len();61let len = u32::try_from(len).unwrap();62let key_index = len - start.as_u32();63let key_index = FuncKeyIndex::from_raw(key_index);64Some(key_index)65} else {66let sparse_start = self.inner.sparse_starts[ns_idx];67if self.inner.sparse_indices.len() > sparse_start.index() {68let (_, &key_index) = self.inner.sparse_indices.last().unwrap();69Some(key_index)70} else {71None72}73}74}7576fn last_func_loc(&self) -> Option<FunctionLoc> {77let (_, &loc) = self.inner.func_locs.last()?;78Some(loc)79}8081/// Push a new entry into this builder.82///83/// Panics if the key or function location is out of order.84pub fn push_func(85&mut self,86key: FuncKey,87func_loc: FunctionLoc,88src_loc: FilePos,89) -> &mut Self {90let (key_ns, key_index) = key.into_parts();9192assert!(93self.last_namespace().is_none_or(|ns| ns <= key_ns),94"`FuncKey`s pushed out of order"95);96assert!(97self.last_key_index().is_none_or(98|i| i <= key_index || self.last_namespace().is_some_and(|ns| ns != key_ns)99),100"`FuncKey`s pushed out of order"101);102assert!(103self.last_func_loc()104.is_none_or(|l| l.start + l.length <= func_loc.start),105"`FunctionLoc`s pushed out of order"106);107108// Make sure that there is a `kind` entry for this key's kind.109let kind_start_index = self110.inner111.namespaces112.last()113.and_then(|(ns_idx, ns)| {114if *ns == key_ns {115Some(self.inner.func_loc_starts[ns_idx])116} else {117None118}119})120.unwrap_or_else(|| {121let start = self.inner.func_locs.next_key();122let ns_idx = self.inner.namespaces.push(key_ns);123let ns_idx2 = self.inner.func_loc_starts.push(start);124let ns_idx3 = self125.inner126.sparse_starts127.push(self.inner.sparse_indices.next_key());128let ns_idx4 = self129.inner130.src_loc_starts131.push(self.inner.src_locs.next_key());132debug_assert_eq!(ns_idx, ns_idx2);133debug_assert_eq!(ns_idx, ns_idx3);134debug_assert_eq!(ns_idx, ns_idx4);135start136});137138if CompiledFunctionsTable::is_dense(key.kind()) {139// Figure out the index within `func_locs` for this key's entry.140let index = kind_start_index.as_u32() + key_index.into_raw();141let index = FuncLocIndex::from_u32(index);142debug_assert!(self.inner.func_locs.get(index).is_none());143144// Fill in null entries for any key indices that have been omitted.145//146// Note that we need a null `FunctionLoc`, but we also need147// `func_locs` to be sorted so that we support reverse148// lookups. Therefore, we take care to create an empty function149// location that starts at the text offset that the previous one (if150// any) ends at, and use that as our null entry.151let null_func_loc = FunctionLoc {152start: self153.last_func_loc()154.map(|l| l.start + l.length)155.unwrap_or_default(),156length: 0,157};158let gap = index.index() - self.inner.func_locs.len();159self.inner160.func_locs161.extend(iter::repeat(null_func_loc).take(gap));162debug_assert_eq!(index, self.inner.func_locs.next_key());163164if CompiledFunctionsTable::has_src_locs(key_ns.kind()) {165self.inner166.src_locs167.extend(iter::repeat(FilePos::none()).take(gap));168}169} else {170debug_assert!(171src_loc.is_none(),172"sparse keys do not have source locations"173);174self.inner.sparse_indices.push(key_index);175}176177// And finally, we push this entry.178self.inner.func_locs.push(func_loc);179if CompiledFunctionsTable::has_src_locs(key_ns.kind()) {180self.inner.src_locs.push(src_loc);181} else {182debug_assert!(src_loc.is_none());183}184185self186}187188/// Finish construction of the `CompiledFunctionsTable`.189pub fn finish(self) -> CompiledFunctionsTable {190self.inner191}192}193194#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]195struct NamespaceIndex(u32);196cranelift_entity::entity_impl!(NamespaceIndex);197198#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]199struct FuncLocIndex(u32);200cranelift_entity::entity_impl!(FuncLocIndex);201202#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]203struct SparseIndex(u32);204cranelift_entity::entity_impl!(SparseIndex);205206#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]207struct SrcLocIndex(u32);208cranelift_entity::entity_impl!(SrcLocIndex);209210/// A table describing the set of functions compiled into an artifact, their211/// locations within the text section, and etc...212///213/// Logically, this type is a map from a `FuncKey` to the associated function's214///215/// * location within the associated text section, and216/// * optional source location.217///218/// How this map is *actually* implemented is with a series of lookup and binary219/// search tables, split out in a data-oriented, struct-of-arrays style. We220/// organize the data in this way is service of three goals:221///222/// 1. Provide fast look ups: We need to look up the metadata for a function by223/// its key at runtime. During instantiation, for example, we need to create224/// `VMFuncRef`s for escaping functions and this requires looking up the225/// locations of those Wasm functions and their associated array-to-Wasm226/// trampolines.227///228/// 2. Keep memory overheads low and code size small: This type is serialized229/// into all of our ELF artifacts and deserialized into all `Module`s and230/// `Component`s at runtime.231///232/// 3. Be generic over any kind of function (whether defined Wasm function,233/// trampoline, or etc...) that we compile: Adding a new kind of trampoline,234/// for example, should not require updating this structure to add a new235/// table of the function locations for just trampolines of that new kind. We236/// should be able to store and query all kinds of functions uniformly.237//238// TODO: This structure could be directly encoded as raw ELF sections, instead239// of a `struct` containing a bunch of `PrimaryMap`s, which would allow us to240// avoid the serialize/deserialize runtime costs.241#[derive(Debug, Serialize, Deserialize)]242pub struct CompiledFunctionsTable {243/// A binary-search index for this table, mapping raw `FuncKeyNamespace`s to244/// their associated `NamespaceIndex`. That `NamespaceIndex` can then be245/// used to find the range of other entity indices that are specific to that246/// namespace.247namespaces: PrimaryMap<NamespaceIndex, FuncKeyNamespace>,248249/// `self.func_loc_starts[i]..self.func_loc_starts[i+1]` describes the range250/// within `self.func_locs` whose entries are associated with the namespace251/// `self.index[i]`.252///253/// When `self.func_loc_starts[i+1]` is out of bounds, then the range is to254/// the end of `self.func_locs`.255func_loc_starts: PrimaryMap<NamespaceIndex, FuncLocIndex>,256257/// `self.sparse_starts[i]..self.sparse_starts[i+1]` describes the range258/// within `self.sparse_indices` whose entries are associated with the259/// namespace `self.index[i]`.260///261/// When `self.sparse_starts[i+1]` is out of bounds, then the range is to262/// the end of `self.sparse_indices`.263///264/// Entries are only valid for sparse, non-dense namespaces.265sparse_starts: PrimaryMap<NamespaceIndex, SparseIndex>,266267/// `self.src_loc_starts[i]..self.src_loc_starts[i+1]` describes the range268/// within `self.src_loc_indices` whose entries are associated with the269/// namespace `self.index[i]`.270///271/// When `self.src_loc_starts[i+1]` is out of bounds, then the range is to272/// the end of `self.src_locs`.273///274/// Entries are only valid for namespaces whose functions have source275/// locations.276src_loc_starts: PrimaryMap<NamespaceIndex, SrcLocIndex>,277278/// `self.sparse_indices[i]` contains the index part of279/// `FuncKey::from_parts(ns, index)` where `ns` is determined by280/// `self.sparse_starts` and is a sparse, non-dense key kind. (Note that for281/// dense keys, this information is implicitly encoded in their offset from282/// the namespace's start index.)283///284/// This is sorted to allow for binary searches.285sparse_indices: PrimaryMap<SparseIndex, FuncKeyIndex>,286287/// `self.func_locs[i]` contains the location within the text section of288/// `FuncKey::from_parts(self.namespaces[ns], i - start)`'s function, where289/// `ns` and `start` are determined by `self.func_loc_starts`.290///291/// Values are sorted by function location to support reverse queries from292/// function location back to `FuncKey`.293///294/// The absence of a function location (for gaps in dense namespaces) is295/// represented with `FunctionLoc::none()`.296func_locs: PrimaryMap<FuncLocIndex, FunctionLoc>,297298/// `self.src_locs[i]` contains the initial source location of299/// `FuncKey::from_parts(self.namespaces[ns], i - start)`'s function, where300/// `ns` and `start` are determined by `self.src_loc_starts`.301///302/// The absence of a source location is represented by `FilePos::none()`.303src_locs: PrimaryMap<SrcLocIndex, FilePos>,304}305306impl CompiledFunctionsTable {307#[inline]308fn namespace_index(&self, namespace: FuncKeyNamespace) -> Option<NamespaceIndex> {309const LINEAR_SEARCH_LIMIT: usize = 32;310if self.namespaces.len() <= LINEAR_SEARCH_LIMIT {311self.namespaces312.iter()313.find_map(|(idx, ns)| if *ns == namespace { Some(idx) } else { None })314} else {315self.namespaces316.binary_search_values_by_key(&namespace, |ns| *ns)317.ok()318}319}320321#[inline]322fn func_loc_range(&self, ns_idx: NamespaceIndex) -> Range<FuncLocIndex> {323let start = self.func_loc_starts[ns_idx];324let next_ns_idx = NamespaceIndex::from_u32(ns_idx.as_u32() + 1);325let end = self326.func_loc_starts327.get(next_ns_idx)328.copied()329.unwrap_or_else(|| self.func_locs.next_key());330start..end331}332333fn sparse_range(&self, ns_idx: NamespaceIndex) -> Range<SparseIndex> {334debug_assert!(!Self::is_dense(self.namespaces[ns_idx].kind()));335let start = self.sparse_starts[ns_idx];336let next_ns_idx = NamespaceIndex::from_u32(ns_idx.as_u32() + 1);337let end = self338.sparse_starts339.get(next_ns_idx)340.copied()341.unwrap_or_else(|| self.sparse_indices.next_key());342start..end343}344345fn src_loc_range(&self, ns_idx: NamespaceIndex) -> Range<SrcLocIndex> {346debug_assert!(Self::has_src_locs(self.namespaces[ns_idx].kind()));347let start = self.src_loc_starts[ns_idx];348let next_ns_idx = NamespaceIndex::from_u32(ns_idx.as_u32() + 1);349let end = self350.src_loc_starts351.get(next_ns_idx)352.copied()353.unwrap_or_else(|| self.src_locs.next_key());354start..end355}356357/// Get the index within `self.{func_locs,src_locs}` that is associated with358/// the given `key`, if any.359#[inline]360fn func_loc_index(&self, key: FuncKey) -> Option<FuncLocIndex> {361let (key_ns, key_index) = key.into_parts();362let ns_idx = self.namespace_index(key_ns)?;363let Range { start, end } = self.func_loc_range(ns_idx);364365let index = if Self::is_dense(key.kind()) {366let index = start.as_u32().checked_add(key_index.into_raw())?;367FuncLocIndex::from_u32(index)368} else {369let sparse_range = self.sparse_range(ns_idx);370let sparse_subslice = self.sparse_indices.get_range(sparse_range).unwrap();371match sparse_subslice.binary_search(&key_index) {372Ok(i) => FuncLocIndex::new(start.index() + i),373Err(_) => return None,374}375};376377if index < end { Some(index) } else { None }378}379380/// Get the location of the function associated with the given `key` inside381/// the text section, if any.382#[inline]383pub fn func_loc(&self, key: FuncKey) -> Option<&FunctionLoc> {384let index = self.func_loc_index(key)?;385let loc = &self.func_locs[index];386if loc.is_empty() { None } else { Some(loc) }387}388389fn src_loc_index(&self, key: FuncKey) -> Option<SrcLocIndex> {390let (key_ns, key_index) = key.into_parts();391if !Self::has_src_locs(key_ns.kind()) {392return None;393}394395let ns_idx = self.namespace_index(key_ns)?;396let Range { start, end } = self.src_loc_range(ns_idx);397398debug_assert!(Self::is_dense(key_ns.kind()));399let index = start.as_u32().checked_add(key_index.into_raw())?;400let index = SrcLocIndex::from_u32(index);401if index >= end {402return None;403}404405Some(index)406}407408/// Get the initial source location of the function associated with the409/// given `key`, if any.410pub fn src_loc(&self, key: FuncKey) -> Option<FilePos> {411let index = self.src_loc_index(key)?;412let loc = self.src_locs[index];413if loc.is_none() { None } else { Some(loc) }414}415416/// Given an offset into the text section, get the key for its associated417/// function and its offset within that function.418pub fn func_by_text_offset(&self, text_offset: u32) -> Option<FuncKey> {419let index = match self.func_locs.as_values_slice().binary_search_by(|loc| {420if loc.is_empty() {421loc.start422.cmp(&text_offset)423.then_with(|| core::cmp::Ordering::Less)424} else {425if loc.start > text_offset {426core::cmp::Ordering::Greater427} else if loc.start + loc.length <= text_offset {428core::cmp::Ordering::Less429} else {430debug_assert!(loc.start <= text_offset);431debug_assert!(text_offset < loc.start + loc.length);432core::cmp::Ordering::Equal433}434}435}) {436// Exact match, the offset is at the end of this function.437Ok(k) => k,438// Not an exact match: `k` is where the offset would be439// "inserted". Since we key based on the end, function `k` might440// contain the offset, so we'll validate on the range check441// below.442Err(k) => k,443};444let index = FuncLocIndex::new(index);445446// Make sure that the text offset is actually within this function.447// Non-exact binary search results can either be because we have a text448// offset within a function but not exactly at its inclusive end, or449// because the text offset is not within any of our functions. We filter450// that latter case out with this check.451let loc = self.func_locs.get(index)?;452let start = loc.start;453let end = start + loc.length;454if text_offset < start || end < text_offset {455return None;456}457458let ns_idx = match self459.func_loc_starts460.binary_search_values_by_key(&index, |s| *s)461{462// Exact match: `i` is the entry's index.463Ok(i) => i,464// Not an exact match: the index, if it were the start of a465// namespace's range, would be at `i`. Therefore, our namespace466// entry is actually at index `i - 1`.467Err(i) => {468let i = i.as_u32();469assert_ne!(i, 0);470NamespaceIndex::from_u32(i - 1)471}472};473let key_ns = self.namespaces[ns_idx];474let start = self.func_loc_starts[ns_idx];475476let key_index = if Self::is_dense(key_ns.kind()) {477let key_index = index.as_u32() - start.as_u32();478FuncKeyIndex::from_raw(key_index)479} else {480let sparse_offset = index.as_u32() - start.as_u32();481let sparse_start = self.sparse_starts[ns_idx];482let sparse_index = SparseIndex::from_u32(sparse_start.as_u32() + sparse_offset);483debug_assert!(484{485let range = self.sparse_range(ns_idx);486range.start <= sparse_index && sparse_index < range.end487},488"{sparse_index:?} is not within {:?}",489self.sparse_range(ns_idx)490);491self.sparse_indices[sparse_index]492};493let key = FuncKey::from_parts(key_ns, key_index);494495Some(key)496}497498/// Whether the given kind's index space is (generally) densely populated499/// and therefore we should densely pack them in the table for `O(1)`500/// lookups; otherwise, we should avoid code size bloat by using the sparse501/// table indirection and `O(log n)` binary search lookups.502fn is_dense(kind: FuncKeyKind) -> bool {503match kind {504FuncKeyKind::DefinedWasmFunction505| FuncKeyKind::WasmToArrayTrampoline506| FuncKeyKind::PulleyHostCall => true,507508FuncKeyKind::ArrayToWasmTrampoline509| FuncKeyKind::WasmToBuiltinTrampoline510| FuncKeyKind::PatchableToBuiltinTrampoline => false,511512#[cfg(feature = "component-model")]513FuncKeyKind::ComponentTrampoline514| FuncKeyKind::ResourceDropTrampoline515| FuncKeyKind::UnsafeIntrinsic => true,516}517}518519/// Whether the given function kind has source locations or not.520fn has_src_locs(kind: FuncKeyKind) -> bool {521match kind {522FuncKeyKind::DefinedWasmFunction => true,523FuncKeyKind::ArrayToWasmTrampoline524| FuncKeyKind::WasmToArrayTrampoline525| FuncKeyKind::WasmToBuiltinTrampoline526| FuncKeyKind::PatchableToBuiltinTrampoline527| FuncKeyKind::PulleyHostCall => false,528#[cfg(feature = "component-model")]529FuncKeyKind::ComponentTrampoline530| FuncKeyKind::ResourceDropTrampoline531| FuncKeyKind::UnsafeIntrinsic => false,532}533}534}535536/// Secondary in-memory results of module compilation.537///538/// This opaque structure can be optionally passed back to539/// `CompiledModule::from_artifacts` to avoid decoding extra information there.540#[derive(Serialize, Deserialize)]541pub struct CompiledModuleInfo {542/// Type information about the compiled WebAssembly module.543pub module: Module,544545/// General compilation metadata.546pub meta: Metadata,547548/// Sorted list, by function index, of names we have for this module.549pub func_names: Vec<FunctionName>,550}551552/// The name of a function stored in the553/// [`ELF_NAME_DATA`](crate::obj::ELF_NAME_DATA) section.554#[derive(Serialize, Deserialize)]555pub struct FunctionName {556/// The Wasm function index of this function.557pub idx: FuncIndex,558/// The offset of the name in the559/// [`ELF_NAME_DATA`](crate::obj::ELF_NAME_DATA) section.560pub offset: u32,561/// The length of the name in bytes.562pub len: u32,563}564565/// Metadata associated with a compiled ELF artifact.566#[derive(Serialize, Deserialize)]567pub struct Metadata {568/// Whether or not the original wasm module contained debug information that569/// we skipped and did not parse.570pub has_unparsed_debuginfo: bool,571572/// Offset in the original wasm file to the code section.573pub code_section_offset: u64,574575/// Whether or not custom wasm-specific dwarf sections were inserted into576/// the ELF image.577///578/// Note that even if this flag is `true` sections may be missing if they579/// weren't found in the original wasm module itself.580pub has_wasm_debuginfo: bool,581582/// Dwarf sections and the offsets at which they're stored in the583/// ELF_WASMTIME_DWARF584pub dwarf: Vec<(u8, Range<u64>)>,585}586587/// Value of a configured setting for a [`Compiler`](crate::Compiler)588#[derive(Serialize, Deserialize, Hash, Eq, PartialEq, Debug)]589pub enum FlagValue<'a> {590/// Name of the value that has been configured for this setting.591Enum(&'a str),592/// The numerical value of the configured settings.593Num(u8),594/// Whether the setting is on or off.595Bool(bool),596}597598impl fmt::Display for FlagValue<'_> {599fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {600match self {601Self::Enum(v) => v.fmt(f),602Self::Num(v) => v.fmt(f),603Self::Bool(v) => v.fmt(f),604}605}606}607608/// Types of objects that can be created by `Compiler::object`609pub enum ObjectKind {610/// A core wasm compilation artifact611Module,612/// A component compilation artifact613Component,614}615616#[cfg(test)]617mod tests {618use super::*;619use crate::{DefinedFuncIndex, StaticModuleIndex};620621fn func_loc(range: Range<u32>) -> FunctionLoc {622FunctionLoc {623start: range.start,624length: range.end - range.start,625}626}627628fn def_func_key(m: u32, f: u32) -> FuncKey {629FuncKey::DefinedWasmFunction(630StaticModuleIndex::from_u32(m),631DefinedFuncIndex::from_u32(f),632)633}634635fn array_to_wasm_tramp_key(m: u32, f: u32) -> FuncKey {636FuncKey::ArrayToWasmTrampoline(637StaticModuleIndex::from_u32(m),638DefinedFuncIndex::from_u32(f),639)640}641642fn make_test_table() -> CompiledFunctionsTable {643let mut builder = CompiledFunctionsTableBuilder::new();644645builder646// ========= Dense =========647.push_func(def_func_key(0, 0), func_loc(0..10), FilePos::new(111))648.push_func(def_func_key(0, 1), func_loc(10..20), FilePos::new(222))649.push_func(def_func_key(0, 2), func_loc(20..30), FilePos::none())650// Gap in dense keys!651.push_func(def_func_key(0, 5), func_loc(30..40), FilePos::new(333))652// ========= Sparse =========653.push_func(654array_to_wasm_tramp_key(0, 1),655func_loc(100..110),656FilePos::none(),657)658.push_func(659array_to_wasm_tramp_key(0, 2),660func_loc(110..120),661FilePos::none(),662)663.push_func(664array_to_wasm_tramp_key(0, 5),665func_loc(120..130),666FilePos::none(),667);668669builder.finish()670}671672#[test]673fn src_locs() {674let table = make_test_table();675676for (key, expected) in [677(def_func_key(0, 0), Some(FilePos::new(111))),678(def_func_key(0, 1), Some(FilePos::new(222))),679(def_func_key(0, 2), None),680(def_func_key(0, 3), None),681(def_func_key(0, 4), None),682(def_func_key(0, 5), Some(FilePos::new(333))),683(array_to_wasm_tramp_key(0, 0), None),684(array_to_wasm_tramp_key(0, 1), None),685(array_to_wasm_tramp_key(0, 2), None),686(array_to_wasm_tramp_key(0, 3), None),687(array_to_wasm_tramp_key(0, 4), None),688(array_to_wasm_tramp_key(0, 5), None),689] {690eprintln!("Checking key {key:?}");691let actual = table.src_loc(key);692assert_eq!(expected, actual);693}694}695696#[test]697fn func_locs() {698let table = make_test_table();699700for (key, expected) in [701(def_func_key(0, 0), Some(0)),702(def_func_key(0, 1), Some(10)),703(def_func_key(0, 2), Some(20)),704(def_func_key(0, 3), None),705(def_func_key(0, 4), None),706(def_func_key(0, 5), Some(30)),707(array_to_wasm_tramp_key(0, 0), None),708(array_to_wasm_tramp_key(0, 1), Some(100)),709(array_to_wasm_tramp_key(0, 2), Some(110)),710(array_to_wasm_tramp_key(0, 3), None),711(array_to_wasm_tramp_key(0, 4), None),712(array_to_wasm_tramp_key(0, 5), Some(120)),713] {714let actual = table.func_loc(key);715match (expected, actual) {716(None, None) => {}717(Some(expected), Some(actual)) => assert_eq!(expected, actual.start),718(None, Some(actual)) => {719panic!("expected no function location for {key:?}, got {actual:?}")720}721(Some(_), None) => {722panic!("expected a function location for {key:?}, but got nothing")723}724}725}726}727728#[test]729fn reverse_func_locs() {730let table = make_test_table();731732for (range, expected) in [733(0..10, Some(def_func_key(0, 0))),734(10..20, Some(def_func_key(0, 1))),735(20..30, Some(def_func_key(0, 2))),736(30..40, Some(def_func_key(0, 5))),737(40..100, None),738(100..110, Some(array_to_wasm_tramp_key(0, 1))),739(110..120, Some(array_to_wasm_tramp_key(0, 2))),740(120..130, Some(array_to_wasm_tramp_key(0, 5))),741(140..150, None),742] {743for i in range {744eprintln!("Checking offset {i}");745let actual = table.func_by_text_offset(i);746assert_eq!(expected, actual);747}748}749}750751#[test]752fn reverse_lookups() {753use arbitrary::{Result, Unstructured};754755arbtest::arbtest(|u| run(u)).budget_ms(1_000);756757fn run(u: &mut Unstructured<'_>) -> Result<()> {758let mut funcs = Vec::new();759760// Build up a random set of functions with random indices.761for _ in 0..u.int_in_range(1..=200)? {762let key = match u.int_in_range(0..=6)? {7630 => FuncKey::DefinedWasmFunction(idx(u, 10)?, idx(u, 200)?),7641 => FuncKey::ArrayToWasmTrampoline(idx(u, 10)?, idx(u, 200)?),7652 => FuncKey::WasmToArrayTrampoline(idx(u, 100)?),7663 => FuncKey::WasmToBuiltinTrampoline(u.arbitrary()?),7674 => FuncKey::PulleyHostCall(u.arbitrary()?),7685 => FuncKey::ComponentTrampoline(u.arbitrary()?, idx(u, 50)?),7696 => FuncKey::ResourceDropTrampoline,770_ => unreachable!(),771};772funcs.push(key);773}774775// Sort/dedup our list of `funcs` to satisfy the requirement of776// `CompiledFunctionsTableBuilder::push_func`.777funcs.sort();778funcs.dedup();779780let mut builder = CompiledFunctionsTableBuilder::new();781let mut size = 0;782let mut expected = Vec::new();783for key in funcs {784let length = u.int_in_range(1..=10)?;785for _ in 0..length {786expected.push(key);787}788// println!("push {key:?} - {length}");789builder.push_func(790key,791FunctionLoc {792start: size,793length,794},795FilePos::none(),796);797size += length;798}799let index = builder.finish();800801let mut expected = expected.iter();802for i in 0..size {803// println!("lookup {i}");804let actual = index.func_by_text_offset(i).unwrap();805assert_eq!(Some(&actual), expected.next());806}807808Ok(())809}810811fn idx<T>(u: &mut Unstructured<'_>, max: usize) -> Result<T>812where813T: EntityRef,814{815Ok(T::new(u.int_in_range(0..=max - 1)?))816}817}818}819820821