./ldc-1.36.0-src/0000755000175000017500000000000014564454266013513 5ustar matthiasmatthias./ldc-1.36.0-src/utils/0000755000175000017500000000000014564454261014646 5ustar matthiasmatthias./ldc-1.36.0-src/utils/FileCheck-14.cpp0000644000175000017500000010777214564454261017427 0ustar matthiasmatthias//===- FileCheck.cpp - Check that File's Contents match what is expected --===// // // 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 // //===----------------------------------------------------------------------===// // // FileCheck does a line-by line check of a file that validates whether it // contains the expected content. This is useful for regression tests etc. // // This program exits with an exit status of 2 on error, exit status of 0 if // the file matched the expected contents, and exit status of 1 if it did not // contain the expected contents. // //===----------------------------------------------------------------------===// #include "llvm/FileCheck/FileCheck.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/Process.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" #include #include using namespace llvm; static cl::extrahelp FileCheckOptsEnv( "\nOptions are parsed from the environment variable FILECHECK_OPTS and\n" "from the command line.\n"); static cl::opt CheckFilename(cl::Positional, cl::desc(""), cl::Optional); static cl::opt InputFilename("input-file", cl::desc("File to check (defaults to stdin)"), cl::init("-"), cl::value_desc("filename")); static cl::list CheckPrefixes( "check-prefix", cl::desc("Prefix to use from check file (defaults to 'CHECK')")); static cl::alias CheckPrefixesAlias( "check-prefixes", cl::aliasopt(CheckPrefixes), cl::CommaSeparated, cl::NotHidden, cl::desc( "Alias for -check-prefix permitting multiple comma separated values")); static cl::list CommentPrefixes( "comment-prefixes", cl::CommaSeparated, cl::Hidden, cl::desc("Comma-separated list of comment prefixes to use from check file\n" "(defaults to 'COM,RUN'). Please avoid using this feature in\n" "LLVM's LIT-based test suites, which should be easier to\n" "maintain if they all follow a consistent comment style. This\n" "feature is meant for non-LIT test suites using FileCheck.")); static cl::opt NoCanonicalizeWhiteSpace( "strict-whitespace", cl::desc("Do not treat all horizontal whitespace as equivalent")); static cl::opt IgnoreCase( "ignore-case", cl::desc("Use case-insensitive matching")); static cl::list ImplicitCheckNot( "implicit-check-not", cl::desc("Add an implicit negative check with this pattern to every\n" "positive check. This can be used to ensure that no instances of\n" "this pattern occur which are not matched by a positive pattern"), cl::value_desc("pattern")); static cl::list GlobalDefines("D", cl::AlwaysPrefix, cl::desc("Define a variable to be used in capture patterns."), cl::value_desc("VAR=VALUE")); static cl::opt AllowEmptyInput( "allow-empty", cl::init(false), cl::desc("Allow the input file to be empty. This is useful when making\n" "checks that some error message does not occur, for example.")); static cl::opt AllowUnusedPrefixes( "allow-unused-prefixes", cl::init(false), cl::ZeroOrMore, cl::desc("Allow prefixes to be specified but not appear in the test.")); static cl::opt MatchFullLines( "match-full-lines", cl::init(false), cl::desc("Require all positive matches to cover an entire input line.\n" "Allows leading and trailing whitespace if --strict-whitespace\n" "is not also passed.")); static cl::opt EnableVarScope( "enable-var-scope", cl::init(false), cl::desc("Enables scope for regex variables. Variables with names that\n" "do not start with '$' will be reset at the beginning of\n" "each CHECK-LABEL block.")); static cl::opt AllowDeprecatedDagOverlap( "allow-deprecated-dag-overlap", cl::init(false), cl::desc("Enable overlapping among matches in a group of consecutive\n" "CHECK-DAG directives. This option is deprecated and is only\n" "provided for convenience as old tests are migrated to the new\n" "non-overlapping CHECK-DAG implementation.\n")); static cl::opt Verbose( "v", cl::init(false), cl::ZeroOrMore, cl::desc("Print directive pattern matches, or add them to the input dump\n" "if enabled.\n")); static cl::opt VerboseVerbose( "vv", cl::init(false), cl::ZeroOrMore, cl::desc("Print information helpful in diagnosing internal FileCheck\n" "issues, or add it to the input dump if enabled. Implies\n" "-v.\n")); // The order of DumpInputValue members affects their precedence, as documented // for -dump-input below. enum DumpInputValue { DumpInputNever, DumpInputFail, DumpInputAlways, DumpInputHelp }; static cl::list DumpInputs( "dump-input", cl::desc("Dump input to stderr, adding annotations representing\n" "currently enabled diagnostics. When there are multiple\n" "occurrences of this option, the that appears earliest\n" "in the list below has precedence. The default is 'fail'.\n"), cl::value_desc("mode"), cl::values(clEnumValN(DumpInputHelp, "help", "Explain input dump and quit"), clEnumValN(DumpInputAlways, "always", "Always dump input"), clEnumValN(DumpInputFail, "fail", "Dump input on failure"), clEnumValN(DumpInputNever, "never", "Never dump input"))); // The order of DumpInputFilterValue members affects their precedence, as // documented for -dump-input-filter below. enum DumpInputFilterValue { DumpInputFilterError, DumpInputFilterAnnotation, DumpInputFilterAnnotationFull, DumpInputFilterAll }; static cl::list DumpInputFilters( "dump-input-filter", cl::desc("In the dump requested by -dump-input, print only input lines of\n" "kind plus any context specified by -dump-input-context.\n" "When there are multiple occurrences of this option, the \n" "that appears earliest in the list below has precedence. The\n" "default is 'error' when -dump-input=fail, and it's 'all' when\n" "-dump-input=always.\n"), cl::values(clEnumValN(DumpInputFilterAll, "all", "All input lines"), clEnumValN(DumpInputFilterAnnotationFull, "annotation-full", "Input lines with annotations"), clEnumValN(DumpInputFilterAnnotation, "annotation", "Input lines with starting points of annotations"), clEnumValN(DumpInputFilterError, "error", "Input lines with starting points of error " "annotations"))); static cl::list DumpInputContexts( "dump-input-context", cl::value_desc("N"), cl::desc("In the dump requested by -dump-input, print input lines\n" "before and input lines after any lines specified by\n" "-dump-input-filter. When there are multiple occurrences of\n" "this option, the largest specified has precedence. The\n" "default is 5.\n")); typedef cl::list::const_iterator prefix_iterator; static void DumpCommandLine(int argc, char **argv) { errs() << "FileCheck command line: "; for (int I = 0; I < argc; I++) errs() << " " << argv[I]; errs() << "\n"; } struct MarkerStyle { /// The starting char (before tildes) for marking the line. char Lead; /// What color to use for this annotation. raw_ostream::Colors Color; /// A note to follow the marker, or empty string if none. std::string Note; /// Does this marker indicate inclusion by -dump-input-filter=error? bool FiltersAsError; MarkerStyle() {} MarkerStyle(char Lead, raw_ostream::Colors Color, const std::string &Note = "", bool FiltersAsError = false) : Lead(Lead), Color(Color), Note(Note), FiltersAsError(FiltersAsError) { assert((!FiltersAsError || !Note.empty()) && "expected error diagnostic to have note"); } }; static MarkerStyle GetMarker(FileCheckDiag::MatchType MatchTy) { switch (MatchTy) { case FileCheckDiag::MatchFoundAndExpected: return MarkerStyle('^', raw_ostream::GREEN); case FileCheckDiag::MatchFoundButExcluded: return MarkerStyle('!', raw_ostream::RED, "error: no match expected", /*FiltersAsError=*/true); case FileCheckDiag::MatchFoundButWrongLine: return MarkerStyle('!', raw_ostream::RED, "error: match on wrong line", /*FiltersAsError=*/true); case FileCheckDiag::MatchFoundButDiscarded: return MarkerStyle('!', raw_ostream::CYAN, "discard: overlaps earlier match"); case FileCheckDiag::MatchFoundErrorNote: // Note should always be overridden within the FileCheckDiag. return MarkerStyle('!', raw_ostream::RED, "error: unknown error after match", /*FiltersAsError=*/true); case FileCheckDiag::MatchNoneAndExcluded: return MarkerStyle('X', raw_ostream::GREEN); case FileCheckDiag::MatchNoneButExpected: return MarkerStyle('X', raw_ostream::RED, "error: no match found", /*FiltersAsError=*/true); case FileCheckDiag::MatchNoneForInvalidPattern: return MarkerStyle('X', raw_ostream::RED, "error: match failed for invalid pattern", /*FiltersAsError=*/true); case FileCheckDiag::MatchFuzzy: return MarkerStyle('?', raw_ostream::MAGENTA, "possible intended match", /*FiltersAsError=*/true); } llvm_unreachable_internal("unexpected match type"); } static void DumpInputAnnotationHelp(raw_ostream &OS) { OS << "The following description was requested by -dump-input=help to\n" << "explain the input dump printed by FileCheck.\n" << "\n" << "Related command-line options:\n" << "\n" << " - -dump-input= enables or disables the input dump\n" << " - -dump-input-filter= filters the input lines\n" << " - -dump-input-context= adjusts the context of filtered lines\n" << " - -v and -vv add more annotations\n" << " - -color forces colors to be enabled both in the dump and below\n" << " - -help documents the above options in more detail\n" << "\n" << "These options can also be set via FILECHECK_OPTS. For example, for\n" << "maximum debugging output on failures:\n" << "\n" << " $ FILECHECK_OPTS='-dump-input-filter=all -vv -color' ninja check\n" << "\n" << "Input dump annotation format:\n" << "\n"; // Labels for input lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "L:"; OS << " labels line number L of the input file\n" << " An extra space is added after each input line to represent" << " the\n" << " newline character\n"; // Labels for annotation lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L"; OS << " labels the only match result for either (1) a pattern of type T" << " from\n" << " line L of the check file if L is an integer or (2) the" << " I-th implicit\n" << " pattern if L is \"imp\" followed by an integer " << "I (index origin one)\n"; OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L'N"; OS << " labels the Nth match result for such a pattern\n"; // Markers on annotation lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "^~~"; OS << " marks good match (reported if -v)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "!~~"; OS << " marks bad match, such as:\n" << " - CHECK-NEXT on same line as previous match (error)\n" << " - CHECK-NOT found (error)\n" << " - CHECK-DAG overlapping match (discarded, reported if " << "-vv)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "X~~"; OS << " marks search range when no match is found, such as:\n" << " - CHECK-NEXT not found (error)\n" << " - CHECK-NOT not found (success, reported if -vv)\n" << " - CHECK-DAG not found after discarded matches (error)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "?"; OS << " marks fuzzy match when no match is found\n"; // Elided lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "..."; OS << " indicates elided input lines and annotations, as specified by\n" << " -dump-input-filter and -dump-input-context\n"; // Colors. OS << " - colors "; WithColor(OS, raw_ostream::GREEN, true) << "success"; OS << ", "; WithColor(OS, raw_ostream::RED, true) << "error"; OS << ", "; WithColor(OS, raw_ostream::MAGENTA, true) << "fuzzy match"; OS << ", "; WithColor(OS, raw_ostream::CYAN, true, false) << "discarded match"; OS << ", "; WithColor(OS, raw_ostream::CYAN, true, true) << "unmatched input"; OS << "\n"; } /// An annotation for a single input line. struct InputAnnotation { /// The index of the match result across all checks unsigned DiagIndex; /// The label for this annotation. std::string Label; /// Is this the initial fragment of a diagnostic that has been broken across /// multiple lines? bool IsFirstLine; /// What input line (one-origin indexing) this annotation marks. This might /// be different from the starting line of the original diagnostic if /// !IsFirstLine. unsigned InputLine; /// The column range (one-origin indexing, open end) in which to mark the /// input line. If InputEndCol is UINT_MAX, treat it as the last column /// before the newline. unsigned InputStartCol, InputEndCol; /// The marker to use. MarkerStyle Marker; /// Whether this annotation represents a good match for an expected pattern. bool FoundAndExpectedMatch; }; /// Get an abbreviation for the check type. static std::string GetCheckTypeAbbreviation(Check::FileCheckType Ty) { switch (Ty) { case Check::CheckPlain: if (Ty.getCount() > 1) return "count"; return "check"; case Check::CheckNext: return "next"; case Check::CheckSame: return "same"; case Check::CheckNot: return "not"; case Check::CheckDAG: return "dag"; case Check::CheckLabel: return "label"; case Check::CheckEmpty: return "empty"; case Check::CheckComment: return "com"; case Check::CheckEOF: return "eof"; case Check::CheckBadNot: return "bad-not"; case Check::CheckBadCount: return "bad-count"; case Check::CheckNone: llvm_unreachable("invalid FileCheckType"); } llvm_unreachable("unknown FileCheckType"); } static void BuildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID, const std::pair &ImpPatBufferIDRange, const std::vector &Diags, std::vector &Annotations, unsigned &LabelWidth) { struct CompareSMLoc { bool operator()(const SMLoc &LHS, const SMLoc &RHS) const { return LHS.getPointer() < RHS.getPointer(); } }; // How many diagnostics does each pattern have? std::map DiagCountPerPattern; for (auto Diag : Diags) ++DiagCountPerPattern[Diag.CheckLoc]; // How many diagnostics have we seen so far per pattern? std::map DiagIndexPerPattern; // How many total diagnostics have we seen so far? unsigned DiagIndex = 0; // What's the widest label? LabelWidth = 0; for (auto DiagItr = Diags.begin(), DiagEnd = Diags.end(); DiagItr != DiagEnd; ++DiagItr) { InputAnnotation A; A.DiagIndex = DiagIndex++; // Build label, which uniquely identifies this check result. unsigned CheckBufferID = SM.FindBufferContainingLoc(DiagItr->CheckLoc); auto CheckLineAndCol = SM.getLineAndColumn(DiagItr->CheckLoc, CheckBufferID); llvm::raw_string_ostream Label(A.Label); Label << GetCheckTypeAbbreviation(DiagItr->CheckTy) << ":"; if (CheckBufferID == CheckFileBufferID) Label << CheckLineAndCol.first; else if (ImpPatBufferIDRange.first <= CheckBufferID && CheckBufferID < ImpPatBufferIDRange.second) Label << "imp" << (CheckBufferID - ImpPatBufferIDRange.first + 1); else llvm_unreachable("expected diagnostic's check location to be either in " "the check file or for an implicit pattern"); if (DiagCountPerPattern[DiagItr->CheckLoc] > 1) Label << "'" << DiagIndexPerPattern[DiagItr->CheckLoc]++; LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size()); A.Marker = GetMarker(DiagItr->MatchTy); if (!DiagItr->Note.empty()) { A.Marker.Note = DiagItr->Note; // It's less confusing if notes that don't actually have ranges don't have // markers. For example, a marker for 'with "VAR" equal to "5"' would // seem to indicate where "VAR" matches, but the location we actually have // for the marker simply points to the start of the match/search range for // the full pattern of which the substitution is potentially just one // component. if (DiagItr->InputStartLine == DiagItr->InputEndLine && DiagItr->InputStartCol == DiagItr->InputEndCol) A.Marker.Lead = ' '; } if (DiagItr->MatchTy == FileCheckDiag::MatchFoundErrorNote) { assert(!DiagItr->Note.empty() && "expected custom note for MatchFoundErrorNote"); A.Marker.Note = "error: " + A.Marker.Note; } A.FoundAndExpectedMatch = DiagItr->MatchTy == FileCheckDiag::MatchFoundAndExpected; // Compute the mark location, and break annotation into multiple // annotations if it spans multiple lines. A.IsFirstLine = true; A.InputLine = DiagItr->InputStartLine; A.InputStartCol = DiagItr->InputStartCol; if (DiagItr->InputStartLine == DiagItr->InputEndLine) { // Sometimes ranges are empty in order to indicate a specific point, but // that would mean nothing would be marked, so adjust the range to // include the following character. A.InputEndCol = std::max(DiagItr->InputStartCol + 1, DiagItr->InputEndCol); Annotations.push_back(A); } else { assert(DiagItr->InputStartLine < DiagItr->InputEndLine && "expected input range not to be inverted"); A.InputEndCol = UINT_MAX; Annotations.push_back(A); for (unsigned L = DiagItr->InputStartLine + 1, E = DiagItr->InputEndLine; L <= E; ++L) { // If a range ends before the first column on a line, then it has no // characters on that line, so there's nothing to render. if (DiagItr->InputEndCol == 1 && L == E) break; InputAnnotation B; B.DiagIndex = A.DiagIndex; B.Label = A.Label; B.IsFirstLine = false; B.InputLine = L; B.Marker = A.Marker; B.Marker.Lead = '~'; B.Marker.Note = ""; B.InputStartCol = 1; if (L != E) B.InputEndCol = UINT_MAX; else B.InputEndCol = DiagItr->InputEndCol; B.FoundAndExpectedMatch = A.FoundAndExpectedMatch; Annotations.push_back(B); } } } } static unsigned FindInputLineInFilter( DumpInputFilterValue DumpInputFilter, unsigned CurInputLine, const std::vector::iterator &AnnotationBeg, const std::vector::iterator &AnnotationEnd) { if (DumpInputFilter == DumpInputFilterAll) return CurInputLine; for (auto AnnotationItr = AnnotationBeg; AnnotationItr != AnnotationEnd; ++AnnotationItr) { switch (DumpInputFilter) { case DumpInputFilterAll: llvm_unreachable("unexpected DumpInputFilterAll"); break; case DumpInputFilterAnnotationFull: return AnnotationItr->InputLine; case DumpInputFilterAnnotation: if (AnnotationItr->IsFirstLine) return AnnotationItr->InputLine; break; case DumpInputFilterError: if (AnnotationItr->IsFirstLine && AnnotationItr->Marker.FiltersAsError) return AnnotationItr->InputLine; break; } } return UINT_MAX; } /// To OS, print a vertical ellipsis (right-justified at LabelWidth) if it would /// occupy less lines than ElidedLines, but print ElidedLines otherwise. Either /// way, clear ElidedLines. Thus, if ElidedLines is empty, do nothing. static void DumpEllipsisOrElidedLines(raw_ostream &OS, std::string &ElidedLines, unsigned LabelWidth) { if (ElidedLines.empty()) return; unsigned EllipsisLines = 3; if (EllipsisLines < StringRef(ElidedLines).count('\n')) { for (unsigned i = 0; i < EllipsisLines; ++i) { WithColor(OS, raw_ostream::BLACK, /*Bold=*/true) << right_justify(".", LabelWidth); OS << '\n'; } } else OS << ElidedLines; ElidedLines.clear(); } static void DumpAnnotatedInput(raw_ostream &OS, const FileCheckRequest &Req, DumpInputFilterValue DumpInputFilter, unsigned DumpInputContext, StringRef InputFileText, std::vector &Annotations, unsigned LabelWidth) { OS << "Input was:\n<<<<<<\n"; // Sort annotations. llvm::sort(Annotations, [](const InputAnnotation &A, const InputAnnotation &B) { // 1. Sort annotations in the order of the input lines. // // This makes it easier to find relevant annotations while // iterating input lines in the implementation below. FileCheck // does not always produce diagnostics in the order of input // lines due to, for example, CHECK-DAG and CHECK-NOT. if (A.InputLine != B.InputLine) return A.InputLine < B.InputLine; // 2. Sort annotations in the temporal order FileCheck produced // their associated diagnostics. // // This sort offers several benefits: // // A. On a single input line, the order of annotations reflects // the FileCheck logic for processing directives/patterns. // This can be helpful in understanding cases in which the // order of the associated directives/patterns in the check // file or on the command line either (i) does not match the // temporal order in which FileCheck looks for matches for the // directives/patterns (due to, for example, CHECK-LABEL, // CHECK-NOT, or `--implicit-check-not`) or (ii) does match // that order but does not match the order of those // diagnostics along an input line (due to, for example, // CHECK-DAG). // // On the other hand, because our presentation format presents // input lines in order, there's no clear way to offer the // same benefit across input lines. For consistency, it might // then seem worthwhile to have annotations on a single line // also sorted in input order (that is, by input column). // However, in practice, this appears to be more confusing // than helpful. Perhaps it's intuitive to expect annotations // to be listed in the temporal order in which they were // produced except in cases the presentation format obviously // and inherently cannot support it (that is, across input // lines). // // B. When diagnostics' annotations are split among multiple // input lines, the user must track them from one input line // to the next. One property of the sort chosen here is that // it facilitates the user in this regard by ensuring the // following: when comparing any two input lines, a // diagnostic's annotations are sorted in the same position // relative to all other diagnostics' annotations. return A.DiagIndex < B.DiagIndex; }); // Compute the width of the label column. const unsigned char *InputFilePtr = InputFileText.bytes_begin(), *InputFileEnd = InputFileText.bytes_end(); unsigned LineCount = InputFileText.count('\n'); if (InputFileEnd[-1] != '\n') ++LineCount; unsigned LineNoWidth = std::log10(LineCount) + 1; // +3 below adds spaces (1) to the left of the (right-aligned) line numbers // on input lines and (2) to the right of the (left-aligned) labels on // annotation lines so that input lines and annotation lines are more // visually distinct. For example, the spaces on the annotation lines ensure // that input line numbers and check directive line numbers never align // horizontally. Those line numbers might not even be for the same file. // One space would be enough to achieve that, but more makes it even easier // to see. LabelWidth = std::max(LabelWidth, LineNoWidth) + 3; // Print annotated input lines. unsigned PrevLineInFilter = 0; // 0 means none so far unsigned NextLineInFilter = 0; // 0 means uncomputed, UINT_MAX means none std::string ElidedLines; raw_string_ostream ElidedLinesOS(ElidedLines); ColorMode TheColorMode = WithColor(OS).colorsEnabled() ? ColorMode::Enable : ColorMode::Disable; if (TheColorMode == ColorMode::Enable) ElidedLinesOS.enable_colors(true); auto AnnotationItr = Annotations.begin(), AnnotationEnd = Annotations.end(); for (unsigned Line = 1; InputFilePtr != InputFileEnd || AnnotationItr != AnnotationEnd; ++Line) { const unsigned char *InputFileLine = InputFilePtr; // Compute the previous and next line included by the filter. if (NextLineInFilter < Line) NextLineInFilter = FindInputLineInFilter(DumpInputFilter, Line, AnnotationItr, AnnotationEnd); assert(NextLineInFilter && "expected NextLineInFilter to be computed"); if (NextLineInFilter == Line) PrevLineInFilter = Line; // Elide this input line and its annotations if it's not within the // context specified by -dump-input-context of an input line included by // -dump-input-filter. However, in case the resulting ellipsis would occupy // more lines than the input lines and annotations it elides, buffer the // elided lines and annotations so we can print them instead. raw_ostream *LineOS = &OS; if ((!PrevLineInFilter || PrevLineInFilter + DumpInputContext < Line) && (NextLineInFilter == UINT_MAX || Line + DumpInputContext < NextLineInFilter)) LineOS = &ElidedLinesOS; else { LineOS = &OS; DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); } // Print right-aligned line number. WithColor(*LineOS, raw_ostream::BLACK, /*Bold=*/true, /*BF=*/false, TheColorMode) << format_decimal(Line, LabelWidth) << ": "; // For the case where -v and colors are enabled, find the annotations for // good matches for expected patterns in order to highlight everything // else in the line. There are no such annotations if -v is disabled. std::vector FoundAndExpectedMatches; if (Req.Verbose && TheColorMode == ColorMode::Enable) { for (auto I = AnnotationItr; I != AnnotationEnd && I->InputLine == Line; ++I) { if (I->FoundAndExpectedMatch) FoundAndExpectedMatches.push_back(*I); } } // Print numbered line with highlighting where there are no matches for // expected patterns. bool Newline = false; { WithColor COS(*LineOS, raw_ostream::SAVEDCOLOR, /*Bold=*/false, /*BG=*/false, TheColorMode); bool InMatch = false; if (Req.Verbose) COS.changeColor(raw_ostream::CYAN, true, true); for (unsigned Col = 1; InputFilePtr != InputFileEnd && !Newline; ++Col) { bool WasInMatch = InMatch; InMatch = false; for (auto M : FoundAndExpectedMatches) { if (M.InputStartCol <= Col && Col < M.InputEndCol) { InMatch = true; break; } } if (!WasInMatch && InMatch) COS.resetColor(); else if (WasInMatch && !InMatch) COS.changeColor(raw_ostream::CYAN, true, true); if (*InputFilePtr == '\n') { Newline = true; COS << ' '; } else COS << *InputFilePtr; ++InputFilePtr; } } *LineOS << '\n'; unsigned InputLineWidth = InputFilePtr - InputFileLine; // Print any annotations. while (AnnotationItr != AnnotationEnd && AnnotationItr->InputLine == Line) { WithColor COS(*LineOS, AnnotationItr->Marker.Color, /*Bold=*/true, /*BG=*/false, TheColorMode); // The two spaces below are where the ": " appears on input lines. COS << left_justify(AnnotationItr->Label, LabelWidth) << " "; unsigned Col; for (Col = 1; Col < AnnotationItr->InputStartCol; ++Col) COS << ' '; COS << AnnotationItr->Marker.Lead; // If InputEndCol=UINT_MAX, stop at InputLineWidth. for (++Col; Col < AnnotationItr->InputEndCol && Col <= InputLineWidth; ++Col) COS << '~'; const std::string &Note = AnnotationItr->Marker.Note; if (!Note.empty()) { // Put the note at the end of the input line. If we were to instead // put the note right after the marker, subsequent annotations for the // same input line might appear to mark this note instead of the input // line. for (; Col <= InputLineWidth; ++Col) COS << ' '; COS << ' ' << Note; } COS << '\n'; ++AnnotationItr; } } DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); OS << ">>>>>>\n"; } int main(int argc, char **argv) { // Enable use of ANSI color codes because FileCheck is using them to // highlight text. llvm::sys::Process::UseANSIEscapeCodes(true); InitLLVM X(argc, argv); cl::ParseCommandLineOptions(argc, argv, /*Overview*/ "", /*Errs*/ nullptr, "FILECHECK_OPTS"); // Select -dump-input* values. The -help documentation specifies the default // value and which value to choose if an option is specified multiple times. // In the latter case, the general rule of thumb is to choose the value that // provides the most information. DumpInputValue DumpInput = DumpInputs.empty() ? DumpInputFail : *std::max_element(DumpInputs.begin(), DumpInputs.end()); DumpInputFilterValue DumpInputFilter; if (DumpInputFilters.empty()) DumpInputFilter = DumpInput == DumpInputAlways ? DumpInputFilterAll : DumpInputFilterError; else DumpInputFilter = *std::max_element(DumpInputFilters.begin(), DumpInputFilters.end()); unsigned DumpInputContext = DumpInputContexts.empty() ? 5 : *std::max_element(DumpInputContexts.begin(), DumpInputContexts.end()); if (DumpInput == DumpInputHelp) { DumpInputAnnotationHelp(outs()); return 0; } if (CheckFilename.empty()) { errs() << " not specified\n"; return 2; } FileCheckRequest Req; append_range(Req.CheckPrefixes, CheckPrefixes); append_range(Req.CommentPrefixes, CommentPrefixes); append_range(Req.ImplicitCheckNot, ImplicitCheckNot); bool GlobalDefineError = false; for (StringRef G : GlobalDefines) { size_t EqIdx = G.find('='); if (EqIdx == std::string::npos) { errs() << "Missing equal sign in command-line definition '-D" << G << "'\n"; GlobalDefineError = true; continue; } if (EqIdx == 0) { errs() << "Missing variable name in command-line definition '-D" << G << "'\n"; GlobalDefineError = true; continue; } Req.GlobalDefines.push_back(G); } if (GlobalDefineError) return 2; Req.AllowEmptyInput = AllowEmptyInput; Req.AllowUnusedPrefixes = AllowUnusedPrefixes; Req.EnableVarScope = EnableVarScope; Req.AllowDeprecatedDagOverlap = AllowDeprecatedDagOverlap; Req.Verbose = Verbose; Req.VerboseVerbose = VerboseVerbose; Req.NoCanonicalizeWhiteSpace = NoCanonicalizeWhiteSpace; Req.MatchFullLines = MatchFullLines; Req.IgnoreCase = IgnoreCase; if (VerboseVerbose) Req.Verbose = true; FileCheck FC(Req); if (!FC.ValidateCheckPrefixes()) return 2; Regex PrefixRE = FC.buildCheckPrefixRegex(); std::string REError; if (!PrefixRE.isValid(REError)) { errs() << "Unable to combine check-prefix strings into a prefix regular " "expression! This is likely a bug in FileCheck's verification of " "the check-prefix strings. Regular expression parsing failed " "with the following error: " << REError << "\n"; return 2; } SourceMgr SM; // Read the expected strings from the check file. ErrorOr> CheckFileOrErr = MemoryBuffer::getFileOrSTDIN(CheckFilename, /*IsText=*/true); if (std::error_code EC = CheckFileOrErr.getError()) { errs() << "Could not open check file '" << CheckFilename << "': " << EC.message() << '\n'; return 2; } MemoryBuffer &CheckFile = *CheckFileOrErr.get(); SmallString<4096> CheckFileBuffer; StringRef CheckFileText = FC.CanonicalizeFile(CheckFile, CheckFileBuffer); unsigned CheckFileBufferID = SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( CheckFileText, CheckFile.getBufferIdentifier()), SMLoc()); std::pair ImpPatBufferIDRange; if (FC.readCheckFile(SM, CheckFileText, PrefixRE, &ImpPatBufferIDRange)) return 2; // Open the file to check and add it to SourceMgr. ErrorOr> InputFileOrErr = MemoryBuffer::getFileOrSTDIN(InputFilename, /*IsText=*/true); if (InputFilename == "-") InputFilename = ""; // Overwrite for improved diagnostic messages if (std::error_code EC = InputFileOrErr.getError()) { errs() << "Could not open input file '" << InputFilename << "': " << EC.message() << '\n'; return 2; } MemoryBuffer &InputFile = *InputFileOrErr.get(); if (InputFile.getBufferSize() == 0 && !AllowEmptyInput) { errs() << "FileCheck error: '" << InputFilename << "' is empty.\n"; DumpCommandLine(argc, argv); return 2; } SmallString<4096> InputFileBuffer; StringRef InputFileText = FC.CanonicalizeFile(InputFile, InputFileBuffer); SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( InputFileText, InputFile.getBufferIdentifier()), SMLoc()); std::vector Diags; int ExitCode = FC.checkInput(SM, InputFileText, DumpInput == DumpInputNever ? nullptr : &Diags) ? EXIT_SUCCESS : 1; if (DumpInput == DumpInputAlways || (ExitCode == 1 && DumpInput == DumpInputFail)) { errs() << "\n" << "Input file: " << InputFilename << "\n" << "Check file: " << CheckFilename << "\n" << "\n" << "-dump-input=help explains the following input dump.\n" << "\n"; std::vector Annotations; unsigned LabelWidth; BuildInputAnnotations(SM, CheckFileBufferID, ImpPatBufferIDRange, Diags, Annotations, LabelWidth); DumpAnnotatedInput(errs(), Req, DumpInputFilter, DumpInputContext, InputFileText, Annotations, LabelWidth); } return ExitCode; } ./ldc-1.36.0-src/utils/not.cpp0000644000175000017500000000352314564454261016155 0ustar matthiasmatthias//===- not.cpp - The 'not' testing tool -----------------------------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // Usage: // not cmd // Will return true if cmd doesn't crash and returns false. // not --crash cmd // Will return true if cmd crashes (e.g. for testing crash reporting). #include "llvm/Support/Path.h" #include "llvm/Support/Program.h" #include "llvm/Support/raw_ostream.h" using namespace llvm; int main(int argc, const char **argv) { bool ExpectCrash = false; ++argv; --argc; if (argc > 0 && StringRef(argv[0]) == "--crash") { ++argv; --argc; ExpectCrash = true; } if (argc == 0) return 1; auto Program = sys::findProgramByName(argv[0]); if (!Program) { errs() << "Error: Unable to find `" << argv[0] << "' in PATH: " << Program.getError().message() << "\n"; return 1; } std::vector Argv; Argv.reserve(argc); for (int i = 0; i < argc; ++i) Argv.push_back(argv[i]); #if LDC_LLVM_VER < 1600 auto Env = llvm::None; #else auto Env = std::nullopt; #endif std::string ErrMsg; int Result = sys::ExecuteAndWait(*Program, Argv, Env, {}, 0, 0, &ErrMsg); #ifdef _WIN32 // Handle abort() in msvcrt -- It has exit code as 3. abort(), aka // unreachable, should be recognized as a crash. However, some binaries use // exit code 3 on non-crash failure paths, so only do this if we expect a // crash. if (ExpectCrash && Result == 3) Result = -3; #endif if (Result < 0) { errs() << "Error: " << ErrMsg << "\n"; if (ExpectCrash) return 0; return 1; } if (ExpectCrash) return 1; return Result == 0; } ./ldc-1.36.0-src/utils/CMakeLists.txt0000644000175000017500000000722014564454261017407 0ustar matthiasmatthias# Build gen_gccbuiltins tools to generate D module from LLVM's list of # GCC-style intrinsics. if(CMAKE_CROSSCOMPILING) message(STATUS "NOT building gen_gccbuiltins utility when cross-compiling (you can use host LDC's ldc/gccbuiltins_*.di files)") return() endif() # The LLVM_INCLUDE_DIR definition is not always set, e.g. on Windows. # strip off anything but the first path string(REGEX REPLACE ";.*$" "" LLVM_INC_DIR "${LLVM_INCLUDE_DIRS}") find_path(LLVM_INTRINSIC_TD_PATH "Intrinsics.td" PATHS ${LLVM_INC_DIR}/llvm ${LLVM_INC_DIR}/llvm/IR NO_DEFAULT_PATH) if (${LLVM_INTRINSIC_TD_PATH} STREQUAL "LLVM_INTRINSIC_TD_PATH-NOTFOUND") message(SEND_ERROR "File Intrinsics.td not found") else() string(REGEX REPLACE "/llvm(/IR)?$" "" LLVM_INTRINSIC_TD_PATH ${LLVM_INTRINSIC_TD_PATH}) message(STATUS "Using path for Intrinsics.td: ${LLVM_INTRINSIC_TD_PATH}") endif() add_executable(gen_gccbuiltins gen_gccbuiltins.cpp) set_target_properties( gen_gccbuiltins PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin COMPILE_FLAGS "${LLVM_CXXFLAGS} ${LDC_CXXFLAGS} \"-DLLVM_INTRINSIC_TD_PATH=R\\\"(${LLVM_INTRINSIC_TD_PATH})\\\"\"" LINK_FLAGS "${SANITIZE_LDFLAGS}" ) target_link_libraries(gen_gccbuiltins ${LLVM_TABLEGEN_LIBRARY} ${LLVM_LIBRARIES} ${CMAKE_DL_LIBS} ${LLVM_LDFLAGS}) if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") target_link_libraries(gen_gccbuiltins dl) endif() if ((TARGET FileCheck) OR (EXISTS ${LLVM_ROOT_DIR}/bin/FileCheck)) # already provided by LLVM message(STATUS "Skip building FileCheck, it is already provided by LLVM") else() # Build FileCheck for testing (build source version depending on LLVM version) set(FILECHECK_SRC FileCheck-${LLVM_VERSION_MAJOR}.cpp) if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${FILECHECK_SRC}) add_executable(FileCheck ${FILECHECK_SRC}) set_target_properties( FileCheck PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin COMPILE_FLAGS "${LLVM_CXXFLAGS} ${LDC_CXXFLAGS}" LINK_FLAGS "${SANITIZE_LDFLAGS}" ) if(NOT (LDC_LLVM_VER LESS 1200)) target_link_libraries(FileCheck LLVMFileCheck) endif() target_link_libraries(FileCheck ${LLVM_LIBRARIES} ${CMAKE_DL_LIBS} ${LLVM_LDFLAGS}) message(STATUS "Building FileCheck from LDC source tree") else() message(STATUS "Skip building FileCheck (source not found), assuming it can be found in LLVM bin directory") endif() endif() if ((TARGET not) OR (EXISTS ${LLVM_ROOT_DIR}/bin/not)) # already provided by LLVM else() # Build `not` for testing add_executable(not not.cpp) set_target_properties( not PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin COMPILE_FLAGS "${LLVM_CXXFLAGS} ${LDC_CXXFLAGS}" LINK_FLAGS "${SANITIZE_LDFLAGS}" ) target_link_libraries(not ${LLVM_LIBRARIES} ${CMAKE_DL_LIBS} ${LLVM_LDFLAGS}) endif() if ((TARGET split-file) OR (EXISTS ${LLVM_ROOT_DIR}/bin/split-filea)) # already provided by LLVM message(STATUS "Skip building split-file, it is already provided by LLVM") else() # Build split-file for testing set(SPLITFILE_SRC split-file.cpp) if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${SPLITFILE_SRC}) add_executable(split-file ${SPLITFILE_SRC}) set_target_properties( split-file PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin COMPILE_FLAGS "${LLVM_CXXFLAGS} ${LDC_CXXFLAGS}" LINK_FLAGS "${SANITIZE_LDFLAGS}" ) target_link_libraries(split-file ${LLVM_LIBRARIES} ${CMAKE_DL_LIBS} ${LLVM_LDFLAGS}) message(STATUS "Building split-file from LDC source tree") else() message(STATUS "Skip building split-file (source not found), assuming it can be found in LLVM bin directory") endif() endif() ./ldc-1.36.0-src/utils/split-file.cpp0000644000175000017500000001663314564454261017433 0ustar matthiasmatthias//===- split-file.cpp - Input splitting utility ---------------------------===// // // 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 // //===----------------------------------------------------------------------===// // // Split input into multipe parts separated by regex '^(.|//)--- ' and extract // the specified part. // //===----------------------------------------------------------------------===// // // The LDC version of this code is based on LLVM 16 and modified such that it // compiles with earlier LLVM versions. // //===----------------------------------------------------------------------===// #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/FileOutputBuffer.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/LineIterator.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/ToolOutputFile.h" #include "llvm/Support/WithColor.h" #include #include using namespace llvm; static cl::OptionCategory cat("split-file Options"); static cl::opt input(cl::Positional, cl::desc("filename"), cl::cat(cat)); static cl::opt output(cl::Positional, cl::desc("directory"), cl::value_desc("directory"), cl::cat(cat)); static cl::opt leadingLines("leading-lines", cl::desc("Preserve line numbers"), cl::cat(cat)); static cl::opt noLeadingLines("no-leading-lines", cl::desc("Don't preserve line numbers (default)"), cl::cat(cat)); static StringRef toolName; static int errorCount; [[noreturn]] static void fatal(StringRef filename, const Twine &message) { if (filename.empty()) WithColor::error(errs(), toolName) << message << '\n'; else WithColor::error(errs(), toolName) << filename << ": " << message << '\n'; exit(1); } static void error(StringRef filename, int64_t line, const Twine &message) { ++errorCount; errs() << filename << ':' << line << ": "; WithColor::error(errs()) << message << '\n'; } namespace { struct Part { const char *begin = nullptr; const char *end = nullptr; int64_t leadingLines = 0; }; /// Checks whether character \p C is whitespace in the "C" locale. /// /// Locale-independent version of the C standard library isspace. inline bool isSpace_ldc(char C) { return C == ' ' || C == '\f' || C == '\n' || C == '\r' || C == '\t' || C == '\v'; } /// Detect the line ending style of the string. /// /// If the string contains a line ending, return the line ending character /// sequence that is detected. Otherwise return '\n' for unix line endings. /// /// \return - The line ending character sequence. [[nodiscard]] StringRef detectEOL(StringRef const &str) { auto Length = str.size(); auto Data = str.data(); size_t Pos = str.find('\r'); if (Pos == str.npos) { // If there is no carriage return, assume unix return "\n"; } if (Pos + 1 < Length && Data[Pos + 1] == '\n') return "\r\n"; // Windows if (Pos > 0 && Data[Pos - 1] == '\n') return "\n\r"; // You monster! return "\r"; // Classic Mac } } // namespace static int handle(MemoryBuffer &inputBuf, StringRef input) { DenseMap partToBegin; StringRef lastPart, separator; StringRef EOL = detectEOL(inputBuf.getBuffer()); for (line_iterator i(inputBuf, /*SkipBlanks=*/false, '\0'); !i.is_at_eof();) { const int64_t lineNo = i.line_number(); const StringRef line = *i++; const size_t markerLen = line.startswith("//") ? 6 : 5; if (!(line.size() >= markerLen && line.substr(markerLen - 4).startswith("--- "))) continue; separator = line.substr(0, markerLen); const StringRef partName = line.substr(markerLen); if (partName.empty()) { error(input, lineNo, "empty part name"); continue; } if (isSpace_ldc(partName.front()) || isSpace_ldc(partName.back())) { error(input, lineNo, "part name cannot have leading or trailing space"); continue; } auto res = partToBegin.try_emplace(partName); if (!res.second) { error(input, lineNo, "'" + separator + partName + "' occurs more than once"); continue; } if (!lastPart.empty()) partToBegin[lastPart].end = line.data(); Part &cur = res.first->second; if (!i.is_at_eof()) cur.begin = i->data(); // If --leading-lines is specified, numEmptyLines is 0. Append newlines so // that the extracted part preserves line numbers. cur.leadingLines = leadingLines ? i.line_number() - 1 : 0; lastPart = partName; } if (lastPart.empty()) fatal(input, "no part separator was found"); if (errorCount) return 1; partToBegin[lastPart].end = inputBuf.getBufferEnd(); std::vector> outputFiles; SmallString<256> partPath; for (auto &keyValue : partToBegin) { partPath.clear(); sys::path::append(partPath, output, keyValue.first); std::error_code ec = sys::fs::create_directories(sys::path::parent_path(partPath)); if (ec) fatal(input, ec.message()); using namespace std; auto f = make_unique(partPath.str(), ec, llvm::sys::fs::OF_None); if (!f) fatal(input, ec.message()); Part &part = keyValue.second; for (int64_t i = 0; i != part.leadingLines; ++i) (*f).os() << EOL; if (part.begin) (*f).os().write(part.begin, part.end - part.begin); outputFiles.push_back(std::move(f)); } for (std::unique_ptr &outputFile : outputFiles) outputFile->keep(); return 0; } int main(int argc, const char **argv) { toolName = sys::path::stem(argv[0]); cl::HideUnrelatedOptions({&cat}); cl::ParseCommandLineOptions( argc, argv, "Split input into multiple parts separated by regex '^(.|//)--- ' and " "extract the part specified by '^(.|//)--- '\n", nullptr, /*EnvVar=*/nullptr, /*LongOptionsUseDoubleDash=*/true); if (input.empty()) fatal("", "input filename is not specified"); if (output.empty()) fatal("", "output directory is not specified"); ErrorOr> bufferOrErr = MemoryBuffer::getFileOrSTDIN(input); if (std::error_code ec = bufferOrErr.getError()) fatal(input, ec.message()); // Delete output if it is a file or an empty directory, so that we can create // a directory. sys::fs::file_status status; if (std::error_code ec = sys::fs::status(output, status)) if (ec.value() != static_cast(std::errc::no_such_file_or_directory)) fatal(output, ec.message()); if (status.type() != sys::fs::file_type::file_not_found && status.type() != sys::fs::file_type::directory_file && status.type() != sys::fs::file_type::regular_file) fatal(output, "output cannot be a special file"); if (std::error_code ec = sys::fs::remove(output, /*IgnoreNonExisting=*/true)) if (ec.value() != static_cast(std::errc::directory_not_empty) && ec.value() != static_cast(std::errc::file_exists)) fatal(output, ec.message()); return handle(**bufferOrErr, input); } ./ldc-1.36.0-src/utils/FileCheck-17.cpp0000644000175000017500000011002414564454261017412 0ustar matthiasmatthias//===- FileCheck.cpp - Check that File's Contents match what is expected --===// // // 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 // //===----------------------------------------------------------------------===// // // FileCheck does a line-by line check of a file that validates whether it // contains the expected content. This is useful for regression tests etc. // // This program exits with an exit status of 2 on error, exit status of 0 if // the file matched the expected contents, and exit status of 1 if it did not // contain the expected contents. // //===----------------------------------------------------------------------===// #include "llvm/FileCheck/FileCheck.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Process.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" #include #include using namespace llvm; static cl::extrahelp FileCheckOptsEnv( "\nOptions are parsed from the environment variable FILECHECK_OPTS and\n" "from the command line.\n"); static cl::opt CheckFilename(cl::Positional, cl::desc(""), cl::Optional); static cl::opt InputFilename("input-file", cl::desc("File to check (defaults to stdin)"), cl::init("-"), cl::value_desc("filename")); static cl::list CheckPrefixes( "check-prefix", cl::desc("Prefix to use from check file (defaults to 'CHECK')")); static cl::alias CheckPrefixesAlias( "check-prefixes", cl::aliasopt(CheckPrefixes), cl::CommaSeparated, cl::NotHidden, cl::desc( "Alias for -check-prefix permitting multiple comma separated values")); static cl::list CommentPrefixes( "comment-prefixes", cl::CommaSeparated, cl::Hidden, cl::desc("Comma-separated list of comment prefixes to use from check file\n" "(defaults to 'COM,RUN'). Please avoid using this feature in\n" "LLVM's LIT-based test suites, which should be easier to\n" "maintain if they all follow a consistent comment style. This\n" "feature is meant for non-LIT test suites using FileCheck.")); static cl::opt NoCanonicalizeWhiteSpace( "strict-whitespace", cl::desc("Do not treat all horizontal whitespace as equivalent")); static cl::opt IgnoreCase( "ignore-case", cl::desc("Use case-insensitive matching")); static cl::list ImplicitCheckNot( "implicit-check-not", cl::desc("Add an implicit negative check with this pattern to every\n" "positive check. This can be used to ensure that no instances of\n" "this pattern occur which are not matched by a positive pattern"), cl::value_desc("pattern")); static cl::list GlobalDefines("D", cl::AlwaysPrefix, cl::desc("Define a variable to be used in capture patterns."), cl::value_desc("VAR=VALUE")); static cl::opt AllowEmptyInput( "allow-empty", cl::init(false), cl::desc("Allow the input file to be empty. This is useful when making\n" "checks that some error message does not occur, for example.")); static cl::opt AllowUnusedPrefixes( "allow-unused-prefixes", cl::desc("Allow prefixes to be specified but not appear in the test.")); static cl::opt MatchFullLines( "match-full-lines", cl::init(false), cl::desc("Require all positive matches to cover an entire input line.\n" "Allows leading and trailing whitespace if --strict-whitespace\n" "is not also passed.")); static cl::opt EnableVarScope( "enable-var-scope", cl::init(false), cl::desc("Enables scope for regex variables. Variables with names that\n" "do not start with '$' will be reset at the beginning of\n" "each CHECK-LABEL block.")); static cl::opt AllowDeprecatedDagOverlap( "allow-deprecated-dag-overlap", cl::init(false), cl::desc("Enable overlapping among matches in a group of consecutive\n" "CHECK-DAG directives. This option is deprecated and is only\n" "provided for convenience as old tests are migrated to the new\n" "non-overlapping CHECK-DAG implementation.\n")); static cl::opt Verbose( "v", cl::desc("Print directive pattern matches, or add them to the input dump\n" "if enabled.\n")); static cl::opt VerboseVerbose( "vv", cl::desc("Print information helpful in diagnosing internal FileCheck\n" "issues, or add it to the input dump if enabled. Implies\n" "-v.\n")); // The order of DumpInputValue members affects their precedence, as documented // for -dump-input below. enum DumpInputValue { DumpInputNever, DumpInputFail, DumpInputAlways, DumpInputHelp }; static cl::list DumpInputs( "dump-input", cl::desc("Dump input to stderr, adding annotations representing\n" "currently enabled diagnostics. When there are multiple\n" "occurrences of this option, the that appears earliest\n" "in the list below has precedence. The default is 'fail'.\n"), cl::value_desc("mode"), cl::values(clEnumValN(DumpInputHelp, "help", "Explain input dump and quit"), clEnumValN(DumpInputAlways, "always", "Always dump input"), clEnumValN(DumpInputFail, "fail", "Dump input on failure"), clEnumValN(DumpInputNever, "never", "Never dump input"))); // The order of DumpInputFilterValue members affects their precedence, as // documented for -dump-input-filter below. enum DumpInputFilterValue { DumpInputFilterError, DumpInputFilterAnnotation, DumpInputFilterAnnotationFull, DumpInputFilterAll }; static cl::list DumpInputFilters( "dump-input-filter", cl::desc("In the dump requested by -dump-input, print only input lines of\n" "kind plus any context specified by -dump-input-context.\n" "When there are multiple occurrences of this option, the \n" "that appears earliest in the list below has precedence. The\n" "default is 'error' when -dump-input=fail, and it's 'all' when\n" "-dump-input=always.\n"), cl::values(clEnumValN(DumpInputFilterAll, "all", "All input lines"), clEnumValN(DumpInputFilterAnnotationFull, "annotation-full", "Input lines with annotations"), clEnumValN(DumpInputFilterAnnotation, "annotation", "Input lines with starting points of annotations"), clEnumValN(DumpInputFilterError, "error", "Input lines with starting points of error " "annotations"))); static cl::list DumpInputContexts( "dump-input-context", cl::value_desc("N"), cl::desc("In the dump requested by -dump-input, print input lines\n" "before and input lines after any lines specified by\n" "-dump-input-filter. When there are multiple occurrences of\n" "this option, the largest specified has precedence. The\n" "default is 5.\n")); typedef cl::list::const_iterator prefix_iterator; static void DumpCommandLine(int argc, char **argv) { errs() << "FileCheck command line: "; for (int I = 0; I < argc; I++) errs() << " " << argv[I]; errs() << "\n"; } struct MarkerStyle { /// The starting char (before tildes) for marking the line. char Lead; /// What color to use for this annotation. raw_ostream::Colors Color; /// A note to follow the marker, or empty string if none. std::string Note; /// Does this marker indicate inclusion by -dump-input-filter=error? bool FiltersAsError; MarkerStyle() {} MarkerStyle(char Lead, raw_ostream::Colors Color, const std::string &Note = "", bool FiltersAsError = false) : Lead(Lead), Color(Color), Note(Note), FiltersAsError(FiltersAsError) { assert((!FiltersAsError || !Note.empty()) && "expected error diagnostic to have note"); } }; static MarkerStyle GetMarker(FileCheckDiag::MatchType MatchTy) { switch (MatchTy) { case FileCheckDiag::MatchFoundAndExpected: return MarkerStyle('^', raw_ostream::GREEN); case FileCheckDiag::MatchFoundButExcluded: return MarkerStyle('!', raw_ostream::RED, "error: no match expected", /*FiltersAsError=*/true); case FileCheckDiag::MatchFoundButWrongLine: return MarkerStyle('!', raw_ostream::RED, "error: match on wrong line", /*FiltersAsError=*/true); case FileCheckDiag::MatchFoundButDiscarded: return MarkerStyle('!', raw_ostream::CYAN, "discard: overlaps earlier match"); case FileCheckDiag::MatchFoundErrorNote: // Note should always be overridden within the FileCheckDiag. return MarkerStyle('!', raw_ostream::RED, "error: unknown error after match", /*FiltersAsError=*/true); case FileCheckDiag::MatchNoneAndExcluded: return MarkerStyle('X', raw_ostream::GREEN); case FileCheckDiag::MatchNoneButExpected: return MarkerStyle('X', raw_ostream::RED, "error: no match found", /*FiltersAsError=*/true); case FileCheckDiag::MatchNoneForInvalidPattern: return MarkerStyle('X', raw_ostream::RED, "error: match failed for invalid pattern", /*FiltersAsError=*/true); case FileCheckDiag::MatchFuzzy: return MarkerStyle('?', raw_ostream::MAGENTA, "possible intended match", /*FiltersAsError=*/true); } llvm_unreachable_internal("unexpected match type"); } static void DumpInputAnnotationHelp(raw_ostream &OS) { OS << "The following description was requested by -dump-input=help to\n" << "explain the input dump printed by FileCheck.\n" << "\n" << "Related command-line options:\n" << "\n" << " - -dump-input= enables or disables the input dump\n" << " - -dump-input-filter= filters the input lines\n" << " - -dump-input-context= adjusts the context of filtered lines\n" << " - -v and -vv add more annotations\n" << " - -color forces colors to be enabled both in the dump and below\n" << " - -help documents the above options in more detail\n" << "\n" << "These options can also be set via FILECHECK_OPTS. For example, for\n" << "maximum debugging output on failures:\n" << "\n" << " $ FILECHECK_OPTS='-dump-input-filter=all -vv -color' ninja check\n" << "\n" << "Input dump annotation format:\n" << "\n"; // Labels for input lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "L:"; OS << " labels line number L of the input file\n" << " An extra space is added after each input line to represent" << " the\n" << " newline character\n"; // Labels for annotation lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L"; OS << " labels the only match result for either (1) a pattern of type T" << " from\n" << " line L of the check file if L is an integer or (2) the" << " I-th implicit\n" << " pattern if L is \"imp\" followed by an integer " << "I (index origin one)\n"; OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L'N"; OS << " labels the Nth match result for such a pattern\n"; // Markers on annotation lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "^~~"; OS << " marks good match (reported if -v)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "!~~"; OS << " marks bad match, such as:\n" << " - CHECK-NEXT on same line as previous match (error)\n" << " - CHECK-NOT found (error)\n" << " - CHECK-DAG overlapping match (discarded, reported if " << "-vv)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "X~~"; OS << " marks search range when no match is found, such as:\n" << " - CHECK-NEXT not found (error)\n" << " - CHECK-NOT not found (success, reported if -vv)\n" << " - CHECK-DAG not found after discarded matches (error)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "?"; OS << " marks fuzzy match when no match is found\n"; // Elided lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "..."; OS << " indicates elided input lines and annotations, as specified by\n" << " -dump-input-filter and -dump-input-context\n"; // Colors. OS << " - colors "; WithColor(OS, raw_ostream::GREEN, true) << "success"; OS << ", "; WithColor(OS, raw_ostream::RED, true) << "error"; OS << ", "; WithColor(OS, raw_ostream::MAGENTA, true) << "fuzzy match"; OS << ", "; WithColor(OS, raw_ostream::CYAN, true, false) << "discarded match"; OS << ", "; WithColor(OS, raw_ostream::CYAN, true, true) << "unmatched input"; OS << "\n"; } /// An annotation for a single input line. struct InputAnnotation { /// The index of the match result across all checks unsigned DiagIndex; /// The label for this annotation. std::string Label; /// Is this the initial fragment of a diagnostic that has been broken across /// multiple lines? bool IsFirstLine; /// What input line (one-origin indexing) this annotation marks. This might /// be different from the starting line of the original diagnostic if /// !IsFirstLine. unsigned InputLine; /// The column range (one-origin indexing, open end) in which to mark the /// input line. If InputEndCol is UINT_MAX, treat it as the last column /// before the newline. unsigned InputStartCol, InputEndCol; /// The marker to use. MarkerStyle Marker; /// Whether this annotation represents a good match for an expected pattern. bool FoundAndExpectedMatch; }; /// Get an abbreviation for the check type. static std::string GetCheckTypeAbbreviation(Check::FileCheckType Ty) { switch (Ty) { case Check::CheckPlain: if (Ty.getCount() > 1) return "count"; return "check"; case Check::CheckNext: return "next"; case Check::CheckSame: return "same"; case Check::CheckNot: return "not"; case Check::CheckDAG: return "dag"; case Check::CheckLabel: return "label"; case Check::CheckEmpty: return "empty"; case Check::CheckComment: return "com"; case Check::CheckEOF: return "eof"; case Check::CheckBadNot: return "bad-not"; case Check::CheckBadCount: return "bad-count"; case Check::CheckMisspelled: return "misspelled"; case Check::CheckNone: llvm_unreachable("invalid FileCheckType"); } llvm_unreachable("unknown FileCheckType"); } static void BuildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID, const std::pair &ImpPatBufferIDRange, const std::vector &Diags, std::vector &Annotations, unsigned &LabelWidth) { struct CompareSMLoc { bool operator()(const SMLoc &LHS, const SMLoc &RHS) const { return LHS.getPointer() < RHS.getPointer(); } }; // How many diagnostics does each pattern have? std::map DiagCountPerPattern; for (auto Diag : Diags) ++DiagCountPerPattern[Diag.CheckLoc]; // How many diagnostics have we seen so far per pattern? std::map DiagIndexPerPattern; // How many total diagnostics have we seen so far? unsigned DiagIndex = 0; // What's the widest label? LabelWidth = 0; for (auto DiagItr = Diags.begin(), DiagEnd = Diags.end(); DiagItr != DiagEnd; ++DiagItr) { InputAnnotation A; A.DiagIndex = DiagIndex++; // Build label, which uniquely identifies this check result. unsigned CheckBufferID = SM.FindBufferContainingLoc(DiagItr->CheckLoc); auto CheckLineAndCol = SM.getLineAndColumn(DiagItr->CheckLoc, CheckBufferID); llvm::raw_string_ostream Label(A.Label); Label << GetCheckTypeAbbreviation(DiagItr->CheckTy) << ":"; if (CheckBufferID == CheckFileBufferID) Label << CheckLineAndCol.first; else if (ImpPatBufferIDRange.first <= CheckBufferID && CheckBufferID < ImpPatBufferIDRange.second) Label << "imp" << (CheckBufferID - ImpPatBufferIDRange.first + 1); else llvm_unreachable("expected diagnostic's check location to be either in " "the check file or for an implicit pattern"); if (DiagCountPerPattern[DiagItr->CheckLoc] > 1) Label << "'" << DiagIndexPerPattern[DiagItr->CheckLoc]++; LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size()); A.Marker = GetMarker(DiagItr->MatchTy); if (!DiagItr->Note.empty()) { A.Marker.Note = DiagItr->Note; // It's less confusing if notes that don't actually have ranges don't have // markers. For example, a marker for 'with "VAR" equal to "5"' would // seem to indicate where "VAR" matches, but the location we actually have // for the marker simply points to the start of the match/search range for // the full pattern of which the substitution is potentially just one // component. if (DiagItr->InputStartLine == DiagItr->InputEndLine && DiagItr->InputStartCol == DiagItr->InputEndCol) A.Marker.Lead = ' '; } if (DiagItr->MatchTy == FileCheckDiag::MatchFoundErrorNote) { assert(!DiagItr->Note.empty() && "expected custom note for MatchFoundErrorNote"); A.Marker.Note = "error: " + A.Marker.Note; } A.FoundAndExpectedMatch = DiagItr->MatchTy == FileCheckDiag::MatchFoundAndExpected; // Compute the mark location, and break annotation into multiple // annotations if it spans multiple lines. A.IsFirstLine = true; A.InputLine = DiagItr->InputStartLine; A.InputStartCol = DiagItr->InputStartCol; if (DiagItr->InputStartLine == DiagItr->InputEndLine) { // Sometimes ranges are empty in order to indicate a specific point, but // that would mean nothing would be marked, so adjust the range to // include the following character. A.InputEndCol = std::max(DiagItr->InputStartCol + 1, DiagItr->InputEndCol); Annotations.push_back(A); } else { assert(DiagItr->InputStartLine < DiagItr->InputEndLine && "expected input range not to be inverted"); A.InputEndCol = UINT_MAX; Annotations.push_back(A); for (unsigned L = DiagItr->InputStartLine + 1, E = DiagItr->InputEndLine; L <= E; ++L) { // If a range ends before the first column on a line, then it has no // characters on that line, so there's nothing to render. if (DiagItr->InputEndCol == 1 && L == E) break; InputAnnotation B; B.DiagIndex = A.DiagIndex; B.Label = A.Label; B.IsFirstLine = false; B.InputLine = L; B.Marker = A.Marker; B.Marker.Lead = '~'; B.Marker.Note = ""; B.InputStartCol = 1; if (L != E) B.InputEndCol = UINT_MAX; else B.InputEndCol = DiagItr->InputEndCol; B.FoundAndExpectedMatch = A.FoundAndExpectedMatch; Annotations.push_back(B); } } } } static unsigned FindInputLineInFilter( DumpInputFilterValue DumpInputFilter, unsigned CurInputLine, const std::vector::iterator &AnnotationBeg, const std::vector::iterator &AnnotationEnd) { if (DumpInputFilter == DumpInputFilterAll) return CurInputLine; for (auto AnnotationItr = AnnotationBeg; AnnotationItr != AnnotationEnd; ++AnnotationItr) { switch (DumpInputFilter) { case DumpInputFilterAll: llvm_unreachable("unexpected DumpInputFilterAll"); break; case DumpInputFilterAnnotationFull: return AnnotationItr->InputLine; case DumpInputFilterAnnotation: if (AnnotationItr->IsFirstLine) return AnnotationItr->InputLine; break; case DumpInputFilterError: if (AnnotationItr->IsFirstLine && AnnotationItr->Marker.FiltersAsError) return AnnotationItr->InputLine; break; } } return UINT_MAX; } /// To OS, print a vertical ellipsis (right-justified at LabelWidth) if it would /// occupy less lines than ElidedLines, but print ElidedLines otherwise. Either /// way, clear ElidedLines. Thus, if ElidedLines is empty, do nothing. static void DumpEllipsisOrElidedLines(raw_ostream &OS, std::string &ElidedLines, unsigned LabelWidth) { if (ElidedLines.empty()) return; unsigned EllipsisLines = 3; if (EllipsisLines < StringRef(ElidedLines).count('\n')) { for (unsigned i = 0; i < EllipsisLines; ++i) { WithColor(OS, raw_ostream::BLACK, /*Bold=*/true) << right_justify(".", LabelWidth); OS << '\n'; } } else OS << ElidedLines; ElidedLines.clear(); } static void DumpAnnotatedInput(raw_ostream &OS, const FileCheckRequest &Req, DumpInputFilterValue DumpInputFilter, unsigned DumpInputContext, StringRef InputFileText, std::vector &Annotations, unsigned LabelWidth) { OS << "Input was:\n<<<<<<\n"; // Sort annotations. llvm::sort(Annotations, [](const InputAnnotation &A, const InputAnnotation &B) { // 1. Sort annotations in the order of the input lines. // // This makes it easier to find relevant annotations while // iterating input lines in the implementation below. FileCheck // does not always produce diagnostics in the order of input // lines due to, for example, CHECK-DAG and CHECK-NOT. if (A.InputLine != B.InputLine) return A.InputLine < B.InputLine; // 2. Sort annotations in the temporal order FileCheck produced // their associated diagnostics. // // This sort offers several benefits: // // A. On a single input line, the order of annotations reflects // the FileCheck logic for processing directives/patterns. // This can be helpful in understanding cases in which the // order of the associated directives/patterns in the check // file or on the command line either (i) does not match the // temporal order in which FileCheck looks for matches for the // directives/patterns (due to, for example, CHECK-LABEL, // CHECK-NOT, or `--implicit-check-not`) or (ii) does match // that order but does not match the order of those // diagnostics along an input line (due to, for example, // CHECK-DAG). // // On the other hand, because our presentation format presents // input lines in order, there's no clear way to offer the // same benefit across input lines. For consistency, it might // then seem worthwhile to have annotations on a single line // also sorted in input order (that is, by input column). // However, in practice, this appears to be more confusing // than helpful. Perhaps it's intuitive to expect annotations // to be listed in the temporal order in which they were // produced except in cases the presentation format obviously // and inherently cannot support it (that is, across input // lines). // // B. When diagnostics' annotations are split among multiple // input lines, the user must track them from one input line // to the next. One property of the sort chosen here is that // it facilitates the user in this regard by ensuring the // following: when comparing any two input lines, a // diagnostic's annotations are sorted in the same position // relative to all other diagnostics' annotations. return A.DiagIndex < B.DiagIndex; }); // Compute the width of the label column. const unsigned char *InputFilePtr = InputFileText.bytes_begin(), *InputFileEnd = InputFileText.bytes_end(); unsigned LineCount = InputFileText.count('\n'); if (InputFileEnd[-1] != '\n') ++LineCount; unsigned LineNoWidth = std::log10(LineCount) + 1; // +3 below adds spaces (1) to the left of the (right-aligned) line numbers // on input lines and (2) to the right of the (left-aligned) labels on // annotation lines so that input lines and annotation lines are more // visually distinct. For example, the spaces on the annotation lines ensure // that input line numbers and check directive line numbers never align // horizontally. Those line numbers might not even be for the same file. // One space would be enough to achieve that, but more makes it even easier // to see. LabelWidth = std::max(LabelWidth, LineNoWidth) + 3; // Print annotated input lines. unsigned PrevLineInFilter = 0; // 0 means none so far unsigned NextLineInFilter = 0; // 0 means uncomputed, UINT_MAX means none std::string ElidedLines; raw_string_ostream ElidedLinesOS(ElidedLines); ColorMode TheColorMode = WithColor(OS).colorsEnabled() ? ColorMode::Enable : ColorMode::Disable; if (TheColorMode == ColorMode::Enable) ElidedLinesOS.enable_colors(true); auto AnnotationItr = Annotations.begin(), AnnotationEnd = Annotations.end(); for (unsigned Line = 1; InputFilePtr != InputFileEnd || AnnotationItr != AnnotationEnd; ++Line) { const unsigned char *InputFileLine = InputFilePtr; // Compute the previous and next line included by the filter. if (NextLineInFilter < Line) NextLineInFilter = FindInputLineInFilter(DumpInputFilter, Line, AnnotationItr, AnnotationEnd); assert(NextLineInFilter && "expected NextLineInFilter to be computed"); if (NextLineInFilter == Line) PrevLineInFilter = Line; // Elide this input line and its annotations if it's not within the // context specified by -dump-input-context of an input line included by // -dump-input-filter. However, in case the resulting ellipsis would occupy // more lines than the input lines and annotations it elides, buffer the // elided lines and annotations so we can print them instead. raw_ostream *LineOS; if ((!PrevLineInFilter || PrevLineInFilter + DumpInputContext < Line) && (NextLineInFilter == UINT_MAX || Line + DumpInputContext < NextLineInFilter)) LineOS = &ElidedLinesOS; else { LineOS = &OS; DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); } // Print right-aligned line number. WithColor(*LineOS, raw_ostream::BLACK, /*Bold=*/true, /*BF=*/false, TheColorMode) << format_decimal(Line, LabelWidth) << ": "; // For the case where -v and colors are enabled, find the annotations for // good matches for expected patterns in order to highlight everything // else in the line. There are no such annotations if -v is disabled. std::vector FoundAndExpectedMatches; if (Req.Verbose && TheColorMode == ColorMode::Enable) { for (auto I = AnnotationItr; I != AnnotationEnd && I->InputLine == Line; ++I) { if (I->FoundAndExpectedMatch) FoundAndExpectedMatches.push_back(*I); } } // Print numbered line with highlighting where there are no matches for // expected patterns. bool Newline = false; { WithColor COS(*LineOS, raw_ostream::SAVEDCOLOR, /*Bold=*/false, /*BG=*/false, TheColorMode); bool InMatch = false; if (Req.Verbose) COS.changeColor(raw_ostream::CYAN, true, true); for (unsigned Col = 1; InputFilePtr != InputFileEnd && !Newline; ++Col) { bool WasInMatch = InMatch; InMatch = false; for (auto M : FoundAndExpectedMatches) { if (M.InputStartCol <= Col && Col < M.InputEndCol) { InMatch = true; break; } } if (!WasInMatch && InMatch) COS.resetColor(); else if (WasInMatch && !InMatch) COS.changeColor(raw_ostream::CYAN, true, true); if (*InputFilePtr == '\n') { Newline = true; COS << ' '; } else COS << *InputFilePtr; ++InputFilePtr; } } *LineOS << '\n'; unsigned InputLineWidth = InputFilePtr - InputFileLine; // Print any annotations. while (AnnotationItr != AnnotationEnd && AnnotationItr->InputLine == Line) { WithColor COS(*LineOS, AnnotationItr->Marker.Color, /*Bold=*/true, /*BG=*/false, TheColorMode); // The two spaces below are where the ": " appears on input lines. COS << left_justify(AnnotationItr->Label, LabelWidth) << " "; unsigned Col; for (Col = 1; Col < AnnotationItr->InputStartCol; ++Col) COS << ' '; COS << AnnotationItr->Marker.Lead; // If InputEndCol=UINT_MAX, stop at InputLineWidth. for (++Col; Col < AnnotationItr->InputEndCol && Col <= InputLineWidth; ++Col) COS << '~'; const std::string &Note = AnnotationItr->Marker.Note; if (!Note.empty()) { // Put the note at the end of the input line. If we were to instead // put the note right after the marker, subsequent annotations for the // same input line might appear to mark this note instead of the input // line. for (; Col <= InputLineWidth; ++Col) COS << ' '; COS << ' ' << Note; } COS << '\n'; ++AnnotationItr; } } DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); OS << ">>>>>>\n"; } int main(int argc, char **argv) { // Enable use of ANSI color codes because FileCheck is using them to // highlight text. llvm::sys::Process::UseANSIEscapeCodes(true); InitLLVM X(argc, argv); cl::ParseCommandLineOptions(argc, argv, /*Overview*/ "", /*Errs*/ nullptr, "FILECHECK_OPTS"); // Select -dump-input* values. The -help documentation specifies the default // value and which value to choose if an option is specified multiple times. // In the latter case, the general rule of thumb is to choose the value that // provides the most information. DumpInputValue DumpInput = DumpInputs.empty() ? DumpInputFail : *std::max_element(DumpInputs.begin(), DumpInputs.end()); DumpInputFilterValue DumpInputFilter; if (DumpInputFilters.empty()) DumpInputFilter = DumpInput == DumpInputAlways ? DumpInputFilterAll : DumpInputFilterError; else DumpInputFilter = *std::max_element(DumpInputFilters.begin(), DumpInputFilters.end()); unsigned DumpInputContext = DumpInputContexts.empty() ? 5 : *std::max_element(DumpInputContexts.begin(), DumpInputContexts.end()); if (DumpInput == DumpInputHelp) { DumpInputAnnotationHelp(outs()); return 0; } if (CheckFilename.empty()) { errs() << " not specified\n"; return 2; } FileCheckRequest Req; append_range(Req.CheckPrefixes, CheckPrefixes); append_range(Req.CommentPrefixes, CommentPrefixes); append_range(Req.ImplicitCheckNot, ImplicitCheckNot); bool GlobalDefineError = false; for (StringRef G : GlobalDefines) { size_t EqIdx = G.find('='); if (EqIdx == std::string::npos) { errs() << "Missing equal sign in command-line definition '-D" << G << "'\n"; GlobalDefineError = true; continue; } if (EqIdx == 0) { errs() << "Missing variable name in command-line definition '-D" << G << "'\n"; GlobalDefineError = true; continue; } Req.GlobalDefines.push_back(G); } if (GlobalDefineError) return 2; Req.AllowEmptyInput = AllowEmptyInput; Req.AllowUnusedPrefixes = AllowUnusedPrefixes; Req.EnableVarScope = EnableVarScope; Req.AllowDeprecatedDagOverlap = AllowDeprecatedDagOverlap; Req.Verbose = Verbose; Req.VerboseVerbose = VerboseVerbose; Req.NoCanonicalizeWhiteSpace = NoCanonicalizeWhiteSpace; Req.MatchFullLines = MatchFullLines; Req.IgnoreCase = IgnoreCase; if (VerboseVerbose) Req.Verbose = true; FileCheck FC(Req); if (!FC.ValidateCheckPrefixes()) return 2; Regex PrefixRE = FC.buildCheckPrefixRegex(); std::string REError; if (!PrefixRE.isValid(REError)) { errs() << "Unable to combine check-prefix strings into a prefix regular " "expression! This is likely a bug in FileCheck's verification of " "the check-prefix strings. Regular expression parsing failed " "with the following error: " << REError << "\n"; return 2; } SourceMgr SM; // Read the expected strings from the check file. ErrorOr> CheckFileOrErr = MemoryBuffer::getFileOrSTDIN(CheckFilename, /*IsText=*/true); if (std::error_code EC = CheckFileOrErr.getError()) { errs() << "Could not open check file '" << CheckFilename << "': " << EC.message() << '\n'; return 2; } MemoryBuffer &CheckFile = *CheckFileOrErr.get(); SmallString<4096> CheckFileBuffer; StringRef CheckFileText = FC.CanonicalizeFile(CheckFile, CheckFileBuffer); unsigned CheckFileBufferID = SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( CheckFileText, CheckFile.getBufferIdentifier()), SMLoc()); std::pair ImpPatBufferIDRange; if (FC.readCheckFile(SM, CheckFileText, PrefixRE, &ImpPatBufferIDRange)) return 2; // Open the file to check and add it to SourceMgr. ErrorOr> InputFileOrErr = MemoryBuffer::getFileOrSTDIN(InputFilename, /*IsText=*/true); if (InputFilename == "-") InputFilename = ""; // Overwrite for improved diagnostic messages if (std::error_code EC = InputFileOrErr.getError()) { errs() << "Could not open input file '" << InputFilename << "': " << EC.message() << '\n'; return 2; } MemoryBuffer &InputFile = *InputFileOrErr.get(); if (InputFile.getBufferSize() == 0 && !AllowEmptyInput) { errs() << "FileCheck error: '" << InputFilename << "' is empty.\n"; DumpCommandLine(argc, argv); return 2; } SmallString<4096> InputFileBuffer; StringRef InputFileText = FC.CanonicalizeFile(InputFile, InputFileBuffer); SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( InputFileText, InputFile.getBufferIdentifier()), SMLoc()); std::vector Diags; int ExitCode = FC.checkInput(SM, InputFileText, DumpInput == DumpInputNever ? nullptr : &Diags) ? EXIT_SUCCESS : 1; if (DumpInput == DumpInputAlways || (ExitCode == 1 && DumpInput == DumpInputFail)) { errs() << "\n" << "Input file: " << InputFilename << "\n" << "Check file: " << CheckFilename << "\n" << "\n" << "-dump-input=help explains the following input dump.\n" << "\n"; std::vector Annotations; unsigned LabelWidth; BuildInputAnnotations(SM, CheckFileBufferID, ImpPatBufferIDRange, Diags, Annotations, LabelWidth); DumpAnnotatedInput(errs(), Req, DumpInputFilter, DumpInputContext, InputFileText, Annotations, LabelWidth); } return ExitCode; } ./ldc-1.36.0-src/utils/README.md0000644000175000017500000000117414564454261016130 0ustar matthiasmatthiasLDC – Utils =============================== The `/utils` directory contains utilities that are used in building LDC (`gen_gccbuiltins.cpp`) and testing LDC (`not` and `FileCheck`). `not` is copied from LLVM `FileCheck` is copied from LLVM, and versioned for each LLVM version that we support (for example, FileCheck-3.9.cpp does not compile with LLVM 3.5). Older versions of FileCheck contain modifications such that they contain new features/bugfixes but still compile with older LLVM versions. How `not` and `FileCheck` are used is decribed here: [LDC Lit-based testsuite](http://wiki.dlang.org/?title=LDC_Lit-based_testsuite). ./ldc-1.36.0-src/utils/FileCheck-15.cpp0000644000175000017500000011002414564454261017410 0ustar matthiasmatthias//===- FileCheck.cpp - Check that File's Contents match what is expected --===// // // 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 // //===----------------------------------------------------------------------===// // // FileCheck does a line-by line check of a file that validates whether it // contains the expected content. This is useful for regression tests etc. // // This program exits with an exit status of 2 on error, exit status of 0 if // the file matched the expected contents, and exit status of 1 if it did not // contain the expected contents. // //===----------------------------------------------------------------------===// #include "llvm/FileCheck/FileCheck.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Process.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" #include #include using namespace llvm; static cl::extrahelp FileCheckOptsEnv( "\nOptions are parsed from the environment variable FILECHECK_OPTS and\n" "from the command line.\n"); static cl::opt CheckFilename(cl::Positional, cl::desc(""), cl::Optional); static cl::opt InputFilename("input-file", cl::desc("File to check (defaults to stdin)"), cl::init("-"), cl::value_desc("filename")); static cl::list CheckPrefixes( "check-prefix", cl::desc("Prefix to use from check file (defaults to 'CHECK')")); static cl::alias CheckPrefixesAlias( "check-prefixes", cl::aliasopt(CheckPrefixes), cl::CommaSeparated, cl::NotHidden, cl::desc( "Alias for -check-prefix permitting multiple comma separated values")); static cl::list CommentPrefixes( "comment-prefixes", cl::CommaSeparated, cl::Hidden, cl::desc("Comma-separated list of comment prefixes to use from check file\n" "(defaults to 'COM,RUN'). Please avoid using this feature in\n" "LLVM's LIT-based test suites, which should be easier to\n" "maintain if they all follow a consistent comment style. This\n" "feature is meant for non-LIT test suites using FileCheck.")); static cl::opt NoCanonicalizeWhiteSpace( "strict-whitespace", cl::desc("Do not treat all horizontal whitespace as equivalent")); static cl::opt IgnoreCase( "ignore-case", cl::desc("Use case-insensitive matching")); static cl::list ImplicitCheckNot( "implicit-check-not", cl::desc("Add an implicit negative check with this pattern to every\n" "positive check. This can be used to ensure that no instances of\n" "this pattern occur which are not matched by a positive pattern"), cl::value_desc("pattern")); static cl::list GlobalDefines("D", cl::AlwaysPrefix, cl::desc("Define a variable to be used in capture patterns."), cl::value_desc("VAR=VALUE")); static cl::opt AllowEmptyInput( "allow-empty", cl::init(false), cl::desc("Allow the input file to be empty. This is useful when making\n" "checks that some error message does not occur, for example.")); static cl::opt AllowUnusedPrefixes( "allow-unused-prefixes", cl::desc("Allow prefixes to be specified but not appear in the test.")); static cl::opt MatchFullLines( "match-full-lines", cl::init(false), cl::desc("Require all positive matches to cover an entire input line.\n" "Allows leading and trailing whitespace if --strict-whitespace\n" "is not also passed.")); static cl::opt EnableVarScope( "enable-var-scope", cl::init(false), cl::desc("Enables scope for regex variables. Variables with names that\n" "do not start with '$' will be reset at the beginning of\n" "each CHECK-LABEL block.")); static cl::opt AllowDeprecatedDagOverlap( "allow-deprecated-dag-overlap", cl::init(false), cl::desc("Enable overlapping among matches in a group of consecutive\n" "CHECK-DAG directives. This option is deprecated and is only\n" "provided for convenience as old tests are migrated to the new\n" "non-overlapping CHECK-DAG implementation.\n")); static cl::opt Verbose( "v", cl::desc("Print directive pattern matches, or add them to the input dump\n" "if enabled.\n")); static cl::opt VerboseVerbose( "vv", cl::desc("Print information helpful in diagnosing internal FileCheck\n" "issues, or add it to the input dump if enabled. Implies\n" "-v.\n")); // The order of DumpInputValue members affects their precedence, as documented // for -dump-input below. enum DumpInputValue { DumpInputNever, DumpInputFail, DumpInputAlways, DumpInputHelp }; static cl::list DumpInputs( "dump-input", cl::desc("Dump input to stderr, adding annotations representing\n" "currently enabled diagnostics. When there are multiple\n" "occurrences of this option, the that appears earliest\n" "in the list below has precedence. The default is 'fail'.\n"), cl::value_desc("mode"), cl::values(clEnumValN(DumpInputHelp, "help", "Explain input dump and quit"), clEnumValN(DumpInputAlways, "always", "Always dump input"), clEnumValN(DumpInputFail, "fail", "Dump input on failure"), clEnumValN(DumpInputNever, "never", "Never dump input"))); // The order of DumpInputFilterValue members affects their precedence, as // documented for -dump-input-filter below. enum DumpInputFilterValue { DumpInputFilterError, DumpInputFilterAnnotation, DumpInputFilterAnnotationFull, DumpInputFilterAll }; static cl::list DumpInputFilters( "dump-input-filter", cl::desc("In the dump requested by -dump-input, print only input lines of\n" "kind plus any context specified by -dump-input-context.\n" "When there are multiple occurrences of this option, the \n" "that appears earliest in the list below has precedence. The\n" "default is 'error' when -dump-input=fail, and it's 'all' when\n" "-dump-input=always.\n"), cl::values(clEnumValN(DumpInputFilterAll, "all", "All input lines"), clEnumValN(DumpInputFilterAnnotationFull, "annotation-full", "Input lines with annotations"), clEnumValN(DumpInputFilterAnnotation, "annotation", "Input lines with starting points of annotations"), clEnumValN(DumpInputFilterError, "error", "Input lines with starting points of error " "annotations"))); static cl::list DumpInputContexts( "dump-input-context", cl::value_desc("N"), cl::desc("In the dump requested by -dump-input, print input lines\n" "before and input lines after any lines specified by\n" "-dump-input-filter. When there are multiple occurrences of\n" "this option, the largest specified has precedence. The\n" "default is 5.\n")); typedef cl::list::const_iterator prefix_iterator; static void DumpCommandLine(int argc, char **argv) { errs() << "FileCheck command line: "; for (int I = 0; I < argc; I++) errs() << " " << argv[I]; errs() << "\n"; } struct MarkerStyle { /// The starting char (before tildes) for marking the line. char Lead; /// What color to use for this annotation. raw_ostream::Colors Color; /// A note to follow the marker, or empty string if none. std::string Note; /// Does this marker indicate inclusion by -dump-input-filter=error? bool FiltersAsError; MarkerStyle() {} MarkerStyle(char Lead, raw_ostream::Colors Color, const std::string &Note = "", bool FiltersAsError = false) : Lead(Lead), Color(Color), Note(Note), FiltersAsError(FiltersAsError) { assert((!FiltersAsError || !Note.empty()) && "expected error diagnostic to have note"); } }; static MarkerStyle GetMarker(FileCheckDiag::MatchType MatchTy) { switch (MatchTy) { case FileCheckDiag::MatchFoundAndExpected: return MarkerStyle('^', raw_ostream::GREEN); case FileCheckDiag::MatchFoundButExcluded: return MarkerStyle('!', raw_ostream::RED, "error: no match expected", /*FiltersAsError=*/true); case FileCheckDiag::MatchFoundButWrongLine: return MarkerStyle('!', raw_ostream::RED, "error: match on wrong line", /*FiltersAsError=*/true); case FileCheckDiag::MatchFoundButDiscarded: return MarkerStyle('!', raw_ostream::CYAN, "discard: overlaps earlier match"); case FileCheckDiag::MatchFoundErrorNote: // Note should always be overridden within the FileCheckDiag. return MarkerStyle('!', raw_ostream::RED, "error: unknown error after match", /*FiltersAsError=*/true); case FileCheckDiag::MatchNoneAndExcluded: return MarkerStyle('X', raw_ostream::GREEN); case FileCheckDiag::MatchNoneButExpected: return MarkerStyle('X', raw_ostream::RED, "error: no match found", /*FiltersAsError=*/true); case FileCheckDiag::MatchNoneForInvalidPattern: return MarkerStyle('X', raw_ostream::RED, "error: match failed for invalid pattern", /*FiltersAsError=*/true); case FileCheckDiag::MatchFuzzy: return MarkerStyle('?', raw_ostream::MAGENTA, "possible intended match", /*FiltersAsError=*/true); } llvm_unreachable_internal("unexpected match type"); } static void DumpInputAnnotationHelp(raw_ostream &OS) { OS << "The following description was requested by -dump-input=help to\n" << "explain the input dump printed by FileCheck.\n" << "\n" << "Related command-line options:\n" << "\n" << " - -dump-input= enables or disables the input dump\n" << " - -dump-input-filter= filters the input lines\n" << " - -dump-input-context= adjusts the context of filtered lines\n" << " - -v and -vv add more annotations\n" << " - -color forces colors to be enabled both in the dump and below\n" << " - -help documents the above options in more detail\n" << "\n" << "These options can also be set via FILECHECK_OPTS. For example, for\n" << "maximum debugging output on failures:\n" << "\n" << " $ FILECHECK_OPTS='-dump-input-filter=all -vv -color' ninja check\n" << "\n" << "Input dump annotation format:\n" << "\n"; // Labels for input lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "L:"; OS << " labels line number L of the input file\n" << " An extra space is added after each input line to represent" << " the\n" << " newline character\n"; // Labels for annotation lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L"; OS << " labels the only match result for either (1) a pattern of type T" << " from\n" << " line L of the check file if L is an integer or (2) the" << " I-th implicit\n" << " pattern if L is \"imp\" followed by an integer " << "I (index origin one)\n"; OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L'N"; OS << " labels the Nth match result for such a pattern\n"; // Markers on annotation lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "^~~"; OS << " marks good match (reported if -v)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "!~~"; OS << " marks bad match, such as:\n" << " - CHECK-NEXT on same line as previous match (error)\n" << " - CHECK-NOT found (error)\n" << " - CHECK-DAG overlapping match (discarded, reported if " << "-vv)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "X~~"; OS << " marks search range when no match is found, such as:\n" << " - CHECK-NEXT not found (error)\n" << " - CHECK-NOT not found (success, reported if -vv)\n" << " - CHECK-DAG not found after discarded matches (error)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "?"; OS << " marks fuzzy match when no match is found\n"; // Elided lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "..."; OS << " indicates elided input lines and annotations, as specified by\n" << " -dump-input-filter and -dump-input-context\n"; // Colors. OS << " - colors "; WithColor(OS, raw_ostream::GREEN, true) << "success"; OS << ", "; WithColor(OS, raw_ostream::RED, true) << "error"; OS << ", "; WithColor(OS, raw_ostream::MAGENTA, true) << "fuzzy match"; OS << ", "; WithColor(OS, raw_ostream::CYAN, true, false) << "discarded match"; OS << ", "; WithColor(OS, raw_ostream::CYAN, true, true) << "unmatched input"; OS << "\n"; } /// An annotation for a single input line. struct InputAnnotation { /// The index of the match result across all checks unsigned DiagIndex; /// The label for this annotation. std::string Label; /// Is this the initial fragment of a diagnostic that has been broken across /// multiple lines? bool IsFirstLine; /// What input line (one-origin indexing) this annotation marks. This might /// be different from the starting line of the original diagnostic if /// !IsFirstLine. unsigned InputLine; /// The column range (one-origin indexing, open end) in which to mark the /// input line. If InputEndCol is UINT_MAX, treat it as the last column /// before the newline. unsigned InputStartCol, InputEndCol; /// The marker to use. MarkerStyle Marker; /// Whether this annotation represents a good match for an expected pattern. bool FoundAndExpectedMatch; }; /// Get an abbreviation for the check type. static std::string GetCheckTypeAbbreviation(Check::FileCheckType Ty) { switch (Ty) { case Check::CheckPlain: if (Ty.getCount() > 1) return "count"; return "check"; case Check::CheckNext: return "next"; case Check::CheckSame: return "same"; case Check::CheckNot: return "not"; case Check::CheckDAG: return "dag"; case Check::CheckLabel: return "label"; case Check::CheckEmpty: return "empty"; case Check::CheckComment: return "com"; case Check::CheckEOF: return "eof"; case Check::CheckBadNot: return "bad-not"; case Check::CheckBadCount: return "bad-count"; case Check::CheckMisspelled: return "misspelled"; case Check::CheckNone: llvm_unreachable("invalid FileCheckType"); } llvm_unreachable("unknown FileCheckType"); } static void BuildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID, const std::pair &ImpPatBufferIDRange, const std::vector &Diags, std::vector &Annotations, unsigned &LabelWidth) { struct CompareSMLoc { bool operator()(const SMLoc &LHS, const SMLoc &RHS) const { return LHS.getPointer() < RHS.getPointer(); } }; // How many diagnostics does each pattern have? std::map DiagCountPerPattern; for (auto Diag : Diags) ++DiagCountPerPattern[Diag.CheckLoc]; // How many diagnostics have we seen so far per pattern? std::map DiagIndexPerPattern; // How many total diagnostics have we seen so far? unsigned DiagIndex = 0; // What's the widest label? LabelWidth = 0; for (auto DiagItr = Diags.begin(), DiagEnd = Diags.end(); DiagItr != DiagEnd; ++DiagItr) { InputAnnotation A; A.DiagIndex = DiagIndex++; // Build label, which uniquely identifies this check result. unsigned CheckBufferID = SM.FindBufferContainingLoc(DiagItr->CheckLoc); auto CheckLineAndCol = SM.getLineAndColumn(DiagItr->CheckLoc, CheckBufferID); llvm::raw_string_ostream Label(A.Label); Label << GetCheckTypeAbbreviation(DiagItr->CheckTy) << ":"; if (CheckBufferID == CheckFileBufferID) Label << CheckLineAndCol.first; else if (ImpPatBufferIDRange.first <= CheckBufferID && CheckBufferID < ImpPatBufferIDRange.second) Label << "imp" << (CheckBufferID - ImpPatBufferIDRange.first + 1); else llvm_unreachable("expected diagnostic's check location to be either in " "the check file or for an implicit pattern"); if (DiagCountPerPattern[DiagItr->CheckLoc] > 1) Label << "'" << DiagIndexPerPattern[DiagItr->CheckLoc]++; LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size()); A.Marker = GetMarker(DiagItr->MatchTy); if (!DiagItr->Note.empty()) { A.Marker.Note = DiagItr->Note; // It's less confusing if notes that don't actually have ranges don't have // markers. For example, a marker for 'with "VAR" equal to "5"' would // seem to indicate where "VAR" matches, but the location we actually have // for the marker simply points to the start of the match/search range for // the full pattern of which the substitution is potentially just one // component. if (DiagItr->InputStartLine == DiagItr->InputEndLine && DiagItr->InputStartCol == DiagItr->InputEndCol) A.Marker.Lead = ' '; } if (DiagItr->MatchTy == FileCheckDiag::MatchFoundErrorNote) { assert(!DiagItr->Note.empty() && "expected custom note for MatchFoundErrorNote"); A.Marker.Note = "error: " + A.Marker.Note; } A.FoundAndExpectedMatch = DiagItr->MatchTy == FileCheckDiag::MatchFoundAndExpected; // Compute the mark location, and break annotation into multiple // annotations if it spans multiple lines. A.IsFirstLine = true; A.InputLine = DiagItr->InputStartLine; A.InputStartCol = DiagItr->InputStartCol; if (DiagItr->InputStartLine == DiagItr->InputEndLine) { // Sometimes ranges are empty in order to indicate a specific point, but // that would mean nothing would be marked, so adjust the range to // include the following character. A.InputEndCol = std::max(DiagItr->InputStartCol + 1, DiagItr->InputEndCol); Annotations.push_back(A); } else { assert(DiagItr->InputStartLine < DiagItr->InputEndLine && "expected input range not to be inverted"); A.InputEndCol = UINT_MAX; Annotations.push_back(A); for (unsigned L = DiagItr->InputStartLine + 1, E = DiagItr->InputEndLine; L <= E; ++L) { // If a range ends before the first column on a line, then it has no // characters on that line, so there's nothing to render. if (DiagItr->InputEndCol == 1 && L == E) break; InputAnnotation B; B.DiagIndex = A.DiagIndex; B.Label = A.Label; B.IsFirstLine = false; B.InputLine = L; B.Marker = A.Marker; B.Marker.Lead = '~'; B.Marker.Note = ""; B.InputStartCol = 1; if (L != E) B.InputEndCol = UINT_MAX; else B.InputEndCol = DiagItr->InputEndCol; B.FoundAndExpectedMatch = A.FoundAndExpectedMatch; Annotations.push_back(B); } } } } static unsigned FindInputLineInFilter( DumpInputFilterValue DumpInputFilter, unsigned CurInputLine, const std::vector::iterator &AnnotationBeg, const std::vector::iterator &AnnotationEnd) { if (DumpInputFilter == DumpInputFilterAll) return CurInputLine; for (auto AnnotationItr = AnnotationBeg; AnnotationItr != AnnotationEnd; ++AnnotationItr) { switch (DumpInputFilter) { case DumpInputFilterAll: llvm_unreachable("unexpected DumpInputFilterAll"); break; case DumpInputFilterAnnotationFull: return AnnotationItr->InputLine; case DumpInputFilterAnnotation: if (AnnotationItr->IsFirstLine) return AnnotationItr->InputLine; break; case DumpInputFilterError: if (AnnotationItr->IsFirstLine && AnnotationItr->Marker.FiltersAsError) return AnnotationItr->InputLine; break; } } return UINT_MAX; } /// To OS, print a vertical ellipsis (right-justified at LabelWidth) if it would /// occupy less lines than ElidedLines, but print ElidedLines otherwise. Either /// way, clear ElidedLines. Thus, if ElidedLines is empty, do nothing. static void DumpEllipsisOrElidedLines(raw_ostream &OS, std::string &ElidedLines, unsigned LabelWidth) { if (ElidedLines.empty()) return; unsigned EllipsisLines = 3; if (EllipsisLines < StringRef(ElidedLines).count('\n')) { for (unsigned i = 0; i < EllipsisLines; ++i) { WithColor(OS, raw_ostream::BLACK, /*Bold=*/true) << right_justify(".", LabelWidth); OS << '\n'; } } else OS << ElidedLines; ElidedLines.clear(); } static void DumpAnnotatedInput(raw_ostream &OS, const FileCheckRequest &Req, DumpInputFilterValue DumpInputFilter, unsigned DumpInputContext, StringRef InputFileText, std::vector &Annotations, unsigned LabelWidth) { OS << "Input was:\n<<<<<<\n"; // Sort annotations. llvm::sort(Annotations, [](const InputAnnotation &A, const InputAnnotation &B) { // 1. Sort annotations in the order of the input lines. // // This makes it easier to find relevant annotations while // iterating input lines in the implementation below. FileCheck // does not always produce diagnostics in the order of input // lines due to, for example, CHECK-DAG and CHECK-NOT. if (A.InputLine != B.InputLine) return A.InputLine < B.InputLine; // 2. Sort annotations in the temporal order FileCheck produced // their associated diagnostics. // // This sort offers several benefits: // // A. On a single input line, the order of annotations reflects // the FileCheck logic for processing directives/patterns. // This can be helpful in understanding cases in which the // order of the associated directives/patterns in the check // file or on the command line either (i) does not match the // temporal order in which FileCheck looks for matches for the // directives/patterns (due to, for example, CHECK-LABEL, // CHECK-NOT, or `--implicit-check-not`) or (ii) does match // that order but does not match the order of those // diagnostics along an input line (due to, for example, // CHECK-DAG). // // On the other hand, because our presentation format presents // input lines in order, there's no clear way to offer the // same benefit across input lines. For consistency, it might // then seem worthwhile to have annotations on a single line // also sorted in input order (that is, by input column). // However, in practice, this appears to be more confusing // than helpful. Perhaps it's intuitive to expect annotations // to be listed in the temporal order in which they were // produced except in cases the presentation format obviously // and inherently cannot support it (that is, across input // lines). // // B. When diagnostics' annotations are split among multiple // input lines, the user must track them from one input line // to the next. One property of the sort chosen here is that // it facilitates the user in this regard by ensuring the // following: when comparing any two input lines, a // diagnostic's annotations are sorted in the same position // relative to all other diagnostics' annotations. return A.DiagIndex < B.DiagIndex; }); // Compute the width of the label column. const unsigned char *InputFilePtr = InputFileText.bytes_begin(), *InputFileEnd = InputFileText.bytes_end(); unsigned LineCount = InputFileText.count('\n'); if (InputFileEnd[-1] != '\n') ++LineCount; unsigned LineNoWidth = std::log10(LineCount) + 1; // +3 below adds spaces (1) to the left of the (right-aligned) line numbers // on input lines and (2) to the right of the (left-aligned) labels on // annotation lines so that input lines and annotation lines are more // visually distinct. For example, the spaces on the annotation lines ensure // that input line numbers and check directive line numbers never align // horizontally. Those line numbers might not even be for the same file. // One space would be enough to achieve that, but more makes it even easier // to see. LabelWidth = std::max(LabelWidth, LineNoWidth) + 3; // Print annotated input lines. unsigned PrevLineInFilter = 0; // 0 means none so far unsigned NextLineInFilter = 0; // 0 means uncomputed, UINT_MAX means none std::string ElidedLines; raw_string_ostream ElidedLinesOS(ElidedLines); ColorMode TheColorMode = WithColor(OS).colorsEnabled() ? ColorMode::Enable : ColorMode::Disable; if (TheColorMode == ColorMode::Enable) ElidedLinesOS.enable_colors(true); auto AnnotationItr = Annotations.begin(), AnnotationEnd = Annotations.end(); for (unsigned Line = 1; InputFilePtr != InputFileEnd || AnnotationItr != AnnotationEnd; ++Line) { const unsigned char *InputFileLine = InputFilePtr; // Compute the previous and next line included by the filter. if (NextLineInFilter < Line) NextLineInFilter = FindInputLineInFilter(DumpInputFilter, Line, AnnotationItr, AnnotationEnd); assert(NextLineInFilter && "expected NextLineInFilter to be computed"); if (NextLineInFilter == Line) PrevLineInFilter = Line; // Elide this input line and its annotations if it's not within the // context specified by -dump-input-context of an input line included by // -dump-input-filter. However, in case the resulting ellipsis would occupy // more lines than the input lines and annotations it elides, buffer the // elided lines and annotations so we can print them instead. raw_ostream *LineOS; if ((!PrevLineInFilter || PrevLineInFilter + DumpInputContext < Line) && (NextLineInFilter == UINT_MAX || Line + DumpInputContext < NextLineInFilter)) LineOS = &ElidedLinesOS; else { LineOS = &OS; DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); } // Print right-aligned line number. WithColor(*LineOS, raw_ostream::BLACK, /*Bold=*/true, /*BF=*/false, TheColorMode) << format_decimal(Line, LabelWidth) << ": "; // For the case where -v and colors are enabled, find the annotations for // good matches for expected patterns in order to highlight everything // else in the line. There are no such annotations if -v is disabled. std::vector FoundAndExpectedMatches; if (Req.Verbose && TheColorMode == ColorMode::Enable) { for (auto I = AnnotationItr; I != AnnotationEnd && I->InputLine == Line; ++I) { if (I->FoundAndExpectedMatch) FoundAndExpectedMatches.push_back(*I); } } // Print numbered line with highlighting where there are no matches for // expected patterns. bool Newline = false; { WithColor COS(*LineOS, raw_ostream::SAVEDCOLOR, /*Bold=*/false, /*BG=*/false, TheColorMode); bool InMatch = false; if (Req.Verbose) COS.changeColor(raw_ostream::CYAN, true, true); for (unsigned Col = 1; InputFilePtr != InputFileEnd && !Newline; ++Col) { bool WasInMatch = InMatch; InMatch = false; for (auto M : FoundAndExpectedMatches) { if (M.InputStartCol <= Col && Col < M.InputEndCol) { InMatch = true; break; } } if (!WasInMatch && InMatch) COS.resetColor(); else if (WasInMatch && !InMatch) COS.changeColor(raw_ostream::CYAN, true, true); if (*InputFilePtr == '\n') { Newline = true; COS << ' '; } else COS << *InputFilePtr; ++InputFilePtr; } } *LineOS << '\n'; unsigned InputLineWidth = InputFilePtr - InputFileLine; // Print any annotations. while (AnnotationItr != AnnotationEnd && AnnotationItr->InputLine == Line) { WithColor COS(*LineOS, AnnotationItr->Marker.Color, /*Bold=*/true, /*BG=*/false, TheColorMode); // The two spaces below are where the ": " appears on input lines. COS << left_justify(AnnotationItr->Label, LabelWidth) << " "; unsigned Col; for (Col = 1; Col < AnnotationItr->InputStartCol; ++Col) COS << ' '; COS << AnnotationItr->Marker.Lead; // If InputEndCol=UINT_MAX, stop at InputLineWidth. for (++Col; Col < AnnotationItr->InputEndCol && Col <= InputLineWidth; ++Col) COS << '~'; const std::string &Note = AnnotationItr->Marker.Note; if (!Note.empty()) { // Put the note at the end of the input line. If we were to instead // put the note right after the marker, subsequent annotations for the // same input line might appear to mark this note instead of the input // line. for (; Col <= InputLineWidth; ++Col) COS << ' '; COS << ' ' << Note; } COS << '\n'; ++AnnotationItr; } } DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); OS << ">>>>>>\n"; } int main(int argc, char **argv) { // Enable use of ANSI color codes because FileCheck is using them to // highlight text. llvm::sys::Process::UseANSIEscapeCodes(true); InitLLVM X(argc, argv); cl::ParseCommandLineOptions(argc, argv, /*Overview*/ "", /*Errs*/ nullptr, "FILECHECK_OPTS"); // Select -dump-input* values. The -help documentation specifies the default // value and which value to choose if an option is specified multiple times. // In the latter case, the general rule of thumb is to choose the value that // provides the most information. DumpInputValue DumpInput = DumpInputs.empty() ? DumpInputFail : *std::max_element(DumpInputs.begin(), DumpInputs.end()); DumpInputFilterValue DumpInputFilter; if (DumpInputFilters.empty()) DumpInputFilter = DumpInput == DumpInputAlways ? DumpInputFilterAll : DumpInputFilterError; else DumpInputFilter = *std::max_element(DumpInputFilters.begin(), DumpInputFilters.end()); unsigned DumpInputContext = DumpInputContexts.empty() ? 5 : *std::max_element(DumpInputContexts.begin(), DumpInputContexts.end()); if (DumpInput == DumpInputHelp) { DumpInputAnnotationHelp(outs()); return 0; } if (CheckFilename.empty()) { errs() << " not specified\n"; return 2; } FileCheckRequest Req; append_range(Req.CheckPrefixes, CheckPrefixes); append_range(Req.CommentPrefixes, CommentPrefixes); append_range(Req.ImplicitCheckNot, ImplicitCheckNot); bool GlobalDefineError = false; for (StringRef G : GlobalDefines) { size_t EqIdx = G.find('='); if (EqIdx == std::string::npos) { errs() << "Missing equal sign in command-line definition '-D" << G << "'\n"; GlobalDefineError = true; continue; } if (EqIdx == 0) { errs() << "Missing variable name in command-line definition '-D" << G << "'\n"; GlobalDefineError = true; continue; } Req.GlobalDefines.push_back(G); } if (GlobalDefineError) return 2; Req.AllowEmptyInput = AllowEmptyInput; Req.AllowUnusedPrefixes = AllowUnusedPrefixes; Req.EnableVarScope = EnableVarScope; Req.AllowDeprecatedDagOverlap = AllowDeprecatedDagOverlap; Req.Verbose = Verbose; Req.VerboseVerbose = VerboseVerbose; Req.NoCanonicalizeWhiteSpace = NoCanonicalizeWhiteSpace; Req.MatchFullLines = MatchFullLines; Req.IgnoreCase = IgnoreCase; if (VerboseVerbose) Req.Verbose = true; FileCheck FC(Req); if (!FC.ValidateCheckPrefixes()) return 2; Regex PrefixRE = FC.buildCheckPrefixRegex(); std::string REError; if (!PrefixRE.isValid(REError)) { errs() << "Unable to combine check-prefix strings into a prefix regular " "expression! This is likely a bug in FileCheck's verification of " "the check-prefix strings. Regular expression parsing failed " "with the following error: " << REError << "\n"; return 2; } SourceMgr SM; // Read the expected strings from the check file. ErrorOr> CheckFileOrErr = MemoryBuffer::getFileOrSTDIN(CheckFilename, /*IsText=*/true); if (std::error_code EC = CheckFileOrErr.getError()) { errs() << "Could not open check file '" << CheckFilename << "': " << EC.message() << '\n'; return 2; } MemoryBuffer &CheckFile = *CheckFileOrErr.get(); SmallString<4096> CheckFileBuffer; StringRef CheckFileText = FC.CanonicalizeFile(CheckFile, CheckFileBuffer); unsigned CheckFileBufferID = SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( CheckFileText, CheckFile.getBufferIdentifier()), SMLoc()); std::pair ImpPatBufferIDRange; if (FC.readCheckFile(SM, CheckFileText, PrefixRE, &ImpPatBufferIDRange)) return 2; // Open the file to check and add it to SourceMgr. ErrorOr> InputFileOrErr = MemoryBuffer::getFileOrSTDIN(InputFilename, /*IsText=*/true); if (InputFilename == "-") InputFilename = ""; // Overwrite for improved diagnostic messages if (std::error_code EC = InputFileOrErr.getError()) { errs() << "Could not open input file '" << InputFilename << "': " << EC.message() << '\n'; return 2; } MemoryBuffer &InputFile = *InputFileOrErr.get(); if (InputFile.getBufferSize() == 0 && !AllowEmptyInput) { errs() << "FileCheck error: '" << InputFilename << "' is empty.\n"; DumpCommandLine(argc, argv); return 2; } SmallString<4096> InputFileBuffer; StringRef InputFileText = FC.CanonicalizeFile(InputFile, InputFileBuffer); SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( InputFileText, InputFile.getBufferIdentifier()), SMLoc()); std::vector Diags; int ExitCode = FC.checkInput(SM, InputFileText, DumpInput == DumpInputNever ? nullptr : &Diags) ? EXIT_SUCCESS : 1; if (DumpInput == DumpInputAlways || (ExitCode == 1 && DumpInput == DumpInputFail)) { errs() << "\n" << "Input file: " << InputFilename << "\n" << "Check file: " << CheckFilename << "\n" << "\n" << "-dump-input=help explains the following input dump.\n" << "\n"; std::vector Annotations; unsigned LabelWidth; BuildInputAnnotations(SM, CheckFileBufferID, ImpPatBufferIDRange, Diags, Annotations, LabelWidth); DumpAnnotatedInput(errs(), Req, DumpInputFilter, DumpInputContext, InputFileText, Annotations, LabelWidth); } return ExitCode; } ./ldc-1.36.0-src/utils/FileCheck-13.cpp0000644000175000017500000011001514564454261017406 0ustar matthiasmatthias//===- FileCheck.cpp - Check that File's Contents match what is expected --===// // // 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 // //===----------------------------------------------------------------------===// // // FileCheck does a line-by line check of a file that validates whether it // contains the expected content. This is useful for regression tests etc. // // This program exits with an exit status of 2 on error, exit status of 0 if // the file matched the expected contents, and exit status of 1 if it did not // contain the expected contents. // //===----------------------------------------------------------------------===// #include "llvm/FileCheck/FileCheck.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/Process.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" #include #include using namespace llvm; static cl::extrahelp FileCheckOptsEnv( "\nOptions are parsed from the environment variable FILECHECK_OPTS and\n" "from the command line.\n"); static cl::opt CheckFilename(cl::Positional, cl::desc(""), cl::Optional); static cl::opt InputFilename("input-file", cl::desc("File to check (defaults to stdin)"), cl::init("-"), cl::value_desc("filename")); static cl::list CheckPrefixes( "check-prefix", cl::desc("Prefix to use from check file (defaults to 'CHECK')")); static cl::alias CheckPrefixesAlias( "check-prefixes", cl::aliasopt(CheckPrefixes), cl::CommaSeparated, cl::NotHidden, cl::desc( "Alias for -check-prefix permitting multiple comma separated values")); static cl::list CommentPrefixes( "comment-prefixes", cl::CommaSeparated, cl::Hidden, cl::desc("Comma-separated list of comment prefixes to use from check file\n" "(defaults to 'COM,RUN'). Please avoid using this feature in\n" "LLVM's LIT-based test suites, which should be easier to\n" "maintain if they all follow a consistent comment style. This\n" "feature is meant for non-LIT test suites using FileCheck.")); static cl::opt NoCanonicalizeWhiteSpace( "strict-whitespace", cl::desc("Do not treat all horizontal whitespace as equivalent")); static cl::opt IgnoreCase( "ignore-case", cl::desc("Use case-insensitive matching")); static cl::list ImplicitCheckNot( "implicit-check-not", cl::desc("Add an implicit negative check with this pattern to every\n" "positive check. This can be used to ensure that no instances of\n" "this pattern occur which are not matched by a positive pattern"), cl::value_desc("pattern")); static cl::list GlobalDefines("D", cl::AlwaysPrefix, cl::desc("Define a variable to be used in capture patterns."), cl::value_desc("VAR=VALUE")); static cl::opt AllowEmptyInput( "allow-empty", cl::init(false), cl::desc("Allow the input file to be empty. This is useful when making\n" "checks that some error message does not occur, for example.")); static cl::opt AllowUnusedPrefixes( "allow-unused-prefixes", cl::init(false), cl::ZeroOrMore, cl::desc("Allow prefixes to be specified but not appear in the test.")); static cl::opt MatchFullLines( "match-full-lines", cl::init(false), cl::desc("Require all positive matches to cover an entire input line.\n" "Allows leading and trailing whitespace if --strict-whitespace\n" "is not also passed.")); static cl::opt EnableVarScope( "enable-var-scope", cl::init(false), cl::desc("Enables scope for regex variables. Variables with names that\n" "do not start with '$' will be reset at the beginning of\n" "each CHECK-LABEL block.")); static cl::opt AllowDeprecatedDagOverlap( "allow-deprecated-dag-overlap", cl::init(false), cl::desc("Enable overlapping among matches in a group of consecutive\n" "CHECK-DAG directives. This option is deprecated and is only\n" "provided for convenience as old tests are migrated to the new\n" "non-overlapping CHECK-DAG implementation.\n")); static cl::opt Verbose( "v", cl::init(false), cl::ZeroOrMore, cl::desc("Print directive pattern matches, or add them to the input dump\n" "if enabled.\n")); static cl::opt VerboseVerbose( "vv", cl::init(false), cl::ZeroOrMore, cl::desc("Print information helpful in diagnosing internal FileCheck\n" "issues, or add it to the input dump if enabled. Implies\n" "-v.\n")); // The order of DumpInputValue members affects their precedence, as documented // for -dump-input below. enum DumpInputValue { DumpInputNever, DumpInputFail, DumpInputAlways, DumpInputHelp }; static cl::list DumpInputs( "dump-input", cl::desc("Dump input to stderr, adding annotations representing\n" "currently enabled diagnostics. When there are multiple\n" "occurrences of this option, the that appears earliest\n" "in the list below has precedence. The default is 'fail'.\n"), cl::value_desc("mode"), cl::values(clEnumValN(DumpInputHelp, "help", "Explain input dump and quit"), clEnumValN(DumpInputAlways, "always", "Always dump input"), clEnumValN(DumpInputFail, "fail", "Dump input on failure"), clEnumValN(DumpInputNever, "never", "Never dump input"))); // The order of DumpInputFilterValue members affects their precedence, as // documented for -dump-input-filter below. enum DumpInputFilterValue { DumpInputFilterError, DumpInputFilterAnnotation, DumpInputFilterAnnotationFull, DumpInputFilterAll }; static cl::list DumpInputFilters( "dump-input-filter", cl::desc("In the dump requested by -dump-input, print only input lines of\n" "kind plus any context specified by -dump-input-context.\n" "When there are multiple occurrences of this option, the \n" "that appears earliest in the list below has precedence. The\n" "default is 'error' when -dump-input=fail, and it's 'all' when\n" "-dump-input=always.\n"), cl::values(clEnumValN(DumpInputFilterAll, "all", "All input lines"), clEnumValN(DumpInputFilterAnnotationFull, "annotation-full", "Input lines with annotations"), clEnumValN(DumpInputFilterAnnotation, "annotation", "Input lines with starting points of annotations"), clEnumValN(DumpInputFilterError, "error", "Input lines with starting points of error " "annotations"))); static cl::list DumpInputContexts( "dump-input-context", cl::value_desc("N"), cl::desc("In the dump requested by -dump-input, print input lines\n" "before and input lines after any lines specified by\n" "-dump-input-filter. When there are multiple occurrences of\n" "this option, the largest specified has precedence. The\n" "default is 5.\n")); typedef cl::list::const_iterator prefix_iterator; static void DumpCommandLine(int argc, char **argv) { errs() << "FileCheck command line: "; for (int I = 0; I < argc; I++) errs() << " " << argv[I]; errs() << "\n"; } struct MarkerStyle { /// The starting char (before tildes) for marking the line. char Lead; /// What color to use for this annotation. raw_ostream::Colors Color; /// A note to follow the marker, or empty string if none. std::string Note; /// Does this marker indicate inclusion by -dump-input-filter=error? bool FiltersAsError; MarkerStyle() {} MarkerStyle(char Lead, raw_ostream::Colors Color, const std::string &Note = "", bool FiltersAsError = false) : Lead(Lead), Color(Color), Note(Note), FiltersAsError(FiltersAsError) { assert((!FiltersAsError || !Note.empty()) && "expected error diagnostic to have note"); } }; static MarkerStyle GetMarker(FileCheckDiag::MatchType MatchTy) { switch (MatchTy) { case FileCheckDiag::MatchFoundAndExpected: return MarkerStyle('^', raw_ostream::GREEN); case FileCheckDiag::MatchFoundButExcluded: return MarkerStyle('!', raw_ostream::RED, "error: no match expected", /*FiltersAsError=*/true); case FileCheckDiag::MatchFoundButWrongLine: return MarkerStyle('!', raw_ostream::RED, "error: match on wrong line", /*FiltersAsError=*/true); case FileCheckDiag::MatchFoundButDiscarded: return MarkerStyle('!', raw_ostream::CYAN, "discard: overlaps earlier match"); case FileCheckDiag::MatchFoundErrorNote: // Note should always be overridden within the FileCheckDiag. return MarkerStyle('!', raw_ostream::RED, "error: unknown error after match", /*FiltersAsError=*/true); case FileCheckDiag::MatchNoneAndExcluded: return MarkerStyle('X', raw_ostream::GREEN); case FileCheckDiag::MatchNoneButExpected: return MarkerStyle('X', raw_ostream::RED, "error: no match found", /*FiltersAsError=*/true); case FileCheckDiag::MatchNoneForInvalidPattern: return MarkerStyle('X', raw_ostream::RED, "error: match failed for invalid pattern", /*FiltersAsError=*/true); case FileCheckDiag::MatchFuzzy: return MarkerStyle('?', raw_ostream::MAGENTA, "possible intended match", /*FiltersAsError=*/true); } llvm_unreachable_internal("unexpected match type"); } static void DumpInputAnnotationHelp(raw_ostream &OS) { OS << "The following description was requested by -dump-input=help to\n" << "explain the input dump printed by FileCheck.\n" << "\n" << "Related command-line options:\n" << "\n" << " - -dump-input= enables or disables the input dump\n" << " - -dump-input-filter= filters the input lines\n" << " - -dump-input-context= adjusts the context of filtered lines\n" << " - -v and -vv add more annotations\n" << " - -color forces colors to be enabled both in the dump and below\n" << " - -help documents the above options in more detail\n" << "\n" << "These options can also be set via FILECHECK_OPTS. For example, for\n" << "maximum debugging output on failures:\n" << "\n" << " $ FILECHECK_OPTS='-dump-input-filter=all -vv -color' ninja check\n" << "\n" << "Input dump annotation format:\n" << "\n"; // Labels for input lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "L:"; OS << " labels line number L of the input file\n" << " An extra space is added after each input line to represent" << " the\n" << " newline character\n"; // Labels for annotation lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L"; OS << " labels the only match result for either (1) a pattern of type T" << " from\n" << " line L of the check file if L is an integer or (2) the" << " I-th implicit\n" << " pattern if L is \"imp\" followed by an integer " << "I (index origin one)\n"; OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L'N"; OS << " labels the Nth match result for such a pattern\n"; // Markers on annotation lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "^~~"; OS << " marks good match (reported if -v)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "!~~"; OS << " marks bad match, such as:\n" << " - CHECK-NEXT on same line as previous match (error)\n" << " - CHECK-NOT found (error)\n" << " - CHECK-DAG overlapping match (discarded, reported if " << "-vv)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "X~~"; OS << " marks search range when no match is found, such as:\n" << " - CHECK-NEXT not found (error)\n" << " - CHECK-NOT not found (success, reported if -vv)\n" << " - CHECK-DAG not found after discarded matches (error)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "?"; OS << " marks fuzzy match when no match is found\n"; // Elided lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "..."; OS << " indicates elided input lines and annotations, as specified by\n" << " -dump-input-filter and -dump-input-context\n"; // Colors. OS << " - colors "; WithColor(OS, raw_ostream::GREEN, true) << "success"; OS << ", "; WithColor(OS, raw_ostream::RED, true) << "error"; OS << ", "; WithColor(OS, raw_ostream::MAGENTA, true) << "fuzzy match"; OS << ", "; WithColor(OS, raw_ostream::CYAN, true, false) << "discarded match"; OS << ", "; WithColor(OS, raw_ostream::CYAN, true, true) << "unmatched input"; OS << "\n"; } /// An annotation for a single input line. struct InputAnnotation { /// The index of the match result across all checks unsigned DiagIndex; /// The label for this annotation. std::string Label; /// Is this the initial fragment of a diagnostic that has been broken across /// multiple lines? bool IsFirstLine; /// What input line (one-origin indexing) this annotation marks. This might /// be different from the starting line of the original diagnostic if /// !IsFirstLine. unsigned InputLine; /// The column range (one-origin indexing, open end) in which to mark the /// input line. If InputEndCol is UINT_MAX, treat it as the last column /// before the newline. unsigned InputStartCol, InputEndCol; /// The marker to use. MarkerStyle Marker; /// Whether this annotation represents a good match for an expected pattern. bool FoundAndExpectedMatch; }; /// Get an abbreviation for the check type. static std::string GetCheckTypeAbbreviation(Check::FileCheckType Ty) { switch (Ty) { case Check::CheckPlain: if (Ty.getCount() > 1) return "count"; return "check"; case Check::CheckNext: return "next"; case Check::CheckSame: return "same"; case Check::CheckNot: return "not"; case Check::CheckDAG: return "dag"; case Check::CheckLabel: return "label"; case Check::CheckEmpty: return "empty"; case Check::CheckComment: return "com"; case Check::CheckEOF: return "eof"; case Check::CheckBadNot: return "bad-not"; case Check::CheckBadCount: return "bad-count"; case Check::CheckNone: llvm_unreachable("invalid FileCheckType"); } llvm_unreachable("unknown FileCheckType"); } static void BuildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID, const std::pair &ImpPatBufferIDRange, const std::vector &Diags, std::vector &Annotations, unsigned &LabelWidth) { struct CompareSMLoc { bool operator()(const SMLoc &LHS, const SMLoc &RHS) const { return LHS.getPointer() < RHS.getPointer(); } }; // How many diagnostics does each pattern have? std::map DiagCountPerPattern; for (auto Diag : Diags) ++DiagCountPerPattern[Diag.CheckLoc]; // How many diagnostics have we seen so far per pattern? std::map DiagIndexPerPattern; // How many total diagnostics have we seen so far? unsigned DiagIndex = 0; // What's the widest label? LabelWidth = 0; for (auto DiagItr = Diags.begin(), DiagEnd = Diags.end(); DiagItr != DiagEnd; ++DiagItr) { InputAnnotation A; A.DiagIndex = DiagIndex++; // Build label, which uniquely identifies this check result. unsigned CheckBufferID = SM.FindBufferContainingLoc(DiagItr->CheckLoc); auto CheckLineAndCol = SM.getLineAndColumn(DiagItr->CheckLoc, CheckBufferID); llvm::raw_string_ostream Label(A.Label); Label << GetCheckTypeAbbreviation(DiagItr->CheckTy) << ":"; if (CheckBufferID == CheckFileBufferID) Label << CheckLineAndCol.first; else if (ImpPatBufferIDRange.first <= CheckBufferID && CheckBufferID < ImpPatBufferIDRange.second) Label << "imp" << (CheckBufferID - ImpPatBufferIDRange.first + 1); else llvm_unreachable("expected diagnostic's check location to be either in " "the check file or for an implicit pattern"); if (DiagCountPerPattern[DiagItr->CheckLoc] > 1) Label << "'" << DiagIndexPerPattern[DiagItr->CheckLoc]++; Label.flush(); LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size()); A.Marker = GetMarker(DiagItr->MatchTy); if (!DiagItr->Note.empty()) { A.Marker.Note = DiagItr->Note; // It's less confusing if notes that don't actually have ranges don't have // markers. For example, a marker for 'with "VAR" equal to "5"' would // seem to indicate where "VAR" matches, but the location we actually have // for the marker simply points to the start of the match/search range for // the full pattern of which the substitution is potentially just one // component. if (DiagItr->InputStartLine == DiagItr->InputEndLine && DiagItr->InputStartCol == DiagItr->InputEndCol) A.Marker.Lead = ' '; } if (DiagItr->MatchTy == FileCheckDiag::MatchFoundErrorNote) { assert(!DiagItr->Note.empty() && "expected custom note for MatchFoundErrorNote"); A.Marker.Note = "error: " + A.Marker.Note; } A.FoundAndExpectedMatch = DiagItr->MatchTy == FileCheckDiag::MatchFoundAndExpected; // Compute the mark location, and break annotation into multiple // annotations if it spans multiple lines. A.IsFirstLine = true; A.InputLine = DiagItr->InputStartLine; A.InputStartCol = DiagItr->InputStartCol; if (DiagItr->InputStartLine == DiagItr->InputEndLine) { // Sometimes ranges are empty in order to indicate a specific point, but // that would mean nothing would be marked, so adjust the range to // include the following character. A.InputEndCol = std::max(DiagItr->InputStartCol + 1, DiagItr->InputEndCol); Annotations.push_back(A); } else { assert(DiagItr->InputStartLine < DiagItr->InputEndLine && "expected input range not to be inverted"); A.InputEndCol = UINT_MAX; Annotations.push_back(A); for (unsigned L = DiagItr->InputStartLine + 1, E = DiagItr->InputEndLine; L <= E; ++L) { // If a range ends before the first column on a line, then it has no // characters on that line, so there's nothing to render. if (DiagItr->InputEndCol == 1 && L == E) break; InputAnnotation B; B.DiagIndex = A.DiagIndex; B.Label = A.Label; B.IsFirstLine = false; B.InputLine = L; B.Marker = A.Marker; B.Marker.Lead = '~'; B.Marker.Note = ""; B.InputStartCol = 1; if (L != E) B.InputEndCol = UINT_MAX; else B.InputEndCol = DiagItr->InputEndCol; B.FoundAndExpectedMatch = A.FoundAndExpectedMatch; Annotations.push_back(B); } } } } static unsigned FindInputLineInFilter( DumpInputFilterValue DumpInputFilter, unsigned CurInputLine, const std::vector::iterator &AnnotationBeg, const std::vector::iterator &AnnotationEnd) { if (DumpInputFilter == DumpInputFilterAll) return CurInputLine; for (auto AnnotationItr = AnnotationBeg; AnnotationItr != AnnotationEnd; ++AnnotationItr) { switch (DumpInputFilter) { case DumpInputFilterAll: llvm_unreachable("unexpected DumpInputFilterAll"); break; case DumpInputFilterAnnotationFull: return AnnotationItr->InputLine; case DumpInputFilterAnnotation: if (AnnotationItr->IsFirstLine) return AnnotationItr->InputLine; break; case DumpInputFilterError: if (AnnotationItr->IsFirstLine && AnnotationItr->Marker.FiltersAsError) return AnnotationItr->InputLine; break; } } return UINT_MAX; } /// To OS, print a vertical ellipsis (right-justified at LabelWidth) if it would /// occupy less lines than ElidedLines, but print ElidedLines otherwise. Either /// way, clear ElidedLines. Thus, if ElidedLines is empty, do nothing. static void DumpEllipsisOrElidedLines(raw_ostream &OS, std::string &ElidedLines, unsigned LabelWidth) { if (ElidedLines.empty()) return; unsigned EllipsisLines = 3; if (EllipsisLines < StringRef(ElidedLines).count('\n')) { for (unsigned i = 0; i < EllipsisLines; ++i) { WithColor(OS, raw_ostream::BLACK, /*Bold=*/true) << right_justify(".", LabelWidth); OS << '\n'; } } else OS << ElidedLines; ElidedLines.clear(); } static void DumpAnnotatedInput(raw_ostream &OS, const FileCheckRequest &Req, DumpInputFilterValue DumpInputFilter, unsigned DumpInputContext, StringRef InputFileText, std::vector &Annotations, unsigned LabelWidth) { OS << "Input was:\n<<<<<<\n"; // Sort annotations. llvm::sort(Annotations, [](const InputAnnotation &A, const InputAnnotation &B) { // 1. Sort annotations in the order of the input lines. // // This makes it easier to find relevant annotations while // iterating input lines in the implementation below. FileCheck // does not always produce diagnostics in the order of input // lines due to, for example, CHECK-DAG and CHECK-NOT. if (A.InputLine != B.InputLine) return A.InputLine < B.InputLine; // 2. Sort annotations in the temporal order FileCheck produced // their associated diagnostics. // // This sort offers several benefits: // // A. On a single input line, the order of annotations reflects // the FileCheck logic for processing directives/patterns. // This can be helpful in understanding cases in which the // order of the associated directives/patterns in the check // file or on the command line either (i) does not match the // temporal order in which FileCheck looks for matches for the // directives/patterns (due to, for example, CHECK-LABEL, // CHECK-NOT, or `--implicit-check-not`) or (ii) does match // that order but does not match the order of those // diagnostics along an input line (due to, for example, // CHECK-DAG). // // On the other hand, because our presentation format presents // input lines in order, there's no clear way to offer the // same benefit across input lines. For consistency, it might // then seem worthwhile to have annotations on a single line // also sorted in input order (that is, by input column). // However, in practice, this appears to be more confusing // than helpful. Perhaps it's intuitive to expect annotations // to be listed in the temporal order in which they were // produced except in cases the presentation format obviously // and inherently cannot support it (that is, across input // lines). // // B. When diagnostics' annotations are split among multiple // input lines, the user must track them from one input line // to the next. One property of the sort chosen here is that // it facilitates the user in this regard by ensuring the // following: when comparing any two input lines, a // diagnostic's annotations are sorted in the same position // relative to all other diagnostics' annotations. return A.DiagIndex < B.DiagIndex; }); // Compute the width of the label column. const unsigned char *InputFilePtr = InputFileText.bytes_begin(), *InputFileEnd = InputFileText.bytes_end(); unsigned LineCount = InputFileText.count('\n'); if (InputFileEnd[-1] != '\n') ++LineCount; unsigned LineNoWidth = std::log10(LineCount) + 1; // +3 below adds spaces (1) to the left of the (right-aligned) line numbers // on input lines and (2) to the right of the (left-aligned) labels on // annotation lines so that input lines and annotation lines are more // visually distinct. For example, the spaces on the annotation lines ensure // that input line numbers and check directive line numbers never align // horizontally. Those line numbers might not even be for the same file. // One space would be enough to achieve that, but more makes it even easier // to see. LabelWidth = std::max(LabelWidth, LineNoWidth) + 3; // Print annotated input lines. unsigned PrevLineInFilter = 0; // 0 means none so far unsigned NextLineInFilter = 0; // 0 means uncomputed, UINT_MAX means none std::string ElidedLines; raw_string_ostream ElidedLinesOS(ElidedLines); ColorMode TheColorMode = WithColor(OS).colorsEnabled() ? ColorMode::Enable : ColorMode::Disable; if (TheColorMode == ColorMode::Enable) ElidedLinesOS.enable_colors(true); auto AnnotationItr = Annotations.begin(), AnnotationEnd = Annotations.end(); for (unsigned Line = 1; InputFilePtr != InputFileEnd || AnnotationItr != AnnotationEnd; ++Line) { const unsigned char *InputFileLine = InputFilePtr; // Compute the previous and next line included by the filter. if (NextLineInFilter < Line) NextLineInFilter = FindInputLineInFilter(DumpInputFilter, Line, AnnotationItr, AnnotationEnd); assert(NextLineInFilter && "expected NextLineInFilter to be computed"); if (NextLineInFilter == Line) PrevLineInFilter = Line; // Elide this input line and its annotations if it's not within the // context specified by -dump-input-context of an input line included by // -dump-input-filter. However, in case the resulting ellipsis would occupy // more lines than the input lines and annotations it elides, buffer the // elided lines and annotations so we can print them instead. raw_ostream *LineOS = &OS; if ((!PrevLineInFilter || PrevLineInFilter + DumpInputContext < Line) && (NextLineInFilter == UINT_MAX || Line + DumpInputContext < NextLineInFilter)) LineOS = &ElidedLinesOS; else { LineOS = &OS; DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); } // Print right-aligned line number. WithColor(*LineOS, raw_ostream::BLACK, /*Bold=*/true, /*BF=*/false, TheColorMode) << format_decimal(Line, LabelWidth) << ": "; // For the case where -v and colors are enabled, find the annotations for // good matches for expected patterns in order to highlight everything // else in the line. There are no such annotations if -v is disabled. std::vector FoundAndExpectedMatches; if (Req.Verbose && TheColorMode == ColorMode::Enable) { for (auto I = AnnotationItr; I != AnnotationEnd && I->InputLine == Line; ++I) { if (I->FoundAndExpectedMatch) FoundAndExpectedMatches.push_back(*I); } } // Print numbered line with highlighting where there are no matches for // expected patterns. bool Newline = false; { WithColor COS(*LineOS, raw_ostream::SAVEDCOLOR, /*Bold=*/false, /*BG=*/false, TheColorMode); bool InMatch = false; if (Req.Verbose) COS.changeColor(raw_ostream::CYAN, true, true); for (unsigned Col = 1; InputFilePtr != InputFileEnd && !Newline; ++Col) { bool WasInMatch = InMatch; InMatch = false; for (auto M : FoundAndExpectedMatches) { if (M.InputStartCol <= Col && Col < M.InputEndCol) { InMatch = true; break; } } if (!WasInMatch && InMatch) COS.resetColor(); else if (WasInMatch && !InMatch) COS.changeColor(raw_ostream::CYAN, true, true); if (*InputFilePtr == '\n') { Newline = true; COS << ' '; } else COS << *InputFilePtr; ++InputFilePtr; } } *LineOS << '\n'; unsigned InputLineWidth = InputFilePtr - InputFileLine; // Print any annotations. while (AnnotationItr != AnnotationEnd && AnnotationItr->InputLine == Line) { WithColor COS(*LineOS, AnnotationItr->Marker.Color, /*Bold=*/true, /*BG=*/false, TheColorMode); // The two spaces below are where the ": " appears on input lines. COS << left_justify(AnnotationItr->Label, LabelWidth) << " "; unsigned Col; for (Col = 1; Col < AnnotationItr->InputStartCol; ++Col) COS << ' '; COS << AnnotationItr->Marker.Lead; // If InputEndCol=UINT_MAX, stop at InputLineWidth. for (++Col; Col < AnnotationItr->InputEndCol && Col <= InputLineWidth; ++Col) COS << '~'; const std::string &Note = AnnotationItr->Marker.Note; if (!Note.empty()) { // Put the note at the end of the input line. If we were to instead // put the note right after the marker, subsequent annotations for the // same input line might appear to mark this note instead of the input // line. for (; Col <= InputLineWidth; ++Col) COS << ' '; COS << ' ' << Note; } COS << '\n'; ++AnnotationItr; } } DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); OS << ">>>>>>\n"; } int main(int argc, char **argv) { // Enable use of ANSI color codes because FileCheck is using them to // highlight text. llvm::sys::Process::UseANSIEscapeCodes(true); InitLLVM X(argc, argv); cl::ParseCommandLineOptions(argc, argv, /*Overview*/ "", /*Errs*/ nullptr, "FILECHECK_OPTS"); // Select -dump-input* values. The -help documentation specifies the default // value and which value to choose if an option is specified multiple times. // In the latter case, the general rule of thumb is to choose the value that // provides the most information. DumpInputValue DumpInput = DumpInputs.empty() ? DumpInputFail : *std::max_element(DumpInputs.begin(), DumpInputs.end()); DumpInputFilterValue DumpInputFilter; if (DumpInputFilters.empty()) DumpInputFilter = DumpInput == DumpInputAlways ? DumpInputFilterAll : DumpInputFilterError; else DumpInputFilter = *std::max_element(DumpInputFilters.begin(), DumpInputFilters.end()); unsigned DumpInputContext = DumpInputContexts.empty() ? 5 : *std::max_element(DumpInputContexts.begin(), DumpInputContexts.end()); if (DumpInput == DumpInputHelp) { DumpInputAnnotationHelp(outs()); return 0; } if (CheckFilename.empty()) { errs() << " not specified\n"; return 2; } FileCheckRequest Req; append_range(Req.CheckPrefixes, CheckPrefixes); append_range(Req.CommentPrefixes, CommentPrefixes); append_range(Req.ImplicitCheckNot, ImplicitCheckNot); bool GlobalDefineError = false; for (StringRef G : GlobalDefines) { size_t EqIdx = G.find('='); if (EqIdx == std::string::npos) { errs() << "Missing equal sign in command-line definition '-D" << G << "'\n"; GlobalDefineError = true; continue; } if (EqIdx == 0) { errs() << "Missing variable name in command-line definition '-D" << G << "'\n"; GlobalDefineError = true; continue; } Req.GlobalDefines.push_back(G); } if (GlobalDefineError) return 2; Req.AllowEmptyInput = AllowEmptyInput; Req.AllowUnusedPrefixes = AllowUnusedPrefixes; Req.EnableVarScope = EnableVarScope; Req.AllowDeprecatedDagOverlap = AllowDeprecatedDagOverlap; Req.Verbose = Verbose; Req.VerboseVerbose = VerboseVerbose; Req.NoCanonicalizeWhiteSpace = NoCanonicalizeWhiteSpace; Req.MatchFullLines = MatchFullLines; Req.IgnoreCase = IgnoreCase; if (VerboseVerbose) Req.Verbose = true; FileCheck FC(Req); if (!FC.ValidateCheckPrefixes()) return 2; Regex PrefixRE = FC.buildCheckPrefixRegex(); std::string REError; if (!PrefixRE.isValid(REError)) { errs() << "Unable to combine check-prefix strings into a prefix regular " "expression! This is likely a bug in FileCheck's verification of " "the check-prefix strings. Regular expression parsing failed " "with the following error: " << REError << "\n"; return 2; } SourceMgr SM; // Read the expected strings from the check file. ErrorOr> CheckFileOrErr = MemoryBuffer::getFileOrSTDIN(CheckFilename, /*IsText=*/true); if (std::error_code EC = CheckFileOrErr.getError()) { errs() << "Could not open check file '" << CheckFilename << "': " << EC.message() << '\n'; return 2; } MemoryBuffer &CheckFile = *CheckFileOrErr.get(); SmallString<4096> CheckFileBuffer; StringRef CheckFileText = FC.CanonicalizeFile(CheckFile, CheckFileBuffer); unsigned CheckFileBufferID = SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( CheckFileText, CheckFile.getBufferIdentifier()), SMLoc()); std::pair ImpPatBufferIDRange; if (FC.readCheckFile(SM, CheckFileText, PrefixRE, &ImpPatBufferIDRange)) return 2; // Open the file to check and add it to SourceMgr. ErrorOr> InputFileOrErr = MemoryBuffer::getFileOrSTDIN(InputFilename, /*IsText=*/true); if (InputFilename == "-") InputFilename = ""; // Overwrite for improved diagnostic messages if (std::error_code EC = InputFileOrErr.getError()) { errs() << "Could not open input file '" << InputFilename << "': " << EC.message() << '\n'; return 2; } MemoryBuffer &InputFile = *InputFileOrErr.get(); if (InputFile.getBufferSize() == 0 && !AllowEmptyInput) { errs() << "FileCheck error: '" << InputFilename << "' is empty.\n"; DumpCommandLine(argc, argv); return 2; } SmallString<4096> InputFileBuffer; StringRef InputFileText = FC.CanonicalizeFile(InputFile, InputFileBuffer); SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( InputFileText, InputFile.getBufferIdentifier()), SMLoc()); std::vector Diags; int ExitCode = FC.checkInput(SM, InputFileText, DumpInput == DumpInputNever ? nullptr : &Diags) ? EXIT_SUCCESS : 1; if (DumpInput == DumpInputAlways || (ExitCode == 1 && DumpInput == DumpInputFail)) { errs() << "\n" << "Input file: " << InputFilename << "\n" << "Check file: " << CheckFilename << "\n" << "\n" << "-dump-input=help explains the following input dump.\n" << "\n"; std::vector Annotations; unsigned LabelWidth; BuildInputAnnotations(SM, CheckFileBufferID, ImpPatBufferIDRange, Diags, Annotations, LabelWidth); DumpAnnotatedInput(errs(), Req, DumpInputFilter, DumpInputContext, InputFileText, Annotations, LabelWidth); } return ExitCode; } ./ldc-1.36.0-src/utils/FileCheck-11.cpp0000644000175000017500000010444714564454261017420 0ustar matthiasmatthias//===- FileCheck.cpp - Check that File's Contents match what is expected --===// // // 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 // //===----------------------------------------------------------------------===// // // FileCheck does a line-by line check of a file that validates whether it // contains the expected content. This is useful for regression tests etc. // // This program exits with an exit status of 2 on error, exit status of 0 if // the file matched the expected contents, and exit status of 1 if it did not // contain the expected contents. // //===----------------------------------------------------------------------===// #include "llvm/Support/CommandLine.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/Process.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" #include "llvm/Support/FileCheck.h" #include using namespace llvm; static cl::extrahelp FileCheckOptsEnv( "\nOptions are parsed from the environment variable FILECHECK_OPTS and\n" "from the command line.\n"); static cl::opt CheckFilename(cl::Positional, cl::desc(""), cl::Optional); static cl::opt InputFilename("input-file", cl::desc("File to check (defaults to stdin)"), cl::init("-"), cl::value_desc("filename")); static cl::list CheckPrefixes( "check-prefix", cl::desc("Prefix to use from check file (defaults to 'CHECK')")); static cl::alias CheckPrefixesAlias( "check-prefixes", cl::aliasopt(CheckPrefixes), cl::CommaSeparated, cl::NotHidden, cl::desc( "Alias for -check-prefix permitting multiple comma separated values")); static cl::list CommentPrefixes( "comment-prefixes", cl::CommaSeparated, cl::Hidden, cl::desc("Comma-separated list of comment prefixes to use from check file\n" "(defaults to 'COM,RUN'). Please avoid using this feature in\n" "LLVM's LIT-based test suites, which should be easier to\n" "maintain if they all follow a consistent comment style. This\n" "feature is meant for non-LIT test suites using FileCheck.")); static cl::opt NoCanonicalizeWhiteSpace( "strict-whitespace", cl::desc("Do not treat all horizontal whitespace as equivalent")); static cl::opt IgnoreCase( "ignore-case", cl::desc("Use case-insensitive matching")); static cl::list ImplicitCheckNot( "implicit-check-not", cl::desc("Add an implicit negative check with this pattern to every\n" "positive check. This can be used to ensure that no instances of\n" "this pattern occur which are not matched by a positive pattern"), cl::value_desc("pattern")); static cl::list GlobalDefines("D", cl::AlwaysPrefix, cl::desc("Define a variable to be used in capture patterns."), cl::value_desc("VAR=VALUE")); static cl::opt AllowEmptyInput( "allow-empty", cl::init(false), cl::desc("Allow the input file to be empty. This is useful when making\n" "checks that some error message does not occur, for example.")); static cl::opt MatchFullLines( "match-full-lines", cl::init(false), cl::desc("Require all positive matches to cover an entire input line.\n" "Allows leading and trailing whitespace if --strict-whitespace\n" "is not also passed.")); static cl::opt EnableVarScope( "enable-var-scope", cl::init(false), cl::desc("Enables scope for regex variables. Variables with names that\n" "do not start with '$' will be reset at the beginning of\n" "each CHECK-LABEL block.")); static cl::opt AllowDeprecatedDagOverlap( "allow-deprecated-dag-overlap", cl::init(false), cl::desc("Enable overlapping among matches in a group of consecutive\n" "CHECK-DAG directives. This option is deprecated and is only\n" "provided for convenience as old tests are migrated to the new\n" "non-overlapping CHECK-DAG implementation.\n")); static cl::opt Verbose( "v", cl::init(false), cl::ZeroOrMore, cl::desc("Print directive pattern matches, or add them to the input dump\n" "if enabled.\n")); static cl::opt VerboseVerbose( "vv", cl::init(false), cl::ZeroOrMore, cl::desc("Print information helpful in diagnosing internal FileCheck\n" "issues, or add it to the input dump if enabled. Implies\n" "-v.\n")); // The order of DumpInputValue members affects their precedence, as documented // for -dump-input below. enum DumpInputValue { DumpInputNever, DumpInputFail, DumpInputAlways, DumpInputHelp }; static cl::list DumpInputs( "dump-input", cl::desc("Dump input to stderr, adding annotations representing\n" "currently enabled diagnostics. When there are multiple\n" "occurrences of this option, the that appears earliest\n" "in the list below has precedence. The default is 'fail'.\n"), cl::value_desc("mode"), cl::values(clEnumValN(DumpInputHelp, "help", "Explain input dump and quit"), clEnumValN(DumpInputAlways, "always", "Always dump input"), clEnumValN(DumpInputFail, "fail", "Dump input on failure"), clEnumValN(DumpInputNever, "never", "Never dump input"))); // The order of DumpInputFilterValue members affects their precedence, as // documented for -dump-input-filter below. enum DumpInputFilterValue { DumpInputFilterError, DumpInputFilterAnnotation, DumpInputFilterAnnotationFull, DumpInputFilterAll }; static cl::list DumpInputFilters( "dump-input-filter", cl::desc("In the dump requested by -dump-input, print only input lines of\n" "kind plus any context specified by -dump-input-context.\n" "When there are multiple occurrences of this option, the \n" "that appears earliest in the list below has precedence. The\n" "default is 'error' when -dump-input=fail, and it's 'all' when\n" "-dump-input=always.\n"), cl::values(clEnumValN(DumpInputFilterAll, "all", "All input lines"), clEnumValN(DumpInputFilterAnnotationFull, "annotation-full", "Input lines with annotations"), clEnumValN(DumpInputFilterAnnotation, "annotation", "Input lines with starting points of annotations"), clEnumValN(DumpInputFilterError, "error", "Input lines with starting points of error " "annotations"))); static cl::list DumpInputContexts( "dump-input-context", cl::value_desc("N"), cl::desc("In the dump requested by -dump-input, print input lines\n" "before and input lines after any lines specified by\n" "-dump-input-filter. When there are multiple occurrences of\n" "this option, the largest specified has precedence. The\n" "default is 5.\n")); typedef cl::list::const_iterator prefix_iterator; static void DumpCommandLine(int argc, char **argv) { errs() << "FileCheck command line: "; for (int I = 0; I < argc; I++) errs() << " " << argv[I]; errs() << "\n"; } struct MarkerStyle { /// The starting char (before tildes) for marking the line. char Lead; /// What color to use for this annotation. raw_ostream::Colors Color; /// A note to follow the marker, or empty string if none. std::string Note; /// Does this marker indicate inclusion by -dump-input-filter=error? bool FiltersAsError; MarkerStyle() {} MarkerStyle(char Lead, raw_ostream::Colors Color, const std::string &Note = "", bool FiltersAsError = false) : Lead(Lead), Color(Color), Note(Note), FiltersAsError(FiltersAsError) { assert((!FiltersAsError || !Note.empty()) && "expected error diagnostic to have note"); } }; static MarkerStyle GetMarker(FileCheckDiag::MatchType MatchTy) { switch (MatchTy) { case FileCheckDiag::MatchFoundAndExpected: return MarkerStyle('^', raw_ostream::GREEN); case FileCheckDiag::MatchFoundButExcluded: return MarkerStyle('!', raw_ostream::RED, "error: no match expected", /*FiltersAsError=*/true); case FileCheckDiag::MatchFoundButWrongLine: return MarkerStyle('!', raw_ostream::RED, "error: match on wrong line", /*FiltersAsError=*/true); case FileCheckDiag::MatchFoundButDiscarded: return MarkerStyle('!', raw_ostream::CYAN, "discard: overlaps earlier match"); case FileCheckDiag::MatchNoneAndExcluded: return MarkerStyle('X', raw_ostream::GREEN); case FileCheckDiag::MatchNoneButExpected: return MarkerStyle('X', raw_ostream::RED, "error: no match found", /*FiltersAsError=*/true); case FileCheckDiag::MatchFuzzy: return MarkerStyle('?', raw_ostream::MAGENTA, "possible intended match", /*FiltersAsError=*/true); } llvm_unreachable_internal("unexpected match type"); } static void DumpInputAnnotationHelp(raw_ostream &OS) { OS << "The following description was requested by -dump-input=help to\n" << "explain the input dump printed by FileCheck.\n" << "\n" << "Related command-line options:\n" << "\n" << " - -dump-input= enables or disables the input dump\n" << " - -dump-input-filter= filters the input lines\n" << " - -dump-input-context= adjusts the context of filtered lines\n" << " - -v and -vv add more annotations\n" << " - -color forces colors to be enabled both in the dump and below\n" << " - -help documents the above options in more detail\n" << "\n" << "These options can also be set via FILECHECK_OPTS. For example, for\n" << "maximum debugging output on failures:\n" << "\n" << " $ FILECHECK_OPTS='-dump-input-filter=all -vv -color' ninja check\n" << "\n" << "Input dump annotation format:\n" << "\n"; // Labels for input lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "L:"; OS << " labels line number L of the input file\n"; // Labels for annotation lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L"; OS << " labels the only match result for either (1) a pattern of type T" << " from\n" << " line L of the check file if L is an integer or (2) the" << " I-th implicit\n" << " pattern if L is \"imp\" followed by an integer " << "I (index origin one)\n"; OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L'N"; OS << " labels the Nth match result for such a pattern\n"; // Markers on annotation lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "^~~"; OS << " marks good match (reported if -v)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "!~~"; OS << " marks bad match, such as:\n" << " - CHECK-NEXT on same line as previous match (error)\n" << " - CHECK-NOT found (error)\n" << " - CHECK-DAG overlapping match (discarded, reported if " << "-vv)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "X~~"; OS << " marks search range when no match is found, such as:\n" << " - CHECK-NEXT not found (error)\n" << " - CHECK-NOT not found (success, reported if -vv)\n" << " - CHECK-DAG not found after discarded matches (error)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "?"; OS << " marks fuzzy match when no match is found\n"; // Elided lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "..."; OS << " indicates elided input lines and annotations, as specified by\n" << " -dump-input-filter and -dump-input-context\n"; // Colors. OS << " - colors "; WithColor(OS, raw_ostream::GREEN, true) << "success"; OS << ", "; WithColor(OS, raw_ostream::RED, true) << "error"; OS << ", "; WithColor(OS, raw_ostream::MAGENTA, true) << "fuzzy match"; OS << ", "; WithColor(OS, raw_ostream::CYAN, true, false) << "discarded match"; OS << ", "; WithColor(OS, raw_ostream::CYAN, true, true) << "unmatched input"; OS << "\n"; } /// An annotation for a single input line. struct InputAnnotation { /// The index of the match result across all checks unsigned DiagIndex; /// The label for this annotation. std::string Label; /// Is this the initial fragment of a diagnostic that has been broken across /// multiple lines? bool IsFirstLine; /// What input line (one-origin indexing) this annotation marks. This might /// be different from the starting line of the original diagnostic if /// !IsFirstLine. unsigned InputLine; /// The column range (one-origin indexing, open end) in which to mark the /// input line. If InputEndCol is UINT_MAX, treat it as the last column /// before the newline. unsigned InputStartCol, InputEndCol; /// The marker to use. MarkerStyle Marker; /// Whether this annotation represents a good match for an expected pattern. bool FoundAndExpectedMatch; }; /// Get an abbreviation for the check type. std::string GetCheckTypeAbbreviation(Check::FileCheckType Ty) { switch (Ty) { case Check::CheckPlain: if (Ty.getCount() > 1) return "count"; return "check"; case Check::CheckNext: return "next"; case Check::CheckSame: return "same"; case Check::CheckNot: return "not"; case Check::CheckDAG: return "dag"; case Check::CheckLabel: return "label"; case Check::CheckEmpty: return "empty"; case Check::CheckComment: return "com"; case Check::CheckEOF: return "eof"; case Check::CheckBadNot: return "bad-not"; case Check::CheckBadCount: return "bad-count"; case Check::CheckNone: llvm_unreachable("invalid FileCheckType"); } llvm_unreachable("unknown FileCheckType"); } static void BuildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID, const std::pair &ImpPatBufferIDRange, const std::vector &Diags, std::vector &Annotations, unsigned &LabelWidth) { // How many diagnostics have we seen so far? unsigned DiagCount = 0; // How many diagnostics has the current check seen so far? unsigned CheckDiagCount = 0; // What's the widest label? LabelWidth = 0; for (auto DiagItr = Diags.begin(), DiagEnd = Diags.end(); DiagItr != DiagEnd; ++DiagItr) { InputAnnotation A; A.DiagIndex = DiagCount++; // Build label, which uniquely identifies this check result. unsigned CheckBufferID = SM.FindBufferContainingLoc(DiagItr->CheckLoc); auto CheckLineAndCol = SM.getLineAndColumn(DiagItr->CheckLoc, CheckBufferID); llvm::raw_string_ostream Label(A.Label); Label << GetCheckTypeAbbreviation(DiagItr->CheckTy) << ":"; if (CheckBufferID == CheckFileBufferID) Label << CheckLineAndCol.first; else if (ImpPatBufferIDRange.first <= CheckBufferID && CheckBufferID < ImpPatBufferIDRange.second) Label << "imp" << (CheckBufferID - ImpPatBufferIDRange.first + 1); else llvm_unreachable("expected diagnostic's check location to be either in " "the check file or for an implicit pattern"); unsigned CheckDiagIndex = UINT_MAX; auto DiagNext = std::next(DiagItr); if (DiagNext != DiagEnd && DiagItr->CheckTy == DiagNext->CheckTy && DiagItr->CheckLoc == DiagNext->CheckLoc) CheckDiagIndex = CheckDiagCount++; else if (CheckDiagCount) { CheckDiagIndex = CheckDiagCount; CheckDiagCount = 0; } if (CheckDiagIndex != UINT_MAX) Label << "'" << CheckDiagIndex; Label.flush(); LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size()); A.Marker = GetMarker(DiagItr->MatchTy); A.FoundAndExpectedMatch = DiagItr->MatchTy == FileCheckDiag::MatchFoundAndExpected; // Compute the mark location, and break annotation into multiple // annotations if it spans multiple lines. A.IsFirstLine = true; A.InputLine = DiagItr->InputStartLine; A.InputStartCol = DiagItr->InputStartCol; if (DiagItr->InputStartLine == DiagItr->InputEndLine) { // Sometimes ranges are empty in order to indicate a specific point, but // that would mean nothing would be marked, so adjust the range to // include the following character. A.InputEndCol = std::max(DiagItr->InputStartCol + 1, DiagItr->InputEndCol); Annotations.push_back(A); } else { assert(DiagItr->InputStartLine < DiagItr->InputEndLine && "expected input range not to be inverted"); A.InputEndCol = UINT_MAX; Annotations.push_back(A); for (unsigned L = DiagItr->InputStartLine + 1, E = DiagItr->InputEndLine; L <= E; ++L) { // If a range ends before the first column on a line, then it has no // characters on that line, so there's nothing to render. if (DiagItr->InputEndCol == 1 && L == E) break; InputAnnotation B; B.DiagIndex = A.DiagIndex; B.Label = A.Label; B.IsFirstLine = false; B.InputLine = L; B.Marker = A.Marker; B.Marker.Lead = '~'; B.Marker.Note = ""; B.InputStartCol = 1; if (L != E) B.InputEndCol = UINT_MAX; else B.InputEndCol = DiagItr->InputEndCol; B.FoundAndExpectedMatch = A.FoundAndExpectedMatch; Annotations.push_back(B); } } } } static unsigned FindInputLineInFilter( DumpInputFilterValue DumpInputFilter, unsigned CurInputLine, const std::vector::iterator &AnnotationBeg, const std::vector::iterator &AnnotationEnd) { if (DumpInputFilter == DumpInputFilterAll) return CurInputLine; for (auto AnnotationItr = AnnotationBeg; AnnotationItr != AnnotationEnd; ++AnnotationItr) { switch (DumpInputFilter) { case DumpInputFilterAll: llvm_unreachable("unexpected DumpInputFilterAll"); break; case DumpInputFilterAnnotationFull: return AnnotationItr->InputLine; case DumpInputFilterAnnotation: if (AnnotationItr->IsFirstLine) return AnnotationItr->InputLine; break; case DumpInputFilterError: if (AnnotationItr->IsFirstLine && AnnotationItr->Marker.FiltersAsError) return AnnotationItr->InputLine; break; } } return UINT_MAX; } /// To OS, print a vertical ellipsis (right-justified at LabelWidth) if it would /// occupy less lines than ElidedLines, but print ElidedLines otherwise. Either /// way, clear ElidedLines. Thus, if ElidedLines is empty, do nothing. static void DumpEllipsisOrElidedLines(raw_ostream &OS, std::string &ElidedLines, unsigned LabelWidth) { if (ElidedLines.empty()) return; unsigned EllipsisLines = 3; if (EllipsisLines < StringRef(ElidedLines).count('\n')) { for (unsigned i = 0; i < EllipsisLines; ++i) { WithColor(OS, raw_ostream::BLACK, /*Bold=*/true) << right_justify(".", LabelWidth); OS << '\n'; } } else OS << ElidedLines; ElidedLines.clear(); } static void DumpAnnotatedInput(raw_ostream &OS, const FileCheckRequest &Req, DumpInputFilterValue DumpInputFilter, unsigned DumpInputContext, StringRef InputFileText, std::vector &Annotations, unsigned LabelWidth) { OS << "Input was:\n<<<<<<\n"; // Sort annotations. std::sort(Annotations.begin(), Annotations.end(), [](const InputAnnotation &A, const InputAnnotation &B) { // 1. Sort annotations in the order of the input lines. // // This makes it easier to find relevant annotations while // iterating input lines in the implementation below. FileCheck // does not always produce diagnostics in the order of input // lines due to, for example, CHECK-DAG and CHECK-NOT. if (A.InputLine != B.InputLine) return A.InputLine < B.InputLine; // 2. Sort annotations in the temporal order FileCheck produced // their associated diagnostics. // // This sort offers several benefits: // // A. On a single input line, the order of annotations reflects // the FileCheck logic for processing directives/patterns. // This can be helpful in understanding cases in which the // order of the associated directives/patterns in the check // file or on the command line either (i) does not match the // temporal order in which FileCheck looks for matches for the // directives/patterns (due to, for example, CHECK-LABEL, // CHECK-NOT, or `--implicit-check-not`) or (ii) does match // that order but does not match the order of those // diagnostics along an input line (due to, for example, // CHECK-DAG). // // On the other hand, because our presentation format presents // input lines in order, there's no clear way to offer the // same benefit across input lines. For consistency, it might // then seem worthwhile to have annotations on a single line // also sorted in input order (that is, by input column). // However, in practice, this appears to be more confusing // than helpful. Perhaps it's intuitive to expect annotations // to be listed in the temporal order in which they were // produced except in cases the presentation format obviously // and inherently cannot support it (that is, across input // lines). // // B. When diagnostics' annotations are split among multiple // input lines, the user must track them from one input line // to the next. One property of the sort chosen here is that // it facilitates the user in this regard by ensuring the // following: when comparing any two input lines, a // diagnostic's annotations are sorted in the same position // relative to all other diagnostics' annotations. return A.DiagIndex < B.DiagIndex; }); // Compute the width of the label column. const unsigned char *InputFilePtr = InputFileText.bytes_begin(), *InputFileEnd = InputFileText.bytes_end(); unsigned LineCount = InputFileText.count('\n'); if (InputFileEnd[-1] != '\n') ++LineCount; unsigned LineNoWidth = std::log10(LineCount) + 1; // +3 below adds spaces (1) to the left of the (right-aligned) line numbers // on input lines and (2) to the right of the (left-aligned) labels on // annotation lines so that input lines and annotation lines are more // visually distinct. For example, the spaces on the annotation lines ensure // that input line numbers and check directive line numbers never align // horizontally. Those line numbers might not even be for the same file. // One space would be enough to achieve that, but more makes it even easier // to see. LabelWidth = std::max(LabelWidth, LineNoWidth) + 3; // Print annotated input lines. unsigned PrevLineInFilter = 0; // 0 means none so far unsigned NextLineInFilter = 0; // 0 means uncomputed, UINT_MAX means none std::string ElidedLines; raw_string_ostream ElidedLinesOS(ElidedLines); ColorMode TheColorMode = WithColor(OS).colorsEnabled() ? ColorMode::Enable : ColorMode::Disable; if (TheColorMode == ColorMode::Enable) ElidedLinesOS.enable_colors(true); auto AnnotationItr = Annotations.begin(), AnnotationEnd = Annotations.end(); for (unsigned Line = 1; InputFilePtr != InputFileEnd || AnnotationItr != AnnotationEnd; ++Line) { const unsigned char *InputFileLine = InputFilePtr; // Compute the previous and next line included by the filter. if (NextLineInFilter < Line) NextLineInFilter = FindInputLineInFilter(DumpInputFilter, Line, AnnotationItr, AnnotationEnd); assert(NextLineInFilter && "expected NextLineInFilter to be computed"); if (NextLineInFilter == Line) PrevLineInFilter = Line; // Elide this input line and its annotations if it's not within the // context specified by -dump-input-context of an input line included by // -dump-input-filter. However, in case the resulting ellipsis would occupy // more lines than the input lines and annotations it elides, buffer the // elided lines and annotations so we can print them instead. raw_ostream *LineOS = &OS; if ((!PrevLineInFilter || PrevLineInFilter + DumpInputContext < Line) && (NextLineInFilter == UINT_MAX || Line + DumpInputContext < NextLineInFilter)) LineOS = &ElidedLinesOS; else { LineOS = &OS; DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); } // Print right-aligned line number. WithColor(*LineOS, raw_ostream::BLACK, /*Bold=*/true, /*BF=*/false, TheColorMode) << format_decimal(Line, LabelWidth) << ": "; // For the case where -v and colors are enabled, find the annotations for // good matches for expected patterns in order to highlight everything // else in the line. There are no such annotations if -v is disabled. std::vector FoundAndExpectedMatches; if (Req.Verbose && TheColorMode == ColorMode::Enable) { for (auto I = AnnotationItr; I != AnnotationEnd && I->InputLine == Line; ++I) { if (I->FoundAndExpectedMatch) FoundAndExpectedMatches.push_back(*I); } } // Print numbered line with highlighting where there are no matches for // expected patterns. bool Newline = false; { WithColor COS(*LineOS, raw_ostream::SAVEDCOLOR, /*Bold=*/false, /*BG=*/false, TheColorMode); bool InMatch = false; if (Req.Verbose) COS.changeColor(raw_ostream::CYAN, true, true); for (unsigned Col = 1; InputFilePtr != InputFileEnd && !Newline; ++Col) { bool WasInMatch = InMatch; InMatch = false; for (auto M : FoundAndExpectedMatches) { if (M.InputStartCol <= Col && Col < M.InputEndCol) { InMatch = true; break; } } if (!WasInMatch && InMatch) COS.resetColor(); else if (WasInMatch && !InMatch) COS.changeColor(raw_ostream::CYAN, true, true); if (*InputFilePtr == '\n') Newline = true; else COS << *InputFilePtr; ++InputFilePtr; } } *LineOS << '\n'; unsigned InputLineWidth = InputFilePtr - InputFileLine - Newline; // Print any annotations. while (AnnotationItr != AnnotationEnd && AnnotationItr->InputLine == Line) { WithColor COS(*LineOS, AnnotationItr->Marker.Color, /*Bold=*/true, /*BG=*/false, TheColorMode); // The two spaces below are where the ": " appears on input lines. COS << left_justify(AnnotationItr->Label, LabelWidth) << " "; unsigned Col; for (Col = 1; Col < AnnotationItr->InputStartCol; ++Col) COS << ' '; COS << AnnotationItr->Marker.Lead; // If InputEndCol=UINT_MAX, stop at InputLineWidth. for (++Col; Col < AnnotationItr->InputEndCol && Col <= InputLineWidth; ++Col) COS << '~'; const std::string &Note = AnnotationItr->Marker.Note; if (!Note.empty()) { // Put the note at the end of the input line. If we were to instead // put the note right after the marker, subsequent annotations for the // same input line might appear to mark this note instead of the input // line. for (; Col <= InputLineWidth; ++Col) COS << ' '; COS << ' ' << Note; } COS << '\n'; ++AnnotationItr; } } DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); OS << ">>>>>>\n"; } int main(int argc, char **argv) { // Enable use of ANSI color codes because FileCheck is using them to // highlight text. llvm::sys::Process::UseANSIEscapeCodes(true); InitLLVM X(argc, argv); cl::ParseCommandLineOptions(argc, argv, /*Overview*/ "", /*Errs*/ nullptr, "FILECHECK_OPTS"); // Select -dump-input* values. The -help documentation specifies the default // value and which value to choose if an option is specified multiple times. // In the latter case, the general rule of thumb is to choose the value that // provides the most information. DumpInputValue DumpInput = DumpInputs.empty() ? DumpInputFail : *std::max_element(DumpInputs.begin(), DumpInputs.end()); DumpInputFilterValue DumpInputFilter; if (DumpInputFilters.empty()) DumpInputFilter = DumpInput == DumpInputAlways ? DumpInputFilterAll : DumpInputFilterError; else DumpInputFilter = *std::max_element(DumpInputFilters.begin(), DumpInputFilters.end()); unsigned DumpInputContext = DumpInputContexts.empty() ? 5 : *std::max_element(DumpInputContexts.begin(), DumpInputContexts.end()); if (DumpInput == DumpInputHelp) { DumpInputAnnotationHelp(outs()); return 0; } if (CheckFilename.empty()) { errs() << " not specified\n"; return 2; } FileCheckRequest Req; for (StringRef Prefix : CheckPrefixes) Req.CheckPrefixes.push_back(Prefix); for (StringRef Prefix : CommentPrefixes) Req.CommentPrefixes.push_back(Prefix); for (StringRef CheckNot : ImplicitCheckNot) Req.ImplicitCheckNot.push_back(CheckNot); bool GlobalDefineError = false; for (StringRef G : GlobalDefines) { size_t EqIdx = G.find('='); if (EqIdx == std::string::npos) { errs() << "Missing equal sign in command-line definition '-D" << G << "'\n"; GlobalDefineError = true; continue; } if (EqIdx == 0) { errs() << "Missing variable name in command-line definition '-D" << G << "'\n"; GlobalDefineError = true; continue; } Req.GlobalDefines.push_back(G); } if (GlobalDefineError) return 2; Req.AllowEmptyInput = AllowEmptyInput; Req.EnableVarScope = EnableVarScope; Req.AllowDeprecatedDagOverlap = AllowDeprecatedDagOverlap; Req.Verbose = Verbose; Req.VerboseVerbose = VerboseVerbose; Req.NoCanonicalizeWhiteSpace = NoCanonicalizeWhiteSpace; Req.MatchFullLines = MatchFullLines; Req.IgnoreCase = IgnoreCase; if (VerboseVerbose) Req.Verbose = true; FileCheck FC(Req); if (!FC.ValidateCheckPrefixes()) return 2; Regex PrefixRE = FC.buildCheckPrefixRegex(); std::string REError; if (!PrefixRE.isValid(REError)) { errs() << "Unable to combine check-prefix strings into a prefix regular " "expression! This is likely a bug in FileCheck's verification of " "the check-prefix strings. Regular expression parsing failed " "with the following error: " << REError << "\n"; return 2; } SourceMgr SM; // Read the expected strings from the check file. ErrorOr> CheckFileOrErr = MemoryBuffer::getFileOrSTDIN(CheckFilename); if (std::error_code EC = CheckFileOrErr.getError()) { errs() << "Could not open check file '" << CheckFilename << "': " << EC.message() << '\n'; return 2; } MemoryBuffer &CheckFile = *CheckFileOrErr.get(); SmallString<4096> CheckFileBuffer; StringRef CheckFileText = FC.CanonicalizeFile(CheckFile, CheckFileBuffer); unsigned CheckFileBufferID = SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( CheckFileText, CheckFile.getBufferIdentifier()), SMLoc()); std::pair ImpPatBufferIDRange; if (FC.readCheckFile(SM, CheckFileText, PrefixRE, &ImpPatBufferIDRange)) return 2; // Open the file to check and add it to SourceMgr. ErrorOr> InputFileOrErr = MemoryBuffer::getFileOrSTDIN(InputFilename); if (InputFilename == "-") InputFilename = ""; // Overwrite for improved diagnostic messages if (std::error_code EC = InputFileOrErr.getError()) { errs() << "Could not open input file '" << InputFilename << "': " << EC.message() << '\n'; return 2; } MemoryBuffer &InputFile = *InputFileOrErr.get(); if (InputFile.getBufferSize() == 0 && !AllowEmptyInput) { errs() << "FileCheck error: '" << InputFilename << "' is empty.\n"; DumpCommandLine(argc, argv); return 2; } SmallString<4096> InputFileBuffer; StringRef InputFileText = FC.CanonicalizeFile(InputFile, InputFileBuffer); SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( InputFileText, InputFile.getBufferIdentifier()), SMLoc()); std::vector Diags; int ExitCode = FC.checkInput(SM, InputFileText, DumpInput == DumpInputNever ? nullptr : &Diags) ? EXIT_SUCCESS : 1; if (DumpInput == DumpInputAlways || (ExitCode == 1 && DumpInput == DumpInputFail)) { errs() << "\n" << "Input file: " << InputFilename << "\n" << "Check file: " << CheckFilename << "\n" << "\n" << "-dump-input=help explains the following input dump.\n" << "\n"; std::vector Annotations; unsigned LabelWidth; BuildInputAnnotations(SM, CheckFileBufferID, ImpPatBufferIDRange, Diags, Annotations, LabelWidth); DumpAnnotatedInput(errs(), Req, DumpInputFilter, DumpInputContext, InputFileText, Annotations, LabelWidth); } return ExitCode; } ./ldc-1.36.0-src/utils/FileCheck-16.cpp0000644000175000017500000011002414564454261017411 0ustar matthiasmatthias//===- FileCheck.cpp - Check that File's Contents match what is expected --===// // // 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 // //===----------------------------------------------------------------------===// // // FileCheck does a line-by line check of a file that validates whether it // contains the expected content. This is useful for regression tests etc. // // This program exits with an exit status of 2 on error, exit status of 0 if // the file matched the expected contents, and exit status of 1 if it did not // contain the expected contents. // //===----------------------------------------------------------------------===// #include "llvm/FileCheck/FileCheck.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Process.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" #include #include using namespace llvm; static cl::extrahelp FileCheckOptsEnv( "\nOptions are parsed from the environment variable FILECHECK_OPTS and\n" "from the command line.\n"); static cl::opt CheckFilename(cl::Positional, cl::desc(""), cl::Optional); static cl::opt InputFilename("input-file", cl::desc("File to check (defaults to stdin)"), cl::init("-"), cl::value_desc("filename")); static cl::list CheckPrefixes( "check-prefix", cl::desc("Prefix to use from check file (defaults to 'CHECK')")); static cl::alias CheckPrefixesAlias( "check-prefixes", cl::aliasopt(CheckPrefixes), cl::CommaSeparated, cl::NotHidden, cl::desc( "Alias for -check-prefix permitting multiple comma separated values")); static cl::list CommentPrefixes( "comment-prefixes", cl::CommaSeparated, cl::Hidden, cl::desc("Comma-separated list of comment prefixes to use from check file\n" "(defaults to 'COM,RUN'). Please avoid using this feature in\n" "LLVM's LIT-based test suites, which should be easier to\n" "maintain if they all follow a consistent comment style. This\n" "feature is meant for non-LIT test suites using FileCheck.")); static cl::opt NoCanonicalizeWhiteSpace( "strict-whitespace", cl::desc("Do not treat all horizontal whitespace as equivalent")); static cl::opt IgnoreCase( "ignore-case", cl::desc("Use case-insensitive matching")); static cl::list ImplicitCheckNot( "implicit-check-not", cl::desc("Add an implicit negative check with this pattern to every\n" "positive check. This can be used to ensure that no instances of\n" "this pattern occur which are not matched by a positive pattern"), cl::value_desc("pattern")); static cl::list GlobalDefines("D", cl::AlwaysPrefix, cl::desc("Define a variable to be used in capture patterns."), cl::value_desc("VAR=VALUE")); static cl::opt AllowEmptyInput( "allow-empty", cl::init(false), cl::desc("Allow the input file to be empty. This is useful when making\n" "checks that some error message does not occur, for example.")); static cl::opt AllowUnusedPrefixes( "allow-unused-prefixes", cl::desc("Allow prefixes to be specified but not appear in the test.")); static cl::opt MatchFullLines( "match-full-lines", cl::init(false), cl::desc("Require all positive matches to cover an entire input line.\n" "Allows leading and trailing whitespace if --strict-whitespace\n" "is not also passed.")); static cl::opt EnableVarScope( "enable-var-scope", cl::init(false), cl::desc("Enables scope for regex variables. Variables with names that\n" "do not start with '$' will be reset at the beginning of\n" "each CHECK-LABEL block.")); static cl::opt AllowDeprecatedDagOverlap( "allow-deprecated-dag-overlap", cl::init(false), cl::desc("Enable overlapping among matches in a group of consecutive\n" "CHECK-DAG directives. This option is deprecated and is only\n" "provided for convenience as old tests are migrated to the new\n" "non-overlapping CHECK-DAG implementation.\n")); static cl::opt Verbose( "v", cl::desc("Print directive pattern matches, or add them to the input dump\n" "if enabled.\n")); static cl::opt VerboseVerbose( "vv", cl::desc("Print information helpful in diagnosing internal FileCheck\n" "issues, or add it to the input dump if enabled. Implies\n" "-v.\n")); // The order of DumpInputValue members affects their precedence, as documented // for -dump-input below. enum DumpInputValue { DumpInputNever, DumpInputFail, DumpInputAlways, DumpInputHelp }; static cl::list DumpInputs( "dump-input", cl::desc("Dump input to stderr, adding annotations representing\n" "currently enabled diagnostics. When there are multiple\n" "occurrences of this option, the that appears earliest\n" "in the list below has precedence. The default is 'fail'.\n"), cl::value_desc("mode"), cl::values(clEnumValN(DumpInputHelp, "help", "Explain input dump and quit"), clEnumValN(DumpInputAlways, "always", "Always dump input"), clEnumValN(DumpInputFail, "fail", "Dump input on failure"), clEnumValN(DumpInputNever, "never", "Never dump input"))); // The order of DumpInputFilterValue members affects their precedence, as // documented for -dump-input-filter below. enum DumpInputFilterValue { DumpInputFilterError, DumpInputFilterAnnotation, DumpInputFilterAnnotationFull, DumpInputFilterAll }; static cl::list DumpInputFilters( "dump-input-filter", cl::desc("In the dump requested by -dump-input, print only input lines of\n" "kind plus any context specified by -dump-input-context.\n" "When there are multiple occurrences of this option, the \n" "that appears earliest in the list below has precedence. The\n" "default is 'error' when -dump-input=fail, and it's 'all' when\n" "-dump-input=always.\n"), cl::values(clEnumValN(DumpInputFilterAll, "all", "All input lines"), clEnumValN(DumpInputFilterAnnotationFull, "annotation-full", "Input lines with annotations"), clEnumValN(DumpInputFilterAnnotation, "annotation", "Input lines with starting points of annotations"), clEnumValN(DumpInputFilterError, "error", "Input lines with starting points of error " "annotations"))); static cl::list DumpInputContexts( "dump-input-context", cl::value_desc("N"), cl::desc("In the dump requested by -dump-input, print input lines\n" "before and input lines after any lines specified by\n" "-dump-input-filter. When there are multiple occurrences of\n" "this option, the largest specified has precedence. The\n" "default is 5.\n")); typedef cl::list::const_iterator prefix_iterator; static void DumpCommandLine(int argc, char **argv) { errs() << "FileCheck command line: "; for (int I = 0; I < argc; I++) errs() << " " << argv[I]; errs() << "\n"; } struct MarkerStyle { /// The starting char (before tildes) for marking the line. char Lead; /// What color to use for this annotation. raw_ostream::Colors Color; /// A note to follow the marker, or empty string if none. std::string Note; /// Does this marker indicate inclusion by -dump-input-filter=error? bool FiltersAsError; MarkerStyle() {} MarkerStyle(char Lead, raw_ostream::Colors Color, const std::string &Note = "", bool FiltersAsError = false) : Lead(Lead), Color(Color), Note(Note), FiltersAsError(FiltersAsError) { assert((!FiltersAsError || !Note.empty()) && "expected error diagnostic to have note"); } }; static MarkerStyle GetMarker(FileCheckDiag::MatchType MatchTy) { switch (MatchTy) { case FileCheckDiag::MatchFoundAndExpected: return MarkerStyle('^', raw_ostream::GREEN); case FileCheckDiag::MatchFoundButExcluded: return MarkerStyle('!', raw_ostream::RED, "error: no match expected", /*FiltersAsError=*/true); case FileCheckDiag::MatchFoundButWrongLine: return MarkerStyle('!', raw_ostream::RED, "error: match on wrong line", /*FiltersAsError=*/true); case FileCheckDiag::MatchFoundButDiscarded: return MarkerStyle('!', raw_ostream::CYAN, "discard: overlaps earlier match"); case FileCheckDiag::MatchFoundErrorNote: // Note should always be overridden within the FileCheckDiag. return MarkerStyle('!', raw_ostream::RED, "error: unknown error after match", /*FiltersAsError=*/true); case FileCheckDiag::MatchNoneAndExcluded: return MarkerStyle('X', raw_ostream::GREEN); case FileCheckDiag::MatchNoneButExpected: return MarkerStyle('X', raw_ostream::RED, "error: no match found", /*FiltersAsError=*/true); case FileCheckDiag::MatchNoneForInvalidPattern: return MarkerStyle('X', raw_ostream::RED, "error: match failed for invalid pattern", /*FiltersAsError=*/true); case FileCheckDiag::MatchFuzzy: return MarkerStyle('?', raw_ostream::MAGENTA, "possible intended match", /*FiltersAsError=*/true); } llvm_unreachable_internal("unexpected match type"); } static void DumpInputAnnotationHelp(raw_ostream &OS) { OS << "The following description was requested by -dump-input=help to\n" << "explain the input dump printed by FileCheck.\n" << "\n" << "Related command-line options:\n" << "\n" << " - -dump-input= enables or disables the input dump\n" << " - -dump-input-filter= filters the input lines\n" << " - -dump-input-context= adjusts the context of filtered lines\n" << " - -v and -vv add more annotations\n" << " - -color forces colors to be enabled both in the dump and below\n" << " - -help documents the above options in more detail\n" << "\n" << "These options can also be set via FILECHECK_OPTS. For example, for\n" << "maximum debugging output on failures:\n" << "\n" << " $ FILECHECK_OPTS='-dump-input-filter=all -vv -color' ninja check\n" << "\n" << "Input dump annotation format:\n" << "\n"; // Labels for input lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "L:"; OS << " labels line number L of the input file\n" << " An extra space is added after each input line to represent" << " the\n" << " newline character\n"; // Labels for annotation lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L"; OS << " labels the only match result for either (1) a pattern of type T" << " from\n" << " line L of the check file if L is an integer or (2) the" << " I-th implicit\n" << " pattern if L is \"imp\" followed by an integer " << "I (index origin one)\n"; OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L'N"; OS << " labels the Nth match result for such a pattern\n"; // Markers on annotation lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "^~~"; OS << " marks good match (reported if -v)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "!~~"; OS << " marks bad match, such as:\n" << " - CHECK-NEXT on same line as previous match (error)\n" << " - CHECK-NOT found (error)\n" << " - CHECK-DAG overlapping match (discarded, reported if " << "-vv)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "X~~"; OS << " marks search range when no match is found, such as:\n" << " - CHECK-NEXT not found (error)\n" << " - CHECK-NOT not found (success, reported if -vv)\n" << " - CHECK-DAG not found after discarded matches (error)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "?"; OS << " marks fuzzy match when no match is found\n"; // Elided lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "..."; OS << " indicates elided input lines and annotations, as specified by\n" << " -dump-input-filter and -dump-input-context\n"; // Colors. OS << " - colors "; WithColor(OS, raw_ostream::GREEN, true) << "success"; OS << ", "; WithColor(OS, raw_ostream::RED, true) << "error"; OS << ", "; WithColor(OS, raw_ostream::MAGENTA, true) << "fuzzy match"; OS << ", "; WithColor(OS, raw_ostream::CYAN, true, false) << "discarded match"; OS << ", "; WithColor(OS, raw_ostream::CYAN, true, true) << "unmatched input"; OS << "\n"; } /// An annotation for a single input line. struct InputAnnotation { /// The index of the match result across all checks unsigned DiagIndex; /// The label for this annotation. std::string Label; /// Is this the initial fragment of a diagnostic that has been broken across /// multiple lines? bool IsFirstLine; /// What input line (one-origin indexing) this annotation marks. This might /// be different from the starting line of the original diagnostic if /// !IsFirstLine. unsigned InputLine; /// The column range (one-origin indexing, open end) in which to mark the /// input line. If InputEndCol is UINT_MAX, treat it as the last column /// before the newline. unsigned InputStartCol, InputEndCol; /// The marker to use. MarkerStyle Marker; /// Whether this annotation represents a good match for an expected pattern. bool FoundAndExpectedMatch; }; /// Get an abbreviation for the check type. static std::string GetCheckTypeAbbreviation(Check::FileCheckType Ty) { switch (Ty) { case Check::CheckPlain: if (Ty.getCount() > 1) return "count"; return "check"; case Check::CheckNext: return "next"; case Check::CheckSame: return "same"; case Check::CheckNot: return "not"; case Check::CheckDAG: return "dag"; case Check::CheckLabel: return "label"; case Check::CheckEmpty: return "empty"; case Check::CheckComment: return "com"; case Check::CheckEOF: return "eof"; case Check::CheckBadNot: return "bad-not"; case Check::CheckBadCount: return "bad-count"; case Check::CheckMisspelled: return "misspelled"; case Check::CheckNone: llvm_unreachable("invalid FileCheckType"); } llvm_unreachable("unknown FileCheckType"); } static void BuildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID, const std::pair &ImpPatBufferIDRange, const std::vector &Diags, std::vector &Annotations, unsigned &LabelWidth) { struct CompareSMLoc { bool operator()(const SMLoc &LHS, const SMLoc &RHS) const { return LHS.getPointer() < RHS.getPointer(); } }; // How many diagnostics does each pattern have? std::map DiagCountPerPattern; for (auto Diag : Diags) ++DiagCountPerPattern[Diag.CheckLoc]; // How many diagnostics have we seen so far per pattern? std::map DiagIndexPerPattern; // How many total diagnostics have we seen so far? unsigned DiagIndex = 0; // What's the widest label? LabelWidth = 0; for (auto DiagItr = Diags.begin(), DiagEnd = Diags.end(); DiagItr != DiagEnd; ++DiagItr) { InputAnnotation A; A.DiagIndex = DiagIndex++; // Build label, which uniquely identifies this check result. unsigned CheckBufferID = SM.FindBufferContainingLoc(DiagItr->CheckLoc); auto CheckLineAndCol = SM.getLineAndColumn(DiagItr->CheckLoc, CheckBufferID); llvm::raw_string_ostream Label(A.Label); Label << GetCheckTypeAbbreviation(DiagItr->CheckTy) << ":"; if (CheckBufferID == CheckFileBufferID) Label << CheckLineAndCol.first; else if (ImpPatBufferIDRange.first <= CheckBufferID && CheckBufferID < ImpPatBufferIDRange.second) Label << "imp" << (CheckBufferID - ImpPatBufferIDRange.first + 1); else llvm_unreachable("expected diagnostic's check location to be either in " "the check file or for an implicit pattern"); if (DiagCountPerPattern[DiagItr->CheckLoc] > 1) Label << "'" << DiagIndexPerPattern[DiagItr->CheckLoc]++; LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size()); A.Marker = GetMarker(DiagItr->MatchTy); if (!DiagItr->Note.empty()) { A.Marker.Note = DiagItr->Note; // It's less confusing if notes that don't actually have ranges don't have // markers. For example, a marker for 'with "VAR" equal to "5"' would // seem to indicate where "VAR" matches, but the location we actually have // for the marker simply points to the start of the match/search range for // the full pattern of which the substitution is potentially just one // component. if (DiagItr->InputStartLine == DiagItr->InputEndLine && DiagItr->InputStartCol == DiagItr->InputEndCol) A.Marker.Lead = ' '; } if (DiagItr->MatchTy == FileCheckDiag::MatchFoundErrorNote) { assert(!DiagItr->Note.empty() && "expected custom note for MatchFoundErrorNote"); A.Marker.Note = "error: " + A.Marker.Note; } A.FoundAndExpectedMatch = DiagItr->MatchTy == FileCheckDiag::MatchFoundAndExpected; // Compute the mark location, and break annotation into multiple // annotations if it spans multiple lines. A.IsFirstLine = true; A.InputLine = DiagItr->InputStartLine; A.InputStartCol = DiagItr->InputStartCol; if (DiagItr->InputStartLine == DiagItr->InputEndLine) { // Sometimes ranges are empty in order to indicate a specific point, but // that would mean nothing would be marked, so adjust the range to // include the following character. A.InputEndCol = std::max(DiagItr->InputStartCol + 1, DiagItr->InputEndCol); Annotations.push_back(A); } else { assert(DiagItr->InputStartLine < DiagItr->InputEndLine && "expected input range not to be inverted"); A.InputEndCol = UINT_MAX; Annotations.push_back(A); for (unsigned L = DiagItr->InputStartLine + 1, E = DiagItr->InputEndLine; L <= E; ++L) { // If a range ends before the first column on a line, then it has no // characters on that line, so there's nothing to render. if (DiagItr->InputEndCol == 1 && L == E) break; InputAnnotation B; B.DiagIndex = A.DiagIndex; B.Label = A.Label; B.IsFirstLine = false; B.InputLine = L; B.Marker = A.Marker; B.Marker.Lead = '~'; B.Marker.Note = ""; B.InputStartCol = 1; if (L != E) B.InputEndCol = UINT_MAX; else B.InputEndCol = DiagItr->InputEndCol; B.FoundAndExpectedMatch = A.FoundAndExpectedMatch; Annotations.push_back(B); } } } } static unsigned FindInputLineInFilter( DumpInputFilterValue DumpInputFilter, unsigned CurInputLine, const std::vector::iterator &AnnotationBeg, const std::vector::iterator &AnnotationEnd) { if (DumpInputFilter == DumpInputFilterAll) return CurInputLine; for (auto AnnotationItr = AnnotationBeg; AnnotationItr != AnnotationEnd; ++AnnotationItr) { switch (DumpInputFilter) { case DumpInputFilterAll: llvm_unreachable("unexpected DumpInputFilterAll"); break; case DumpInputFilterAnnotationFull: return AnnotationItr->InputLine; case DumpInputFilterAnnotation: if (AnnotationItr->IsFirstLine) return AnnotationItr->InputLine; break; case DumpInputFilterError: if (AnnotationItr->IsFirstLine && AnnotationItr->Marker.FiltersAsError) return AnnotationItr->InputLine; break; } } return UINT_MAX; } /// To OS, print a vertical ellipsis (right-justified at LabelWidth) if it would /// occupy less lines than ElidedLines, but print ElidedLines otherwise. Either /// way, clear ElidedLines. Thus, if ElidedLines is empty, do nothing. static void DumpEllipsisOrElidedLines(raw_ostream &OS, std::string &ElidedLines, unsigned LabelWidth) { if (ElidedLines.empty()) return; unsigned EllipsisLines = 3; if (EllipsisLines < StringRef(ElidedLines).count('\n')) { for (unsigned i = 0; i < EllipsisLines; ++i) { WithColor(OS, raw_ostream::BLACK, /*Bold=*/true) << right_justify(".", LabelWidth); OS << '\n'; } } else OS << ElidedLines; ElidedLines.clear(); } static void DumpAnnotatedInput(raw_ostream &OS, const FileCheckRequest &Req, DumpInputFilterValue DumpInputFilter, unsigned DumpInputContext, StringRef InputFileText, std::vector &Annotations, unsigned LabelWidth) { OS << "Input was:\n<<<<<<\n"; // Sort annotations. llvm::sort(Annotations, [](const InputAnnotation &A, const InputAnnotation &B) { // 1. Sort annotations in the order of the input lines. // // This makes it easier to find relevant annotations while // iterating input lines in the implementation below. FileCheck // does not always produce diagnostics in the order of input // lines due to, for example, CHECK-DAG and CHECK-NOT. if (A.InputLine != B.InputLine) return A.InputLine < B.InputLine; // 2. Sort annotations in the temporal order FileCheck produced // their associated diagnostics. // // This sort offers several benefits: // // A. On a single input line, the order of annotations reflects // the FileCheck logic for processing directives/patterns. // This can be helpful in understanding cases in which the // order of the associated directives/patterns in the check // file or on the command line either (i) does not match the // temporal order in which FileCheck looks for matches for the // directives/patterns (due to, for example, CHECK-LABEL, // CHECK-NOT, or `--implicit-check-not`) or (ii) does match // that order but does not match the order of those // diagnostics along an input line (due to, for example, // CHECK-DAG). // // On the other hand, because our presentation format presents // input lines in order, there's no clear way to offer the // same benefit across input lines. For consistency, it might // then seem worthwhile to have annotations on a single line // also sorted in input order (that is, by input column). // However, in practice, this appears to be more confusing // than helpful. Perhaps it's intuitive to expect annotations // to be listed in the temporal order in which they were // produced except in cases the presentation format obviously // and inherently cannot support it (that is, across input // lines). // // B. When diagnostics' annotations are split among multiple // input lines, the user must track them from one input line // to the next. One property of the sort chosen here is that // it facilitates the user in this regard by ensuring the // following: when comparing any two input lines, a // diagnostic's annotations are sorted in the same position // relative to all other diagnostics' annotations. return A.DiagIndex < B.DiagIndex; }); // Compute the width of the label column. const unsigned char *InputFilePtr = InputFileText.bytes_begin(), *InputFileEnd = InputFileText.bytes_end(); unsigned LineCount = InputFileText.count('\n'); if (InputFileEnd[-1] != '\n') ++LineCount; unsigned LineNoWidth = std::log10(LineCount) + 1; // +3 below adds spaces (1) to the left of the (right-aligned) line numbers // on input lines and (2) to the right of the (left-aligned) labels on // annotation lines so that input lines and annotation lines are more // visually distinct. For example, the spaces on the annotation lines ensure // that input line numbers and check directive line numbers never align // horizontally. Those line numbers might not even be for the same file. // One space would be enough to achieve that, but more makes it even easier // to see. LabelWidth = std::max(LabelWidth, LineNoWidth) + 3; // Print annotated input lines. unsigned PrevLineInFilter = 0; // 0 means none so far unsigned NextLineInFilter = 0; // 0 means uncomputed, UINT_MAX means none std::string ElidedLines; raw_string_ostream ElidedLinesOS(ElidedLines); ColorMode TheColorMode = WithColor(OS).colorsEnabled() ? ColorMode::Enable : ColorMode::Disable; if (TheColorMode == ColorMode::Enable) ElidedLinesOS.enable_colors(true); auto AnnotationItr = Annotations.begin(), AnnotationEnd = Annotations.end(); for (unsigned Line = 1; InputFilePtr != InputFileEnd || AnnotationItr != AnnotationEnd; ++Line) { const unsigned char *InputFileLine = InputFilePtr; // Compute the previous and next line included by the filter. if (NextLineInFilter < Line) NextLineInFilter = FindInputLineInFilter(DumpInputFilter, Line, AnnotationItr, AnnotationEnd); assert(NextLineInFilter && "expected NextLineInFilter to be computed"); if (NextLineInFilter == Line) PrevLineInFilter = Line; // Elide this input line and its annotations if it's not within the // context specified by -dump-input-context of an input line included by // -dump-input-filter. However, in case the resulting ellipsis would occupy // more lines than the input lines and annotations it elides, buffer the // elided lines and annotations so we can print them instead. raw_ostream *LineOS; if ((!PrevLineInFilter || PrevLineInFilter + DumpInputContext < Line) && (NextLineInFilter == UINT_MAX || Line + DumpInputContext < NextLineInFilter)) LineOS = &ElidedLinesOS; else { LineOS = &OS; DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); } // Print right-aligned line number. WithColor(*LineOS, raw_ostream::BLACK, /*Bold=*/true, /*BF=*/false, TheColorMode) << format_decimal(Line, LabelWidth) << ": "; // For the case where -v and colors are enabled, find the annotations for // good matches for expected patterns in order to highlight everything // else in the line. There are no such annotations if -v is disabled. std::vector FoundAndExpectedMatches; if (Req.Verbose && TheColorMode == ColorMode::Enable) { for (auto I = AnnotationItr; I != AnnotationEnd && I->InputLine == Line; ++I) { if (I->FoundAndExpectedMatch) FoundAndExpectedMatches.push_back(*I); } } // Print numbered line with highlighting where there are no matches for // expected patterns. bool Newline = false; { WithColor COS(*LineOS, raw_ostream::SAVEDCOLOR, /*Bold=*/false, /*BG=*/false, TheColorMode); bool InMatch = false; if (Req.Verbose) COS.changeColor(raw_ostream::CYAN, true, true); for (unsigned Col = 1; InputFilePtr != InputFileEnd && !Newline; ++Col) { bool WasInMatch = InMatch; InMatch = false; for (auto M : FoundAndExpectedMatches) { if (M.InputStartCol <= Col && Col < M.InputEndCol) { InMatch = true; break; } } if (!WasInMatch && InMatch) COS.resetColor(); else if (WasInMatch && !InMatch) COS.changeColor(raw_ostream::CYAN, true, true); if (*InputFilePtr == '\n') { Newline = true; COS << ' '; } else COS << *InputFilePtr; ++InputFilePtr; } } *LineOS << '\n'; unsigned InputLineWidth = InputFilePtr - InputFileLine; // Print any annotations. while (AnnotationItr != AnnotationEnd && AnnotationItr->InputLine == Line) { WithColor COS(*LineOS, AnnotationItr->Marker.Color, /*Bold=*/true, /*BG=*/false, TheColorMode); // The two spaces below are where the ": " appears on input lines. COS << left_justify(AnnotationItr->Label, LabelWidth) << " "; unsigned Col; for (Col = 1; Col < AnnotationItr->InputStartCol; ++Col) COS << ' '; COS << AnnotationItr->Marker.Lead; // If InputEndCol=UINT_MAX, stop at InputLineWidth. for (++Col; Col < AnnotationItr->InputEndCol && Col <= InputLineWidth; ++Col) COS << '~'; const std::string &Note = AnnotationItr->Marker.Note; if (!Note.empty()) { // Put the note at the end of the input line. If we were to instead // put the note right after the marker, subsequent annotations for the // same input line might appear to mark this note instead of the input // line. for (; Col <= InputLineWidth; ++Col) COS << ' '; COS << ' ' << Note; } COS << '\n'; ++AnnotationItr; } } DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); OS << ">>>>>>\n"; } int main(int argc, char **argv) { // Enable use of ANSI color codes because FileCheck is using them to // highlight text. llvm::sys::Process::UseANSIEscapeCodes(true); InitLLVM X(argc, argv); cl::ParseCommandLineOptions(argc, argv, /*Overview*/ "", /*Errs*/ nullptr, "FILECHECK_OPTS"); // Select -dump-input* values. The -help documentation specifies the default // value and which value to choose if an option is specified multiple times. // In the latter case, the general rule of thumb is to choose the value that // provides the most information. DumpInputValue DumpInput = DumpInputs.empty() ? DumpInputFail : *std::max_element(DumpInputs.begin(), DumpInputs.end()); DumpInputFilterValue DumpInputFilter; if (DumpInputFilters.empty()) DumpInputFilter = DumpInput == DumpInputAlways ? DumpInputFilterAll : DumpInputFilterError; else DumpInputFilter = *std::max_element(DumpInputFilters.begin(), DumpInputFilters.end()); unsigned DumpInputContext = DumpInputContexts.empty() ? 5 : *std::max_element(DumpInputContexts.begin(), DumpInputContexts.end()); if (DumpInput == DumpInputHelp) { DumpInputAnnotationHelp(outs()); return 0; } if (CheckFilename.empty()) { errs() << " not specified\n"; return 2; } FileCheckRequest Req; append_range(Req.CheckPrefixes, CheckPrefixes); append_range(Req.CommentPrefixes, CommentPrefixes); append_range(Req.ImplicitCheckNot, ImplicitCheckNot); bool GlobalDefineError = false; for (StringRef G : GlobalDefines) { size_t EqIdx = G.find('='); if (EqIdx == std::string::npos) { errs() << "Missing equal sign in command-line definition '-D" << G << "'\n"; GlobalDefineError = true; continue; } if (EqIdx == 0) { errs() << "Missing variable name in command-line definition '-D" << G << "'\n"; GlobalDefineError = true; continue; } Req.GlobalDefines.push_back(G); } if (GlobalDefineError) return 2; Req.AllowEmptyInput = AllowEmptyInput; Req.AllowUnusedPrefixes = AllowUnusedPrefixes; Req.EnableVarScope = EnableVarScope; Req.AllowDeprecatedDagOverlap = AllowDeprecatedDagOverlap; Req.Verbose = Verbose; Req.VerboseVerbose = VerboseVerbose; Req.NoCanonicalizeWhiteSpace = NoCanonicalizeWhiteSpace; Req.MatchFullLines = MatchFullLines; Req.IgnoreCase = IgnoreCase; if (VerboseVerbose) Req.Verbose = true; FileCheck FC(Req); if (!FC.ValidateCheckPrefixes()) return 2; Regex PrefixRE = FC.buildCheckPrefixRegex(); std::string REError; if (!PrefixRE.isValid(REError)) { errs() << "Unable to combine check-prefix strings into a prefix regular " "expression! This is likely a bug in FileCheck's verification of " "the check-prefix strings. Regular expression parsing failed " "with the following error: " << REError << "\n"; return 2; } SourceMgr SM; // Read the expected strings from the check file. ErrorOr> CheckFileOrErr = MemoryBuffer::getFileOrSTDIN(CheckFilename, /*IsText=*/true); if (std::error_code EC = CheckFileOrErr.getError()) { errs() << "Could not open check file '" << CheckFilename << "': " << EC.message() << '\n'; return 2; } MemoryBuffer &CheckFile = *CheckFileOrErr.get(); SmallString<4096> CheckFileBuffer; StringRef CheckFileText = FC.CanonicalizeFile(CheckFile, CheckFileBuffer); unsigned CheckFileBufferID = SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( CheckFileText, CheckFile.getBufferIdentifier()), SMLoc()); std::pair ImpPatBufferIDRange; if (FC.readCheckFile(SM, CheckFileText, PrefixRE, &ImpPatBufferIDRange)) return 2; // Open the file to check and add it to SourceMgr. ErrorOr> InputFileOrErr = MemoryBuffer::getFileOrSTDIN(InputFilename, /*IsText=*/true); if (InputFilename == "-") InputFilename = ""; // Overwrite for improved diagnostic messages if (std::error_code EC = InputFileOrErr.getError()) { errs() << "Could not open input file '" << InputFilename << "': " << EC.message() << '\n'; return 2; } MemoryBuffer &InputFile = *InputFileOrErr.get(); if (InputFile.getBufferSize() == 0 && !AllowEmptyInput) { errs() << "FileCheck error: '" << InputFilename << "' is empty.\n"; DumpCommandLine(argc, argv); return 2; } SmallString<4096> InputFileBuffer; StringRef InputFileText = FC.CanonicalizeFile(InputFile, InputFileBuffer); SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( InputFileText, InputFile.getBufferIdentifier()), SMLoc()); std::vector Diags; int ExitCode = FC.checkInput(SM, InputFileText, DumpInput == DumpInputNever ? nullptr : &Diags) ? EXIT_SUCCESS : 1; if (DumpInput == DumpInputAlways || (ExitCode == 1 && DumpInput == DumpInputFail)) { errs() << "\n" << "Input file: " << InputFilename << "\n" << "Check file: " << CheckFilename << "\n" << "\n" << "-dump-input=help explains the following input dump.\n" << "\n"; std::vector Annotations; unsigned LabelWidth; BuildInputAnnotations(SM, CheckFileBufferID, ImpPatBufferIDRange, Diags, Annotations, LabelWidth); DumpAnnotatedInput(errs(), Req, DumpInputFilter, DumpInputContext, InputFileText, Annotations, LabelWidth); } return ExitCode; } ./ldc-1.36.0-src/utils/FileCheck-12.cpp0000644000175000017500000010623514564454261017416 0ustar matthiasmatthias//===- FileCheck.cpp - Check that File's Contents match what is expected --===// // // 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 // //===----------------------------------------------------------------------===// // // FileCheck does a line-by line check of a file that validates whether it // contains the expected content. This is useful for regression tests etc. // // This program exits with an exit status of 2 on error, exit status of 0 if // the file matched the expected contents, and exit status of 1 if it did not // contain the expected contents. // //===----------------------------------------------------------------------===// #include "llvm/FileCheck/FileCheck.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/Process.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" #include using namespace llvm; static cl::extrahelp FileCheckOptsEnv( "\nOptions are parsed from the environment variable FILECHECK_OPTS and\n" "from the command line.\n"); static cl::opt CheckFilename(cl::Positional, cl::desc(""), cl::Optional); static cl::opt InputFilename("input-file", cl::desc("File to check (defaults to stdin)"), cl::init("-"), cl::value_desc("filename")); static cl::list CheckPrefixes( "check-prefix", cl::desc("Prefix to use from check file (defaults to 'CHECK')")); static cl::alias CheckPrefixesAlias( "check-prefixes", cl::aliasopt(CheckPrefixes), cl::CommaSeparated, cl::NotHidden, cl::desc( "Alias for -check-prefix permitting multiple comma separated values")); static cl::list CommentPrefixes( "comment-prefixes", cl::CommaSeparated, cl::Hidden, cl::desc("Comma-separated list of comment prefixes to use from check file\n" "(defaults to 'COM,RUN'). Please avoid using this feature in\n" "LLVM's LIT-based test suites, which should be easier to\n" "maintain if they all follow a consistent comment style. This\n" "feature is meant for non-LIT test suites using FileCheck.")); static cl::opt NoCanonicalizeWhiteSpace( "strict-whitespace", cl::desc("Do not treat all horizontal whitespace as equivalent")); static cl::opt IgnoreCase( "ignore-case", cl::desc("Use case-insensitive matching")); static cl::list ImplicitCheckNot( "implicit-check-not", cl::desc("Add an implicit negative check with this pattern to every\n" "positive check. This can be used to ensure that no instances of\n" "this pattern occur which are not matched by a positive pattern"), cl::value_desc("pattern")); static cl::list GlobalDefines("D", cl::AlwaysPrefix, cl::desc("Define a variable to be used in capture patterns."), cl::value_desc("VAR=VALUE")); static cl::opt AllowEmptyInput( "allow-empty", cl::init(false), cl::desc("Allow the input file to be empty. This is useful when making\n" "checks that some error message does not occur, for example.")); static cl::opt AllowUnusedPrefixes( "allow-unused-prefixes", cl::init(true), cl::desc("Allow prefixes to be specified but not appear in the test.")); static cl::opt MatchFullLines( "match-full-lines", cl::init(false), cl::desc("Require all positive matches to cover an entire input line.\n" "Allows leading and trailing whitespace if --strict-whitespace\n" "is not also passed.")); static cl::opt EnableVarScope( "enable-var-scope", cl::init(false), cl::desc("Enables scope for regex variables. Variables with names that\n" "do not start with '$' will be reset at the beginning of\n" "each CHECK-LABEL block.")); static cl::opt AllowDeprecatedDagOverlap( "allow-deprecated-dag-overlap", cl::init(false), cl::desc("Enable overlapping among matches in a group of consecutive\n" "CHECK-DAG directives. This option is deprecated and is only\n" "provided for convenience as old tests are migrated to the new\n" "non-overlapping CHECK-DAG implementation.\n")); static cl::opt Verbose( "v", cl::init(false), cl::ZeroOrMore, cl::desc("Print directive pattern matches, or add them to the input dump\n" "if enabled.\n")); static cl::opt VerboseVerbose( "vv", cl::init(false), cl::ZeroOrMore, cl::desc("Print information helpful in diagnosing internal FileCheck\n" "issues, or add it to the input dump if enabled. Implies\n" "-v.\n")); // The order of DumpInputValue members affects their precedence, as documented // for -dump-input below. enum DumpInputValue { DumpInputNever, DumpInputFail, DumpInputAlways, DumpInputHelp }; static cl::list DumpInputs( "dump-input", cl::desc("Dump input to stderr, adding annotations representing\n" "currently enabled diagnostics. When there are multiple\n" "occurrences of this option, the that appears earliest\n" "in the list below has precedence. The default is 'fail'.\n"), cl::value_desc("mode"), cl::values(clEnumValN(DumpInputHelp, "help", "Explain input dump and quit"), clEnumValN(DumpInputAlways, "always", "Always dump input"), clEnumValN(DumpInputFail, "fail", "Dump input on failure"), clEnumValN(DumpInputNever, "never", "Never dump input"))); // The order of DumpInputFilterValue members affects their precedence, as // documented for -dump-input-filter below. enum DumpInputFilterValue { DumpInputFilterError, DumpInputFilterAnnotation, DumpInputFilterAnnotationFull, DumpInputFilterAll }; static cl::list DumpInputFilters( "dump-input-filter", cl::desc("In the dump requested by -dump-input, print only input lines of\n" "kind plus any context specified by -dump-input-context.\n" "When there are multiple occurrences of this option, the \n" "that appears earliest in the list below has precedence. The\n" "default is 'error' when -dump-input=fail, and it's 'all' when\n" "-dump-input=always.\n"), cl::values(clEnumValN(DumpInputFilterAll, "all", "All input lines"), clEnumValN(DumpInputFilterAnnotationFull, "annotation-full", "Input lines with annotations"), clEnumValN(DumpInputFilterAnnotation, "annotation", "Input lines with starting points of annotations"), clEnumValN(DumpInputFilterError, "error", "Input lines with starting points of error " "annotations"))); static cl::list DumpInputContexts( "dump-input-context", cl::value_desc("N"), cl::desc("In the dump requested by -dump-input, print input lines\n" "before and input lines after any lines specified by\n" "-dump-input-filter. When there are multiple occurrences of\n" "this option, the largest specified has precedence. The\n" "default is 5.\n")); typedef cl::list::const_iterator prefix_iterator; static void DumpCommandLine(int argc, char **argv) { errs() << "FileCheck command line: "; for (int I = 0; I < argc; I++) errs() << " " << argv[I]; errs() << "\n"; } struct MarkerStyle { /// The starting char (before tildes) for marking the line. char Lead; /// What color to use for this annotation. raw_ostream::Colors Color; /// A note to follow the marker, or empty string if none. std::string Note; /// Does this marker indicate inclusion by -dump-input-filter=error? bool FiltersAsError; MarkerStyle() {} MarkerStyle(char Lead, raw_ostream::Colors Color, const std::string &Note = "", bool FiltersAsError = false) : Lead(Lead), Color(Color), Note(Note), FiltersAsError(FiltersAsError) { assert((!FiltersAsError || !Note.empty()) && "expected error diagnostic to have note"); } }; static MarkerStyle GetMarker(FileCheckDiag::MatchType MatchTy) { switch (MatchTy) { case FileCheckDiag::MatchFoundAndExpected: return MarkerStyle('^', raw_ostream::GREEN); case FileCheckDiag::MatchFoundButExcluded: return MarkerStyle('!', raw_ostream::RED, "error: no match expected", /*FiltersAsError=*/true); case FileCheckDiag::MatchFoundButWrongLine: return MarkerStyle('!', raw_ostream::RED, "error: match on wrong line", /*FiltersAsError=*/true); case FileCheckDiag::MatchFoundButDiscarded: return MarkerStyle('!', raw_ostream::CYAN, "discard: overlaps earlier match"); case FileCheckDiag::MatchNoneAndExcluded: return MarkerStyle('X', raw_ostream::GREEN); case FileCheckDiag::MatchNoneButExpected: return MarkerStyle('X', raw_ostream::RED, "error: no match found", /*FiltersAsError=*/true); case FileCheckDiag::MatchFuzzy: return MarkerStyle('?', raw_ostream::MAGENTA, "possible intended match", /*FiltersAsError=*/true); } llvm_unreachable_internal("unexpected match type"); } static void DumpInputAnnotationHelp(raw_ostream &OS) { OS << "The following description was requested by -dump-input=help to\n" << "explain the input dump printed by FileCheck.\n" << "\n" << "Related command-line options:\n" << "\n" << " - -dump-input= enables or disables the input dump\n" << " - -dump-input-filter= filters the input lines\n" << " - -dump-input-context= adjusts the context of filtered lines\n" << " - -v and -vv add more annotations\n" << " - -color forces colors to be enabled both in the dump and below\n" << " - -help documents the above options in more detail\n" << "\n" << "These options can also be set via FILECHECK_OPTS. For example, for\n" << "maximum debugging output on failures:\n" << "\n" << " $ FILECHECK_OPTS='-dump-input-filter=all -vv -color' ninja check\n" << "\n" << "Input dump annotation format:\n" << "\n"; // Labels for input lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "L:"; OS << " labels line number L of the input file\n"; // Labels for annotation lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L"; OS << " labels the only match result for either (1) a pattern of type T" << " from\n" << " line L of the check file if L is an integer or (2) the" << " I-th implicit\n" << " pattern if L is \"imp\" followed by an integer " << "I (index origin one)\n"; OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L'N"; OS << " labels the Nth match result for such a pattern\n"; // Markers on annotation lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "^~~"; OS << " marks good match (reported if -v)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "!~~"; OS << " marks bad match, such as:\n" << " - CHECK-NEXT on same line as previous match (error)\n" << " - CHECK-NOT found (error)\n" << " - CHECK-DAG overlapping match (discarded, reported if " << "-vv)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "X~~"; OS << " marks search range when no match is found, such as:\n" << " - CHECK-NEXT not found (error)\n" << " - CHECK-NOT not found (success, reported if -vv)\n" << " - CHECK-DAG not found after discarded matches (error)\n" << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "?"; OS << " marks fuzzy match when no match is found\n"; // Elided lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "..."; OS << " indicates elided input lines and annotations, as specified by\n" << " -dump-input-filter and -dump-input-context\n"; // Colors. OS << " - colors "; WithColor(OS, raw_ostream::GREEN, true) << "success"; OS << ", "; WithColor(OS, raw_ostream::RED, true) << "error"; OS << ", "; WithColor(OS, raw_ostream::MAGENTA, true) << "fuzzy match"; OS << ", "; WithColor(OS, raw_ostream::CYAN, true, false) << "discarded match"; OS << ", "; WithColor(OS, raw_ostream::CYAN, true, true) << "unmatched input"; OS << "\n"; } /// An annotation for a single input line. struct InputAnnotation { /// The index of the match result across all checks unsigned DiagIndex; /// The label for this annotation. std::string Label; /// Is this the initial fragment of a diagnostic that has been broken across /// multiple lines? bool IsFirstLine; /// What input line (one-origin indexing) this annotation marks. This might /// be different from the starting line of the original diagnostic if /// !IsFirstLine. unsigned InputLine; /// The column range (one-origin indexing, open end) in which to mark the /// input line. If InputEndCol is UINT_MAX, treat it as the last column /// before the newline. unsigned InputStartCol, InputEndCol; /// The marker to use. MarkerStyle Marker; /// Whether this annotation represents a good match for an expected pattern. bool FoundAndExpectedMatch; }; /// Get an abbreviation for the check type. static std::string GetCheckTypeAbbreviation(Check::FileCheckType Ty) { switch (Ty) { case Check::CheckPlain: if (Ty.getCount() > 1) return "count"; return "check"; case Check::CheckNext: return "next"; case Check::CheckSame: return "same"; case Check::CheckNot: return "not"; case Check::CheckDAG: return "dag"; case Check::CheckLabel: return "label"; case Check::CheckEmpty: return "empty"; case Check::CheckComment: return "com"; case Check::CheckEOF: return "eof"; case Check::CheckBadNot: return "bad-not"; case Check::CheckBadCount: return "bad-count"; case Check::CheckNone: llvm_unreachable("invalid FileCheckType"); } llvm_unreachable("unknown FileCheckType"); } static void BuildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID, const std::pair &ImpPatBufferIDRange, const std::vector &Diags, std::vector &Annotations, unsigned &LabelWidth) { // How many diagnostics have we seen so far? unsigned DiagCount = 0; // How many diagnostics has the current check seen so far? unsigned CheckDiagCount = 0; // What's the widest label? LabelWidth = 0; for (auto DiagItr = Diags.begin(), DiagEnd = Diags.end(); DiagItr != DiagEnd; ++DiagItr) { InputAnnotation A; A.DiagIndex = DiagCount++; // Build label, which uniquely identifies this check result. unsigned CheckBufferID = SM.FindBufferContainingLoc(DiagItr->CheckLoc); auto CheckLineAndCol = SM.getLineAndColumn(DiagItr->CheckLoc, CheckBufferID); llvm::raw_string_ostream Label(A.Label); Label << GetCheckTypeAbbreviation(DiagItr->CheckTy) << ":"; if (CheckBufferID == CheckFileBufferID) Label << CheckLineAndCol.first; else if (ImpPatBufferIDRange.first <= CheckBufferID && CheckBufferID < ImpPatBufferIDRange.second) Label << "imp" << (CheckBufferID - ImpPatBufferIDRange.first + 1); else llvm_unreachable("expected diagnostic's check location to be either in " "the check file or for an implicit pattern"); unsigned CheckDiagIndex = UINT_MAX; auto DiagNext = std::next(DiagItr); if (DiagNext != DiagEnd && DiagItr->CheckTy == DiagNext->CheckTy && DiagItr->CheckLoc == DiagNext->CheckLoc) CheckDiagIndex = CheckDiagCount++; else if (CheckDiagCount) { CheckDiagIndex = CheckDiagCount; CheckDiagCount = 0; } if (CheckDiagIndex != UINT_MAX) Label << "'" << CheckDiagIndex; Label.flush(); LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size()); A.Marker = GetMarker(DiagItr->MatchTy); if (!DiagItr->Note.empty()) { A.Marker.Note = DiagItr->Note; // It's less confusing if notes that don't actually have ranges don't have // markers. For example, a marker for 'with "VAR" equal to "5"' would // seem to indicate where "VAR" matches, but the location we actually have // for the marker simply points to the start of the match/search range for // the full pattern of which the substitution is potentially just one // component. if (DiagItr->InputStartLine == DiagItr->InputEndLine && DiagItr->InputStartCol == DiagItr->InputEndCol) A.Marker.Lead = ' '; } A.FoundAndExpectedMatch = DiagItr->MatchTy == FileCheckDiag::MatchFoundAndExpected; // Compute the mark location, and break annotation into multiple // annotations if it spans multiple lines. A.IsFirstLine = true; A.InputLine = DiagItr->InputStartLine; A.InputStartCol = DiagItr->InputStartCol; if (DiagItr->InputStartLine == DiagItr->InputEndLine) { // Sometimes ranges are empty in order to indicate a specific point, but // that would mean nothing would be marked, so adjust the range to // include the following character. A.InputEndCol = std::max(DiagItr->InputStartCol + 1, DiagItr->InputEndCol); Annotations.push_back(A); } else { assert(DiagItr->InputStartLine < DiagItr->InputEndLine && "expected input range not to be inverted"); A.InputEndCol = UINT_MAX; Annotations.push_back(A); for (unsigned L = DiagItr->InputStartLine + 1, E = DiagItr->InputEndLine; L <= E; ++L) { // If a range ends before the first column on a line, then it has no // characters on that line, so there's nothing to render. if (DiagItr->InputEndCol == 1 && L == E) break; InputAnnotation B; B.DiagIndex = A.DiagIndex; B.Label = A.Label; B.IsFirstLine = false; B.InputLine = L; B.Marker = A.Marker; B.Marker.Lead = '~'; B.Marker.Note = ""; B.InputStartCol = 1; if (L != E) B.InputEndCol = UINT_MAX; else B.InputEndCol = DiagItr->InputEndCol; B.FoundAndExpectedMatch = A.FoundAndExpectedMatch; Annotations.push_back(B); } } } } static unsigned FindInputLineInFilter( DumpInputFilterValue DumpInputFilter, unsigned CurInputLine, const std::vector::iterator &AnnotationBeg, const std::vector::iterator &AnnotationEnd) { if (DumpInputFilter == DumpInputFilterAll) return CurInputLine; for (auto AnnotationItr = AnnotationBeg; AnnotationItr != AnnotationEnd; ++AnnotationItr) { switch (DumpInputFilter) { case DumpInputFilterAll: llvm_unreachable("unexpected DumpInputFilterAll"); break; case DumpInputFilterAnnotationFull: return AnnotationItr->InputLine; case DumpInputFilterAnnotation: if (AnnotationItr->IsFirstLine) return AnnotationItr->InputLine; break; case DumpInputFilterError: if (AnnotationItr->IsFirstLine && AnnotationItr->Marker.FiltersAsError) return AnnotationItr->InputLine; break; } } return UINT_MAX; } /// To OS, print a vertical ellipsis (right-justified at LabelWidth) if it would /// occupy less lines than ElidedLines, but print ElidedLines otherwise. Either /// way, clear ElidedLines. Thus, if ElidedLines is empty, do nothing. static void DumpEllipsisOrElidedLines(raw_ostream &OS, std::string &ElidedLines, unsigned LabelWidth) { if (ElidedLines.empty()) return; unsigned EllipsisLines = 3; if (EllipsisLines < StringRef(ElidedLines).count('\n')) { for (unsigned i = 0; i < EllipsisLines; ++i) { WithColor(OS, raw_ostream::BLACK, /*Bold=*/true) << right_justify(".", LabelWidth); OS << '\n'; } } else OS << ElidedLines; ElidedLines.clear(); } static void DumpAnnotatedInput(raw_ostream &OS, const FileCheckRequest &Req, DumpInputFilterValue DumpInputFilter, unsigned DumpInputContext, StringRef InputFileText, std::vector &Annotations, unsigned LabelWidth) { OS << "Input was:\n<<<<<<\n"; // Sort annotations. llvm::sort(Annotations, [](const InputAnnotation &A, const InputAnnotation &B) { // 1. Sort annotations in the order of the input lines. // // This makes it easier to find relevant annotations while // iterating input lines in the implementation below. FileCheck // does not always produce diagnostics in the order of input // lines due to, for example, CHECK-DAG and CHECK-NOT. if (A.InputLine != B.InputLine) return A.InputLine < B.InputLine; // 2. Sort annotations in the temporal order FileCheck produced // their associated diagnostics. // // This sort offers several benefits: // // A. On a single input line, the order of annotations reflects // the FileCheck logic for processing directives/patterns. // This can be helpful in understanding cases in which the // order of the associated directives/patterns in the check // file or on the command line either (i) does not match the // temporal order in which FileCheck looks for matches for the // directives/patterns (due to, for example, CHECK-LABEL, // CHECK-NOT, or `--implicit-check-not`) or (ii) does match // that order but does not match the order of those // diagnostics along an input line (due to, for example, // CHECK-DAG). // // On the other hand, because our presentation format presents // input lines in order, there's no clear way to offer the // same benefit across input lines. For consistency, it might // then seem worthwhile to have annotations on a single line // also sorted in input order (that is, by input column). // However, in practice, this appears to be more confusing // than helpful. Perhaps it's intuitive to expect annotations // to be listed in the temporal order in which they were // produced except in cases the presentation format obviously // and inherently cannot support it (that is, across input // lines). // // B. When diagnostics' annotations are split among multiple // input lines, the user must track them from one input line // to the next. One property of the sort chosen here is that // it facilitates the user in this regard by ensuring the // following: when comparing any two input lines, a // diagnostic's annotations are sorted in the same position // relative to all other diagnostics' annotations. return A.DiagIndex < B.DiagIndex; }); // Compute the width of the label column. const unsigned char *InputFilePtr = InputFileText.bytes_begin(), *InputFileEnd = InputFileText.bytes_end(); unsigned LineCount = InputFileText.count('\n'); if (InputFileEnd[-1] != '\n') ++LineCount; unsigned LineNoWidth = std::log10(LineCount) + 1; // +3 below adds spaces (1) to the left of the (right-aligned) line numbers // on input lines and (2) to the right of the (left-aligned) labels on // annotation lines so that input lines and annotation lines are more // visually distinct. For example, the spaces on the annotation lines ensure // that input line numbers and check directive line numbers never align // horizontally. Those line numbers might not even be for the same file. // One space would be enough to achieve that, but more makes it even easier // to see. LabelWidth = std::max(LabelWidth, LineNoWidth) + 3; // Print annotated input lines. unsigned PrevLineInFilter = 0; // 0 means none so far unsigned NextLineInFilter = 0; // 0 means uncomputed, UINT_MAX means none std::string ElidedLines; raw_string_ostream ElidedLinesOS(ElidedLines); ColorMode TheColorMode = WithColor(OS).colorsEnabled() ? ColorMode::Enable : ColorMode::Disable; if (TheColorMode == ColorMode::Enable) ElidedLinesOS.enable_colors(true); auto AnnotationItr = Annotations.begin(), AnnotationEnd = Annotations.end(); for (unsigned Line = 1; InputFilePtr != InputFileEnd || AnnotationItr != AnnotationEnd; ++Line) { const unsigned char *InputFileLine = InputFilePtr; // Compute the previous and next line included by the filter. if (NextLineInFilter < Line) NextLineInFilter = FindInputLineInFilter(DumpInputFilter, Line, AnnotationItr, AnnotationEnd); assert(NextLineInFilter && "expected NextLineInFilter to be computed"); if (NextLineInFilter == Line) PrevLineInFilter = Line; // Elide this input line and its annotations if it's not within the // context specified by -dump-input-context of an input line included by // -dump-input-filter. However, in case the resulting ellipsis would occupy // more lines than the input lines and annotations it elides, buffer the // elided lines and annotations so we can print them instead. raw_ostream *LineOS = &OS; if ((!PrevLineInFilter || PrevLineInFilter + DumpInputContext < Line) && (NextLineInFilter == UINT_MAX || Line + DumpInputContext < NextLineInFilter)) LineOS = &ElidedLinesOS; else { LineOS = &OS; DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); } // Print right-aligned line number. WithColor(*LineOS, raw_ostream::BLACK, /*Bold=*/true, /*BF=*/false, TheColorMode) << format_decimal(Line, LabelWidth) << ": "; // For the case where -v and colors are enabled, find the annotations for // good matches for expected patterns in order to highlight everything // else in the line. There are no such annotations if -v is disabled. std::vector FoundAndExpectedMatches; if (Req.Verbose && TheColorMode == ColorMode::Enable) { for (auto I = AnnotationItr; I != AnnotationEnd && I->InputLine == Line; ++I) { if (I->FoundAndExpectedMatch) FoundAndExpectedMatches.push_back(*I); } } // Print numbered line with highlighting where there are no matches for // expected patterns. bool Newline = false; { WithColor COS(*LineOS, raw_ostream::SAVEDCOLOR, /*Bold=*/false, /*BG=*/false, TheColorMode); bool InMatch = false; if (Req.Verbose) COS.changeColor(raw_ostream::CYAN, true, true); for (unsigned Col = 1; InputFilePtr != InputFileEnd && !Newline; ++Col) { bool WasInMatch = InMatch; InMatch = false; for (auto M : FoundAndExpectedMatches) { if (M.InputStartCol <= Col && Col < M.InputEndCol) { InMatch = true; break; } } if (!WasInMatch && InMatch) COS.resetColor(); else if (WasInMatch && !InMatch) COS.changeColor(raw_ostream::CYAN, true, true); if (*InputFilePtr == '\n') Newline = true; else COS << *InputFilePtr; ++InputFilePtr; } } *LineOS << '\n'; unsigned InputLineWidth = InputFilePtr - InputFileLine - Newline; // Print any annotations. while (AnnotationItr != AnnotationEnd && AnnotationItr->InputLine == Line) { WithColor COS(*LineOS, AnnotationItr->Marker.Color, /*Bold=*/true, /*BG=*/false, TheColorMode); // The two spaces below are where the ": " appears on input lines. COS << left_justify(AnnotationItr->Label, LabelWidth) << " "; unsigned Col; for (Col = 1; Col < AnnotationItr->InputStartCol; ++Col) COS << ' '; COS << AnnotationItr->Marker.Lead; // If InputEndCol=UINT_MAX, stop at InputLineWidth. for (++Col; Col < AnnotationItr->InputEndCol && Col <= InputLineWidth; ++Col) COS << '~'; const std::string &Note = AnnotationItr->Marker.Note; if (!Note.empty()) { // Put the note at the end of the input line. If we were to instead // put the note right after the marker, subsequent annotations for the // same input line might appear to mark this note instead of the input // line. for (; Col <= InputLineWidth; ++Col) COS << ' '; COS << ' ' << Note; } COS << '\n'; ++AnnotationItr; } } DumpEllipsisOrElidedLines(OS, ElidedLinesOS.str(), LabelWidth); OS << ">>>>>>\n"; } int main(int argc, char **argv) { // Enable use of ANSI color codes because FileCheck is using them to // highlight text. llvm::sys::Process::UseANSIEscapeCodes(true); InitLLVM X(argc, argv); cl::ParseCommandLineOptions(argc, argv, /*Overview*/ "", /*Errs*/ nullptr, "FILECHECK_OPTS"); // Select -dump-input* values. The -help documentation specifies the default // value and which value to choose if an option is specified multiple times. // In the latter case, the general rule of thumb is to choose the value that // provides the most information. DumpInputValue DumpInput = DumpInputs.empty() ? DumpInputFail : *std::max_element(DumpInputs.begin(), DumpInputs.end()); DumpInputFilterValue DumpInputFilter; if (DumpInputFilters.empty()) DumpInputFilter = DumpInput == DumpInputAlways ? DumpInputFilterAll : DumpInputFilterError; else DumpInputFilter = *std::max_element(DumpInputFilters.begin(), DumpInputFilters.end()); unsigned DumpInputContext = DumpInputContexts.empty() ? 5 : *std::max_element(DumpInputContexts.begin(), DumpInputContexts.end()); if (DumpInput == DumpInputHelp) { DumpInputAnnotationHelp(outs()); return 0; } if (CheckFilename.empty()) { errs() << " not specified\n"; return 2; } FileCheckRequest Req; for (StringRef Prefix : CheckPrefixes) Req.CheckPrefixes.push_back(Prefix); for (StringRef Prefix : CommentPrefixes) Req.CommentPrefixes.push_back(Prefix); for (StringRef CheckNot : ImplicitCheckNot) Req.ImplicitCheckNot.push_back(CheckNot); bool GlobalDefineError = false; for (StringRef G : GlobalDefines) { size_t EqIdx = G.find('='); if (EqIdx == std::string::npos) { errs() << "Missing equal sign in command-line definition '-D" << G << "'\n"; GlobalDefineError = true; continue; } if (EqIdx == 0) { errs() << "Missing variable name in command-line definition '-D" << G << "'\n"; GlobalDefineError = true; continue; } Req.GlobalDefines.push_back(G); } if (GlobalDefineError) return 2; Req.AllowEmptyInput = AllowEmptyInput; Req.AllowUnusedPrefixes = AllowUnusedPrefixes; Req.EnableVarScope = EnableVarScope; Req.AllowDeprecatedDagOverlap = AllowDeprecatedDagOverlap; Req.Verbose = Verbose; Req.VerboseVerbose = VerboseVerbose; Req.NoCanonicalizeWhiteSpace = NoCanonicalizeWhiteSpace; Req.MatchFullLines = MatchFullLines; Req.IgnoreCase = IgnoreCase; if (VerboseVerbose) Req.Verbose = true; FileCheck FC(Req); if (!FC.ValidateCheckPrefixes()) return 2; Regex PrefixRE = FC.buildCheckPrefixRegex(); std::string REError; if (!PrefixRE.isValid(REError)) { errs() << "Unable to combine check-prefix strings into a prefix regular " "expression! This is likely a bug in FileCheck's verification of " "the check-prefix strings. Regular expression parsing failed " "with the following error: " << REError << "\n"; return 2; } SourceMgr SM; // Read the expected strings from the check file. ErrorOr> CheckFileOrErr = MemoryBuffer::getFileOrSTDIN(CheckFilename); if (std::error_code EC = CheckFileOrErr.getError()) { errs() << "Could not open check file '" << CheckFilename << "': " << EC.message() << '\n'; return 2; } MemoryBuffer &CheckFile = *CheckFileOrErr.get(); SmallString<4096> CheckFileBuffer; StringRef CheckFileText = FC.CanonicalizeFile(CheckFile, CheckFileBuffer); unsigned CheckFileBufferID = SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( CheckFileText, CheckFile.getBufferIdentifier()), SMLoc()); std::pair ImpPatBufferIDRange; if (FC.readCheckFile(SM, CheckFileText, PrefixRE, &ImpPatBufferIDRange)) return 2; // Open the file to check and add it to SourceMgr. ErrorOr> InputFileOrErr = MemoryBuffer::getFileOrSTDIN(InputFilename); if (InputFilename == "-") InputFilename = ""; // Overwrite for improved diagnostic messages if (std::error_code EC = InputFileOrErr.getError()) { errs() << "Could not open input file '" << InputFilename << "': " << EC.message() << '\n'; return 2; } MemoryBuffer &InputFile = *InputFileOrErr.get(); if (InputFile.getBufferSize() == 0 && !AllowEmptyInput) { errs() << "FileCheck error: '" << InputFilename << "' is empty.\n"; DumpCommandLine(argc, argv); return 2; } SmallString<4096> InputFileBuffer; StringRef InputFileText = FC.CanonicalizeFile(InputFile, InputFileBuffer); SM.AddNewSourceBuffer(MemoryBuffer::getMemBuffer( InputFileText, InputFile.getBufferIdentifier()), SMLoc()); std::vector Diags; int ExitCode = FC.checkInput(SM, InputFileText, DumpInput == DumpInputNever ? nullptr : &Diags) ? EXIT_SUCCESS : 1; if (DumpInput == DumpInputAlways || (ExitCode == 1 && DumpInput == DumpInputFail)) { errs() << "\n" << "Input file: " << InputFilename << "\n" << "Check file: " << CheckFilename << "\n" << "\n" << "-dump-input=help explains the following input dump.\n" << "\n"; std::vector Annotations; unsigned LabelWidth; BuildInputAnnotations(SM, CheckFileBufferID, ImpPatBufferIDRange, Diags, Annotations, LabelWidth); DumpAnnotatedInput(errs(), Req, DumpInputFilter, DumpInputContext, InputFileText, Annotations, LabelWidth); } return ExitCode; } ./ldc-1.36.0-src/utils/gen_gccbuiltins.cpp0000644000175000017500000001164014564454261020513 0ustar matthiasmatthias//===-- gen_gccbuiltins.cpp - GCC builtin module generator ----------------===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// // // This tool reads the GCC builtin definitions from LLVM's Intrinsics.td for // a given architecture and accordingly generates a ldc.gccbuiltins_ // module for using them from D code. // //===----------------------------------------------------------------------===// #include "llvm/TableGen/Main.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Path.h" #include "llvm/TableGen/Record.h" #include #include #include #include #include #include using namespace std; using namespace llvm; #if LDC_LLVM_VER >= 1500 #define BUILTIN_NAME_STRING "ClangBuiltinName" #else #define BUILTIN_NAME_STRING "GCCBuiltinName" #endif string dtype(Record* rec, bool readOnlyMem) { Init* typeInit = rec->getValueInit("VT"); if(!typeInit) return ""; string type = typeInit->getAsString(); if(type == "iPTR") return readOnlyMem ? "const void*" : "void*"; string vec = ""; if(type[0] == 'v') { size_t i = 1; while(i != type.size() && type[i] <= '9' && type[i] >= '0') i++; vec = type.substr(1, i - 1); type = type.substr(i); } if(type == "i1" && vec.empty()) return "bool"; else if(type == "i8") return "byte" + vec; else if(type == "i16") return "short" + vec; else if(type == "i32") return "int" + vec; else if(type == "i64") return "long" + vec; else if(type == "f32") return "float" + vec; else if(type == "f64") return "double" + vec; else return ""; } StringRef attributes(ListInit* propertyList) { const auto prop = propertyList->size() ? propertyList->getElementAsRecord(0)->getName() : ""; if (prop == "IntrNoMem") return " pure @safe"; if (prop == "IntrReadArgMem" || prop == "IntrReadWriteArgMem") return " pure"; return ""; } void processRecord(raw_ostream& os, Record& rec, string arch) { if(!rec.getValue(BUILTIN_NAME_STRING)) return; const StringRef builtinName = rec.getValueAsString(BUILTIN_NAME_STRING); string name = rec.getName().str(); if(name.substr(0, 4) != "int_" || name.find(arch) == string::npos) return; name = name.substr(4); replace(name.begin(), name.end(), '_', '.'); name = string("llvm.") + name; ListInit* propsList = rec.getValueAsListInit("IntrProperties"); const StringRef prop = propsList->size() ? propsList->getElementAsRecord(0)->getName() : ""; bool readOnlyMem = prop == "IntrReadArgMem" || prop == "IntrReadMem"; ListInit* paramsList = rec.getValueAsListInit("ParamTypes"); vector params; for(unsigned int i = 0; i < paramsList->size(); i++) { string t = dtype(paramsList->getElementAsRecord(i), readOnlyMem); if(t == "") return; params.push_back(t); } ListInit* retList = rec.getValueAsListInit("RetTypes"); string ret; size_t sz = retList->size(); if(sz == 0) ret = "void"; else if(sz == 1) { ret = dtype(retList->getElementAsRecord(0), false); if(ret == "") return; } else return; os << "pragma(LDC_intrinsic, \"" << name << "\")\n "; os << ret << " " << builtinName << "("; if(params.size()) os << params[0]; for(size_t i = 1; i < params.size(); i++) os << ", " << params[i]; os << ")" << attributes(propsList) << ";\n\n"; } std::string arch; bool emit(raw_ostream& os, RecordKeeper& records) { os << "module ldc.gccbuiltins_"; os << arch; os << "; \n\nimport core.simd;\n\nnothrow @nogc:\n\n"; const auto &defs = records.getDefs(); for (const auto& d : defs) processRecord(os, *d.second, arch); return false; } int main(int argc, char** argv) { if(argc != 3) { fprintf(stderr, "There must be exactly two command line arguments\n"); return 1; } llvm::SmallString<128> file(LLVM_INTRINSIC_TD_PATH); sys::path::append(file, "llvm"); sys::path::append(file, "IR"); sys::path::append(file, "Intrinsics.td"); string iStr = string("-I=") + LLVM_INTRINSIC_TD_PATH; string oStr = string("-o=") + argv[1]; vector args2(argv, argv + 1); args2.push_back(const_cast(file.c_str())); args2.push_back(const_cast(iStr.c_str())); args2.push_back(const_cast(oStr.c_str())); cl::ParseCommandLineOptions(args2.size(), &args2[0]); arch = argv[2]; return TableGenMain(argv[0], emit); } ./ldc-1.36.0-src/packaging/0000755000175000017500000000000014564454261015432 5ustar matthiasmatthias./ldc-1.36.0-src/packaging/bash_completion.d/0000755000175000017500000000000014564454261021022 5ustar matthiasmatthias./ldc-1.36.0-src/packaging/bash_completion.d/ldc20000644000175000017500000002350414564454261021575 0ustar matthiasmatthias# bash completion for ldc2 function elementExists(){ local i isRunning result i=0 isRunning=1 result=0 #~ for i in ${opts_with_equal[@]}; do while [ "$isRunning" -eq 1 ]; do if [ "$i" -ge "${#opts_with_equal[@]}" ]; then isRunning=0 elif [ "${opts_with_equal[$i]}" == "$1" ]; then result=1 isRunning=0 fi ((i++)) done echo $result } function _ldc(){ local prev cur opts opts_with_equal isEqualOptions opts="\ -D -Df -H \ -Hd -Hf -I -J \ -L -X -Xf -annotate \ -asm-verbose -c -check-printf-calls -code-model \ -d -d-debug -d-version -debuglib \ -defaultlib -deps -enable-asserts -enable-boundscheck \ -disable-d-passes -disable-excess-fp-precision -disable-fp-elim -disable-gc \ -disable-non-leaf-fp-elim -enable-preconditions -disable-red-zone -disable-simplify-drtcalls \ -disable-spill-fusing -enable-contracts -enable-correct-eh-support -enable-fp-mad \ -enable-inlining -enable-invariants -enable-load-pre -enable-no-infs-fp-math \ -enable-no-nans-fp-math -enable-objc-arc-opts -enable-postconditions -enable-tbaa \ -enable-unsafe-fp-math -fatal-assembler-warnings -fdata-sections -ffunction-sections \ -float-abi -help -ignore -internalize-public-api-file \ -internalize-public-api-list -jit-emit-debug -jit-enable-eh -join-liveintervals \ -lib -limit-float-precision -linkonce-templates -m32 \ -m64 -march -mattr -mc-x86-disable-arith-relaxation\ -mcpu -mtriple -nested-ctx -noasm \ -nodefaultlib -noruntime -noverify -nozero-initialized-in-bss \ -O -O0 -O1 -O2 \ -O3 -O4 -O5 -o- \ -od -of -op -oq \ -output-bc -output-ll -output-o -output-s \ -pre-RA-sched -print-after-all -print-before-all -print-machineinstrs \ -profile-estimator-loop-weight -profile-info-file -profile-verifier-noassert -realign-stack \ -regalloc -release -relocation-model -rewriter \ -run -schedule-spills -segmented-stacks -shared \ -shrink-wrap -singleobj -soft-float -spiller \ -stack-alignment -stack-protector-buffer-size -stats -tailcallopt \ -time-passes -unittest -v -v-cg \ -verify-dom-info -verify-loop-info -verify-regalloc -verify-region-info \ -version -vv -w -x86-asm-syntax \ -x86-use-vzeroupper " opts_with_equal=(-Df Hd -Hf -I -J -Xf -code-model -d-version -debuglib -defaultlib -deps \ -internalize-public-api-file -float-abi -limit-float-precision -march -mtriple -mattr \ -mcpu -mtriple -nested-ctx -od -pre-RA-sched -regalloc relocation-model -rewriter \ -run -spiller -spiller ) COMPREPLY=() _get_comp_words_by_ref -n : cur prev case ${cur} in #~ -*=*[0-9]) #~ prev="${cur%%=*}=" #~ cur=${cur#*=} # add an = #~ COMPREPLY=( ${prev}$(compgen -W "0 1 2 3 4 5 6 7 8 9" -- "${cur}") ) #~ return 0 #~ ;; '-code-model='*) prev="${cur%%=*}=" cur=${cur#*=} COMPREPLY=( ${prev}$(compgen -W "default small kernel medium large" -- "${cur}") ) return 0 ;; '-float-abi='*) prev="${cur%%=*}=" cur=${cur#*=} COMPREPLY=( ${prev}$(compgen -W "default smolt hard" -- "${cur}") ) return 0 ;; '-nested-ctx='*) prev="${cur%%=*}=" cur=${cur#*=} COMPREPLY=( ${prev}$(compgen -W "array hybrid" -- "${cur}") ) return 0 ;; '-pre-RA-sched='*) prev="${cur%%=*}=" cur=${cur#*=} COMPREPLY=( ${prev}$(compgen -W "default list-td list-burr list-tdrr source list-hybrid list-ilp" -- "${cur}") ) return 0 ;; '-regalloc='*) prev="${cur%%=*}=" cur=${cur#*=} COMPREPLY=(${prev} $(compgen -W "default fast greedy linearscan basic" -- "${cur}") ) return 0 ;; '-relocation-model='*) prev="${cur%%=*}=" cur=${cur#*=} COMPREPLY=( ${prev}$(compgen -W "default static pic dynamic-no-pic" -- "${cur}") ) return 0 ;; '-rewriter'*) prev="${cur%%=*}=" cur=${cur#*=} COMPREPLY=( ${prev}$(compgen -W "local trivial" -- "${cur}") ) return 0 ;; '-spiller='*) COMPREPLY=( ${prev}$(compgen -W "trivial standard inline" -- "${cur}") ) return 0 ;; '-x86-asm-syntax='*) prev="${cur%%=*}=" cur=${cur#*=} COMPREPLY=( ${prev}$(compgen -W "att intel" -- "${cur}") ) return 0 ;; '-Dd='*|'-Hd='*|'-I='*|'-J='*|'-od='*) # add an = prev="${cur%%=*}=" cur=${cur#*=} COMPREPLY=( ${prev}$(compgen -S '/' -d "${cur}") ) return 0 ;; -'Df='*|'-Hf='*|'-deps='*|'-internalize-public-api-file='*|'-of='*|'-Xf='*) # add an = prev="${cur%%=*}=" cur=${cur#*=} COMPREPLY=( ${prev}$(compgen -f "${cur}") ) return 0 ;; -'internalize-public-api-list'*|'-mattr'*|'-d-debug'*|'-d-version'*|'-debuglib'*|-'defaultlib'*|'-march'*|'-mattr'*|'-mcpu'*|'-mtriple'*|'-run'*) # add an = prev="${cur%%=*}=" cur=${cur#*=} COMPREPLY=( ${prev}$(compgen "${cur}") ) return 0 ;; '-limit-float-precision='*|'-stack-alignment='*|'-stack-protector-buffer-size='*) # add an = prev="${cur%%=*}=" cur=${cur#*=} COMPREPLY=( ${prev}$(compgen -W "0 1 2 3 4 5 6 7 8 9" -- "${cur}") ) return 0 ;; esac if [[ "${cur}" != -* ]]; then _filedir '@(d|di|o)' else isEqualOptions=$(elementExists "${cur}") if [ "${isEqualOptions:-0}" -eq 1 ]; then COMPREPLY=( $(compgen -W "${opts}" -S '=' -- "${cur}") ) else COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) fi fi } complete -o nospace -F _ldc ldc2 # Local variables: # mode: shell-script # sh-basic-offset: 4 # sh-indent-comment: t # indent-tabs-mode: nil # End: # ex: ts=4 sw=4 et filetype=sh ./ldc-1.36.0-src/packaging/README.txt0000644000175000017500000000302014564454261017123 0ustar matthiasmatthiasThis is a standalone (DMD-style) binary package for LDC, the LLVM-based D compiler. No installation is required, simply use the executables in the 'bin' subfolder. The compiler configuration file is etc\ldc2.conf and can be easily customized to your liking, e.g., adding implicit command-line options and setting up cross- compilation. If you have an installed Visual C++ toolchain (Visual Studio/Build Tools 2015 or newer), LDC defaults to using linker and libraries of the latest Visual C++ installation it can find. You can set the LDC_VSDIR environment variable to select a specific version, e.g., 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Community'. MSVC toolchain detection and setup is skipped if LDC is run inside a 'VS Native/Cross Tools Command Prompt' (more precisely, if the VSINSTALLDIR environment variable is set). If you don't have a Visual C++ installation, LDC falls back to the integrated LLD (the LLVM linker) and the bundled WinSDK & Visual C++ runtime (import) libraries based on MinGW-w64. In that case, the generated executables depend on an installed (redistributable) Visual C++ 2015+ runtime (vcruntime140.dll, ucrtbase.dll etc.). Linking the Visual C++ runtime statically requires the static libraries from a Visual C++ toolchain, which cannot be bundled with 3rd-party compilers such as LDC due to their restrictive license; see https://github.com/ldc-developers/mingw-w64-libs for details. For further information, including on how to report bugs, please refer to the LDC wiki: http://wiki.dlang.org/LDC. ./ldc-1.36.0-src/packaging/dlang-tools_version0000644000175000017500000000001014564454261021334 0ustar matthiasmatthiasv2.106.1./ldc-1.36.0-src/packaging/mimalloc_version0000644000175000017500000000000614564454261020713 0ustar matthiasmatthiasv1.8.2./ldc-1.36.0-src/packaging/dub_version0000644000175000017500000000000714564454261017671 0ustar matthiasmatthiasv1.35.1./ldc-1.36.0-src/packaging/README0000644000175000017500000000111214564454261016305 0ustar matthiasmatthiasThis is a standalone (DMD-style) binary package for LDC, the LLVM-based D compiler. No installation is required, simply use the executables in the 'bin' subdirectory. Just make sure that the C compiler (i.e., gcc/clang) is in PATH when linking (or set the CC environment variable appropriately). The compiler configuration file is etc/ldc2.conf and can be easily customized to your liking, e.g., adding implicit command-line options and setting up cross- compilation. For further information, including on how to report bugs, please refer to the LDC wiki: http://wiki.dlang.org/LDC. ./ldc-1.36.0-src/packaging/reggae_version0000644000175000017500000000005014564454261020347 0ustar matthiasmatthiasb9e70e1b2f24e897c532da0515d55f137bba798a./ldc-1.36.0-src/packaging/windows_installer.iss0000644000175000017500000001530014564454261021720 0ustar matthiasmatthias; Build it with a cmdline like this: ; "C:\Program Files (x86)\Inno Setup 6\iscc" "/OC:\output" /DLDCVersion=1.23.4 "/DLDCDir=C:\LDC\ldc2-1.23.4-windows-multilib" windows_installer.iss ;#define LDCVersion "1.24.0" ;#define LDCDir "C:\LDC\ldc2-1.24.0-windows-multilib" ; Strip revision from LDCVersion and use as app ID. ; => LDC 1.24.1 will upgrade LDC 1.24.0, but LDC 1.25.0-beta1 is a separate 1.25 family #define LDCAppId RemoveFileExt(LDCVersion) [Setup] AppId=LDC_developers_LDC_{#LDCAppId} AppName=LDC AppVersion={#LDCVersion} AppVerName=LDC {#LDCVersion} ArchitecturesAllowed=x64 ; Enable /CURRENTUSER cmdline option to install for current user only, requiring no admin privileges. ; This affects the default install dir (override with /DIR="x:\dirname") and the registry root key (HKCU, not HKLM). PrivilegesRequiredOverridesAllowed=dialog WizardStyle=modern DisableProgramGroupPage=yes DisableReadyPage=yes DefaultDirName={autopf64}\LDC {#LDCAppId} OutputBaseFilename=ldc2-{#LDCVersion}-windows-multilib Compression=lzma2/ultra64 SolidCompression=yes [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" [Files] Source: "{#LDCDir}\*"; Excludes: "\lib32,\lib64"; DestDir: "{app}"; Components: core; Flags: ignoreversion recursesubdirs Source: "{#LDCDir}\lib64\*"; DestDir: "{app}\lib64"; Components: lib64; Flags: ignoreversion recursesubdirs Source: "{#LDCDir}\lib32\*"; DestDir: "{app}\lib32"; Components: lib32; Flags: ignoreversion recursesubdirs [Components] Name: core; Description: "Executables, config file and imports"; Types: full compact custom; Flags: fixed Name: lib64; Description: "x64 libraries"; Types: full compact Name: lib32; Description: "x86 libraries"; Types: full [Run] ; note: not added to PATH for silent installs with /SILENT or /VERYSILENT Filename: "{cmd}"; Parameters: "/c echo hello"; Check: not IsInUserEnvPath; BeforeInstall: AddToUserEnvPath; Description: "Add to PATH environment variable for current user"; Flags: postinstall skipifsilent runhidden nowait Filename: "{cmd}"; Parameters: "/c echo hello"; Check: IsAdminInstallMode and not IsInSystemEnvPath; BeforeInstall: AddToSystemEnvPath; Description: "Add to system PATH environment variable"; Flags: postinstall skipifsilent runhidden nowait unchecked Filename: "{app}\README.txt"; Description: "View the README file"; Flags: postinstall shellexec skipifdoesntexist skipifsilent unchecked [Registry] ; note: 32-bit registry view of HKLM\SOFTWARE (default admin install) or HKCU\SOFTWARE (/CURRENTUSER) Root: HKA; Subkey: "SOFTWARE\LDC"; Flags: uninsdeletekeyifempty Root: HKA; Subkey: "SOFTWARE\LDC\{#LDCAppId}"; Flags: uninsdeletekey Root: HKA; Subkey: "SOFTWARE\LDC\{#LDCAppId}"; ValueType: string; ValueName: "InstallationFolder"; ValueData: "{app}" Root: HKA; Subkey: "SOFTWARE\LDC\{#LDCAppId}"; ValueType: string; ValueName: "Version"; ValueData: "{#LDCVersion}" [Code] function GetTargetBinDir(): string; begin result := ExpandConstant('{app}') + '\bin'; end; const WM_SETTINGCHANGE = 26; { make the shell etc. reload environment variables } procedure RefreshEnvironment(); var Dummy: string; begin Dummy := 'Environment'; SendBroadcastNotifyMessage(WM_SETTINGCHANGE, 0, CastStringToInteger(Dummy)); end; { add the target bin dir to PATH if not already present } function AddToEnvPath(const RootKey: Integer; const SubKeyName: string): Boolean; var Path: string; Dir: string; begin result := False; if not RegQueryStringValue(RootKey, SubKeyName, 'Path', Path) then Path := ''; Dir := GetTargetBinDir(); { skip if already present } if Pos(';' + Uppercase(Dir) + ';', ';' + Uppercase(Path) + ';') > 0 then exit; { prepend `;` } Path := Dir + ';' + Path; result := RegWriteStringValue(RootKey, SubKeyName, 'Path', Path); end; { add the target bin dir to user PATH if not already present } procedure AddToUserEnvPath(); begin if AddToEnvPath(HKCU, 'Environment') then begin Log(Format('Added to user PATH: %s', [GetTargetBinDir()])); RefreshEnvironment(); end; end; { add the target bin dir to system PATH if not already present } procedure AddToSystemEnvPath(); begin if AddToEnvPath(HKLM, 'System\CurrentControlSet\Control\Session Manager\Environment') then begin Log(Format('Added to system PATH: %s', [GetTargetBinDir()])); RefreshEnvironment(); end; end; { remove the target bin dir from PATH if present } function RemoveFromEnvPath(const RootKey: Integer; const SubKeyName: string): Boolean; var Path: string; Dir: string; P: Integer; begin result := False; if not RegQueryStringValue(RootKey, SubKeyName, 'Path', Path) then exit; Dir := GetTargetBinDir(); P := Pos(';' + Uppercase(Dir) + ';', ';' + Uppercase(Path)); if P = 0 then exit; { remove `;` from Path } Delete(Path, P, Length(Dir) + 1); result := RegWriteStringValue(RootKey, SubKeyName, 'Path', Path); end; { remove the target bin dir from user PATH if present } procedure RemoveFromUserEnvPath(); begin if RemoveFromEnvPath(HKCU, 'Environment') then begin Log(Format('Removed from user PATH: %s', [GetTargetBinDir()])); RefreshEnvironment(); end; end; { remove the target bin dir from system PATH if present } procedure RemoveFromSystemEnvPath(); begin if RemoveFromEnvPath(HKLM, 'System\CurrentControlSet\Control\Session Manager\Environment') then begin Log(Format('Removed from system PATH: %s', [GetTargetBinDir()])); RefreshEnvironment(); end; end; { check if the target bin dir is already in PATH } function IsInEnvPath(const RootKey: Integer; const SubKeyName: string): Boolean; var Path: string; begin result := False; if RegQueryStringValue(RootKey, SubKeyName, 'Path', Path) then result := Pos(';' + Uppercase(GetTargetBinDir()) + ';', ';' + Uppercase(Path) + ';') > 0; end; { check if the target bin dir is already in user PATH } function IsInUserEnvPath(): Boolean; begin result := IsInEnvPath(HKCU, 'Environment'); end; { check if the target bin dir is already in system PATH } function IsInSystemEnvPath(): Boolean; begin result := IsInEnvPath(HKLM, 'System\CurrentControlSet\Control\Session Manager\Environment'); end; { adapt 'Next' button label because of hidden ready page } procedure CurPageChanged(CurPageID: Integer); begin if CurPageID = wpSelectComponents then WizardForm.NextButton.Caption := SetupMessage(msgButtonInstall); end; { remove bin dir from user/system PATH at post-uninstall } procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); begin if (CurUninstallStep = usPostUninstall) then begin RemoveFromUserEnvPath(); if IsAdminInstallMode then RemoveFromSystemEnvPath(); end; end; ./ldc-1.36.0-src/.github/0000755000175000017500000000000014564454261015046 5ustar matthiasmatthias./ldc-1.36.0-src/.github/actions/0000755000175000017500000000000014546260720016501 5ustar matthiasmatthias./ldc-1.36.0-src/.github/actions/4a-test-ldc2/0000755000175000017500000000000014564454261020611 5ustar matthiasmatthias./ldc-1.36.0-src/.github/actions/4a-test-ldc2/action.yml0000644000175000017500000000022514564454261022610 0ustar matthiasmatthiasname: Run LDC D unittests runs: using: composite steps: - run: cd ../build && ctest --output-on-failure -R "ldc2-unittest" shell: bash ./ldc-1.36.0-src/.github/actions/2a-build-pgo/0000755000175000017500000000000014564454261020670 5ustar matthiasmatthias./ldc-1.36.0-src/.github/actions/2a-build-pgo/action.yml0000644000175000017500000000212314564454261022666 0ustar matthiasmatthiasname: Build LDC with PGO instrumentation & gather profile from compiling default libs inputs: cmake_flags: required: false default: '' arch: required: false # Windows only runs: using: composite steps: - name: Build LDC with PGO instrumentation & gather profiles from compiling default libs uses: ./.github/actions/helper-build-ldc with: build_dir: pgo-ldc host_dc: ../bootstrap-ldc/bin/ldmd2 # tweak -vp-counters-per-site to avoid `LLVM Profile Warning: Unable to track new values: Running out of static counters.` cmake_flags: >- -DBUILD_SHARED_LIBS=OFF "-DDFLAGS_LDC=-fprofile-generate -vp-counters-per-site=1.5" ${{ inputs.cmake_flags }} arch: ${{ inputs.arch }} env: LLVM_PROFILE_FILE: ${{ github.workspace }}/../pgo-ldc/%p.profraw - name: Merge PGO profiles # to ../pgo-ldc/merged.profdata shell: bash run: | set -eux cd ../pgo-ldc ../bootstrap-ldc/bin/ldc-profdata merge --output=merged.profdata *.profraw ls -lh *.prof{data,raw} ./ldc-1.36.0-src/.github/actions/7-package/0000755000175000017500000000000014564454261020245 5ustar matthiasmatthias./ldc-1.36.0-src/.github/actions/7-package/action.yml0000644000175000017500000001120714564454261022246 0ustar matthiasmatthiasname: Create package & upload artifact(s) inputs: arch: required: true os: required: false default: '' # native cross_target_triple: required: false default: '' runs: using: composite steps: - name: Set DMD environment variable shell: bash run: | set -eux cd .. if [[ '${{ inputs.cross_target_triple }}' != '' ]]; then DMD="$PWD/bootstrap-ldc/bin/ldmd2" else DMD="$PWD/installed/bin/ldmd2" fi echo "DMD=$DMD" >> $GITHUB_ENV - name: Build & copy dub shell: bash run: | set -eux cd .. git clone --recursive https://github.com/dlang/dub.git cd dub git checkout "$(cat ../ldc/packaging/dub_version)" DFLAGS='' $DMD -run build.d -O -w -linkonce-templates ${DFLAGS:-} cp bin/dub ../installed/bin/ if [[ '${{ inputs.cross_target_triple }}' == '' ]]; then ../installed/bin/dub --version fi - name: Build & copy dlang tools shell: bash run: | set -eux cd .. git clone --recursive https://github.com/dlang/tools.git dlang-tools cd dlang-tools git checkout "$(cat ../ldc/packaging/dlang-tools_version)" mkdir bin $DMD -w -de -dip1000 rdmd.d -of=bin/rdmd $DMD -w -de -dip1000 ddemangle.d -of=bin/ddemangle $DMD -w -de -dip1000 DustMite/dustmite.d DustMite/splitter.d DustMite/polyhash.d -of=bin/dustmite cp bin/{rdmd,ddemangle,dustmite} ../installed/bin/ - name: Build & copy reggae shell: bash run: | set -eux cd .. git clone --recursive https://github.com/atilaneves/reggae.git cd reggae git checkout "$(cat ../ldc/packaging/reggae_version)" archFlag='' if [[ '${{ inputs.cross_target_triple }}' != '' ]]; then archFlag='--arch=${{ inputs.cross_target_triple }}' fi # use host compiler's dub, which is guaranteed to be native DFLAGS="-O -linkonce-templates ${DFLAGS:-}" dub build -v \ --build-mode=allAtOnce --combined $archFlag \ --compiler="$(dirname "$DMD")/ldc2" cp bin/reggae ../installed/bin/ if [[ '${{ inputs.cross_target_triple }}' == '' ]]; then ../installed/bin/reggae --version -b ninja fi - name: Pack installation dir shell: bash run: | set -euxo pipefail cd .. mkdir artifacts if [[ '${{ github.ref }}' = refs/tags/v* ]]; then artifactID='${{ github.ref }}' artifactID="${artifactID:11}" else artifactID='${{ github.sha }}' artifactID="${artifactID:0:8}" fi os='${{ inputs.os }}' if [[ "$os" == '' ]]; then if [[ '${{ runner.os }}' == Linux ]]; then os=linux elif [[ '${{ runner.os }}' == macOS ]]; then os=osx elif [[ '${{ runner.os }}' == Windows ]]; then os=windows else echo "Error: unknown OS '${{ runner.os }}'" exit 1 fi fi artifactName="ldc2-$artifactID-$os-${{ inputs.arch }}" mv installed $artifactName if [[ '${{ runner.os }}' == Windows ]]; then 7z a -mx=9 artifacts/$artifactName.7z $artifactName >/dev/null else chmod -R go=rX $artifactName if [[ '${{ runner.os }}' == macOS ]]; then sudo chown -R root:wheel $artifactName tar -cf - $artifactName | 7za a artifacts/$artifactName.tar.xz -si -txz -mx9 else tar -cf - --owner=0 --group=0 $artifactName | 7za a artifacts/$artifactName.tar.xz -si -txz -mx9 fi fi # export ARTIFACT_{ID,NAME} echo "ARTIFACT_ID=$artifactID" >> $GITHUB_ENV echo "ARTIFACT_NAME=$os-${{ inputs.arch }}" >> $GITHUB_ENV - name: 'Linux: Pack source dir' if: runner.os == 'Linux' && inputs.os == '' shell: bash run: | set -eux git clean -dffx git submodule foreach git clean -dffx cd .. artifactName="ldc-$ARTIFACT_ID-src" chmod -R go=rX ldc GZIP=-9 tar -czf artifacts/$artifactName.tar.gz --exclude-vcs --owner=0 --group=0 --transform="s,^ldc,$artifactName," ldc # unpack & create zip tar -xf artifacts/$artifactName.tar.gz zip -r -9 artifacts/$artifactName.zip $artifactName >/dev/null - name: 'Move artifacts dir for uploading' shell: bash run: mv ../artifacts ./ - name: Upload artifact(s) uses: actions/upload-artifact@v3 with: name: ${{ env.ARTIFACT_NAME }} path: artifacts/ ./ldc-1.36.0-src/.github/actions/1-setup/0000755000175000017500000000000014564454261020004 5ustar matthiasmatthias./ldc-1.36.0-src/.github/actions/1-setup/action.yml0000644000175000017500000001254114564454261022007 0ustar matthiasmatthiasname: Install prerequisites inputs: clang_version: required: true llvm_version: required: true arch: required: true runs: using: composite steps: - name: 'Linux: Install required apt packages' if: runner.os == 'Linux' shell: bash run: | set -eux export DEBIAN_FRONTEND=noninteractive sudo dpkg --add-architecture i386 sudo apt-get -q update # Don't use latest gdb v10+ from Ubuntu toolchain PPA with regressions, use official v9 sudo apt-get -yq install \ git-core cmake g++-multilib \ libcurl4 libcurl4:i386 \ curl gdb=9.1-0ubuntu1 p7zip-full tzdata unzip zip python3-pip - name: 'Linux: Download & extract clang' # into ../clang if: runner.os == 'Linux' shell: bash run: | set -eux cd .. curl -fL --retry 3 --max-time 300 -o clang.tar.xz \ https://github.com/llvm/llvm-project/releases/download/llvmorg-${{ inputs.clang_version }}/clang+llvm-${{ inputs.clang_version }}-x86_64-linux-gnu-ubuntu-18.04.tar.xz mkdir clang tar -xf clang.tar.xz --strip 1 -C clang rm clang.tar.xz clang/bin/clang --version - name: 'Windows: Install clang' if: runner.os == 'Windows' shell: bash run: | set -eux cd .. curl -fL --retry 3 --max-time 300 -o clang.exe \ https://github.com/llvm/llvm-project/releases/download/llvmorg-${{ inputs.clang_version }}/LLVM-${{ inputs.clang_version }}-win64.exe ./clang.exe //S # double-slash for bash rm clang.exe # C:\Program Files\LLVM\bin should already be in PATH clang-cl --version - name: Download & extract LDC-flavoured LLVM # into ../llvm shell: bash run: | set -eux cd .. version='${{ inputs.llvm_version }}' if [[ "$version" = *.* ]]; then tag="ldc-v$version" else tag=CI fi arch='${{ inputs.arch }}' # use assertions for untagged builds assertsSuffix="-withAsserts" if [[ '${{ github.ref }}' = refs/tags/* ]]; then assertsSuffix="" fi if [[ '${{ runner.os }}' == Windows ]]; then curl -fL --retry 3 --max-time 300 -o llvm.7z \ https://github.com/ldc-developers/llvm-project/releases/download/$tag/llvm-$version-windows-$arch$assertsSuffix.7z mkdir llvm cd llvm 7z x ../llvm.7z >/dev/null rm ../llvm.7z cd .. else if [[ '${{ runner.os }}' == Linux ]]; then os=linux elif [[ '${{ runner.os }}' == macOS ]]; then os=osx fi curl -fL --retry 3 --max-time 300 -o llvm.tar.xz \ https://github.com/ldc-developers/llvm-project/releases/download/$tag/llvm-$version-$os-$arch$assertsSuffix.tar.xz mkdir llvm tar -xf llvm.tar.xz --strip 1 -C llvm rm llvm.tar.xz fi llvm/bin/llvm-config --version - name: 'Linux: Make lld the default linker' if: runner.os == 'Linux' shell: bash run: | set -eux sudo ln -sf "$(dirname "$PWD")/llvm/bin/ld.lld" /usr/bin/ld ld --version - name: Install ninja uses: symmetryinvestments/gha-setup-ninja@v1 - name: Install D host compiler uses: dlang-community/setup-dlang@v1 with: compiler: ldc-latest - name: 'Posix: Clear LD_LIBRARY_PATH env variable' # don't use host druntime/Phobos .so/.dylib etc. if: runner.os != 'Windows' shell: bash run: echo "LD_LIBRARY_PATH=" >> $GITHUB_ENV - name: Install lit shell: bash run: | set -euxo pipefail python3 --version python3 -m pip install --user setuptools wheel python3 -m pip install --user lit python3 -c "import lit.main; lit.main.main();" --version . | head -n 1 # the druntime tests require GNU make - name: 'Windows: Make sure GNU make is installed' if: runner.os == 'Windows' shell: cmd run: make --version - name: 'Windows: Download & extract libcurl' # into ../libcurl/ldc2 if: runner.os == 'Windows' shell: bash run: | set -eux cd .. curl -fL --retry 3 --max-time 60 -o libcurl.7z \ https://github.com/ldc-developers/mingw-w64-libs/releases/download/v8.0.0/libcurl-7.74.0-zlib-static-ipv6-sspi-schannel.7z mkdir libcurl cd libcurl 7z x ../libcurl.7z >/dev/null rm ../libcurl.7z mkdir ldc2 if [[ '${{ inputs.arch }}' == x64 ]]; then cp dmd2/windows/bin64/libcurl.dll ldc2/ cp dmd2/windows/lib64/*.* ldc2/ else cp dmd2/windows/bin/libcurl.dll ldc2/ cp dmd2/windows/lib32mscoff/*.* ldc2/ fi ls -lh ldc2/ - name: 'Windows: Set LDC_VSDIR env variable' # to somewhat speed-up MSVC auto-detection if: runner.os == 'Windows' shell: bash run: echo "LDC_VSDIR=$(vswhere -latest -property installationPath)" >> $GITHUB_ENV - name: 'Windows x86: Make CMake configure 64-bit clang-cl for 32-bit code emission' if: runner.os == 'Windows' && inputs.arch == 'x86' shell: bash run: | set -eux echo "CFLAGS=-m32" >> $GITHUB_ENV echo "CXXFLAGS=-m32" >> $GITHUB_ENV echo "ASMFLAGS=-m32" >> $GITHUB_ENV ./ldc-1.36.0-src/.github/actions/merge-macos/0000755000175000017500000000000014564454261020705 5ustar matthiasmatthias./ldc-1.36.0-src/.github/actions/merge-macos/action.yml0000644000175000017500000001114714564454261022711 0ustar matthiasmatthiasname: Merge x86_64 & arm64 packages to universal one runs: using: composite steps: - name: Download x86_64 artifact uses: actions/download-artifact@v3 with: name: osx-x86_64 path: artifacts/ - name: Download arm64 artifact uses: actions/download-artifact@v3 with: name: osx-arm64 path: artifacts/ - name: Extract & merge artifacts shell: bash run: | set -euxo pipefail mkdir ldc2-{x86_64,arm64} tar -xf artifacts/ldc2-*-x86_64.tar.xz --strip 1 -C ldc2-x86_64 tar -xf artifacts/ldc2-*-arm64.tar.xz --strip 1 -C ldc2-arm64 cp -R ldc2-x86_64 ldc2-universal cd ldc2-universal # rename/copy lib dirs mv lib lib-x86_64 cp -R ../ldc2-arm64/lib lib-arm64 cp -R ../ldc2-arm64/lib-ios-arm64 ./ # merge executables to universal ones for exe in bin/*; do rm $exe lipo -create -output $exe ../ldc2-x86_64/$exe ../ldc2-arm64/$exe done ios_triple_suffix=$(grep -o -E '\-apple-ios[0-9\.]+' etc/ldc2.conf | head -n1) ios_version=${ios_triple_suffix:10} # ldc2.conf: replace the default section and add extra sections # note: arm64-apple-ios section already exists sections=" default: { // default switches injected before all explicit command-line switches switches = [ \"-defaultlib=phobos2-ldc,druntime-ldc\", ]; // default switches appended after all explicit command-line switches post-switches = [ \"-I%%ldcbinarypath%%/../import\", ]; // default directories to be searched for libraries when linking lib-dirs = []; // default rpath when linking against the shared default libs rpath = \"\"; }; \"x86_64-apple-\": { switches = [ \"-defaultlib=phobos2-ldc,druntime-ldc\", \"-Xcc=-target\", \"-Xcc=x86_64-apple-macos\", ]; lib-dirs = [ \"%%ldcbinarypath%%/../lib-x86_64\", ]; rpath = \"%%ldcbinarypath%%/../lib-x86_64\"; }; \"arm64-apple-\": { switches = [ \"-defaultlib=phobos2-ldc,druntime-ldc\", \"-Xcc=-target\", \"-Xcc=arm64-apple-macos\", ]; lib-dirs = [ \"%%ldcbinarypath%%/../lib-arm64\", ]; rpath = \"%%ldcbinarypath%%/../lib-arm64\"; }; \"arm64-apple-ios\": { switches = [ \"-defaultlib=phobos2-ldc,druntime-ldc\", \"-Xcc=-target\", \"-Xcc=arm64-apple-ios$ios_version\", \"-Xcc=-miphoneos-version-min=$ios_version\", \"-Xcc=-isysroot\", \"-Xcc=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk\", ]; lib-dirs = [ \"%%ldcbinarypath%%/../lib-ios-arm64\", ]; rpath = \"%%ldcbinarypath%%/../lib-ios-arm64\"; };" perl -0777 -pi -e "s|\\ndefault:\\n.+?\\n\\};|$sections|s" etc/ldc2.conf cat etc/ldc2.conf - name: Run x86_64/arm64 macOS/iOS cross-compilation smoke tests shell: bash run: | set -eux echo 'void main() { import std.stdio; writefln("Hello world, %d bits", size_t.sizeof * 8); }' > hello.d for os in macos ios; do for arch in x86_64 arm64; do triple="$arch-apple-$os" ldc2-universal/bin/ldc2 -mtriple="$triple" hello.d ldc2-universal/bin/ldc2 -mtriple="$triple" hello.d -link-defaultlib-shared done done - name: Pack universal package shell: bash run: | set -eux mkdir newArtifacts if [[ '${{ github.ref }}' = refs/tags/v* ]]; then artifactID='${{ github.ref }}' artifactID="${artifactID:11}" else artifactID='${{ github.sha }}' artifactID="${artifactID:0:8}" fi artifactName="ldc2-$artifactID-osx-universal" mv ldc2-universal $artifactName chmod -R go=rX $artifactName sudo chown -R root:wheel $artifactName tar -cf - $artifactName | 7za a newArtifacts/$artifactName.tar.xz -si -txz -mx9 - name: Upload universal package uses: actions/upload-artifact@v3 with: name: osx-universal path: newArtifacts/ ./ldc-1.36.0-src/.github/actions/2-build-bootstrap/0000755000175000017500000000000014564454261021757 5ustar matthiasmatthias./ldc-1.36.0-src/.github/actions/2-build-bootstrap/action.yml0000644000175000017500000000120314564454261023753 0ustar matthiasmatthiasname: Build bootstrap LDC inputs: cmake_flags: required: false default: '' arch: required: false # Windows only runs: using: composite steps: - name: Check CMake and ninja versions shell: bash run: | set -eux cmake --version ninja --version - name: Build bootstrap LDC uses: ./.github/actions/helper-build-ldc with: build_dir: bootstrap-ldc host_dc: ldmd2 cmake_flags: >- -DBUILD_SHARED_LIBS=OFF ${{ inputs.cmake_flags }} arch: ${{ inputs.arch }} - run: ../bootstrap-ldc/bin/ldc2 --version shell: bash ./ldc-1.36.0-src/.github/actions/3-build-cross/0000755000175000017500000000000014564454261021074 5ustar matthiasmatthias./ldc-1.36.0-src/.github/actions/3-build-cross/action.yml0000644000175000017500000001406314564454261023100 0ustar matthiasmatthiasname: Cross-compile LDC inputs: arch: required: true os: required: true llvm_version: required: true cmake_flags: required: false default: '' with_pgo: required: false default: false android_ndk_version: required: false default: r21e android_api_level: required: false default: 21 runs: using: composite steps: - name: Download & extract LDC-flavoured LLVM for ${{ inputs.os }}-${{ inputs.arch }} target # into ../llvm-cross shell: bash run: | set -eux cd .. version='${{ inputs.llvm_version }}' if [[ "$version" = *.* ]]; then tag="ldc-v$version" else tag=CI fi if [[ '${{ inputs.os }}' == android ]]; then assertsSuffix="" else # Use assertions for untagged builds. Must be the same as for the host LLVM package, because # llvm-config of host package will be used for the cross build configuration. assertsSuffix="-withAsserts" if [[ '${{ github.ref }}' = refs/tags/* ]]; then assertsSuffix="" fi fi curl -fL --retry 3 --max-time 300 -o llvm-cross.tar.xz \ https://github.com/ldc-developers/llvm-project/releases/download/$tag/llvm-$version-${{ inputs.os }}-${{ inputs.arch }}$assertsSuffix.tar.xz mkdir llvm-cross tar -xf llvm-cross.tar.xz --strip 1 -C llvm-cross rm llvm-cross.tar.xz - name: Make non-native llvm-config runnable on host shell: bash run: | set -eux cd .. if [[ '${{ inputs.os }}' == android ]]; then # Android: use a bash script template version="$(llvm/bin/llvm-config --version)" # from native LLVM sed ldc/.github/actions/3-build-cross/android-llvm-config.in \ -e "s|@LLVM_VERSION@|$version|g" \ -e "s|@LLVM_INSTALL_DIR@|$PWD/llvm-cross|g" \ -e "s|@LLVM_DEFAULT_TARGET_TRIPLE@|irrelevant-android-triple|g" \ -e "s|@LLVM_TARGETS@|AArch64 ARM X86 WebAssembly|g" \ > llvm-cross/bin/llvm-config chmod 755 llvm-cross/bin/llvm-config else # copy from native LLVM cp llvm/bin/llvm-config llvm-cross/bin/ fi # set up DFLAGS to make bootstrap ldmd2 cross-compile/link - name: 'macOS: Set DFLAGS, CROSS_TRIPLE & CROSS_CMAKE_FLAGS' if: inputs.os == 'osx' shell: bash run: | set -eux cd .. arch='${{ inputs.arch }}' triple="$arch-apple-macos" echo "DFLAGS=-mtriple=$triple -L-L$PWD/build-cross-libs/lib -Xcc=-target -Xcc=$triple -Xcc=-isysroot -Xcc=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk" >> $GITHUB_ENV echo "CROSS_TRIPLE=$triple" >> $GITHUB_ENV echo "CROSS_CMAKE_FLAGS=-DCMAKE_OSX_ARCHITECTURES=$arch" >> $GITHUB_ENV - name: 'Android: Download & extract NDK, then set DFLAGS, CROSS_TRIPLE & CROSS_CMAKE_FLAGS' if: inputs.os == 'android' shell: bash run: | set -eux cd .. version='${{ inputs.android_ndk_version }}' curl -fL --retry 3 --max-time 300 -o android-ndk.zip \ https://dl.google.com/android/repository/android-ndk-$version-linux-x86_64.zip unzip android-ndk.zip >/dev/null mv "android-ndk-$version" android-ndk rm android-ndk.zip # The NDK toolchain file enforces `-g` as base C[XX] flag - remove it to # *significantly* reduce executable sizes toolchainFile="$PWD/android-ndk/build/cmake/android.toolchain.cmake" sed -i 's|^ -g$||' "$toolchainFile" arch='${{ inputs.arch }}' apiLevel='${{ inputs.android_api_level }}' cmakeFlags="-DTARGET_SYSTEM='Android;Linux;UNIX'" if [[ "$arch" == armv7a ]]; then triple="$arch-linux-androideabi$apiLevel" cmakeFlags+=' -DANDROID_ABI=armeabi-v7a' elif [[ "$arch" == aarch64 ]]; then triple="$arch-linux-android$apiLevel" cmakeFlags+=' -DANDROID_ABI=arm64-v8a' fi cmakeFlags+=" -DANDROID_NATIVE_API_LEVEL=$apiLevel" cmakeFlags+=" -DANDROID_STL=c++_static" cmakeFlags+=" -DCMAKE_TOOLCHAIN_FILE=$toolchainFile" cmakeFlags+=" -DLDC_LINK_MANUALLY=ON -DD_LINKER_ARGS='-fuse-ld=bfd;-L$PWD/build-cross-libs/lib;-lphobos2-ldc;-ldruntime-ldc'" echo "DFLAGS=-mtriple=$triple -fvisibility=hidden -L-L$PWD/build-cross-libs/lib -gcc=$PWD/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/$triple-clang" >> $GITHUB_ENV echo "CROSS_TRIPLE=$triple" >> $GITHUB_ENV echo "CROSS_CMAKE_FLAGS=$cmakeFlags" >> $GITHUB_ENV - name: 'macOS: Build mimalloc' if: inputs.os == 'osx' uses: ./.github/actions/helper-mimalloc with: cmake_flags: ${{ env.CROSS_CMAKE_FLAGS }} - name: Cross-compile default libraries shell: bash run: | set -euxo pipefail cd .. flags='${{ inputs.cmake_flags }}' # may contain double-quotes flags+=" $CROSS_CMAKE_FLAGS" # convert to array, stripping all `-D` prefixes IFS=$'\n' flags=( $(xargs -n1 <<<"$flags" | cut -b3-) ) bootstrap-ldc/bin/ldc-build-runtime --ninja \ --buildDir="build-cross-libs" \ --dFlags="${DFLAGS// /;}" \ --ldcSrcDir="$PWD/ldc" \ "${flags[@]}" - name: Cross-compile LDC executables uses: ./.github/actions/helper-build-ldc with: build_dir: build-cross host_dc: ../bootstrap-ldc/bin/ldmd2 llvm_dir: llvm-cross specify_install_dir: true cmake_flags: >- -DCMAKE_CROSSCOMPILING=True ${{ inputs.os == 'osx' && '-DALTERNATIVE_MALLOC_O="$PWD/../build-mimalloc/CMakeFiles/mimalloc-obj.dir/src/static.c.o"' || '' }} ${{ inputs.cmake_flags }} ${{ inputs.with_pgo == 'true' && '-DDFLAGS_LDC=-fprofile-use=../pgo-ldc/merged.profdata' || '' }} ${{ env.CROSS_CMAKE_FLAGS }} build_targets: ldc2 ldmd2 ldc-build-runtime ldc-build-plugin ldc-profdata ldc-profgen ldc-prune-cache timetrace2txt ./ldc-1.36.0-src/.github/actions/3-build-cross/android-llvm-config.in0000644000175000017500000001562414564454261025267 0ustar matthiasmatthias#!/bin/bash show_help () { echo "usage: llvm-config