mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 17:00:25 +01:00
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:
+1
-48
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user