Skip to content

Commit 57d355f

Browse files
committed
Fix source locations when trim-paths is being used
- Add lit test for it: $ litcheck lit run --path bin tests/lit/source-location/ - Add HIR serialization in form of text
1 parent ca6ae73 commit 57d355f

File tree

16 files changed

+240
-7
lines changed

16 files changed

+240
-7
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Makefile.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,7 @@ args = [
335335
"${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/bin",
336336
"${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/tests/lit/parse",
337337
"${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/tests/lit/wasm-translation",
338+
"${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/tests/lit/source-location",
338339
]
339340
dependencies = ["litcheck"]
340341

frontend/wasm/src/config.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use alloc::borrow::Cow;
1+
use alloc::{borrow::Cow, vec::Vec};
2+
use std::path::PathBuf;
23

34
/// Configuration for the WASM translation.
45
#[derive(Clone)]
@@ -8,6 +9,9 @@ pub struct WasmTranslationConfig {
89
/// binary, and an override name is not specified
910
pub source_name: Cow<'static, str>,
1011

12+
/// Path prefixes to try when resolving relative paths from trimmed DWARF debug information.
13+
pub trim_path_prefixes: Vec<PathBuf>,
14+
1115
/// If specified, overrides the module/component name with the one specified
1216
pub override_name: Option<Cow<'static, str>>,
1317

@@ -26,6 +30,7 @@ impl core::fmt::Debug for WasmTranslationConfig {
2630
let world = if self.world.is_some() { "Some" } else { "None" };
2731
f.debug_struct("WasmTranslationConfig")
2832
.field("source_name", &self.source_name)
33+
.field("trim_path_prefixes", &self.trim_path_prefixes)
2934
.field("override_name", &self.override_name)
3035
.field("world", &world)
3136
.field("generate_native_debuginfo", &self.generate_native_debuginfo)
@@ -38,6 +43,7 @@ impl Default for WasmTranslationConfig {
3843
fn default() -> Self {
3944
Self {
4045
source_name: Cow::Borrowed("noname"),
46+
trim_path_prefixes: Vec::new(),
4147
override_name: None,
4248
world: None,
4349
generate_native_debuginfo: false,

frontend/wasm/src/module/build_ir.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ pub fn build_ir_module(
145145
&addr2line,
146146
context.session(),
147147
&mut func_validator,
148+
_config,
148149
)?;
149150
}
150151
Ok(())

frontend/wasm/src/module/func_translator.rs

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ impl FuncTranslator {
6969
addr2line: &addr2line::Context<DwarfReader<'_>>,
7070
session: &Session,
7171
func_validator: &mut FuncValidator<impl WasmModuleResources>,
72+
config: &crate::WasmTranslationConfig,
7273
) -> WasmResult<()> {
7374
let context = func.borrow().as_operation().context_rc();
7475
let mut op_builder = midenc_hir::OpBuilder::new(context)
@@ -110,6 +111,7 @@ impl FuncTranslator {
110111
addr2line,
111112
session,
112113
func_validator,
114+
config,
113115
)?;
114116

115117
builder.finalize();
@@ -198,6 +200,7 @@ fn parse_function_body<B: ?Sized + Builder>(
198200
addr2line: &addr2line::Context<DwarfReader<'_>>,
199201
session: &Session,
200202
func_validator: &mut FuncValidator<impl WasmModuleResources>,
203+
config: &crate::WasmTranslationConfig,
201204
) -> WasmResult<()> {
202205
// The control stack is initialized with a single block representing the whole function.
203206
debug_assert_eq!(state.control_stack.len(), 1, "State not initialized");
@@ -216,15 +219,59 @@ fn parse_function_body<B: ?Sized + Builder>(
216219
if let Some(loc) = addr2line.find_location(offset).into_diagnostic()? {
217220
if let Some(file) = loc.file {
218221
let path = std::path::Path::new(file);
219-
let path = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
220-
if path.exists() {
221-
let source_file = session.source_manager.load_file(&path).into_diagnostic()?;
222+
223+
// Resolve relative paths to absolute paths
224+
let resolved_path = if path.is_relative() {
225+
// Strategy 1: Try trim_path_prefixes
226+
if let Some(resolved) = config.trim_path_prefixes.iter().find_map(|prefix| {
227+
let candidate = prefix.join(path);
228+
if candidate.exists() {
229+
// Canonicalize to get absolute path
230+
candidate.canonicalize().ok()
231+
} else {
232+
None
233+
}
234+
}) {
235+
Some(resolved)
236+
}
237+
// Strategy 2: Try session.options.current_dir as fallback
238+
else {
239+
let current_dir_candidate = session.options.current_dir.join(path);
240+
if current_dir_candidate.exists() {
241+
current_dir_candidate.canonicalize().ok()
242+
} else {
243+
None
244+
}
245+
}
246+
} else {
247+
// Path is absolute, but verify it exists and canonicalize it
248+
if path.exists() {
249+
path.canonicalize().ok()
250+
} else {
251+
None
252+
}
253+
};
254+
255+
if let Some(absolute_path) = resolved_path {
256+
debug_assert!(
257+
absolute_path.is_absolute(),
258+
"resolved path should be absolute: {}",
259+
absolute_path.display()
260+
);
261+
log::debug!(target: "module-parser",
262+
"resolved source path '{}' -> '{}'",
263+
file,
264+
absolute_path.display()
265+
);
266+
let source_file =
267+
session.source_manager.load_file(&absolute_path).into_diagnostic()?;
222268
let line = loc.line.and_then(LineNumber::new).unwrap_or_default();
223269
let column = loc.column.and_then(ColumnNumber::new).unwrap_or_default();
224270
span = source_file.line_column_to_span(line, column).unwrap_or_default();
225271
} else {
226272
log::debug!(target: "module-parser",
227-
"failed to locate span for instruction at offset {offset} in function {func_name}"
273+
"failed to resolve source path '{file}' for instruction at offset \
274+
{offset} in function {func_name}"
228275
);
229276
}
230277
}

hir/src/ir/print.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ use crate::{
1313
pub struct OpPrintingFlags {
1414
pub print_entry_block_headers: bool,
1515
pub print_intrinsic_attributes: bool,
16+
pub print_source_locations: bool,
1617
}
1718

1819
impl Default for OpPrintingFlags {
1920
fn default() -> Self {
2021
Self {
2122
print_entry_block_headers: true,
2223
print_intrinsic_attributes: false,
24+
print_source_locations: false,
2325
}
2426
}
2527
}
@@ -175,6 +177,32 @@ pub fn render_regions(op: &Operation, flags: &OpPrintingFlags) -> crate::formatt
175177
+ const_text(";")
176178
}
177179

180+
pub fn render_source_location(op: &Operation, context: &Context) -> crate::formatter::Document {
181+
use crate::formatter::*;
182+
183+
// Check if the span is valid (not default/empty)
184+
if op.span.is_unknown() {
185+
return Document::Empty;
186+
}
187+
188+
// Try to resolve the source location
189+
let session = context.session();
190+
if let Ok(source_file) = session.source_manager.get(op.span.source_id()) {
191+
let location = source_file.location(op.span);
192+
// Format: #loc("filename":line:col)
193+
let filename = source_file.uri().as_str();
194+
let loc_str = format!(
195+
" #loc(\"{}\":{}:{})",
196+
filename,
197+
location.line.to_u32(),
198+
location.column.to_u32()
199+
);
200+
return text(loc_str);
201+
}
202+
203+
Document::Empty
204+
}
205+
178206
struct OperationPrinter<'a> {
179207
op: &'a Operation,
180208
flags: &'a OpPrintingFlags,
@@ -280,6 +308,13 @@ impl PrettyPrint for OperationPrinter<'_> {
280308
doc + const_text(" ") + attrs
281309
};
282310

311+
// Add source location if requested
312+
let doc = if self.flags.print_source_locations {
313+
doc + render_source_location(self.op, self.context)
314+
} else {
315+
doc
316+
};
317+
283318
if self.op.has_regions() {
284319
doc + render_regions(self.op, self.flags)
285320
} else {

midenc-compile/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ std = [
2626
]
2727

2828
[dependencies]
29+
anyhow.workspace = true
2930
clap = { workspace = true, optional = true }
3031
log.workspace = true
3132
inventory.workspace = true

midenc-compile/src/compiler.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,29 @@ pub struct UnstableOptions {
348348
arg(long, default_value_t = false, help_heading = "Passes")
349349
)]
350350
pub print_ir_after_modified: bool,
351+
/// Print source location information in HIR output
352+
///
353+
/// When enabled, HIR output will include #loc() annotations showing the source file,
354+
/// line, and column for each operation.
355+
#[cfg_attr(
356+
feature = "std",
357+
arg(
358+
long = "print-hir-source-locations",
359+
default_value_t = false,
360+
help_heading = "Printers"
361+
)
362+
)]
363+
pub print_hir_source_locations: bool,
364+
/// Specify path prefixes to try when resolving relative paths from DWARF debug info
365+
#[cfg_attr(
366+
feature = "std",
367+
arg(
368+
long = "trim-path-prefix",
369+
value_name = "PATH",
370+
help_heading = "Debugging"
371+
)
372+
)]
373+
pub trim_path_prefixes: Vec<PathBuf>,
351374
}
352375

353376
impl CodegenOptions {
@@ -516,6 +539,8 @@ impl Compiler {
516539
options.print_ir_after_all = unstable.print_ir_after_all;
517540
options.print_ir_after_pass = unstable.print_ir_after_pass;
518541
options.print_ir_after_modified = unstable.print_ir_after_modified;
542+
options.print_hir_source_locations = unstable.print_hir_source_locations;
543+
options.trim_path_prefixes = unstable.trim_path_prefixes;
519544

520545
// Establish --target-dir
521546
let target_dir = if self.target_dir.is_absolute() {

midenc-compile/src/stages/link.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
use alloc::{borrow::ToOwned, collections::BTreeMap, sync::Arc, vec::Vec};
1+
use alloc::{borrow::ToOwned, collections::BTreeMap, string::ToString, sync::Arc, vec::Vec};
22

33
use midenc_frontend_wasm::FrontendOutput;
44
use midenc_hir::{interner::Symbol, BuilderExt, OpBuilder, SourceSpan};
55
#[cfg(feature = "std")]
66
use midenc_session::Path;
77
use midenc_session::{
88
diagnostics::{Severity, Spanned},
9-
InputType, ProjectType,
9+
InputType, OutputMode, OutputType, ProjectType,
1010
};
1111

1212
use super::*;
@@ -133,6 +133,7 @@ impl Stage for LinkStage {
133133
InputType::Stdin { name, input } => {
134134
let config = wasm::WasmTranslationConfig {
135135
source_name: name.file_stem().unwrap().to_owned().into(),
136+
trim_path_prefixes: context.session().options.trim_path_prefixes.clone(),
136137
world: Some(world),
137138
..Default::default()
138139
};
@@ -151,6 +152,22 @@ impl Stage for LinkStage {
151152

152153
link_output.link_libraries_from(context.session())?;
153154

155+
// Emit HIR if requested
156+
let session = context.session();
157+
if session.should_emit(OutputType::Hir) {
158+
use midenc_hir::{Op, OpPrinter, OpPrintingFlags};
159+
let flags = OpPrintingFlags {
160+
print_entry_block_headers: true,
161+
print_intrinsic_attributes: false,
162+
print_source_locations: session.options.print_hir_source_locations,
163+
};
164+
let op = link_output.component.borrow();
165+
let hir_context = op.as_operation().context();
166+
let doc = op.as_operation().print(&flags, hir_context);
167+
let hir_str = doc.to_string();
168+
session.emit(OutputMode::Text, &hir_str).into_diagnostic()?;
169+
}
170+
154171
if context.session().parse_only() {
155172
log::debug!("stopping compiler early (parse-only=true)");
156173
return Err(CompilerStopped.into());
@@ -181,8 +198,10 @@ fn parse_hir_from_wasm_file(
181198
let mut bytes = Vec::with_capacity(1024);
182199
file.read_to_end(&mut bytes).into_diagnostic()?;
183200
let file_name = path.file_stem().unwrap().to_str().unwrap().to_owned();
201+
184202
let config = wasm::WasmTranslationConfig {
185203
source_name: file_name.into(),
204+
trim_path_prefixes: context.session().options.trim_path_prefixes.clone(),
186205
world: Some(world),
187206
..Default::default()
188207
};

midenc-session/src/emit.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,25 @@ impl<T: Emit> Emit for Arc<T> {
205205
}
206206
}
207207

208+
impl Emit for alloc::string::String {
209+
fn name(&self) -> Option<Symbol> {
210+
None
211+
}
212+
213+
fn output_type(&self, _mode: OutputMode) -> OutputType {
214+
OutputType::Hir
215+
}
216+
217+
fn write_to<W: Writer>(
218+
&self,
219+
mut writer: W,
220+
_mode: OutputMode,
221+
_session: &Session,
222+
) -> anyhow::Result<()> {
223+
writer.write_fmt(format_args!("{self}\n"))
224+
}
225+
}
226+
208227
impl Emit for miden_assembly::ast::Module {
209228
fn name(&self) -> Option<Symbol> {
210229
Some(Symbol::intern(self.path().to_string()))

0 commit comments

Comments
 (0)