Files
carbon-lang/testing/file_test/autoupdate.h
T
4845f40dff Switch CARBON_CHECK to a format string API (#4285)
This switches `DCHECK` and `FATAL` as well.

The goal is to reduce the code size impact of these assertions so that
we can keep more of them enabled. Currently, the largest cost I see from
`CHECK` is not the actual check or the cold code itself, but actually
the failure to inline trivial functions due to the presence of the cold
code. This means that our goal isn't to reduce apparent code size in the
final binary but the LLVM IR cost assessed for these routines in the
inliner, which closely correlates with code size but is a bit different.

As discussed in #4283, experimentation shows that a single function call
with a minimal number of arguments is the lowest cost model for these.
This is easily achieved with a format-string API that internally uses
`llvm::formatv`. This PR is essentially the `CHECK` version of #4283.

However, the check macros are substantially harder to make work with
both format strings and streaming because they also take a condition.
Also, unexpectedly, I was very successful at devising a regular
expression based automated rewrite from the streaming to the format
string form with only low 10s of manual fixes. This includes compacting
strings broken up across lines, etc. Given how well that went, I've
prepared this PR which just directly switches to the format string API
and migrate everything to use it.

One nice side-effect is that the format string approach ends up greatly
simplifying the implementation here as well.

This is ... *shockingly* effective. Parsing speeds up by more than 3%
with just this change. And checking speeds up by **8%** with this change
alone:
```
BM_CompileAPIFileDenseDecls<Phase::Parse>/256      86.3µs ± 1%  82.9µs ± 1%  -3.94%  (p=0.000 n=17+19)
BM_CompileAPIFileDenseDecls<Phase::Parse>/1024      431µs ± 1%   415µs ± 1%  -3.76%  (p=0.000 n=18+19)
BM_CompileAPIFileDenseDecls<Phase::Parse>/4096     1.77ms ± 1%  1.71ms ± 1%  -3.18%  (p=0.000 n=18+19)
BM_CompileAPIFileDenseDecls<Phase::Parse>/16384    7.44ms ± 1%  7.17ms ± 2%  -3.56%  (p=0.000 n=18+20)
BM_CompileAPIFileDenseDecls<Phase::Parse>/65536    30.7ms ± 1%  29.7ms ± 1%  -3.15%  (p=0.000 n=18+20)
BM_CompileAPIFileDenseDecls<Phase::Parse>/262144    131ms ± 1%   127ms ± 1%  -2.81%  (p=0.000 n=18+18)
BM_CompileAPIFileDenseDecls<Phase::Check>/256       878µs ± 2%   800µs ± 1%  -8.91%  (p=0.000 n=19+20)
BM_CompileAPIFileDenseDecls<Phase::Check>/1024     1.88ms ± 2%  1.72ms ± 1%  -8.56%  (p=0.000 n=19+20)
BM_CompileAPIFileDenseDecls<Phase::Check>/4096     5.78ms ± 2%  5.28ms ± 1%  -8.70%  (p=0.000 n=20+18)
BM_CompileAPIFileDenseDecls<Phase::Check>/16384    21.9ms ± 1%  20.1ms ± 1%  -8.02%  (p=0.000 n=18+20)
BM_CompileAPIFileDenseDecls<Phase::Check>/65536    90.4ms ± 2%  83.1ms ± 1%  -8.04%  (p=0.000 n=19+20)
BM_CompileAPIFileDenseDecls<Phase::Check>/262144    381ms ± 2%   352ms ± 1%  -7.79%  (p=0.000 n=19+19)
```

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com>
2024-09-12 16:42:08 +00:00

238 lines
8.7 KiB
C++

// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#ifndef CARBON_TESTING_FILE_TEST_AUTOUPDATE_H_
#define CARBON_TESTING_FILE_TEST_AUTOUPDATE_H_
#include <filesystem>
#include <utility>
#include "common/check.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "re2/re2.h"
#include "testing/file_test/line.h"
namespace Carbon::Testing {
class FileTestAutoupdater {
public:
struct LineNumberReplacement {
bool has_file;
// The line replacement. The pattern should match lines. If has_file,
// pattern should have a file and line group; otherwise, only a line group,
// but default_file_re should be provided.
//
// Uses shared_ptr for storage in SmallVector.
std::shared_ptr<RE2> re;
// line_formatv should provide {0} to substitute with [[@LINE...]] deltas.
std::string line_formatv;
};
explicit FileTestAutoupdater(
const std::filesystem::path& file_test_path, std::string test_command,
std::string dump_command, llvm::StringRef input_content,
const llvm::SmallVector<llvm::StringRef>& filenames,
int autoupdate_line_number,
const llvm::SmallVector<FileTestLine>& non_check_lines,
llvm::StringRef stdout, llvm::StringRef stderr,
const std::optional<RE2>& default_file_re,
const llvm::SmallVector<LineNumberReplacement>& line_number_replacements,
std::function<void(std::string&)> do_extra_check_replacements)
: file_test_path_(file_test_path),
test_command_(std::move(test_command)),
dump_command_(std::move(dump_command)),
input_content_(input_content),
filenames_(filenames),
autoupdate_line_number_(autoupdate_line_number),
non_check_lines_(non_check_lines),
default_file_re_(default_file_re),
line_number_replacements_(line_number_replacements),
do_extra_check_replacements_(std::move(do_extra_check_replacements)),
// BuildCheckLines should only be called after other member
// initialization.
stdout_(BuildCheckLines(stdout, "STDOUT")),
stderr_(BuildCheckLines(stderr, "STDERR")),
any_attached_stdout_lines_(std::any_of(
stdout_.lines.begin(), stdout_.lines.end(),
[&](const CheckLine& line) { return line.line_number() != -1; })),
non_check_line_(non_check_lines_.begin()) {
for (const auto& replacement : line_number_replacements_) {
CARBON_CHECK(replacement.has_file || default_file_re_,
"For replacement with pattern `{0}` to have has_file=false, "
"override GetDefaultFileRE.",
replacement.re->pattern());
CARBON_CHECK(replacement.re->ok(), "Invalid line replacement RE2: {0}",
replacement.re->error());
}
}
// Automatically updates CHECKs in the provided file when dry_run=false.
// Returns true if generated file content differs from actual file content.
auto Run(bool dry_run) -> bool;
private:
// The file and line number that a CHECK line refers to, and the
// replacement from which they were determined, if any.
struct FileAndLineNumber {
explicit FileAndLineNumber(int file_number) : file_number(file_number) {}
explicit FileAndLineNumber(const LineNumberReplacement* replacement,
int file_number, absl::string_view line_number);
const LineNumberReplacement* replacement = nullptr;
int file_number;
int line_number = -1;
};
// A TIP line added by autoupdate. Not associated with any line in output.
class TipLine : public FileTestLineBase {
public:
explicit TipLine(std::string line)
: FileTestLineBase(-1, -1), line_(std::move(line)) {}
auto Print(llvm::raw_ostream& out) const -> void override { out << line_; }
auto is_blank() const -> bool override { return line_.empty(); }
private:
std::string line_;
};
// A CHECK line which is integrated into autoupdate output.
class CheckLine : public FileTestLineBase {
public:
// RE2 is passed by a pointer because it doesn't support std::optional.
explicit CheckLine(FileAndLineNumber file_and_line_number, std::string line)
: FileTestLineBase(file_and_line_number.file_number,
file_and_line_number.line_number),
replacement_(file_and_line_number.replacement),
line_(std::move(line)) {}
auto Print(llvm::raw_ostream& out) const -> void override {
out << indent_ << line_;
}
// When the location of the CHECK in output is known, we can set the indent
// and its line.
auto SetOutputLine(llvm::StringRef indent, int output_file_number,
int output_line_number) -> void {
indent_ = indent;
output_file_number_ = output_file_number;
output_line_number_ = output_line_number;
}
// When the location of all lines in a file are known, we can set the line
// offset based on the target line.
auto RemapLineNumbers(
const llvm::DenseMap<std::pair<int, int>, int>& output_line_remap,
const llvm::SmallVector<int>& new_last_line_numbers) -> void;
auto is_blank() const -> bool override { return false; }
private:
const LineNumberReplacement* replacement_;
std::string line_;
llvm::StringRef indent_;
int output_file_number_ = -1;
int output_line_number_ = -1;
};
// Clusters information for stdout and stderr.
struct CheckLines {
explicit CheckLines(llvm::SmallVector<CheckLine> lines)
: lines(std::move(lines)), cursor(this->lines.begin()) {}
// The full list of check lines.
llvm::SmallVector<CheckLine> lines;
// An iterator into check_lines.
CheckLine* cursor;
};
// Looks for the patterns in the line. Returns the first match, or defaulted
// information if not found.
auto GetFileAndLineNumber(
const llvm::DenseMap<llvm::StringRef, int>& file_to_number_map,
int default_file_number, const std::string& check_line)
-> FileAndLineNumber;
// Builds CheckLine lists for autoupdate.
auto BuildCheckLines(llvm::StringRef output, const char* label) -> CheckLines;
// Adds a non-check line to the new_lines and output_line_remap. The caller
// still needs to advance the cursor when ready.
auto AddRemappedNonCheckLine() -> void;
// Adds TIP lines for file_test usage.
auto AddTips() -> void;
// Returns true if there's a CheckLine that should be added at
// `to_line_number`.
auto ShouldAddCheckLine(const CheckLines& check_lines, bool to_file_end) const
-> bool;
// Adds check_lines until output reaches:
// - If not to_file_end, non_check_line.
// - If to_file_end, the end of the file.
auto AddCheckLines(CheckLines& check_lines, bool to_file_end) -> void;
// Adds remaining check lines for the current file. stderr is always included,
// but stdout is only included when either any_attached_stdout_lines_ or
// is_last_file is true.
auto FinishFile(bool is_last_file) -> void;
// Starts a new split file, updating file and line numbers. Advances past the
// split line.
auto StartSplitFile() -> void;
// Passed-in state.
const std::filesystem::path& file_test_path_;
std::string test_command_;
std::string dump_command_;
llvm::StringRef input_content_;
const llvm::SmallVector<llvm::StringRef>& filenames_;
int autoupdate_line_number_;
const llvm::SmallVector<FileTestLine>& non_check_lines_;
const std::optional<RE2>& default_file_re_;
const llvm::SmallVector<LineNumberReplacement>& line_number_replacements_;
std::function<void(std::string&)> do_extra_check_replacements_;
// Generated TIP lines, from AddTips.
llvm::SmallVector<TipLine> tips_;
// The constructed CheckLine list and cursor.
CheckLines stdout_;
CheckLines stderr_;
// Whether any stdout lines have an associated line number.
bool any_attached_stdout_lines_;
// Iterators for the main Run loop.
const FileTestLine* non_check_line_;
// Tracks the new last line numbers for each file.
llvm::SmallVector<int> new_last_line_numbers_;
// A reusable blank line. new_lines_ can contain a reference back to it.
const FileTestLine blank_line_ = FileTestLine(-1, -1, "");
// Stitched-together content.
llvm::SmallVector<const FileTestLineBase*> new_lines_;
// Maps {file_number, original line number} to a new line number.
llvm::DenseMap<std::pair<int, int>, int> output_line_remap_;
// The current output file number; mainly used for tracking progression.
int output_file_number_ = 0;
// The current output line number in stitched content.
int output_line_number_ = 0;
};
} // namespace Carbon::Testing
#endif // CARBON_TESTING_FILE_TEST_AUTOUPDATE_H_