Skip to content

Commit 9840c72

Browse files
committed
fix(dwarf): attempt to fix emitting dwarf symbols for debugging
Signed-off-by: Radu Matei <[email protected]>
1 parent d938a9d commit 9840c72

File tree

5 files changed

+160
-6
lines changed

5 files changed

+160
-6
lines changed

cranelift/codegen/src/machinst/abi.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1548,6 +1548,11 @@ impl<M: ABIMachineSpec> Callee<M> {
15481548
&self.sized_stackslots
15491549
}
15501550

1551+
/// The keys of all sized stack slots for debuginfo purposes.
1552+
pub fn sized_stackslot_keys(&self) -> &SecondaryMap<StackSlot, Option<StackSlotKey>> {
1553+
&self.sized_stackslot_keys
1554+
}
1555+
15511556
/// The offsets of all dynamic stack slots (not spill slots) for debuginfo purposes.
15521557
pub fn dynamic_stackslot_offsets(&self) -> &PrimaryMap<DynamicStackSlot, u32> {
15531558
&self.dynamic_stackslots

cranelift/codegen/src/machinst/vcode.rs

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1234,15 +1234,23 @@ impl<I: VCodeInst> VCode<I> {
12341234
inst_offsets: &[CodeOffset],
12351235
func_body_len: u32,
12361236
) -> ValueLabelsRanges {
1237+
let mut value_labels_ranges: ValueLabelsRanges = HashMap::new();
1238+
1239+
// Add synthetic full-body entries from the debug-locals stack
1240+
// slot before processing regalloc2 output so that the
1241+
// regalloc2-based (narrower but potentially register-based)
1242+
// entries are appended after and can take precedence in the
1243+
// DWARF transform's range intersection logic.
1244+
self.add_debug_locals_slot_ranges(&mut value_labels_ranges, func_body_len);
1245+
12371246
if self.debug_value_labels.is_empty() {
1238-
return ValueLabelsRanges::default();
1247+
return value_labels_ranges;
12391248
}
12401249

12411250
if trace_log_enabled!() {
12421251
self.log_value_labels_ranges(regalloc, inst_offsets);
12431252
}
12441253

1245-
let mut value_labels_ranges: ValueLabelsRanges = HashMap::new();
12461254
for &(label, from, to, alloc) in &regalloc.debug_locations {
12471255
let label = ValueLabel::from_u32(label);
12481256
let ranges = value_labels_ranges.entry(label).or_insert_with(|| vec![]);
@@ -1313,6 +1321,67 @@ impl<I: VCodeInst> VCode<I> {
13131321
value_labels_ranges
13141322
}
13151323

1324+
/// If a sized stack slot is marked with the debug-locals sentinel
1325+
/// key, add a full-body `ValueLocRange` for each Wasm local (and
1326+
/// for the vmctx pointer stored after all locals) so that DWARF
1327+
/// expressions can always find values at known CFA-relative offsets.
1328+
fn add_debug_locals_slot_ranges(
1329+
&self,
1330+
value_labels_ranges: &mut ValueLabelsRanges,
1331+
func_body_len: u32,
1332+
) {
1333+
const DEBUG_LOCALS_KEY_SENTINEL: u64 = 0xDEBF_DEAD_0000_0000;
1334+
const VMCTX_LABEL: u32 = 0xffff_fffe;
1335+
let offsets = self.abi.sized_stackslot_offsets();
1336+
let keys = self.abi.sized_stackslot_keys();
1337+
for (slot, slot_offset) in offsets {
1338+
let Some(key) = keys[slot] else { continue };
1339+
let bits = key.bits();
1340+
if bits & 0xFFFF_FFFF_0000_0000 != DEBUG_LOCALS_KEY_SENTINEL {
1341+
continue;
1342+
}
1343+
let num_locals = (bits & 0xFFFF_FFFF) as u32;
1344+
let slot_base = *slot_offset as i64;
1345+
let slot_base_to_caller_sp =
1346+
self.abi.slot_base_to_caller_sp_offset() as i64;
1347+
let caller_sp_to_cfa =
1348+
crate::isa::unwind::systemv::caller_sp_to_cfa_offset() as i64;
1349+
let cfa_to_slot_base =
1350+
-(slot_base_to_caller_sp + caller_sp_to_cfa) + slot_base;
1351+
1352+
for i in 0..num_locals {
1353+
let label = ValueLabel::from_u32(i);
1354+
let per_local_offset = (i as i64) * 16;
1355+
let cfa_offset = cfa_to_slot_base + per_local_offset;
1356+
let loc = LabelValueLoc::CFAOffset(cfa_offset);
1357+
1358+
let ranges = value_labels_ranges
1359+
.entry(label)
1360+
.or_insert_with(|| vec![]);
1361+
ranges.push(ValueLocRange {
1362+
loc,
1363+
start: 0,
1364+
end: func_body_len,
1365+
});
1366+
}
1367+
1368+
// Also add a synthetic range for vmctx (frame base / memory
1369+
// deref), stored at offset num_locals * 16 in the slot.
1370+
let vmctx_label = ValueLabel::from_u32(VMCTX_LABEL);
1371+
let vmctx_offset = (num_locals as i64) * 16;
1372+
let vmctx_cfa = cfa_to_slot_base + vmctx_offset;
1373+
let vmctx_loc = LabelValueLoc::CFAOffset(vmctx_cfa);
1374+
let ranges = value_labels_ranges
1375+
.entry(vmctx_label)
1376+
.or_insert_with(|| vec![]);
1377+
ranges.push(ValueLocRange {
1378+
loc: vmctx_loc,
1379+
start: 0,
1380+
end: func_body_len,
1381+
});
1382+
}
1383+
}
1384+
13161385
fn log_value_labels_ranges(&self, regalloc: &regalloc2::Output, inst_offsets: &[CodeOffset]) {
13171386
debug_assert!(trace_log_enabled!());
13181387

crates/cranelift/src/func_environ.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,16 @@ pub struct FuncEnvironment<'module_environment> {
216216
/// The stack-slot used for exposing Wasm state via debug
217217
/// instrumentation, if any, and the builder containing its metadata.
218218
pub(crate) state_slot: Option<(ir::StackSlot, FrameStateSlotBuilder)>,
219+
220+
/// Stack slot for pinning Wasm local values when native debug info is
221+
/// enabled. Stores to this slot at function exits keep the register
222+
/// allocator from discarding local values that are needed for DWARF
223+
/// location expressions.
224+
debug_locals_slot: Option<ir::StackSlot>,
225+
226+
/// Total number of Wasm locals (parameters + declared locals) for
227+
/// debug local pinning.
228+
num_wasm_locals: u32,
219229
}
220230

221231
impl<'module_environment> FuncEnvironment<'module_environment> {
@@ -277,6 +287,9 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
277287
stack_switching_values_buffer: None,
278288

279289
state_slot: None,
290+
291+
debug_locals_slot: None,
292+
num_wasm_locals: 0,
280293
}
281294
}
282295

@@ -1239,6 +1252,64 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
12391252
}
12401253
}
12411254

1255+
/// Sentinel in the high 32 bits of the StackSlotKey that marks a
1256+
/// slot as holding pinned Wasm locals for native DWARF debug info.
1257+
/// The low 32 bits encode the number of locals. Each local `i`
1258+
/// occupies bytes `[i*16 .. (i+1)*16)` in the slot, and the
1259+
/// vmctx pointer is stored at `[num_locals*16 .. (num_locals+1)*16)`.
1260+
pub(crate) const DEBUG_LOCALS_KEY_SENTINEL: u64 = 0xDEBF_DEAD_0000_0000;
1261+
1262+
/// Create a stack slot for pinning Wasm local values when native debug
1263+
/// info is enabled. On every `local.set`/`local.tee` (and at
1264+
/// initialization), the value is stored to this slot so that DWARF
1265+
/// location expressions can always find it at a known stack offset.
1266+
/// An extra 16-byte entry at the end holds the vmctx pointer (frame
1267+
/// base), which is needed to dereference Wasm linear memory.
1268+
pub(crate) fn create_debug_locals_slot(
1269+
&mut self,
1270+
builder: &mut FunctionBuilder,
1271+
num_locals: u32,
1272+
) {
1273+
if !self.tunables.debug_native || num_locals == 0 {
1274+
return;
1275+
}
1276+
self.num_wasm_locals = num_locals;
1277+
let slot_size = (num_locals + 1) * 16;
1278+
let key = ir::StackSlotKey::new(Self::DEBUG_LOCALS_KEY_SENTINEL | num_locals as u64);
1279+
let slot = builder.create_sized_stack_slot(ir::StackSlotData::new_with_key(
1280+
ir::StackSlotKind::ExplicitSlot,
1281+
slot_size,
1282+
0,
1283+
key,
1284+
));
1285+
self.debug_locals_slot = Some(slot);
1286+
}
1287+
1288+
/// Store a value for a local in the debug locals slot, if present.
1289+
pub(crate) fn debug_locals_slot_local_set(
1290+
&self,
1291+
builder: &mut FunctionBuilder,
1292+
local: u32,
1293+
value: ir::Value,
1294+
) {
1295+
if let Some(slot) = self.debug_locals_slot {
1296+
let offset = (local as i32) * 16;
1297+
builder.ins().stack_store(value, slot, offset);
1298+
}
1299+
}
1300+
1301+
/// Store the vmctx pointer in the debug locals slot (after all locals).
1302+
pub(crate) fn debug_locals_slot_store_vmctx(
1303+
&mut self,
1304+
builder: &mut FunctionBuilder,
1305+
) {
1306+
if let Some(slot) = self.debug_locals_slot {
1307+
let vmctx = self.vmctx_val(&mut builder.cursor());
1308+
let offset = (self.num_wasm_locals as i32) * 16;
1309+
builder.ins().stack_store(vmctx, slot, offset);
1310+
}
1311+
}
1312+
12421313
/// Update the state slot layout with a new layout given a local.
12431314
pub(crate) fn add_state_slot_local(
12441315
&mut self,

crates/cranelift/src/translate/code_translator.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ pub fn translate_operator(
164164
let label = ValueLabel::from_u32(*local_index);
165165
builder.set_val_label(val, label);
166166
environ.state_slot_local_set(builder, *local_index, val);
167+
environ.debug_locals_slot_local_set(builder, *local_index, val);
167168
}
168169
Operator::LocalTee { local_index } => {
169170
let mut val = environ.stacks.peek1();
@@ -178,6 +179,7 @@ pub fn translate_operator(
178179
let label = ValueLabel::from_u32(*local_index);
179180
builder.set_val_label(val, label);
180181
environ.state_slot_local_set(builder, *local_index, val);
182+
environ.debug_locals_slot_local_set(builder, *local_index, val);
181183
}
182184
/********************************** Globals ****************************************
183185
* `get_global` and `set_global` are handled by the environment.

crates/cranelift/src/translate/func_translator.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::translate::translation_utils::get_vmctx_value_label;
1111
use cranelift_codegen::entity::EntityRef;
1212
use cranelift_codegen::ir::{self, Block, InstBuilder, ValueLabel};
1313
use cranelift_codegen::timing;
14-
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
14+
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext, Variable};
1515
use wasmparser::{BinaryReader, FuncValidator, FunctionBody, OperatorsReader, WasmModuleResources};
1616
use wasmtime_environ::{TypeConvert, WasmResult};
1717

@@ -89,7 +89,14 @@ impl FuncTranslator {
8989
.stacks
9090
.initialize(&builder.func.signature, exit_block);
9191

92-
parse_local_decls(&mut reader, &mut builder, num_params, environ, validator)?;
92+
let total_locals =
93+
parse_local_decls(&mut reader, &mut builder, num_params, environ, validator)?;
94+
environ.create_debug_locals_slot(&mut builder, total_locals as u32);
95+
for i in 0..total_locals as u32 {
96+
let val = builder.use_var(Variable::from_u32(i));
97+
environ.debug_locals_slot_local_set(&mut builder, i, val);
98+
}
99+
environ.debug_locals_slot_store_vmctx(&mut builder);
93100
parse_function_body(validator, reader, &mut builder, environ)?;
94101

95102
builder.finalize();
@@ -145,7 +152,7 @@ fn parse_local_decls(
145152
num_params: usize,
146153
environ: &mut FuncEnvironment<'_>,
147154
validator: &mut FuncValidator<impl WasmModuleResources>,
148-
) -> WasmResult<()> {
155+
) -> WasmResult<usize> {
149156
let mut next_local = num_params;
150157
let local_count = reader.read_var_u32()?;
151158

@@ -158,7 +165,7 @@ fn parse_local_decls(
158165
declare_locals(builder, count, ty, &mut next_local, environ)?;
159166
}
160167

161-
Ok(())
168+
Ok(next_local)
162169
}
163170

164171
/// Declare `count` local variables of the same type, starting from `next_local`.

0 commit comments

Comments
 (0)