1use crate::prelude::*;
5use crate::{
6 EntityRef, FilePos, FuncIndex, FuncKey, FuncKeyIndex, FuncKeyKind, FuncKeyNamespace, Module,
7 PanicOnOom as _, collections::PrimaryMap,
8};
9use core::ops::Range;
10use core::{fmt, u32};
11use core::{iter, str};
12use serde_derive::{Deserialize, Serialize};
13#[cfg(feature = "rr")]
14use sha2::{Digest, Sha256};
15
16#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
19pub struct FunctionLoc {
20 pub start: u32,
23 pub length: u32,
25}
26
27impl FunctionLoc {
28 #[inline]
30 pub fn is_empty(&self) -> bool {
31 self.length == 0
32 }
33}
34
35#[derive(Copy, Clone, Default, PartialEq, Eq, Ord, PartialOrd, Debug, Serialize, Deserialize)]
40pub struct WasmChecksum([u8; 32]);
41
42impl WasmChecksum {
43 #[cfg(feature = "rr")]
47 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 #[cfg(not(feature = "rr"))]
58 pub fn from_binary(_: &[u8], _: bool) -> WasmChecksum {
59 WasmChecksum::default()
60 }
61}
62
63impl core::ops::Deref for WasmChecksum {
64 type Target = [u8; 32];
65
66 fn deref(&self) -> &Self::Target {
67 &self.0
68 }
69}
70
71pub struct CompiledFunctionsTableBuilder {
73 inner: CompiledFunctionsTable,
74}
75
76impl CompiledFunctionsTableBuilder {
77 pub fn new() -> Self {
79 Self {
80 inner: CompiledFunctionsTable {
81 namespaces: PrimaryMap::new(),
82 func_loc_starts: PrimaryMap::new(),
83 sparse_starts: PrimaryMap::new(),
84 src_loc_starts: PrimaryMap::new(),
85 sparse_indices: PrimaryMap::new(),
86 func_locs: PrimaryMap::new(),
87 src_locs: PrimaryMap::new(),
88 },
89 }
90 }
91
92 fn last_namespace(&self) -> Option<FuncKeyNamespace> {
93 let (_, &ns) = self.inner.namespaces.last()?;
94 Some(ns)
95 }
96
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
117 fn last_func_loc(&self) -> Option<FunctionLoc> {
118 let (_, &loc) = self.inner.func_locs.last()?;
119 Some(loc)
120 }
121
122 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 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 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 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 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 pub fn finish(self) -> CompiledFunctionsTable {
235 self.inner
236 }
237}
238
239#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
240struct NamespaceIndex(u32);
241cranelift_entity::entity_impl!(NamespaceIndex);
242
243#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
244struct FuncLocIndex(u32);
245cranelift_entity::entity_impl!(FuncLocIndex);
246
247#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
248struct SparseIndex(u32);
249cranelift_entity::entity_impl!(SparseIndex);
250
251#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
252struct SrcLocIndex(u32);
253cranelift_entity::entity_impl!(SrcLocIndex);
254
255#[derive(Debug, Serialize, Deserialize)]
287pub struct CompiledFunctionsTable {
288 namespaces: PrimaryMap<NamespaceIndex, FuncKeyNamespace>,
293
294 func_loc_starts: PrimaryMap<NamespaceIndex, FuncLocIndex>,
301
302 sparse_starts: PrimaryMap<NamespaceIndex, SparseIndex>,
311
312 src_loc_starts: PrimaryMap<NamespaceIndex, SrcLocIndex>,
322
323 sparse_indices: PrimaryMap<SparseIndex, FuncKeyIndex>,
331
332 func_locs: PrimaryMap<FuncLocIndex, FunctionLoc>,
342
343 src_locs: PrimaryMap<SrcLocIndex, FilePos>,
349}
350
351impl CompiledFunctionsTable {
352 #[inline]
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]
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
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
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 #[inline]
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 #[inline]
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
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 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 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 Ok(k) => k,
483 Err(k) => k,
488 };
489 let index = FuncLocIndex::new(index);
490
491 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 Ok(i) => i,
509 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 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 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#[derive(Serialize, Deserialize)]
586pub struct CompiledModuleInfo {
587 pub module: Module,
589
590 pub meta: Metadata,
592
593 pub func_names: Vec<FunctionName>,
595
596 pub checksum: WasmChecksum,
598}
599
600#[derive(Serialize, Deserialize)]
603pub struct FunctionName {
604 pub idx: FuncIndex,
606 pub offset: u32,
609 pub len: u32,
611}
612
613#[derive(Serialize, Deserialize)]
615pub struct Metadata {
616 pub has_unparsed_debuginfo: bool,
619
620 pub code_section_offset: u64,
622
623 pub has_wasm_debuginfo: bool,
629
630 pub dwarf: Vec<(u8, Range<u64>)>,
633}
634
635#[derive(Serialize, Deserialize, Hash, Eq, PartialEq, Debug)]
637pub enum FlagValue<'a> {
638 Enum(&'a str),
640 Num(u8),
642 Bool(bool),
644}
645
646impl fmt::Display for FlagValue<'_> {
647 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
656pub enum ObjectKind {
658 Module,
660 Component,
662}
663
664#[cfg(test)]
665mod tests {
666 use super::*;
667 use crate::{DefinedFuncIndex, StaticModuleIndex};
668
669 fn func_loc(range: Range<u32>) -> FunctionLoc {
670 FunctionLoc {
671 start: range.start,
672 length: range.end - range.start,
673 }
674 }
675
676 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
683 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
690 fn make_test_table() -> CompiledFunctionsTable {
691 let mut builder = CompiledFunctionsTableBuilder::new();
692
693 builder
694 .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 .push_func(def_func_key(0, 5), func_loc(30..40), FilePos::new(333))
700 .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]
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]
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]
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]
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 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 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 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 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}