Repository: bytecodealliance/wasmtime
Pull request #8693 (2024-05-25 19:55:05)

Add basic support for DWARF processing with components
This commit updates the native-DWARF processing (the `-D debug-info` CLI flag) to support components. Previously component support was not implemented and if there was more than one core wasm module within a component then dwarf would be ignored entirely.

This commit contains a number of refactorings to plumb a more full compilation context throughout the dwarf processing pipeline. Previously the data structures used only were able to support a single module. A new `Compilation` structure is used to represent the results of an entire compilation and is plumbed through the various locations. Most of the refactorings in this commit were then to extend loops to loop over more things and handle the case where there is more than one core wasm module.

I'll admit I'm not expert on DWARF but basic examples appear to work locally and most of the additions here seemed relatively straightforward in terms of "add another loop to iterate over more things" but I'm not 100% sure how well this will work. In theory this now supports concatenating DWARF sections across multiple core wasm modules, but that's not super well tested.

<!--
Please make sure you include the following information:

- If this work has been discussed elsewhere, please include a link to that
  conversation. If it was discussed in an issue, just mention "issue #...".

- Explain why this change is needed. If the details are in an issue already,
  this can be brief.

Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.html

Please ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->


github-actions[bot]: #### Subscribe to Label Action

cc @saulecabrera

<details>
This issue or pull request has been labeled: "wasmtime:api", "winch"

Thus the following users have been cc'd because of the following labels:

* saulecabrera: winch

To subscribe or unsubscribe from this label, edit the <code>.github/subscribe-to-label.json</code> configuration file.

[Learn more.](https://github.com/bytecodealliance/subscribe-to-label-action)
</details>

Patch:
diff --git a/crates/cranelift/src/compiler.rs b/crates/cranelift/src/compiler.rs
index 6b3390b96..a4ed9a0c4 100644
--- a/crates/cranelift/src/compiler.rs
+++ b/crates/cranelift/src/compiler.rs
@@ -1,4 +1,4 @@
-use crate::debug::{DwarfSectionRelocTarget, ModuleMemoryOffset};
+use crate::debug::DwarfSectionRelocTarget;
 use crate::func_environ::FuncEnvironment;
 use crate::DEBUG_ASSERT_TRAP_CODE;
 use crate::{array_call_signature, CompiledFunction, ModuleTextBuilder};
@@ -12,11 +12,9 @@ use cranelift_codegen::isa::{
 use cranelift_codegen::print_errors::pretty_error;
 use cranelift_codegen::Context;
 use cranelift_codegen::{CompiledCode, MachStackMap};
-use cranelift_entity::{EntityRef, PrimaryMap};
+use cranelift_entity::PrimaryMap;
 use cranelift_frontend::FunctionBuilder;
-use cranelift_wasm::{
-    DefinedFuncIndex, FuncTranslator, MemoryIndex, OwnedMemoryIndex, WasmFuncType, WasmValType,
-};
+use cranelift_wasm::{DefinedFuncIndex, FuncTranslator, WasmFuncType, WasmValType};
 use object::write::{Object, StandardSegment, SymbolId};
 use object::{RelocationEncoding, RelocationFlags, RelocationKind, SectionKind};
 use std::any::Any;
@@ -29,7 +27,8 @@ use wasmparser::{FuncValidatorAllocations, FunctionBody};
 use wasmtime_environ::{
     AddressMapSection, BuiltinFunctionIndex, CacheStore, CompileError, FlagValue, FunctionBodyData,
     FunctionLoc, ModuleTranslation, ModuleTypesBuilder, PtrSize, RelocationTarget,
-    StackMapInformation, TrapEncodingBuilder, Tunables, VMOffsets, WasmFunctionInfo,
+    StackMapInformation, StaticModuleIndex, TrapEncodingBuilder, Tunables, VMOffsets,
+    WasmFunctionInfo,
 };
 
 #[cfg(feature = "component-model")]
@@ -440,63 +439,33 @@ impl wasmtime_environ::Compiler for Compiler {
         self
     }
 
-    fn append_dwarf(
+    fn append_dwarf<'a>(
         &self,
         obj: &mut Object<'_>,
-        translation: &ModuleTranslation<'_>,
-        funcs: &PrimaryMap<DefinedFuncIndex, (SymbolId, &(dyn Any + Send))>,
-        dwarf_package_bytes: Option<&[u8]>,
-        tunables: &Tunables,
+        translations: &'a PrimaryMap<StaticModuleIndex, ModuleTranslation<'a>>,
+        get_func: &'a dyn Fn(
+            StaticModuleIndex,
+            DefinedFuncIndex,
+        ) -> (SymbolId, &'a (dyn Any + Send)),
+        dwarf_package_bytes: Option<&'a [u8]>,
+        tunables: &'a Tunables,
     ) -> Result<()> {
-        let ofs = VMOffsets::new(
-            self.isa
-                .triple()
-                .architecture
-                .pointer_width()
-                .unwrap()
-                .bytes(),
-            &translation.module,
-        );
-
-        let memory_offset = if ofs.num_imported_memories > 0 {
-            ModuleMemoryOffset::Imported {
-                offset_to_vm_memory_definition: ofs.vmctx_vmmemory_import(MemoryIndex::new(0))
-                    + u32::from(ofs.vmmemory_import_from()),
-                offset_to_memory_base: ofs.ptr.vmmemory_definition_base().into(),
-            }
-        } else if ofs.num_defined_memories > 0 {
-            // The addition of shared memory makes the following assumption,
-            // "owned memory index = 0", possibly false. If the first memory
-            // is a shared memory, the base pointer will not be stored in
-            // the `owned_memories` array. The following code should
-            // eventually be fixed to not only handle shared memories but
-            // also multiple memories.
-            assert_eq!(
-                ofs.num_defined_memories, ofs.num_owned_memories,
-                "the memory base pointer may be incorrect due to sharing memory"
-            );
-            ModuleMemoryOffset::Defined(
-                ofs.vmctx_vmmemory_definition_base(OwnedMemoryIndex::new(0)),
+        let get_func = move |m, f| {
+            let (sym, any) = get_func(m, f);
+            (
+                sym,
+                any.downcast_ref::<CompiledFunction>().unwrap().metadata(),
             )
-        } else {
-            ModuleMemoryOffset::None
         };
-        let functions_info = funcs
-            .iter()
-            .map(|(_, (_, func))| {
-                let f = func.downcast_ref::<CompiledFunction>().unwrap();
-                f.metadata()
-            })
-            .collect();
-        let dwarf_sections = crate::debug::emit_dwarf(
+        let mut compilation = crate::debug::Compilation::new(
             &*self.isa,
-            &translation.debuginfo,
-            &functions_info,
-            &memory_offset,
+            translations,
+            &get_func,
             dwarf_package_bytes,
             tunables,
-        )
-        .with_context(|| "failed to emit DWARF debug information")?;
+        );
+        let dwarf_sections = crate::debug::emit_dwarf(&*self.isa, &mut compilation)
+            .with_context(|| "failed to emit DWARF debug information")?;
 
         let (debug_bodies, debug_relocs): (Vec<_>, Vec<_>) = dwarf_sections
             .iter()
@@ -515,7 +484,7 @@ impl wasmtime_environ::Compiler for Compiler {
             let section_id = *dwarf_sections_ids.get(name).unwrap();
             for reloc in relocs {
                 let target_symbol = match reloc.target {
-                    DwarfSectionRelocTarget::Func(index) => funcs[DefinedFuncIndex::new(index)].0,
+                    DwarfSectionRelocTarget::Func(id) => compilation.symbol_id(id),
                     DwarfSectionRelocTarget::Section(name) => {
                         obj.section_symbol(dwarf_sections_ids[name])
                     }
diff --git a/crates/cranelift/src/debug.rs b/crates/cranelift/src/debug.rs
index 083a3ba47..ed9c47310 100644
--- a/crates/cranelift/src/debug.rs
+++ b/crates/cranelift/src/debug.rs
@@ -1,5 +1,14 @@
 //! Debug utils for WebAssembly using Cranelift.
 
+use crate::CompiledFunctionMetadata;
+use cranelift_codegen::isa::TargetIsa;
+use object::write::SymbolId;
+use std::collections::HashMap;
+use wasmtime_environ::{
+    DefinedFuncIndex, EntityRef, MemoryIndex, ModuleTranslation, OwnedMemoryIndex, PrimaryMap,
+    PtrSize, StaticModuleIndex, Tunables, VMOffsets,
+};
+
 /// Memory definition offset in the VMContext structure.
 #[derive(Debug, Clone)]
 pub enum ModuleMemoryOffset {
@@ -18,6 +27,154 @@ pub enum ModuleMemoryOffset {
     },
 }
 
+type Reader<'input> = gimli::EndianSlice<'input, gimli::LittleEndian>;
+
+/// "Package structure" to collect together various artifacts/results of a
+/// compilation.
+///
+/// This structure is threaded through a number of top-level functions of DWARF
+/// processing within in this submodule to pass along all the bits-and-pieces of
+/// the compilation context.
+pub struct Compilation<'a> {
+    /// All module translations which were present in this compilation.
+    ///
+    /// This map has one entry for core wasm modules and may have multiple (or
+    /// zero) for components.
+    translations: &'a PrimaryMap<StaticModuleIndex, ModuleTranslation<'a>>,
+
+    /// Accessor of a particular compiled function for a module.
+    ///
+    /// This returns the `object`-based-symbol for the function as well as the
+    /// `&CompiledFunction`.
+    get_func:
+        &'a dyn Fn(StaticModuleIndex, DefinedFuncIndex) -> (SymbolId, &'a CompiledFunctionMetadata),
+
+    /// Optionally-specified `*.dwp` file, currently only supported for core
+    /// wasm modules.
+    dwarf_package_bytes: Option<&'a [u8]>,
+
+    /// Compilation settings used when producing functions.
+    tunables: &'a Tunables,
+
+    /// Translation between `SymbolId` and a `usize`-based symbol which gimli
+    /// uses.
+    symbol_index_to_id: Vec<SymbolId>,
+    symbol_id_to_index: HashMap<SymbolId, (usize, StaticModuleIndex, DefinedFuncIndex)>,
+
+    /// The `ModuleMemoryOffset` for each module within `translations`.
+    ///
+    /// Note that this doesn't support multi-memory at this time.
+    module_memory_offsets: PrimaryMap<StaticModuleIndex, ModuleMemoryOffset>,
+}
+
+impl<'a> Compilation<'a> {
+    pub fn new(
+        isa: &dyn TargetIsa,
+        translations: &'a PrimaryMap<StaticModuleIndex, ModuleTranslation<'a>>,
+        get_func: &'a dyn Fn(
+            StaticModuleIndex,
+            DefinedFuncIndex,
+        ) -> (SymbolId, &'a CompiledFunctionMetadata),
+        dwarf_package_bytes: Option<&'a [u8]>,
+        tunables: &'a Tunables,
+    ) -> Compilation<'a> {
+        // Build the `module_memory_offsets` map based on the modules in
+        // `translations`.
+        let mut module_memory_offsets = PrimaryMap::new();
+        for (i, translation) in translations {
+            let ofs = VMOffsets::new(
+                isa.triple().architecture.pointer_width().unwrap().bytes(),
+                &translation.module,
+            );
+
+            let memory_offset = if ofs.num_imported_memories > 0 {
+                ModuleMemoryOffset::Imported {
+                    offset_to_vm_memory_definition: ofs.vmctx_vmmemory_import(MemoryIndex::new(0))
+                        + u32::from(ofs.vmmemory_import_from()),
+                    offset_to_memory_base: ofs.ptr.vmmemory_definition_base().into(),
+                }
+            } else if ofs.num_defined_memories > 0 {
+                // The addition of shared memory makes the following assumption,
+                // "owned memory index = 0", possibly false. If the first memory
+                // is a shared memory, the base pointer will not be stored in
+                // the `owned_memories` array. The following code should
+                // eventually be fixed to not only handle shared memories but
+                // also multiple memories.
+                assert_eq!(
+                    ofs.num_defined_memories, ofs.num_owned_memories,
+                    "the memory base pointer may be incorrect due to sharing memory"
+                );
+                ModuleMemoryOffset::Defined(
+                    ofs.vmctx_vmmemory_definition_base(OwnedMemoryIndex::new(0)),
+                )
+            } else {
+                ModuleMemoryOffset::None
+            };
+            let j = module_memory_offsets.push(memory_offset);
+            assert_eq!(i, j);
+        }
+
+        // Build the `symbol <=> usize` mappings
+        let mut symbol_index_to_id = Vec::new();
+        let mut symbol_id_to_index = HashMap::new();
+
+        for (module, translation) in translations {
+            for func in translation.module.defined_func_indices() {
+                let (sym, _func) = get_func(module, func);
+                symbol_id_to_index.insert(sym, (symbol_index_to_id.len(), module, func));
+                symbol_index_to_id.push(sym);
+            }
+        }
+
+        Compilation {
+            translations,
+            get_func,
+            dwarf_package_bytes,
+            tunables,
+            symbol_index_to_id,
+            symbol_id_to_index,
+            module_memory_offsets,
+        }
+    }
+
+    /// Returns an iterator over all function indexes present in this
+    /// compilation.
+    ///
+    /// Each function is additionally accompanied with its module index.
+    fn indexes(&self) -> impl Iterator<Item = (StaticModuleIndex, DefinedFuncIndex)> + '_ {
+        self.translations
+            .iter()
+            .flat_map(|(i, t)| t.module.defined_func_indices().map(move |j| (i, j)))
+    }
+
+    /// Returns an iterator of all functions with their module, symbol, and
+    /// function metadata that were produced during compilation.
+    fn functions(
+        &self,
+    ) -> impl Iterator<Item = (StaticModuleIndex, usize, &'a CompiledFunctionMetadata)> + '_ {
+        self.indexes().map(move |(module, func)| {
+            let (sym, func) = self.function(module, func);
+            (module, sym, func)
+        })
+    }
+
+    /// Returns the symbol and metadata associated with a specific function.
+    fn function(
+        &self,
+        module: StaticModuleIndex,
+        func: DefinedFuncIndex,
+    ) -> (usize, &'a CompiledFunctionMetadata) {
+        let (sym, func) = (self.get_func)(module, func);
+        (self.symbol_id_to_index[&sym].0, func)
+    }
+
+    /// Maps a `usize`-based symbol used by gimli to the object-based
+    /// `SymbolId`.
+    pub fn symbol_id(&self, sym: usize) -> SymbolId {
+        self.symbol_index_to_id[sym]
+    }
+}
+
 pub use write_debuginfo::{emit_dwarf, DwarfSectionRelocTarget};
 
 mod gc;
diff --git a/crates/cranelift/src/debug/gc.rs b/crates/cranelift/src/debug/gc.rs
index c6398fb82..bb45e8e83 100644
--- a/crates/cranelift/src/debug/gc.rs
+++ b/crates/cranelift/src/debug/gc.rs
@@ -1,8 +1,10 @@
 use crate::debug::transform::AddressTransform;
+use crate::debug::{Compilation, Reader};
 use gimli::constants;
 use gimli::read;
-use gimli::{Reader, UnitSectionOffset};
+use gimli::UnitSectionOffset;
 use std::collections::{HashMap, HashSet};
+use wasmtime_environ::{PrimaryMap, StaticModuleIndex};
 
 #[derive(Debug)]
 pub struct Dependencies {
@@ -65,23 +67,26 @@ impl Dependencies {
     }
 }
 
-pub fn build_dependencies<R: Reader<Offset = usize>>(
-    dwarf: &read::Dwarf<R>,
-    dwp: &Option<read::DwarfPackage<R>>,
-    at: &AddressTransform,
+pub fn build_dependencies(
+    compilation: &mut Compilation<'_>,
+    dwp: &Option<read::DwarfPackage<Reader<'_>>>,
+    at: &PrimaryMap<StaticModuleIndex, AddressTransform>,
 ) -> read::Result<Dependencies> {
     let mut deps = Dependencies::new();
-    let mut units = dwarf.units();
-    while let Some(unit) = units.next()? {
-        build_unit_dependencies(unit, dwarf, dwp, at, &mut deps)?;
+    for (i, translation) in compilation.translations.iter() {
+        let dwarf = &translation.debuginfo.dwarf;
+        let mut units = dwarf.units();
+        while let Some(unit) = units.next()? {
+            build_unit_dependencies(unit, dwarf, dwp, &at[i], &mut deps)?;
+        }
     }
     Ok(deps)
 }
 
-fn build_unit_dependencies<R: Reader<Offset = usize>>(
-    header: read::UnitHeader<R>,
-    dwarf: &read::Dwarf<R>,
-    dwp: &Option<read::DwarfPackage<R>>,
+fn build_unit_dependencies(
+    header: read::UnitHeader<Reader<'_>>,
+    dwarf: &read::Dwarf<Reader<'_>>,
+    dwp: &Option<read::DwarfPackage<Reader<'_>>>,
     at: &AddressTransform,
     deps: &mut Dependencies,
 ) -> read::Result<()> {
@@ -103,7 +108,7 @@ fn build_unit_dependencies<R: Reader<Offset = usize>>(
     Ok(())
 }
 
-fn has_die_back_edge<R: Reader<Offset = usize>>(die: &read::DebuggingInformationEntry<R>) -> bool {
+fn has_die_back_edge(die: &read::DebuggingInformationEntry<Reader<'_>>) -> bool {
     match die.tag() {
         constants::DW_TAG_variable
         | constants::DW_TAG_constant
@@ -123,10 +128,10 @@ fn has_die_back_edge<R: Reader<Offset = usize>>(die: &read::DebuggingInformation
     }
 }
 
-fn has_valid_code_range<R: Reader<Offset = usize>>(
-    die: &read::DebuggingInformationEntry<R>,
-    dwarf: &read::Dwarf<R>,
-    unit: &read::Unit<R>,
+fn has_valid_code_range(
+    die: &read::DebuggingInformationEntry<Reader<'_>>,
+    dwarf: &read::Dwarf<Reader<'_>>,
+    unit: &read::Unit<Reader<'_>>,
     at: &AddressTransform,
 ) -> read::Result<bool> {
     match die.tag() {
@@ -199,10 +204,10 @@ fn has_valid_code_range<R: Reader<Offset = usize>>(
     Ok(false)
 }
 
-fn build_die_dependencies<R: Reader<Offset = usize>>(
-    die: read::EntriesTreeNode<R>,
-    dwarf: &read::Dwarf<R>,
-    unit: &read::Unit<R>,
+fn build_die_dependencies(
+    die: read::EntriesTreeNode<Reader<'_>>,
+    dwarf: &read::Dwarf<Reader<'_>>,
+    unit: &read::Unit<Reader<'_>>,
     at: &AddressTransform,
     deps: &mut Dependencies,
 ) -> read::Result<()> {
@@ -229,11 +234,11 @@ fn build_die_dependencies<R: Reader<Offset = usize>>(
     Ok(())
 }
 
-fn build_attr_dependencies<R: Reader<Offset = usize>>(
-    attr: &read::Attribute<R>,
+fn build_attr_dependencies(
+    attr: &read::Attribute<Reader<'_>>,
     offset: UnitSectionOffset,
-    _dwarf: &read::Dwarf<R>,
-    unit: &read::Unit<R>,
+    _dwarf: &read::Dwarf<Reader<'_>>,
+    unit: &read::Unit<Reader<'_>>,
     _at: &AddressTransform,
     deps: &mut Dependencies,
 ) -> read::Result<()> {
diff --git a/crates/cranelift/src/debug/transform/address_transform.rs b/crates/cranelift/src/debug/transform/address_transform.rs
index 5a437c8c1..2bb126dde 100644
--- a/crates/cranelift/src/debug/transform/address_transform.rs
+++ b/crates/cranelift/src/debug/transform/address_transform.rs
@@ -1,7 +1,8 @@
-use crate::{CompiledFunctionsMetadata, FunctionAddressMap};
+use crate::debug::Compilation;
+use crate::FunctionAddressMap;
 use gimli::write;
 use std::collections::BTreeMap;
-use wasmtime_environ::{DefinedFuncIndex, EntityRef, FilePos, PrimaryMap, WasmFileInfo};
+use wasmtime_environ::{DefinedFuncIndex, FilePos, PrimaryMap, StaticModuleIndex};
 
 pub type GeneratedAddress = usize;
 pub type WasmAddress = u64;
@@ -18,6 +19,7 @@ pub struct AddressMap {
 /// length, and instructions addresses.
 #[derive(Debug)]
 pub struct FunctionMap {
+    pub symbol: usize,
     pub offset: GeneratedAddress,
     pub len: GeneratedAddress,
     pub wasm_start: WasmAddress,
@@ -187,11 +189,19 @@ fn build_function_lookup(
 }
 
 fn build_function_addr_map(
-    funcs: &CompiledFunctionsMetadata,
-    code_section_offset: u64,
+    compilation: &Compilation<'_>,
+    module: StaticModuleIndex,
 ) -> PrimaryMap<DefinedFuncIndex, FunctionMap> {
     let mut map = PrimaryMap::new();
-    for (_, metadata) in funcs {
+    for idx in compilation.translations[module]
+        .module
+        .defined_func_indices()
+    {
+        let (symbol, metadata) = compilation.function(module, idx);
+        let code_section_offset = compilation.translations[module]
+            .debuginfo
+            .wasm_file
+            .code_section_offset;
         let ft = &metadata.address_map;
         let mut fn_map = Vec::new();
         for t in ft.instructions.iter() {
@@ -213,6 +223,7 @@ fn build_function_addr_map(
         }
 
         map.push(FunctionMap {
+            symbol,
             offset: ft.body_offset,
             len: ft.body_len as usize,
             wasm_start: get_wasm_code_offset(ft.start_srcloc, code_section_offset),
@@ -352,7 +363,7 @@ impl<'a> Iterator for TransformRangeEndIter<'a> {
 }
 
 // Utility iterator to iterate by translated function ranges.
-pub struct TransformRangeIter<'a> {
+struct TransformRangeIter<'a> {
     func: &'a FuncTransform,
     start_it: TransformRangeStartIter<'a>,
     end_it: TransformRangeEndIter<'a>,
@@ -448,11 +459,18 @@ impl<'a> Iterator for TransformRangeIter<'a> {
 }
 
 impl AddressTransform {
-    pub fn new(funcs: &CompiledFunctionsMetadata, wasm_file: &WasmFileInfo) -> Self {
-        let code_section_offset = wasm_file.code_section_offset;
-
+    pub fn new(compilation: &Compilation<'_>, module: StaticModuleIndex) -> Self {
         let mut func = BTreeMap::new();
-        for (i, metadata) in funcs {
+        let code_section_offset = compilation.translations[module]
+            .debuginfo
+            .wasm_file
+            .code_section_offset;
+
+        for idx in compilation.translations[module]
+            .module
+            .defined_func_indices()
+        {
+            let (_, metadata) = compilation.function(module, idx);
             let (fn_start, fn_end, lookup) =
                 build_function_lookup(&metadata.address_map, code_section_offset);
 
@@ -461,17 +479,56 @@ impl AddressTransform {
                 FuncTransform {
                     start: fn_start,
                     end: fn_end,
-                    index: i,
+                    index: idx,
                     lookup,
                 },
             );
         }
 
-        let map = build_function_addr_map(funcs, code_section_offset);
+        let map = build_function_addr_map(compilation, module);
         let func = Vec::from_iter(func.into_iter());
         AddressTransform { map, func }
     }
 
+    #[cfg(test)]
+    pub fn mock(
+        module_map: &wasmtime_environ::PrimaryMap<
+            wasmtime_environ::DefinedFuncIndex,
+            &crate::CompiledFunctionMetadata,
+        >,
+        wasm_file: wasmtime_environ::WasmFileInfo,
+    ) -> Self {
+        let mut translations = wasmtime_environ::PrimaryMap::new();
+        let mut translation = wasmtime_environ::ModuleTranslation::default();
+        translation.debuginfo.wasm_file = wasm_file;
+        translation
+            .module
+            .push_function(wasmtime_environ::ModuleInternedTypeIndex::from_u32(0));
+        translations.push(translation);
+
+        let mut dummy_obj = object::write::Object::new(
+            object::BinaryFormat::Elf,
+            object::Architecture::Wasm32,
+            object::Endianness::Little,
+        );
+        let dummy_symbol = dummy_obj.add_file_symbol(Vec::new());
+        let func_lookup = move |_, f| (dummy_symbol, module_map[f]);
+        let tunables = wasmtime_environ::Tunables::default_host();
+        let compile = Compilation::new(
+            &*cranelift_codegen::isa::lookup(target_lexicon::Triple::host())
+                .unwrap()
+                .finish(cranelift_codegen::settings::Flags::new(
+                    cranelift_codegen::settings::builder(),
+                ))
+                .unwrap(),
+            &translations,
+            &func_lookup,
+            None,
+            &tunables,
+        );
+        Self::new(&compile, StaticModuleIndex::from_u32(0))
+    }
+
     fn find_func(&self, addr: u64) -> Option<&FuncTransform> {
         // TODO check if we need to include end address
         let func = match self.func.binary_search_by(|entry| entry.0.cmp(&addr)) {
@@ -494,20 +551,20 @@ impl AddressTransform {
         self.find_func(addr).map(|f| f.index)
     }
 
-    pub fn translate_raw(&self, addr: u64) -> Option<(DefinedFuncIndex, GeneratedAddress)> {
+    fn translate_raw(&self, addr: u64) -> Option<(usize, GeneratedAddress)> {
         if addr == 0 {
             // It's normally 0 for debug info without the linked code.
             return None;
         }
         if let Some(func) = self.find_func(addr) {
+            let map = &self.map[func.index];
             if addr == func.end {
                 // Clamp last address to the end to extend translation to the end
                 // of the function.
-                let map = &self.map[func.index];
-                return Some((func.index, map.len));
+                return Some((map.symbol, map.len));
             }
             let first_result = TransformRangeStartIter::new(func, addr).next();
-            first_result.map(|(address, _)| (func.index, address))
+            first_result.map(|(address, _)| (map.symbol, address))
         } else {
             // Address was not found: function was not compiled?
             None
@@ -520,8 +577,8 @@ impl AddressTransform {
 
     pub fn translate(&self, addr: u64) -> Option<write::Address> {
         self.translate_raw(addr)
-            .map(|(func_index, address)| write::Address::Symbol {
-                symbol: func_index.index(),
+            .map(|(symbol, address)| write::Address::Symbol {
+                symbol,
                 addend: address as i64,
             })
     }
@@ -530,14 +587,15 @@ impl AddressTransform {
         &'a self,
         start: u64,
         end: u64,
-    ) -> Option<(DefinedFuncIndex, impl Iterator<Item = (usize, usize)> + 'a)> {
+    ) -> Option<(usize, impl Iterator<Item = (usize, usize)> + 'a)> {
         if start == 0 {
             // It's normally 0 for debug info without the linked code.
             return None;
         }
         if let Some(func) = self.find_func(start) {
             let result = TransformRangeIter::new(func, start, end);
-            return Some((func.index, result));
+            let symbol = self.map[func.index].symbol;
+            return Some((symbol, result));
         }
         // Address was not found: function was not compiled?
         None
@@ -578,8 +636,8 @@ impl AddressTransform {
         }
 
         match self.translate_ranges_raw(start, end) {
-            Some((func_index, ranges)) => TranslateRangesResult::Raw {
-                symbol: func_index.index(),
+            Some((symbol, ranges)) => TranslateRangesResult::Raw {
+                symbol,
                 it: Box::new(ranges),
             },
             None => TranslateRangesResult::Empty,
@@ -731,9 +789,9 @@ mod tests {
             ..Default::default()
         };
         let input = PrimaryMap::from_iter([&func]);
-        let at = AddressTransform::new(
+        let at = AddressTransform::mock(
             &input,
-            &WasmFileInfo {
+            WasmFileInfo {
                 path: None,
                 code_section_offset: 1,
                 imported_func_count: 0,
diff --git a/crates/cranelift/src/debug/transform/expression.rs b/crates/cranelift/src/debug/transform/expression.rs
index 17c560906..19015ea1e 100644
--- a/crates/cranelift/src/debug/transform/expression.rs
+++ b/crates/cranelift/src/debug/transform/expression.rs
@@ -13,7 +13,6 @@ use std::cmp::PartialEq;
 use std::collections::{HashMap, HashSet};
 use std::hash::{Hash, Hasher};
 use std::rc::Rc;
-use wasmtime_environ::{DefinedFuncIndex, EntityRef};
 
 #[derive(Debug)]
 pub struct FunctionFrameInfo<'a> {
@@ -254,9 +253,7 @@ impl CompiledExpression {
                 Box<dyn Iterator<Item = (write::Address, u64)> + 'a>,
                 Vec<u8>,
             ),
-            Ranges(
-                Box<dyn Iterator<Item = Result<(DefinedFuncIndex, usize, usize, Vec<u8>)>> + 'a>,
-            ),
+            Ranges(Box<dyn Iterator<Item = Result<(usize, usize, usize, Vec<u8>)>> + 'a>),
         }
         impl Iterator for BuildWithLocalsResult<'_> {
             type Item = Result<(write::Address, u64, write::Expression)>;
@@ -267,10 +264,10 @@ impl CompiledExpression {
                         .next()
                         .map(|(addr, len)| Ok((addr, len, write::Expression::raw(code.to_vec())))),
                     BuildWithLocalsResult::Ranges(it) => it.next().map(|r| {
-                        r.map(|(func_index, start, end, code_buf)| {
+                        r.map(|(symbol, start, end, code_buf)| {
                             (
                                 write::Address::Symbol {
-                                    symbol: func_index.index(),
+                                    symbol,
                                     addend: start as i64,
                                 },
                                 (end - start) as u64,
@@ -650,7 +647,7 @@ where
 
 #[derive(Debug, Clone)]
 struct CachedValueLabelRange {
-    func_index: DefinedFuncIndex,
+    func_index: usize,
     start: usize,
     end: usize,
     label_location: HashMap<ValueLabel, LabelValueLoc>,
@@ -1176,7 +1173,7 @@ mod tests {
             imported_func_count: 0,
             path: None,
         };
-        AddressTransform::new(&module_map, &fi)
+        AddressTransform::mock(&module_map, fi)
     }
 
     fn create_mock_value_ranges() -> (ValueLabelsRanges, (ValueLabel, ValueLabel, ValueLabel)) {
@@ -1225,7 +1222,6 @@ mod tests {
     fn test_debug_value_range_builder() {
         use super::ValueLabelRangesBuilder;
         use crate::debug::ModuleMemoryOffset;
-        use wasmtime_environ::{DefinedFuncIndex, EntityRef};
 
         let addr_tr = create_mock_address_transform();
         let (value_ranges, value_labels) = create_mock_value_ranges();
@@ -1238,7 +1234,7 @@ mod tests {
         let builder = ValueLabelRangesBuilder::new(&[(10, 20)], &addr_tr, Some(&fi));
         let ranges = builder.into_ranges().collect::<Vec<_>>();
         assert_eq!(ranges.len(), 1);
-        assert_eq!(ranges[0].func_index, DefinedFuncIndex::new(0));
+        assert_eq!(ranges[0].func_index, 0);
         assert_eq!(ranges[0].start, 0);
         assert_eq!(ranges[0].end, 30);
 
diff --git a/crates/cranelift/src/debug/transform/line_program.rs b/crates/cranelift/src/debug/transform/line_program.rs
index 148fe80f3..715428aee 100644
--- a/crates/cranelift/src/debug/transform/line_program.rs
+++ b/crates/cranelift/src/debug/transform/line_program.rs
@@ -6,7 +6,7 @@ use gimli::{
     write, AttributeValue::DebugLineRef, DebugLine, DebugLineOffset, DebugStr,
     DebuggingInformationEntry, LineEncoding, Unit,
 };
-use wasmtime_environ::{DefinedFuncIndex, EntityRef};
+use wasmtime_environ::DefinedFuncIndex;
 
 #[derive(Debug)]
 enum SavedLineProgramRow {
@@ -222,7 +222,7 @@ where
                     continue; // no code generated
                 }
             };
-            let symbol = index.index();
+            let symbol = map.symbol;
             let base_addr = map.offset;
             out_program.begin_sequence(Some(write::Address::Symbol { symbol, addend: 0 }));
             // TODO track and place function declaration line here
diff --git a/crates/cranelift/src/debug/transform/mod.rs b/crates/cranelift/src/debug/transform/mod.rs
index 0695c3f3a..cc988280d 100644
--- a/crates/cranelift/src/debug/transform/mod.rs
+++ b/crates/cranelift/src/debug/transform/mod.rs
@@ -2,8 +2,7 @@ use self::refs::DebugInfoRefsMap;
 use self::simulate::generate_simulated_dwarf;
 use self::unit::clone_unit;
 use crate::debug::gc::build_dependencies;
-use crate::debug::ModuleMemoryOffset;
-use crate::CompiledFunctionsMetadata;
+use crate::debug::Compilation;
 use anyhow::Error;
 use cranelift_codegen::isa::TargetIsa;
 use gimli::{
@@ -12,7 +11,9 @@ use gimli::{
 };
 use std::{collections::HashSet, fmt::Debug};
 use thiserror::Error;
-use wasmtime_environ::{DebugInfoData, ModuleTranslation, Tunables};
+use wasmtime_environ::{
+    DefinedFuncIndex, ModuleTranslation, PrimaryMap, StaticModuleIndex, Tunables,
+};
 
 pub use address_transform::AddressTransform;
 
@@ -26,6 +27,21 @@ mod simulate;
 mod unit;
 mod utils;
 
+impl<'a> Compilation<'a> {
+    fn function_frame_info(
+        &mut self,
+        module: StaticModuleIndex,
+        func: DefinedFuncIndex,
+    ) -> expression::FunctionFrameInfo<'a> {
+        let (_, func) = self.function(module, func);
+
+        expression::FunctionFrameInfo {
+            value_ranges: &func.value_labels_ranges,
+            memory_offset: self.module_memory_offsets[module].clone(),
+        }
+    }
+}
+
 pub(crate) trait Reader: gimli::Reader<Offset = usize> + Send + Sync {}
 
 impl<'input, Endian> Reader for gimli::EndianSlice<'input, Endian> where
@@ -134,34 +150,25 @@ fn read_dwarf_package_from_bytes<'data>(
 
 pub fn transform_dwarf(
     isa: &dyn TargetIsa,
-    di: &DebugInfoData,
-    funcs: &CompiledFunctionsMetadata,
-    memory_offset: &ModuleMemoryOffset,
-    dwarf_package_bytes: Option<&[u8]>,
-    tunables: &Tunables,
+    compilation: &mut Compilation<'_>,
 ) -> Result<write::Dwarf, Error> {
-    let addr_tr = AddressTransform::new(funcs, &di.wasm_file);
+    let mut transforms = PrimaryMap::new();
+    for (i, _) in compilation.translations.iter() {
+        transforms.push(AddressTransform::new(compilation, i));
+    }
 
     let buffer = Vec::new();
 
-    let dwarf_package = dwarf_package_bytes
+    let dwarf_package = compilation
+        .dwarf_package_bytes
         .map(
             |bytes| -> Option<DwarfPackage<gimli::EndianSlice<'_, gimli::LittleEndian>>> {
-                read_dwarf_package_from_bytes(bytes, &buffer, tunables)
+                read_dwarf_package_from_bytes(bytes, &buffer, compilation.tunables)
             },
         )
         .flatten();
 
-    let reachable = build_dependencies(&di.dwarf, &dwarf_package, &addr_tr)?.get_reachable();
-
-    let context = DebugInputContext {
-        debug_str: &di.dwarf.debug_str,
-        debug_line: &di.dwarf.debug_line,
-        debug_addr: &di.dwarf.debug_addr,
-        rnglists: &di.dwarf.ranges,
-        loclists: &di.dwarf.locations,
-        reachable: &reachable,
-    };
+    let reachable = build_dependencies(compilation, &dwarf_package, &transforms)?.get_reachable();
 
     let out_encoding = gimli::Encoding {
         format: gimli::Format::Dwarf32,
@@ -178,51 +185,61 @@ pub fn transform_dwarf(
     let mut di_ref_map = DebugInfoRefsMap::new();
 
     let mut translated = HashSet::new();
-    let mut iter = di.dwarf.debug_info.units();
 
-    while let Some(header) = iter.next().unwrap_or(None) {
-        let unit = di.dwarf.unit(header)?;
+    for (module, translation) in compilation.translations.iter() {
+        let addr_tr = &transforms[module];
+        let di = &translation.debuginfo;
+        let context = DebugInputContext {
+            debug_str: &di.dwarf.debug_str,
+            debug_line: &di.dwarf.debug_line,
+            debug_addr: &di.dwarf.debug_addr,
+            rnglists: &di.dwarf.ranges,
+            loclists: &di.dwarf.locations,
+            reachable: &reachable,
+        };
+        let mut iter = di.dwarf.debug_info.units();
 
-        let mut resolved_unit = None;
-        let mut split_dwarf = None;
+        while let Some(header) = iter.next().unwrap_or(None) {
+            let unit = di.dwarf.unit(header)?;
 
-        if let gimli::UnitType::Skeleton(_dwo_id) = unit.header.type_() {
-            if let Some(dwarf_package) = &dwarf_package {
-                if let Some((fused, fused_dwarf)) =
-                    replace_unit_from_split_dwarf(&unit, dwarf_package, &di.dwarf)
-                {
-                    resolved_unit = Some(fused);
-                    split_dwarf = Some(fused_dwarf);
+            let mut resolved_unit = None;
+            let mut split_dwarf = None;
+
+            if let gimli::UnitType::Skeleton(_dwo_id) = unit.header.type_() {
+                if let Some(dwarf_package) = &dwarf_package {
+                    if let Some((fused, fused_dwarf)) =
+                        replace_unit_from_split_dwarf(&unit, dwarf_package, &di.dwarf)
+                    {
+                        resolved_unit = Some(fused);
+                        split_dwarf = Some(fused_dwarf);
+                    }
                 }
             }
-        }
 
-        if let Some((id, ref_map, pending_refs)) = clone_unit(
-            &di.dwarf,
-            &unit,
-            resolved_unit.as_ref(),
-            split_dwarf.as_ref(),
-            &context,
-            &addr_tr,
-            funcs,
-            memory_offset,
-            out_encoding,
-            &mut out_units,
-            &mut out_strings,
-            &mut translated,
-            isa,
-        )? {
-            di_ref_map.insert(&header, id, ref_map);
-            pending_di_refs.push((id, pending_refs));
+            if let Some((id, ref_map, pending_refs)) = clone_unit(
+                compilation,
+                module,
+                &unit,
+                resolved_unit.as_ref(),
+                split_dwarf.as_ref(),
+                &context,
+                &addr_tr,
+                out_encoding,
+                &mut out_units,
+                &mut out_strings,
+                &mut translated,
+                isa,
+            )? {
+                di_ref_map.insert(&header, id, ref_map);
+                pending_di_refs.push((id, pending_refs));
+            }
         }
     }
     di_ref_map.patch(pending_di_refs.into_iter(), &mut out_units);
 
     generate_simulated_dwarf(
-        &addr_tr,
-        di,
-        memory_offset,
-        funcs,
+        compilation,
+        &transforms,
         &translated,
         out_encoding,
         &mut out_units,
diff --git a/crates/cranelift/src/debug/transform/range_info_builder.rs b/crates/cranelift/src/debug/transform/range_info_builder.rs
index 7fda1c33e..568eeac94 100644
--- a/crates/cranelift/src/debug/transform/range_info_builder.rs
+++ b/crates/cranelift/src/debug/transform/range_info_builder.rs
@@ -2,7 +2,7 @@ use super::address_transform::AddressTransform;
 use super::{DebugInputContext, Reader};
 use anyhow::Error;
 use gimli::{write, AttributeValue, DebuggingInformationEntry, RangeListsOffset, Unit};
-use wasmtime_environ::{DefinedFuncIndex, EntityRef};
+use wasmtime_environ::DefinedFuncIndex;
 
 pub(crate) enum RangeInfoBuilder {
     Undefined,
@@ -172,8 +172,8 @@ impl RangeInfoBuilder {
                 }
             }
             RangeInfoBuilder::Function(index) => {
+                let symbol = addr_tr.map()[*index].symbol;
                 let range = addr_tr.func_range(*index);
-                let symbol = index.index();
                 let addr = write::Address::Symbol {
                     symbol,
                     addend: range.0 as i64,
diff --git a/crates/cranelift/src/debug/transform/simulate.rs b/crates/cranelift/src/debug/transform/simulate.rs
index af3a24125..b93caf0e4 100644
--- a/crates/cranelift/src/debug/transform/simulate.rs
+++ b/crates/cranelift/src/debug/transform/simulate.rs
@@ -1,8 +1,7 @@
 use super::expression::{CompiledExpression, FunctionFrameInfo};
-use super::utils::{add_internal_types, append_vmctx_info, get_function_frame_info};
+use super::utils::{add_internal_types, append_vmctx_info};
 use super::AddressTransform;
-use crate::debug::ModuleMemoryOffset;
-use crate::CompiledFunctionsMetadata;
+use crate::debug::{Compilation, ModuleMemoryOffset};
 use anyhow::{Context, Error};
 use cranelift_codegen::isa::TargetIsa;
 use cranelift_wasm::get_vmctx_value_label;
@@ -12,7 +11,7 @@ use std::collections::{HashMap, HashSet};
 use std::path::PathBuf;
 use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
 use wasmtime_environ::{
-    DebugInfoData, DefinedFuncIndex, EntityRef, FuncIndex, FunctionMetadata, WasmFileInfo,
+    DebugInfoData, EntityRef, FunctionMetadata, PrimaryMap, StaticModuleIndex, WasmFileInfo,
     WasmValType,
 };
 
@@ -31,8 +30,8 @@ macro_rules! assert_dwarf_str {
 }
 
 fn generate_line_info(
-    addr_tr: &AddressTransform,
-    translated: &HashSet<DefinedFuncIndex>,
+    addr_tr: &PrimaryMap<StaticModuleIndex, AddressTransform>,
+    translated: &HashSet<usize>,
     out_encoding: gimli::Encoding,
     w: &WasmFileInfo,
     comp_dir_id: write::StringId,
@@ -58,12 +57,17 @@ fn generate_line_info(
         None,
     );
 
-    for (i, map) in addr_tr.map() {
-        let symbol = i.index();
-        if translated.contains(&i) {
-            continue;
-        }
-
+    let maps = addr_tr.iter().flat_map(|(_, transform)| {
+        transform.map().iter().filter_map(|(_, map)| {
+            if translated.contains(&map.symbol) {
+                None
+            } else {
+                Some((map.symbol, map))
+            }
+        })
+    });
+
+    for (symbol, map) in maps {
         let base_addr = map.offset;
         out_program.begin_sequence(Some(write::Address::Symbol { symbol, addend: 0 }));
         for addr_map in map.addresses.iter() {
@@ -279,26 +283,24 @@ fn check_invalid_chars_in_path(path: PathBuf) -> Option<PathBuf> {
 }
 
 pub fn generate_simulated_dwarf(
-    addr_tr: &AddressTransform,
-    di: &DebugInfoData,
-    memory_offset: &ModuleMemoryOffset,
-    funcs: &CompiledFunctionsMetadata,
-    translated: &HashSet<DefinedFuncIndex>,
+    compilation: &mut Compilation<'_>,
+    addr_tr: &PrimaryMap<StaticModuleIndex, AddressTransform>,
+    translated: &HashSet<usize>,
     out_encoding: gimli::Encoding,
     out_units: &mut write::UnitTable,
     out_strings: &mut write::StringTable,
     isa: &dyn TargetIsa,
 ) -> Result<(), Error> {
-    let path = di
-        .wasm_file
-        .path
-        .to_owned()
-        .and_then(check_invalid_chars_in_path)
-        .unwrap_or_else(|| autogenerate_dwarf_wasm_path(di));
-
-    let func_names = &di.name_section.func_names;
-    let locals_names = &di.name_section.locals_names;
-    let imported_func_count = di.wasm_file.imported_func_count;
+    let (wasm_file, path) = {
+        let di = &compilation.translations.iter().next().unwrap().1.debuginfo;
+        let path = di
+            .wasm_file
+            .path
+            .to_owned()
+            .and_then(check_invalid_chars_in_path)
+            .unwrap_or_else(|| autogenerate_dwarf_wasm_path(di));
+        (&di.wasm_file, path)
+    };
 
     let (unit, root_id, name_id) = {
         let comp_dir_id = out_strings.add(assert_dwarf_str!(path
@@ -317,7 +319,7 @@ pub fn generate_simulated_dwarf(
             addr_tr,
             translated,
             out_encoding,
-            &di.wasm_file,
+            wasm_file,
             comp_dir_id,
             name_id,
             name,
@@ -343,14 +345,21 @@ pub fn generate_simulated_dwarf(
         (unit, root_id, name_id)
     };
 
-    let wasm_types = add_wasm_types(unit, root_id, out_strings, memory_offset);
+    let mut module_wasm_types = PrimaryMap::new();
+    for (module, memory_offset) in compilation.module_memory_offsets.iter() {
+        let wasm_types = add_wasm_types(unit, root_id, out_strings, memory_offset);
+        let i = module_wasm_types.push(wasm_types);
+        assert_eq!(i, module);
+    }
 
-    for (i, map) in addr_tr.map().iter() {
-        let index = i.index();
-        if translated.contains(&i) {
+    for (module, index) in compilation.indexes().collect::<Vec<_>>() {
+        let (symbol, _) = compilation.function(module, index);
+        if translated.contains(&symbol) {
             continue;
         }
 
+        let addr_tr = &addr_tr[module];
+        let map = &addr_tr.map()[index];
         let start = map.offset as u64;
         let end = start + map.len as u64;
         let die_id = unit.add(root_id, gimli::DW_TAG_subprogram);
@@ -358,7 +367,7 @@ pub fn generate_simulated_dwarf(
         die.set(
             gimli::DW_AT_low_pc,
             write::AttributeValue::Address(write::Address::Symbol {
-                symbol: index,
+                symbol,
                 addend: start as i64,
             }),
         );
@@ -367,13 +376,17 @@ pub fn generate_simulated_dwarf(
             write::AttributeValue::Udata(end - start),
         );
 
-        let func_index = imported_func_count + (index as u32);
-        let id = match func_names
-            .get(&FuncIndex::from_u32(func_index))
+        let translation = &compilation.translations[module];
+        let func_index = translation.module.func_index(index);
+        let di = &translation.debuginfo;
+        let id = match di
+            .name_section
+            .func_names
+            .get(&func_index)
             .and_then(|s| check_invalid_chars_in_name(s))
         {
             Some(n) => out_strings.add(assert_dwarf_str!(n)),
-            None => out_strings.add(format!("wasm-function[{}]", func_index)),
+            None => out_strings.add(format!("wasm-function[{}]", func_index.as_u32())),
         };
 
         die.set(gimli::DW_AT_name, write::AttributeValue::StringRef(id));
@@ -390,21 +403,20 @@ pub fn generate_simulated_dwarf(
             write::AttributeValue::Udata(wasm_offset),
         );
 
-        if let Some(frame_info) = get_function_frame_info(memory_offset, funcs, i) {
-            let source_range = addr_tr.func_source_range(i);
-            generate_vars(
-                unit,
-                die_id,
-                addr_tr,
-                &frame_info,
-                &[(source_range.0, source_range.1)],
-                &wasm_types,
-                &di.wasm_file.funcs[index],
-                locals_names.get(&FuncIndex::from_u32(index as u32)),
-                out_strings,
-                isa,
-            )?;
-        }
+        let frame_info = compilation.function_frame_info(module, index);
+        let source_range = addr_tr.func_source_range(index);
+        generate_vars(
+            unit,
+            die_id,
+            addr_tr,
+            &frame_info,
+            &[(source_range.0, source_range.1)],
+            &module_wasm_types[module],
+            &di.wasm_file.funcs[index.as_u32() as usize],
+            di.name_section.locals_names.get(&func_index),
+            out_strings,
+            isa,
+        )?;
     }
 
     Ok(())
diff --git a/crates/cranelift/src/debug/transform/unit.rs b/crates/cranelift/src/debug/transform/unit.rs
index 0b7e070ad..107c8d6ff 100644
--- a/crates/cranelift/src/debug/transform/unit.rs
+++ b/crates/cranelift/src/debug/transform/unit.rs
@@ -4,17 +4,16 @@ use super::expression::compile_expression;
 use super::line_program::clone_line_program;
 use super::range_info_builder::RangeInfoBuilder;
 use super::refs::{PendingDebugInfoRefs, PendingUnitRefs, UnitRefsMap};
-use super::utils::{add_internal_types, append_vmctx_info, get_function_frame_info};
-use super::{DebugInputContext, Reader};
-use crate::debug::ModuleMemoryOffset;
-use crate::CompiledFunctionsMetadata;
+use super::utils::{add_internal_types, append_vmctx_info};
+use super::DebugInputContext;
+use crate::debug::{Compilation, Reader};
 use anyhow::{Context, Error};
 use cranelift_codegen::ir::Endianness;
 use cranelift_codegen::isa::TargetIsa;
 use gimli::write;
 use gimli::{AttributeValue, DebuggingInformationEntry, Unit};
 use std::collections::HashSet;
-use wasmtime_environ::DefinedFuncIndex;
+use wasmtime_environ::StaticModuleIndex;
 use wasmtime_versioned_export_macros::versioned_stringify_ident;
 
 struct InheritedAttr<T> {
@@ -45,14 +44,11 @@ impl<T> InheritedAttr<T> {
     }
 }
 
-fn get_base_type_name<R>(
-    type_entry: &DebuggingInformationEntry<R>,
-    unit: &Unit<R, R::Offset>,
-    context: &DebugInputContext<R>,
-) -> Result<String, Error>
-where
-    R: Reader,
-{
+fn get_base_type_name(
+    type_entry: &DebuggingInformationEntry<Reader<'_>>,
+    unit: &Unit<Reader<'_>, usize>,
+    context: &DebugInputContext<Reader<'_>>,
+) -> Result<String, Error> {
     // FIXME remove recursion.
     if let Some(AttributeValue::UnitRef(ref offset)) = type_entry.attr_value(gimli::DW_AT_type)? {
         let mut entries = unit.entries_at_offset(*offset)?;
@@ -107,20 +103,17 @@ enum WebAssemblyPtrKind {
 ///
 /// Notice that "resolve_vmctx_memory_ptr" is external/builtin
 /// subprogram that is not part of Wasm code.
-fn replace_pointer_type<R>(
+fn replace_pointer_type(
     parent_id: write::UnitEntryId,
     kind: WebAssemblyPtrKind,
     comp_unit: &mut write::Unit,
     wp_die_id: write::UnitEntryId,
-    pointer_type_entry: &DebuggingInformationEntry<R>,
-    unit: &Unit<R, R::Offset>,
-    context: &DebugInputContext<R>,
+    pointer_type_entry: &DebuggingInformationEntry<Reader<'_>>,
+    unit: &Unit<Reader<'_>, usize>,
+    context: &DebugInputContext<Reader<'_>>,
     out_strings: &mut write::StringTable,
     pending_die_refs: &mut PendingUnitRefs,
-) -> Result<write::UnitEntryId, Error>
-where
-    R: Reader,
-{
+) -> Result<write::UnitEntryId, Error> {
     const WASM_PTR_LEN: u8 = 4;
 
     macro_rules! add_tag {
@@ -245,7 +238,7 @@ where
     Ok(wrapper_die_id)
 }
 
-fn is_dead_code<R: Reader>(entry: &DebuggingInformationEntry<R>) -> bool {
+fn is_dead_code(entry: &DebuggingInformationEntry<Reader<'_>>) -> bool {
     const TOMBSTONE: u64 = u32::MAX as u64;
 
     match entry.attr_value(gimli::DW_AT_low_pc) {
@@ -254,24 +247,20 @@ fn is_dead_code<R: Reader>(entry: &DebuggingInformationEntry<R>) -> bool {
     }
 }
 
-pub(crate) fn clone_unit<'a, R>(
-    dwarf: &gimli::Dwarf<R>,
-    unit: &Unit<R, R::Offset>,
-    split_unit: Option<&Unit<R, R::Offset>>,
-    split_dwarf: Option<&gimli::Dwarf<R>>,
-    context: &DebugInputContext<R>,
-    addr_tr: &'a AddressTransform,
-    funcs: &'a CompiledFunctionsMetadata,
-    memory_offset: &ModuleMemoryOffset,
+pub(crate) fn clone_unit(
+    compilation: &mut Compilation<'_>,
+    module: StaticModuleIndex,
+    unit: &Unit<Reader<'_>, usize>,
+    split_unit: Option<&Unit<Reader<'_>, usize>>,
+    split_dwarf: Option<&gimli::Dwarf<Reader<'_>>>,
+    context: &DebugInputContext<Reader<'_>>,
+    addr_tr: &AddressTransform,
     out_encoding: gimli::Encoding,
     out_units: &mut write::UnitTable,
     out_strings: &mut write::StringTable,
-    translated: &mut HashSet<DefinedFuncIndex>,
+    translated: &mut HashSet<usize>,
     isa: &dyn TargetIsa,
-) -> Result<Option<(write::UnitId, UnitRefsMap, PendingDebugInfoRefs)>, Error>
-where
-    R: Reader,
-{
+) -> Result<Option<(write::UnitId, UnitRefsMap, PendingDebugInfoRefs)>, Error> {
     let mut die_ref_map = UnitRefsMap::new();
     let mut pending_die_refs = PendingUnitRefs::new();
     let mut pending_di_refs = PendingDebugInfoRefs::new();
@@ -291,6 +280,9 @@ where
         }
     }
 
+    let dwarf = &compilation.translations[module].debuginfo.dwarf;
+    let memory_offset = &compilation.module_memory_offsets[module];
+
     // Iterate over all of this compilation unit's entries.
     let mut entries = program_unit.entries();
     let (mut comp_unit, unit_id, file_map, file_index_base, cu_low_pc, wp_die_id, vmctx_die_id) =
@@ -403,12 +395,11 @@ where
             let range_builder = RangeInfoBuilder::from_subprogram_die(
                 dwarf, &unit, entry, context, addr_tr, cu_low_pc,
             )?;
-            if let RangeInfoBuilder::Function(func_index) = range_builder {
-                if let Some(frame_info) = get_function_frame_info(memory_offset, funcs, func_index)
-                {
-                    current_value_range.push(new_stack_len, frame_info);
-                }
-                translated.insert(func_index);
+            if let RangeInfoBuilder::Function(func) = range_builder {
+                let frame_info = compilation.function_frame_info(module, func);
+                current_value_range.push(new_stack_len, frame_info);
+                let (symbol, _) = compilation.function(module, func);
+                translated.insert(symbol);
                 current_scope_ranges.push(new_stack_len, range_builder.get_ranges(addr_tr));
                 Some(range_builder)
             } else {
diff --git a/crates/cranelift/src/debug/transform/utils.rs b/crates/cranelift/src/debug/transform/utils.rs
index 951833bf4..1ddbfe7d7 100644
--- a/crates/cranelift/src/debug/transform/utils.rs
+++ b/crates/cranelift/src/debug/transform/utils.rs
@@ -1,12 +1,11 @@
 use super::address_transform::AddressTransform;
 use super::expression::{CompiledExpression, FunctionFrameInfo};
 use crate::debug::ModuleMemoryOffset;
-use crate::CompiledFunctionsMetadata;
 use anyhow::Error;
 use cranelift_codegen::isa::TargetIsa;
 use gimli::write;
-use wasmtime_environ::DefinedFuncIndex;
 use wasmtime_versioned_export_macros::versioned_stringify_ident;
+
 ///Adds internal Wasm utility types DIEs such as WebAssemblyPtr and
 /// WasmtimeVMContext.
 ///
@@ -164,23 +163,3 @@ pub(crate) fn append_vmctx_info(
 
     Ok(())
 }
-
-pub(crate) fn get_function_frame_info<'a, 'b, 'c>(
-    memory_offset: &ModuleMemoryOffset,
-    funcs: &'b CompiledFunctionsMetadata,
-    func_index: DefinedFuncIndex,
-) -> Option<FunctionFrameInfo<'a>>
-where
-    'b: 'a,
-    'c: 'a,
-{
-    if let Some(func) = funcs.get(func_index) {
-        let frame_info = FunctionFrameInfo {
-            value_ranges: &func.value_labels_ranges,
-            memory_offset: memory_offset.clone(),
-        };
-        Some(frame_info)
-    } else {
-        None
-    }
-}
diff --git a/crates/cranelift/src/debug/write_debuginfo.rs b/crates/cranelift/src/debug/write_debuginfo.rs
index e5099a704..66b2b1dcc 100644
--- a/crates/cranelift/src/debug/write_debuginfo.rs
+++ b/crates/cranelift/src/debug/write_debuginfo.rs
@@ -1,15 +1,12 @@
 pub use crate::debug::transform::transform_dwarf;
-use crate::debug::ModuleMemoryOffset;
-use crate::CompiledFunctionsMetadata;
+use crate::debug::Compilation;
 use cranelift_codegen::ir::Endianness;
 use cranelift_codegen::isa::{
     unwind::{CfaUnwindInfo, UnwindInfo},
     TargetIsa,
 };
-use cranelift_entity::EntityRef;
 use gimli::write::{Address, Dwarf, EndianVec, FrameTable, Result, Sections, Writer};
 use gimli::{RunTimeEndian, SectionId};
-use wasmtime_environ::DebugInfoData;
 
 #[allow(missing_docs)]
 pub struct DwarfSection {
@@ -142,13 +139,13 @@ impl Writer for WriterRelocate {
 
 fn create_frame_table(
     isa: &dyn TargetIsa,
-    funcs: &CompiledFunctionsMetadata,
+    compilation: &mut Compilation<'_>,
 ) -> Option<FrameTable> {
     let mut table = FrameTable::default();
 
     let cie_id = table.add_cie(isa.create_systemv_cie()?);
 
-    for (i, metadata) in funcs {
+    for (_, symbol, metadata) in compilation.functions() {
         // The CFA-based unwind info will either be natively present, or we
         // have generated it and placed into the "cfa_unwind_info" auxiliary
         // field. We shouldn't emit both, though, it'd be wasteful.
@@ -161,13 +158,7 @@ fn create_frame_table(
         }
 
         if let Some(info) = unwind_info {
-            table.add_fde(
-                cie_id,
-                info.to_fde(Address::Symbol {
-                    symbol: i.index(),
-                    addend: 0,
-                }),
-            );
+            table.add_fde(cie_id, info.to_fde(Address::Symbol { symbol, addend: 0 }));
         }
     }
 
@@ -176,21 +167,10 @@ fn create_frame_table(
 
 pub fn emit_dwarf(
     isa: &dyn TargetIsa,
-    debuginfo_data: &DebugInfoData,
-    funcs: &CompiledFunctionsMetadata,
-    memory_offset: &ModuleMemoryOffset,
-    dwarf_package_bytes: Option<&[u8]>,
-    tunables: &wasmtime_environ::Tunables,
+    compilation: &mut Compilation<'_>,
 ) -> anyhow::Result<Vec<DwarfSection>> {
-    let dwarf = transform_dwarf(
-        isa,
-        debuginfo_data,
-        funcs,
-        memory_offset,
-        dwarf_package_bytes,
-        tunables,
-    )?;
-    let frame_table = create_frame_table(isa, funcs);
+    let dwarf = transform_dwarf(isa, compilation)?;
+    let frame_table = create_frame_table(isa, compilation);
     let sections = emit_dwarf_sections(isa, dwarf, frame_table)?;
     Ok(sections)
 }
diff --git a/crates/cranelift/src/lib.rs b/crates/cranelift/src/lib.rs
index 9a977560d..396b97c9c 100644
--- a/crates/cranelift/src/lib.rs
+++ b/crates/cranelift/src/lib.rs
@@ -11,9 +11,7 @@ use cranelift_codegen::{
     settings, FinalizedMachReloc, FinalizedRelocTarget, MachTrap,
 };
 use cranelift_entity::PrimaryMap;
-use cranelift_wasm::{
-    DefinedFuncIndex, FuncIndex, WasmFuncType, WasmHeapTopType, WasmHeapType, WasmValType,
-};
+use cranelift_wasm::{FuncIndex, WasmFuncType, WasmHeapTopType, WasmHeapType, WasmValType};
 
 use target_lexicon::Architecture;
 use wasmtime_environ::{
@@ -34,8 +32,6 @@ mod debug;
 mod func_environ;
 mod gc;
 
-type CompiledFunctionsMetadata<'a> = PrimaryMap<DefinedFuncIndex, &'a CompiledFunctionMetadata>;
-
 /// Trap code used for debug assertions we emit in our JIT code.
 const DEBUG_ASSERT_TRAP_CODE: u16 = u16::MAX;
 
diff --git a/crates/environ/src/compile/mod.rs b/crates/environ/src/compile/mod.rs
index 9a4f80eef..05151c8f1 100644
--- a/crates/environ/src/compile/mod.rs
+++ b/crates/environ/src/compile/mod.rs
@@ -5,7 +5,7 @@ use crate::prelude::*;
 use crate::{obj, Tunables};
 use crate::{
     BuiltinFunctionIndex, DefinedFuncIndex, FlagValue, FuncIndex, FunctionLoc, ObjectKind,
-    PrimaryMap, WasmError, WasmFuncType, WasmFunctionInfo,
+    PrimaryMap, StaticModuleIndex, WasmError, WasmFuncType, WasmFunctionInfo,
 };
 use anyhow::Result;
 use object::write::{Object, SymbolId};
@@ -353,15 +353,20 @@ pub trait Compiler: Send + Sync {
     #[cfg(feature = "component-model")]
     fn component_compiler(&self) -> &dyn crate::component::ComponentCompiler;
 
-    /// Appends generated DWARF sections to the `obj` specified for the compiled
-    /// functions.
-    fn append_dwarf(
+    /// Appends generated DWARF sections to the `obj` specified.
+    ///
+    /// The `translations` track all compiled functions and `get_func` can be
+    /// used to acquire the metadata for a particular function within a module.
+    fn append_dwarf<'a>(
         &self,
         obj: &mut Object<'_>,
-        translation: &ModuleTranslation<'_>,
-        funcs: &PrimaryMap<DefinedFuncIndex, (SymbolId, &(dyn Any + Send))>,
-        dwarf_package_bytes: Option<&[u8]>,
-        tunables: &Tunables,
+        translations: &'a PrimaryMap<StaticModuleIndex, ModuleTranslation<'a>>,
+        get_func: &'a dyn Fn(
+            StaticModuleIndex,
+            DefinedFuncIndex,
+        ) -> (SymbolId, &'a (dyn Any + Send)),
+        dwarf_package_bytes: Option<&'a [u8]>,
+        tunables: &'a Tunables,
     ) -> Result<()>;
 
     /// Creates a new System V Common Information Entry for the ISA.
diff --git a/crates/environ/src/module.rs b/crates/environ/src/module.rs
index 705201089..cd82115de 100644
--- a/crates/environ/src/module.rs
+++ b/crates/environ/src/module.rs
@@ -676,6 +676,12 @@ impl Module {
             func_ref: FuncRefIndex::reserved_value(),
         })
     }
+
+    /// Returns an iterator over all of the defined function indices in this
+    /// module.
+    pub fn defined_func_indices(&self) -> impl Iterator<Item = DefinedFuncIndex> {
+        (0..self.functions.len() - self.num_imported_funcs).map(|i| DefinedFuncIndex::new(i))
+    }
 }
 
 /// Type information about functions in a wasm module.
diff --git a/crates/wasmtime/src/compile.rs b/crates/wasmtime/src/compile.rs
index d06d90343..a9811814e 100644
--- a/crates/wasmtime/src/compile.rs
+++ b/crates/wasmtime/src/compile.rs
@@ -710,42 +710,18 @@ impl FunctionIndices {
         )?;
 
         // If requested, generate and add DWARF information.
-        if tunables.generate_native_debuginfo &&
-            // We can only add DWARF once. Supporting DWARF for components of
-            // multiple Wasm modules will require merging the DWARF sections
-            // together.
-            translations.len() == 1
-        {
-            for (module, translation) in &translations {
-                let funcs: PrimaryMap<_, _> = self
-                    .indices
-                    .get(&CompileKey::WASM_FUNCTION_KIND)
-                    .map(|xs| {
-                        xs.range(
-                            CompileKey::wasm_function(module, DefinedFuncIndex::from_u32(0))
-                                ..=CompileKey::wasm_function(
-                                    module,
-                                    DefinedFuncIndex::from_u32(u32::MAX - 1),
-                                ),
-                        )
-                    })
-                    .into_iter()
-                    .flat_map(|x| x)
-                    .map(|(_, x)| {
-                        let i = x.unwrap_function();
-                        (symbol_ids_and_locs[i].0, &*compiled_funcs[i].1)
-                    })
-                    .collect();
-                if !funcs.is_empty() {
-                    compiler.append_dwarf(
-                        &mut obj,
-                        translation,
-                        &funcs,
-                        dwarf_package_bytes,
-                        tunables,
-                    )?;
-                }
-            }
+        if tunables.generate_native_debuginfo {
+            compiler.append_dwarf(
+                &mut obj,
+                &translations,
+                &|module, func| {
+                    let bucket = &self.indices[&CompileKey::WASM_FUNCTION_KIND];
+                    let i = bucket[&CompileKey::wasm_function(module, func)].unwrap_function();
+                    (symbol_ids_and_locs[i].0, &*compiled_funcs[i].1)
+                },
+                dwarf_package_bytes,
+                tunables,
+            )?;
         }
 
         let mut obj = wasmtime_environ::ObjectBuilder::new(obj, tunables);
diff --git a/crates/winch/src/compiler.rs b/crates/winch/src/compiler.rs
index 5df12b118..f6438723d 100644
--- a/crates/winch/src/compiler.rs
+++ b/crates/winch/src/compiler.rs
@@ -9,7 +9,7 @@ use wasmtime_cranelift::{CompiledFunction, ModuleTextBuilder};
 use wasmtime_environ::{
     AddressMapSection, BuiltinFunctionIndex, CompileError, DefinedFuncIndex, FunctionBodyData,
     FunctionLoc, ModuleTranslation, ModuleTypesBuilder, PrimaryMap, RelocationTarget,
-    TrapEncodingBuilder, Tunables, VMOffsets, WasmFunctionInfo,
+    StaticModuleIndex, TrapEncodingBuilder, Tunables, VMOffsets, WasmFunctionInfo,
 };
 use winch_codegen::{BuiltinFunctions, TargetIsa};
 
@@ -208,13 +208,16 @@ impl wasmtime_environ::Compiler for Compiler {
         self.trampolines.component_compiler()
     }
 
-    fn append_dwarf(
+    fn append_dwarf<'a>(
         &self,
         _obj: &mut Object<'_>,
-        _translation: &ModuleTranslation<'_>,
-        _funcs: &PrimaryMap<DefinedFuncIndex, (SymbolId, &(dyn Any + Send))>,
-        _dwarf_package: Option<&[u8]>,
-        _tunables: &wasmtime_environ::Tunables,
+        _translations: &'a PrimaryMap<StaticModuleIndex, ModuleTranslation<'a>>,
+        _get_func: &'a dyn Fn(
+            StaticModuleIndex,
+            DefinedFuncIndex,
+        ) -> (SymbolId, &'a (dyn Any + Send)),
+        _dwarf_package_bytes: Option<&'a [u8]>,
+        _tunables: &'a Tunables,
     ) -> Result<()> {
         todo!()
     }


Test patch:
diff --git a/tests/all/debug/lldb.rs b/tests/all/debug/lldb.rs
index d0c653e06..dadf8a154 100644
--- a/tests/all/debug/lldb.rs
+++ b/tests/all/debug/lldb.rs
@@ -356,7 +356,7 @@ check: exited with status = 0
     #[test]
     #[ignore]
     fn dwarf_simple() -> Result<()> {
-        for wasm in [DWARF_SIMPLE] {
+        for wasm in [DWARF_SIMPLE, DWARF_SIMPLE_COMPONENT] {
             test_dwarf_simple(wasm, &[])?;
         }
         Ok(())