mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-30 09:05:03 +01:00
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>
157 lines
6.1 KiB
C++
157 lines
6.1 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
|
|
|
|
#include "toolchain/driver/clang_runner.h"
|
|
|
|
#include <gmock/gmock.h>
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <utility>
|
|
|
|
#include "common/check.h"
|
|
#include "common/ostream.h"
|
|
#include "llvm/ADT/ScopeExit.h"
|
|
#include "llvm/Object/Binary.h"
|
|
#include "llvm/Support/FormatVariadic.h"
|
|
#include "llvm/Support/Program.h"
|
|
#include "llvm/TargetParser/Host.h"
|
|
#include "testing/base/global_exe_path.h"
|
|
#include "testing/base/test_raw_ostream.h"
|
|
|
|
namespace Carbon {
|
|
namespace {
|
|
|
|
using ::Carbon::Testing::TestRawOstream;
|
|
using ::testing::HasSubstr;
|
|
using ::testing::StrEq;
|
|
|
|
// While these are marked as "internal" APIs, they seem to work and be pretty
|
|
// widely used for their exact documented behavior.
|
|
using ::testing::internal::CaptureStderr;
|
|
using ::testing::internal::CaptureStdout;
|
|
using ::testing::internal::GetCapturedStderr;
|
|
using ::testing::internal::GetCapturedStdout;
|
|
|
|
// Calls the provided lambda with `stderr` and `stdout` captured and saved into
|
|
// the provided output parameters. The lambda's result is returned. It is
|
|
// important to not put anything inside the lambda whose output would be useful
|
|
// in interpreting test errors such as Google Test assertions as their output
|
|
// will end up captured as well.
|
|
template <typename CallableT>
|
|
static auto RunWithCapturedOutput(std::string& out, std::string& err,
|
|
CallableT callable) {
|
|
CaptureStderr();
|
|
CaptureStdout();
|
|
auto result = callable();
|
|
// No need to flush stderr.
|
|
err = GetCapturedStderr();
|
|
llvm::outs().flush();
|
|
out = GetCapturedStdout();
|
|
return result;
|
|
}
|
|
|
|
TEST(ClangRunnerTest, Version) {
|
|
TestRawOstream test_os;
|
|
const auto install_paths =
|
|
InstallPaths::MakeForBazelRunfiles(Testing::GetExePath());
|
|
std::string target = llvm::sys::getDefaultTargetTriple();
|
|
ClangRunner runner(&install_paths, target, &test_os);
|
|
|
|
std::string out;
|
|
std::string err;
|
|
EXPECT_TRUE(RunWithCapturedOutput(out, err,
|
|
[&] { return runner.Run({"--version"}); }));
|
|
// The arguments to Clang should be part of the verbose log.
|
|
EXPECT_THAT(test_os.TakeStr(), HasSubstr("--version"));
|
|
|
|
// No need to flush stderr, just check its contents.
|
|
EXPECT_THAT(err, StrEq(""));
|
|
|
|
// Flush and get the captured stdout to test that this command worked.
|
|
// We don't care about any particular version, just that it is printed.
|
|
EXPECT_THAT(out, HasSubstr("clang version"));
|
|
// The target should match what we provided.
|
|
EXPECT_THAT(out, HasSubstr((llvm::Twine("Target: ") + target).str()));
|
|
// Clang's install should be our private LLVM install bin directory.
|
|
EXPECT_THAT(out, HasSubstr(std::string("InstalledDir: ") +
|
|
install_paths.llvm_install_bin()));
|
|
}
|
|
|
|
// Utility to write a test file. We don't need the full power provided here yet,
|
|
// but we anticipate adding more tests such as compiling basic C++ code in the
|
|
// future and this provides a basis for building those tests.
|
|
static auto WriteTestFile(llvm::StringRef name_suffix, llvm::Twine contents)
|
|
-> std::filesystem::path {
|
|
std::filesystem::path test_tmpdir;
|
|
if (char* tmpdir_env = getenv("TEST_TMPDIR"); tmpdir_env != nullptr) {
|
|
test_tmpdir = std::string(tmpdir_env);
|
|
} else {
|
|
test_tmpdir = std::filesystem::temp_directory_path();
|
|
}
|
|
|
|
const auto* unit_test = ::testing::UnitTest::GetInstance();
|
|
const auto* test_info = unit_test->current_test_info();
|
|
std::filesystem::path test_file =
|
|
test_tmpdir / llvm::formatv("{0}_{1}_{2}", test_info->test_suite_name(),
|
|
test_info->name(), name_suffix)
|
|
.str();
|
|
// Make debugging a bit easier by cleaning up any files from previous runs.
|
|
// This is only necessary when not run in Bazel's test environment.
|
|
std::filesystem::remove(test_file);
|
|
CARBON_CHECK(!std::filesystem::exists(test_file));
|
|
|
|
{
|
|
std::error_code ec;
|
|
llvm::raw_fd_ostream test_file_stream(test_file.string(), ec);
|
|
CARBON_CHECK(!ec, "Test file error: {0}", ec.message());
|
|
test_file_stream << contents;
|
|
}
|
|
return test_file;
|
|
}
|
|
|
|
// It's hard to write a portable and reliable unittest for all the layers of the
|
|
// Clang driver because they work hard to interact with the underlying
|
|
// filesystem and operating system. For now, we just check that a link command
|
|
// is echoed back with plausible contents.
|
|
//
|
|
// TODO: We should eventually strive to have a more complete setup that lets us
|
|
// test more complete Clang functionality here.
|
|
TEST(ClangRunnerTest, LinkCommandEcho) {
|
|
// Just create some empty files to use in a synthetic link command below.
|
|
std::filesystem::path foo_file = WriteTestFile("foo.o", "");
|
|
std::filesystem::path bar_file = WriteTestFile("bar.o", "");
|
|
|
|
const auto install_paths =
|
|
InstallPaths::MakeForBazelRunfiles(Testing::GetExePath());
|
|
std::string verbose_out;
|
|
llvm::raw_string_ostream verbose_os(verbose_out);
|
|
std::string target = llvm::sys::getDefaultTargetTriple();
|
|
ClangRunner runner(&install_paths, target, &verbose_os);
|
|
std::string out;
|
|
std::string err;
|
|
EXPECT_TRUE(RunWithCapturedOutput(out, err,
|
|
[&] {
|
|
return runner.Run({"-###", "-o", "binary",
|
|
foo_file.string(),
|
|
bar_file.string()});
|
|
}))
|
|
<< "Verbose output from runner:\n"
|
|
<< verbose_out << "\n";
|
|
|
|
// Because we use `-###' above, we should just see the command that the Clang
|
|
// driver would have run in a subprocess. This will be very architecture
|
|
// dependent and have lots of variety, but we expect to see both file strings
|
|
// in it the command at least.
|
|
EXPECT_THAT(err, HasSubstr(foo_file.string())) << err;
|
|
EXPECT_THAT(err, HasSubstr(bar_file.string())) << err;
|
|
|
|
// And no non-stderr output should be produced.
|
|
EXPECT_THAT(out, StrEq(""));
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace Carbon
|