Skip to content

Commit fadfcb6

Browse files
authored
Rollup merge of #72625 - Amanieu:asm-srcloc, r=petrochenkov
Improve inline asm error diagnostics Previously we were just using the raw LLVM error output (with line, caret, etc) as the diagnostic message, which ends up looking rather out of place with our existing diagnostics. The new diagnostics properly format the diagnostics and also take advantage of LLVM's per-line `srcloc` attribute to map an error in inline assembly directly to the relevant line of source code. Incidentally also fixes #71639 by disabling `srcloc` metadata during LTO builds since we don't know what crate it might have come from. We can only resolve `srcloc`s from the currently crate since it indexes into the source map for the current crate. Fixes #72664 Fixes #71639 r? @petrochenkov ### Old style ```rust #![feature(llvm_asm)] fn main() { unsafe { let _x: i32; llvm_asm!( "mov $0, $1 invalid_instruction $0, $1 mov $0, $1" : "=&r" (_x) : "r" (0) :: "intel" ); } } ``` ``` error: <inline asm>:3:14: error: invalid instruction mnemonic 'invalid_instruction' invalid_instruction ecx, eax ^~~~~~~~~~~~~~~~~~~ --> src/main.rs:6:9 | 6 | / llvm_asm!( 7 | | "mov $0, $1 8 | | invalid_instruction $0, $1 9 | | mov $0, $1" ... | 12 | | :: "intel" 13 | | ); | |__________^ ``` ### New style ```rust #![feature(asm)] fn main() { unsafe { asm!( "mov {0}, {1} invalid_instruction {0}, {1} mov {0}, {1}", out(reg) _, in(reg) 0i64, ); } } ``` ``` error: invalid instruction mnemonic 'invalid_instruction' --> test.rs:7:14 | 7 | invalid_instruction {0}, {1} | ^ | note: instantiated into assembly here --> <inline asm>:3:14 | 3 | invalid_instruction rax, rcx | ^^^^^^^^^^^^^^^^^^^ ```
2 parents f1661d2 + fc497f7 commit fadfcb6

File tree

29 files changed

+367
-57
lines changed

29 files changed

+367
-57
lines changed

src/libfmt_macros/lib.rs

+23-1
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,11 @@ pub struct Parser<'a> {
191191
append_newline: bool,
192192
/// Whether this formatting string is a literal or it comes from a macro.
193193
is_literal: bool,
194+
/// Start position of the current line.
195+
cur_line_start: usize,
196+
/// Start and end byte offset of every line of the format string. Excludes
197+
/// newline characters and leading whitespace.
198+
pub line_spans: Vec<InnerSpan>,
194199
}
195200

196201
impl<'a> Iterator for Parser<'a> {
@@ -235,10 +240,15 @@ impl<'a> Iterator for Parser<'a> {
235240
None
236241
}
237242
}
238-
'\n' => Some(String(self.string(pos))),
239243
_ => Some(String(self.string(pos))),
240244
}
241245
} else {
246+
if self.is_literal && self.cur_line_start != self.input.len() {
247+
let start = self.to_span_index(self.cur_line_start);
248+
let end = self.to_span_index(self.input.len());
249+
self.line_spans.push(start.to(end));
250+
self.cur_line_start = self.input.len();
251+
}
242252
None
243253
}
244254
}
@@ -266,6 +276,8 @@ impl<'a> Parser<'a> {
266276
last_opening_brace: None,
267277
append_newline,
268278
is_literal,
279+
cur_line_start: 0,
280+
line_spans: vec![],
269281
}
270282
}
271283

@@ -433,7 +445,17 @@ impl<'a> Parser<'a> {
433445
'{' | '}' => {
434446
return &self.input[start..pos];
435447
}
448+
'\n' if self.is_literal => {
449+
let start = self.to_span_index(self.cur_line_start);
450+
let end = self.to_span_index(pos);
451+
self.line_spans.push(start.to(end));
452+
self.cur_line_start = pos + 1;
453+
self.cur.next();
454+
}
436455
_ => {
456+
if self.is_literal && pos == self.cur_line_start && c.is_whitespace() {
457+
self.cur_line_start = pos + c.len_utf8();
458+
}
437459
self.cur.next();
438460
}
439461
}

src/librustc_ast/ast.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1252,7 +1252,7 @@ pub enum ExprKind {
12521252
Ret(Option<P<Expr>>),
12531253

12541254
/// Output of the `asm!()` macro.
1255-
InlineAsm(InlineAsm),
1255+
InlineAsm(P<InlineAsm>),
12561256
/// Output of the `llvm_asm!()` macro.
12571257
LlvmInlineAsm(P<LlvmInlineAsm>),
12581258

@@ -1971,6 +1971,7 @@ pub struct InlineAsm {
19711971
pub template: Vec<InlineAsmTemplatePiece>,
19721972
pub operands: Vec<(InlineAsmOperand, Span)>,
19731973
pub options: InlineAsmOptions,
1974+
pub line_spans: Vec<Span>,
19741975
}
19751976

19761977
/// Inline assembly dialect.

src/librustc_ast_lowering/expr.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1267,7 +1267,8 @@ impl<'hir> LoweringContext<'_, 'hir> {
12671267

12681268
let operands = self.arena.alloc_from_iter(operands);
12691269
let template = self.arena.alloc_from_iter(asm.template.iter().cloned());
1270-
let hir_asm = hir::InlineAsm { template, operands, options: asm.options };
1270+
let line_spans = self.arena.alloc_slice(&asm.line_spans[..]);
1271+
let hir_asm = hir::InlineAsm { template, operands, options: asm.options, line_spans };
12711272
hir::ExprKind::InlineAsm(self.arena.alloc(hir_asm))
12721273
}
12731274

src/librustc_builtin_macros/asm.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -513,10 +513,16 @@ fn expand_preparsed_asm(ecx: &mut ExtCtxt<'_>, sp: Span, args: AsmArgs) -> P<ast
513513
}
514514
}
515515

516-
let inline_asm = ast::InlineAsm { template, operands, options: args.options };
516+
let line_spans = if parser.line_spans.is_empty() {
517+
vec![template_sp]
518+
} else {
519+
parser.line_spans.iter().map(|span| template_span.from_inner(*span)).collect()
520+
};
521+
522+
let inline_asm = ast::InlineAsm { template, operands, options: args.options, line_spans };
517523
P(ast::Expr {
518524
id: ast::DUMMY_NODE_ID,
519-
kind: ast::ExprKind::InlineAsm(inline_asm),
525+
kind: ast::ExprKind::InlineAsm(P(inline_asm)),
520526
span: sp,
521527
attrs: ast::AttrVec::new(),
522528
tokens: None,

src/librustc_codegen_llvm/asm.rs

+24-8
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use rustc_data_structures::fx::FxHashMap;
1414
use rustc_hir as hir;
1515
use rustc_middle::span_bug;
1616
use rustc_middle::ty::layout::TyAndLayout;
17-
use rustc_span::Span;
17+
use rustc_span::{Pos, Span};
1818
use rustc_target::abi::*;
1919
use rustc_target::asm::*;
2020

@@ -97,7 +97,7 @@ impl AsmBuilderMethods<'tcx> for Builder<'a, 'll, 'tcx> {
9797
ia.volatile,
9898
ia.alignstack,
9999
ia.dialect,
100-
span,
100+
&[span],
101101
);
102102
if r.is_none() {
103103
return false;
@@ -119,7 +119,7 @@ impl AsmBuilderMethods<'tcx> for Builder<'a, 'll, 'tcx> {
119119
template: &[InlineAsmTemplatePiece],
120120
operands: &[InlineAsmOperandRef<'tcx, Self>],
121121
options: InlineAsmOptions,
122-
span: Span,
122+
line_spans: &[Span],
123123
) {
124124
let asm_arch = self.tcx.sess.asm_arch.unwrap();
125125

@@ -287,9 +287,9 @@ impl AsmBuilderMethods<'tcx> for Builder<'a, 'll, 'tcx> {
287287
volatile,
288288
alignstack,
289289
dialect,
290-
span,
290+
line_spans,
291291
)
292-
.unwrap_or_else(|| span_bug!(span, "LLVM asm constraint validation failed"));
292+
.unwrap_or_else(|| span_bug!(line_spans[0], "LLVM asm constraint validation failed"));
293293

294294
if options.contains(InlineAsmOptions::PURE) {
295295
if options.contains(InlineAsmOptions::NOMEM) {
@@ -341,7 +341,7 @@ fn inline_asm_call(
341341
volatile: bool,
342342
alignstack: bool,
343343
dia: LlvmAsmDialect,
344-
span: Span,
344+
line_spans: &[Span],
345345
) -> Option<&'ll Value> {
346346
let volatile = if volatile { llvm::True } else { llvm::False };
347347
let alignstack = if alignstack { llvm::True } else { llvm::False };
@@ -382,8 +382,24 @@ fn inline_asm_call(
382382
key.len() as c_uint,
383383
);
384384

385-
let val: &'ll Value = bx.const_i32(span.ctxt().outer_expn().as_u32() as i32);
386-
llvm::LLVMSetMetadata(call, kind, llvm::LLVMMDNodeInContext(bx.llcx, &val, 1));
385+
// srcloc contains one integer for each line of assembly code.
386+
// Unfortunately this isn't enough to encode a full span so instead
387+
// we just encode the start position of each line.
388+
// FIXME: Figure out a way to pass the entire line spans.
389+
let mut srcloc = vec![];
390+
if dia == LlvmAsmDialect::Intel && line_spans.len() > 1 {
391+
// LLVM inserts an extra line to add the ".intel_syntax", so add
392+
// a dummy srcloc entry for it.
393+
//
394+
// Don't do this if we only have 1 line span since that may be
395+
// due to the asm template string coming from a macro. LLVM will
396+
// default to the first srcloc for lines that don't have an
397+
// associated srcloc.
398+
srcloc.push(bx.const_i32(0));
399+
}
400+
srcloc.extend(line_spans.iter().map(|span| bx.const_i32(span.lo().to_u32() as i32)));
401+
let md = llvm::LLVMMDNodeInContext(bx.llcx, srcloc.as_ptr(), srcloc.len() as u32);
402+
llvm::LLVMSetMetadata(call, kind, md);
387403

388404
Some(call)
389405
} else {

src/librustc_codegen_llvm/back/write.rs

+43-8
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use rustc_middle::bug;
2323
use rustc_middle::ty::TyCtxt;
2424
use rustc_session::config::{self, Lto, OutputType, Passes, Sanitizer, SwitchWithOptPath};
2525
use rustc_session::Session;
26+
use rustc_span::InnerSpan;
2627
use rustc_target::spec::{CodeModel, RelocModel};
2728

2829
use libc::{c_char, c_int, c_uint, c_void, size_t};
@@ -238,12 +239,19 @@ impl<'a> Drop for DiagnosticHandlers<'a> {
238239
}
239240
}
240241

241-
unsafe extern "C" fn report_inline_asm(
242+
fn report_inline_asm(
242243
cgcx: &CodegenContext<LlvmCodegenBackend>,
243-
msg: &str,
244-
cookie: c_uint,
244+
msg: String,
245+
mut cookie: c_uint,
246+
source: Option<(String, Vec<InnerSpan>)>,
245247
) {
246-
cgcx.diag_emitter.inline_asm_error(cookie as u32, msg.to_owned());
248+
// In LTO build we may get srcloc values from other crates which are invalid
249+
// since they use a different source map. To be safe we just suppress these
250+
// in LTO builds.
251+
if matches!(cgcx.lto, Lto::Fat | Lto::Thin) {
252+
cookie = 0;
253+
}
254+
cgcx.diag_emitter.inline_asm_error(cookie as u32, msg, source);
247255
}
248256

249257
unsafe extern "C" fn inline_asm_handler(diag: &SMDiagnostic, user: *const c_void, cookie: c_uint) {
@@ -252,10 +260,37 @@ unsafe extern "C" fn inline_asm_handler(diag: &SMDiagnostic, user: *const c_void
252260
}
253261
let (cgcx, _) = *(user as *const (&CodegenContext<LlvmCodegenBackend>, &Handler));
254262

255-
let msg = llvm::build_string(|s| llvm::LLVMRustWriteSMDiagnosticToString(diag, s))
256-
.expect("non-UTF8 SMDiagnostic");
263+
// Recover the post-substitution assembly code from LLVM for better
264+
// diagnostics.
265+
let mut have_source = false;
266+
let mut buffer = String::new();
267+
let mut loc = 0;
268+
let mut ranges = [0; 8];
269+
let mut num_ranges = ranges.len() / 2;
270+
let msg = llvm::build_string(|msg| {
271+
buffer = llvm::build_string(|buffer| {
272+
have_source = llvm::LLVMRustUnpackSMDiagnostic(
273+
diag,
274+
msg,
275+
buffer,
276+
&mut loc,
277+
ranges.as_mut_ptr(),
278+
&mut num_ranges,
279+
);
280+
})
281+
.expect("non-UTF8 inline asm");
282+
})
283+
.expect("non-UTF8 SMDiagnostic");
284+
285+
let source = have_source.then(|| {
286+
let mut spans = vec![InnerSpan::new(loc as usize, loc as usize)];
287+
for i in 0..num_ranges {
288+
spans.push(InnerSpan::new(ranges[i * 2] as usize, ranges[i * 2 + 1] as usize));
289+
}
290+
(buffer, spans)
291+
});
257292

258-
report_inline_asm(cgcx, &msg, cookie);
293+
report_inline_asm(cgcx, msg, cookie, source);
259294
}
260295

261296
unsafe extern "C" fn diagnostic_handler(info: &DiagnosticInfo, user: *mut c_void) {
@@ -266,7 +301,7 @@ unsafe extern "C" fn diagnostic_handler(info: &DiagnosticInfo, user: *mut c_void
266301

267302
match llvm::diagnostic::Diagnostic::unpack(info) {
268303
llvm::diagnostic::InlineAsm(inline) => {
269-
report_inline_asm(cgcx, &llvm::twine_to_string(inline.message), inline.cookie);
304+
report_inline_asm(cgcx, llvm::twine_to_string(inline.message), inline.cookie, None);
270305
}
271306

272307
llvm::diagnostic::Optimization(opt) => {

src/librustc_codegen_llvm/llvm/ffi.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -2070,7 +2070,14 @@ extern "C" {
20702070
);
20712071

20722072
#[allow(improper_ctypes)]
2073-
pub fn LLVMRustWriteSMDiagnosticToString(d: &SMDiagnostic, s: &RustString);
2073+
pub fn LLVMRustUnpackSMDiagnostic(
2074+
d: &SMDiagnostic,
2075+
message_out: &RustString,
2076+
buffer_out: &RustString,
2077+
loc_out: &mut c_uint,
2078+
ranges_out: *mut c_uint,
2079+
num_ranges: &mut usize,
2080+
) -> bool;
20742081

20752082
pub fn LLVMRustWriteArchive(
20762083
Dst: *const c_char,

src/librustc_codegen_ssa/back/write.rs

+33-6
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ use rustc_session::cgu_reuse_tracker::CguReuseTracker;
3131
use rustc_session::config::{self, CrateType, Lto, OutputFilenames, OutputType};
3232
use rustc_session::config::{Passes, Sanitizer, SwitchWithOptPath};
3333
use rustc_session::Session;
34-
use rustc_span::hygiene::ExpnId;
3534
use rustc_span::source_map::SourceMap;
3635
use rustc_span::symbol::{sym, Symbol};
36+
use rustc_span::{BytePos, FileName, InnerSpan, Pos, Span};
3737
use rustc_target::spec::{MergeFunctions, PanicStrategy};
3838

3939
use std::any::Any;
@@ -1551,7 +1551,7 @@ fn spawn_work<B: ExtraBackendMethods>(cgcx: CodegenContext<B>, work: WorkItem<B>
15511551

15521552
enum SharedEmitterMessage {
15531553
Diagnostic(Diagnostic),
1554-
InlineAsmError(u32, String),
1554+
InlineAsmError(u32, String, Option<(String, Vec<InnerSpan>)>),
15551555
AbortIfErrors,
15561556
Fatal(String),
15571557
}
@@ -1572,8 +1572,13 @@ impl SharedEmitter {
15721572
(SharedEmitter { sender }, SharedEmitterMain { receiver })
15731573
}
15741574

1575-
pub fn inline_asm_error(&self, cookie: u32, msg: String) {
1576-
drop(self.sender.send(SharedEmitterMessage::InlineAsmError(cookie, msg)));
1575+
pub fn inline_asm_error(
1576+
&self,
1577+
cookie: u32,
1578+
msg: String,
1579+
source: Option<(String, Vec<InnerSpan>)>,
1580+
) {
1581+
drop(self.sender.send(SharedEmitterMessage::InlineAsmError(cookie, msg, source)));
15771582
}
15781583

15791584
pub fn fatal(&self, msg: &str) {
@@ -1626,8 +1631,30 @@ impl SharedEmitterMain {
16261631
}
16271632
handler.emit_diagnostic(&d);
16281633
}
1629-
Ok(SharedEmitterMessage::InlineAsmError(cookie, msg)) => {
1630-
sess.span_err(ExpnId::from_u32(cookie).expn_data().call_site, &msg)
1634+
Ok(SharedEmitterMessage::InlineAsmError(cookie, msg, source)) => {
1635+
let msg = msg.strip_prefix("error: ").unwrap_or(&msg);
1636+
1637+
// If the cookie is 0 then we don't have span information.
1638+
let mut err = if cookie == 0 {
1639+
sess.struct_err(&msg)
1640+
} else {
1641+
let pos = BytePos::from_u32(cookie);
1642+
let span = Span::with_root_ctxt(pos, pos);
1643+
sess.struct_span_err(span, &msg)
1644+
};
1645+
1646+
// Point to the generated assembly if it is available.
1647+
if let Some((buffer, spans)) = source {
1648+
let source = sess
1649+
.source_map()
1650+
.new_source_file(FileName::inline_asm_source_code(&buffer), buffer);
1651+
let source_span = Span::with_root_ctxt(source.start_pos, source.end_pos);
1652+
let spans: Vec<_> =
1653+
spans.iter().map(|sp| source_span.from_inner(*sp)).collect();
1654+
err.span_note(spans, "instantiated into assembly here");
1655+
}
1656+
1657+
err.emit();
16311658
}
16321659
Ok(SharedEmitterMessage::AbortIfErrors) => {
16331660
sess.abort_if_errors();

src/librustc_codegen_ssa/mir/block.rs

+10-2
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
831831
template: &[ast::InlineAsmTemplatePiece],
832832
operands: &[mir::InlineAsmOperand<'tcx>],
833833
options: ast::InlineAsmOptions,
834+
line_spans: &[Span],
834835
destination: Option<mir::BasicBlock>,
835836
) {
836837
let span = terminator.source_info.span;
@@ -930,7 +931,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
930931
})
931932
.collect();
932933

933-
bx.codegen_inline_asm(template, &operands, options, span);
934+
bx.codegen_inline_asm(template, &operands, options, line_spans);
934935

935936
if let Some(target) = destination {
936937
helper.funclet_br(self, &mut bx, target);
@@ -1033,14 +1034,21 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
10331034
bug!("borrowck false edges in codegen")
10341035
}
10351036

1036-
mir::TerminatorKind::InlineAsm { template, ref operands, options, destination } => {
1037+
mir::TerminatorKind::InlineAsm {
1038+
template,
1039+
ref operands,
1040+
options,
1041+
line_spans,
1042+
destination,
1043+
} => {
10371044
self.codegen_asm_terminator(
10381045
helper,
10391046
bx,
10401047
terminator,
10411048
template,
10421049
operands,
10431050
options,
1051+
line_spans,
10441052
destination,
10451053
);
10461054
}

src/librustc_codegen_ssa/traits/asm.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ pub trait AsmBuilderMethods<'tcx>: BackendTypes {
5252
template: &[InlineAsmTemplatePiece],
5353
operands: &[InlineAsmOperandRef<'tcx, Self>],
5454
options: InlineAsmOptions,
55-
span: Span,
55+
line_spans: &[Span],
5656
);
5757
}
5858

0 commit comments

Comments
 (0)