https://github.com/arichardson updated https://github.com/llvm/llvm-project/pull/227569
>From eb5078a444fba7049bb513dd14bf79cf1c2ed48d 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 719214b7465ead6a20df96df0b80b01e4f9dbac5 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 a511e84cc1db1e5510936059d6bbebfdcf27cd79 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
