mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-11-14 23:18:47 +00:00
This adds free function declarations to the top of the source and
adjusts many of the synthesized expressions in the benchmark source to
emit calls to them. This gets the distribution of expressions in our
generated definitions to be significantly more realistic compared to
existing C++ function bodies here and in LLVM.
This still doesn't get us to start calling more methods, have class
types in the signatures of the calls, or have more interesting call
graphs, but seems like a reasonable first step and still an improvement
in the overall fidelity of our benchmarks.
Illustrative generated Carbon:
```carbon
fn Abcde(x: i32, y: i64*) -> (bool, i64);
fn Fghij(p: i32*) -> i32;
// ...
fn MyClass.Method(self, p: i32) -> i32 {
var acc64: i64 = 0;
var acc: i32 = acc64 as i32;
acc = acc + self.Checksum() + p + 1;
let (abc: bool, def: i64) = Abcde(0, &acc64);
acc = (acc + (if abc then 1 else 0) + (def as i32)) + Fghij(&acc) * 2;
return 0;
}
```
Assisted-by: Claude Code and Antigravity with Gemini
---------
Co-authored-by: Geoff Romer <gromer@google.com>
784 lines
33 KiB
C++
784 lines
33 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/benchmarking/source_gen.h"
|
|
|
|
#include <gmock/gmock.h>
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <optional>
|
|
#include <string>
|
|
|
|
#include "common/error.h"
|
|
#include "common/raw_string_ostream.h"
|
|
#include "common/set.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/Support/FormatVariadic.h"
|
|
#include "llvm/Support/VirtualFileSystem.h"
|
|
#include "testing/base/capture_std_streams.h"
|
|
#include "testing/base/global_exe_path.h"
|
|
#include "toolchain/base/install_paths_test_helpers.h"
|
|
#include "toolchain/driver/clang_runner.h"
|
|
#include "toolchain/driver/driver.h"
|
|
|
|
namespace Carbon::Testing {
|
|
namespace {
|
|
|
|
using ::testing::AllOf;
|
|
using ::testing::ContainerEq;
|
|
using ::testing::Contains;
|
|
using ::testing::ContainsRegex;
|
|
using ::testing::Each;
|
|
using ::testing::Eq;
|
|
using ::testing::Ge;
|
|
using ::testing::Gt;
|
|
using ::testing::HasSubstr;
|
|
using ::testing::Le;
|
|
using ::testing::MatchesRegex;
|
|
using ::testing::Not;
|
|
using ::testing::SizeIs;
|
|
using ::testing::StrEq;
|
|
using ::testing::TestParamInfo;
|
|
using ::testing::TestWithParam;
|
|
using ::testing::Values;
|
|
|
|
// Tiny helper to sum the sizes of a range of ranges. Uses a template to avoid
|
|
// hard coding any specific types for the two ranges.
|
|
template <typename T>
|
|
static auto SumSizes(const T& range) -> ssize_t {
|
|
ssize_t sum = 0;
|
|
for (const auto& inner_range : range) {
|
|
sum += inner_range.size();
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
TEST(SourceGenTest, Identifiers) {
|
|
SourceGen gen;
|
|
|
|
auto idents = gen.GetShuffledIdentifiers(1000);
|
|
EXPECT_THAT(idents.size(), Eq(1000));
|
|
for (llvm::StringRef ident : idents) {
|
|
EXPECT_THAT(ident, MatchesRegex("[A-Za-z][A-Za-z0-9_]*"));
|
|
}
|
|
|
|
// We should have at least one identifier of each length [1, 64]. The exact
|
|
// distribution is an implementation detail designed to vaguely match the
|
|
// expected distribution in source code.
|
|
for (int size : llvm::seq_inclusive(1, 64)) {
|
|
EXPECT_THAT(idents, Contains(SizeIs(size)));
|
|
}
|
|
|
|
// Check that identifiers 4 characters or shorter are more common than longer
|
|
// lengths. This is a very rough way of double checking that we got the
|
|
// intended distribution.
|
|
for (int short_size : llvm::seq_inclusive(1, 4)) {
|
|
int short_count = llvm::count_if(idents, [&](auto ident) {
|
|
return static_cast<int>(ident.size()) == short_size;
|
|
});
|
|
for (int long_size : llvm::seq_inclusive(5, 64)) {
|
|
EXPECT_THAT(short_count, Gt(llvm::count_if(idents, [&](auto ident) {
|
|
return static_cast<int>(ident.size()) == long_size;
|
|
})));
|
|
}
|
|
}
|
|
|
|
// Check that repeated calls are different in interesting ways, but have the
|
|
// exact same total bytes.
|
|
ssize_t idents_size_sum = SumSizes(idents);
|
|
for (auto _ : llvm::seq(10)) {
|
|
auto idents2 = gen.GetShuffledIdentifiers(1000);
|
|
EXPECT_THAT(idents2, SizeIs(1000));
|
|
// Should be (at least) a different shuffle of identifiers.
|
|
EXPECT_THAT(idents2, Not(ContainerEq(idents)));
|
|
// But the sum of lengths should be identical.
|
|
EXPECT_THAT(SumSizes(idents2), Eq(idents_size_sum));
|
|
}
|
|
|
|
// Check length constraints have the desired effect.
|
|
idents =
|
|
gen.GetShuffledIdentifiers(1000, /*min_length=*/10, /*max_length=*/20);
|
|
EXPECT_THAT(idents, Each(SizeIs(AllOf(Ge(10), Le(20)))));
|
|
}
|
|
|
|
// For fixed parameters, the total number of bytes across the returned
|
|
// identifiers must not depend on the random seed, even though the specific
|
|
// identifiers do. This checks that across a range of parameters and across many
|
|
// freshly-seeded generators (each `SourceGen` gets an independent random seed).
|
|
TEST(SourceGenTest, IdentifierByteSumStableAcrossSeeds) {
|
|
struct Config {
|
|
int number;
|
|
int min_length;
|
|
int max_length;
|
|
bool uniform;
|
|
bool unique;
|
|
};
|
|
// A spread of parameters including: the default range, narrow ranges, the
|
|
// single-length extreme, uniform distributions, and a uniform range with a
|
|
// `max_length` well beyond the 64 limit that only the uniform path allows.
|
|
Config configs[] = {
|
|
{.number = 1000, .min_length = 1, .max_length = 64, .uniform = false},
|
|
{.number = 1000, .min_length = 4, .max_length = 64, .uniform = false},
|
|
{.number = 999, .min_length = 1, .max_length = 64, .uniform = false},
|
|
{.number = 1000, .min_length = 10, .max_length = 20, .uniform = false},
|
|
{.number = 1000, .min_length = 8, .max_length = 8, .uniform = false},
|
|
{.number = 100, .min_length = 10, .max_length = 19, .uniform = true},
|
|
{.number = 97, .min_length = 10, .max_length = 19, .uniform = true},
|
|
{.number = 500, .min_length = 50, .max_length = 200, .uniform = true},
|
|
{.number = 1000,
|
|
.min_length = 4,
|
|
.max_length = 64,
|
|
.uniform = false,
|
|
.unique = true},
|
|
{.number = 1000,
|
|
.min_length = 4,
|
|
.max_length = 4,
|
|
.uniform = false,
|
|
.unique = true},
|
|
{.number = 200,
|
|
.min_length = 30,
|
|
.max_length = 120,
|
|
.uniform = true,
|
|
.unique = true},
|
|
};
|
|
|
|
for (const Config& c : configs) {
|
|
SCOPED_TRACE(llvm::formatv(
|
|
"Config: number={0} min_length={1} max_length={2} uniform={3} "
|
|
"unique={4}",
|
|
c.number, c.min_length, c.max_length, c.uniform, c.unique));
|
|
std::optional<ssize_t> expected_sum;
|
|
bool any_different = false;
|
|
std::optional<llvm::SmallVector<std::string>> first;
|
|
constexpr int NumSeeds = 8;
|
|
for (int seed : llvm::seq(NumSeeds)) {
|
|
// Each iteration constructs a fresh generator with an independent random
|
|
// seed; the traced index identifies which iteration failed.
|
|
SCOPED_TRACE(llvm::formatv("Seed iteration: {0}", seed));
|
|
SourceGen gen;
|
|
auto idents = c.unique
|
|
? gen.GetShuffledUniqueIdentifiers(
|
|
c.number, c.min_length, c.max_length, c.uniform)
|
|
: gen.GetShuffledIdentifiers(c.number, c.min_length,
|
|
c.max_length, c.uniform);
|
|
EXPECT_THAT(idents, SizeIs(c.number));
|
|
EXPECT_THAT(idents,
|
|
Each(SizeIs(AllOf(Ge(c.min_length), Le(c.max_length)))));
|
|
|
|
ssize_t sum = SumSizes(idents);
|
|
if (!expected_sum) {
|
|
expected_sum = sum;
|
|
first.emplace(idents.begin(), idents.end());
|
|
continue;
|
|
}
|
|
// The byte sum must be identical regardless of the seed.
|
|
EXPECT_THAT(sum, Eq(*expected_sum));
|
|
if (!llvm::equal(idents, *first)) {
|
|
any_different = true;
|
|
}
|
|
}
|
|
// Sanity check that the generators really are producing different content,
|
|
// so that the invariance check above is meaningful rather than trivially
|
|
// passing on identical output.
|
|
EXPECT_TRUE(any_different);
|
|
}
|
|
}
|
|
|
|
TEST(SourceGenTest, UniformIdentifiers) {
|
|
SourceGen gen;
|
|
// Check that uniform identifier length results in exact coverage of each
|
|
// possible length for an easy case, both without and with a remainder.
|
|
auto idents =
|
|
gen.GetShuffledIdentifiers(100, /*min_length=*/10, /*max_length=*/19,
|
|
/*uniform=*/true);
|
|
EXPECT_THAT(idents, Contains(SizeIs(10)).Times(10));
|
|
EXPECT_THAT(idents, Contains(SizeIs(11)).Times(10));
|
|
EXPECT_THAT(idents, Contains(SizeIs(12)).Times(10));
|
|
EXPECT_THAT(idents, Contains(SizeIs(13)).Times(10));
|
|
EXPECT_THAT(idents, Contains(SizeIs(14)).Times(10));
|
|
EXPECT_THAT(idents, Contains(SizeIs(15)).Times(10));
|
|
EXPECT_THAT(idents, Contains(SizeIs(16)).Times(10));
|
|
EXPECT_THAT(idents, Contains(SizeIs(17)).Times(10));
|
|
EXPECT_THAT(idents, Contains(SizeIs(18)).Times(10));
|
|
EXPECT_THAT(idents, Contains(SizeIs(19)).Times(10));
|
|
|
|
idents = gen.GetShuffledIdentifiers(97, /*min_length=*/10, /*max_length=*/19,
|
|
/*uniform=*/true);
|
|
EXPECT_THAT(idents, Contains(SizeIs(10)).Times(10));
|
|
EXPECT_THAT(idents, Contains(SizeIs(11)).Times(10));
|
|
EXPECT_THAT(idents, Contains(SizeIs(12)).Times(10));
|
|
EXPECT_THAT(idents, Contains(SizeIs(13)).Times(10));
|
|
EXPECT_THAT(idents, Contains(SizeIs(14)).Times(10));
|
|
EXPECT_THAT(idents, Contains(SizeIs(15)).Times(10));
|
|
EXPECT_THAT(idents, Contains(SizeIs(16)).Times(10));
|
|
EXPECT_THAT(idents, Contains(SizeIs(17)).Times(9));
|
|
EXPECT_THAT(idents, Contains(SizeIs(18)).Times(9));
|
|
EXPECT_THAT(idents, Contains(SizeIs(19)).Times(9));
|
|
}
|
|
|
|
// Largely covered by `Identifiers` and `UniformIdentifiers`, but need to check
|
|
// for uniqueness specifically.
|
|
TEST(SourceGenTest, UniqueIdentifiers) {
|
|
SourceGen gen;
|
|
|
|
auto unique = gen.GetShuffledUniqueIdentifiers(1000);
|
|
EXPECT_THAT(unique.size(), Eq(1000));
|
|
Set<llvm::StringRef> set;
|
|
for (llvm::StringRef ident : unique) {
|
|
EXPECT_THAT(ident, MatchesRegex("[A-Za-z][A-Za-z0-9_]*"));
|
|
EXPECT_TRUE(set.Insert(ident).is_inserted())
|
|
<< "Colliding identifier: " << ident;
|
|
}
|
|
|
|
// Check single length specifically where uniqueness is the most challenging.
|
|
set.Clear();
|
|
unique = gen.GetShuffledUniqueIdentifiers(1000, /*min_length=*/4,
|
|
/*max_length=*/4);
|
|
for (llvm::StringRef ident : unique) {
|
|
EXPECT_TRUE(set.Insert(ident).is_inserted())
|
|
<< "Colliding identifier: " << ident;
|
|
}
|
|
}
|
|
|
|
// Compiles `source` and returns whether it compiled with no diagnostics at all.
|
|
// Generated code should be warning-free: warnings would add diagnostic
|
|
// emission to the compile benchmarks.
|
|
auto TestCompile(SourceGen::Language language, llvm::StringRef source) -> bool {
|
|
llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> fs =
|
|
new llvm::vfs::InMemoryFileSystem;
|
|
InstallPaths installation(
|
|
InstallPaths::MakeForBazelRunfiles(Testing::GetExePath()));
|
|
|
|
if (language == SourceGen::Language::Carbon) {
|
|
RawStringOstream diagnostics;
|
|
Driver driver(fs, &installation, /*input_stream=*/nullptr, &llvm::outs(),
|
|
&diagnostics);
|
|
AddPreludeFilesToVfs(installation, fs);
|
|
fs->addFile("test.carbon", /*ModificationTime=*/0,
|
|
llvm::MemoryBuffer::getMemBuffer(source));
|
|
bool success = driver
|
|
.RunCommand({"compile", "--phase=check",
|
|
"--no-include-carbon-core", "test.carbon"})
|
|
.success;
|
|
std::string output = diagnostics.TakeStr();
|
|
EXPECT_THAT(output, StrEq(""));
|
|
return success && output.empty();
|
|
}
|
|
|
|
// Clang reads the system headers from the real filesystem.
|
|
llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> overlay_fs =
|
|
new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem());
|
|
overlay_fs->pushOverlay(fs);
|
|
fs->addFile("test.cpp", /*ModificationTime=*/0,
|
|
llvm::MemoryBuffer::getMemBuffer(source));
|
|
ClangRunner runner(&installation, overlay_fs);
|
|
std::string out;
|
|
std::string err;
|
|
ErrorOr<bool> result = CallWithCapturedOutput(out, err, [&] {
|
|
return runner.RunWithNoRuntimes({"-fsyntax-only", "test.cpp"});
|
|
});
|
|
EXPECT_THAT(err, StrEq(""));
|
|
return result.ok() && *result && err.empty();
|
|
}
|
|
|
|
TEST(SourceGenTest, GenApiFileDenseDeclsTest) {
|
|
SourceGen gen;
|
|
|
|
std::string source =
|
|
gen.GenApiFileDenseDecls(1000, SourceGen::DenseDeclParams{});
|
|
// Should be within 1% of the requested line count.
|
|
EXPECT_THAT(source, Contains('\n').Times(AllOf(Ge(950), Le(1050))));
|
|
|
|
EXPECT_TRUE(TestCompile(SourceGen::Language::Carbon, source));
|
|
}
|
|
|
|
TEST(SourceGenTest, GenApiFileDenseDeclsCppTest) {
|
|
SourceGen gen(SourceGen::Language::Cpp);
|
|
|
|
// Generate a 1000-line file which is enough to have a reasonably accurate
|
|
// line count estimate and have a few classes.
|
|
std::string source =
|
|
gen.GenApiFileDenseDecls(1000, SourceGen::DenseDeclParams{});
|
|
// Should be within 10% of the requested line count.
|
|
EXPECT_THAT(source, Contains('\n').Times(AllOf(Ge(900), Le(1100))));
|
|
|
|
EXPECT_TRUE(TestCompile(SourceGen::Language::Cpp, source));
|
|
}
|
|
|
|
static auto CountLines(llvm::StringRef source) -> ssize_t {
|
|
return llvm::count(source, '\n');
|
|
}
|
|
|
|
// Matches a `SourceGen::Language` for which
|
|
// `GenApiFileDenseDecls(target_lines, params)` generates files with the same
|
|
// byte and line counts using each of `num_seeds` independently seeded
|
|
// generators. The files must not all have the same content, which would make
|
|
// that match vacuous.
|
|
// NOLINTNEXTLINE(modernize-use-trailing-return-type): Macro based function.
|
|
MATCHER_P3(GeneratesSeedIndependentSize, target_lines, params, num_seeds,
|
|
"generates files of the same size with every seed") {
|
|
SourceGen::Language language = arg;
|
|
std::string first =
|
|
SourceGen(language).GenApiFileDenseDecls(target_lines, params);
|
|
bool any_different = false;
|
|
for (int i : llvm::seq(1, num_seeds)) {
|
|
std::string source =
|
|
SourceGen(language).GenApiFileDenseDecls(target_lines, params);
|
|
if (source.size() != first.size() ||
|
|
CountLines(source) != CountLines(first)) {
|
|
*result_listener << "where seed iteration " << i << " generated "
|
|
<< source.size() << " bytes and " << CountLines(source)
|
|
<< " lines, but seed iteration 0 generated "
|
|
<< first.size() << " bytes and " << CountLines(first)
|
|
<< " lines";
|
|
return false;
|
|
}
|
|
any_different = any_different || source != first;
|
|
}
|
|
if (!any_different) {
|
|
*result_listener << "where all files have the same content";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Each test generates files with one set of parameters, and checks that they
|
|
// compile cleanly and have the same size for any seed, which benchmarks need to
|
|
// be comparable. Each test runs for both languages.
|
|
using GenApiFileDenseDeclsParamsTest = TestWithParam<SourceGen::Language>;
|
|
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
Languages, GenApiFileDenseDeclsParamsTest,
|
|
Values(SourceGen::Language::Carbon, SourceGen::Language::Cpp),
|
|
[](const TestParamInfo<SourceGen::Language>& info) -> std::string {
|
|
return info.param == SourceGen::Language::Carbon ? "Carbon" : "Cpp";
|
|
});
|
|
|
|
// Barely enough lines for one class.
|
|
TEST_P(GenApiFileDenseDeclsParamsTest, DefaultParamsAt200Lines) {
|
|
SourceGen::DenseDeclParams params;
|
|
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(200, params, 16));
|
|
EXPECT_TRUE(TestCompile(
|
|
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(200, params)));
|
|
}
|
|
|
|
TEST_P(GenApiFileDenseDeclsParamsTest, DefaultParamsAt1000Lines) {
|
|
SourceGen::DenseDeclParams params;
|
|
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(1000, params, 16));
|
|
EXPECT_TRUE(TestCompile(
|
|
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(1000, params)));
|
|
}
|
|
|
|
TEST_P(GenApiFileDenseDeclsParamsTest, DefaultParamsAt5000Lines) {
|
|
SourceGen::DenseDeclParams params;
|
|
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(5000, params, 16));
|
|
EXPECT_TRUE(TestCompile(
|
|
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(5000, params)));
|
|
}
|
|
|
|
TEST_P(GenApiFileDenseDeclsParamsTest, DefaultParamsAt20000Lines) {
|
|
SourceGen::DenseDeclParams params;
|
|
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(20000, params, 16));
|
|
EXPECT_TRUE(TestCompile(
|
|
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(20000, params)));
|
|
}
|
|
|
|
// Function declarations only: no methods and no fields.
|
|
TEST_P(GenApiFileDenseDeclsParamsTest, OnlyFunctions) {
|
|
SourceGen::DenseDeclParams params = {
|
|
.class_params = {.public_function_decls = 20,
|
|
.public_method_decls = 0,
|
|
.private_function_decls = 0,
|
|
.private_method_decls = 0,
|
|
.private_field_decls = 0}};
|
|
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(5000, params, 12));
|
|
EXPECT_TRUE(TestCompile(
|
|
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(5000, params)));
|
|
}
|
|
|
|
// Large parameter counts, which wrap onto several lines.
|
|
TEST_P(GenApiFileDenseDeclsParamsTest, WrappedParamLists) {
|
|
SourceGen::DenseDeclParams params = {
|
|
.class_params = {.public_function_decls = 2,
|
|
.public_function_decl_params = {.max_params = 16},
|
|
.public_method_decls = 4,
|
|
.public_method_decl_params = {.max_params = 16},
|
|
.private_function_decls = 0,
|
|
.private_method_decls = 0,
|
|
.private_field_decls = 0}};
|
|
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(5000, params, 12));
|
|
EXPECT_TRUE(TestCompile(
|
|
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(5000, params)));
|
|
}
|
|
|
|
// The default shape scaled up 2x.
|
|
TEST_P(GenApiFileDenseDeclsParamsTest, DoubledDefaultShape) {
|
|
SourceGen::DenseDeclParams params = {
|
|
.class_params = {.public_function_decls = 8,
|
|
.public_method_decls = 20,
|
|
.private_function_decls = 4,
|
|
.private_method_decls = 16,
|
|
.private_field_decls = 12}};
|
|
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(5000, params, 12));
|
|
EXPECT_TRUE(TestCompile(
|
|
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(5000, params)));
|
|
}
|
|
|
|
// A class's fields can't reference it or any later class, so field-heavy
|
|
// classes leave few type uses for references to a class. Whether the valid type
|
|
// names run out depends on the shuffle, so these tests use many seeds.
|
|
TEST_P(GenApiFileDenseDeclsParamsTest, ManyFieldsAndTwoFunctions) {
|
|
SourceGen::DenseDeclParams params = {
|
|
.class_params = {.public_function_decls = 1,
|
|
.public_method_decls = 1,
|
|
.private_function_decls = 0,
|
|
.private_method_decls = 0,
|
|
.private_field_decls = 30}};
|
|
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(3000, params, 32));
|
|
EXPECT_TRUE(TestCompile(
|
|
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(3000, params)));
|
|
}
|
|
|
|
TEST_P(GenApiFileDenseDeclsParamsTest, ManyFieldsAndOneMethod) {
|
|
SourceGen::DenseDeclParams params = {
|
|
.class_params = {.public_function_decls = 0,
|
|
.public_method_decls = 1,
|
|
.private_function_decls = 0,
|
|
.private_method_decls = 0,
|
|
.private_field_decls = 50}};
|
|
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(3000, params, 32));
|
|
EXPECT_TRUE(TestCompile(
|
|
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(3000, params)));
|
|
}
|
|
|
|
// Classes with only fields (no functions or methods). Because fields have their
|
|
// own type pool, fields can still reference earlier classes.
|
|
TEST_P(GenApiFileDenseDeclsParamsTest, OnlyFields) {
|
|
SourceGen::DenseDeclParams params = {
|
|
.class_params = {.public_function_decls = 0,
|
|
.public_method_decls = 0,
|
|
.private_function_decls = 0,
|
|
.private_method_decls = 0,
|
|
.private_field_decls = 16}};
|
|
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(3000, params, 32));
|
|
EXPECT_TRUE(TestCompile(
|
|
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(3000, params)));
|
|
}
|
|
|
|
// Definitions read class-typed parameters through `Checksum`, and return class
|
|
// values through `Make`.
|
|
static auto InlineDefsParams() -> SourceGen::DenseDeclParams {
|
|
return {.class_params = {.inline_function_defs = 3,
|
|
.inline_getters = 2,
|
|
.inline_predicates = 2,
|
|
.inline_forwarders = 2}};
|
|
}
|
|
|
|
TEST_P(GenApiFileDenseDeclsParamsTest, InlineDefs) {
|
|
SourceGen::DenseDeclParams params = InlineDefsParams();
|
|
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(2000, params, 16));
|
|
EXPECT_TRUE(TestCompile(
|
|
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(2000, params)));
|
|
}
|
|
|
|
TEST(SourceGenTest, GenApiFileDenseDeclsInlineDefs) {
|
|
SourceGen gen;
|
|
std::string source = gen.GenApiFileDenseDecls(2000, InlineDefsParams());
|
|
EXPECT_THAT(source, HasSubstr(".Checksum()"));
|
|
EXPECT_THAT(source, HasSubstr(".Make()"));
|
|
EXPECT_THAT(source, HasSubstr("Impl("));
|
|
}
|
|
|
|
// C++ initializes a class with a default constructor in place of `Make`, which
|
|
// leaves the fields private.
|
|
TEST(SourceGenTest, GenApiFileDenseDeclsInlineDefsCpp) {
|
|
SourceGen gen(SourceGen::Language::Cpp);
|
|
std::string source = gen.GenApiFileDenseDecls(2000, InlineDefsParams());
|
|
EXPECT_THAT(source, HasSubstr("()\n : tag(0)"));
|
|
EXPECT_THAT(source, HasSubstr("\n private:\n int tag;\n"));
|
|
}
|
|
|
|
// Many large inline definitions and few other declarations. Locals and
|
|
// parameters draw from the same identifiers of each length, and C++ rejects a
|
|
// local that has the same name as a parameter, so compile several seeds.
|
|
TEST_P(GenApiFileDenseDeclsParamsTest, LargeInlineDefs) {
|
|
SourceGen::DenseDeclParams params = {
|
|
.class_params = {
|
|
.public_function_decls = 1,
|
|
.public_method_decls = 1,
|
|
.private_function_decls = 0,
|
|
.private_method_decls = 0,
|
|
.private_field_decls = 4,
|
|
.inline_function_defs = 8,
|
|
.inline_function_def_sizes = {{.statements = 20, .weight = 1},
|
|
{.statements = 60, .weight = 1}}}};
|
|
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(3000, params, 24));
|
|
for (int _ : llvm::seq(4)) {
|
|
EXPECT_TRUE(TestCompile(
|
|
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(3000, params)));
|
|
}
|
|
}
|
|
|
|
// Many fields, which `Make` initializes, often with an earlier class's
|
|
// `Make`.
|
|
TEST_P(GenApiFileDenseDeclsParamsTest, ManyFieldsToMake) {
|
|
SourceGen::DenseDeclParams params = {
|
|
.class_params = {.public_function_decls = 1,
|
|
.public_method_decls = 1,
|
|
.private_function_decls = 0,
|
|
.private_method_decls = 0,
|
|
.private_field_decls = 24,
|
|
.inline_function_defs = 2}};
|
|
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(3000, params, 24));
|
|
EXPECT_TRUE(TestCompile(
|
|
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(3000, params)));
|
|
}
|
|
|
|
// Many getters and predicates, and no other fields.
|
|
TEST_P(GenApiFileDenseDeclsParamsTest, ManyGettersAndPredicates) {
|
|
SourceGen::DenseDeclParams params = {
|
|
.class_params = {.private_field_decls = 0,
|
|
.inline_getters = 8,
|
|
.inline_predicates = 8}};
|
|
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(3000, params, 24));
|
|
EXPECT_TRUE(TestCompile(
|
|
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(3000, params)));
|
|
}
|
|
|
|
// Many forwarders with many parameters, which can have class types.
|
|
TEST_P(GenApiFileDenseDeclsParamsTest, ManyForwarders) {
|
|
SourceGen::DenseDeclParams params = {
|
|
.class_params = {.inline_forwarders = 8,
|
|
.inline_forwarder_params = {.max_params = 8}}};
|
|
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(3000, params, 24));
|
|
EXPECT_TRUE(TestCompile(
|
|
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(3000, params)));
|
|
}
|
|
|
|
// Every declared function and method has an out-of-line definition, using the
|
|
// default size distribution (from 0-statement minimal definitions through
|
|
// deeply nested definitions).
|
|
TEST_P(GenApiFileDenseDeclsParamsTest, OutOfLineDefs) {
|
|
SourceGen::DenseDeclParams params = {
|
|
.class_params = {.inline_function_defs = 2},
|
|
.define_decls_out_of_line = true};
|
|
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(3000, params, 16));
|
|
EXPECT_TRUE(TestCompile(
|
|
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(3000, params)));
|
|
}
|
|
|
|
// Large out-of-line definitions, which nest deeply. A definition's block
|
|
// arrangement and which locals its statements read depend on the seed, so
|
|
// compile several seeds.
|
|
TEST_P(GenApiFileDenseDeclsParamsTest, LargeOutOfLineDefs) {
|
|
SourceGen::DenseDeclParams params = {
|
|
.class_params = {.public_function_decls = 1,
|
|
.public_method_decls = 2,
|
|
.private_function_decls = 0,
|
|
.private_method_decls = 1,
|
|
.private_field_decls = 2},
|
|
.define_decls_out_of_line = true,
|
|
.out_of_line_def_sizes = {{.statements = 40, .weight = 1},
|
|
{.statements = 150, .weight = 1}}};
|
|
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(3000, params, 24));
|
|
for (int _ : llvm::seq(4)) {
|
|
EXPECT_TRUE(TestCompile(
|
|
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(3000, params)));
|
|
}
|
|
}
|
|
|
|
// Plans declare class-typed locals (using both `Self` and other classes in both
|
|
// inline and out-of-line definitions) and destructure tuples.
|
|
TEST(SourceGenTest, GenApiFileDenseDeclsPlanDeclarations) {
|
|
SourceGen::DenseDeclParams params = {
|
|
.class_params = {.public_function_decls = 1,
|
|
.public_method_decls = 2,
|
|
.private_function_decls = 0,
|
|
.private_method_decls = 1,
|
|
.private_field_decls = 2,
|
|
.inline_function_defs = 2,
|
|
.inline_function_def_sizes = {{.statements = 40,
|
|
.weight = 1}}},
|
|
.define_decls_out_of_line = true,
|
|
.out_of_line_def_sizes = {{.statements = 60, .weight = 1}}};
|
|
|
|
SourceGen carbon_gen;
|
|
std::string source = carbon_gen.GenApiFileDenseDecls(3000, params);
|
|
auto [carbon_inline, carbon_out_of_line] =
|
|
llvm::StringRef(source).split("\nfn ");
|
|
for (llvm::StringRef section : {carbon_inline, carbon_out_of_line}) {
|
|
EXPECT_THAT(section, HasSubstr(": Self = Make();"));
|
|
EXPECT_THAT(section, ContainsRegex("var [A-Za-z0-9_]+: [A-Za-z0-9_]{5,} = "
|
|
"[A-Za-z0-9_]+\\.Make\\(\\);"));
|
|
}
|
|
EXPECT_THAT(source, ContainsRegex("(let|var) \\([A-Za-z0-9_]+: "));
|
|
|
|
SourceGen cpp_gen(SourceGen::Language::Cpp);
|
|
source = cpp_gen.GenApiFileDenseDecls(3000, params);
|
|
auto [cpp_inline, cpp_out_of_line] = llvm::StringRef(source).split("\nauto ");
|
|
for (llvm::StringRef section : {cpp_inline, cpp_out_of_line}) {
|
|
EXPECT_THAT(section, HasSubstr(" = Self();"));
|
|
EXPECT_THAT(section, ContainsRegex("[A-Za-z0-9_]{5,} [A-Za-z0-9_]+ = "
|
|
"[A-Za-z0-9_]+\\(\\);"));
|
|
}
|
|
EXPECT_THAT(source, HasSubstr("auto ["));
|
|
}
|
|
|
|
// Definitions call free functions, passing `&acc` and `&acc64` for pointer
|
|
// parameters and destructuring tuple-returning calls.
|
|
TEST_P(GenApiFileDenseDeclsParamsTest, FreeFunctionCalls) {
|
|
SourceGen::DenseDeclParams params = {
|
|
.class_params = {.inline_function_defs = 2},
|
|
.define_decls_out_of_line = true,
|
|
.free_function_decls_per_class = 2};
|
|
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(3000, params, 16));
|
|
std::string source = SourceGen(GetParam()).GenApiFileDenseDecls(3000, params);
|
|
EXPECT_TRUE(TestCompile(GetParam(), source));
|
|
EXPECT_THAT(source, HasSubstr("&acc"));
|
|
EXPECT_THAT(source, HasSubstr("&acc64"));
|
|
}
|
|
|
|
// When no free function returns a tuple in `CallDestructureForms`,
|
|
// `DestructureCall` steps fall back to literal destructuring while keeping file
|
|
// sizes seed-independent.
|
|
TEST_P(GenApiFileDenseDeclsParamsTest, FreeFunctionCallsWithoutTupleReturns) {
|
|
SourceGen::DenseDeclParams params = {
|
|
.class_params = {.public_function_decls = 1,
|
|
.public_method_decls = 1,
|
|
.private_function_decls = 0,
|
|
.private_method_decls = 0,
|
|
.private_field_decls = 2},
|
|
.define_decls_out_of_line = true,
|
|
.out_of_line_def_sizes = {{.statements = 60, .weight = 1}},
|
|
.free_function_decls_per_class = 2};
|
|
// Drop the tuple entries at the end of `fixed_type_weights` so no free
|
|
// function returns a destructurable tuple.
|
|
llvm::erase_if(params.type_use_params.fixed_type_weights,
|
|
[](const SourceGen::TypeUseParams::FixedTypeWeight& fw) {
|
|
return fw.carbon_spelling.starts_with("(");
|
|
});
|
|
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(3000, params, 16));
|
|
std::string source = SourceGen(GetParam()).GenApiFileDenseDecls(3000, params);
|
|
EXPECT_TRUE(TestCompile(GetParam(), source));
|
|
EXPECT_THAT(source, HasSubstr("&acc64"));
|
|
if (GetParam() == SourceGen::Language::Carbon) {
|
|
EXPECT_THAT(source,
|
|
ContainsRegex("(let|var) \\([A-Za-z0-9_]+: [^)]+\\) = \\("));
|
|
EXPECT_THAT(source, Not(ContainsRegex("let \\([^)]+\\) = [A-Za-z]")));
|
|
} else {
|
|
EXPECT_THAT(source, HasSubstr("] = std::pair{"));
|
|
EXPECT_THAT(
|
|
source,
|
|
Not(ContainsRegex("auto \\[[A-Za-z0-9_, ]+\\] = [A-Za-z0-9_]+\\(")));
|
|
}
|
|
}
|
|
|
|
TEST(SourceGenTest, GenApiFileDenseDeclsFreeFunctionCalls) {
|
|
SourceGen::DenseDeclParams params = {
|
|
.class_params = {.public_function_decls = 1,
|
|
.public_method_decls = 1,
|
|
.private_function_decls = 0,
|
|
.private_method_decls = 0,
|
|
.private_field_decls = 2},
|
|
.define_decls_out_of_line = true,
|
|
.out_of_line_def_sizes = {{.statements = 60, .weight = 1}},
|
|
.free_function_decls_per_class = 4};
|
|
|
|
auto carbon_destructure_call_regex = [](llvm::StringRef first_type,
|
|
llvm::StringRef second_type) {
|
|
return llvm::formatv(
|
|
"let \\([A-Za-z0-9_]+: {0}, [A-Za-z0-9_]+: {1}\\) = "
|
|
"[A-Za-z0-9_]{{5,}\\(",
|
|
first_type, second_type)
|
|
.str();
|
|
};
|
|
|
|
SourceGen carbon_gen;
|
|
std::string source = carbon_gen.GenApiFileDenseDecls(3000, params);
|
|
auto [carbon_free_decls, carbon_classes_and_defs] =
|
|
llvm::StringRef(source).split("\nclass ");
|
|
EXPECT_THAT(carbon_free_decls, ContainsRegex("\nfn [A-Za-z0-9_]{5,}\\("));
|
|
EXPECT_THAT(carbon_classes_and_defs,
|
|
ContainsRegex(carbon_destructure_call_regex("bool", "i64")));
|
|
EXPECT_THAT(carbon_classes_and_defs,
|
|
ContainsRegex(carbon_destructure_call_regex("i32", "i64\\*")));
|
|
|
|
SourceGen cpp_gen(SourceGen::Language::Cpp);
|
|
source = cpp_gen.GenApiFileDenseDecls(3000, params);
|
|
auto [cpp_free_decls, cpp_classes_and_defs] =
|
|
llvm::StringRef(source).split("\nclass ");
|
|
EXPECT_THAT(cpp_free_decls, ContainsRegex("\nauto [A-Za-z0-9_]{5,}\\("));
|
|
EXPECT_THAT(cpp_classes_and_defs,
|
|
ContainsRegex("const auto \\[[A-Za-z0-9_]+, [A-Za-z0-9_]+\\] = "
|
|
"[A-Za-z0-9_]{5,}\\("));
|
|
}
|
|
|
|
// The line estimates have to track the emitted lines closely, or files miss
|
|
// their target size. The target is large so that rounding to a whole number of
|
|
// classes is small next to the tolerance. C++ gets a larger tolerance for its
|
|
// access specifier lines, which the estimates don't count.
|
|
TEST(SourceGenTest, GenApiFileDenseDeclsLineTargetAccuracy) {
|
|
struct NamedParams {
|
|
llvm::StringLiteral name;
|
|
SourceGen::DenseDeclParams params;
|
|
};
|
|
llvm::SmallVector<NamedParams, 0> param_set;
|
|
param_set.push_back({.name = "dense_decls",
|
|
.params = {.class_params = {.inline_function_defs = 1,
|
|
.inline_getters = 1,
|
|
.inline_predicates = 1,
|
|
.inline_forwarders = 1},
|
|
.free_function_decls_per_class = 2}});
|
|
// The implementation file shape.
|
|
param_set.push_back(
|
|
{.name = "impl_file",
|
|
.params = {
|
|
.class_params = {.public_function_decls = 2,
|
|
.public_function_decl_params = {.max_params = 2},
|
|
.public_method_decls = 6,
|
|
.public_method_decl_params = {.max_params = 2},
|
|
.private_function_decls = 2,
|
|
.private_function_decl_params = {.max_params = 2},
|
|
.private_method_decls = 2,
|
|
.private_method_decl_params = {.max_params = 2},
|
|
.private_field_decls = 4},
|
|
.define_decls_out_of_line = true,
|
|
.free_function_decls_per_class = 2}});
|
|
// The realistic header shape.
|
|
param_set.push_back(
|
|
{.name = "realistic_header",
|
|
.params = {
|
|
.class_params = {.public_function_decls = 0,
|
|
.public_method_decls = 2,
|
|
.private_function_decls = 0,
|
|
.private_method_decls = 0,
|
|
.private_field_decls = 3,
|
|
.inline_function_defs = 2,
|
|
.inline_function_decl_params = {.max_params = 2},
|
|
.inline_getters = 2,
|
|
.inline_predicates = 1,
|
|
.inline_forwarders = 2},
|
|
.free_function_decls_per_class = 1}});
|
|
|
|
constexpr int TargetLines = 20000;
|
|
for (const auto& [name, params] : param_set) {
|
|
for (SourceGen::Language language :
|
|
{SourceGen::Language::Carbon, SourceGen::Language::Cpp}) {
|
|
SourceGen gen(language);
|
|
std::string source = gen.GenApiFileDenseDecls(TargetLines, params);
|
|
ssize_t lines = CountLines(source);
|
|
if (language == SourceGen::Language::Carbon) {
|
|
// Within 2% of the requested line count.
|
|
EXPECT_THAT(lines, AllOf(Ge(19600), Le(20400))) << name;
|
|
} else {
|
|
// Within 10% of the requested line count.
|
|
EXPECT_THAT(lines, AllOf(Ge(18000), Le(22000))) << name;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace Carbon::Testing
|