Centralize benchmarking infrastructure and the toolchain-wide benchmarks (#7212)

The benchmarks themselves aren't really specific to `driver`.

Keeping the source generation near to the primary use case of
benchmarking also seems like a more discoverable location.

I feel a little bad doing this reorganization right after I gave a talk
with links to a bunch of this code, but seems good to reorganize a bit
before doing some work to extend things now that we have full standard
library support for C++ benchmarking and other improvements.

Assisted-by: Antigravity with Gemini
This commit is contained in:
Chandler Carruth
2026-05-15 20:17:23 +00:00
committed by GitHub
parent 42a4dba9fd
commit e041afd98d
10 changed files with 100 additions and 89 deletions
+1 -48
View File
@@ -5,7 +5,7 @@
# Trivial, single-file testing libraries. More complex libraries should get
# their own directory.
load("//bazel/cc_rules:defs.bzl", "cc_binary", "cc_library", "cc_test")
load("//bazel/cc_rules:defs.bzl", "cc_library", "cc_test")
package(default_visibility = ["//visibility:public"])
@@ -56,53 +56,6 @@ cc_library(
],
)
cc_library(
name = "source_gen_lib",
testonly = 1,
srcs = ["source_gen.cpp"],
hdrs = ["source_gen.h"],
deps = [
"//common:check",
"//common:map",
"//common:raw_string_ostream",
"//common:set",
"//toolchain/lex:token_kind",
"@abseil-cpp//absl/random",
"@llvm-project//llvm:Support",
],
)
cc_test(
name = "source_gen_test",
size = "small",
srcs = ["source_gen_test.cpp"],
deps = [
":global_exe_path",
":gtest_main",
":source_gen_lib",
"//common:all_llvm_targets",
"//common:set",
"//toolchain/base:install_paths_test_helpers",
"//toolchain/driver",
"@googletest//:gtest",
"@llvm-project//llvm:Support",
],
)
cc_binary(
name = "source_gen",
testonly = 1,
srcs = ["source_gen_main.cpp"],
deps = [
":source_gen_lib",
"//common:bazel_working_dir",
"//common:command_line",
"//common:init_llvm",
"//common:ostream",
"@llvm-project//llvm:Support",
],
)
cc_library(
name = "file_helpers",
testonly = 1,
-920
View File
@@ -1,920 +0,0 @@
// 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 "testing/base/source_gen.h"
#include <algorithm>
#include <array>
#include <numeric>
#include <string>
#include <utility>
#include "common/raw_string_ostream.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/Sequence.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/FormatVariadic.h"
#include "toolchain/lex/token_kind.h"
namespace Carbon::Testing {
auto SourceGen::Global() -> SourceGen& {
static SourceGen global_gen;
return global_gen;
}
SourceGen::SourceGen(Language language) : language_(language) {}
// Heuristic numbers used in synthesizing various identifier sequences.
static constexpr int MinClassNameLength = 5;
static constexpr int MinMemberNameLength = 4;
// The shuffled state used to generate some number of classes.
//
// This state encodes everything used to generate class definitions. The state
// will be consumed until empty.
//
// Detailed comments for out-of-line methods are on their definitions.
class SourceGen::ClassGenState {
public:
ClassGenState(SourceGen& gen, int num_classes,
const ClassParams& class_params,
const TypeUseParams& type_use_params);
auto public_function_param_counts() -> llvm::SmallVectorImpl<int>& {
return public_function_param_counts_;
}
auto public_method_param_counts() -> llvm::SmallVectorImpl<int>& {
return public_method_param_counts_;
}
auto private_function_param_counts() -> llvm::SmallVectorImpl<int>& {
return private_function_param_counts_;
}
auto private_method_param_counts() -> llvm::SmallVectorImpl<int>& {
return private_method_param_counts_;
}
auto class_names() -> llvm::SmallVectorImpl<llvm::StringRef>& {
return class_names_;
}
auto member_names() -> llvm::SmallVectorImpl<llvm::StringRef>& {
return member_names_;
}
auto param_names() -> llvm::SmallVectorImpl<llvm::StringRef>& {
return param_names_;
}
auto type_names() -> llvm::SmallVectorImpl<llvm::StringRef>& {
return type_names_;
}
auto AddValidTypeName(llvm::StringRef type_name) -> void {
valid_type_names_.Insert(type_name);
}
auto GetValidTypeName() -> llvm::StringRef;
private:
auto BuildClassAndTypeNames(SourceGen& gen, int num_classes, int num_types,
const TypeUseParams& type_use_params) -> void;
llvm::SmallVector<int> public_function_param_counts_;
llvm::SmallVector<int> public_method_param_counts_;
llvm::SmallVector<int> private_function_param_counts_;
llvm::SmallVector<int> private_method_param_counts_;
llvm::SmallVector<llvm::StringRef> class_names_;
llvm::SmallVector<llvm::StringRef> member_names_;
llvm::SmallVector<llvm::StringRef> param_names_;
llvm::SmallVector<llvm::StringRef> type_names_;
Set<llvm::StringRef> valid_type_names_;
int last_type_name_index_ = 0;
};
// A helper to sum elements of a range.
template <typename T>
static auto Sum(const T& range) -> int {
return std::accumulate(range.begin(), range.end(), 0);
}
// Given a number of class definitions and the params with which to generate
// them, builds the state that will be used while generating that many classes.
//
// We build the state first and across all the class definitions that will be
// generated so that we can distribute random components across all the
// definitions.
SourceGen::ClassGenState::ClassGenState(SourceGen& gen, int num_classes,
const ClassParams& class_params,
const TypeUseParams& type_use_params) {
public_function_param_counts_ =
gen.GetShuffledInts(num_classes * class_params.public_function_decls, 0,
class_params.public_function_decl_params.max_params);
public_method_param_counts_ =
gen.GetShuffledInts(num_classes * class_params.public_method_decls, 0,
class_params.public_method_decl_params.max_params);
private_function_param_counts_ =
gen.GetShuffledInts(num_classes * class_params.private_function_decls, 0,
class_params.private_function_decl_params.max_params);
private_method_param_counts_ =
gen.GetShuffledInts(num_classes * class_params.private_method_decls, 0,
class_params.private_method_decl_params.max_params);
int num_members =
num_classes *
(class_params.public_function_decls + class_params.public_method_decls +
class_params.private_function_decls + class_params.private_method_decls +
class_params.private_field_decls);
member_names_ = gen.GetShuffledIdentifiers(
num_members, /*min_length=*/MinMemberNameLength);
int num_params =
Sum(public_function_param_counts_) + Sum(public_method_param_counts_) +
Sum(private_function_param_counts_) + Sum(private_method_param_counts_);
param_names_ = gen.GetShuffledIdentifiers(num_params);
BuildClassAndTypeNames(gen, num_classes, num_members + num_params,
type_use_params);
}
auto SourceGen::ClassGenState::GetValidTypeName() -> llvm::StringRef {
// Check that we don't completely wrap the type names by tracking where we
// started.
int initial_last_type_name_index = last_type_name_index_;
// Now search the type names, starting from the last used index, to find the
// first valid name.
for (;;) {
if (last_type_name_index_ == 0) {
last_type_name_index_ = type_names_.size();
}
--last_type_name_index_;
llvm::StringRef& type_name = type_names_[last_type_name_index_];
if (valid_type_names_.Contains(type_name)) {
// Found a valid type name, swap it with the back and pop that off.
std::swap(type_names_.back(), type_name);
return type_names_.pop_back_val();
}
CARBON_CHECK(last_type_name_index_ != initial_last_type_name_index,
"Failed to find a valid type name with {0} candidates, an "
"initial index of {1}, and with {2} classes left to emit!",
type_names_.size(), initial_last_type_name_index,
class_names_.size());
}
}
// Build both the class names this file will declare and a list of type
// references to use throughout those classes.
//
// We combine a list of fixed types in the `type_use_params` with the list of
// class names that will be defined to form the spelling of all the referenced
// types. The `type_use_params` provides weights for each fixed type as well as
// an overall weight for referencing class names that are being declared. We
// build a set of type references so that its histogram will roughly match these
// weights.
//
// For each of the fixed types, `type_use_params` provides a spelling for both
// Carbon and C++.
//
// We distribute our references to declared class names evenly to the extent
// possible.
//
// Before all the references are formed, the class names are kept their original
// unshuffled order. This ensures that any uneven sampling of names is done
// deterministically. At the end, we randomly shuffle the sequences of both the
// declared class names and type references to provide an unpredictable order in
// the generated output.
auto SourceGen::ClassGenState::BuildClassAndTypeNames(
SourceGen& gen, int num_classes, int num_types,
const TypeUseParams& type_use_params) -> void {
// Initially get the sequence of class names without shuffling so we can
// compute our type name pool from them prior to any shuffling.
class_names_ =
gen.GetUniqueIdentifiers(num_classes, /*min_length=*/MinClassNameLength);
type_names_.reserve(num_types);
// Compute the sum of weights and pre-process the fixed types.
int type_weight_sum = type_use_params.declared_types_weight;
for (const auto& fixed_type_weight : type_use_params.fixed_type_weights) {
type_weight_sum += fixed_type_weight.weight;
// Add all the fixed type spellings as immediately valid.
valid_type_names_.Insert(gen.IsCpp() ? fixed_type_weight.cpp_spelling
: fixed_type_weight.carbon_spelling);
}
// Compute the number of declared types used. We expect to have a decent
// number of repeated names, so we repeatedly append the entire sequence of
// class names until there is some remainder of names needed.
int num_declared_types =
num_types * type_use_params.declared_types_weight / type_weight_sum;
for ([[maybe_unused]] auto _ : llvm::seq(num_declared_types / num_classes)) {
llvm::append_range(type_names_, class_names_);
}
// Now append the remainder number of class names. This is where the class
// names being un-shuffled is essential. We're going to have one extra
// reference to some fraction of the class names and we want that to be a
// stable subset.
type_names_.append(class_names_.begin(),
class_names_.begin() + (num_declared_types % num_classes));
CARBON_CHECK(static_cast<int>(type_names_.size()) == num_declared_types);
// Use each fixed type weight to append the expected number of copies of that
// type. This isn't exact however, and is designed to stop short.
for (const auto& fixed_type_weight : type_use_params.fixed_type_weights) {
int num_fixed_type = num_types * fixed_type_weight.weight / type_weight_sum;
type_names_.append(num_fixed_type, gen.IsCpp()
? fixed_type_weight.cpp_spelling
: fixed_type_weight.carbon_spelling);
}
// If we need a tail of types to hit the exact number, simply round-robin
// through the fixed types without any weighting. With reasonably large
// numbers of types this won't distort the distribution in an interesting way
// and is simpler than trying to scale the distribution down.
while (static_cast<int>(type_names_.size()) < num_types) {
for (const auto& fixed_type_weight :
llvm::ArrayRef(type_use_params.fixed_type_weights)
.take_front(num_types - type_names_.size())) {
type_names_.push_back(gen.IsCpp() ? fixed_type_weight.cpp_spelling
: fixed_type_weight.carbon_spelling);
}
}
CARBON_CHECK(static_cast<int>(type_names_.size()) == num_types);
last_type_name_index_ = num_types;
// Now shuffle both the class names and the type names.
std::shuffle(class_names_.begin(), class_names_.end(), gen.rng_);
std::shuffle(type_names_.begin(), type_names_.end(), gen.rng_);
}
// Some heuristic numbers used when formatting generated code. These heuristics
// are loosely based on what we expect to make Carbon code readable, and might
// not fit as well in C++, but we use the same heuristics across languages for
// simplicity and to make the output in different languages more directly
// comparable.
static constexpr int NumSingleLineFunctionParams = 3;
static constexpr int NumSingleLineMethodParams = 2;
static constexpr int MaxParamsPerLine = 4;
static auto EstimateAvgFunctionDeclLines(SourceGen::FunctionDeclParams params)
-> double {
// Currently model a uniform distribution [0, max] parameters. Assume a line
// break before the first parameter for >3 and after every 4th.
int param_lines = 0;
for (int num_params : llvm::seq_inclusive(0, params.max_params)) {
if (num_params > NumSingleLineFunctionParams) {
param_lines += (num_params + MaxParamsPerLine - 1) / MaxParamsPerLine;
}
}
return 1.0 + static_cast<double>(param_lines) / (params.max_params + 1);
}
static auto EstimateAvgMethodDeclLines(SourceGen::MethodDeclParams params)
-> double {
// Currently model a uniform distribution [0, max] parameters. Assume a line
// break before the first parameter for >2 and after every 4th.
int param_lines = 0;
for (int num_params : llvm::seq_inclusive(0, params.max_params)) {
if (num_params > NumSingleLineMethodParams) {
param_lines += (num_params + MaxParamsPerLine - 1) / MaxParamsPerLine;
}
}
return 1.0 + static_cast<double>(param_lines) / (params.max_params + 1);
}
// Note that this should match the heuristics used when formatting.
// TODO: See top-level TODO about line estimates and formatting.
static auto EstimateAvgClassDefLines(SourceGen::ClassParams params) -> double {
// Comment line, and class open line.
double avg = 2.0;
// One comment line and blank line per function, plus the function lines.
avg +=
(2.0 + EstimateAvgFunctionDeclLines(params.public_function_decl_params)) *
params.public_function_decls;
avg += (2.0 + EstimateAvgMethodDeclLines(params.public_method_decl_params)) *
params.public_method_decls;
avg += (2.0 +
EstimateAvgFunctionDeclLines(params.private_function_decl_params)) *
params.private_function_decls;
avg += (2.0 + EstimateAvgMethodDeclLines(params.private_method_decl_params)) *
params.private_method_decls;
// A blank line and all the fields (if any).
if (params.private_field_decls > 0) {
avg += 1.0 + params.private_field_decls;
}
// No need to account for the class close line, we have an extra blank line
// count for the last of the above.
return avg;
}
auto SourceGen::GenApiFileDenseDecls(int target_lines,
const DenseDeclParams& params)
-> std::string {
RawStringOstream source;
// Figure out how many classes fit in our target lines, each separated by a
// blank line. We need to account the comment lines below to start the file.
// Note that we want a blank line after our file comment block, so every class
// needs a blank line.
constexpr int NumFileCommentLines = 4;
double avg_class_lines = EstimateAvgClassDefLines(params.class_params);
CARBON_CHECK(target_lines > NumFileCommentLines + avg_class_lines,
"Not enough target lines to generate a single class!");
int num_classes = static_cast<double>(target_lines - NumFileCommentLines) /
(avg_class_lines + 1);
int expected_lines =
NumFileCommentLines + num_classes * (avg_class_lines + 1);
source << "// Generated " << (!IsCpp() ? "Carbon" : "C++")
<< " source file.\n";
source << llvm::formatv(
"// {0} target lines: {1} classes, {2} expected lines",
target_lines, num_classes, expected_lines)
<< "\n";
source << "//\n// Generating as an API file with dense declarations.\n";
// Carbon uses an implicitly imported prelude to get builtin types, but C++
// requires header files so include those.
if (IsCpp()) {
source << "\n";
// Header for specific integer types like `std::int64_t`.
source << "#include <cstdint>\n";
// Header for `std::pair`.
source << "#include <utility>\n";
}
auto class_gen_state = ClassGenState(*this, num_classes, params.class_params,
params.type_use_params);
for ([[maybe_unused]] auto _ : llvm::seq(num_classes)) {
source << "\n";
GenerateClassDef(params.class_params, class_gen_state, source);
}
// Make sure we consumed all the state.
CARBON_CHECK(class_gen_state.public_function_param_counts().empty());
CARBON_CHECK(class_gen_state.public_method_param_counts().empty());
CARBON_CHECK(class_gen_state.private_function_param_counts().empty());
CARBON_CHECK(class_gen_state.private_method_param_counts().empty());
CARBON_CHECK(class_gen_state.class_names().empty());
CARBON_CHECK(class_gen_state.type_names().empty());
return source.TakeStr();
}
auto SourceGen::GetShuffledIdentifiers(int number, int min_length,
int max_length, bool uniform)
-> llvm::SmallVector<llvm::StringRef> {
llvm::SmallVector<llvm::StringRef> idents =
GetIdentifiers(number, min_length, max_length, uniform);
std::shuffle(idents.begin(), idents.end(), rng_);
return idents;
}
auto SourceGen::GetShuffledUniqueIdentifiers(int number, int min_length,
int max_length, bool uniform)
-> llvm::SmallVector<llvm::StringRef> {
CARBON_CHECK(min_length >= 4,
"Cannot trivially guarantee enough distinct, unique identifiers "
"for lengths <= 3");
llvm::SmallVector<llvm::StringRef> idents =
GetUniqueIdentifiers(number, min_length, max_length, uniform);
std::shuffle(idents.begin(), idents.end(), rng_);
return idents;
}
auto SourceGen::GetIdentifiers(int number, int min_length, int max_length,
bool uniform)
-> llvm::SmallVector<llvm::StringRef> {
llvm::SmallVector<llvm::StringRef> idents = GetIdentifiersImpl(
number, min_length, max_length, uniform,
[this](int length, int length_count,
llvm::SmallVectorImpl<llvm::StringRef>& dest) {
llvm::append_range(dest,
GetSingleLengthIdentifiers(length, length_count));
});
return idents;
}
auto SourceGen::GetUniqueIdentifiers(int number, int min_length, int max_length,
bool uniform)
-> llvm::SmallVector<llvm::StringRef> {
CARBON_CHECK(min_length >= 4,
"Cannot trivially guarantee enough distinct, unique identifiers "
"for lengths <= 3");
llvm::SmallVector<llvm::StringRef> idents =
GetIdentifiersImpl(number, min_length, max_length, uniform,
[this](int length, int length_count,
llvm::SmallVectorImpl<llvm::StringRef>& dest) {
AppendUniqueIdentifiers(length, length_count, dest);
});
return idents;
}
auto SourceGen::GetSingleLengthIdentifiers(int length, int number)
-> llvm::ArrayRef<llvm::StringRef> {
llvm::SmallVector<llvm::StringRef>& idents =
identifiers_by_length_.Insert(length, {}).value();
if (static_cast<int>(idents.size()) < number) {
idents.reserve(number);
for ([[maybe_unused]] auto _ : llvm::seq<int>(idents.size(), number)) {
auto ident_storage =
llvm::MutableArrayRef(reinterpret_cast<char*>(storage_.Allocate(
/*Size=*/length, /*Alignment=*/1)),
length);
GenerateRandomIdentifier(ident_storage);
llvm::StringRef new_id(ident_storage.data(), length);
idents.push_back(new_id);
}
CARBON_CHECK(static_cast<int>(idents.size()) == number);
}
return llvm::ArrayRef(idents).slice(0, number);
}
static auto IdentifierStartChars() -> llvm::ArrayRef<char> {
static llvm::SmallVector<char> chars = [] {
llvm::SmallVector<char> chars;
for (char c : llvm::seq_inclusive('A', 'Z')) {
chars.push_back(c);
}
for (char c : llvm::seq_inclusive('a', 'z')) {
chars.push_back(c);
}
return chars;
}();
return chars;
}
static auto IdentifierChars() -> llvm::ArrayRef<char> {
static llvm::SmallVector<char> chars = [] {
llvm::ArrayRef<char> start_chars = IdentifierStartChars();
llvm::SmallVector<char> chars(start_chars.begin(), start_chars.end());
chars.push_back('_');
for (char c : llvm::seq_inclusive('0', '9')) {
chars.push_back(c);
}
return chars;
}();
return chars;
}
static constexpr llvm::StringRef NonCarbonCppKeywords[] = {
"asm", "do", "double", "float", "int", "long", "new", "signed",
"std", "try", "unix", "unsigned", "xor", "NAN", "M_E", "M_PI",
};
// Returns a random identifier string of the specified length.
//
// Ensures this is a valid identifier, avoiding any overlapping syntaxes or
// keywords both in Carbon and C++.
//
// This routine is somewhat expensive and so is useful to cache and reduce the
// frequency of calls. However, each time it is called it computes a completely
// new random identifier and so can be useful to eventually find a distinct
// identifier when needed.
auto SourceGen::GenerateRandomIdentifier(
llvm::MutableArrayRef<char> dest_storage) -> void {
llvm::ArrayRef<char> start_chars = IdentifierStartChars();
llvm::ArrayRef<char> chars = IdentifierChars();
llvm::StringRef ident(dest_storage.data(), dest_storage.size());
do {
dest_storage[0] =
start_chars[absl::Uniform<int>(rng_, 0, start_chars.size())];
for (int i : llvm::seq<int>(1, dest_storage.size())) {
dest_storage[i] = chars[absl::Uniform<int>(rng_, 0, chars.size())];
}
} while (
// TODO: Clean up and simplify this code. With some small refactorings and
// post-processing we should be able to make this both easier to read and
// less inefficient.
llvm::any_of(
Lex::TokenKind::KeywordTokens,
[ident](auto token) { return ident == token.fixed_spelling(); }) ||
llvm::is_contained(NonCarbonCppKeywords, ident) ||
ident.ends_with("_t") || ident.ends_with("_MIN") ||
ident.ends_with("_MAX") || ident.ends_with("_C") ||
(llvm::is_contained({'i', 'u', 'f'}, ident[0]) &&
llvm::all_of(ident.substr(1),
[](const char c) { return llvm::isDigit(c); })));
}
// Appends a number of unique, random identifiers with a particular length to
// the provided destination vector.
//
// Uses, and when necessary grows, a cached sequence of random identifiers with
// the specified length. Because these are cached, this is efficient to call
// repeatedly, but will not produce a different sequence of identifiers.
auto SourceGen::AppendUniqueIdentifiers(
int length, int number, llvm::SmallVectorImpl<llvm::StringRef>& dest)
-> void {
auto& [count, unique_idents] =
unique_identifiers_by_length_.Insert(length, {}).value();
// See if we need to grow our pool of unique identifiers with the requested
// length.
if (count < number) {
// We'll need to insert exactly the requested new unique identifiers. All
// our other inserts will find an existing entry.
unique_idents.GrowForInsertCount(count - number);
// Generate the needed number of identifiers.
for ([[maybe_unused]] auto _ : llvm::seq<int>(count, number)) {
// Allocate stable storage for the identifier so we can form stable
// `StringRef`s to it.
auto ident_storage =
llvm::MutableArrayRef(reinterpret_cast<char*>(storage_.Allocate(
/*Size=*/length, /*Alignment=*/1)),
length);
// Repeatedly generate novel identifiers of this length until we find a
// new unique one.
for (;;) {
GenerateRandomIdentifier(ident_storage);
auto result =
unique_idents.Insert(llvm::StringRef(ident_storage.data(), length));
if (result.is_inserted()) {
break;
}
}
}
count = number;
}
// Append all the identifiers directly out of the set. We make no guarantees
// about the relative order so we just use the non-deterministic order of the
// set and avoid additional storage.
//
// TODO: It's awkward the `ForEach` here can't early-exit. This just walks the
// whole set which is harmless if inefficient. We should add early exiting
// the loop support to `Set` and update this code.
unique_idents.ForEach([&](llvm::StringRef ident) {
if (number > 0) {
dest.push_back(ident);
--number;
}
});
CARBON_CHECK(number == 0);
}
// An array of the counts that should be used for each identifier length to
// produce our desired distribution.
//
// Note that the zero-based index corresponds to a 1-based length, so the count
// for identifiers of length 1 is at index 0.
static constexpr std::array<int, 64> IdentifierLengthCounts = [] {
std::array<int, 64> ident_length_counts;
// For non-uniform distribution, we simulate a distribution roughly based on
// the observed histogram of identifier lengths, but smoothed a bit and
// reduced to small counts so that we cycle through all the lengths
// reasonably quickly. We want sampling of even 10% of NumTokens from this
// in a round-robin form to not be skewed overly much. This still inherently
// compresses the long tail as we'd rather have coverage even though it
// distorts the distribution a bit.
//
// The distribution here comes from a script that analyzes source code run
// over a few directories of LLVM. The script renders a visual ascii-art
// histogram along with the data for each bucket, and that output is
// included in comments above each bucket size below to help visualize the
// rough shape we're aiming for.
//
// 1 characters [3976] ███████████████████████████████▊
ident_length_counts[0] = 40;
// 2 characters [3724] █████████████████████████████▊
ident_length_counts[1] = 40;
// 3 characters [4173] █████████████████████████████████▍
ident_length_counts[2] = 40;
// 4 characters [5000] ████████████████████████████████████████
ident_length_counts[3] = 50;
// 5 characters [1568] ████████████▌
ident_length_counts[4] = 20;
// 6 characters [2226] █████████████████▊
ident_length_counts[5] = 20;
// 7 characters [2380] ███████████████████
ident_length_counts[6] = 20;
// 8 characters [1786] ██████████████▎
ident_length_counts[7] = 18;
// 9 characters [1397] ███████████▏
ident_length_counts[8] = 12;
// 10 characters [ 739] █████▉
ident_length_counts[9] = 12;
// 11 characters [ 779] ██████▎
ident_length_counts[10] = 12;
// 12 characters [1344] ██████████▊
ident_length_counts[11] = 12;
// 13 characters [ 498] ████
ident_length_counts[12] = 5;
// 14 characters [ 284] ██▎
ident_length_counts[13] = 3;
// 15 characters [ 172] █▍
// 16 characters [ 278] ██▎
// 17 characters [ 191] █▌
// 18 characters [ 207] █▋
for (int i = 14; i < 18; ++i) {
ident_length_counts[i] = 2;
}
// 19 - 63 characters are all <100 but non-zero, and we map them to 1 for
// coverage despite slightly over weighting the tail.
for (int i = 18; i < 64; ++i) {
ident_length_counts[i] = 1;
}
return ident_length_counts;
}();
// A template function that implements the common logic of `GetIdentifiers` and
// `GetUniqueIdentifiers`. Most parameters correspond to the parameters of those
// functions. Additionally, an `AppendFunc` callable is provided to implement
// the appending operation.
//
// The main functionality provided here is collecting the correct number of
// identifiers from each of the lengths in the range [min_length, max_length]
// and either in our default representative distribution or a uniform
// distribution.
auto SourceGen::GetIdentifiersImpl(int number, int min_length, int max_length,
bool uniform,
llvm::function_ref<AppendFn> append)
-> llvm::SmallVector<llvm::StringRef> {
CARBON_CHECK(min_length <= max_length);
CARBON_CHECK(
uniform || max_length <= 64,
"Cannot produce a meaningful non-uniform distribution of lengths longer "
"than 64 as those are exceedingly rare in our observed data sets.");
llvm::SmallVector<llvm::StringRef> idents;
idents.reserve(number);
// First, compute the total weight of the distribution so we know how many
// identifiers we'll get each time we collect from it.
int num_lengths = max_length - min_length + 1;
auto length_counts =
llvm::ArrayRef(IdentifierLengthCounts).slice(min_length - 1, num_lengths);
int count_sum = uniform ? num_lengths : Sum(length_counts);
CARBON_CHECK(count_sum >= 1);
int number_rem = number % count_sum;
// Finally, walk through each length in the distribution.
for (int length : llvm::seq_inclusive(min_length, max_length)) {
// Scale how many identifiers we want of this length if computing a
// non-uniform distribution. For uniform, we always take one.
int scale = uniform ? 1 : IdentifierLengthCounts[length - 1];
// Now we can compute how many identifiers of this length to request.
int length_count = (number / count_sum) * scale;
if (number_rem > 0) {
int rem_adjustment = std::min(scale, number_rem);
length_count += rem_adjustment;
number_rem -= rem_adjustment;
}
append(length, length_count, idents);
}
CARBON_CHECK(number_rem == 0, "Unexpected number remaining: {0}", number_rem);
CARBON_CHECK(static_cast<int>(idents.size()) == number,
"Ended up with {0} identifiers instead of the requested {1}",
idents.size(), number);
return idents;
}
// Returns a shuffled sequence of integers in the range [min, max].
//
// The order of the returned integers is random, but each integer in the range
// appears the same number of times in the result, with the number of
// appearances rounded up for lower numbers and rounded down for higher numbers
// in order to exactly produce `number` results.
auto SourceGen::GetShuffledInts(int number, int min, int max)
-> llvm::SmallVector<int> {
llvm::SmallVector<int> ints;
ints.reserve(number);
// Evenly distribute to each value between min and max.
int num_values = max - min + 1;
for (int i : llvm::seq_inclusive(min, max)) {
int i_count = number / num_values;
i_count += i < (min + (number % num_values));
ints.append(i_count, i);
}
CARBON_CHECK(static_cast<int>(ints.size()) == number);
std::shuffle(ints.begin(), ints.end(), rng_);
return ints;
}
// A helper to pop series of unique identifiers off a sequence of random
// identifiers that may have duplicates.
//
// This is particularly designed to work with the sequences of non-unique
// identifiers produced by `GetShuffledIdentifiers` with the important property
// that while popping off unique identifiers found in the shuffled list, we
// don't change the distribution of identifier lengths.
//
// The uniqueness is only per-instance of the class, and so an instance can be
// used to extract a series of names that share a scope.
//
// It works by scanning the sequence to extract each unique identifier found,
// swapping it to the back and popping it off the list. This does shuffle the
// order, but it isn't expected to do so in an interesting way.
//
// It also provides a fallback path in case there are no unique identifiers left
// which computes fresh, random identifiers with the same length as the next one
// in the sequence until a unique one is found.
//
// For simplicity of the fallback path, the lifetime of the identifiers produced
// is bound to the lifetime of the popper instance, and not the generator as a
// whole. If this is ever a problematic constraint, we can start copying
// fallback identifiers into the generator's storage.
class SourceGen::UniqueIdentifierPopper {
public:
explicit UniqueIdentifierPopper(SourceGen& gen,
llvm::SmallVectorImpl<llvm::StringRef>& data)
: gen_(&gen), data_(&data), it_(data_->rbegin()) {}
// Pop the next unique identifier that can be found in the data, or synthesize
// one with a valid length. Always consumes exactly one identifier from the
// data.
//
// Note that the lifetime of the underlying identifier is that of the popper
// and not the underlying data.
auto Pop() -> llvm::StringRef {
for (auto end = data_->rend(); it_ != end; ++it_) {
auto insert = set_.Insert(*it_);
if (!insert.is_inserted()) {
continue;
}
if (it_ != data_->rbegin()) {
std::swap(*data_->rbegin(), *it_);
}
CARBON_CHECK(insert.key() == data_->back());
return data_->pop_back_val();
}
// Out of unique elements. Overwrite the back, preserving its length,
// generating a new identifiers until we find a unique one and return that.
// This ensures we continue to consume the structure and produce the same
// size identifiers even in the fallback.
int length = data_->pop_back_val().size();
auto fallback_ident_storage =
llvm::MutableArrayRef(reinterpret_cast<char*>(gen_->storage_.Allocate(
/*Size=*/length, /*Alignment=*/1)),
length);
for (;;) {
gen_->GenerateRandomIdentifier(fallback_ident_storage);
auto fallback_id = llvm::StringRef(fallback_ident_storage.data(), length);
if (set_.Insert(fallback_id).is_inserted()) {
return fallback_id;
}
}
}
private:
SourceGen* gen_;
llvm::SmallVectorImpl<llvm::StringRef>* data_;
llvm::SmallVectorImpl<llvm::StringRef>::reverse_iterator it_;
Set<llvm::StringRef> set_;
};
// Generates a function declaration and writes it to the provided stream.
//
// The declaration can be configured with a function name, private modifier,
// whether it is a method, the parameter count, an how indented it is.
//
// This is also provided a collection of identifiers to consume as parameter
// names -- it will use a unique popper to extract unique parameter names from
// this collection.
auto SourceGen::GenerateFunctionDecl(
llvm::StringRef name, bool is_private, bool is_method, int param_count,
llvm::StringRef indent, llvm::SmallVectorImpl<llvm::StringRef>& param_names,
llvm::function_ref<auto()->llvm::StringRef> get_type_name,
llvm::raw_ostream& os) -> void {
os << indent << "// TODO: make better comment text\n";
if (!IsCpp()) {
os << indent << (is_private ? "private " : "") << "fn " << name;
if (is_method) {
os << "[self: Self]";
}
} else {
os << indent;
if (!is_method) {
os << "static ";
}
os << "auto " << name;
}
os << "(";
if (param_count >
(is_method ? NumSingleLineMethodParams : NumSingleLineFunctionParams)) {
os << "\n" << indent << " ";
}
UniqueIdentifierPopper unique_param_names(*this, param_names);
for (int i : llvm::seq(param_count)) {
if (i > 0) {
if ((i % MaxParamsPerLine) == 0) {
os << ",\n" << indent << " ";
} else {
os << ", ";
}
}
if (!IsCpp()) {
os << unique_param_names.Pop() << ": " << get_type_name();
} else {
os << get_type_name() << " " << unique_param_names.Pop();
}
}
os << ")";
os << " -> " << get_type_name();
os << ";\n";
}
// Generate a class definition and write it to the provided stream.
//
// The structure of the definition is guided by the `params` provided, and it
// consumes the provided state.
auto SourceGen::GenerateClassDef(const ClassParams& params,
ClassGenState& state, llvm::raw_ostream& os)
-> void {
llvm::StringRef name = state.class_names().pop_back_val();
os << "// TODO: make better comment text\n";
os << "class " << name << " {\n";
if (IsCpp()) {
os << " public:\n";
}
// Field types can't be the class we're currently declaring. We enforce this
// by collecting them before inserting that type into the valid set.
llvm::SmallVector<llvm::StringRef> field_type_names;
field_type_names.reserve(params.private_field_decls);
for ([[maybe_unused]] auto _ : llvm::seq(params.private_field_decls)) {
field_type_names.push_back(state.GetValidTypeName());
}
// Mark this class as now a valid type now that field type names have been
// collected. We can reference this class from functions and methods within
// the definition.
state.AddValidTypeName(name);
UniqueIdentifierPopper unique_member_names(*this, state.member_names());
llvm::ListSeparator line_sep("\n");
for ([[maybe_unused]] auto _ : llvm::seq(params.public_function_decls)) {
os << line_sep;
GenerateFunctionDecl(
unique_member_names.Pop(), /*is_private=*/false,
/*is_method=*/false,
state.public_function_param_counts().pop_back_val(),
/*indent=*/" ", state.param_names(),
[&] { return state.GetValidTypeName(); }, os);
}
for ([[maybe_unused]] auto _ : llvm::seq(params.public_method_decls)) {
os << line_sep;
GenerateFunctionDecl(
unique_member_names.Pop(), /*is_private=*/false,
/*is_method=*/true, state.public_method_param_counts().pop_back_val(),
/*indent=*/" ", state.param_names(),
[&] { return state.GetValidTypeName(); }, os);
}
if (IsCpp()) {
os << "\n private:\n";
// Reset the separator.
line_sep = llvm::ListSeparator("\n");
}
for ([[maybe_unused]] auto _ : llvm::seq(params.private_function_decls)) {
os << line_sep;
GenerateFunctionDecl(
unique_member_names.Pop(), /*is_private=*/true,
/*is_method=*/false,
state.private_function_param_counts().pop_back_val(),
/*indent=*/" ", state.param_names(),
[&] { return state.GetValidTypeName(); }, os);
}
for ([[maybe_unused]] auto _ : llvm::seq(params.private_method_decls)) {
os << line_sep;
GenerateFunctionDecl(
unique_member_names.Pop(), /*is_private=*/true,
/*is_method=*/true, state.private_method_param_counts().pop_back_val(),
/*indent=*/" ", state.param_names(),
[&] { return state.GetValidTypeName(); }, os);
}
os << line_sep;
for (llvm::StringRef type_name : field_type_names) {
if (!IsCpp()) {
os << " private var " << unique_member_names.Pop() << ": " << type_name
<< ";\n";
} else {
os << " " << type_name << " " << unique_member_names.Pop() << ";\n";
}
}
os << "}" << (IsCpp() ? ";" : "") << "\n";
}
} // namespace Carbon::Testing
-299
View File
@@ -1,299 +0,0 @@
// 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_BASE_SOURCE_GEN_H_
#define CARBON_TESTING_BASE_SOURCE_GEN_H_
#include <string>
#include "absl/random/random.h"
#include "common/map.h"
#include "common/set.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Allocator.h"
namespace Carbon::Testing {
// Provides source code generation facilities.
//
// This class works to generate valid but random & meaningless source code in
// interesting patterns for benchmarking. It is very incomplete. A high level
// set of long-term goals:
//
// - Generate interesting patterns and structures of code that have emerged as
// toolchain performance bottlenecks in practice in C++ codebases.
// - Generate code that includes most Carbon language features (and whatever
// reasonable C++ analogs could be used for comparative purposes):
// - Functions
// - Classes with class functions, methods, and fields
// - Interfaces
// - Checked generics and templates
// - Nested and unnested impls
// - Nested classes
// - Inline and out-of-line function and method definitions
// - Imports and exports
// - API files and impl files.
// - Be random but deterministic. The goal is benchmarking and so while this
// code should strive for not producing trivially predictable patterns, it
// should also strive to be consistent and suitable for benchmarking. Wherever
// possible, it should permute the order and content without randomizing the
// total count, size, or complexity.
//
// Note that the default and primary generation target is interesting Carbon
// source code. We have a best-effort to alternatively generate comparable C++
// constructs to the Carbon ones for comparative benchmarking, but there is no
// goal to cover all the interesting C++ patterns we might want to benchmark,
// and we don't aim for perfectly synthesizing C++ analogs. We can always drop
// fidelity for the C++ code path if needed for simplicity.
//
// TODO: There are numerous places where we hard code a fixed quantity. Instead,
// we should build a rich but general system to easily encode a discrete
// distribution that is sampled. We have a specialized version of this for
// identifiers that should be generalized.
class SourceGen {
public:
enum class Language : uint8_t {
Carbon,
Cpp,
};
struct FunctionDeclParams {
// TODD: Arbitrary default, should switch to a distribution from data.
int max_params = 4;
};
struct MethodDeclParams {
// TODD: Arbitrary default, should switch to a distribution from data.
int max_params = 4;
};
// Parameters used to generate a class in a generated file.
//
// Currently, this uses a fixed number of each kind of declaration, with
// arbitrary defaults chosen. The defaults currently skew towards large
// classes with lots of nested declarations.
// TODO: Switch these to distributions based on data.
//
// TODO: Add support for generating definitions and parameters to control
// them.
//
// TODO: Add heuristic for how many functions have return types.
struct ClassParams {
int public_function_decls = 4;
FunctionDeclParams public_function_decl_params = {.max_params = 8};
int public_method_decls = 10;
MethodDeclParams public_method_decl_params;
int private_function_decls = 2;
FunctionDeclParams private_function_decl_params = {.max_params = 6};
int private_method_decls = 8;
MethodDeclParams private_method_decl_params = {.max_params = 6};
int private_field_decls = 6;
};
// Parameters used to select type _uses_, as opposed to definitions.
//
// These govern what distribution of types are used for function parameters,
// returns, and fields.
//
// Mainly these provide a coarse histogram of weights to shape the
// distribution of different type options, and try to fit that as closely as
// possible.
//
// The default weights in the histogram were arbitrarily selected based on
// intuition about importance for benchmarking and not based on any
// measurement. We arrange for them to sum to 100 so that the weights can be
// view as %s of the type uses.
//
// The specific builtin type options used in the weights were also selected
// arbitrarily.
//
// TODO: Improve the set of builtin types and the weighting if real world code
// ends up sharply different.
//
// TODO: Add a heuristic to make some % of type references via pointers (or
// other compound types).
struct TypeUseParams {
// The weights in the histogram start with a sequence fixed types described
// with a Carbon and C++ string, and their associated weight.
struct FixedTypeWeight {
llvm::StringRef carbon_spelling;
llvm::StringRef cpp_spelling;
int weight;
};
llvm::SmallVector<FixedTypeWeight> fixed_type_weights = {
// Combined weight of 65 for a core set of builtin types.
{.carbon_spelling = "bool", .cpp_spelling = "bool", .weight = 25},
{.carbon_spelling = "i32", .cpp_spelling = "int", .weight = 20},
{.carbon_spelling = "i64",
.cpp_spelling = "std::int64_t",
.weight = 10},
{.carbon_spelling = "i32*", .cpp_spelling = "int*", .weight = 5},
{.carbon_spelling = "i64*",
.cpp_spelling = "std::int64_t*",
.weight = 5},
// A weight of 5 distributed across tuple structures
{.carbon_spelling = "(bool, i64)",
.cpp_spelling = "std::pair<bool, std::int64_t>",
.weight = 2},
{.carbon_spelling = "(i32, i64*)",
.cpp_spelling = "std::pair<int, std::int64_t*>",
.weight = 3},
};
// The weight for using types declared in the file. These will be randomly
// shuffled references, and when there are more type references than
// declared, include repeated references.
int declared_types_weight = 30;
};
// Parameters used to generate a file with dense declarations.
struct DenseDeclParams {
// TODO: Add more parameters to control generating top-level constructs
// other than class definitions.
// Parameters used when generating class definitions.
ClassParams class_params = {};
// Parameters used to guide the selection of types for use in declarations.
TypeUseParams type_use_params = {};
};
// Access a global instance of this type to generate Carbon code for
// benchmarks, tests, or other places where sharing a common instance is
// useful. Note that there is nothing thread safe about this instance or type.
static auto Global() -> SourceGen&;
// Construct a source generator for the provided language, by default Carbon.
explicit SourceGen(Language language = Language::Carbon);
// Generate an API file with dense classes containing function forward
// declarations.
//
// Accepts a number of `target_lines` for the resulting source code. This is a
// rough approximation used to scale all the other constructs up and down
// accordingly. For C++ source generation, we work to generate the same number
// of constructs as Carbon would for the given line count over keeping the
// actual line count close to the target.
//
// TODO: Currently, the formatting and line breaks of generating code are
// extremely rough still, and those are a large factor in adherence to
// `target_lines`. Long term, the goal is to get as close as we can to any
// automatically formatted code while still keeping the stability of
// benchmarking.
auto GenApiFileDenseDecls(int target_lines, const DenseDeclParams& params)
-> std::string;
// Get some number of randomly shuffled identifiers.
//
// The identifiers start with a character [A-Za-z], other characters may also
// include [0-9_]. Both Carbon and C++ keywords are excluded along with any
// other non-identifier syntaxes that overlap to ensure all of these can be
// used as identifiers.
//
// The order will be different for each call to this function, but the
// specific identifiers may remain the same in order to reduce the cost of
// repeated calls. However, the sum of the identifier sizes returned is
// guaranteed to be the same for every call with the same number of
// identifiers so that benchmarking all of these identifiers has predictable
// and stable cost.
//
// Optionally, callers can request a minimum and maximum length. By default,
// the length distribution used across the identifiers will mirror the
// observed distribution of identifiers in C++ source code and our expectation
// of them in Carbon source code. The maximum length in this default
// distribution cannot be more than 64.
//
// Callers can request a uniform distribution across [min_length, max_length],
// and when it is requested there is no limit on `max_length`.
auto GetShuffledIdentifiers(int number, int min_length = 1,
int max_length = 64, bool uniform = false)
-> llvm::SmallVector<llvm::StringRef>;
// Same as `GetShuffledIdentifiers`, but ensures there are no collisions.
auto GetShuffledUniqueIdentifiers(int number, int min_length = 4,
int max_length = 64, bool uniform = false)
-> llvm::SmallVector<llvm::StringRef>;
// Returns a collection of un-shuffled identifiers, otherwise the same as
// `GetShuffledIdentifiers`.
//
// Usually, benchmarks should use the shuffled version. However, this is
// useful when deterministic access to the identifiers is needed to avoid
// introducing noise, or if there is already a post-processing step to shuffle
// things, since shuffling is very expensive in debug builds.
auto GetIdentifiers(int number, int min_length = 1, int max_length = 64,
bool uniform = false)
-> llvm::SmallVector<llvm::StringRef>;
// Returns a collection of un-shuffled unique identifiers, otherwise the same
// as `GetShuffledUniqueIdentifiers`.
//
// Usually, benchmarks should use the shuffled version. However, this is
// useful when deterministic access to the identifiers is needed to avoid
// introducing noise, or if there is already a post-processing step to shuffle
// things, since shuffling is very expensive in debug builds.
auto GetUniqueIdentifiers(int number, int min_length = 1, int max_length = 64,
bool uniform = false)
-> llvm::SmallVector<llvm::StringRef>;
// Returns a shared collection of random identifiers of a specific length.
//
// For a single, exact length, we have an even cheaper routine to return
// access to a shared collection of identifiers. The order of these is a
// single fixed random order for a given execution. The returned array
// reference is only valid until the next call any method on this generator.
auto GetSingleLengthIdentifiers(int length, int number)
-> llvm::ArrayRef<llvm::StringRef>;
private:
class ClassGenState;
friend ClassGenState;
class UniqueIdentifierPopper;
friend UniqueIdentifierPopper;
using AppendFn = auto(int length, int number,
llvm::SmallVectorImpl<llvm::StringRef>& dest) -> void;
auto IsCpp() -> bool { return language_ == Language::Cpp; }
auto GenerateRandomIdentifier(llvm::MutableArrayRef<char> dest_storage)
-> void;
auto AppendUniqueIdentifiers(int length, int number,
llvm::SmallVectorImpl<llvm::StringRef>& dest)
-> void;
auto GetIdentifiersImpl(int number, int min_length, int max_length,
bool uniform, llvm::function_ref<AppendFn> append)
-> llvm::SmallVector<llvm::StringRef>;
auto GetShuffledInts(int number, int min, int max) -> llvm::SmallVector<int>;
auto GenerateFunctionDecl(
llvm::StringRef name, bool is_private, bool is_method, int param_count,
llvm::StringRef indent,
llvm::SmallVectorImpl<llvm::StringRef>& param_names,
llvm::function_ref<auto()->llvm::StringRef> get_type_name,
llvm::raw_ostream& os) -> void;
auto GenerateClassDef(const ClassParams& params, ClassGenState& state,
llvm::raw_ostream& os) -> void;
absl::BitGen rng_;
llvm::BumpPtrAllocator storage_;
Map<int, llvm::SmallVector<llvm::StringRef>> identifiers_by_length_;
Map<int, std::pair<int, Set<llvm::StringRef>>> unique_identifiers_by_length_;
Language language_;
};
} // namespace Carbon::Testing
#endif // CARBON_TESTING_BASE_SOURCE_GEN_H_
-118
View File
@@ -1,118 +0,0 @@
// 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 <optional>
#include <system_error>
#include "common/bazel_working_dir.h"
#include "common/command_line.h"
#include "common/init_llvm.h"
#include "common/ostream.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/FileSystem.h"
#include "testing/base/source_gen.h"
namespace Carbon::Testing {
namespace {
constexpr CommandLine::CommandInfo Info = {
.name = "source_gen",
.help = R"""(
A source generator for Carbon.
)""",
};
constexpr CommandLine::ArgInfo OutputArgInfo = {
.name = "output",
.value_name = "FILE",
.help = R"""(
Writes the generate source code to a file rather than stdout.
)""",
};
constexpr CommandLine::ArgInfo LinesArgInfo = {
.name = "lines",
.value_name = "N",
.help = R"""(
The number of lines of code to target for a generated source file.
)""",
};
constexpr CommandLine::ArgInfo LanguageArgInfo = {
.name = "language",
//.value_name = "[carbon|cpp]",
.help = R"""(
The language of source code to generate. The C++ source generation is best
effort to try to provide as much comparable benchmarking as possible, but the
primary language focus is generating Carbon.
)""",
};
auto Run(llvm::ArrayRef<llvm::StringRef> args) -> bool {
// Default to outputting to stdout and writing 10k lines of source code.
llvm::StringRef output_filename = "-";
int lines = 10'000;
SourceGen::Language language;
auto parse_result = CommandLine::Parse(
args, llvm::outs(), Info, [&](CommandLine::CommandBuilder& b) {
b.AddStringOption(OutputArgInfo,
[&](auto& arg_b) { arg_b.Set(&output_filename); });
b.AddIntegerOption(LinesArgInfo,
[&](auto& arg_b) { arg_b.Set(&lines); });
b.AddOneOfOption(LanguageArgInfo, [&](auto& arg_b) {
arg_b.SetOneOf(
{
arg_b.OneOfValue("carbon", SourceGen::Language::Carbon)
.Default(true),
arg_b.OneOfValue("cpp", SourceGen::Language::Cpp),
},
&language);
});
// No-op action as there is only one operation for this command.
b.Do([] {});
});
if (!parse_result.ok()) {
llvm::errs() << "error: " << *parse_result << "\n";
return false;
} else if (*parse_result == CommandLine::ParseResult::MetaSuccess) {
// Fully handled by the CLI library.
return true;
}
std::optional<llvm::raw_fd_ostream> output_file;
llvm::raw_fd_ostream* output = &llvm::outs();
if (output_filename != "-") {
std::error_code ec;
output_file.emplace(output_filename, ec, llvm::sys::fs::OF_None);
if (ec) {
llvm::errs() << "ERROR: Unable to open output file '" << output_filename
<< "': " << ec.message() << "\n";
return false;
}
output = &*output_file;
}
SourceGen gen(language);
*output << gen.GenApiFileDenseDecls(lines, SourceGen::DenseDeclParams{});
output->flush();
return true;
}
} // namespace
} // namespace Carbon::Testing
auto main(int argc, char** argv) -> int {
// Do LLVM's initialization first, this will also transform UTF-16 to UTF-8.
Carbon::InitLLVM init_llvm(argc, argv);
Carbon::SetWorkingDirForBazelRun();
llvm::SmallVector<llvm::StringRef> args(argv + 1, argv + argc);
bool success = Carbon::Testing::Run(args);
return success ? EXIT_SUCCESS : EXIT_FAILURE;
}
-189
View File
@@ -1,189 +0,0 @@
// 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 "testing/base/source_gen.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <string>
#include "common/set.h"
#include "testing/base/global_exe_path.h"
#include "toolchain/base/install_paths_test_helpers.h"
#include "toolchain/driver/driver.h"
namespace Carbon::Testing {
namespace {
using ::testing::AllOf;
using ::testing::ContainerEq;
using ::testing::Contains;
using ::testing::Each;
using ::testing::Eq;
using ::testing::Ge;
using ::testing::Gt;
using ::testing::Le;
using ::testing::MatchesRegex;
using ::testing::SizeIs;
// 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 ([[maybe_unused]] 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)))));
}
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;
}
}
// Check that the source code doesn't have compiler errors.
auto TestCompile(llvm::StringRef source) -> bool {
llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> fs =
new llvm::vfs::InMemoryFileSystem;
InstallPaths installation(
InstallPaths::MakeForBazelRunfiles(Testing::GetExePath()));
Driver driver(fs, &installation, /*input_stream=*/nullptr, &llvm::outs(),
&llvm::errs());
AddPreludeFilesToVfs(installation, fs);
fs->addFile("test.carbon", /*ModificationTime=*/0,
llvm::MemoryBuffer::getMemBuffer(source));
return driver.RunCommand({"compile", "--phase=check", "test.carbon"}).success;
}
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))));
// Make sure we generated valid Carbon code.
EXPECT_TRUE(TestCompile(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))));
// TODO: When the driver supports compiling C++ code as easily as Carbon, we
// should test that the generated C++ code is valid.
}
} // namespace
} // namespace Carbon::Testing