[llvm-branch-commits] [llvm] [mlir] [Support][llvm-mc] Add column checking to -verify directives (PR #227570)
Alexander Richardson via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Sun Oct 18 12:42:19 PDT 2026
https://github.com/arichardson updated https://github.com/llvm/llvm-project/pull/227570
>From f252ad664d3587daba5197bd4953ebd68aa2885c Mon Sep 17 00:00:00 2001
From: Alex Richardson <alexrichardson at google.com>
Date: Sun, 18 Oct 2026 12:40:42 -0700
Subject: [PATCH] [nspr] initial commit
---
llvm/docs/CommandGuide/llvm-mc.md | 13 +++++--
.../Support/SourceMgrDiagnosticVerifier.h | 36 +++++++++++--------
.../Support/SourceMgrDiagnosticVerifier.cpp | 35 +++++++++++++-----
llvm/test/tools/llvm-mc/verify/basic.s | 28 ++++++++++++++-
.../SourceMgrDiagnosticVerifierTest.cpp | 24 +++++++++++--
mlir/lib/IR/Diagnostics.cpp | 3 +-
6 files changed, 107 insertions(+), 32 deletions(-)
diff --git a/llvm/docs/CommandGuide/llvm-mc.md b/llvm/docs/CommandGuide/llvm-mc.md
index 1ab1c1dbf1b12..037eb57393ce5 100644
--- a/llvm/docs/CommandGuide/llvm-mc.md
+++ b/llvm/docs/CommandGuide/llvm-mc.md
@@ -168,23 +168,30 @@ comma-separated list of prefixes to recognize; a bare `--verify` (with no
A directive has the form:
```text
-<prefix>-<kind>[-re][@<offset>] {{<text>}}
+<prefix>-<kind>[-re][@<offset>][:<col>] {{<text>}}
```
`<kind>` is one of `error`, `warning`, `note` or `remark`. Without
an `@<offset>`, the directive applies to its own line; `@+N`/`@-N`
apply to the line `N` lines below/above, `@above`/`@below` apply to
the nearest non-directive line above/below, and `@unknown` matches
-diagnostics reported without a source location. By default `<text>` is matched as a substring of
+diagnostics reported without a source location. An optional `:<col>` (matching
+the column shown in the tool's own `file:line:col:` diagnostic output)
+additionally requires the diagnostic to be at that exact column; without it,
+only the line is checked. By default `<text>` is matched as a substring of
the diagnostic's message; with the `-re` suffix, any `{{...}}` block
nested inside `<text>` is matched as a regular expression instead (the
rest of `<text>` is matched literally), for example:
```asm
-## Matches only diagnostics containing this exact text.
+## Matches only diagnostics containing this exact text, on this line.
.foo
# expected-error at -1 {{unknown directive}}
+## Also requires the diagnostic to be at column 1.
+.foo
+# expected-error at -1:1 {{unknown directive}}
+
## Matches any diagnostic starting with "unknown" and ending with
## "directive"; only the nested {{.*}} is a regex, the surrounding text
## is literal.
diff --git a/llvm/include/llvm/Support/SourceMgrDiagnosticVerifier.h b/llvm/include/llvm/Support/SourceMgrDiagnosticVerifier.h
index 9252b926eabb6..ba89052447011 100644
--- a/llvm/include/llvm/Support/SourceMgrDiagnosticVerifier.h
+++ b/llvm/include/llvm/Support/SourceMgrDiagnosticVerifier.h
@@ -16,10 +16,6 @@
#ifndef LLVM_SUPPORT_SOURCEMGRDIAGNOSTICVERIFIER_H
#define LLVM_SUPPORT_SOURCEMGRDIAGNOSTICVERIFIER_H
-#include <optional>
-#include <string>
-#include <vector>
-
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallVector.h"
@@ -27,6 +23,9 @@
#include "llvm/Support/Regex.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Support/raw_ostream.h"
+#include <optional>
+#include <string>
+#include <vector>
namespace llvm {
@@ -75,22 +74,26 @@ class LLVM_ABI SourceMgrDiagnosticVerifier {
ArrayRef<std::string> CommentPrefixes = {});
/// Matches a single actual diagnostic against the expected diagnostics
- /// recorded for \p Buf / \p LineNo, scanning \p Buf first if it hasn't been
- /// scanned yet. If \p HasLoc is false, the diagnostic has no location and is
- /// matched against the '@unknown' list instead (\p Buf / \p LineNo are
- /// ignored). If \p HasLoc is true but \p Buf is null (e.g. the diagnostic's
- /// file isn't a known buffer), the diagnostic is matched against an empty
- /// list, i.e. it can never match and is always unexpected. On a near miss,
- /// prints a message to \c OS. \p ReportUnexpected controls whether near
- /// misses / unexpected diagnostics are reported at all.
+ /// recorded for \p Buf / \p LineNo / \p ColNo, scanning \p Buf first if it
+ /// hasn't been scanned yet. If \p HasLoc is false, the diagnostic has no
+ /// location and is matched against the '@unknown' list instead (\p Buf /
+ /// \p LineNo / \p ColNo are ignored). If \p HasLoc is true but \p Buf is
+ /// null (e.g. the diagnostic's file isn't a known buffer), the diagnostic
+ /// is matched against an empty list, i.e. it can never match and is always
+ /// unexpected. \p ColNo is only checked against expected diagnostics that
+ /// requested a column (via ':<col>'); others match on line alone regardless
+ /// of \p ColNo. On a near miss, prints a message to \c OS. \p
+ /// ReportUnexpected controls whether near misses / unexpected diagnostics
+ /// are reported at all.
MatchResult process(SourceMgr::DiagKind Kind, bool HasLoc,
- const MemoryBuffer *Buf, unsigned LineNo,
+ const MemoryBuffer *Buf, unsigned LineNo, unsigned ColNo,
StringRef Message, bool ReportUnexpected = true);
/// Convenience overload for diagnostics already packaged as an
/// \c SMDiagnostic (e.g. from a \c SourceMgr diagnostic handler). Resolves
- /// the source buffer and line from \p Diag, and if the diagnostic is
- /// unexpected and \p ReportUnexpected is true, prints \p Diag to \c OS.
+ /// the source buffer and 1-based line/column from \p Diag, and if the
+ /// diagnostic is unexpected and \p ReportUnexpected is true, prints \p Diag
+ /// to \c OS.
MatchResult process(const SMDiagnostic &Diag, bool ReportUnexpected = true);
/// Reports (to \c OS) any expected diagnostic that was never matched by a
@@ -112,6 +115,9 @@ class LLVM_ABI SourceMgrDiagnosticVerifier {
SourceMgr::DiagKind Kind;
/// The line number the expected diagnostic should be on.
unsigned LineNo;
+ /// The column the expected diagnostic should be at, or 0 if the column
+ /// isn't checked (only the line is).
+ unsigned ColNo = 0;
/// The location of the expected diagnostic within the input file.
SMLoc FileLoc;
/// A flag indicating if the expected diagnostic has been matched yet.
diff --git a/llvm/lib/Support/SourceMgrDiagnosticVerifier.cpp b/llvm/lib/Support/SourceMgrDiagnosticVerifier.cpp
index 7efca8ce20d4c..f4bb90ddb5a5e 100644
--- a/llvm/lib/Support/SourceMgrDiagnosticVerifier.cpp
+++ b/llvm/lib/Support/SourceMgrDiagnosticVerifier.cpp
@@ -7,13 +7,11 @@
//===----------------------------------------------------------------------===//
#include "llvm/Support/SourceMgrDiagnosticVerifier.h"
-
-#include <algorithm>
-
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/raw_ostream.h"
+#include <algorithm>
using namespace llvm;
@@ -39,7 +37,7 @@ SourceMgrDiagnosticVerifier::SourceMgrDiagnosticVerifier(
// clang-format off
Expected = Regex("(^|[^[:alnum:]_-])(" + PrefixAlt +
")-(error|note|remark|warning)(-re)?"
- " *(@([+-][0-9]+|above|below|unknown))?"
+ " *(@([+-][0-9]+|above|below|unknown))?(:([0-9]+))?"
" *{{(.*)}}$");
// clang-format on
}
@@ -170,12 +168,19 @@ SourceMgrDiagnosticVerifier::computeExpectedDiags(const MemoryBuffer *Buf) {
.Case("warning", SourceMgr::DK_Warning)
.Case("remark", SourceMgr::DK_Remark)
.Case("note", SourceMgr::DK_Note);
- ExpectedDiag Record(Kind, LineNo + 1, ExpectedStart, Matches[7]);
+ ExpectedDiag Record(Kind, LineNo + 1, ExpectedStart, Matches[9]);
// Check to see if this is a regex match, i.e. it includes the `-re`.
if (!Matches[4].empty() && !computeRegex(Record))
continue;
+ // Parse an optional ':<col>' column requirement.
+ if (!Matches[8].empty() &&
+ (Matches[8].getAsInteger(10, Record.ColNo) || Record.ColNo == 0)) {
+ emitError(Record, "invalid column '" + Matches[7] + "'");
+ continue;
+ }
+
StringRef OffsetMatch = Matches[5];
if (!OffsetMatch.empty()) {
OffsetMatch = OffsetMatch.drop_front(1);
@@ -207,6 +212,12 @@ SourceMgrDiagnosticVerifier::computeExpectedDiags(const MemoryBuffer *Buf) {
Record.LineNo -= Offset;
}
} else if (OffsetMatch.consume_front("unknown")) {
+ // An '@unknown' diagnostic has no source location, so it cannot
+ // also require a column.
+ if (Record.ColNo != 0) {
+ emitError(Record, "cannot specify a column with '@unknown'");
+ continue;
+ }
// This is matching unknown locations.
Record.FileLoc = SMLoc();
ExpectedUnknownLocDiags.emplace_back(std::move(Record));
@@ -231,10 +242,9 @@ SourceMgrDiagnosticVerifier::computeExpectedDiags(const MemoryBuffer *Buf) {
return ExpectedDiags;
}
-SourceMgrDiagnosticVerifier::MatchResult
-SourceMgrDiagnosticVerifier::process(SourceMgr::DiagKind Kind, bool HasLoc,
- const MemoryBuffer *Buf, unsigned LineNo,
- StringRef Message, bool ReportUnexpected) {
+SourceMgrDiagnosticVerifier::MatchResult SourceMgrDiagnosticVerifier::process(
+ SourceMgr::DiagKind Kind, bool HasLoc, const MemoryBuffer *Buf,
+ unsigned LineNo, unsigned ColNo, StringRef Message, bool ReportUnexpected) {
MutableArrayRef<ExpectedDiag> Diags;
if (HasLoc) {
// If the buffer couldn't be resolved, `Diags` stays empty: a diagnostic
@@ -258,6 +268,10 @@ SourceMgrDiagnosticVerifier::process(SourceMgr::DiagKind Kind, bool HasLoc,
// File line must match (unless it's an unknown location).
if (HasLoc && E.LineNo != LineNo)
continue;
+ // Column is only checked against expected diagnostics that requested one
+ // (via ':<col>'); others match on line alone.
+ if (HasLoc && E.ColNo != 0 && E.ColNo != ColNo)
+ continue;
if (!E.match(Message))
continue;
if (E.Kind == Kind) {
@@ -300,9 +314,12 @@ SourceMgrDiagnosticVerifier::process(const SMDiagnostic &Diag,
if (unsigned ID = Mgr.FindBufferContainingLoc(Diag.getLoc()))
Buf = Mgr.getMemoryBuffer(ID);
}
+ // SMDiagnostic::getColumnNo() is 0-based; -verify directives are written
+ // against the printed (1-based) column, matching "file:line:col:" output.
MatchResult Result =
process(Diag.getKind(), HasLoc, Buf,
HasLoc ? static_cast<unsigned>(Diag.getLineNo()) : 0,
+ HasLoc ? static_cast<unsigned>(Diag.getColumnNo()) + 1 : 0,
Diag.getMessage(), ReportUnexpected);
if (Result == MatchResult::Unexpected)
printDiag(Diag);
diff --git a/llvm/test/tools/llvm-mc/verify/basic.s b/llvm/test/tools/llvm-mc/verify/basic.s
index eb3e6236207ce..ea6a08717b9e4 100644
--- a/llvm/test/tools/llvm-mc/verify/basic.s
+++ b/llvm/test/tools/llvm-mc/verify/basic.s
@@ -5,9 +5,18 @@
## Matching expected-error directives: a plain substring match, a '-re'
## regex match (note the nested {{...}} for the regex portion itself), an
-## '@+1' forward offset, and '@above'/'@below' offsets all pass.
+## '@+1' forward offset, '@above'/'@below' offsets, a bare ':<col>' column
+## check, and a combined '@offset:col' all pass.
# RUN: llvm-mc -triple riscv32 -verify %t/match.s
+## A directive with a column that doesn't match the actual diagnostic's
+## column (or with an invalid column such as ':0' or '@unknown:<col>') fails
+## verification.
+# RUN: not llvm-mc -triple riscv32 -verify %t/wrong-column.s 2>&1 \
+# RUN: | FileCheck %t/wrong-column.s --check-prefix=WRONG-COLUMN
+# RUN: not llvm-mc -triple riscv32 -verify %t/invalid-column.s 2>&1 \
+# RUN: | FileCheck %t/invalid-column.s --check-prefix=INVALID-COLUMN
+
## Malformed '-re' regex directives (missing closing '}}' or invalid regex
## syntax) and out-of-bounds line offsets fail verification with an error at
## the directive.
@@ -79,6 +88,11 @@
# expected-error at below {{unknown directive}}
.below_directive
+.qux_directive # expected-error:1 {{unknown directive}}
+
+.quux_directive
+# expected-error at -1:1 {{unknown directive}}
+
## Multiple expected-note directives on the same line whose substrings both
## match the first emitted note ("unexpected extra operand for instruction")
## still match both notes because unmatched directives are preferred over
@@ -88,6 +102,18 @@ sfence.vma a0, 0x10
# expected-note at -2 {{r}}
# expected-note at -3 {{register must be a GPR}}
+#--- wrong-column.s
+.foo_directive # expected-error:99 {{unknown directive}}
+# WRONG-COLUMN: error: unknown directive
+# WRONG-COLUMN: expected error "unknown directive" was not produced
+
+#--- invalid-column.s
+addi a0, a0, 1
+# expected-error at -1:0 {{never happens}}
+# expected-error at unknown:1 {{never happens}}
+# INVALID-COLUMN: error: invalid column ':0'
+# INVALID-COLUMN: error: cannot specify a column with '@unknown'
+
#--- invalid-regex.s
addi a0, a0, 1
# expected-error-re at -1 {{unclosed {{regex}}
diff --git a/llvm/unittests/Support/SourceMgrDiagnosticVerifierTest.cpp b/llvm/unittests/Support/SourceMgrDiagnosticVerifierTest.cpp
index 222ed92ce5df1..74835df8ab396 100644
--- a/llvm/unittests/Support/SourceMgrDiagnosticVerifierTest.cpp
+++ b/llvm/unittests/Support/SourceMgrDiagnosticVerifierTest.cpp
@@ -99,7 +99,7 @@ TEST_F(SourceMgrDiagnosticVerifierTest, NullBufferWithHasLocIsUnexpected) {
SourceMgrDiagnosticVerifier Verifier(SM, OS);
EXPECT_EQ(Verifier.process(SourceMgr::DK_Error, /*HasLoc=*/true,
- /*Buf=*/nullptr, 1, "anything"),
+ /*Buf=*/nullptr, 1, 1, "anything"),
MatchResult::Unexpected);
EXPECT_FALSE(Verifier.verify());
}
@@ -129,10 +129,11 @@ TEST_F(SourceMgrDiagnosticVerifierTest, LineZeroAndLeadingZeroOffsets) {
SourceMgrDiagnosticVerifier Verifier(SM, OS);
EXPECT_EQ(Verifier.process(SourceMgr::DK_Remark, /*HasLoc=*/true, Buf,
- /*LineNo=*/0, "implicit module at line 0"),
+ /*LineNo=*/0, /*ColNo=*/0,
+ "implicit module at line 0"),
MatchResult::Matched);
EXPECT_EQ(Verifier.process(SourceMgr::DK_Error, /*HasLoc=*/true, Buf,
- /*LineNo=*/3, "decimal leading zero"),
+ /*LineNo=*/3, /*ColNo=*/1, "decimal leading zero"),
MatchResult::Matched);
EXPECT_TRUE(Verifier.verify());
EXPECT_EQ(Output, "");
@@ -192,3 +193,20 @@ TEST_F(SourceMgrDiagnosticVerifierTest, CustomPrefixesAndCommentScoping) {
EXPECT_TRUE(Verifier.verify());
EXPECT_EQ(Output, "");
}
+
+TEST_F(SourceMgrDiagnosticVerifierTest, ColumnChecking) {
+ const MemoryBuffer *Buf = addBuffer("bad_op arg1, arg2\n"
+ "# expected-error at -1:8 {{bad arg1}}\n");
+ SourceMgrDiagnosticVerifier Verifier(SM, OS);
+
+ // Column 8 ('arg1') matches; column 1 ('bad_op') does not.
+ SMLoc Col1Loc = SMLoc::getFromPointer(Buf->getBufferStart());
+ SMLoc Col8Loc = SMLoc::getFromPointer(Buf->getBufferStart() + 7);
+ EXPECT_EQ(
+ Verifier.process(SM.GetMessage(Col1Loc, SourceMgr::DK_Error, "bad arg1")),
+ MatchResult::Unexpected);
+ EXPECT_EQ(
+ Verifier.process(SM.GetMessage(Col8Loc, SourceMgr::DK_Error, "bad arg1")),
+ MatchResult::Matched);
+ EXPECT_FALSE(Verifier.verify());
+}
diff --git a/mlir/lib/IR/Diagnostics.cpp b/mlir/lib/IR/Diagnostics.cpp
index 5f1a86e1ed495..3855fc70d4db7 100644
--- a/mlir/lib/IR/Diagnostics.cpp
+++ b/mlir/lib/IR/Diagnostics.cpp
@@ -721,7 +721,8 @@ void SourceMgrDiagnosticVerifierHandler::process(LocationAttr loc,
using MatchResult = llvm::SourceMgrDiagnosticVerifier::MatchResult;
MatchResult result = impl->verifier.process(
getDiagKind(kind), /*hasLoc=*/static_cast<bool>(fileLoc), buf,
- fileLoc ? fileLoc.getLine() : 0, msg, reportUnexpected);
+ fileLoc ? fileLoc.getLine() : 0, fileLoc ? fileLoc.getColumn() : 0, msg,
+ reportUnexpected);
if (result == MatchResult::Unexpected)
emitDiagnostic(loc, "unexpected " + getDiagKindStr(kind) + ": " + msg,
DiagnosticSeverity::Error);
More information about the llvm-branch-commits
mailing list