about summary refs log tree commit diff
path: root/compiler/rustc_codegen_cranelift/src/debuginfo
diff options
context:
space:
mode:
authorbjorn3 <bjorn3@users.noreply.github.com>2020-10-26 09:53:27 +0100
committerbjorn3 <bjorn3@users.noreply.github.com>2020-10-26 09:53:27 +0100
commitac4f7deb2f3558d2d923fa6ddcbb7210db9c2d52 (patch)
treeca7dcb9c908285e2af6eb0d8807d3e81dc9ba2ee /compiler/rustc_codegen_cranelift/src/debuginfo
parentcf798c1ec65a5ec3491846777f9003fabb881b4a (diff)
parent793d26047f994e23415f8f6bb5686ff25d3dda92 (diff)
downloadrust-ac4f7deb2f3558d2d923fa6ddcbb7210db9c2d52.tar.gz
rust-ac4f7deb2f3558d2d923fa6ddcbb7210db9c2d52.zip
Add 'compiler/rustc_codegen_cranelift/' from commit '793d26047f994e23415f8f6bb5686ff25d3dda92'
git-subtree-dir: compiler/rustc_codegen_cranelift
git-subtree-mainline: cf798c1ec65a5ec3491846777f9003fabb881b4a
git-subtree-split: 793d26047f994e23415f8f6bb5686ff25d3dda92
Diffstat (limited to 'compiler/rustc_codegen_cranelift/src/debuginfo')
-rw-r--r--compiler/rustc_codegen_cranelift/src/debuginfo/emit.rs204
-rw-r--r--compiler/rustc_codegen_cranelift/src/debuginfo/line_info.rs258
-rw-r--r--compiler/rustc_codegen_cranelift/src/debuginfo/mod.rs487
-rw-r--r--compiler/rustc_codegen_cranelift/src/debuginfo/unwind.rs167
4 files changed, 1116 insertions, 0 deletions
diff --git a/compiler/rustc_codegen_cranelift/src/debuginfo/emit.rs b/compiler/rustc_codegen_cranelift/src/debuginfo/emit.rs
new file mode 100644
index 00000000000..cf8fee2b1d1
--- /dev/null
+++ b/compiler/rustc_codegen_cranelift/src/debuginfo/emit.rs
@@ -0,0 +1,204 @@
+//! Write the debuginfo into an object file.
+
+use rustc_data_structures::fx::FxHashMap;
+
+use gimli::write::{Address, AttributeValue, EndianVec, Result, Sections, Writer};
+use gimli::{RunTimeEndian, SectionId};
+
+use crate::backend::WriteDebugInfo;
+
+use super::DebugContext;
+
+impl DebugContext<'_> {
+    pub(crate) fn emit<P: WriteDebugInfo>(&mut self, product: &mut P) {
+        let unit_range_list_id = self.dwarf.unit.ranges.add(self.unit_range_list.clone());
+        let root = self.dwarf.unit.root();
+        let root = self.dwarf.unit.get_mut(root);
+        root.set(
+            gimli::DW_AT_ranges,
+            AttributeValue::RangeListRef(unit_range_list_id),
+        );
+
+        let mut sections = Sections::new(WriterRelocate::new(self.endian));
+        self.dwarf.write(&mut sections).unwrap();
+
+        let mut section_map = FxHashMap::default();
+        let _: Result<()> = sections.for_each_mut(|id, section| {
+            if !section.writer.slice().is_empty() {
+                let section_id = product.add_debug_section(id, section.writer.take());
+                section_map.insert(id, section_id);
+            }
+            Ok(())
+        });
+
+        let _: Result<()> = sections.for_each(|id, section| {
+            if let Some(section_id) = section_map.get(&id) {
+                for reloc in &section.relocs {
+                    product.add_debug_reloc(&section_map, section_id, reloc);
+                }
+            }
+            Ok(())
+        });
+    }
+}
+
+#[derive(Clone)]
+pub(crate) struct DebugReloc {
+    pub(crate) offset: u32,
+    pub(crate) size: u8,
+    pub(crate) name: DebugRelocName,
+    pub(crate) addend: i64,
+    pub(crate) kind: object::RelocationKind,
+}
+
+#[derive(Clone)]
+pub(crate) enum DebugRelocName {
+    Section(SectionId),
+    Symbol(usize),
+}
+
+/// A [`Writer`] that collects all necessary relocations.
+#[derive(Clone)]
+pub(super) struct WriterRelocate {
+    pub(super) relocs: Vec<DebugReloc>,
+    pub(super) writer: EndianVec<RunTimeEndian>,
+}
+
+impl WriterRelocate {
+    pub(super) fn new(endian: RunTimeEndian) -> Self {
+        WriterRelocate {
+            relocs: Vec::new(),
+            writer: EndianVec::new(endian),
+        }
+    }
+
+    /// Perform the collected relocations to be usable for JIT usage.
+    #[cfg(feature = "jit")]
+    pub(super) fn relocate_for_jit(
+        mut self,
+        jit_product: &cranelift_simplejit::SimpleJITProduct,
+    ) -> Vec<u8> {
+        use std::convert::TryInto;
+
+        for reloc in self.relocs.drain(..) {
+            match reloc.name {
+                super::DebugRelocName::Section(_) => unreachable!(),
+                super::DebugRelocName::Symbol(sym) => {
+                    let addr = jit_product
+                        .lookup_func(cranelift_module::FuncId::from_u32(sym.try_into().unwrap()));
+                    let val = (addr as u64 as i64 + reloc.addend) as u64;
+                    self.writer
+                        .write_udata_at(reloc.offset as usize, val, reloc.size)
+                        .unwrap();
+                }
+            }
+        }
+        self.writer.into_vec()
+    }
+}
+
+impl Writer for WriterRelocate {
+    type Endian = RunTimeEndian;
+
+    fn endian(&self) -> Self::Endian {
+        self.writer.endian()
+    }
+
+    fn len(&self) -> usize {
+        self.writer.len()
+    }
+
+    fn write(&mut self, bytes: &[u8]) -> Result<()> {
+        self.writer.write(bytes)
+    }
+
+    fn write_at(&mut self, offset: usize, bytes: &[u8]) -> Result<()> {
+        self.writer.write_at(offset, bytes)
+    }
+
+    fn write_address(&mut self, address: Address, size: u8) -> Result<()> {
+        match address {
+            Address::Constant(val) => self.write_udata(val, size),
+            Address::Symbol { symbol, addend } => {
+                let offset = self.len() as u64;
+                self.relocs.push(DebugReloc {
+                    offset: offset as u32,
+                    size,
+                    name: DebugRelocName::Symbol(symbol),
+                    addend: addend as i64,
+                    kind: object::RelocationKind::Absolute,
+                });
+                self.write_udata(0, size)
+            }
+        }
+    }
+
+    fn write_offset(&mut self, val: usize, section: SectionId, size: u8) -> Result<()> {
+        let offset = self.len() as u32;
+        self.relocs.push(DebugReloc {
+            offset,
+            size,
+            name: DebugRelocName::Section(section),
+            addend: val as i64,
+            kind: object::RelocationKind::Absolute,
+        });
+        self.write_udata(0, size)
+    }
+
+    fn write_offset_at(
+        &mut self,
+        offset: usize,
+        val: usize,
+        section: SectionId,
+        size: u8,
+    ) -> Result<()> {
+        self.relocs.push(DebugReloc {
+            offset: offset as u32,
+            size,
+            name: DebugRelocName::Section(section),
+            addend: val as i64,
+            kind: object::RelocationKind::Absolute,
+        });
+        self.write_udata_at(offset, 0, size)
+    }
+
+    fn write_eh_pointer(&mut self, address: Address, eh_pe: gimli::DwEhPe, size: u8) -> Result<()> {
+        match address {
+            // Address::Constant arm copied from gimli
+            Address::Constant(val) => {
+                // Indirect doesn't matter here.
+                let val = match eh_pe.application() {
+                    gimli::DW_EH_PE_absptr => val,
+                    gimli::DW_EH_PE_pcrel => {
+                        // TODO: better handling of sign
+                        let offset = self.len() as u64;
+                        offset.wrapping_sub(val)
+                    }
+                    _ => {
+                        return Err(gimli::write::Error::UnsupportedPointerEncoding(eh_pe));
+                    }
+                };
+                self.write_eh_pointer_data(val, eh_pe.format(), size)
+            }
+            Address::Symbol { symbol, addend } => match eh_pe.application() {
+                gimli::DW_EH_PE_pcrel => {
+                    let size = match eh_pe.format() {
+                        gimli::DW_EH_PE_sdata4 => 4,
+                        _ => return Err(gimli::write::Error::UnsupportedPointerEncoding(eh_pe)),
+                    };
+                    self.relocs.push(DebugReloc {
+                        offset: self.len() as u32,
+                        size,
+                        name: DebugRelocName::Symbol(symbol),
+                        addend,
+                        kind: object::RelocationKind::Relative,
+                    });
+                    self.write_udata(0, size)
+                }
+                _ => {
+                    return Err(gimli::write::Error::UnsupportedPointerEncoding(eh_pe));
+                }
+            },
+        }
+    }
+}
diff --git a/compiler/rustc_codegen_cranelift/src/debuginfo/line_info.rs b/compiler/rustc_codegen_cranelift/src/debuginfo/line_info.rs
new file mode 100644
index 00000000000..4de84855328
--- /dev/null
+++ b/compiler/rustc_codegen_cranelift/src/debuginfo/line_info.rs
@@ -0,0 +1,258 @@
+//! Line info generation (`.debug_line`)
+
+use std::ffi::OsStr;
+use std::path::{Component, Path};
+
+use crate::prelude::*;
+
+use rustc_span::{
+    FileName, Pos, SourceFile, SourceFileAndLine, SourceFileHash, SourceFileHashAlgorithm,
+};
+
+use cranelift_codegen::binemit::CodeOffset;
+use cranelift_codegen::machinst::MachSrcLoc;
+
+use gimli::write::{
+    Address, AttributeValue, FileId, FileInfo, LineProgram, LineString, LineStringTable,
+    UnitEntryId,
+};
+
+// OPTIMIZATION: It is cheaper to do this in one pass than using `.parent()` and `.file_name()`.
+fn split_path_dir_and_file(path: &Path) -> (&Path, &OsStr) {
+    let mut iter = path.components();
+    let file_name = match iter.next_back() {
+        Some(Component::Normal(p)) => p,
+        component => {
+            panic!(
+                "Path component {:?} of path {} is an invalid filename",
+                component,
+                path.display()
+            );
+        }
+    };
+    let parent = iter.as_path();
+    (parent, file_name)
+}
+
+// OPTIMIZATION: Avoid UTF-8 validation on UNIX.
+fn osstr_as_utf8_bytes(path: &OsStr) -> &[u8] {
+    #[cfg(unix)]
+    {
+        use std::os::unix::ffi::OsStrExt;
+        return path.as_bytes();
+    }
+    #[cfg(not(unix))]
+    {
+        return path.to_str().unwrap().as_bytes();
+    }
+}
+
+pub(crate) const MD5_LEN: usize = 16;
+
+pub fn make_file_info(hash: SourceFileHash) -> Option<FileInfo> {
+    if hash.kind == SourceFileHashAlgorithm::Md5 {
+        let mut buf = [0u8; MD5_LEN];
+        buf.copy_from_slice(hash.hash_bytes());
+        Some(FileInfo {
+            timestamp: 0,
+            size: 0,
+            md5: buf,
+        })
+    } else {
+        None
+    }
+}
+
+fn line_program_add_file(
+    line_program: &mut LineProgram,
+    line_strings: &mut LineStringTable,
+    file: &SourceFile,
+) -> FileId {
+    match &file.name {
+        FileName::Real(path) => {
+            let (dir_path, file_name) = split_path_dir_and_file(path.stable_name());
+            let dir_name = osstr_as_utf8_bytes(dir_path.as_os_str());
+            let file_name = osstr_as_utf8_bytes(file_name);
+
+            let dir_id = if !dir_name.is_empty() {
+                let dir_name = LineString::new(dir_name, line_program.encoding(), line_strings);
+                line_program.add_directory(dir_name)
+            } else {
+                line_program.default_directory()
+            };
+            let file_name = LineString::new(file_name, line_program.encoding(), line_strings);
+
+            let info = make_file_info(file.src_hash);
+
+            line_program.file_has_md5 &= info.is_some();
+            line_program.add_file(file_name, dir_id, info)
+        }
+        // FIXME give more appropriate file names
+        filename => {
+            let dir_id = line_program.default_directory();
+            let dummy_file_name = LineString::new(
+                filename.to_string().into_bytes(),
+                line_program.encoding(),
+                line_strings,
+            );
+            line_program.add_file(dummy_file_name, dir_id, None)
+        }
+    }
+}
+
+impl<'tcx> DebugContext<'tcx> {
+    pub(super) fn emit_location(&mut self, entry_id: UnitEntryId, span: Span) {
+        let loc = self.tcx.sess.source_map().lookup_char_pos(span.lo());
+
+        let file_id = line_program_add_file(
+            &mut self.dwarf.unit.line_program,
+            &mut self.dwarf.line_strings,
+            &loc.file,
+        );
+
+        let entry = self.dwarf.unit.get_mut(entry_id);
+
+        entry.set(
+            gimli::DW_AT_decl_file,
+            AttributeValue::FileIndex(Some(file_id)),
+        );
+        entry.set(
+            gimli::DW_AT_decl_line,
+            AttributeValue::Udata(loc.line as u64),
+        );
+        // FIXME: probably omit this
+        entry.set(
+            gimli::DW_AT_decl_column,
+            AttributeValue::Udata(loc.col.to_usize() as u64),
+        );
+    }
+
+    pub(super) fn create_debug_lines(
+        &mut self,
+        isa: &dyn cranelift_codegen::isa::TargetIsa,
+        symbol: usize,
+        entry_id: UnitEntryId,
+        context: &Context,
+        function_span: Span,
+        source_info_set: &indexmap::IndexSet<SourceInfo>,
+    ) -> CodeOffset {
+        let tcx = self.tcx;
+        let line_program = &mut self.dwarf.unit.line_program;
+        let func = &context.func;
+
+        let line_strings = &mut self.dwarf.line_strings;
+        let mut last_span = None;
+        let mut last_file = None;
+        let mut create_row_for_span = |line_program: &mut LineProgram, span: Span| {
+            if let Some(last_span) = last_span {
+                if span == last_span {
+                    line_program.generate_row();
+                    return;
+                }
+            }
+            last_span = Some(span);
+
+            // Based on https://github.com/rust-lang/rust/blob/e369d87b015a84653343032833d65d0545fd3f26/src/librustc_codegen_ssa/mir/mod.rs#L116-L131
+            // In order to have a good line stepping behavior in debugger, we overwrite debug
+            // locations of macro expansions with that of the outermost expansion site
+            // (unless the crate is being compiled with `-Z debug-macros`).
+            let span = if !span.from_expansion() || tcx.sess.opts.debugging_opts.debug_macros {
+                span
+            } else {
+                // Walk up the macro expansion chain until we reach a non-expanded span.
+                // We also stop at the function body level because no line stepping can occur
+                // at the level above that.
+                rustc_span::hygiene::walk_chain(span, function_span.ctxt())
+            };
+
+            let (file, line, col) = match tcx.sess.source_map().lookup_line(span.lo()) {
+                Ok(SourceFileAndLine { sf: file, line }) => {
+                    let line_pos = file.line_begin_pos(span.lo());
+
+                    (
+                        file,
+                        u64::try_from(line).unwrap() + 1,
+                        u64::from((span.lo() - line_pos).to_u32()) + 1,
+                    )
+                }
+                Err(file) => (file, 0, 0),
+            };
+
+            // line_program_add_file is very slow.
+            // Optimize for the common case of the current file not being changed.
+            let current_file_changed = if let Some(last_file) = &last_file {
+                // If the allocations are not equal, then the files may still be equal, but that
+                // is not a problem, as this is just an optimization.
+                !rustc_data_structures::sync::Lrc::ptr_eq(last_file, &file)
+            } else {
+                true
+            };
+            if current_file_changed {
+                let file_id = line_program_add_file(line_program, line_strings, &file);
+                line_program.row().file = file_id;
+                last_file = Some(file.clone());
+            }
+
+            line_program.row().line = line;
+            line_program.row().column = col;
+            line_program.generate_row();
+        };
+
+        line_program.begin_sequence(Some(Address::Symbol { symbol, addend: 0 }));
+
+        let mut func_end = 0;
+
+        if let Some(ref mcr) = &context.mach_compile_result {
+            for &MachSrcLoc { start, end, loc } in mcr.buffer.get_srclocs_sorted() {
+                line_program.row().address_offset = u64::from(start);
+                if !loc.is_default() {
+                    let source_info = *source_info_set.get_index(loc.bits() as usize).unwrap();
+                    create_row_for_span(line_program, source_info.span);
+                } else {
+                    create_row_for_span(line_program, function_span);
+                }
+                func_end = end;
+            }
+
+            line_program.end_sequence(u64::from(func_end));
+
+            func_end = mcr.buffer.total_size();
+        } else {
+            let encinfo = isa.encoding_info();
+            let mut blocks = func.layout.blocks().collect::<Vec<_>>();
+            blocks.sort_by_key(|block| func.offsets[*block]); // Ensure inst offsets always increase
+
+            for block in blocks {
+                for (offset, inst, size) in func.inst_offsets(block, &encinfo) {
+                    let srcloc = func.srclocs[inst];
+                    line_program.row().address_offset = u64::from(offset);
+                    if !srcloc.is_default() {
+                        let source_info =
+                            *source_info_set.get_index(srcloc.bits() as usize).unwrap();
+                        create_row_for_span(line_program, source_info.span);
+                    } else {
+                        create_row_for_span(line_program, function_span);
+                    }
+                    func_end = offset + size;
+                }
+            }
+            line_program.end_sequence(u64::from(func_end));
+        }
+
+        assert_ne!(func_end, 0);
+
+        let entry = self.dwarf.unit.get_mut(entry_id);
+        entry.set(
+            gimli::DW_AT_low_pc,
+            AttributeValue::Address(Address::Symbol { symbol, addend: 0 }),
+        );
+        entry.set(
+            gimli::DW_AT_high_pc,
+            AttributeValue::Udata(u64::from(func_end)),
+        );
+
+        self.emit_location(entry_id, function_span);
+
+        func_end
+    }
+}
diff --git a/compiler/rustc_codegen_cranelift/src/debuginfo/mod.rs b/compiler/rustc_codegen_cranelift/src/debuginfo/mod.rs
new file mode 100644
index 00000000000..cbf9522b1d7
--- /dev/null
+++ b/compiler/rustc_codegen_cranelift/src/debuginfo/mod.rs
@@ -0,0 +1,487 @@
+//! Handling of everything related to debuginfo.
+
+mod emit;
+mod line_info;
+mod unwind;
+
+use crate::prelude::*;
+
+use rustc_index::vec::IndexVec;
+
+use cranelift_codegen::entity::EntityRef;
+use cranelift_codegen::ir::{StackSlots, ValueLabel, ValueLoc};
+use cranelift_codegen::isa::TargetIsa;
+use cranelift_codegen::ValueLocRange;
+
+use gimli::write::{
+    Address, AttributeValue, DwarfUnit, Expression, LineProgram, LineString, Location,
+    LocationList, Range, RangeList, UnitEntryId,
+};
+use gimli::{Encoding, Format, LineEncoding, RunTimeEndian, X86_64};
+
+pub(crate) use emit::{DebugReloc, DebugRelocName};
+pub(crate) use unwind::UnwindContext;
+
+fn target_endian(tcx: TyCtxt<'_>) -> RunTimeEndian {
+    use rustc_target::abi::Endian;
+
+    match tcx.data_layout.endian {
+        Endian::Big => RunTimeEndian::Big,
+        Endian::Little => RunTimeEndian::Little,
+    }
+}
+
+pub(crate) struct DebugContext<'tcx> {
+    tcx: TyCtxt<'tcx>,
+
+    endian: RunTimeEndian,
+
+    dwarf: DwarfUnit,
+    unit_range_list: RangeList,
+
+    clif_types: FxHashMap<Type, UnitEntryId>,
+    types: FxHashMap<Ty<'tcx>, UnitEntryId>,
+}
+
+impl<'tcx> DebugContext<'tcx> {
+    pub(crate) fn new(tcx: TyCtxt<'tcx>, isa: &dyn TargetIsa) -> Self {
+        let encoding = Encoding {
+            format: Format::Dwarf32,
+            // TODO: this should be configurable
+            // macOS doesn't seem to support DWARF > 3
+            // 5 version is required for md5 file hash
+            version: if tcx.sess.target.options.is_like_osx {
+                3
+            } else {
+                // FIXME change to version 5 once the gdb and lldb shipping with the latest debian
+                // support it.
+                4
+            },
+            address_size: isa.frontend_config().pointer_bytes(),
+        };
+
+        let mut dwarf = DwarfUnit::new(encoding);
+
+        // FIXME: how to get version when building out of tree?
+        // Normally this would use option_env!("CFG_VERSION").
+        let producer = format!("cg_clif (rustc {})", "unknown version");
+        let comp_dir = tcx.sess.working_dir.0.to_string_lossy().into_owned();
+        let (name, file_info) = match tcx.sess.local_crate_source_file.clone() {
+            Some(path) => {
+                let name = path.to_string_lossy().into_owned();
+                (name, None)
+            }
+            None => (tcx.crate_name(LOCAL_CRATE).to_string(), None),
+        };
+
+        let mut line_program = LineProgram::new(
+            encoding,
+            LineEncoding::default(),
+            LineString::new(comp_dir.as_bytes(), encoding, &mut dwarf.line_strings),
+            LineString::new(name.as_bytes(), encoding, &mut dwarf.line_strings),
+            file_info,
+        );
+        line_program.file_has_md5 = file_info.is_some();
+
+        dwarf.unit.line_program = line_program;
+
+        {
+            let name = dwarf.strings.add(name);
+            let comp_dir = dwarf.strings.add(comp_dir);
+
+            let root = dwarf.unit.root();
+            let root = dwarf.unit.get_mut(root);
+            root.set(
+                gimli::DW_AT_producer,
+                AttributeValue::StringRef(dwarf.strings.add(producer)),
+            );
+            root.set(
+                gimli::DW_AT_language,
+                AttributeValue::Language(gimli::DW_LANG_Rust),
+            );
+            root.set(gimli::DW_AT_name, AttributeValue::StringRef(name));
+            root.set(gimli::DW_AT_comp_dir, AttributeValue::StringRef(comp_dir));
+            root.set(
+                gimli::DW_AT_low_pc,
+                AttributeValue::Address(Address::Constant(0)),
+            );
+        }
+
+        DebugContext {
+            tcx,
+
+            endian: target_endian(tcx),
+
+            dwarf,
+            unit_range_list: RangeList(Vec::new()),
+
+            clif_types: FxHashMap::default(),
+            types: FxHashMap::default(),
+        }
+    }
+
+    fn dwarf_ty_for_clif_ty(&mut self, ty: Type) -> UnitEntryId {
+        if let Some(type_id) = self.clif_types.get(&ty) {
+            return *type_id;
+        }
+
+        let new_entry = |dwarf: &mut DwarfUnit, tag| dwarf.unit.add(dwarf.unit.root(), tag);
+
+        let primitive = |dwarf: &mut DwarfUnit, ate| {
+            let type_id = new_entry(dwarf, gimli::DW_TAG_base_type);
+            let type_entry = dwarf.unit.get_mut(type_id);
+            type_entry.set(gimli::DW_AT_encoding, AttributeValue::Encoding(ate));
+            type_id
+        };
+
+        let type_id = if ty.is_bool() {
+            primitive(&mut self.dwarf, gimli::DW_ATE_boolean)
+        } else if ty.is_int() {
+            primitive(&mut self.dwarf, gimli::DW_ATE_address)
+        } else if ty.is_float() {
+            primitive(&mut self.dwarf, gimli::DW_ATE_float)
+        } else {
+            new_entry(&mut self.dwarf, gimli::DW_TAG_structure_type)
+        };
+
+        let type_entry = self.dwarf.unit.get_mut(type_id);
+        type_entry.set(
+            gimli::DW_AT_name,
+            AttributeValue::String(format!("{}", ty).replace('i', "u").into_bytes()),
+        );
+        type_entry.set(
+            gimli::DW_AT_byte_size,
+            AttributeValue::Udata(u64::from(ty.bytes())),
+        );
+
+        type_id
+    }
+
+    fn dwarf_ty(&mut self, ty: Ty<'tcx>) -> UnitEntryId {
+        if let Some(type_id) = self.types.get(ty) {
+            return *type_id;
+        }
+
+        let new_entry = |dwarf: &mut DwarfUnit, tag| dwarf.unit.add(dwarf.unit.root(), tag);
+
+        let primitive = |dwarf: &mut DwarfUnit, ate| {
+            let type_id = new_entry(dwarf, gimli::DW_TAG_base_type);
+            let type_entry = dwarf.unit.get_mut(type_id);
+            type_entry.set(gimli::DW_AT_encoding, AttributeValue::Encoding(ate));
+            type_id
+        };
+
+        let name = format!("{}", ty);
+        let layout = self.tcx.layout_of(ParamEnv::reveal_all().and(ty)).unwrap();
+
+        let type_id = match ty.kind() {
+            ty::Bool => primitive(&mut self.dwarf, gimli::DW_ATE_boolean),
+            ty::Char => primitive(&mut self.dwarf, gimli::DW_ATE_UTF),
+            ty::Uint(_) => primitive(&mut self.dwarf, gimli::DW_ATE_unsigned),
+            ty::Int(_) => primitive(&mut self.dwarf, gimli::DW_ATE_signed),
+            ty::Float(_) => primitive(&mut self.dwarf, gimli::DW_ATE_float),
+            ty::Ref(_, pointee_ty, _mutbl)
+            | ty::RawPtr(ty::TypeAndMut {
+                ty: pointee_ty,
+                mutbl: _mutbl,
+            }) => {
+                let type_id = new_entry(&mut self.dwarf, gimli::DW_TAG_pointer_type);
+
+                // Ensure that type is inserted before recursing to avoid duplicates
+                self.types.insert(ty, type_id);
+
+                let pointee = self.dwarf_ty(pointee_ty);
+
+                let type_entry = self.dwarf.unit.get_mut(type_id);
+
+                //type_entry.set(gimli::DW_AT_mutable, AttributeValue::Flag(mutbl == rustc_hir::Mutability::Mut));
+                type_entry.set(gimli::DW_AT_type, AttributeValue::UnitRef(pointee));
+
+                type_id
+            }
+            ty::Adt(adt_def, _substs) if adt_def.is_struct() && !layout.is_unsized() => {
+                let type_id = new_entry(&mut self.dwarf, gimli::DW_TAG_structure_type);
+
+                // Ensure that type is inserted before recursing to avoid duplicates
+                self.types.insert(ty, type_id);
+
+                let variant = adt_def.non_enum_variant();
+
+                for (field_idx, field_def) in variant.fields.iter().enumerate() {
+                    let field_offset = layout.fields.offset(field_idx);
+                    let field_layout = layout
+                        .field(
+                            &layout::LayoutCx {
+                                tcx: self.tcx,
+                                param_env: ParamEnv::reveal_all(),
+                            },
+                            field_idx,
+                        )
+                        .unwrap();
+
+                    let field_type = self.dwarf_ty(field_layout.ty);
+
+                    let field_id = self.dwarf.unit.add(type_id, gimli::DW_TAG_member);
+                    let field_entry = self.dwarf.unit.get_mut(field_id);
+
+                    field_entry.set(
+                        gimli::DW_AT_name,
+                        AttributeValue::String(field_def.ident.as_str().to_string().into_bytes()),
+                    );
+                    field_entry.set(
+                        gimli::DW_AT_data_member_location,
+                        AttributeValue::Udata(field_offset.bytes()),
+                    );
+                    field_entry.set(gimli::DW_AT_type, AttributeValue::UnitRef(field_type));
+                }
+
+                type_id
+            }
+            _ => new_entry(&mut self.dwarf, gimli::DW_TAG_structure_type),
+        };
+
+        let type_entry = self.dwarf.unit.get_mut(type_id);
+
+        type_entry.set(gimli::DW_AT_name, AttributeValue::String(name.into_bytes()));
+        type_entry.set(
+            gimli::DW_AT_byte_size,
+            AttributeValue::Udata(layout.size.bytes()),
+        );
+
+        self.types.insert(ty, type_id);
+
+        type_id
+    }
+
+    fn define_local(&mut self, scope: UnitEntryId, name: String, ty: Ty<'tcx>) -> UnitEntryId {
+        let dw_ty = self.dwarf_ty(ty);
+
+        let var_id = self.dwarf.unit.add(scope, gimli::DW_TAG_variable);
+        let var_entry = self.dwarf.unit.get_mut(var_id);
+
+        var_entry.set(gimli::DW_AT_name, AttributeValue::String(name.into_bytes()));
+        var_entry.set(gimli::DW_AT_type, AttributeValue::UnitRef(dw_ty));
+
+        var_id
+    }
+
+    pub(crate) fn define_function(
+        &mut self,
+        instance: Instance<'tcx>,
+        func_id: FuncId,
+        name: &str,
+        isa: &dyn TargetIsa,
+        context: &Context,
+        source_info_set: &indexmap::IndexSet<SourceInfo>,
+        local_map: IndexVec<mir::Local, CPlace<'tcx>>,
+    ) {
+        let symbol = func_id.as_u32() as usize;
+        let mir = self.tcx.instance_mir(instance.def);
+
+        // FIXME: add to appropriate scope instead of root
+        let scope = self.dwarf.unit.root();
+
+        let entry_id = self.dwarf.unit.add(scope, gimli::DW_TAG_subprogram);
+        let entry = self.dwarf.unit.get_mut(entry_id);
+        let name_id = self.dwarf.strings.add(name);
+        // Gdb requires DW_AT_name. Otherwise the DW_TAG_subprogram is skipped.
+        entry.set(gimli::DW_AT_name, AttributeValue::StringRef(name_id));
+        entry.set(
+            gimli::DW_AT_linkage_name,
+            AttributeValue::StringRef(name_id),
+        );
+
+        let end =
+            self.create_debug_lines(isa, symbol, entry_id, context, mir.span, source_info_set);
+
+        self.unit_range_list.0.push(Range::StartLength {
+            begin: Address::Symbol { symbol, addend: 0 },
+            length: u64::from(end),
+        });
+
+        if isa.get_mach_backend().is_some() {
+            return; // Not yet implemented for the AArch64 backend.
+        }
+
+        let func_entry = self.dwarf.unit.get_mut(entry_id);
+        // Gdb requires both DW_AT_low_pc and DW_AT_high_pc. Otherwise the DW_TAG_subprogram is skipped.
+        func_entry.set(
+            gimli::DW_AT_low_pc,
+            AttributeValue::Address(Address::Symbol { symbol, addend: 0 }),
+        );
+        // Using Udata for DW_AT_high_pc requires at least DWARF4
+        func_entry.set(gimli::DW_AT_high_pc, AttributeValue::Udata(u64::from(end)));
+
+        // FIXME Remove once actual debuginfo for locals works.
+        for (i, (param, &val)) in context
+            .func
+            .signature
+            .params
+            .iter()
+            .zip(
+                context
+                    .func
+                    .dfg
+                    .block_params(context.func.layout.entry_block().unwrap()),
+            )
+            .enumerate()
+        {
+            use cranelift_codegen::ir::ArgumentPurpose;
+            let base_name = match param.purpose {
+                ArgumentPurpose::Normal => "arg",
+                ArgumentPurpose::StructArgument(_) => "struct_arg",
+                ArgumentPurpose::StructReturn => "sret",
+                ArgumentPurpose::Link
+                | ArgumentPurpose::FramePointer
+                | ArgumentPurpose::CalleeSaved => continue,
+                ArgumentPurpose::VMContext
+                | ArgumentPurpose::SignatureId
+                | ArgumentPurpose::CallerTLS
+                | ArgumentPurpose::CalleeTLS
+                | ArgumentPurpose::StackLimit => unreachable!(),
+            };
+            let name = format!("{}{}", base_name, i);
+
+            let dw_ty = self.dwarf_ty_for_clif_ty(param.value_type);
+            let loc =
+                translate_loc(isa, context.func.locations[val], &context.func.stack_slots).unwrap();
+
+            let arg_id = self
+                .dwarf
+                .unit
+                .add(entry_id, gimli::DW_TAG_formal_parameter);
+            let var_entry = self.dwarf.unit.get_mut(arg_id);
+
+            var_entry.set(gimli::DW_AT_name, AttributeValue::String(name.into_bytes()));
+            var_entry.set(gimli::DW_AT_type, AttributeValue::UnitRef(dw_ty));
+            var_entry.set(gimli::DW_AT_location, AttributeValue::Exprloc(loc));
+        }
+
+        // FIXME make it more reliable and implement scopes before re-enabling this.
+        if false {
+            let value_labels_ranges = context.build_value_labels_ranges(isa).unwrap();
+
+            for (local, _local_decl) in mir.local_decls.iter_enumerated() {
+                let ty = self.tcx.subst_and_normalize_erasing_regions(
+                    instance.substs,
+                    ty::ParamEnv::reveal_all(),
+                    &mir.local_decls[local].ty,
+                );
+                let var_id = self.define_local(entry_id, format!("{:?}", local), ty);
+
+                let location = place_location(
+                    self,
+                    isa,
+                    symbol,
+                    context,
+                    &local_map,
+                    &value_labels_ranges,
+                    Place {
+                        local,
+                        projection: ty::List::empty(),
+                    },
+                );
+
+                let var_entry = self.dwarf.unit.get_mut(var_id);
+                var_entry.set(gimli::DW_AT_location, location);
+            }
+        }
+
+        // FIXME create locals for all entries in mir.var_debug_info
+    }
+}
+
+fn place_location<'tcx>(
+    debug_context: &mut DebugContext<'tcx>,
+    isa: &dyn TargetIsa,
+    symbol: usize,
+    context: &Context,
+    local_map: &IndexVec<mir::Local, CPlace<'tcx>>,
+    #[allow(rustc::default_hash_types)] value_labels_ranges: &std::collections::HashMap<
+        ValueLabel,
+        Vec<ValueLocRange>,
+    >,
+    place: Place<'tcx>,
+) -> AttributeValue {
+    assert!(place.projection.is_empty()); // FIXME implement them
+
+    match local_map[place.local].inner() {
+        CPlaceInner::Var(_local, var) => {
+            let value_label = cranelift_codegen::ir::ValueLabel::new(var.index());
+            if let Some(value_loc_ranges) = value_labels_ranges.get(&value_label) {
+                let loc_list = LocationList(
+                    value_loc_ranges
+                        .iter()
+                        .map(|value_loc_range| Location::StartEnd {
+                            begin: Address::Symbol {
+                                symbol,
+                                addend: i64::from(value_loc_range.start),
+                            },
+                            end: Address::Symbol {
+                                symbol,
+                                addend: i64::from(value_loc_range.end),
+                            },
+                            data: translate_loc(
+                                isa,
+                                value_loc_range.loc,
+                                &context.func.stack_slots,
+                            )
+                            .unwrap(),
+                        })
+                        .collect(),
+                );
+                let loc_list_id = debug_context.dwarf.unit.locations.add(loc_list);
+
+                AttributeValue::LocationListRef(loc_list_id)
+            } else {
+                // FIXME set value labels for unused locals
+
+                AttributeValue::Exprloc(Expression::new())
+            }
+        }
+        CPlaceInner::VarPair(_, _, _) => {
+            // FIXME implement this
+
+            AttributeValue::Exprloc(Expression::new())
+        }
+        CPlaceInner::VarLane(_, _, _) => {
+            // FIXME implement this
+
+            AttributeValue::Exprloc(Expression::new())
+        }
+        CPlaceInner::Addr(_, _) => {
+            // FIXME implement this (used by arguments and returns)
+
+            AttributeValue::Exprloc(Expression::new())
+
+            // For PointerBase::Stack:
+            //AttributeValue::Exprloc(translate_loc(ValueLoc::Stack(*stack_slot), &context.func.stack_slots).unwrap())
+        }
+    }
+}
+
+// Adapted from https://github.com/CraneStation/wasmtime/blob/5a1845b4caf7a5dba8eda1fef05213a532ed4259/crates/debug/src/transform/expression.rs#L59-L137
+fn translate_loc(
+    isa: &dyn TargetIsa,
+    loc: ValueLoc,
+    stack_slots: &StackSlots,
+) -> Option<Expression> {
+    match loc {
+        ValueLoc::Reg(reg) => {
+            let machine_reg = isa.map_dwarf_register(reg).unwrap();
+            let mut expr = Expression::new();
+            expr.op_reg(gimli::Register(machine_reg));
+            Some(expr)
+        }
+        ValueLoc::Stack(ss) => {
+            if let Some(ss_offset) = stack_slots[ss].offset {
+                let mut expr = Expression::new();
+                expr.op_breg(X86_64::RBP, i64::from(ss_offset) + 16);
+                Some(expr)
+            } else {
+                None
+            }
+        }
+        _ => None,
+    }
+}
diff --git a/compiler/rustc_codegen_cranelift/src/debuginfo/unwind.rs b/compiler/rustc_codegen_cranelift/src/debuginfo/unwind.rs
new file mode 100644
index 00000000000..61ebd931d2f
--- /dev/null
+++ b/compiler/rustc_codegen_cranelift/src/debuginfo/unwind.rs
@@ -0,0 +1,167 @@
+//! Unwind info generation (`.eh_frame`)
+
+use crate::prelude::*;
+
+use cranelift_codegen::isa::{unwind::UnwindInfo, TargetIsa};
+
+use gimli::write::{Address, CieId, EhFrame, FrameTable, Section};
+
+use crate::backend::WriteDebugInfo;
+
+pub(crate) struct UnwindContext<'tcx> {
+    tcx: TyCtxt<'tcx>,
+    frame_table: FrameTable,
+    cie_id: Option<CieId>,
+}
+
+impl<'tcx> UnwindContext<'tcx> {
+    pub(crate) fn new(tcx: TyCtxt<'tcx>, isa: &dyn TargetIsa) -> Self {
+        let mut frame_table = FrameTable::default();
+
+        let cie_id = if let Some(mut cie) = isa.create_systemv_cie() {
+            if isa.flags().is_pic() {
+                cie.fde_address_encoding =
+                    gimli::DwEhPe(gimli::DW_EH_PE_pcrel.0 | gimli::DW_EH_PE_sdata4.0);
+            }
+            Some(frame_table.add_cie(cie))
+        } else {
+            None
+        };
+
+        UnwindContext {
+            tcx,
+            frame_table,
+            cie_id,
+        }
+    }
+
+    pub(crate) fn add_function(&mut self, func_id: FuncId, context: &Context, isa: &dyn TargetIsa) {
+        let unwind_info = if let Some(unwind_info) = context.create_unwind_info(isa).unwrap() {
+            unwind_info
+        } else {
+            return;
+        };
+
+        match unwind_info {
+            UnwindInfo::SystemV(unwind_info) => {
+                self.frame_table.add_fde(
+                    self.cie_id.unwrap(),
+                    unwind_info.to_fde(Address::Symbol {
+                        symbol: func_id.as_u32() as usize,
+                        addend: 0,
+                    }),
+                );
+            }
+            UnwindInfo::WindowsX64(_) => {
+                // FIXME implement this
+            }
+        }
+    }
+
+    pub(crate) fn emit<P: WriteDebugInfo>(self, product: &mut P) {
+        let mut eh_frame = EhFrame::from(super::emit::WriterRelocate::new(super::target_endian(
+            self.tcx,
+        )));
+        self.frame_table.write_eh_frame(&mut eh_frame).unwrap();
+
+        if !eh_frame.0.writer.slice().is_empty() {
+            let id = eh_frame.id();
+            let section_id = product.add_debug_section(id, eh_frame.0.writer.into_vec());
+            let mut section_map = FxHashMap::default();
+            section_map.insert(id, section_id);
+
+            for reloc in &eh_frame.0.relocs {
+                product.add_debug_reloc(&section_map, &section_id, reloc);
+            }
+        }
+    }
+
+    #[cfg(feature = "jit")]
+    pub(crate) unsafe fn register_jit(
+        self,
+        jit_product: &cranelift_simplejit::SimpleJITProduct,
+    ) -> Option<UnwindRegistry> {
+        let mut eh_frame = EhFrame::from(super::emit::WriterRelocate::new(super::target_endian(
+            self.tcx,
+        )));
+        self.frame_table.write_eh_frame(&mut eh_frame).unwrap();
+
+        if eh_frame.0.writer.slice().is_empty() {
+            return None;
+        }
+
+        let mut eh_frame = eh_frame.0.relocate_for_jit(jit_product);
+
+        // GCC expects a terminating "empty" length, so write a 0 length at the end of the table.
+        eh_frame.extend(&[0, 0, 0, 0]);
+
+        let mut registrations = Vec::new();
+
+        // =======================================================================
+        // Everything after this line up to the end of the file is loosly based on
+        // https://github.com/bytecodealliance/wasmtime/blob/4471a82b0c540ff48960eca6757ccce5b1b5c3e4/crates/jit/src/unwind/systemv.rs
+        #[cfg(target_os = "macos")]
+        {
+            // On macOS, `__register_frame` takes a pointer to a single FDE
+            let start = eh_frame.as_ptr();
+            let end = start.add(eh_frame.len());
+            let mut current = start;
+
+            // Walk all of the entries in the frame table and register them
+            while current < end {
+                let len = std::ptr::read::<u32>(current as *const u32) as usize;
+
+                // Skip over the CIE
+                if current != start {
+                    __register_frame(current);
+                    registrations.push(current as usize);
+                }
+
+                // Move to the next table entry (+4 because the length itself is not inclusive)
+                current = current.add(len + 4);
+            }
+        }
+        #[cfg(not(target_os = "macos"))]
+        {
+            // On other platforms, `__register_frame` will walk the FDEs until an entry of length 0
+            let ptr = eh_frame.as_ptr();
+            __register_frame(ptr);
+            registrations.push(ptr as usize);
+        }
+
+        Some(UnwindRegistry {
+            _frame_table: eh_frame,
+            registrations,
+        })
+    }
+}
+
+/// Represents a registry of function unwind information for System V ABI.
+pub(crate) struct UnwindRegistry {
+    _frame_table: Vec<u8>,
+    registrations: Vec<usize>,
+}
+
+extern "C" {
+    // libunwind import
+    fn __register_frame(fde: *const u8);
+    fn __deregister_frame(fde: *const u8);
+}
+
+impl Drop for UnwindRegistry {
+    fn drop(&mut self) {
+        unsafe {
+            // libgcc stores the frame entries as a linked list in decreasing sort order
+            // based on the PC value of the registered entry.
+            //
+            // As we store the registrations in increasing order, it would be O(N^2) to
+            // deregister in that order.
+            //
+            // To ensure that we just pop off the first element in the list upon every
+            // deregistration, walk our list of registrations backwards.
+            for fde in self.registrations.iter().rev() {
+                __deregister_frame(*fde as *const _);
+            }
+        }
+    }
+}