https://github.com/arichardson updated 
https://github.com/llvm/llvm-project/pull/227569

>From 79ef794f54f0b6f58ee4274ff57a343d3eea3547 Mon Sep 17 00:00:00 2001
From: Alex Richardson <[email protected]>
Date: Tue, 29 Sep 2026 22:49:13 -0700
Subject: [PATCH 1/3] [llvm-mc] Add -verify=<prefixes> diagnostic-verification
 mode

Add a `-verify` (and `-verify=<prefix1,prefix2,...>`) option to `llvm-mc`,
backed by `llvm::SourceMgrDiagnosticVerifier`, to check assembly diagnostics
inline via `expected-(error|warning|remark|note)` comments without needing
an external `not llvm-mc ... 2>&1 | FileCheck` pipeline.

In `-verify` mode, assembly output is suppressed (`MCNullStreamer`), all
buffers registered with `SourceMgr` (including `.include`s) are scanned for
directives scoped to the target's `MCAsmInfo::getCommentString()` plus
`//` and `#`, and `llvm-mc` exits zero if and only if all expected
diagnostics match and no unexpected diagnostics are emitted.

This commit was created with the help of AI tools
---
 llvm/docs/CommandGuide/llvm-mc.md      |  49 +++++++++++
 llvm/docs/TestingGuide.md              |  11 +++
 llvm/test/tools/llvm-mc/verify/basic.s | 112 +++++++++++++++++++++++++
 llvm/tools/llvm-mc/CMakeLists.txt      |   1 +
 llvm/tools/llvm-mc/Verifier.cpp        |  97 +++++++++++++++++++++
 llvm/tools/llvm-mc/Verifier.h          |  64 ++++++++++++++
 llvm/tools/llvm-mc/llvm-mc.cpp         |  58 +++++++++++++
 7 files changed, 392 insertions(+)
 create mode 100644 llvm/test/tools/llvm-mc/verify/basic.s
 create mode 100644 llvm/tools/llvm-mc/Verifier.cpp
 create mode 100644 llvm/tools/llvm-mc/Verifier.h

diff --git a/llvm/docs/CommandGuide/llvm-mc.md 
b/llvm/docs/CommandGuide/llvm-mc.md
index 4fdde854cdecc3..7ca33b7d7ed99a 100644
--- a/llvm/docs/CommandGuide/llvm-mc.md
+++ b/llvm/docs/CommandGuide/llvm-mc.md
@@ -157,6 +157,55 @@ prints in Intel/MASM syntax.
 Choose DWARF debug sections compression.
 :::
 
+:::{option} --verify=<prefixes>
+
+Instead of just printing diagnostics, check them against `<prefix>-error`,
+`<prefix>-warning`, `<prefix>-note` and `<prefix>-remark` comments in
+the input, similar to Clang's `-verify` flag. `<prefixes>` is a
+comma-separated list of prefixes to recognize; a bare `--verify` (with no
+`=<prefixes>`) is equivalent to `--verify=expected`.
+
+A directive has the form:
+```asm
+<prefix>-<kind>[-re][@<offset>] {{<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, and `@above`/`@below` apply to
+the nearest non-directive line above/below. 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.
+.foo
+# expected-error@-1 {{unknown directive}}
+
+## Matches any diagnostic starting with "unknown" and ending with
+## "directive"; only the nested {{.*}} is a regex, the surrounding text
+## is literal.
+.foo
+# expected-error-re@-1 {{unknown {{.*}} directive}}
+```
+
+A directive must appear within a comment recognized by the target being
+assembled for (its own comment string, plus `//` and `#` if the target
+allows additional comment forms); text that merely resembles a directive
+elsewhere on the line is ignored. Directives are matched line-based, so they
+are not recognized inside multi-line `/* ... */` block comments. Use a
+single-line comment form instead.
+
+Under `--verify`, the process exit code reflects whether the diagnostics
+matched what was expected, not whether assembling/disassembling itself
+produced an error: a fully expected error is a successful (exit code 0) run.
+`--verify` is not supported together with `--as-lex`, since `--as-lex`
+does not report its errors as diagnostics with a message or location.
+
+:::
+
 ## EXIT STATUS
 
 If {program}`llvm-mc` succeeds, it will exit with 0. Otherwise, if an error
diff --git a/llvm/docs/TestingGuide.md b/llvm/docs/TestingGuide.md
index 77803c4443da26..b344a688b17a2f 100644
--- a/llvm/docs/TestingGuide.md
+++ b/llvm/docs/TestingGuide.md
@@ -561,6 +561,17 @@ This test will fail if placed into a `download` directory.
 To make your tests robust, always use `opt ... < %s` in the `RUN` line.
 {program}`opt` does not output a `ModuleID` when input comes from stdin.
 
+Another common source of fragile tests is checking assembler diagnostics with
+plain `FileCheck`, e.g. `RUN: not llvm-mc ... | FileCheck %s` with unanchored
+`CHECK: error:` lines. `FileCheck` only asserts that the listed diagnostics
+appear somewhere, in order; it does not notice an unrelated *extra*
+diagnostic that a later change starts emitting next to the one under test.
+For `llvm-mc` tests, prefer `--verify` (see the
+{doc}`llvm-mc <CommandGuide/llvm-mc>` documentation), which checks that every
+diagnostic produced matches an `expected-error`/`expected-warning`/
+`expected-note`/`expected-remark` comment in the input and vice versa,
+similar to Clang's `-verify` flag.
+
 ### Platform-Specific Tests
 
 Whenever adding tests that require the knowledge of a specific platform,
diff --git a/llvm/test/tools/llvm-mc/verify/basic.s 
b/llvm/test/tools/llvm-mc/verify/basic.s
new file mode 100644
index 00000000000000..ce4b717c91132b
--- /dev/null
+++ b/llvm/test/tools/llvm-mc/verify/basic.s
@@ -0,0 +1,112 @@
+# Tests for llvm-mc's -verify=<prefixes> diagnostic-verification mode.
+
+# REQUIRES: riscv-registered-target
+# RUN: rm -rf %t && split-file %s %t
+
+## Matching expected-error directives: a plain substring match, a '-re'
+## regex match (note the nested {{...}} for the regex portion itself), and
+## an '@+1' forward offset all pass.
+# RUN: llvm-mc -triple riscv32 -verify %t/match.s
+
+## A custom '-verify=<prefix>' prefix is recognized instead of 'expected'.
+# RUN: llvm-mc -triple riscv32 -verify=check %t/match-custom-prefix.s
+
+## A diagnostic whose text matches an expected-* comment but whose kind
+## doesn't (e.g. expected-warning for an actual error) is a "near miss": it
+## still counts as not produced, since it wasn't a full match.
+# RUN: not llvm-mc -triple riscv32 -verify %t/near-miss.s 2>&1 \
+# RUN:   | FileCheck %t/near-miss.s --check-prefix=NEAR-MISS
+
+## A diagnostic with no matching expected-* comment fails verification.
+# RUN: not llvm-mc -triple riscv32 -verify %t/unexpected.s 2>&1 \
+# RUN:   | FileCheck %t/unexpected.s --check-prefix=UNEXPECTED
+
+## An expected-error that is never produced fails verification.
+# RUN: not llvm-mc -triple riscv32 -verify %t/missing.s 2>&1 \
+# RUN:   | FileCheck %t/missing.s --check-prefix=MISSING
+
+## An expected-error in a '.include'd file that's never produced also fails
+## verification: the included file's buffer isn't known to SourceMgr until
+## it's actually .include'd during parsing, so process() never has an actual
+## diagnostic in it to trigger scanning it. This must be caught by verify()
+## re-scanning all of SourceMgr's buffers, not just the ones process() saw.
+# RUN: not llvm-mc -triple riscv32 -verify -I %t %t/main-include.s 2>&1 \
+# RUN:   | FileCheck %t/included-missing.s --check-prefix=INCLUDE-MISSING
+
+## A directive-looking magic string outside of a comment is not treated as a
+## directive, so the resulting diagnostic is still unexpected.
+# RUN: not llvm-mc -triple riscv32 -verify %t/outside-comment.s 2>&1 \
+# RUN:   | FileCheck %t/outside-comment.s --check-prefix=UNEXPECTED
+
+## A file with no diagnostics and no expected-* comments passes. Unlike
+## clang's -verify, there is no dedicated 'expected-no-diagnostics' marker:
+## none is needed, since if nothing is expected and nothing happens,
+## verification trivially succeeds, and any diagnostic that *does* occur is
+## still caught as unexpected (see the 'unexpected' case above).
+# RUN: llvm-mc -triple riscv32 -verify %t/no-diagnostics.s
+
+## Diagnostics reported through MCContext (e.g. by the DWARF CFI checker,
+## which doesn't go through SourceMgr's diagnostic handler) are also
+## checked, not just diagnostics from the assembler/parser.
+# RUN: llvm-mc -triple riscv32 -verify -validate-cfi -filetype=null %t/cfi.s
+
+## -verify is rejected together with -as-lex, since -as-lex doesn't report
+## its errors as diagnostics with a message/location at all.
+# RUN: not llvm-mc -triple riscv32 -verify -as-lex %t/no-diagnostics.s 2>&1 \
+# RUN:   | FileCheck %s --check-prefix=AS-LEX-ERR
+# AS-LEX-ERR: -verify is not supported with -as-lex
+
+#--- match.s
+.foo_directive
+# expected-error@-1 {{unknown directive}}
+
+.bar_directive
+# expected-error-re@-1 {{unknown {{.*}}directive}}
+
+# expected-error@+1 {{unknown directive}}
+.baz_directive
+
+#--- match-custom-prefix.s
+.foo_directive
+# check-error@-1 {{unknown directive}}
+
+#--- near-miss.s
+.foo_directive
+# expected-warning@-1 {{unknown directive}}
+# NEAR-MISS: 'error' diagnostic emitted when expecting a 'warning'
+# NEAR-MISS: expected warning "unknown directive" was not produced
+
+#--- unexpected.s
+.foo_directive
+# UNEXPECTED: error: unknown directive
+
+#--- missing.s
+addi a0, a0, 1
+# expected-error@-1 {{this will never happen}}
+# MISSING: expected error "this will never happen" was not produced
+
+#--- main-include.s
+.include "included-missing.s"
+
+#--- included-missing.s
+addi a0, a0, 1
+# expected-error@-1 {{this will never happen}}
+# INCLUDE-MISSING: expected error "this will never happen" was not produced
+
+#--- outside-comment.s
+.foo_directive  expected-error {{unknown directive}}
+# UNEXPECTED: error: unknown directive
+
+#--- no-diagnostics.s
+addi a0, a0, 1
+
+#--- cfi.s
+       .text
+       .globl  f
+       .type   f,@function
+f:
+       .cfi_startproc
+       .cfi_same_value ra
+       li a0, 10
+       # expected-error@-1 {{changed register X10, that register X10's 
unwinding rule uses, but there is no CFI directives about it}}
+       .cfi_endproc
diff --git a/llvm/tools/llvm-mc/CMakeLists.txt 
b/llvm/tools/llvm-mc/CMakeLists.txt
index 67ac890966fa67..bf7f217bc13c15 100644
--- a/llvm/tools/llvm-mc/CMakeLists.txt
+++ b/llvm/tools/llvm-mc/CMakeLists.txt
@@ -13,5 +13,6 @@ set(LLVM_LINK_COMPONENTS
 add_llvm_tool(llvm-mc
   llvm-mc.cpp
   Disassembler.cpp
+  Verifier.cpp
   )
 
diff --git a/llvm/tools/llvm-mc/Verifier.cpp b/llvm/tools/llvm-mc/Verifier.cpp
new file mode 100644
index 00000000000000..19de8d7f785cd3
--- /dev/null
+++ b/llvm/tools/llvm-mc/Verifier.cpp
@@ -0,0 +1,97 @@
+//===- Verifier.cpp - Diagnostic Verifier for llvm-mc 
---------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "Verifier.h"
+#include "llvm/MC/MCContext.h"
+#include "llvm/Support/SourceMgr.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace llvm;
+
+namespace {
+/// SourceMgr::PrintMessage unconditionally re-invokes SrcMgr's installed
+/// DiagHandler if one is set, rather than only doing so for diagnostics that
+/// didn't originate from the handler itself. Since MCVerifier is installed as
+/// that handler, and its own printing (near-miss / not-produced messages)
+/// goes through SourceMgr::PrintMessage, calling into it from inside the
+/// handler would otherwise re-enter the handler and recurse indefinitely.
+/// This temporarily clears the handler for the duration of the guard so that
+/// PrintMessage prints directly instead.
+class ScopedHandlerDisable {
+  SourceMgr &SrcMgr;
+  SourceMgr::DiagHandlerTy SavedHandler;
+  void *SavedContext;
+
+public:
+  explicit ScopedHandlerDisable(SourceMgr &SrcMgr)
+      : SrcMgr(SrcMgr), SavedHandler(SrcMgr.getDiagHandler()),
+        SavedContext(SrcMgr.getDiagContext()) {
+    SrcMgr.setDiagHandler(nullptr, nullptr);
+  }
+  ~ScopedHandlerDisable() { SrcMgr.setDiagHandler(SavedHandler, SavedContext); 
}
+};
+} // namespace
+
+MCVerifier::MCVerifier(SourceMgr &SrcMgr, ArrayRef<std::string> Prefixes,
+                       ArrayRef<std::string> CommentPrefixes)
+    : SrcMgr(SrcMgr), Verifier(Prefixes, CommentPrefixes) {
+  // Scan every buffer already known to SrcMgr up front, so that an expected
+  // diagnostic that's never actually produced (e.g. because the run never
+  // reports any diagnostic at all) is still recorded and reported as missing
+  // by verify(), rather than never being noticed since process() was never
+  // called for that buffer. Guarded like every other call into Verifier in
+  // this file: a malformed directive (e.g. an invalid '-re' regex) makes
+  // computeExpectedDiags print through SrcMgr, which would otherwise re-enter
+  // installHandlers's handler if the constructor ever ran after it (it
+  // currently never does, at the sole call site in llvm-mc.cpp).
+  ScopedHandlerDisable Guard(SrcMgr);
+  for (unsigned I = 0, E = SrcMgr.getNumBuffers(); I != E; ++I)
+    (void)Verifier.computeExpectedDiags(errs(), SrcMgr,
+                                        SrcMgr.getMemoryBuffer(I + 1));
+}
+
+MCVerifier::~MCVerifier() {
+  // SrcMgr may outlive this MCVerifier; don't leave its handler pointing at
+  // freed memory.
+  if (SrcMgr.getDiagHandler() == &MCVerifier::handleSourceMgrDiag)
+    SrcMgr.setDiagHandler(nullptr, nullptr);
+}
+
+void MCVerifier::installHandlers(MCContext &Ctx) {
+  SrcMgr.setDiagHandler(&MCVerifier::handleSourceMgrDiag, this);
+  Ctx.setDiagnosticHandler(
+      [this](const SMDiagnostic &Diag, bool /*InlineAsm*/,
+             const SourceMgr & /*SrcMgr*/,
+             std::vector<const MDNode *> & /*LocInfos*/) { process(Diag); });
+}
+
+bool MCVerifier::verify() {
+  ScopedHandlerDisable Guard(SrcMgr);
+  return Verifier.verify(errs(), SrcMgr);
+}
+
+void MCVerifier::handleSourceMgrDiag(const SMDiagnostic &Diag, void *Context) {
+  static_cast<MCVerifier *>(Context)->process(Diag);
+}
+
+void MCVerifier::process(const SMDiagnostic &Diag) {
+  ScopedHandlerDisable Guard(SrcMgr);
+
+  bool HasLoc = Diag.getLoc().isValid();
+  const MemoryBuffer *Buf = nullptr;
+  if (HasLoc) {
+    if (unsigned ID = SrcMgr.FindBufferContainingLoc(Diag.getLoc()))
+      Buf = SrcMgr.getMemoryBuffer(ID);
+  }
+
+  auto Result = Verifier.process(
+      errs(), SrcMgr, Diag.getKind(), HasLoc, Buf,
+      HasLoc ? static_cast<unsigned>(Diag.getLineNo()) : 0, Diag.getMessage());
+  if (Result == SourceMgrDiagnosticVerifier::MatchResult::Unexpected)
+    SrcMgr.PrintMessage(errs(), Diag);
+}
diff --git a/llvm/tools/llvm-mc/Verifier.h b/llvm/tools/llvm-mc/Verifier.h
new file mode 100644
index 00000000000000..d567141ef0a0c1
--- /dev/null
+++ b/llvm/tools/llvm-mc/Verifier.h
@@ -0,0 +1,64 @@
+//===- Verifier.h - Diagnostic Verifier for llvm-mc -----------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This implements '-verify=<prefixes>', a mode that checks emitted
+// diagnostics against '<prefix>-error'/'-warning'/'-note'/'-remark' comments
+// in the input, similar to clang's '-verify' flag.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TOOLS_LLVM_MC_VERIFIER_H
+#define LLVM_TOOLS_LLVM_MC_VERIFIER_H
+
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/Support/SourceMgrDiagnosticVerifier.h"
+#include <string>
+
+namespace llvm {
+
+class MCContext;
+class SourceMgr;
+class SMDiagnostic;
+
+/// Checks the diagnostics produced while assembling/disassembling against
+/// '<prefix>-<kind>' comments in the input.
+class MCVerifier {
+public:
+  /// \p Prefixes are the accepted directive prefixes (from '-verify=...').
+  /// \p CommentPrefixes are the comment-start strings recognized by the
+  /// current target, so a directive outside of a comment is ignored.
+  MCVerifier(SourceMgr &SrcMgr, ArrayRef<std::string> Prefixes,
+             ArrayRef<std::string> CommentPrefixes);
+
+  /// Clears SrcMgr's diagnostic handler if it's still this object's, so a
+  /// SourceMgr that outlives this MCVerifier is never left with a dangling
+  /// handler pointing at freed memory.
+  ~MCVerifier();
+
+  /// Installs this as the diagnostic handler for both the SourceMgr passed to
+  /// the constructor and \p Ctx. Both are needed: most parser/disassembler
+  /// diagnostics go through SourceMgr, but some (e.g. the DWARF CFI checker's,
+  /// via MCContext::reportError/reportWarning) are reported directly through
+  /// MCContext and never reach SourceMgr's handler at all.
+  void installHandlers(MCContext &Ctx);
+
+  /// Reports any expected diagnostic that was never produced, and returns
+  /// whether verification succeeded overall.
+  bool verify();
+
+private:
+  static void handleSourceMgrDiag(const SMDiagnostic &Diag, void *Context);
+  void process(const SMDiagnostic &Diag);
+
+  SourceMgr &SrcMgr;
+  SourceMgrDiagnosticVerifier Verifier;
+};
+
+} // namespace llvm
+
+#endif // LLVM_TOOLS_LLVM_MC_VERIFIER_H
diff --git a/llvm/tools/llvm-mc/llvm-mc.cpp b/llvm/tools/llvm-mc/llvm-mc.cpp
index 01d42f07e337da..0d9ded02cd1596 100644
--- a/llvm/tools/llvm-mc/llvm-mc.cpp
+++ b/llvm/tools/llvm-mc/llvm-mc.cpp
@@ -12,6 +12,7 @@
 
//===----------------------------------------------------------------------===//
 
 #include "Disassembler.h"
+#include "Verifier.h"
 #include "llvm/ADT/ScopeExit.h"
 #include "llvm/DWARFCFIChecker/DWARFCFIFunctionFrameAnalyzer.h"
 #include "llvm/DWARFCFIChecker/DWARFCFIFunctionFrameStreamer.h"
@@ -223,6 +224,15 @@ static cl::opt<bool> ValidateCFI("validate-cfi",
                                  cl::desc("Validate the CFI directives"),
                                  cl::cat(MCCategory));
 
+static cl::list<std::string> Verify(
+    "verify", cl::CommaSeparated, cl::ValueOptional,
+    cl::desc(
+        "Check the diagnostics produced against '<prefix>-error'/"
+        "'-warning'/'-note'/'-remark' comments in the input instead of just "
+        "printing them, using a comma-separated list of <prefixes> "
+        "(default: 'expected') as the recognized directive prefixes"),
+    cl::value_desc("prefixes"), cl::cat(MCCategory));
+
 enum ActionType {
   AC_AsLex,
   AC_Assemble,
@@ -420,6 +430,14 @@ int main(int argc, char **argv) {
   setDwarfDebugProducer();
 
   const char *ProgName = argv[0];
+
+  bool VerifyEnabled = Verify.getNumOccurrences() > 0;
+  if (VerifyEnabled && Action == AC_AsLex) {
+    WithColor::error(errs(), ProgName)
+        << "-verify is not supported with -as-lex\n";
+    return 1;
+  }
+
   const Target *TheTarget = GetTarget(ProgName);
   if (!TheTarget)
     return 1;
@@ -464,6 +482,37 @@ int main(int argc, char **argv) {
   MAI->setPreserveAsmComments(PreserveComments);
   MAI->setCommentColumn(CommentColumn);
 
+  std::unique_ptr<MCVerifier> Verifier;
+  if (VerifyEnabled) {
+    std::vector<std::string> Prefixes;
+    for (StringRef Prefix : Verify)
+      if (!Prefix.empty())
+        Prefixes.push_back(Prefix.str());
+    // A bare '-verify' (no '=value'), an explicit empty prefix list, and a
+    // list that parses to only empty strings (e.g. a stray comma) all mean
+    // "use the default prefix".
+    if (Prefixes.empty())
+      Prefixes = {"expected"};
+
+    // Only text at or after a recognized comment marker is scanned for
+    // directives, since the comment character varies by target and .s
+    // source is more likely than e.g. MLIR IR to contain incidental text
+    // that happens to match a directive. Deliberately excludes '/*': the
+    // scan is line-based, so it cannot correctly recognize a directive on a
+    // continuation line of a multi-line block comment (nor correctly stop
+    // treating text as commented once a block comment closes with '*/' on
+    // the same line). Directives must use a single-line comment form.
+    std::vector<std::string> CommentPrefixes = {
+        std::string(MAI->getCommentString())};
+    if (MAI->shouldAllowAdditionalComments()) {
+      CommentPrefixes.push_back("//");
+      CommentPrefixes.push_back("#");
+    }
+
+    Verifier =
+        std::make_unique<MCVerifier>(SrcMgr, Prefixes, CommentPrefixes);
+  }
+
   // Package up features to be passed to target/subtarget
   SubtargetFeatures Features;
   std::string FeaturesStr;
@@ -496,6 +545,9 @@ int main(int argc, char **argv) {
       TheTarget->createMCObjectFileInfo(Ctx, PIC, LargeCodeModel));
   Ctx.setObjectFileInfo(MOFI.get());
 
+  if (Verifier)
+    Verifier->installHandlers(Ctx);
+
   Ctx.setGenDwarfForAssembly(GenDwarfForAssembly);
   // Default to 4 for dwarf version.
   unsigned DwarfVersion = MCOptions.DwarfVersion ? MCOptions.DwarfVersion : 4;
@@ -674,6 +726,12 @@ int main(int argc, char **argv) {
     Res = Disassembler::disassemble(*TheTarget, *STI, *Str, *Buffer, SrcMgr,
                                     Ctx, HexBytes, NumBenchmarkRuns);
 
+  // Under -verify, the exit code reflects whether the diagnostics matched
+  // what was expected, not whether assembling/disassembling itself hit an
+  // error: an expected error is a successful run.
+  if (Verifier)
+    Res = Verifier->verify() ? 0 : 1;
+
   // Keep output if no errors.
   if (Res == 0) {
     Out->keep();

>From a5fa2091e830a9de8afb75bf004956965094f57e Mon Sep 17 00:00:00 2001
From: Alex Richardson <[email protected]>
Date: Sun, 18 Oct 2026 12:40:42 -0700
Subject: [PATCH 2/3] [nspr] update

---
 llvm/docs/CommandGuide/llvm-mc.md      |  22 ++---
 llvm/docs/TestingGuide.md              |   2 +-
 llvm/test/tools/llvm-mc/verify/basic.s |  76 +++++++++++++----
 llvm/tools/llvm-mc/CMakeLists.txt      |   1 -
 llvm/tools/llvm-mc/Verifier.cpp        |  97 ---------------------
 llvm/tools/llvm-mc/Verifier.h          |  64 --------------
 llvm/tools/llvm-mc/llvm-mc.cpp         | 112 +++++++++++++------------
 7 files changed, 130 insertions(+), 244 deletions(-)
 delete mode 100644 llvm/tools/llvm-mc/Verifier.cpp
 delete mode 100644 llvm/tools/llvm-mc/Verifier.h

diff --git a/llvm/docs/CommandGuide/llvm-mc.md 
b/llvm/docs/CommandGuide/llvm-mc.md
index 7ca33b7d7ed99a..1ab1c1dbf1b128 100644
--- a/llvm/docs/CommandGuide/llvm-mc.md
+++ b/llvm/docs/CommandGuide/llvm-mc.md
@@ -166,18 +166,19 @@ comma-separated list of prefixes to recognize; a bare 
`--verify` (with no
 `=<prefixes>`) is equivalent to `--verify=expected`.
 
 A directive has the form:
-```asm
+
+```text
 <prefix>-<kind>[-re][@<offset>] {{<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, and `@above`/`@below` apply to
-the nearest non-directive line above/below. 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:
+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
+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.
@@ -194,9 +195,10 @@ example:
 A directive must appear within a comment recognized by the target being
 assembled for (its own comment string, plus `//` and `#` if the target
 allows additional comment forms); text that merely resembles a directive
-elsewhere on the line is ignored. Directives are matched line-based, so they
-are not recognized inside multi-line `/* ... */` block comments. Use a
-single-line comment form instead.
+elsewhere on the line is ignored. Directives are matched line-based (at most
+one directive per line, with nothing other than whitespace following the
+closing `}}`), so they are not recognized inside `/*` and `*/` block comments.
+Use a single-line comment form instead.
 
 Under `--verify`, the process exit code reflects whether the diagnostics
 matched what was expected, not whether assembling/disassembling itself
diff --git a/llvm/docs/TestingGuide.md b/llvm/docs/TestingGuide.md
index b344a688b17a2f..415a2276fc56d3 100644
--- a/llvm/docs/TestingGuide.md
+++ b/llvm/docs/TestingGuide.md
@@ -566,7 +566,7 @@ plain `FileCheck`, e.g. `RUN: not llvm-mc ... | FileCheck 
%s` with unanchored
 `CHECK: error:` lines. `FileCheck` only asserts that the listed diagnostics
 appear somewhere, in order; it does not notice an unrelated *extra*
 diagnostic that a later change starts emitting next to the one under test.
-For `llvm-mc` tests, prefer `--verify` (see the
+For diagnostic-only `llvm-mc` tests, consider using `--verify` (see the
 {doc}`llvm-mc <CommandGuide/llvm-mc>` documentation), which checks that every
 diagnostic produced matches an `expected-error`/`expected-warning`/
 `expected-note`/`expected-remark` comment in the input and vice versa,
diff --git a/llvm/test/tools/llvm-mc/verify/basic.s 
b/llvm/test/tools/llvm-mc/verify/basic.s
index ce4b717c91132b..eb3e6236207ce8 100644
--- a/llvm/test/tools/llvm-mc/verify/basic.s
+++ b/llvm/test/tools/llvm-mc/verify/basic.s
@@ -1,15 +1,25 @@
-# Tests for llvm-mc's -verify=<prefixes> diagnostic-verification mode.
+## Tests for llvm-mc's -verify=<prefixes> diagnostic-verification mode.
 
 # REQUIRES: riscv-registered-target
 # RUN: rm -rf %t && split-file %s %t
 
 ## Matching expected-error directives: a plain substring match, a '-re'
-## regex match (note the nested {{...}} for the regex portion itself), and
-## an '@+1' forward offset all pass.
+## regex match (note the nested {{...}} for the regex portion itself), an
+## '@+1' forward offset, and '@above'/'@below' offsets all pass.
 # RUN: llvm-mc -triple riscv32 -verify %t/match.s
 
-## A custom '-verify=<prefix>' prefix is recognized instead of 'expected'.
-# RUN: llvm-mc -triple riscv32 -verify=check %t/match-custom-prefix.s
+## Malformed '-re' regex directives (missing closing '}}' or invalid regex
+## syntax) and out-of-bounds line offsets fail verification with an error at
+## the directive.
+# RUN: not llvm-mc -triple riscv32 -verify %t/invalid-regex.s 2>&1 \
+# RUN:   | FileCheck %t/invalid-regex.s --check-prefix=INVALID-REGEX
+# RUN: not llvm-mc -triple riscv32 -verify %t/invalid-offset.s 2>&1 \
+# RUN:   | FileCheck %t/invalid-offset.s --check-prefix=INVALID-OFFSET
+
+## Custom '-verify=<prefixes>' prefixes (including a comma-separated list)
+## are recognized instead of 'expected', and prefixes that merely end with
+## '<prefix>' (such as 'other-check' or 'uncheck') are ignored.
+# RUN: llvm-mc -triple riscv32 -verify=check,alt %t/match-custom-prefix.s
 
 ## A diagnostic whose text matches an expected-* comment but whose kind
 ## doesn't (e.g. expected-warning for an actual error) is a "near miss": it
@@ -25,11 +35,8 @@
 # RUN: not llvm-mc -triple riscv32 -verify %t/missing.s 2>&1 \
 # RUN:   | FileCheck %t/missing.s --check-prefix=MISSING
 
-## An expected-error in a '.include'd file that's never produced also fails
-## verification: the included file's buffer isn't known to SourceMgr until
-## it's actually .include'd during parsing, so process() never has an actual
-## diagnostic in it to trigger scanning it. This must be caught by verify()
-## re-scanning all of SourceMgr's buffers, not just the ones process() saw.
+## An expected-error in a '.include'd file that emits no diagnostics is still
+## checked by verify().
 # RUN: not llvm-mc -triple riscv32 -verify -I %t %t/main-include.s 2>&1 \
 # RUN:   | FileCheck %t/included-missing.s --check-prefix=INCLUDE-MISSING
 
@@ -38,11 +45,7 @@
 # RUN: not llvm-mc -triple riscv32 -verify %t/outside-comment.s 2>&1 \
 # RUN:   | FileCheck %t/outside-comment.s --check-prefix=UNEXPECTED
 
-## A file with no diagnostics and no expected-* comments passes. Unlike
-## clang's -verify, there is no dedicated 'expected-no-diagnostics' marker:
-## none is needed, since if nothing is expected and nothing happens,
-## verification trivially succeeds, and any diagnostic that *does* occur is
-## still caught as unexpected (see the 'unexpected' case above).
+## A file with no diagnostics and no expected-* directives passes.
 # RUN: llvm-mc -triple riscv32 -verify %t/no-diagnostics.s
 
 ## Diagnostics reported through MCContext (e.g. by the DWARF CFI checker,
@@ -50,6 +53,10 @@
 ## checked, not just diagnostics from the assembler/parser.
 # RUN: llvm-mc -triple riscv32 -verify -validate-cfi -filetype=null %t/cfi.s
 
+## Disassembler diagnostics (e.g. invalid instruction encodings) are also
+## checked under -disassemble -verify.
+# RUN: llvm-mc -triple riscv32 -disassemble -verify %t/disassemble.txt
+
 ## -verify is rejected together with -as-lex, since -as-lex doesn't report
 ## its errors as diagnostics with a message/location at all.
 # RUN: not llvm-mc -triple riscv32 -verify -as-lex %t/no-diagnostics.s 2>&1 \
@@ -66,9 +73,43 @@
 # expected-error@+1 {{unknown directive}}
 .baz_directive
 
+.above_directive
+# expected-error@above {{unknown directive}}
+
+# expected-error@below {{unknown directive}}
+.below_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
+## reusing an already-matched one.
+sfence.vma a0, 0x10
+# expected-error@-1 {{invalid instruction, any one of the following would fix 
this:}}
+# expected-note@-2 {{r}}
+# expected-note@-3 {{register must be a GPR}}
+
+#--- invalid-regex.s
+addi a0, a0, 1
+# expected-error-re@-1 {{unclosed {{regex}}
+# expected-error-re@-2 {{invalid {{[}} regex}}
+# INVALID-REGEX: error: found start of regex with no end '}}'
+# INVALID-REGEX: error: invalid regex:
+
+#--- invalid-offset.s
+addi a0, a0, 1
+# expected-error@-99 {{before start}}
+# expected-error@+99 {{past end}}
+# INVALID-OFFSET: error: line offset '-99' is before the start of the file
+# INVALID-OFFSET: error: line offset '+99' is past the end of the file
+
 #--- match-custom-prefix.s
 .foo_directive
 # check-error@-1 {{unknown directive}}
+# other-check-error@-2 {{not a match for -verify=check,alt}}
+# uncheck-error@-3 {{not a match for -verify=check,alt}}
+
+.bar_directive
+# alt-error@-1 {{unknown directive}}
 
 #--- near-miss.s
 .foo_directive
@@ -106,7 +147,10 @@ addi a0, a0, 1
        .type   f,@function
 f:
        .cfi_startproc
-       .cfi_same_value ra
+       .cfi_same_value a0
        li a0, 10
        # expected-error@-1 {{changed register X10, that register X10's 
unwinding rule uses, but there is no CFI directives about it}}
        .cfi_endproc
+
+#--- disassemble.txt
+0x00 0x00 0x00 0x00 # expected-warning {{invalid instruction encoding}}
diff --git a/llvm/tools/llvm-mc/CMakeLists.txt 
b/llvm/tools/llvm-mc/CMakeLists.txt
index bf7f217bc13c15..67ac890966fa67 100644
--- a/llvm/tools/llvm-mc/CMakeLists.txt
+++ b/llvm/tools/llvm-mc/CMakeLists.txt
@@ -13,6 +13,5 @@ set(LLVM_LINK_COMPONENTS
 add_llvm_tool(llvm-mc
   llvm-mc.cpp
   Disassembler.cpp
-  Verifier.cpp
   )
 
diff --git a/llvm/tools/llvm-mc/Verifier.cpp b/llvm/tools/llvm-mc/Verifier.cpp
deleted file mode 100644
index 19de8d7f785cd3..00000000000000
--- a/llvm/tools/llvm-mc/Verifier.cpp
+++ /dev/null
@@ -1,97 +0,0 @@
-//===- Verifier.cpp - Diagnostic Verifier for llvm-mc 
---------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-#include "Verifier.h"
-#include "llvm/MC/MCContext.h"
-#include "llvm/Support/SourceMgr.h"
-#include "llvm/Support/raw_ostream.h"
-
-using namespace llvm;
-
-namespace {
-/// SourceMgr::PrintMessage unconditionally re-invokes SrcMgr's installed
-/// DiagHandler if one is set, rather than only doing so for diagnostics that
-/// didn't originate from the handler itself. Since MCVerifier is installed as
-/// that handler, and its own printing (near-miss / not-produced messages)
-/// goes through SourceMgr::PrintMessage, calling into it from inside the
-/// handler would otherwise re-enter the handler and recurse indefinitely.
-/// This temporarily clears the handler for the duration of the guard so that
-/// PrintMessage prints directly instead.
-class ScopedHandlerDisable {
-  SourceMgr &SrcMgr;
-  SourceMgr::DiagHandlerTy SavedHandler;
-  void *SavedContext;
-
-public:
-  explicit ScopedHandlerDisable(SourceMgr &SrcMgr)
-      : SrcMgr(SrcMgr), SavedHandler(SrcMgr.getDiagHandler()),
-        SavedContext(SrcMgr.getDiagContext()) {
-    SrcMgr.setDiagHandler(nullptr, nullptr);
-  }
-  ~ScopedHandlerDisable() { SrcMgr.setDiagHandler(SavedHandler, SavedContext); 
}
-};
-} // namespace
-
-MCVerifier::MCVerifier(SourceMgr &SrcMgr, ArrayRef<std::string> Prefixes,
-                       ArrayRef<std::string> CommentPrefixes)
-    : SrcMgr(SrcMgr), Verifier(Prefixes, CommentPrefixes) {
-  // Scan every buffer already known to SrcMgr up front, so that an expected
-  // diagnostic that's never actually produced (e.g. because the run never
-  // reports any diagnostic at all) is still recorded and reported as missing
-  // by verify(), rather than never being noticed since process() was never
-  // called for that buffer. Guarded like every other call into Verifier in
-  // this file: a malformed directive (e.g. an invalid '-re' regex) makes
-  // computeExpectedDiags print through SrcMgr, which would otherwise re-enter
-  // installHandlers's handler if the constructor ever ran after it (it
-  // currently never does, at the sole call site in llvm-mc.cpp).
-  ScopedHandlerDisable Guard(SrcMgr);
-  for (unsigned I = 0, E = SrcMgr.getNumBuffers(); I != E; ++I)
-    (void)Verifier.computeExpectedDiags(errs(), SrcMgr,
-                                        SrcMgr.getMemoryBuffer(I + 1));
-}
-
-MCVerifier::~MCVerifier() {
-  // SrcMgr may outlive this MCVerifier; don't leave its handler pointing at
-  // freed memory.
-  if (SrcMgr.getDiagHandler() == &MCVerifier::handleSourceMgrDiag)
-    SrcMgr.setDiagHandler(nullptr, nullptr);
-}
-
-void MCVerifier::installHandlers(MCContext &Ctx) {
-  SrcMgr.setDiagHandler(&MCVerifier::handleSourceMgrDiag, this);
-  Ctx.setDiagnosticHandler(
-      [this](const SMDiagnostic &Diag, bool /*InlineAsm*/,
-             const SourceMgr & /*SrcMgr*/,
-             std::vector<const MDNode *> & /*LocInfos*/) { process(Diag); });
-}
-
-bool MCVerifier::verify() {
-  ScopedHandlerDisable Guard(SrcMgr);
-  return Verifier.verify(errs(), SrcMgr);
-}
-
-void MCVerifier::handleSourceMgrDiag(const SMDiagnostic &Diag, void *Context) {
-  static_cast<MCVerifier *>(Context)->process(Diag);
-}
-
-void MCVerifier::process(const SMDiagnostic &Diag) {
-  ScopedHandlerDisable Guard(SrcMgr);
-
-  bool HasLoc = Diag.getLoc().isValid();
-  const MemoryBuffer *Buf = nullptr;
-  if (HasLoc) {
-    if (unsigned ID = SrcMgr.FindBufferContainingLoc(Diag.getLoc()))
-      Buf = SrcMgr.getMemoryBuffer(ID);
-  }
-
-  auto Result = Verifier.process(
-      errs(), SrcMgr, Diag.getKind(), HasLoc, Buf,
-      HasLoc ? static_cast<unsigned>(Diag.getLineNo()) : 0, Diag.getMessage());
-  if (Result == SourceMgrDiagnosticVerifier::MatchResult::Unexpected)
-    SrcMgr.PrintMessage(errs(), Diag);
-}
diff --git a/llvm/tools/llvm-mc/Verifier.h b/llvm/tools/llvm-mc/Verifier.h
deleted file mode 100644
index d567141ef0a0c1..00000000000000
--- a/llvm/tools/llvm-mc/Verifier.h
+++ /dev/null
@@ -1,64 +0,0 @@
-//===- Verifier.h - Diagnostic Verifier for llvm-mc -----------*- C++ -*-===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-//
-// This implements '-verify=<prefixes>', a mode that checks emitted
-// diagnostics against '<prefix>-error'/'-warning'/'-note'/'-remark' comments
-// in the input, similar to clang's '-verify' flag.
-//
-//===----------------------------------------------------------------------===//
-
-#ifndef LLVM_TOOLS_LLVM_MC_VERIFIER_H
-#define LLVM_TOOLS_LLVM_MC_VERIFIER_H
-
-#include "llvm/ADT/ArrayRef.h"
-#include "llvm/Support/SourceMgrDiagnosticVerifier.h"
-#include <string>
-
-namespace llvm {
-
-class MCContext;
-class SourceMgr;
-class SMDiagnostic;
-
-/// Checks the diagnostics produced while assembling/disassembling against
-/// '<prefix>-<kind>' comments in the input.
-class MCVerifier {
-public:
-  /// \p Prefixes are the accepted directive prefixes (from '-verify=...').
-  /// \p CommentPrefixes are the comment-start strings recognized by the
-  /// current target, so a directive outside of a comment is ignored.
-  MCVerifier(SourceMgr &SrcMgr, ArrayRef<std::string> Prefixes,
-             ArrayRef<std::string> CommentPrefixes);
-
-  /// Clears SrcMgr's diagnostic handler if it's still this object's, so a
-  /// SourceMgr that outlives this MCVerifier is never left with a dangling
-  /// handler pointing at freed memory.
-  ~MCVerifier();
-
-  /// Installs this as the diagnostic handler for both the SourceMgr passed to
-  /// the constructor and \p Ctx. Both are needed: most parser/disassembler
-  /// diagnostics go through SourceMgr, but some (e.g. the DWARF CFI checker's,
-  /// via MCContext::reportError/reportWarning) are reported directly through
-  /// MCContext and never reach SourceMgr's handler at all.
-  void installHandlers(MCContext &Ctx);
-
-  /// Reports any expected diagnostic that was never produced, and returns
-  /// whether verification succeeded overall.
-  bool verify();
-
-private:
-  static void handleSourceMgrDiag(const SMDiagnostic &Diag, void *Context);
-  void process(const SMDiagnostic &Diag);
-
-  SourceMgr &SrcMgr;
-  SourceMgrDiagnosticVerifier Verifier;
-};
-
-} // namespace llvm
-
-#endif // LLVM_TOOLS_LLVM_MC_VERIFIER_H
diff --git a/llvm/tools/llvm-mc/llvm-mc.cpp b/llvm/tools/llvm-mc/llvm-mc.cpp
index 0d9ded02cd1596..5fef851e0dd9c6 100644
--- a/llvm/tools/llvm-mc/llvm-mc.cpp
+++ b/llvm/tools/llvm-mc/llvm-mc.cpp
@@ -12,7 +12,6 @@
 
//===----------------------------------------------------------------------===//
 
 #include "Disassembler.h"
-#include "Verifier.h"
 #include "llvm/ADT/ScopeExit.h"
 #include "llvm/DWARFCFIChecker/DWARFCFIFunctionFrameAnalyzer.h"
 #include "llvm/DWARFCFIChecker/DWARFCFIFunctionFrameStreamer.h"
@@ -39,6 +38,7 @@
 #include "llvm/Support/InitLLVM.h"
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/SourceMgr.h"
+#include "llvm/Support/SourceMgrDiagnosticVerifier.h"
 #include "llvm/Support/TargetSelect.h"
 #include "llvm/Support/TimeProfiler.h"
 #include "llvm/Support/ToolOutputFile.h"
@@ -122,11 +122,7 @@ static cl::opt<unsigned> CommentColumn("comment-column",
                                        cl::desc("Asm comments indentation"),
                                        cl::init(40));
 
-enum OutputFileType {
-  OFT_Null,
-  OFT_AssemblyFile,
-  OFT_ObjectFile
-};
+enum OutputFileType { OFT_Null, OFT_AssemblyFile, OFT_ObjectFile };
 static cl::opt<OutputFileType>
     FileType("filetype", cl::init(OFT_AssemblyFile),
              cl::desc("Choose an output file type:"),
@@ -226,11 +222,8 @@ static cl::opt<bool> ValidateCFI("validate-cfi",
 
 static cl::list<std::string> Verify(
     "verify", cl::CommaSeparated, cl::ValueOptional,
-    cl::desc(
-        "Check the diagnostics produced against '<prefix>-error'/"
-        "'-warning'/'-note'/'-remark' comments in the input instead of just "
-        "printing them, using a comma-separated list of <prefixes> "
-        "(default: 'expected') as the recognized directive prefixes"),
+    cl::desc("Verify emitted diagnostics against '<prefix>-<kind>' comments in 
"
+             "the input (default prefix: 'expected')"),
     cl::value_desc("prefixes"), cl::cat(MCCategory));
 
 enum ActionType {
@@ -279,8 +272,8 @@ static const Target *GetTarget(const char *ProgName) {
 
   // Get the target specific parser.
   std::string Error;
-  const Target *TheTarget = TargetRegistry::lookupTarget(ArchName, TheTriple,
-                                                         Error);
+  const Target *TheTarget =
+      TargetRegistry::lookupTarget(ArchName, TheTriple, Error);
   if (!TheTarget) {
     WithColor::error(errs(), ProgName) << Error;
     return nullptr;
@@ -291,8 +284,8 @@ static const Target *GetTarget(const char *ProgName) {
   return TheTarget;
 }
 
-static std::unique_ptr<ToolOutputFile> GetOutputStream(StringRef Path,
-    sys::fs::OpenFlags Flags) {
+static std::unique_ptr<ToolOutputFile>
+GetOutputStream(StringRef Path, sys::fs::OpenFlags Flags) {
   std::error_code EC;
   auto Out = std::make_unique<ToolOutputFile>(Path, EC, Flags);
   if (EC) {
@@ -316,13 +309,12 @@ static void setDwarfDebugFlags(int argc, char **argv) {
 
 static std::string DwarfDebugProducer;
 static void setDwarfDebugProducer() {
-  if(!getenv("DEBUG_PRODUCER"))
+  if (!getenv("DEBUG_PRODUCER"))
     return;
   DwarfDebugProducer += getenv("DEBUG_PRODUCER");
 }
 
-static int AsLexInput(SourceMgr &SrcMgr, MCAsmInfo &MAI,
-                      raw_ostream &OS) {
+static int AsLexInput(SourceMgr &SrcMgr, MCAsmInfo &MAI, raw_ostream &OS) {
 
   AsmLexer Lexer(MAI);
   Lexer.setBuffer(SrcMgr.getMemoryBuffer(SrcMgr.getMainFileID())->getBuffer());
@@ -339,7 +331,7 @@ static int AsLexInput(SourceMgr &SrcMgr, MCAsmInfo &MAI,
 }
 
 static int fillCommandLineSymbols(MCAsmParser &Parser) {
-  for (auto &I: DefineSymbol) {
+  for (auto &I : DefineSymbol) {
     auto Pair = StringRef(I).split('=');
     auto Sym = Pair.first;
     auto Val = Pair.second;
@@ -363,8 +355,7 @@ static int AssembleInput(const char *ProgName, const Target 
*TheTarget,
                          SourceMgr &SrcMgr, MCContext &Ctx, MCStreamer &Str,
                          MCAsmInfo &MAI, MCSubtargetInfo &STI,
                          MCInstrInfo &MCII, MCTargetOptions const &MCOptions) {
-  std::unique_ptr<MCAsmParser> Parser(
-      createMCAsmParser(SrcMgr, Ctx, Str, MAI));
+  std::unique_ptr<MCAsmParser> Parser(createMCAsmParser(SrcMgr, Ctx, Str, 
MAI));
   std::unique_ptr<MCTargetAsmParser> TAP(
       TheTarget->createMCAsmParser(STI, *Parser, MCII));
 
@@ -375,7 +366,7 @@ static int AssembleInput(const char *ProgName, const Target 
*TheTarget,
   }
 
   int SymbolResult = fillCommandLineSymbols(*Parser);
-  if(SymbolResult)
+  if (SymbolResult)
     return SymbolResult;
   Parser->setShowParsedOperands(ShowInstOperands);
   Parser->setTargetParser(*TAP);
@@ -482,37 +473,6 @@ int main(int argc, char **argv) {
   MAI->setPreserveAsmComments(PreserveComments);
   MAI->setCommentColumn(CommentColumn);
 
-  std::unique_ptr<MCVerifier> Verifier;
-  if (VerifyEnabled) {
-    std::vector<std::string> Prefixes;
-    for (StringRef Prefix : Verify)
-      if (!Prefix.empty())
-        Prefixes.push_back(Prefix.str());
-    // A bare '-verify' (no '=value'), an explicit empty prefix list, and a
-    // list that parses to only empty strings (e.g. a stray comma) all mean
-    // "use the default prefix".
-    if (Prefixes.empty())
-      Prefixes = {"expected"};
-
-    // Only text at or after a recognized comment marker is scanned for
-    // directives, since the comment character varies by target and .s
-    // source is more likely than e.g. MLIR IR to contain incidental text
-    // that happens to match a directive. Deliberately excludes '/*': the
-    // scan is line-based, so it cannot correctly recognize a directive on a
-    // continuation line of a multi-line block comment (nor correctly stop
-    // treating text as commented once a block comment closes with '*/' on
-    // the same line). Directives must use a single-line comment form.
-    std::vector<std::string> CommentPrefixes = {
-        std::string(MAI->getCommentString())};
-    if (MAI->shouldAllowAdditionalComments()) {
-      CommentPrefixes.push_back("//");
-      CommentPrefixes.push_back("#");
-    }
-
-    Verifier =
-        std::make_unique<MCVerifier>(SrcMgr, Prefixes, CommentPrefixes);
-  }
-
   // Package up features to be passed to target/subtarget
   SubtargetFeatures Features;
   std::string FeaturesStr;
@@ -545,8 +505,50 @@ int main(int argc, char **argv) {
       TheTarget->createMCObjectFileInfo(Ctx, PIC, LargeCodeModel));
   Ctx.setObjectFileInfo(MOFI.get());
 
-  if (Verifier)
-    Verifier->installHandlers(Ctx);
+  std::optional<SourceMgrDiagnosticVerifier> Verifier;
+  if (VerifyEnabled) {
+    std::vector<std::string> Prefixes;
+    for (StringRef Prefix : Verify)
+      if (!Prefix.empty())
+        Prefixes.push_back(Prefix.str());
+    // A bare '-verify' (no '=value'), an explicit empty prefix list, and a
+    // list that parses to only empty strings (e.g. a stray comma) all mean
+    // "use the default prefix".
+    if (Prefixes.empty())
+      Prefixes = {"expected"};
+
+    // Scope directive scanning to single-line comments recognized by the
+    // target (plus '//' and '#' when allowed) so directive-like text in
+    // operands or string literals is ignored.
+    std::vector<std::string> CommentPrefixes;
+    auto AddCommentPrefix = [&](StringRef Prefix) {
+      if (!Prefix.empty() && !llvm::is_contained(CommentPrefixes, Prefix))
+        CommentPrefixes.push_back(Prefix.str());
+    };
+    StringRef TargetComment = MAI->getCommentString();
+    // AsmLexer::isAtStartOfComment also accepts a single '#' when
+    // CommentString is "##" (X86 Darwin), which subsumes "##".
+    if (TargetComment == "##")
+      TargetComment = "#";
+    AddCommentPrefix(TargetComment);
+    if (MAI->shouldAllowAdditionalComments()) {
+      AddCommentPrefix("//");
+      AddCommentPrefix("#");
+    }
+
+    Verifier.emplace(SrcMgr, errs(), Prefixes, CommentPrefixes);
+    // Install on both Ctx and SrcMgr: most parser/disassembler diagnostics go
+    // through SrcMgr, while some (e.g. DWARF CFI checker diagnostics via
+    // MCContext::reportError/reportWarning) go directly through Ctx.
+    Ctx.setDiagnosticHandler(
+        [&](const SMDiagnostic &Diag, bool, const SourceMgr &,
+            std::vector<const MDNode *> &) { Verifier->process(Diag); });
+    SrcMgr.setDiagHandler(
+        [](const SMDiagnostic &Diag, void *Ctx) {
+          static_cast<MCContext *>(Ctx)->diagnose(Diag);
+        },
+        &Ctx);
+  }
 
   Ctx.setGenDwarfForAssembly(GenDwarfForAssembly);
   // Default to 4 for dwarf version.

>From 45d3e0ab746aade6ddad626c93310d31c844ec5b Mon Sep 17 00:00:00 2001
From: Alex Richardson <[email protected]>
Date: Sun, 18 Oct 2026 15:09:09 -0700
Subject: [PATCH 3/3] [nspr] update

---
 llvm/docs/CommandGuide/llvm-mc.md             |  56 ++---
 llvm/docs/TestingGuide.md                     |  11 -
 llvm/test/tools/llvm-mc/verify/basic.s        | 195 ++++++++----------
 .../tools/llvm-mc/verify/disassemble-zos.txt  |   6 +
 llvm/tools/llvm-mc/llvm-mc.cpp                | 132 ++++++------
 5 files changed, 181 insertions(+), 219 deletions(-)
 create mode 100644 llvm/test/tools/llvm-mc/verify/disassemble-zos.txt

diff --git a/llvm/docs/CommandGuide/llvm-mc.md 
b/llvm/docs/CommandGuide/llvm-mc.md
index 1ab1c1dbf1b128..f49861c88173f3 100644
--- a/llvm/docs/CommandGuide/llvm-mc.md
+++ b/llvm/docs/CommandGuide/llvm-mc.md
@@ -157,55 +157,31 @@ prints in Intel/MASM syntax.
 Choose DWARF debug sections compression.
 :::
 
-:::{option} --verify=<prefixes>
-
-Instead of just printing diagnostics, check them against `<prefix>-error`,
-`<prefix>-warning`, `<prefix>-note` and `<prefix>-remark` comments in
-the input, similar to Clang's `-verify` flag. `<prefixes>` is a
-comma-separated list of prefixes to recognize; a bare `--verify` (with no
-`=<prefixes>`) is equivalent to `--verify=expected`.
-
-A directive has the form:
+:::{option} --verify[=<prefixes>]
+Check diagnostics against directives in comments of the input instead of
+printing them, similar to Clang's `-verify`. `<prefixes>` is a comma-separated
+list of directive prefixes (default: `expected`). A directive has the form
 
 ```text
 <prefix>-<kind>[-re][@<offset>] {{<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
-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:
+where `<kind>` is `error`, `warning`, `note` or `remark`, and `<text>` must be
+a substring of the diagnostic message (with `-re`, `{{...}}` blocks inside
+`<text>` are regular expressions). Without `@<offset>`, the directive applies
+to its own line; `@+N` and `@-N` refer to the line `N` lines below or above,
+`@above` and `@below` to the nearest non-directive line, and `@unknown` to
+diagnostics without a location. For example:
 
 ```asm
-## Matches only diagnostics containing this exact text.
-.foo
-# expected-error@-1 {{unknown directive}}
-
-## Matches any diagnostic starting with "unknown" and ending with
-## "directive"; only the nested {{.*}} is a regex, the surrounding text
-## is literal.
-.foo
-# expected-error-re@-1 {{unknown {{.*}} directive}}
+.foo # expected-error {{unknown directive}}
 ```
 
-A directive must appear within a comment recognized by the target being
-assembled for (its own comment string, plus `//` and `#` if the target
-allows additional comment forms); text that merely resembles a directive
-elsewhere on the line is ignored. Directives are matched line-based (at most
-one directive per line, with nothing other than whitespace following the
-closing `}}`), so they are not recognized inside `/*` and `*/` block comments.
-Use a single-line comment form instead.
-
-Under `--verify`, the process exit code reflects whether the diagnostics
-matched what was expected, not whether assembling/disassembling itself
-produced an error: a fully expected error is a successful (exit code 0) run.
-`--verify` is not supported together with `--as-lex`, since `--as-lex`
-does not report its errors as diagnostics with a message or location.
-
+Directives are only recognized in single-line comments, at most one per line.
+The exit status is 0 if every diagnostic matches a directive and every
+directive matches at least one diagnostic. Unlike Clang, directives take no
+count and match any number of identical diagnostics on their line, and
+`expected-no-diagnostics` is not needed.
 :::
 
 ## EXIT STATUS
diff --git a/llvm/docs/TestingGuide.md b/llvm/docs/TestingGuide.md
index 415a2276fc56d3..77803c4443da26 100644
--- a/llvm/docs/TestingGuide.md
+++ b/llvm/docs/TestingGuide.md
@@ -561,17 +561,6 @@ This test will fail if placed into a `download` directory.
 To make your tests robust, always use `opt ... < %s` in the `RUN` line.
 {program}`opt` does not output a `ModuleID` when input comes from stdin.
 
-Another common source of fragile tests is checking assembler diagnostics with
-plain `FileCheck`, e.g. `RUN: not llvm-mc ... | FileCheck %s` with unanchored
-`CHECK: error:` lines. `FileCheck` only asserts that the listed diagnostics
-appear somewhere, in order; it does not notice an unrelated *extra*
-diagnostic that a later change starts emitting next to the one under test.
-For diagnostic-only `llvm-mc` tests, consider using `--verify` (see the
-{doc}`llvm-mc <CommandGuide/llvm-mc>` documentation), which checks that every
-diagnostic produced matches an `expected-error`/`expected-warning`/
-`expected-note`/`expected-remark` comment in the input and vice versa,
-similar to Clang's `-verify` flag.
-
 ### Platform-Specific Tests
 
 Whenever adding tests that require the knowledge of a specific platform,
diff --git a/llvm/test/tools/llvm-mc/verify/basic.s 
b/llvm/test/tools/llvm-mc/verify/basic.s
index eb3e6236207ce8..cd5ee445c45e11 100644
--- a/llvm/test/tools/llvm-mc/verify/basic.s
+++ b/llvm/test/tools/llvm-mc/verify/basic.s
@@ -1,67 +1,62 @@
-## Tests for llvm-mc's -verify=<prefixes> diagnostic-verification mode.
+## Tests for llvm-mc's --verify diagnostic-verification mode.
 
 # REQUIRES: riscv-registered-target
-# RUN: rm -rf %t && split-file %s %t
-
-## 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.
-# RUN: llvm-mc -triple riscv32 -verify %t/match.s
-
-## Malformed '-re' regex directives (missing closing '}}' or invalid regex
-## syntax) and out-of-bounds line offsets fail verification with an error at
-## the directive.
-# RUN: not llvm-mc -triple riscv32 -verify %t/invalid-regex.s 2>&1 \
-# RUN:   | FileCheck %t/invalid-regex.s --check-prefix=INVALID-REGEX
-# RUN: not llvm-mc -triple riscv32 -verify %t/invalid-offset.s 2>&1 \
-# RUN:   | FileCheck %t/invalid-offset.s --check-prefix=INVALID-OFFSET
-
-## Custom '-verify=<prefixes>' prefixes (including a comma-separated list)
-## are recognized instead of 'expected', and prefixes that merely end with
-## '<prefix>' (such as 'other-check' or 'uncheck') are ignored.
-# RUN: llvm-mc -triple riscv32 -verify=check,alt %t/match-custom-prefix.s
-
-## A diagnostic whose text matches an expected-* comment but whose kind
-## doesn't (e.g. expected-warning for an actual error) is a "near miss": it
-## still counts as not produced, since it wasn't a full match.
-# RUN: not llvm-mc -triple riscv32 -verify %t/near-miss.s 2>&1 \
-# RUN:   | FileCheck %t/near-miss.s --check-prefix=NEAR-MISS
-
-## A diagnostic with no matching expected-* comment fails verification.
-# RUN: not llvm-mc -triple riscv32 -verify %t/unexpected.s 2>&1 \
-# RUN:   | FileCheck %t/unexpected.s --check-prefix=UNEXPECTED
-
-## An expected-error that is never produced fails verification.
-# RUN: not llvm-mc -triple riscv32 -verify %t/missing.s 2>&1 \
-# RUN:   | FileCheck %t/missing.s --check-prefix=MISSING
-
-## An expected-error in a '.include'd file that emits no diagnostics is still
-## checked by verify().
-# RUN: not llvm-mc -triple riscv32 -verify -I %t %t/main-include.s 2>&1 \
-# RUN:   | FileCheck %t/included-missing.s --check-prefix=INCLUDE-MISSING
-
-## A directive-looking magic string outside of a comment is not treated as a
-## directive, so the resulting diagnostic is still unexpected.
-# RUN: not llvm-mc -triple riscv32 -verify %t/outside-comment.s 2>&1 \
-# RUN:   | FileCheck %t/outside-comment.s --check-prefix=UNEXPECTED
-
-## A file with no diagnostics and no expected-* directives passes.
-# RUN: llvm-mc -triple riscv32 -verify %t/no-diagnostics.s
-
-## Diagnostics reported through MCContext (e.g. by the DWARF CFI checker,
-## which doesn't go through SourceMgr's diagnostic handler) are also
-## checked, not just diagnostics from the assembler/parser.
-# RUN: llvm-mc -triple riscv32 -verify -validate-cfi -filetype=null %t/cfi.s
-
-## Disassembler diagnostics (e.g. invalid instruction encodings) are also
-## checked under -disassemble -verify.
-# RUN: llvm-mc -triple riscv32 -disassemble -verify %t/disassemble.txt
-
-## -verify is rejected together with -as-lex, since -as-lex doesn't report
-## its errors as diagnostics with a message/location at all.
-# RUN: not llvm-mc -triple riscv32 -verify -as-lex %t/no-diagnostics.s 2>&1 \
-# RUN:   | FileCheck %s --check-prefix=AS-LEX-ERR
-# AS-LEX-ERR: -verify is not supported with -as-lex
+# RUN: rm -rf %t && split-file %s %t && cd %t
+
+## Matching directives: a substring match, a '-re' regex match, '@+N' and
+## '@-N' offsets, '@above'/'@below', and directives in '#' and '//' comments.
+# RUN: llvm-mc --triple=riscv32 --verify match.s
+
+## Custom prefixes are recognized instead of 'expected', but not as the suffix
+## of a longer word such as 'uncheck'.
+# RUN: llvm-mc --triple=riscv32 --verify=check,alt custom-prefix.s
+
+## Malformed '-re' directives fail verification.
+# RUN: not llvm-mc --triple=riscv32 --verify invalid-regex.s 2>&1 \
+# RUN:   | FileCheck invalid-regex.s
+
+## A diagnostic that matches the text but not the kind of a directive.
+# RUN: not llvm-mc --triple=riscv32 --verify near-miss.s 2>&1 \
+# RUN:   | FileCheck near-miss.s
+
+## Diagnostics without a matching directive fail verification, including when
+## the directive-like text is outside of a comment.
+# RUN: not llvm-mc --triple=riscv32 --verify unexpected.s 2>&1 \
+# RUN:   | FileCheck unexpected.s
+# RUN: not llvm-mc --triple=riscv32 --verify outside-comment.s 2>&1 \
+# RUN:   | FileCheck outside-comment.s
+
+## A directive that is never matched fails verification.
+# RUN: not llvm-mc --triple=riscv32 --verify missing.s 2>&1 \
+# RUN:   | FileCheck missing.s
+
+## A file without diagnostics or directives passes.
+# RUN: llvm-mc --triple=riscv32 --verify no-diagnostics.s
+
+## Diagnostics reported through MCContext rather than SourceMgr (e.g. by the
+## CFI checker) are checked too.
+# RUN: llvm-mc --triple=riscv32 --verify --validate-cfi --filetype=null cfi.s
+
+## Disassembler diagnostics are checked too.
+# RUN: llvm-mc --triple=riscv32 --disassemble --verify disassemble.txt
+
+## Output files are not kept if assembling failed, even if all errors were
+## expected.
+# RUN: llvm-mc --triple=riscv32 --verify --filetype=obj -o match.o match.s
+# RUN: not ls match.o
+
+## Errors that are not reported as diagnostics cannot be expected.
+# RUN: not llvm-mc --triple=riscv32 --verify --defsym=bad no-diagnostics.s 
2>&1 \
+# RUN:   | FileCheck %s --check-prefix=DEFSYM
+# DEFSYM: error: defsym must be of the form: sym=value: bad
+
+# RUN: not llvm-mc --triple=riscv32 --verify=1x no-diagnostics.s 2>&1 \
+# RUN:   | FileCheck %s --check-prefix=BAD-PREFIX
+# BAD-PREFIX: error: invalid prefix '1x' for --verify: prefixes must start 
with a letter and contain only alphanumeric characters, hyphens and underscores
+
+# RUN: not llvm-mc --triple=riscv32 --verify --as-lex no-diagnostics.s 2>&1 \
+# RUN:   | FileCheck %s --check-prefix=AS-LEX
+# AS-LEX: error: --verify is not supported with --as-lex
 
 #--- match.s
 .foo_directive
@@ -79,78 +74,62 @@
 # expected-error@below {{unknown directive}}
 .below_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
-## reusing an already-matched one.
+.qux_directive # expected-error {{unknown directive}}
+.quux_directive // expected-error {{unknown directive}}
+
 sfence.vma a0, 0x10
 # expected-error@-1 {{invalid instruction, any one of the following would fix 
this:}}
-# expected-note@-2 {{r}}
+# expected-note@-2 {{unexpected extra operand for instruction}}
 # expected-note@-3 {{register must be a GPR}}
 
-#--- invalid-regex.s
-addi a0, a0, 1
-# expected-error-re@-1 {{unclosed {{regex}}
-# expected-error-re@-2 {{invalid {{[}} regex}}
-# INVALID-REGEX: error: found start of regex with no end '}}'
-# INVALID-REGEX: error: invalid regex:
-
-#--- invalid-offset.s
-addi a0, a0, 1
-# expected-error@-99 {{before start}}
-# expected-error@+99 {{past end}}
-# INVALID-OFFSET: error: line offset '-99' is before the start of the file
-# INVALID-OFFSET: error: line offset '+99' is past the end of the file
-
-#--- match-custom-prefix.s
+#--- custom-prefix.s
 .foo_directive
 # check-error@-1 {{unknown directive}}
-# other-check-error@-2 {{not a match for -verify=check,alt}}
-# uncheck-error@-3 {{not a match for -verify=check,alt}}
+# uncheck-error@-2 {{not a match for --verify=check,alt}}
+# expected-error@-3 {{not a match for --verify=check,alt}}
 
 .bar_directive
 # alt-error@-1 {{unknown directive}}
 
+#--- invalid-regex.s
+addi a0, a0, 1
+# expected-error-re@-1 {{unclosed {{regex}}
+# expected-error-re@-2 {{invalid {{[}} regex}}
+# CHECK: error: found start of regex with no end '}}'
+# CHECK: error: invalid regex:
+
 #--- near-miss.s
 .foo_directive
 # expected-warning@-1 {{unknown directive}}
-# NEAR-MISS: 'error' diagnostic emitted when expecting a 'warning'
-# NEAR-MISS: expected warning "unknown directive" was not produced
+# CHECK: error: 'error' diagnostic emitted when expecting a 'warning'
+# CHECK: error: expected warning "unknown directive" was not produced
 
 #--- unexpected.s
 .foo_directive
-# UNEXPECTED: error: unknown directive
+# CHECK: error: unknown directive
 
-#--- missing.s
-addi a0, a0, 1
-# expected-error@-1 {{this will never happen}}
-# MISSING: expected error "this will never happen" was not produced
-
-#--- main-include.s
-.include "included-missing.s"
+#--- outside-comment.s
+.foo_directive expected-error {{unknown directive}}
+# CHECK: error: unknown directive
 
-#--- included-missing.s
+#--- missing.s
 addi a0, a0, 1
 # expected-error@-1 {{this will never happen}}
-# INCLUDE-MISSING: expected error "this will never happen" was not produced
-
-#--- outside-comment.s
-.foo_directive  expected-error {{unknown directive}}
-# UNEXPECTED: error: unknown directive
+# CHECK: error: expected error "this will never happen" was not produced
 
 #--- no-diagnostics.s
 addi a0, a0, 1
 
 #--- cfi.s
-       .text
-       .globl  f
-       .type   f,@function
+  .text
+  .globl f
+  .type f,@function
 f:
-       .cfi_startproc
-       .cfi_same_value a0
-       li a0, 10
-       # expected-error@-1 {{changed register X10, that register X10's 
unwinding rule uses, but there is no CFI directives about it}}
-       .cfi_endproc
+  .cfi_startproc
+  .cfi_same_value a0
+  li a0, 10
+  # expected-error@-1 {{changed register X10, that register X10's unwinding 
rule uses, but there is no CFI directives about it}}
+  .cfi_endproc
 
 #--- disassemble.txt
 0x00 0x00 0x00 0x00 # expected-warning {{invalid instruction encoding}}
diff --git a/llvm/test/tools/llvm-mc/verify/disassemble-zos.txt 
b/llvm/test/tools/llvm-mc/verify/disassemble-zos.txt
new file mode 100644
index 00000000000000..4d0ffc9bda760f
--- /dev/null
+++ b/llvm/test/tools/llvm-mc/verify/disassemble-zos.txt
@@ -0,0 +1,6 @@
+# REQUIRES: systemz-registered-target
+## z/OS assembly uses '*' comments, but llvm-mc's disassembler input always
+## uses '#' comments, so directives are recognized there.
+# RUN: llvm-mc --triple=s390x-ibm-zos --disassemble --verify %s
+
+0x00 0x00 # expected-warning {{invalid instruction encoding}}
diff --git a/llvm/tools/llvm-mc/llvm-mc.cpp b/llvm/tools/llvm-mc/llvm-mc.cpp
index 5fef851e0dd9c6..1a8fbf65be4203 100644
--- a/llvm/tools/llvm-mc/llvm-mc.cpp
+++ b/llvm/tools/llvm-mc/llvm-mc.cpp
@@ -13,6 +13,7 @@
 
 #include "Disassembler.h"
 #include "llvm/ADT/ScopeExit.h"
+#include "llvm/ADT/StringExtras.h"
 #include "llvm/DWARFCFIChecker/DWARFCFIFunctionFrameAnalyzer.h"
 #include "llvm/DWARFCFIChecker/DWARFCFIFunctionFrameStreamer.h"
 #include "llvm/MC/MCAsmBackend.h"
@@ -46,6 +47,7 @@
 #include "llvm/Support/WithColor.h"
 #include "llvm/TargetParser/Host.h"
 #include <memory>
+#include <optional>
 
 using namespace llvm;
 
@@ -122,7 +124,11 @@ static cl::opt<unsigned> CommentColumn("comment-column",
                                        cl::desc("Asm comments indentation"),
                                        cl::init(40));
 
-enum OutputFileType { OFT_Null, OFT_AssemblyFile, OFT_ObjectFile };
+enum OutputFileType {
+  OFT_Null,
+  OFT_AssemblyFile,
+  OFT_ObjectFile
+};
 static cl::opt<OutputFileType>
     FileType("filetype", cl::init(OFT_AssemblyFile),
              cl::desc("Choose an output file type:"),
@@ -220,11 +226,11 @@ static cl::opt<bool> ValidateCFI("validate-cfi",
                                  cl::desc("Validate the CFI directives"),
                                  cl::cat(MCCategory));
 
-static cl::list<std::string> Verify(
-    "verify", cl::CommaSeparated, cl::ValueOptional,
-    cl::desc("Verify emitted diagnostics against '<prefix>-<kind>' comments in 
"
-             "the input (default prefix: 'expected')"),
-    cl::value_desc("prefixes"), cl::cat(MCCategory));
+static cl::list<std::string>
+    Verify("verify", cl::CommaSeparated, cl::ValueOptional,
+           cl::desc("Check diagnostics against '<prefix>-<kind>' comments in "
+                    "the input (default prefix: 'expected')"),
+           cl::value_desc("prefixes"), cl::cat(MCCategory));
 
 enum ActionType {
   AC_AsLex,
@@ -272,8 +278,8 @@ static const Target *GetTarget(const char *ProgName) {
 
   // Get the target specific parser.
   std::string Error;
-  const Target *TheTarget =
-      TargetRegistry::lookupTarget(ArchName, TheTriple, Error);
+  const Target *TheTarget = TargetRegistry::lookupTarget(ArchName, TheTriple,
+                                                         Error);
   if (!TheTarget) {
     WithColor::error(errs(), ProgName) << Error;
     return nullptr;
@@ -284,8 +290,8 @@ static const Target *GetTarget(const char *ProgName) {
   return TheTarget;
 }
 
-static std::unique_ptr<ToolOutputFile>
-GetOutputStream(StringRef Path, sys::fs::OpenFlags Flags) {
+static std::unique_ptr<ToolOutputFile> GetOutputStream(StringRef Path,
+    sys::fs::OpenFlags Flags) {
   std::error_code EC;
   auto Out = std::make_unique<ToolOutputFile>(Path, EC, Flags);
   if (EC) {
@@ -309,12 +315,13 @@ static void setDwarfDebugFlags(int argc, char **argv) {
 
 static std::string DwarfDebugProducer;
 static void setDwarfDebugProducer() {
-  if (!getenv("DEBUG_PRODUCER"))
+  if(!getenv("DEBUG_PRODUCER"))
     return;
   DwarfDebugProducer += getenv("DEBUG_PRODUCER");
 }
 
-static int AsLexInput(SourceMgr &SrcMgr, MCAsmInfo &MAI, raw_ostream &OS) {
+static int AsLexInput(SourceMgr &SrcMgr, MCAsmInfo &MAI,
+                      raw_ostream &OS) {
 
   AsmLexer Lexer(MAI);
   Lexer.setBuffer(SrcMgr.getMemoryBuffer(SrcMgr.getMainFileID())->getBuffer());
@@ -331,7 +338,7 @@ static int AsLexInput(SourceMgr &SrcMgr, MCAsmInfo &MAI, 
raw_ostream &OS) {
 }
 
 static int fillCommandLineSymbols(MCAsmParser &Parser) {
-  for (auto &I : DefineSymbol) {
+  for (auto &I: DefineSymbol) {
     auto Pair = StringRef(I).split('=');
     auto Sym = Pair.first;
     auto Val = Pair.second;
@@ -355,7 +362,8 @@ static int AssembleInput(const char *ProgName, const Target 
*TheTarget,
                          SourceMgr &SrcMgr, MCContext &Ctx, MCStreamer &Str,
                          MCAsmInfo &MAI, MCSubtargetInfo &STI,
                          MCInstrInfo &MCII, MCTargetOptions const &MCOptions) {
-  std::unique_ptr<MCAsmParser> Parser(createMCAsmParser(SrcMgr, Ctx, Str, 
MAI));
+  std::unique_ptr<MCAsmParser> Parser(
+      createMCAsmParser(SrcMgr, Ctx, Str, MAI));
   std::unique_ptr<MCTargetAsmParser> TAP(
       TheTarget->createMCAsmParser(STI, *Parser, MCII));
 
@@ -366,7 +374,7 @@ static int AssembleInput(const char *ProgName, const Target 
*TheTarget,
   }
 
   int SymbolResult = fillCommandLineSymbols(*Parser);
-  if (SymbolResult)
+  if(SymbolResult)
     return SymbolResult;
   Parser->setShowParsedOperands(ShowInstOperands);
   Parser->setTargetParser(*TAP);
@@ -422,10 +430,27 @@ int main(int argc, char **argv) {
 
   const char *ProgName = argv[0];
 
-  bool VerifyEnabled = Verify.getNumOccurrences() > 0;
-  if (VerifyEnabled && Action == AC_AsLex) {
+  // A bare --verify (or a stray comma) yields empty values; ignore them.
+  SmallVector<StringRef> VerifyPrefixes;
+  for (StringRef Prefix : Verify) {
+    if (Prefix.empty())
+      continue;
+    if (!isAlpha(Prefix[0]) || !all_of(Prefix, [](char C) {
+          return isAlnum(C) || C == '-' || C == '_';
+        })) {
+      WithColor::error(errs(), ProgName)
+          << "invalid prefix '" << Prefix
+          << "' for --verify: prefixes must start with a letter and contain "
+             "only alphanumeric characters, hyphens and underscores\n";
+      return 1;
+    }
+    VerifyPrefixes.push_back(Prefix);
+  }
+  if (Verify.getNumOccurrences() && VerifyPrefixes.empty())
+    VerifyPrefixes.push_back("expected");
+  if (!VerifyPrefixes.empty() && Action == AC_AsLex) {
     WithColor::error(errs(), ProgName)
-        << "-verify is not supported with -as-lex\n";
+        << "--verify is not supported with --as-lex\n";
     return 1;
   }
 
@@ -506,43 +531,27 @@ int main(int argc, char **argv) {
   Ctx.setObjectFileInfo(MOFI.get());
 
   std::optional<SourceMgrDiagnosticVerifier> Verifier;
-  if (VerifyEnabled) {
-    std::vector<std::string> Prefixes;
-    for (StringRef Prefix : Verify)
-      if (!Prefix.empty())
-        Prefixes.push_back(Prefix.str());
-    // A bare '-verify' (no '=value'), an explicit empty prefix list, and a
-    // list that parses to only empty strings (e.g. a stray comma) all mean
-    // "use the default prefix".
-    if (Prefixes.empty())
-      Prefixes = {"expected"};
-
-    // Scope directive scanning to single-line comments recognized by the
-    // target (plus '//' and '#' when allowed) so directive-like text in
-    // operands or string literals is ignored.
-    std::vector<std::string> CommentPrefixes;
-    auto AddCommentPrefix = [&](StringRef Prefix) {
-      if (!Prefix.empty() && !llvm::is_contained(CommentPrefixes, Prefix))
-        CommentPrefixes.push_back(Prefix.str());
-    };
-    StringRef TargetComment = MAI->getCommentString();
-    // AsmLexer::isAtStartOfComment also accepts a single '#' when
-    // CommentString is "##" (X86 Darwin), which subsumes "##".
-    if (TargetComment == "##")
-      TargetComment = "#";
-    AddCommentPrefix(TargetComment);
-    if (MAI->shouldAllowAdditionalComments()) {
-      AddCommentPrefix("//");
-      AddCommentPrefix("#");
+  bool SawError = false;
+  if (!VerifyPrefixes.empty()) {
+    // Only recognize directives in comments. llvm-mc's disassembler input
+    // only supports '#' comments.
+    SmallVector<StringRef, 3> CommentPrefixes;
+    if (Action == AC_Assemble) {
+      CommentPrefixes.push_back(MAI->getCommentString());
+      if (MAI->shouldAllowAdditionalComments())
+        CommentPrefixes.append({"//", "#"});
+    } else {
+      CommentPrefixes.push_back("#");
     }
-
-    Verifier.emplace(SrcMgr, errs(), Prefixes, CommentPrefixes);
-    // Install on both Ctx and SrcMgr: most parser/disassembler diagnostics go
-    // through SrcMgr, while some (e.g. DWARF CFI checker diagnostics via
-    // MCContext::reportError/reportWarning) go directly through Ctx.
-    Ctx.setDiagnosticHandler(
-        [&](const SMDiagnostic &Diag, bool, const SourceMgr &,
-            std::vector<const MDNode *> &) { Verifier->process(Diag); });
+    Verifier.emplace(SrcMgr, errs(), VerifyPrefixes, CommentPrefixes);
+    // Most diagnostics are reported through SrcMgr, but some (e.g. from the
+    // CFI checker) are only reported through Ctx, so route both to Ctx.
+    Ctx.setDiagnosticHandler([&](const SMDiagnostic &Diag, bool,
+                                 const SourceMgr &,
+                                 std::vector<const MDNode *> &) {
+      SawError |= Diag.getKind() == SourceMgr::DK_Error;
+      Verifier->process(Diag);
+    });
     SrcMgr.setDiagHandler(
         [](const SMDiagnostic &Diag, void *Ctx) {
           static_cast<MCContext *>(Ctx)->diagnose(Diag);
@@ -728,12 +737,6 @@ int main(int argc, char **argv) {
     Res = Disassembler::disassemble(*TheTarget, *STI, *Str, *Buffer, SrcMgr,
                                     Ctx, HexBytes, NumBenchmarkRuns);
 
-  // Under -verify, the exit code reflects whether the diagnostics matched
-  // what was expected, not whether assembling/disassembling itself hit an
-  // error: an expected error is a successful run.
-  if (Verifier)
-    Res = Verifier->verify() ? 0 : 1;
-
   // Keep output if no errors.
   if (Res == 0) {
     Out->keep();
@@ -741,5 +744,14 @@ int main(int argc, char **argv) {
       DwoOut->keep();
   }
 
+  // Under --verify, a run whose errors were all expected succeeds. Failures
+  // that were not reported as diagnostics (e.g. an invalid --defsym) cannot
+  // be expected and still fail.
+  if (Verifier) {
+    bool Verified = Verifier->verify();
+    if (Res == 0 || SawError)
+      Res = Verified ? 0 : 1;
+  }
+
   return Res;
 }

_______________________________________________
llvm-branch-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits

Reply via email to