Files
wasmtime/crates/debug/src/transform/unit.rs
Alex Crichton 65eaca35dd Refactor where results of compilation are stored (#2086)
* Refactor where results of compilation are stored

This commit refactors the internals of compilation in Wasmtime to change
where results of individual function compilation are stored. Previously
compilation resulted in many maps being returned, and compilation
results generally held all these maps together. This commit instead
switches this to have all metadata stored in a `CompiledFunction`
instead of having a separate map for each item that can be stored.

The motivation for this is primarily to help out with future
module-linking-related PRs. What exactly "module level" is depends on
how we interpret modules and how many modules are in play, so it's a bit
easier for operations in wasmtime to work at the function level where
possible. This means that we don't have to pass around multiple
different maps and a function index, but instead just one map or just
one entry representing a compiled function.

Additionally this change updates where the parallelism of compilation
happens, pushing it into `wasmtime-jit` instead of `wasmtime-environ`.
This is another goal where `wasmtime-jit` will have more knowledge about
module-level pieces with module linking in play. User-facing-wise this
should be the same in terms of parallel compilation, though.

The ultimate goal of this refactoring is to make it easier for the
results of compilation to actually be a set of wasm modules. This means
we won't be able to have a map-per-metadata where the primary key is the
function index, because there will be many modules within one "object
file".

* Don't clear out fields, just don't store them

Persist a smaller set of fields in `CompilationArtifacts` instead of
trying to clear fields out and dynamically not accessing them.
2020-08-03 12:20:51 -05:00

482 lines
18 KiB
Rust

use super::address_transform::AddressTransform;
use super::attr::{clone_die_attributes, FileAttributeContext};
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, TransformError};
use anyhow::{Context, Error};
use gimli::write;
use gimli::{AttributeValue, DebuggingInformationEntry, Unit};
use std::collections::HashSet;
use wasmtime_environ::isa::TargetIsa;
use wasmtime_environ::wasm::DefinedFuncIndex;
use wasmtime_environ::{CompiledFunctions, ModuleMemoryOffset};
struct InheritedAttr<T> {
stack: Vec<(usize, T)>,
}
impl<T> InheritedAttr<T> {
fn new() -> Self {
InheritedAttr { stack: Vec::new() }
}
fn update(&mut self, depth: usize) {
while !self.stack.is_empty() && self.stack.last().unwrap().0 >= depth {
self.stack.pop();
}
}
fn push(&mut self, depth: usize, value: T) {
self.stack.push((depth, value));
}
fn top(&self) -> Option<&T> {
self.stack.last().map(|entry| &entry.1)
}
fn is_empty(&self) -> bool {
self.stack.is_empty()
}
}
fn get_base_type_name<R>(
type_entry: &DebuggingInformationEntry<R>,
unit: &Unit<R, R::Offset>,
context: &DebugInputContext<R>,
) -> Result<String, Error>
where
R: Reader,
{
// 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)?;
entries.next_entry()?;
if let Some(die) = entries.current() {
if let Some(AttributeValue::DebugStrRef(str_offset)) =
die.attr_value(gimli::DW_AT_name)?
{
return Ok(String::from(
context.debug_str.get_str(str_offset)?.to_string()?,
));
}
match die.tag() {
gimli::DW_TAG_const_type => {
return Ok(format!("const {}", get_base_type_name(die, unit, context)?));
}
gimli::DW_TAG_pointer_type => {
return Ok(format!("{}*", get_base_type_name(die, unit, context)?));
}
gimli::DW_TAG_reference_type => {
return Ok(format!("{}&", get_base_type_name(die, unit, context)?));
}
gimli::DW_TAG_array_type => {
return Ok(format!("{}[]", get_base_type_name(die, unit, context)?));
}
_ => (),
}
}
}
Ok(String::from("??"))
}
enum WebAssemblyPtrKind {
Reference,
Pointer,
}
/// Replaces WebAssembly pointer type DIE with the wrapper
/// which natively represented by offset in a Wasm memory.
///
/// `pointer_type_entry` is a DW_TAG_pointer_type entry (e.g. `T*`),
/// which refers its base type (e.g. `T`), or is a
/// DW_TAG_reference_type (e.g. `T&`).
///
/// The generated wrapper is a structure that contains only the
/// `__ptr` field. The utility operators overloads is added to
/// provide better debugging experience.
///
/// Wrappers of pointer and reference types are identical except for
/// their name -- they are formatted and accessed from a debugger
/// the same way.
///
/// Notice that "resolve_vmctx_memory_ptr" is external/builtin
/// subprogram that is not part of Wasm code.
fn replace_pointer_type<R>(
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>,
out_strings: &mut write::StringTable,
pending_die_refs: &mut PendingUnitRefs,
) -> Result<write::UnitEntryId, Error>
where
R: Reader,
{
const WASM_PTR_LEN: u8 = 4;
macro_rules! add_tag {
($parent_id:ident, $tag:expr => $die:ident as $die_id:ident { $($a:path = $v:expr),* }) => {
let $die_id = comp_unit.add($parent_id, $tag);
#[allow(unused_variables)]
let $die = comp_unit.get_mut($die_id);
$( $die.set($a, $v); )*
};
}
// Build DW_TAG_structure_type for the wrapper:
// .. DW_AT_name = "WebAssemblyPtrWrapper<T>",
// .. DW_AT_byte_size = 4,
let name = match kind {
WebAssemblyPtrKind::Pointer => format!(
"WebAssemblyPtrWrapper<{}>",
get_base_type_name(pointer_type_entry, unit, context)?
),
WebAssemblyPtrKind::Reference => format!(
"WebAssemblyRefWrapper<{}>",
get_base_type_name(pointer_type_entry, unit, context)?
),
};
add_tag!(parent_id, gimli::DW_TAG_structure_type => wrapper_die as wrapper_die_id {
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add(name.as_str())),
gimli::DW_AT_byte_size = write::AttributeValue::Data1(WASM_PTR_LEN)
});
// Build DW_TAG_pointer_type for `WebAssemblyPtrWrapper<T>*`:
// .. DW_AT_type = <wrapper_die>
add_tag!(parent_id, gimli::DW_TAG_pointer_type => wrapper_ptr_type as wrapper_ptr_type_id {
gimli::DW_AT_type = write::AttributeValue::UnitRef(wrapper_die_id)
});
let base_type_id = pointer_type_entry.attr_value(gimli::DW_AT_type)?;
// Build DW_TAG_reference_type for `T&`:
// .. DW_AT_type = <base_type>
add_tag!(parent_id, gimli::DW_TAG_reference_type => ref_type as ref_type_id {});
if let Some(AttributeValue::UnitRef(ref offset)) = base_type_id {
pending_die_refs.insert(ref_type_id, gimli::DW_AT_type, *offset);
}
// Build DW_TAG_pointer_type for `T*`:
// .. DW_AT_type = <base_type>
add_tag!(parent_id, gimli::DW_TAG_pointer_type => ptr_type as ptr_type_id {});
if let Some(AttributeValue::UnitRef(ref offset)) = base_type_id {
pending_die_refs.insert(ptr_type_id, gimli::DW_AT_type, *offset);
}
// Build wrapper_die's DW_TAG_template_type_parameter:
// .. DW_AT_name = "T"
// .. DW_AT_type = <base_type>
add_tag!(wrapper_die_id, gimli::DW_TAG_template_type_parameter => t_param_die as t_param_die_id {
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("T"))
});
if let Some(AttributeValue::UnitRef(ref offset)) = base_type_id {
pending_die_refs.insert(t_param_die_id, gimli::DW_AT_type, *offset);
}
// Build wrapper_die's DW_TAG_member for `__ptr`:
// .. DW_AT_name = "__ptr"
// .. DW_AT_type = <wp_die>
// .. DW_AT_location = 0
add_tag!(wrapper_die_id, gimli::DW_TAG_member => m_die as m_die_id {
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("__ptr")),
gimli::DW_AT_type = write::AttributeValue::UnitRef(wp_die_id),
gimli::DW_AT_data_member_location = write::AttributeValue::Data1(0)
});
// Build wrapper_die's DW_TAG_subprogram for `ptr()`:
// .. DW_AT_linkage_name = "resolve_vmctx_memory_ptr"
// .. DW_AT_name = "ptr"
// .. DW_AT_type = <ptr_type>
// .. DW_TAG_formal_parameter
// .. .. DW_AT_type = <wrapper_ptr_type>
// .. .. DW_AT_artificial = 1
add_tag!(wrapper_die_id, gimli::DW_TAG_subprogram => deref_op_die as deref_op_die_id {
gimli::DW_AT_linkage_name = write::AttributeValue::StringRef(out_strings.add("resolve_vmctx_memory_ptr")),
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("ptr")),
gimli::DW_AT_type = write::AttributeValue::UnitRef(ptr_type_id)
});
add_tag!(deref_op_die_id, gimli::DW_TAG_formal_parameter => deref_op_this_param as deref_op_this_param_id {
gimli::DW_AT_type = write::AttributeValue::UnitRef(wrapper_ptr_type_id),
gimli::DW_AT_artificial = write::AttributeValue::Flag(true)
});
// Build wrapper_die's DW_TAG_subprogram for `operator*`:
// .. DW_AT_linkage_name = "resolve_vmctx_memory_ptr"
// .. DW_AT_name = "operator*"
// .. DW_AT_type = <ref_type>
// .. DW_TAG_formal_parameter
// .. .. DW_AT_type = <wrapper_ptr_type>
// .. .. DW_AT_artificial = 1
add_tag!(wrapper_die_id, gimli::DW_TAG_subprogram => deref_op_die as deref_op_die_id {
gimli::DW_AT_linkage_name = write::AttributeValue::StringRef(out_strings.add("resolve_vmctx_memory_ptr")),
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("operator*")),
gimli::DW_AT_type = write::AttributeValue::UnitRef(ref_type_id)
});
add_tag!(deref_op_die_id, gimli::DW_TAG_formal_parameter => deref_op_this_param as deref_op_this_param_id {
gimli::DW_AT_type = write::AttributeValue::UnitRef(wrapper_ptr_type_id),
gimli::DW_AT_artificial = write::AttributeValue::Flag(true)
});
// Build wrapper_die's DW_TAG_subprogram for `operator->`:
// .. DW_AT_linkage_name = "resolve_vmctx_memory_ptr"
// .. DW_AT_name = "operator->"
// .. DW_AT_type = <ptr_type>
// .. DW_TAG_formal_parameter
// .. .. DW_AT_type = <wrapper_ptr_type>
// .. .. DW_AT_artificial = 1
add_tag!(wrapper_die_id, gimli::DW_TAG_subprogram => deref_op_die as deref_op_die_id {
gimli::DW_AT_linkage_name = write::AttributeValue::StringRef(out_strings.add("resolve_vmctx_memory_ptr")),
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("operator->")),
gimli::DW_AT_type = write::AttributeValue::UnitRef(ptr_type_id)
});
add_tag!(deref_op_die_id, gimli::DW_TAG_formal_parameter => deref_op_this_param as deref_op_this_param_id {
gimli::DW_AT_type = write::AttributeValue::UnitRef(wrapper_ptr_type_id),
gimli::DW_AT_artificial = write::AttributeValue::Flag(true)
});
Ok(wrapper_die_id)
}
pub(crate) fn clone_unit<'a, R>(
unit: Unit<R, R::Offset>,
context: &DebugInputContext<R>,
addr_tr: &'a AddressTransform,
funcs: &'a CompiledFunctions,
memory_offset: &ModuleMemoryOffset,
out_encoding: gimli::Encoding,
out_units: &mut write::UnitTable,
out_strings: &mut write::StringTable,
translated: &mut HashSet<DefinedFuncIndex>,
isa: &dyn TargetIsa,
) -> Result<Option<(write::UnitId, UnitRefsMap, PendingDebugInfoRefs)>, Error>
where
R: Reader,
{
let mut die_ref_map = UnitRefsMap::new();
let mut pending_die_refs = PendingUnitRefs::new();
let mut pending_di_refs = PendingDebugInfoRefs::new();
let mut stack = Vec::new();
// Iterate over all of this compilation unit's entries.
let mut entries = unit.entries();
let (mut comp_unit, unit_id, file_map, file_index_base, cu_low_pc, wp_die_id, vmctx_die_id) =
if let Some((depth_delta, entry)) = entries.next_dfs()? {
assert_eq!(depth_delta, 0);
let (out_line_program, debug_line_offset, file_map, file_index_base) =
clone_line_program(
&unit,
entry,
addr_tr,
out_encoding,
context.debug_str,
context.debug_str_offsets,
context.debug_line_str,
context.debug_line,
out_strings,
)?;
if entry.tag() == gimli::DW_TAG_compile_unit {
let unit_id = out_units.add(write::Unit::new(out_encoding, out_line_program));
let comp_unit = out_units.get_mut(unit_id);
let root_id = comp_unit.root();
die_ref_map.insert(entry.offset(), root_id);
let cu_low_pc = if let Some(AttributeValue::Addr(addr)) =
entry.attr_value(gimli::DW_AT_low_pc)?
{
addr
} else if let Some(AttributeValue::DebugAddrIndex(i)) =
entry.attr_value(gimli::DW_AT_low_pc)?
{
context.debug_addr.get_address(4, unit.addr_base, i)?
} else {
// FIXME? return Err(TransformError("No low_pc for unit header").into());
0
};
clone_die_attributes(
&unit,
entry,
context,
addr_tr,
None,
comp_unit,
root_id,
None,
None,
cu_low_pc,
out_strings,
&mut pending_die_refs,
&mut pending_di_refs,
FileAttributeContext::Root(Some(debug_line_offset)),
isa,
)?;
let (wp_die_id, vmctx_die_id) =
add_internal_types(comp_unit, root_id, out_strings, memory_offset);
stack.push(root_id);
(
comp_unit,
unit_id,
file_map,
file_index_base,
cu_low_pc,
wp_die_id,
vmctx_die_id,
)
} else {
return Err(TransformError("Unexpected unit header").into());
}
} else {
return Ok(None); // empty
};
let mut skip_at_depth = None;
let mut current_frame_base = InheritedAttr::new();
let mut current_value_range = InheritedAttr::new();
let mut current_scope_ranges = InheritedAttr::new();
while let Some((depth_delta, entry)) = entries.next_dfs()? {
let depth_delta = if let Some((depth, cached)) = skip_at_depth {
let new_depth = depth + depth_delta;
if new_depth > 0 {
skip_at_depth = Some((new_depth, cached));
continue;
}
skip_at_depth = None;
new_depth + cached
} else {
depth_delta
};
if !context
.reachable
.contains(&entry.offset().to_unit_section_offset(&unit))
{
// entry is not reachable: discarding all its info.
skip_at_depth = Some((0, depth_delta));
continue;
}
let new_stack_len = stack.len().wrapping_add(depth_delta as usize);
current_frame_base.update(new_stack_len);
current_scope_ranges.update(new_stack_len);
current_value_range.update(new_stack_len);
let range_builder = if entry.tag() == gimli::DW_TAG_subprogram {
let range_builder =
RangeInfoBuilder::from_subprogram_die(&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);
current_scope_ranges.push(new_stack_len, range_builder.get_ranges(addr_tr));
Some(range_builder)
} else {
// FIXME current_scope_ranges.push()
None
}
} else {
let high_pc = entry.attr_value(gimli::DW_AT_high_pc)?;
let ranges = entry.attr_value(gimli::DW_AT_ranges)?;
if high_pc.is_some() || ranges.is_some() {
let range_builder = RangeInfoBuilder::from(&unit, entry, context, cu_low_pc)?;
current_scope_ranges.push(new_stack_len, range_builder.get_ranges(addr_tr));
Some(range_builder)
} else {
None
}
};
if depth_delta <= 0 {
for _ in depth_delta..1 {
stack.pop();
}
} else {
assert_eq!(depth_delta, 1);
}
if let Some(AttributeValue::Exprloc(expr)) = entry.attr_value(gimli::DW_AT_frame_base)? {
if let Some(expr) = compile_expression(&expr, unit.encoding(), None)? {
current_frame_base.push(new_stack_len, expr);
}
}
let parent = stack.last().unwrap();
if entry.tag() == gimli::DW_TAG_pointer_type || entry.tag() == gimli::DW_TAG_reference_type
{
// Wrap pointer types.
let pointer_kind = match entry.tag() {
gimli::DW_TAG_pointer_type => WebAssemblyPtrKind::Pointer,
gimli::DW_TAG_reference_type => WebAssemblyPtrKind::Reference,
_ => panic!(),
};
let die_id = replace_pointer_type(
*parent,
pointer_kind,
comp_unit,
wp_die_id,
entry,
&unit,
context,
out_strings,
&mut pending_die_refs,
)?;
stack.push(die_id);
assert_eq!(stack.len(), new_stack_len);
die_ref_map.insert(entry.offset(), die_id);
continue;
}
let die_id = comp_unit.add(*parent, entry.tag());
stack.push(die_id);
assert_eq!(stack.len(), new_stack_len);
die_ref_map.insert(entry.offset(), die_id);
clone_die_attributes(
&unit,
entry,
context,
addr_tr,
current_value_range.top(),
&mut comp_unit,
die_id,
range_builder,
current_scope_ranges.top(),
cu_low_pc,
out_strings,
&mut pending_die_refs,
&mut pending_di_refs,
FileAttributeContext::Children {
file_map: &file_map,
file_index_base,
frame_base: current_frame_base.top(),
},
isa,
)?;
if entry.tag() == gimli::DW_TAG_subprogram && !current_scope_ranges.is_empty() {
append_vmctx_info(
comp_unit,
die_id,
vmctx_die_id,
addr_tr,
current_value_range.top(),
current_scope_ranges.top().context("range")?,
out_strings,
isa,
)?;
}
}
die_ref_map.patch(pending_die_refs, comp_unit);
Ok(Some((unit_id, die_ref_map, pending_di_refs)))
}