use crate::prelude::*;
use crate::{FilePos, FuncIndex, FuncKey, FuncKeyIndex, FuncKeyKind, FuncKeyNamespace, Module};
use core::ops::Range;
use core::{fmt, u32};
use core::{iter, str};
use cranelift_entity::{EntityRef, PrimaryMap};
use serde_derive::{Deserialize, Serialize};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct FunctionLoc {
pub start: u32,
pub length: u32,
}
impl FunctionLoc {
#[inline]
pub fn is_empty(&self) -> bool {
self.length == 0
}
}
pub struct CompiledFunctionsTableBuilder {
inner: CompiledFunctionsTable,
}
impl CompiledFunctionsTableBuilder {
pub fn new() -> Self {
Self {
inner: CompiledFunctionsTable {
namespaces: PrimaryMap::new(),
func_loc_starts: PrimaryMap::new(),
sparse_starts: PrimaryMap::new(),
src_loc_starts: PrimaryMap::new(),
sparse_indices: PrimaryMap::new(),
func_locs: PrimaryMap::new(),
src_locs: PrimaryMap::new(),
},
}
}
fn last_namespace(&self) -> Option<FuncKeyNamespace> {
let (_, &ns) = self.inner.namespaces.last()?;
Some(ns)
}
fn last_key_index(&self) -> Option<FuncKeyIndex> {
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<FunctionLoc> {
let (_, &loc) = self.inner.func_locs.last()?;
Some(loc)
}
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"
);
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);
let ns_idx2 = self.inner.func_loc_starts.push(start);
let ns_idx3 = self
.inner
.sparse_starts
.push(self.inner.sparse_indices.next_key());
let ns_idx4 = self
.inner
.src_loc_starts
.push(self.inner.src_locs.next_key());
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()) {
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());
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
.extend(iter::repeat(null_func_loc).take(gap));
debug_assert_eq!(index, self.inner.func_locs.next_key());
if CompiledFunctionsTable::has_src_locs(key_ns.kind()) {
self.inner
.src_locs
.extend(iter::repeat(FilePos::none()).take(gap));
}
} else {
debug_assert!(
src_loc.is_none(),
"sparse keys do not have source locations"
);
self.inner.sparse_indices.push(key_index);
}
self.inner.func_locs.push(func_loc);
if CompiledFunctionsTable::has_src_locs(key_ns.kind()) {
self.inner.src_locs.push(src_loc);
} else {
debug_assert!(src_loc.is_none());
}
self
}
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);
#[derive(Debug, Serialize, Deserialize)]
pub struct CompiledFunctionsTable {
namespaces: PrimaryMap<NamespaceIndex, FuncKeyNamespace>,
func_loc_starts: PrimaryMap<NamespaceIndex, FuncLocIndex>,
sparse_starts: PrimaryMap<NamespaceIndex, SparseIndex>,
src_loc_starts: PrimaryMap<NamespaceIndex, SrcLocIndex>,
sparse_indices: PrimaryMap<SparseIndex, FuncKeyIndex>,
func_locs: PrimaryMap<FuncLocIndex, FunctionLoc>,
src_locs: PrimaryMap<SrcLocIndex, FilePos>,
}
impl CompiledFunctionsTable {
#[inline]
fn namespace_index(&self, namespace: FuncKeyNamespace) -> Option<NamespaceIndex> {
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<FuncLocIndex> {
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<SparseIndex> {
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<SrcLocIndex> {
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
}
#[inline]
fn func_loc_index(&self, key: FuncKey) -> Option<FuncLocIndex> {
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 }
}
#[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<SrcLocIndex> {
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)
}
pub fn src_loc(&self, key: FuncKey) -> Option<FilePos> {
let index = self.src_loc_index(key)?;
let loc = self.src_locs[index];
if loc.is_none() { None } else { Some(loc) }
}
pub fn func_by_text_offset(&self, text_offset: u32) -> Option<FuncKey> {
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
}
}
}) {
Ok(k) => k,
Err(k) => k,
};
let index = FuncLocIndex::new(index);
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)
{
Ok(i) => i,
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)
}
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,
}
}
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,
}
}
}
#[derive(Serialize, Deserialize)]
pub struct CompiledModuleInfo {
pub module: Module,
pub meta: Metadata,
pub func_names: Vec<FunctionName>,
}
#[derive(Serialize, Deserialize)]
pub struct FunctionName {
pub idx: FuncIndex,
pub offset: u32,
pub len: u32,
}
#[derive(Serialize, Deserialize)]
pub struct Metadata {
pub has_unparsed_debuginfo: bool,
pub code_section_offset: u64,
pub has_wasm_debuginfo: bool,
pub dwarf: Vec<(u8, Range<u64>)>,
}
#[derive(Serialize, Deserialize, Hash, Eq, PartialEq, Debug)]
pub enum FlagValue<'a> {
Enum(&'a str),
Num(u8),
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),
}
}
}
pub enum ObjectKind {
Module,
Component,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{DefinedFuncIndex, StaticModuleIndex};
fn func_loc(range: Range<u32>) -> 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
.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())
.push_func(def_func_key(0, 5), func_loc(30..40), FilePos::new(333))
.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();
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);
}
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);
}
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 {
let actual = index.func_by_text_offset(i).unwrap();
assert_eq!(Some(&actual), expected.next());
}
Ok(())
}
fn idx<T>(u: &mut Unstructured<'_>, max: usize) -> Result<T>
where
T: EntityRef,
{
Ok(T::new(u.int_in_range(0..=max - 1)?))
}
}
}