Files
carbon-lang/toolchain/benchmarking/source_gen.cpp
T
Chandler CarruthandRichard Smith 39e69f0b4f Address a subtle instability in source_gen and shard its tests (#8052)
Previously, it was possible for rounding to result in small percentage
constructs to be dropped entirely, breaking invariants. For example,
when generating >100 entities, a 2% share should reliably be >=2.

Some parts of source gen already propagated remainders to avoid exactly
this issue, but we weren't propagating them everywhere, notably across
depths and different definitions in plans.

We fix this by introducing a helper for this more robust form of percent
scaling that carries remainders across the slices. It also seeds this
rounding from Phi across [0,100) to keep the edge cases uniformly
distributed without causing instability from the source generator's
seed. The seeded remainder doesn't impact providing the invariant, so
the fix should be durable.

Once this is done, we can shard the tests without the separate processes
allowing the above failure mode to occur.

Assisted-by: Antigravity with Gemini

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
2026-10-30 05:04:57 +00:00

3316 lines
137 KiB
C++

// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "toolchain/benchmarking/source_gen.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <numbers>
#include <numeric>
#include <string>
#include <utility>
#include "common/check.h"
#include "common/raw_string_ostream.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/STLExtras.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 length of every local variable name. How many times a definition spells
// each local depends on the local's position, so a single length keeps the byte
// count from depending on which name lands where. Locals are shorter than class
// and free function names, so they can't shadow a class or free function that
// the definition names. This is hard-coded rather than in `ClassParams` because
// seed-independent output sizes require a single length `< MinClassNameLength`;
// it would only need to be parameterized to benchmark identifier-length
// sensitivity specifically for local variables.
static constexpr int LocalNameLength = 3;
// The number of function and method declarations in each class.
static auto NumDeclsPerClass(const SourceGen::ClassParams& params) -> int {
return params.public_function_decls + params.public_method_decls +
params.private_function_decls + params.private_method_decls;
}
// The number of inline definitions of every kind in each class.
static auto NumInlineDefsPerClass(const SourceGen::ClassParams& params) -> int {
return params.inline_function_defs + params.inline_getters +
params.inline_predicates + params.inline_forwarders;
}
// Returns the fixed types that satisfy `eligible`, each repeated `weight` times
// and interleaved with the others.
static auto WeightedFixedTypes(
const SourceGen::TypeUseParams& params,
llvm::function_ref<
auto(const SourceGen::TypeUseParams::FixedTypeWeight&)->bool>
eligible)
-> llvm::SmallVector<const SourceGen::TypeUseParams::FixedTypeWeight*> {
llvm::SmallVector<const SourceGen::TypeUseParams::FixedTypeWeight*> weighted;
for (int round = 0;; ++round) {
int size = weighted.size();
for (const auto& fw : params.fixed_type_weights) {
if (fw.weight > round && eligible(fw)) {
weighted.push_back(&fw);
}
}
if (static_cast<int>(weighted.size()) == size) {
return weighted;
}
}
}
// The kinds of step in a definition plan. A plan fixes how many steps of each
// kind a definition has at each depth of nesting, and the definition arranges
// them randomly. A step has the same size wherever it lands at its depth, so
// the arrangement doesn't change the definition's size.
//
// TODO: Plans read each parameter once, read class-typed locals only with
// `Checksum`, and only call free functions, with fixed arguments. Real
// definitions read parameters and fields throughout, and call methods of their
// locals with arguments computed from other locals. Add those statements and
// expressions, fixing each one's spelling before the shuffle as the type use
// pools do, so that sizes stay the same for every seed.
enum class PlanStepKind : uint8_t {
// `var <local>: i32 = <expr>;`, whose local is available to be read and
// updated by subsequent steps.
ScalarLocal,
// `var <local>: <class> = <class>.Make();`, whose local is read with
// `Checksum` by the next step and is available to be read (but not updated)
// by other steps.
ClassTypeLocal,
// A declaration that destructures a tuple into two locals, such as
// `let (<local>: i32, <local>: i64) = (<expr>, <expr> as i64);`, whose locals
// are read by the next step and whose `i32` locals are available to be read
// (and updated, when `var`) by subsequent steps. `spelling_index` selects
// among `DestructureForms`.
Destructure,
// A declaration that destructures the tuple returned by a call to a free
// function into two locals, such as
// `let (<local>: bool, <local>: i64) = <call>;`, whose locals are read by the
// next step and whose `i32` locals are available to be read (but not updated)
// by subsequent steps. When no free function returns a tuple in
// `CallDestructureForms`, this falls back to `Destructure` using
// `spelling_index`.
DestructureCall,
// `<local> = <expr>;`
Update,
// `<local> = <local> + <call>;`, where a consumer template reads the result
// of a call to a free function.
Call,
// `if (<cond>) {`, a block, and `}`.
If,
// `if (<cond>) {`, a block, `} else {`, a block, and `}`.
IfElse,
// `if (<cond>) {`, a block, `} else if (<cond>) {`, a block, and `}`.
ElseIf,
// `while (<cond>) {`, a block, and `}`.
While,
// `if (<cond>) {`, `return <value>;`, and `}`.
GuardReturn,
// `if (<cond>) {`, `break;` or `continue;`, and `}`, in a loop's block.
GuardExit,
// `<local> = <local> + <reads>;`, where the reads apply a consumer template
// to each parameter, and read `self` in a method. Definitions add one of
// these to their plan's steps at depth 0.
ReadParams,
};
static constexpr int NumPlanStepKinds =
static_cast<int>(PlanStepKind::ReadParams) + 1;
// The number of blocks that a step of `kind` opens.
static auto NumPlanBlocks(PlanStepKind kind) -> int {
switch (kind) {
case PlanStepKind::If:
case PlanStepKind::While:
return 1;
case PlanStepKind::IfElse:
case PlanStepKind::ElseIf:
return 2;
default:
return 0;
}
}
// Whether a step of `kind` declares locals, which the step after it reads.
static auto IsLocalDeclaration(PlanStepKind kind) -> bool {
return kind == PlanStepKind::ScalarLocal ||
kind == PlanStepKind::ClassTypeLocal ||
kind == PlanStepKind::Destructure ||
kind == PlanStepKind::DestructureCall;
}
// A step in a definition plan. `spelling_index` selects among the available
// spellings for `kind`, in `[0, NumPlanSpellings(kind))`.
struct PlanStep {
PlanStepKind kind;
int spelling_index;
};
// The expressions and conditions in plan steps. `{0}` and `{1}` are locals,
// which all have the same length. Conditions compare a single local, so that
// they can't compare a local with itself.
static constexpr llvm::StringLiteral PlanExprs[] = {
"{0} + 1", "{0} * 2", "{0} - {1}", "({0} % 7) + {1}"};
static constexpr llvm::StringLiteral PlanConds[] = {"{0} > 3", "{0} < 9",
"{0} == 2", "{0} != 5"};
// A form of `Destructure` step. `{0}` and `{1}` are operands, and `{a}` and
// `{b}` the two locals, which `read` reads as an `i32` expression. Later steps
// can also read the locals that are `i32`s, and update them when they're
// `var`s.
struct DestructureForm {
llvm::StringLiteral carbon_decl;
llvm::StringLiteral cpp_decl;
llvm::StringLiteral carbon_read;
llvm::StringLiteral cpp_read;
bool is_var;
bool a_is_i32;
bool b_is_i32;
};
static constexpr DestructureForm DestructureForms[] = {
{.carbon_decl = "let ({a}: i32, {b}: i32) = ({0} + 1, {1} * 2);",
.cpp_decl = "const auto [{a}, {b}] = std::pair{{0} + 1, {1} * 2};",
.carbon_read = "({a} + {b})",
.cpp_read = "({a} + {b})",
.is_var = false,
.a_is_i32 = true,
.b_is_i32 = true},
{.carbon_decl = "let ({a}: i32, {b}: i64) = ({0}, {1} as i64);",
.cpp_decl = "const auto [{a}, {b}] = "
"std::pair{{0}, static_cast<std::int64_t>({1})};",
.carbon_read = "({a} + ({b} as i32))",
.cpp_read = "({a} + static_cast<int>({b}))",
.is_var = false,
.a_is_i32 = true,
.b_is_i32 = false},
{.carbon_decl = "var ({a}: bool, {b}: i32) = ({0} > 3, {1});",
.cpp_decl = "auto [{a}, {b}] = std::pair{{0} > 3, {1}};",
.carbon_read = "((if {a} then 1 else 0) + {b})",
.cpp_read = "(({a} ? 1 : 0) + {b})",
.is_var = true,
.a_is_i32 = false,
.b_is_i32 = true},
};
// A form of `DestructureCall` step, for the tuple type that its callee returns.
// `{a}` and `{b}` are the two locals, and `read` reads them along with an
// operand, `{0}`, since the call has none. Later steps can also read the locals
// that are `i32`s. C++ destructures every call with `const auto [{a}, {b}]`.
struct CallDestructureForm {
llvm::StringLiteral carbon_type;
llvm::StringLiteral cpp_type;
llvm::StringLiteral carbon_pattern;
llvm::StringLiteral carbon_read;
llvm::StringLiteral cpp_read;
bool a_is_i32;
bool b_is_i32;
};
static constexpr CallDestructureForm CallDestructureForms[] = {
{.carbon_type = "(bool, i64)",
.cpp_type = "std::pair<bool, std::int64_t>",
.carbon_pattern = "let ({a}: bool, {b}: i64)",
.carbon_read = "({0} + (if {a} then 1 else 0) + ({b} as i32))",
.cpp_read = "({0} + ({a} ? 1 : 0) + static_cast<int>({b}))",
.a_is_i32 = false,
.b_is_i32 = false},
{.carbon_type = "(i32, i64*)",
.cpp_type = "std::pair<int, std::int64_t*>",
.carbon_pattern = "let ({a}: i32, {b}: i64*)",
.carbon_read = "({0} + {a} + (*{b} as i32))",
.cpp_read = "({0} + {a} + static_cast<int>(*{b}))",
.a_is_i32 = true,
.b_is_i32 = false},
};
// The number of distinct spellings for a step of `kind`.
static auto NumPlanSpellings(PlanStepKind kind) -> int {
switch (kind) {
case PlanStepKind::ScalarLocal:
case PlanStepKind::Update:
return std::size(PlanExprs);
case PlanStepKind::ClassTypeLocal:
case PlanStepKind::Call:
return 1;
case PlanStepKind::Destructure:
case PlanStepKind::DestructureCall:
// `DestructureCall` uses `DestructureForms` when no free function returns
// a destructurable tuple.
return std::size(DestructureForms);
case PlanStepKind::If:
case PlanStepKind::IfElse:
case PlanStepKind::ElseIf:
case PlanStepKind::While:
case PlanStepKind::GuardReturn:
return std::size(PlanConds);
case PlanStepKind::GuardExit:
// Pairs one of `PlanConds` with `break;` or `continue;`.
return std::lcm<int>(std::size(PlanConds), 2);
case PlanStepKind::ReadParams:
return 1;
}
}
// The steps of a definition plan at each depth. The statement that a guard
// returns or exits with is one depth below the guard, and isn't listed.
struct DefPlan {
llvm::SmallVector<llvm::SmallVector<PlanStep>> levels;
// The number of lines in the plan's steps. A definition whose plan has calls
// or destructured calls also has a line declaring `acc64`, which `lines`
// counts.
int lines = 0;
// The number of local variables that the plan's steps declare.
int locals = 0;
// The number of class-typed locals among `locals`.
int class_type_locals = 0;
// The class names of the plan's class-typed locals, with `""` for `Self`.
llvm::SmallVector<llvm::StringRef> class_type_local_types;
// The number of `Call` steps in the plan.
int calls = 0;
// The number of `DestructureCall` steps in the plan.
int destructure_calls = 0;
};
// The internal shape calibration of definition plans, which approximates the
// function definitions in a sample of the Carbon toolchain's, LLVM's, and
// Clang's implementation files, as classified with libclang (excluding
// definitions with a `switch`).
//
// These constants are hardcoded rather than in `DenseDeclParams` or
// `ClassParams` unless a benchmark or test needs to vary a specific structural
// dimension inside definitions (such as nesting depth, branching factor, or
// local variable density) independently of statement count.
//
// All rates and bucket distributions below are integer percentages (with
// multi-bucket rows summing to 100).
// A row of `PlanShapeTable` giving the shape parameters for plans with at
// least `min_statements` statements: the percentage of plans with each maximum
// depth, and the number of guard returns at a depth as a percentage of the
// statements at the next depth.
struct PlanShapeRow {
int min_statements;
std::array<int, 6> max_depth_percents;
int guard_return_percent;
};
// Rows are sorted in strictly increasing order of `min_statements`, starting
// with `min_statements = 0` so that every statement count has an applicable
// row. A plan deterministically uses the last row with
// `min_statements <= statements`.
static constexpr PlanShapeRow PlanShapeTable[] = {
{.min_statements = 0,
.max_depth_percents = {100, 0, 0, 0, 0, 0},
.guard_return_percent = 0},
{.min_statements = 2,
.max_depth_percents = {25, 62, 13, 0, 0, 0},
.guard_return_percent = 65},
{.min_statements = 5,
.max_depth_percents = {8, 64, 25, 3, 0, 0},
.guard_return_percent = 47},
{.min_statements = 8,
.max_depth_percents = {7, 46, 34, 11, 2, 0},
.guard_return_percent = 32},
{.min_statements = 13,
.max_depth_percents = {2, 36, 39, 18, 4, 1},
.guard_return_percent = 21},
{.min_statements = 20,
.max_depth_percents = {2, 21, 42, 25, 7, 3},
.guard_return_percent = 16},
{.min_statements = 29,
.max_depth_percents = {0, 11, 32, 35, 17, 5},
.guard_return_percent = 12},
{.min_statements = 43,
.max_depth_percents = {0, 2, 23, 42, 21, 12},
.guard_return_percent = 10},
{.min_statements = 67,
.max_depth_percents = {0, 5, 4, 29, 30, 32},
.guard_return_percent = 12},
};
// Indexed by a plan's maximum depth, each row gives the percentage of the
// plan's statements at each depth (summing to 100). Every definition also has
// three statements at depth 0, so plans have a lower share there than real
// definitions.
static constexpr std::array<int, 6> PlanDepthPercents[] = {
{100, 0, 0, 0, 0, 0}, {60, 40, 0, 0, 0, 0}, {38, 45, 17, 0, 0, 0},
{25, 38, 27, 10, 0, 0}, {17, 29, 28, 18, 8, 0}, {13, 25, 24, 19, 13, 6},
};
// The percentage of loop blocks that have a guard exit (`break` or `continue`).
static constexpr int LoopExitPercent = 35;
// The percentage of non-guard statements at the next depth that open a new
// `if`, `else`, or `while` block (~2.1 statements per block).
static constexpr int NewBlockPercent = 48;
// The percentages of those blocks that are a loop's, and that are one of the
// two blocks of an `if` with an `else` or `else if`.
static constexpr int LoopBlockPercent = 15;
static constexpr int ElseBlockPercent = 30;
// The percentage of those two-block `if` statements that use `else if` rather
// than `else`.
static constexpr int ElseIfPercent = 42;
// How many times more likely a loop block is than another block of the same
// size to get each further statement when arranging a definition.
static constexpr int LoopBlockWeight = 3;
// The percentage of updates that are calls, when definitions can make calls.
static constexpr int CallPercent = 50;
// The percentage of destructurings that destructure a call, when definitions
// can make calls. In the sampled Carbon, LLVM, and Clang implementation files,
// 89% of structured bindings bind a call's result.
static constexpr int CallDestructurePercent = 89;
// The percentages of the statements at depth 0, and deeper, that declare a new
// local variable.
static constexpr int TopLocalPercent = 35;
static constexpr int NestedLocalPercent = 25;
// The percentages of declarations that destructure a tuple, and of the rest
// that declare a local of a class type. Structured bindings are 0.2-1.9% of the
// declarations in the sampled Carbon, LLVM, and Clang implementation files, and
// Carbon's tuples suggest the high end. Class types are 29-78% of the types of
// local variables, 49% on average.
static constexpr int DestructurePercent = 2;
static constexpr int ClassTypeLocalPercent = 50;
// Returns a percentage value from a sequence that is spread evenly in [0, 100).
// The sequence is chosen so that consecutive subsequences are spaced out as
// evenly as possible, with percentages well-separated from each other and
// averaging to ~50% even in short subsequences.
static auto EvenlySpreadPercent(int index) -> int {
CARBON_CHECK(index >= 0);
// Multiples of `std::numbers::phi` mod 1.0 in `double` spread evenly across
// `[0, 100)` without needing a fixed-point rational approximation.
return static_cast<int>(std::fmod((index + 0.5) * std::numbers::phi, 1.0) *
100);
}
// Scales a count by a percentage, carrying the remainder across calls.
//
// Each call adds `count * percent` percentage points and returns `1` per full
// `100` points, keeping the `< 100` leftover points in `remainder_`. Across any
// sequence of calls, the total returned is
// `(initial_remainder + sum(count * percent)) / 100`, which is always either
// the floor or the ceiling of the exact unrounded total regardless of where
// `remainder_` starts in `[0, 100)`. Spreading starting offsets evenly across
// `[0, 100)` keeps rounding unbiased and varies which call gets an extra step.
class PercentScaler {
public:
explicit PercentScaler(int& next_index)
: remainder_(EvenlySpreadPercent(next_index++)) {}
auto operator()(int count, int percent) -> int {
remainder_ += count * percent;
int scaled = remainder_ / 100;
remainder_ %= 100;
return scaled;
}
private:
int remainder_;
};
// Deterministic `PercentScaler`s for `ComposeDefPlan`, shared across a sequence
// of plans so remainders carry from one definition to the next. Initialized
// from plan parameters rather than `SourceGen` so generated file sizes are
// identical across `SourceGen` seeds and processes.
struct PlanScalers {
explicit PlanScalers(int seed_index) : next_index(seed_index) {}
int next_index;
PercentScaler exits{next_index};
PercentScaler guards{next_index};
PercentScaler next_blocks{next_index};
PercentScaler loops{next_index};
PercentScaler pairs{next_index};
PercentScaler locals{next_index};
PercentScaler else_ifs{next_index};
PercentScaler destructures{next_index};
PercentScaler class_type_locals{next_index};
PercentScaler destructure_calls{next_index};
PercentScaler calls{next_index};
};
// Returns the maximum depth of the `variant`th plan with `statements`
// statements. Variants of a size follow the distribution in its row of
// `PlanShapeTable` using `EvenlySpreadPercent(variant)`.
static auto ChoosePlanMaxDepth(const PlanShapeRow& shape, int statements,
int variant) -> int {
int choice = EvenlySpreadPercent(variant);
int max_depth = 0;
for (int percent : shape.max_depth_percents) {
choice -= percent;
if (choice < 0) {
break;
}
++max_depth;
}
return std::min(max_depth, statements - 1);
}
// Splits `statements` statements between depths 0 through `max_depth`,
// following `PlanDepthPercents` and giving each depth at least one. Uses a
// fresh `PercentScaler` per plan because each row sums to `100` (so a shared
// scaler's remainder would return to its initial value after every plan).
static auto SplitPlanDepths(int statements, int max_depth, int& next_index)
-> llvm::SmallVector<int> {
PercentScaler scale_by_percent(next_index);
llvm::SmallVector<int> depth_counts;
for (int percent :
llvm::ArrayRef(PlanDepthPercents[max_depth]).take_front(max_depth + 1)) {
depth_counts.push_back(scale_by_percent(statements, percent));
}
for (int& count : depth_counts) {
if (count == 0) {
++count;
--*llvm::max_element(depth_counts);
}
}
return depth_counts;
}
// Composes the plan of a definition with `statements` statements, for the
// `variant`th definition of that size. With `calls`, some of the updates and
// destructurings call free functions. Percentage-scaled counts round with
// `scalers` so definitions of one size vary while tracking the calibrated
// rates.
static auto ComposeDefPlan(int statements, int variant, bool calls,
PlanScalers& scalers) -> DefPlan {
DefPlan plan;
if (statements == 0) {
return plan;
}
// Find the last row satisfying `statements >= row.min_statements`. This
// invariant is satisfied for `*shape` throughout the loop because the first
// row has `min_statements == 0`.
static_assert(PlanShapeTable[0].min_statements == 0);
const PlanShapeRow* shape = PlanShapeTable;
for (const PlanShapeRow& row : PlanShapeTable) {
if (&row != PlanShapeTable) {
CARBON_CHECK(row.min_statements > (&row - 1)->min_statements);
}
if (statements >= row.min_statements) {
shape = &row;
}
}
llvm::SmallVector<int> depth_counts = SplitPlanDepths(
statements, ChoosePlanMaxDepth(*shape, statements, variant),
scalers.next_index);
plan.levels.resize(depth_counts.size());
// Select each step's spelling starting from `variant` and advancing with
// each step of the same kind, so that spellings rotate both across variants
// of the same size and across same-kind steps within a plan.
std::array<int, NumPlanStepKinds> next_spelling = {};
next_spelling.fill(variant);
auto add = [&](int depth, PlanStepKind kind, int count) {
int num_spellings = NumPlanSpellings(kind);
int& next = next_spelling[static_cast<int>(kind)];
for (auto _ : llvm::seq(count)) {
plan.levels[depth].push_back(
{.kind = kind, .spelling_index = next % num_spellings});
++next;
}
};
// How many of `depth_counts[depth]` are the `return`, `break`, or `continue`
// body statements of guards at `depth - 1` (which are emitted as part of
// those guards rather than as separate `PlanStep`s at `depth`).
int guard_children = 0;
// The blocks at a depth, and how many of them are loop blocks.
int blocks = 1;
int loop_blocks = 0;
int num_depths = depth_counts.size();
for (int depth : llvm::seq(num_depths)) {
int count = depth_counts[depth];
int heads = count - guard_children;
int next = depth + 1 < num_depths ? depth_counts[depth + 1] : 0;
int guards = 0;
int exits = 0;
int ifs = 0;
int pairs = 0;
int loops = 0;
int next_blocks = 0;
if (next > 0) {
// Guards' statements can't hold deeper ones, so leave a free statement at
// the next depth when there are any.
int min_free = depth + 2 < num_depths ? 1 : 0;
exits = std::min({scalers.exits(loop_blocks, LoopExitPercent), heads,
next - min_free});
guards = std::min({scalers.guards(next, shape->guard_return_percent),
heads - exits, next - exits - min_free});
// The number of statements at the next depth that aren't the body of a
// guard at this depth, and so must be placed in blocks opened by
// non-guard statements at this depth.
int free = next - guards - exits;
// When there are such `free` statements at the next depth (rather than
// every statement at the next depth being a guard's body), keep at least
// one non-guard statement at this depth to open a block for them.
if (free > 0 && heads == guards + exits) {
if (guards > 0) {
--guards;
} else {
--exits;
}
++free;
}
if (free > 0) {
next_blocks = std::max(1, scalers.next_blocks(free, NewBlockPercent));
loops = scalers.loops(next_blocks, LoopBlockPercent);
pairs = std::min(scalers.pairs(next_blocks, ElseBlockPercent / 2),
(next_blocks - loops) / 2);
ifs = next_blocks - loops - 2 * pairs;
// Fit the statements that open blocks into this depth, merging blocks
// when they don't fit.
int room = heads - guards - exits;
while (ifs + pairs + loops > room) {
if (ifs >= 2) {
ifs -= 2;
++pairs;
} else if (ifs > 0) {
--ifs;
--next_blocks;
} else if (loops > 0) {
--loops;
--next_blocks;
} else {
--pairs;
next_blocks -= 2;
}
}
}
}
int fixed = guards + exits + ifs + pairs + loops;
// Each block at this depth ends with a step that isn't a local declaration.
int max_locals = heads - fixed - std::max(0, blocks - fixed);
int local_percent = depth == 0 ? TopLocalPercent : NestedLocalPercent;
int locals = std::min(scalers.locals(heads, local_percent), max_locals);
int else_ifs = scalers.else_ifs(pairs, ElseIfPercent);
int updates = heads - fixed - locals;
int destructures = scalers.destructures(locals, DestructurePercent);
int class_type_locals =
scalers.class_type_locals(locals - destructures, ClassTypeLocalPercent);
int destructure_calls =
calls ? scalers.destructure_calls(destructures, CallDestructurePercent)
: 0;
int num_calls = calls ? scalers.calls(updates, CallPercent) : 0;
add(depth, PlanStepKind::ScalarLocal,
locals - destructures - class_type_locals);
add(depth, PlanStepKind::ClassTypeLocal, class_type_locals);
add(depth, PlanStepKind::Destructure, destructures - destructure_calls);
add(depth, PlanStepKind::DestructureCall, destructure_calls);
add(depth, PlanStepKind::Update, updates - num_calls);
add(depth, PlanStepKind::Call, num_calls);
add(depth, PlanStepKind::If, ifs);
add(depth, PlanStepKind::IfElse, pairs - else_ifs);
add(depth, PlanStepKind::ElseIf, else_ifs);
add(depth, PlanStepKind::While, loops);
add(depth, PlanStepKind::GuardReturn, guards);
add(depth, PlanStepKind::GuardExit, exits);
// Each step takes a line, and so does the `}` that ends each block and
// guard, and the line between the blocks of a pair.
plan.lines += count + ifs + loops + 2 * pairs + guards + exits;
// Each destructuring declares two locals.
plan.locals += locals + destructures;
plan.class_type_locals += class_type_locals;
plan.calls += num_calls;
plan.destructure_calls += destructure_calls;
guard_children = guards + exits;
blocks = next_blocks;
loop_blocks = loops;
}
// Definitions with calls or destructured calls declare `acc64` on an extra
// line before `acc`; see `GenerateFunctionDef`.
if (plan.calls + plan.destructure_calls > 0) {
++plan.lines;
}
return plan;
}
// Returns how many of `num_defs` definitions have each size in `sizes`, in
// proportion to its weight. The definitions left over from rounding down go to
// the sizes with the largest remainders, the earliest first on ties.
static auto ApportionDefSizes(int num_defs,
llvm::ArrayRef<SourceGen::DefSizeWeight> sizes)
-> llvm::SmallVector<int> {
int64_t total_weight = 0;
for (const SourceGen::DefSizeWeight& size : sizes) {
CARBON_CHECK(size.statements >= 0 && size.weight >= 0,
"Definition sizes and weights can't be negative.");
total_weight += size.weight;
}
CARBON_CHECK(total_weight > 0,
"Definition sizes need a positive total weight.");
llvm::SmallVector<int> counts;
llvm::SmallVector<int64_t> remainders;
int left_over = num_defs;
for (const SourceGen::DefSizeWeight& size : sizes) {
int64_t share = static_cast<int64_t>(num_defs) * size.weight;
counts.push_back(share / total_weight);
remainders.push_back(share % total_weight);
left_over -= counts.back();
}
llvm::SmallVector<int> order(sizes.size());
std::iota(order.begin(), order.end(), 0);
llvm::stable_sort(order, [&](int lhs, int rhs) {
return remainders[lhs] > remainders[rhs];
});
for (int index : llvm::ArrayRef(order).take_front(left_over)) {
++counts[index];
}
return counts;
}
// Builds the plans for `num_defs` definitions, with sizes following `sizes` and
// with calls when `calls`, and appends them to `plans`. Each size's plans
// spread evenly through the order, so that any stretch of the order has a mix
// of sizes.
static auto BuildDefPlans(int num_defs,
llvm::ArrayRef<SourceGen::DefSizeWeight> sizes,
bool calls, llvm::SmallVectorImpl<DefPlan>& plans)
-> void {
// Entries are sorted by `position`, which is `(variant + 0.5) / count`, to
// interleave buckets with different counts.
struct Entry {
double position;
int statements;
int variant;
};
llvm::SmallVector<Entry> entries;
entries.reserve(num_defs);
llvm::SmallVector<int> counts = ApportionDefSizes(num_defs, sizes);
for (auto [size, count] : llvm::zip_equal(sizes, counts)) {
for (int variant : llvm::seq(count)) {
entries.push_back({.position = (variant + 0.5) / count,
.statements = size.statements,
.variant = variant});
}
}
llvm::stable_sort(entries, [](const Entry& lhs, const Entry& rhs) {
return lhs.position < rhs.position;
});
PlanScalers scalers(num_defs * 2 + static_cast<int>(calls));
plans.reserve(plans.size() + num_defs);
for (const Entry& entry : entries) {
plans.push_back(
ComposeDefPlan(entry.statements, entry.variant, calls, scalers));
}
}
// Estimates the average number of lines in the plans of definitions with sizes
// following `sizes`, and with calls when `calls`. Uses `double` (like the
// other `EstimateAvg*Lines` helpers below) to accumulate fractional average
// line counts across variants and constructs before a single final rounding to
// a whole number of classes.
static auto EstimateAvgDefPlanLines(
llvm::ArrayRef<SourceGen::DefSizeWeight> sizes, bool calls) -> double {
// Average over enough variants that their rounding evens out.
constexpr int NumVariants = 16;
double lines = 0.0;
int64_t total_weight = 0;
for (const SourceGen::DefSizeWeight& size : sizes) {
PlanScalers scalers(size.statements * 2 + static_cast<int>(calls));
int size_lines = 0;
for (int variant : llvm::seq(NumVariants)) {
size_lines +=
ComposeDefPlan(size.statements, variant, calls, scalers).lines;
}
lines += static_cast<double>(size_lines) / NumVariants * size.weight;
total_weight += size.weight;
}
CARBON_CHECK(total_weight > 0,
"Definition sizes need a positive total weight.");
return lines / total_weight;
}
// 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:
// A free function declaration, which definitions call. `args` is the argument
// list of every call to it.
struct FreeFunction {
llvm::StringRef name;
llvm::SmallVector<llvm::StringRef> param_types;
std::string args;
llvm::StringRef return_type;
};
// A call in a definition: the index of the callee in `free_functions()`, and
// the consumer template that reads its result.
struct CallRef {
int callee_index;
llvm::StringRef consumer;
};
// A destructured call in a definition: the index of the callee in
// `free_functions()`, and the index of its form in `CallDestructureForms`.
struct DestructureCallRef {
int callee_index;
int form_index;
};
ClassGenState(SourceGen& gen, int num_classes, const DenseDeclParams& 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 inline_function_param_counts() -> llvm::SmallVectorImpl<int>& {
return inline_function_param_counts_;
}
auto forwarder_param_counts() -> llvm::SmallVectorImpl<int>& {
return forwarder_param_counts_;
}
// The plans of the out-of-line definitions, followed by those of inline
// function definitions, starting at `inline_def_plans_start()`. A definition
// uses the plan at the index of its return type's use among the plans of its
// kind; see `BuildTypePool`.
auto def_plans() -> llvm::ArrayRef<DefPlan> { return def_plans_; }
auto inline_def_plans_start() -> int { return inline_def_plans_start_; }
auto free_functions() -> llvm::ArrayRef<FreeFunction> {
return free_functions_;
}
// The shuffled order to declare the free functions in.
auto free_emission_order() -> llvm::ArrayRef<int> {
return free_emission_order_;
}
auto free_param_names() -> llvm::SmallVectorImpl<llvm::StringRef>& {
return free_param_names_;
}
// The calls that definition plans make, shuffled.
auto call_refs() -> llvm::SmallVectorImpl<CallRef>& { return call_refs_; }
// The destructured calls that definition plans make, shuffled.
auto destructure_call_refs() -> llvm::SmallVectorImpl<DestructureCallRef>& {
return destructure_call_refs_;
}
// Emits the next call in `call_refs()`, read with its consumer template.
auto EmitCall(llvm::raw_ostream& os) -> void {
CallRef ref = call_refs_.pop_back_val();
const FreeFunction& callee = free_functions_[ref.callee_index];
auto [prefix, suffix] = ref.consumer.split("{0}");
os << prefix << callee.name << "(" << callee.args << ")" << suffix;
}
// Takes the next destructured call, spells it in `call`, and returns the form
// that destructures it. Returns null when no free function returns a tuple
// with a form.
auto NextDestructureCall(std::string& call) -> const CallDestructureForm* {
if (destructure_call_refs_.empty()) {
return nullptr;
}
DestructureCallRef ref = destructure_call_refs_.pop_back_val();
const FreeFunction& callee = free_functions_[ref.callee_index];
call = (callee.name + "(" + callee.args + ")").str();
return &CallDestructureForms[ref.form_index];
}
auto getter_field_types() -> llvm::SmallVectorImpl<llvm::StringRef>& {
return getter_field_types_;
}
// The type of a field that a predicate tests, and the predicate template for
// that type.
struct PredicateField {
llvm::StringRef type;
llvm::StringRef predicate;
};
auto predicate_fields() -> llvm::SmallVectorImpl<PredicateField>& {
return predicate_fields_;
}
auto class_names() -> llvm::SmallVectorImpl<llvm::StringRef>& {
return class_names_;
}
auto decl_names() -> llvm::SmallVectorImpl<llvm::StringRef>& {
return decl_names_;
}
auto field_names() -> llvm::SmallVectorImpl<llvm::StringRef>& {
return field_names_;
}
auto param_names() -> llvm::SmallVectorImpl<llvm::StringRef>& {
return param_names_;
}
auto inline_function_names() -> llvm::SmallVectorImpl<llvm::StringRef>& {
return inline_function_names_;
}
auto inline_param_names() -> llvm::SmallVectorImpl<llvm::StringRef>& {
return inline_param_names_;
}
auto local_names() -> llvm::SmallVectorImpl<llvm::StringRef>& {
return local_names_;
}
auto accessed_field_names() -> llvm::SmallVectorImpl<llvm::StringRef>& {
return accessed_field_names_;
}
auto forwarder_names() -> llvm::SmallVectorImpl<llvm::StringRef>& {
return forwarder_names_;
}
auto forwarder_param_names() -> llvm::SmallVectorImpl<llvm::StringRef>& {
return forwarder_param_names_;
}
auto AddValidTypeName(llvm::StringRef type_name) -> void {
current_class_ = type_name;
valid_type_names_.Insert(type_name);
}
// The names of all classes and free functions in the file. Parameter,
// member, and field names exclude these so that they can't shadow a name that
// a definition uses.
auto file_scope_name_set() -> const Set<llvm::StringRef>& {
return file_scope_name_set_;
}
// Emits an expression producing a value of `type`: a call to the class's
// `Make` function, or the fixed type's value expression.
auto ProduceValue(llvm::StringRef type, llvm::raw_ostream& os) -> void {
if (class_name_set_.Contains(type)) {
os << type << (is_cpp_ ? "()" : ".Make()");
} else {
os << fixed_value_.Lookup(type).value();
}
}
// A use of a type drawn from a pool, and its index in the pool before the
// pool was shuffled. A consumed use also has the consumer template that reads
// it.
struct TypeUse {
llvm::StringRef name;
llvm::StringRef consumer;
int index = -1;
};
// Each category of type use draws from its own pool so that every slot in a
// pool emits its type name (and any associated initializer, return value, or
// parameter-read expression) the same number of times. This keeps the
// generated file's byte count independent of how shuffling assigns types to
// slots within a pool.
// Return and parameter types of declarations without out-of-line
// definitions (spelled once).
auto GetDeclType() -> TypeUse { return GetValidTypeUse(decl_type_pool_); }
// Parameter types of inline function definitions (spelled once in the
// signature, and read once in the definition using a consumer template).
auto GetInlineParamType() -> TypeUse {
return GetValidTypeUse(inline_param_pool_);
}
// Return types of inline function definitions (spelled once in the
// signature, and constructed in `return` statements).
auto GetInlineReturnType() -> TypeUse {
return GetValidReturnType(inline_return_pool_, /*is_inline=*/true);
}
// Return types of declarations with out-of-line definitions (spelled in both
// the declaration and the definition, and constructed in `return`
// statements).
auto GetOutOfLineReturnType() -> TypeUse {
return GetValidReturnType(out_of_line_return_pool_, /*is_inline=*/false);
}
// Parameter types of declarations with out-of-line definitions (spelled in
// both the declaration and the definition, and read once in the definition
// using a consumer template).
auto GetOutOfLineParamType() -> TypeUse {
return GetValidTypeUse(out_of_line_param_pool_);
}
// Field types (spelled once in the field declaration, and initialized with a
// value expression in `Make` when the file has definitions).
auto GetFieldType() -> TypeUse { return GetValidTypeUse(field_type_pool_); }
// Return and parameter types of forwarders (spelled in both the forwarder
// and the `<name>Impl` declaration it calls).
auto GetForwardType() -> TypeUse {
return GetValidTypeUse(forward_type_pool_);
}
auto has_defs() -> bool { return has_defs_; }
auto define_decls_out_of_line() -> bool { return define_decls_out_of_line_; }
auto type_pools_empty() -> bool {
return decl_type_pool_.uses.empty() && inline_param_pool_.uses.empty() &&
inline_return_pool_.uses.empty() && field_type_pool_.uses.empty() &&
forward_type_pool_.uses.empty() &&
out_of_line_return_pool_.uses.empty() &&
out_of_line_param_pool_.uses.empty();
}
private:
// A pool of type uses, removed as they are emitted. `GetValidTypeUse` resumes
// its search at `last_index`.
struct TypePool {
llvm::SmallVector<TypeUse> uses;
int last_index = 0;
};
auto GetValidTypeUse(
TypePool& pool, llvm::function_ref<auto(const TypeUse&)->bool> is_valid =
[](const TypeUse&) { return true; }) -> TypeUse;
auto GetValidReturnType(TypePool& pool, bool is_inline) -> TypeUse;
auto BuildClassAndTypeNames(SourceGen& gen, int num_classes,
const DenseDeclParams& params) -> void;
auto BuildFreeFunction(
SourceGen& gen, int i, llvm::StringRef name,
llvm::ArrayRef<const TypeUseParams::FixedTypeWeight*> weighted_types,
int max_params) -> FreeFunction;
auto BuildCallRefs(SourceGen& gen, int num_calls)
-> llvm::SmallVector<CallRef>;
auto BuildDestructureCallRefs(SourceGen& gen, int num_calls)
-> llvm::SmallVector<DestructureCallRef>;
auto BuildTypePool(SourceGen& gen, int num_types, int max_refs_per_class,
bool needs_value_expr, bool consumed,
bool exclude_last_class,
const TypeUseParams& type_use_params) -> TypePool;
auto AssignClassTypeLocalTypes(SourceGen& gen, int defs_per_class,
llvm::MutableArrayRef<DefPlan> plans) -> 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<int> inline_function_param_counts_;
llvm::SmallVector<int> forwarder_param_counts_;
llvm::SmallVector<DefPlan> def_plans_;
int inline_def_plans_start_;
llvm::SmallVector<FreeFunction> free_functions_;
llvm::SmallVector<int> free_emission_order_;
llvm::SmallVector<llvm::StringRef> free_param_names_;
llvm::SmallVector<CallRef> call_refs_;
llvm::SmallVector<DestructureCallRef> destructure_call_refs_;
// Getters return a copy of their field, and `Make` initializes it, so getter
// and predicate fields have fixed types with a value expression. These types
// cycle through the eligible types in proportion to their weights, so that
// their spellings don't depend on the seed.
llvm::SmallVector<llvm::StringRef> getter_field_types_;
llvm::SmallVector<PredicateField> predicate_fields_;
llvm::SmallVector<llvm::StringRef> class_names_;
// Out-of-line definitions spell declared function and method names a second
// time, and `Make` does the same for field names, so these have separate
// pools.
llvm::SmallVector<llvm::StringRef> decl_names_;
llvm::SmallVector<llvm::StringRef> field_names_;
llvm::SmallVector<llvm::StringRef> param_names_;
// Inline function names are shorter than `MinMemberNameLength`, so they can't
// collide with class, member, or field names.
llvm::SmallVector<llvm::StringRef> inline_function_names_;
llvm::SmallVector<llvm::StringRef> inline_param_names_;
llvm::SmallVector<llvm::StringRef> local_names_;
// A getter or predicate spells its field's name again, and a forwarder's
// call and the declaration it calls spell its name and parameter names
// again, so these have separate pools.
llvm::SmallVector<llvm::StringRef> accessed_field_names_;
llvm::SmallVector<llvm::StringRef> forwarder_names_;
llvm::SmallVector<llvm::StringRef> forwarder_param_names_;
bool is_cpp_;
// Whether the file has any function or method definitions. Then each class
// also gets a `Make` function (a default constructor in C++), a `Checksum`
// method, and a `tag` field.
bool has_defs_;
bool define_decls_out_of_line_;
TypePool decl_type_pool_;
TypePool inline_param_pool_;
TypePool inline_return_pool_;
TypePool field_type_pool_;
TypePool forward_type_pool_;
TypePool out_of_line_return_pool_;
TypePool out_of_line_param_pool_;
llvm::StringRef current_class_;
Set<llvm::StringRef> valid_type_names_;
Set<llvm::StringRef> class_name_set_;
Set<llvm::StringRef> file_scope_name_set_;
// The value expression and consumer templates of each fixed type, keyed by
// its spelling. These refer into the `TypeUseParams`, which outlives
// generation.
Map<llvm::StringRef, llvm::StringRef> fixed_value_;
Map<llvm::StringRef, llvm::ArrayRef<llvm::StringRef>> fixed_consumers_;
llvm::ArrayRef<llvm::StringRef> class_consumers_;
};
// 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 DenseDeclParams& params)
: is_cpp_(gen.IsCpp()),
has_defs_(NumInlineDefsPerClass(params.class_params) > 0 ||
params.define_decls_out_of_line),
define_decls_out_of_line_(params.define_decls_out_of_line) {
const ClassParams& class_params = params.class_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_inline_functions = num_classes * class_params.inline_function_defs;
decl_names_ =
gen.GetShuffledIdentifiers(num_classes * NumDeclsPerClass(class_params),
/*min_length=*/MinMemberNameLength);
field_names_ =
gen.GetShuffledIdentifiers(num_classes * class_params.private_field_decls,
/*min_length=*/MinMemberNameLength);
int num_out_of_line_defs = define_decls_out_of_line_
? num_classes * NumDeclsPerClass(class_params)
: 0;
// Each out-of-line definition has one parameter beyond its random count; see
// `BuildClassAndTypeNames`.
int num_params = Sum(public_function_param_counts_) +
Sum(public_method_param_counts_) +
Sum(private_function_param_counts_) +
Sum(private_method_param_counts_) + num_out_of_line_defs;
param_names_ = gen.GetShuffledIdentifiers(num_params);
inline_function_param_counts_ =
gen.GetShuffledInts(num_inline_functions, 0,
class_params.inline_function_decl_params.max_params);
// Each inline definition has one parameter beyond its random count; see
// `BuildClassAndTypeNames`.
int num_inline_params =
Sum(inline_function_param_counts_) + num_inline_functions;
// Definitions only make calls when the file has free functions to call.
bool calls = params.free_function_decls_per_class > 0;
BuildDefPlans(num_out_of_line_defs, params.out_of_line_def_sizes, calls,
def_plans_);
inline_def_plans_start_ = def_plans_.size();
BuildDefPlans(num_inline_functions, class_params.inline_function_def_sizes,
calls, def_plans_);
int num_locals = 0;
for (const DefPlan& plan : def_plans_) {
num_locals += plan.locals;
}
int num_getters = num_classes * class_params.inline_getters;
int num_predicates = num_classes * class_params.inline_predicates;
int num_forwarders = num_classes * class_params.inline_forwarders;
inline_function_names_ = gen.GetShuffledIdentifiers(
num_inline_functions + num_getters + num_predicates, /*min_length=*/2,
/*max_length=*/MinMemberNameLength - 1);
inline_param_names_ = gen.GetShuffledIdentifiers(num_inline_params);
local_names_ = gen.GetShuffledIdentifiers(num_locals,
/*min_length=*/LocalNameLength,
/*max_length=*/LocalNameLength);
accessed_field_names_ =
gen.GetShuffledIdentifiers(num_getters + num_predicates,
/*min_length=*/MinMemberNameLength);
forwarder_param_counts_ = gen.GetShuffledInts(
num_forwarders, 0, class_params.inline_forwarder_params.max_params);
forwarder_names_ =
gen.GetShuffledIdentifiers(num_forwarders, /*min_length=*/2,
/*max_length=*/MinMemberNameLength - 1);
forwarder_param_names_ =
gen.GetShuffledIdentifiers(Sum(forwarder_param_counts_));
BuildClassAndTypeNames(gen, num_classes, params);
}
auto SourceGen::ClassGenState::GetValidTypeUse(
TypePool& pool, llvm::function_ref<auto(const TypeUse&)->bool> is_valid)
-> TypeUse {
// Check that we don't completely wrap the type names by tracking where we
// started.
int initial_last_index = pool.last_index;
// Now search the type uses, starting from the last used index, to find the
// first valid one.
for (;;) {
if (pool.last_index == 0) {
pool.last_index = pool.uses.size();
}
--pool.last_index;
TypeUse& use = pool.uses[pool.last_index];
if (valid_type_names_.Contains(use.name) && is_valid(use)) {
// Found a valid type use, swap it with the back and pop that off.
std::swap(pool.uses.back(), use);
return pool.uses.pop_back_val();
}
// `BuildTypePool` caps the references to each class so that a valid type
// use always remains.
CARBON_CHECK(pool.last_index != initial_last_index,
"Failed to find a valid type name with {0} candidates, an "
"initial index of {1}, and with {2} classes left to emit!",
pool.uses.size(), initial_last_index, class_names_.size());
}
}
auto SourceGen::ClassGenState::GetValidReturnType(TypePool& pool,
bool is_inline) -> TypeUse {
int plans_start = is_inline ? inline_def_plans_start_ : 0;
return GetValidTypeUse(pool, [&](const TypeUse& use) {
// A definition spells its own class `Self` (represented here by the empty
// string), never by class name. Inline definitions can only name classes
// defined prior to `current_class_`, whereas out-of-line definitions follow
// every class and can name any other class.
const DefPlan& plan = def_plans_[plans_start + use.index];
for (llvm::StringRef type : plan.class_type_local_types) {
if (!type.empty() && (type == current_class_ ||
(is_inline && !valid_type_names_.Contains(type)))) {
return false;
}
}
return true;
});
}
// Builds a shuffled pool of `num_types` type uses, mixing references to the
// classes with the fixed types to roughly match the weights in
// `type_use_params`. `max_refs_per_class` caps the references to each class.
//
// With `needs_value_expr`, the pool only includes fixed types that have a value
// expression (for initializing a field in `Make` or returning a value from a
// function definition). With `consumed`, each use gets one of its type's
// consumer templates, assigned round-robin per type before the shuffle so that
// the mix of templates doesn't depend on the seed. With `exclude_last_class`,
// the pool excludes the last class defined in the file (`class_names_[0]`):
// field types are drawn before the current class is valid, and return types are
// paired with definition plans whose non-`Self` class-typed locals cannot name
// the enclosing class (or, for inline definitions, any later class). Each use
// also records its pre-shuffle `index` in the pool so callers can pair it with
// other unshuffled per-function state (such as `def_plans_`), keeping the
// pairing and the resulting generated code size independent of the shuffle
// seed.
//
// `valid_type_names_` must already contain the fixed type spellings.
auto SourceGen::ClassGenState::BuildTypePool(
SourceGen& gen, int num_types, int max_refs_per_class,
bool needs_value_expr, bool consumed, bool exclude_last_class,
const TypeUseParams& type_use_params) -> TypePool {
TypePool pool;
if (num_types == 0) {
return pool;
}
pool.uses.reserve(num_types);
auto fixed_spelling = [&](const TypeUseParams::FixedTypeWeight& fw) {
return gen.IsCpp() ? fw.cpp_spelling : fw.carbon_spelling;
};
auto fixed_usable = [&](const TypeUseParams::FixedTypeWeight& fw) {
return !needs_value_expr ||
!(gen.IsCpp() ? fw.cpp_value : fw.carbon_value).empty();
};
Map<llvm::StringRef, int> consumer_counters;
auto append_use = [&](llvm::StringRef name) {
llvm::StringRef consumer;
if (consumed) {
llvm::ArrayRef<llvm::StringRef> consumers =
class_name_set_.Contains(name)
? class_consumers_
: fixed_consumers_.Lookup(name).value();
int& counter = consumer_counters.Insert(name, 0).value();
consumer = consumers[counter++ % consumers.size()];
}
pool.uses.push_back({.name = name, .consumer = consumer});
};
int type_weight_sum = type_use_params.declared_types_weight;
for (const auto& fixed_type_weight : type_use_params.fixed_type_weights) {
if (fixed_usable(fixed_type_weight)) {
type_weight_sum += fixed_type_weight.weight;
}
}
// 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. The class
// defined last has the first unshuffled name; see `BuildClassAndTypeNames`.
llvm::ArrayRef<llvm::StringRef> classes = class_names_;
if (exclude_last_class) {
classes = classes.drop_front();
}
int num_classes = classes.size();
int num_declared_types =
num_classes == 0
? 0
: num_types * type_use_params.declared_types_weight / type_weight_sum;
int full_copies = num_classes == 0 ? 0 : num_declared_types / num_classes;
int remainder = num_classes == 0 ? 0 : num_declared_types % num_classes;
// Cap the references to each class so that `GetValidTypeUse` always finds a
// valid type regardless of the shuffle order:
// - For pools with `!exclude_last_class`, a class is added to
// `valid_type_names_` after its fields are chosen, so references to a
// class can only be used on its own functions/methods or in later classes.
// In particular, the last class defined in the file can only be referenced
// by its own functions/methods, so each class can be referenced at most
// `max_refs_per_class` times (the number of draws per class from this
// pool).
// - For pools with `exclude_last_class` (`field_type_pool_`,
// `inline_return_pool_`, and `out_of_line_return_pool_`), a class cannot
// use its own type (or any later class's type, for fields and inline
// definitions). Excluding the last class defined in the file ensures every
// remaining class is valid during at least the last class's draws, and in
// general at most `k * max_refs_per_class` entries are invalid when `k`
// classes remain after the current class, so a valid entry is always
// available.
//
// The fixed types below replace any references removed by the cap, so the
// pool's spellings and total byte count remain independent of the shuffle.
if (full_copies >= max_refs_per_class) {
full_copies = max_refs_per_class;
remainder = 0;
}
for (auto _ : llvm::seq(full_copies)) {
for (llvm::StringRef name : classes) {
append_use(name);
}
}
// 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.
for (llvm::StringRef name : classes.slice(0, remainder)) {
append_use(name);
}
num_declared_types = full_copies * num_classes + remainder;
CARBON_CHECK(static_cast<int>(pool.uses.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) {
if (!fixed_usable(fixed_type_weight)) {
continue;
}
int num_fixed_type = num_types * fixed_type_weight.weight / type_weight_sum;
for (auto _ : llvm::seq(num_fixed_type)) {
append_use(fixed_spelling(fixed_type_weight));
}
}
// If we need a tail of types to hit the exact number, simply round-robin
// through the usable 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>(pool.uses.size()) < num_types) {
for (const auto& fixed_type_weight : type_use_params.fixed_type_weights) {
if (static_cast<int>(pool.uses.size()) >= num_types) {
break;
}
if (fixed_usable(fixed_type_weight)) {
append_use(fixed_spelling(fixed_type_weight));
}
}
}
CARBON_CHECK(static_cast<int>(pool.uses.size()) == num_types);
pool.last_index = num_types;
for (auto [index, use] : llvm::enumerate(pool.uses)) {
use.index = static_cast<int>(index);
}
std::shuffle(pool.uses.begin(), pool.uses.end(), gen.rng_);
return pool;
}
// Assigns the types of class-typed locals for `plans`, which are paired by
// pre-shuffle index with a return-type pool built with
// `exclude_last_class = true` and `max_refs_per_class = defs_per_class`.
//
// In that pool, each index `< (num_classes - 1) * defs_per_class` has return
// type `classes[index % (num_classes - 1)]` (where `classes` excludes the last
// class defined in the file) or a fixed type, and the final `defs_per_class`
// indices have fixed return types. Assigning `classes[index % (num_classes -
// 1)]` (or `""` for `Self`) to the first group and `""` (`Self`) to the final
// `defs_per_class` plans ensures each plan depends on at most one class from
// `classes`, and at least `defs_per_class` plans are valid even in the first
// class defined in the file (where no prior classes exist). Because this runs
// before `class_names_` is shuffled, the multiset of class-local spellings and
// the file's total byte count are independent of the seed.
auto SourceGen::ClassGenState::AssignClassTypeLocalTypes(
SourceGen& gen, int defs_per_class, llvm::MutableArrayRef<DefPlan> plans)
-> void {
int total_class_type_locals = 0;
for (const DefPlan& plan : plans) {
total_class_type_locals += plan.class_type_locals;
}
if (total_class_type_locals == 0) {
return;
}
llvm::ArrayRef<llvm::StringRef> classes =
llvm::ArrayRef(class_names_).drop_front();
int num_classes = class_names_.size();
int class_plans = static_cast<int>(classes.size()) * defs_per_class;
int self_locals = 0;
for (DefPlan& plan : plans.drop_front(class_plans)) {
plan.class_type_local_types.append(plan.class_type_locals, "");
self_locals += plan.class_type_locals;
}
int target_self_locals =
(total_class_type_locals + num_classes - 1) / num_classes;
int extra_self_locals = std::max(0, target_self_locals - self_locals);
for (auto [index, plan] : llvm::enumerate(plans.take_front(class_plans))) {
llvm::StringRef class_name = classes[index % classes.size()];
plan.class_type_local_types.reserve(plan.class_type_locals);
for (auto _ : llvm::seq(plan.class_type_locals)) {
if (extra_self_locals > 0) {
plan.class_type_local_types.push_back("");
--extra_self_locals;
} else {
plan.class_type_local_types.push_back(class_name);
}
}
std::shuffle(plan.class_type_local_types.begin(),
plan.class_type_local_types.end(), gen.rng_);
}
}
// Builds the class names and the type use pools. Each pool caps the references
// to each class at the number of uses each class draws from it after its
// fields; see `BuildTypePool`.
auto SourceGen::ClassGenState::BuildClassAndTypeNames(
SourceGen& gen, int num_classes, const DenseDeclParams& params) -> void {
const ClassParams& class_params = params.class_params;
const TypeUseParams& type_use_params = params.type_use_params;
// Initially get the sequence of class and free function names without
// shuffling so we can compute our type pools from them prior to any
// shuffling. Drawing both from one set of unique names keeps them distinct,
// and `MinClassNameLength` keeps them longer than local and inline function
// names so those can't shadow them.
int num_free_functions = num_classes * params.free_function_decls_per_class;
llvm::SmallVector<llvm::StringRef> file_scope_names =
gen.GetUniqueIdentifiers(num_classes + num_free_functions,
/*min_length=*/MinClassNameLength);
class_names_ =
llvm::to_vector(llvm::ArrayRef(file_scope_names).take_front(num_classes));
llvm::ArrayRef<llvm::StringRef> free_function_names =
llvm::ArrayRef(file_scope_names).slice(num_classes);
for (llvm::StringRef name : class_names_) {
class_name_set_.Insert(name);
}
for (llvm::StringRef name : file_scope_names) {
file_scope_name_set_.Insert(name);
}
// Every fixed type needs a consumer template, since any of them can be the
// type of a consumed parameter.
for (const auto& fw : type_use_params.fixed_type_weights) {
llvm::StringRef spelling =
gen.IsCpp() ? fw.cpp_spelling : fw.carbon_spelling;
valid_type_names_.Insert(spelling);
fixed_value_.Insert(spelling, gen.IsCpp() ? fw.cpp_value : fw.carbon_value);
llvm::ArrayRef<llvm::StringRef> consumers =
gen.IsCpp() ? fw.cpp_consumers : fw.carbon_consumers;
CARBON_CHECK(!consumers.empty(),
"Fixed type `{0}` needs at least one consumer template.",
spelling);
for (llvm::StringRef consumer : consumers) {
CARBON_CHECK(consumer.contains("{0}"),
"Fixed type `{0}` has a consumer template without a name "
"placeholder.",
spelling);
}
fixed_consumers_.Insert(spelling, consumers);
}
CARBON_CHECK(!type_use_params.class_consumers.empty(),
"Class types need at least one consumer template.");
class_consumers_ = type_use_params.class_consumers;
int decls_per_class = NumDeclsPerClass(class_params);
int num_decl_returns = num_classes * decls_per_class;
int num_decl_params =
Sum(public_function_param_counts_) + Sum(public_method_param_counts_) +
Sum(private_function_param_counts_) + Sum(private_method_param_counts_);
int num_inline_returns = num_classes * class_params.inline_function_defs;
int num_inline_params = Sum(inline_function_param_counts_);
int num_fields = num_classes * class_params.private_field_decls;
// Each class draws `inline_function_defs` return types from this pool after
// its fields. As for out-of-line definitions, a definition's guards also
// return values of its return type, so each use's pre-shuffle index pairs it
// with a definition plan.
inline_return_pool_ =
BuildTypePool(gen, num_inline_returns, class_params.inline_function_defs,
/*needs_value_expr=*/true, /*consumed=*/false,
/*exclude_last_class=*/true, type_use_params);
AssignClassTypeLocalTypes(
gen, class_params.inline_function_defs,
llvm::MutableArrayRef(def_plans_).drop_front(inline_def_plans_start_));
// Each class draws `private_field_decls` field types from this pool before
// becoming a valid type itself. When the file has definitions, `Make`
// initializes every field with a value expression.
field_type_pool_ =
BuildTypePool(gen, num_fields, class_params.private_field_decls,
/*needs_value_expr=*/has_defs_, /*consumed=*/false,
/*exclude_last_class=*/true, type_use_params);
// Each inline definition has one parameter beyond its random count, so that
// each class draws at least `inline_function_defs` parameter types from this
// pool, and the pool can include class types.
int num_inline_extra_params = num_inline_returns;
inline_param_pool_ =
BuildTypePool(gen, num_inline_params + num_inline_extra_params,
class_params.inline_function_defs,
/*needs_value_expr=*/false, /*consumed=*/true,
/*exclude_last_class=*/false, type_use_params);
if (!define_decls_out_of_line_) {
// Each class draws `decls_per_class` return types from this pool after its
// fields.
decl_type_pool_ =
BuildTypePool(gen, num_decl_returns + num_decl_params, decls_per_class,
/*needs_value_expr=*/false, /*consumed=*/false,
/*exclude_last_class=*/false, type_use_params);
} else {
// Each class draws `decls_per_class` return types from this pool after its
// fields. A definition's guards also return values of its return type, so
// each use's pre-shuffle index pairs it with the definition plan at the
// same index.
out_of_line_return_pool_ =
BuildTypePool(gen, num_decl_returns, decls_per_class,
/*needs_value_expr=*/true, /*consumed=*/false,
/*exclude_last_class=*/true, type_use_params);
AssignClassTypeLocalTypes(
gen, decls_per_class,
llvm::MutableArrayRef(def_plans_).take_front(inline_def_plans_start_));
// As for inline definitions, each declaration has one parameter beyond its
// random count, so that each class draws at least `decls_per_class`
// parameter types from this pool.
int num_decl_extra_params = num_decl_returns;
out_of_line_param_pool_ = BuildTypePool(
gen, num_decl_params + num_decl_extra_params, decls_per_class,
/*needs_value_expr=*/false, /*consumed=*/true,
/*exclude_last_class=*/false, type_use_params);
}
// Each class draws `inline_forwarders` return types from this pool after its
// fields.
forward_type_pool_ =
BuildTypePool(gen,
num_classes * class_params.inline_forwarders +
Sum(forwarder_param_counts_),
class_params.inline_forwarders,
/*needs_value_expr=*/false, /*consumed=*/false,
/*exclude_last_class=*/false, type_use_params);
auto value = [&](const TypeUseParams::FixedTypeWeight& fw) {
return gen.IsCpp() ? fw.cpp_value : fw.carbon_value;
};
auto predicate = [&](const TypeUseParams::FixedTypeWeight& fw) {
return gen.IsCpp() ? fw.cpp_predicate : fw.carbon_predicate;
};
auto spelling = [&](const TypeUseParams::FixedTypeWeight& fw) {
return gen.IsCpp() ? fw.cpp_spelling : fw.carbon_spelling;
};
int num_getters = num_classes * class_params.inline_getters;
auto getter_types = WeightedFixedTypes(
type_use_params, [&](const auto& fw) { return !value(fw).empty(); });
CARBON_CHECK(num_getters == 0 || !getter_types.empty(),
"Getters need a fixed type with a value expression.");
for (int i : llvm::seq(num_getters)) {
getter_field_types_.push_back(
spelling(*getter_types[i % getter_types.size()]));
}
std::shuffle(getter_field_types_.begin(), getter_field_types_.end(),
gen.rng_);
int num_predicates = num_classes * class_params.inline_predicates;
auto predicate_types =
WeightedFixedTypes(type_use_params, [&](const auto& fw) {
return !value(fw).empty() && !predicate(fw).empty();
});
CARBON_CHECK(num_predicates == 0 || !predicate_types.empty(),
"Predicates need a fixed type with a value expression and a "
"predicate template.");
for (int i : llvm::seq(num_predicates)) {
const auto& fw = *predicate_types[i % predicate_types.size()];
predicate_fields_.push_back(
{.type = spelling(fw), .predicate = predicate(fw)});
}
std::shuffle(predicate_fields_.begin(), predicate_fields_.end(), gen.rng_);
free_functions_.reserve(num_free_functions);
auto free_function_types = WeightedFixedTypes(
type_use_params, [](const auto& /*fw*/) { return true; });
CARBON_CHECK(num_free_functions == 0 || !free_function_types.empty(),
"Free functions need a fixed type.");
int num_free_params = 0;
for (auto [i, name] : llvm::enumerate(free_function_names)) {
free_functions_.push_back(
BuildFreeFunction(gen, static_cast<int>(i), name, free_function_types,
params.free_function_decl_params.max_params));
num_free_params += free_functions_.back().param_types.size();
}
free_param_names_ = gen.GetShuffledIdentifiers(num_free_params);
int num_calls = 0;
int num_destructure_calls = 0;
for (const DefPlan& plan : def_plans_) {
num_calls += plan.calls;
num_destructure_calls += plan.destructure_calls;
}
call_refs_ = BuildCallRefs(gen, num_calls);
destructure_call_refs_ = BuildDestructureCallRefs(gen, num_destructure_calls);
free_emission_order_ = llvm::to_vector(llvm::seq(num_free_functions));
std::shuffle(free_emission_order_.begin(), free_emission_order_.end(),
gen.rng_);
// Classes are defined from the back, so the first unshuffled name is always
// the class defined last, which field and return pools leave out; see
// `BuildTypePool`.
std::shuffle(class_names_.begin() + 1, class_names_.end(), gen.rng_);
}
// Builds the `i`th free function declaration. Its signature depends only on
// `i`, not on the seed, so that the bytes of its calls don't either: its
// parameter count cycles through [0, max_params], its parameter types cycle
// through `weighted_types`, and its return type is the `i`th of
// `weighted_types`, so that return types follow the weights.
auto SourceGen::ClassGenState::BuildFreeFunction(
SourceGen& gen, int i, llvm::StringRef name,
llvm::ArrayRef<const TypeUseParams::FixedTypeWeight*> weighted_types,
int max_params) -> FreeFunction {
FreeFunction fn;
fn.name = name;
// Start each function's parameter types at a different point in the cycle.
int offset = i * (max_params + 2);
auto next_type = [&]() -> const TypeUseParams::FixedTypeWeight* {
return weighted_types[offset++ % weighted_types.size()];
};
int param_count = i % (max_params + 1);
RawStringOstream args;
llvm::ListSeparator sep;
for (auto _ : llvm::seq(param_count)) {
const auto& fw = *next_type();
fn.param_types.push_back(gen.IsCpp() ? fw.cpp_spelling
: fw.carbon_spelling);
llvm::StringRef arg = gen.IsCpp() ? fw.cpp_arg : fw.carbon_arg;
if (arg.empty()) {
arg = gen.IsCpp() ? fw.cpp_value : fw.carbon_value;
}
CARBON_CHECK(!arg.empty(),
"Fixed type `{0}` needs an argument or value expression.",
fn.param_types.back());
args << sep << arg;
}
fn.args = args.TakeStr();
const auto& return_type = *weighted_types[i % weighted_types.size()];
fn.return_type =
gen.IsCpp() ? return_type.cpp_spelling : return_type.carbon_spelling;
return fn;
}
// Builds a shuffled pool of `num_calls` calls, distributed evenly over the free
// functions. Each call gets one of its result type's consumer templates,
// round-robin per type before the shuffle.
auto SourceGen::ClassGenState::BuildCallRefs(SourceGen& gen, int num_calls)
-> llvm::SmallVector<CallRef> {
llvm::SmallVector<CallRef> refs;
if (num_calls == 0) {
return refs;
}
int num_free = free_functions_.size();
CARBON_CHECK(num_free > 0);
refs.reserve(num_calls);
Map<llvm::StringRef, int> consumer_counters;
auto append_ref = [&](int callee_index) {
llvm::StringRef ret = free_functions_[callee_index].return_type;
llvm::ArrayRef<llvm::StringRef> consumers =
fixed_consumers_.Lookup(ret).value();
int& counter = consumer_counters.Insert(ret, 0).value();
refs.push_back({.callee_index = callee_index,
.consumer = consumers[counter++ % consumers.size()]});
};
int full_copies = num_calls / num_free;
int remainder = num_calls % num_free;
for (auto _ : llvm::seq(full_copies)) {
for (int callee_index : llvm::seq(num_free)) {
append_ref(callee_index);
}
}
// Give the remaining calls to a prefix of the unshuffled free functions, so
// that which functions get an extra call doesn't depend on the seed.
for (int callee_index : llvm::seq(remainder)) {
append_ref(callee_index);
}
std::shuffle(refs.begin(), refs.end(), gen.rng_);
return refs;
}
// Builds a shuffled pool of `num_calls` destructured calls, distributed evenly
// over the free functions that return a tuple with a `CallDestructureForms`
// entry, with any remainder going to a fixed prefix of them. Returns an empty
// pool when there are none.
auto SourceGen::ClassGenState::BuildDestructureCallRefs(SourceGen& gen,
int num_calls)
-> llvm::SmallVector<DestructureCallRef> {
llvm::SmallVector<DestructureCallRef> eligible;
for (auto [callee_index, fn] : llvm::enumerate(free_functions_)) {
for (auto [form_index, call_form] : llvm::enumerate(CallDestructureForms)) {
if (fn.return_type ==
(gen.IsCpp() ? call_form.cpp_type : call_form.carbon_type)) {
eligible.push_back({.callee_index = static_cast<int>(callee_index),
.form_index = static_cast<int>(form_index)});
}
}
}
llvm::SmallVector<DestructureCallRef> refs;
if (eligible.empty()) {
return refs;
}
refs.reserve(num_calls);
for (int i : llvm::seq(num_calls)) {
refs.push_back(eligible[i % eligible.size()]);
}
std::shuffle(refs.begin(), refs.end(), gen.rng_);
return refs;
}
// 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;
// `extra_params` is the number of parameters a function has beyond its random
// count.
static auto EstimateAvgFunctionDeclLines(SourceGen::FunctionDeclParams params,
int extra_params = 0) -> double {
// Currently model a uniform distribution [0, max] random 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(extra_params, params.max_params + extra_params)) {
if (num_params > NumSingleLineFunctionParams) {
param_lines += (num_params + MaxParamsPerLine - 1) / MaxParamsPerLine;
}
}
return 1.0 + static_cast<double>(param_lines) / (params.max_params + 1);
}
// See `EstimateAvgFunctionDeclLines` for the meaning of `extra_params`.
static auto EstimateAvgMethodDeclLines(SourceGen::MethodDeclParams params,
int extra_params = 0) -> double {
// Currently model a uniform distribution [0, max] random parameters. Assume
// a line break before the first parameter for >2 and after every 4th slot,
// where a Carbon method's `self` takes the first slot. C++ methods have no
// `self`, so this slightly overestimates their lines.
int param_lines = 0;
for (int num_params :
llvm::seq_inclusive(extra_params, params.max_params + extra_params)) {
if (num_params > NumSingleLineMethodParams) {
param_lines += 1 + num_params / MaxParamsPerLine;
}
}
return 1.0 + static_cast<double>(param_lines) / (params.max_params + 1);
}
// Estimates the average number of lines in an inline function definition,
// excluding its comment. The definition has an accumulator line, a line reading
// the parameters, its plan's lines, a return line, and a closing brace line.
static auto EstimateAvgInlineFunctionDefLines(
const SourceGen::DenseDeclParams& dense_params) -> double {
const SourceGen::ClassParams& params = dense_params.class_params;
constexpr int ExtraParams = 1;
return EstimateAvgFunctionDeclLines(params.inline_function_decl_params,
ExtraParams) +
4.0 +
EstimateAvgDefPlanLines(
params.inline_function_def_sizes,
/*calls=*/dense_params.free_function_decls_per_class > 0);
}
// Note that this should match the heuristics used when formatting.
// TODO: See top-level TODO about line estimates and formatting.
//
// This includes each class's share of the free function declarations, whose
// number is proportional to the number of classes.
static auto EstimateAvgClassDefLines(
const SourceGen::DenseDeclParams& dense_params) -> double {
const SourceGen::ClassParams& params = dense_params.class_params;
bool define_decls_out_of_line = dense_params.define_decls_out_of_line;
// Comment line, and class open line.
double avg = 2.0;
// Each class's share of the free function declarations, which each have a
// blank line and a comment line.
avg += dense_params.free_function_decls_per_class *
(2.0 +
EstimateAvgFunctionDeclLines(dense_params.free_function_decl_params));
// Declarations with out-of-line definitions have an extra parameter.
int decl_extra_params = define_decls_out_of_line ? 1 : 0;
// One comment line and blank line per function, plus the function lines.
avg += (2.0 + EstimateAvgFunctionDeclLines(params.public_function_decl_params,
decl_extra_params)) *
params.public_function_decls;
avg += (2.0 + EstimateAvgMethodDeclLines(params.public_method_decl_params,
decl_extra_params)) *
params.public_method_decls;
avg += (2.0 + EstimateAvgFunctionDeclLines(
params.private_function_decl_params, decl_extra_params)) *
params.private_function_decls;
avg += (2.0 + EstimateAvgMethodDeclLines(params.private_method_decl_params,
decl_extra_params)) *
params.private_method_decls;
avg += (2.0 + EstimateAvgInlineFunctionDefLines(dense_params)) *
params.inline_function_defs;
// Getters and predicates are on one line.
avg += 3.0 * (params.inline_getters + params.inline_predicates);
// A forwarder's definition has a return line and a closing brace line, and
// the declaration it calls has the same signature.
double forwarder_signature_lines =
EstimateAvgMethodDeclLines(params.inline_forwarder_params);
avg += (2.0 + forwarder_signature_lines + 2.0 + 2.0 +
forwarder_signature_lines) *
params.inline_forwarders;
bool has_defs = NumInlineDefsPerClass(params) > 0 || define_decls_out_of_line;
// A blank line and all the fields (if any), including `tag` when the file has
// definitions.
double num_fields = params.private_field_decls + params.inline_getters +
params.inline_predicates + (has_defs ? 1.0 : 0.0);
if (num_fields > 0) {
avg += 1.0 + num_fields;
}
// `Make` and `Checksum` each have a blank line, a comment line, a signature
// line, a return line, and a closing brace line. A C++ constructor puts its
// initializers and empty definition on one line, and the class begins with a
// `Self` alias.
if (has_defs) {
avg += 10.0;
}
// Each out-of-line definition has a blank line, a comment line, a signature
// line, an accumulator line, a line reading the parameters, its plan's lines,
// a return line, and a closing brace line.
if (define_decls_out_of_line) {
avg +=
(7.0 + EstimateAvgDefPlanLines(
dense_params.out_of_line_def_sizes,
/*calls=*/dense_params.free_function_decls_per_class > 0)) *
NumDeclsPerClass(params);
}
// 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);
CARBON_CHECK(target_lines > NumFileCommentLines + avg_class_lines,
"Not enough target lines to generate a single class!");
// Round to the nearest whole class. Truncating can leave the file nearly a
// whole class short of the target, which matters when classes with
// definitions run to hundreds of lines.
int num_classes =
std::lround((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 "
<< (params.define_decls_out_of_line
? "an implementation file with out-of-line definitions"
: "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);
GenerateFreeFunctionDecls(class_gen_state, source);
llvm::SmallVector<OutOfLineDef, 0> out_of_line_defs;
for (auto _ : llvm::seq(num_classes)) {
source << "\n";
GenerateClassDef(params.class_params, class_gen_state, source,
out_of_line_defs);
}
// Out-of-line definitions follow every class, as in an implementation file,
// where all of the API's declarations are visible.
for (const OutOfLineDef& def : out_of_line_defs) {
source << "\n";
GenerateOutOfLineDef(class_gen_state, def.class_name, def.sig, 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.inline_function_param_counts().empty());
CARBON_CHECK(class_gen_state.call_refs().empty());
CARBON_CHECK(class_gen_state.destructure_call_refs().empty());
CARBON_CHECK(class_gen_state.forwarder_param_counts().empty());
CARBON_CHECK(class_gen_state.getter_field_types().empty());
CARBON_CHECK(class_gen_state.predicate_fields().empty());
CARBON_CHECK(class_gen_state.class_names().empty());
CARBON_CHECK(class_gen_state.type_pools_empty());
// The identifier lengths in each name pool don't depend on the seed, so
// emitting every name keeps the byte count seed-independent.
CARBON_CHECK(class_gen_state.decl_names().empty());
CARBON_CHECK(class_gen_state.field_names().empty());
CARBON_CHECK(class_gen_state.param_names().empty());
CARBON_CHECK(class_gen_state.free_param_names().empty());
CARBON_CHECK(class_gen_state.inline_function_names().empty());
CARBON_CHECK(class_gen_state.inline_param_names().empty());
CARBON_CHECK(class_gen_state.local_names().empty());
CARBON_CHECK(class_gen_state.accessed_field_names().empty());
CARBON_CHECK(class_gen_state.forwarder_names().empty());
CARBON_CHECK(class_gen_state.forwarder_param_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 (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", "catch", "do", "double", "float", "goto", "int",
"long", "new", "operator", "signed", "std", "this", "throw",
"try", "typename", "unix", "unsigned", "using", "xor", "M_E",
"M_El", "M_PI", "NAN", "NULL",
};
// Names that generated code declares itself, which random identifiers must
// avoid. For example, an inline function named `Make` would collide with its
// class's `Make` function.
static constexpr llvm::StringRef ReservedGeneratedNames[] = {
"Make", "Checksum", "acc", "acc64", "tag"};
// 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) ||
llvm::is_contained(ReservedGeneratedNames, ident) ||
ident.ends_with("Impl") || 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 (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.
for (llvm::StringRef ident : unique_idents.entries()) {
if (number == 0) {
break;
}
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. For a uniform
// distribution every length has weight one, so the sum is simply the number
// of lengths; this also avoids indexing the bounded `IdentifierLengthCounts`
// table, which only covers lengths up to 64 and which uniform callers are
// allowed to exceed.
int num_lengths = max_length - min_length + 1;
int count_sum = uniform ? num_lengths
: Sum(llvm::ArrayRef(IdentifierLengthCounts)
.slice(min_length - 1, num_lengths));
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:
// The popper never returns an identifier in `excluded`. `excluded` isn't
// copied, so it must outlive the popper and not change while it's in use.
explicit UniqueIdentifierPopper(
SourceGen& gen, llvm::SmallVectorImpl<llvm::StringRef>& data,
const Set<llvm::StringRef>* excluded = nullptr)
: gen_(&gen), data_(&data), it_(data_->rbegin()), excluded_(excluded) {}
// The identifiers this popper has returned.
auto used() const -> const Set<llvm::StringRef>& { return set_; }
// Prevents this popper from returning any of `names`, which are copied.
auto Reserve(const Set<llvm::StringRef>& names) -> void {
for (llvm::StringRef name : names.entries()) {
set_.Insert(name);
}
}
// 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_) {
if (excluded_ && excluded_->Contains(*it_)) {
continue;
}
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 (excluded_ && excluded_->Contains(fallback_id)) {
continue;
}
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_;
const Set<llvm::StringRef>* excluded_;
Set<llvm::StringRef> set_;
};
// Emits a parameter list, wrapping it as declarations do. Carbon methods begin
// with `self`.
auto SourceGen::EmitParams(bool is_method, llvm::ArrayRef<TypedName> params,
llvm::StringRef indent, llvm::raw_ostream& os)
-> void {
if (static_cast<int>(params.size()) >
(is_method ? NumSingleLineMethodParams : NumSingleLineFunctionParams)) {
os << "\n" << indent << " ";
}
// For Carbon methods, `self` is the first explicit parameter. Its type is
// omitted, which defaults it to `Self`.
bool is_carbon_method = is_method && !IsCpp();
if (is_carbon_method) {
os << "self";
}
for (auto [i, param] : llvm::enumerate(params)) {
// `self` occupies the first slot for Carbon methods, so shift the index
// used for separators and line wrapping.
int slot = static_cast<int>(i) + (is_carbon_method ? 1 : 0);
if (slot > 0) {
if ((slot % MaxParamsPerLine) == 0) {
os << ",\n" << indent << " ";
} else {
os << ", ";
}
}
if (!IsCpp()) {
os << param.name << ": " << param.type;
} else {
os << param.type << " " << param.name;
}
}
}
// Emits a function declaration with the given signature.
auto SourceGen::EmitFunctionDecl(llvm::StringRef name, bool is_private,
bool is_method,
llvm::ArrayRef<TypedName> params,
llvm::StringRef return_type,
llvm::StringRef indent, llvm::raw_ostream& os)
-> void {
os << indent << "// TODO: make better comment text\n";
if (!IsCpp()) {
os << indent << (is_private ? "private " : "") << "fn " << name;
} else {
os << indent;
if (!is_method) {
os << "static ";
}
os << "auto " << name;
}
os << "(";
EmitParams(is_method, params, indent, os);
os << ") -> " << return_type << ";\n";
}
// Generates the file's free function declarations.
auto SourceGen::GenerateFreeFunctionDecls(ClassGenState& state,
llvm::raw_ostream& os) -> void {
for (int i : state.free_emission_order()) {
const ClassGenState::FreeFunction& fn = state.free_functions()[i];
UniqueIdentifierPopper unique_param_names(*this, state.free_param_names(),
&state.file_scope_name_set());
llvm::SmallVector<TypedName> params;
params.reserve(fn.param_types.size());
for (llvm::StringRef type : fn.param_types) {
params.push_back({.name = unique_param_names.Pop(), .type = type});
}
os << "\n// TODO: make better comment text\n";
os << (IsCpp() ? "auto " : "fn ") << fn.name << "(";
EmitParams(/*is_method=*/false, params, /*indent=*/"", os);
os << ") -> " << fn.return_type << ";\n";
}
}
// 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, and how indented it is. Its
// parameter names and types come from `state`. When `captured` is not null, the
// declaration has an out-of-line definition, and records its signature in
// `captured`.
auto SourceGen::GenerateFunctionDecl(ClassGenState& state, llvm::StringRef name,
bool is_private, bool is_method,
int param_count, llvm::StringRef indent,
llvm::raw_ostream& os,
FunctionSig* captured) -> void {
bool out_of_line = captured != nullptr;
// Add the extra parameter; see `BuildClassAndTypeNames`.
if (out_of_line) {
param_count += 1;
}
// Exclude class and free function names so that a parameter can't shadow one
// that an out-of-line definition names.
UniqueIdentifierPopper unique_param_names(*this, state.param_names(),
&state.file_scope_name_set());
FunctionSig sig = {.name = name, .is_method = is_method};
sig.params.reserve(param_count);
sig.param_consumers.reserve(param_count);
for (auto _ : llvm::seq(param_count)) {
llvm::StringRef param = unique_param_names.Pop();
ClassGenState::TypeUse type =
out_of_line ? state.GetOutOfLineParamType() : state.GetDeclType();
sig.params.push_back({.name = param, .type = type.name});
sig.param_consumers.push_back(type.consumer);
}
ClassGenState::TypeUse return_type =
out_of_line ? state.GetOutOfLineReturnType() : state.GetDeclType();
sig.return_type = return_type.name;
sig.def_plan = return_type.index;
EmitFunctionDecl(name, is_private, is_method, sig.params, sig.return_type,
indent, os);
if (captured) {
*captured = std::move(sig);
}
}
// Emits `format` with `name` substituted for its `{0}`.
static auto EmitTemplate(llvm::StringRef format, llvm::StringRef name,
llvm::raw_ostream& os) -> void {
CARBON_CHECK(!format.empty());
auto [prefix, suffix] = format.split("{0}");
os << prefix << name << suffix;
}
// A step of an arranged definition. A step that opens blocks opens them at
// `first_block` and, for a second block, the index after it.
struct ArrangedStep {
PlanStep plan;
int first_block = -1;
};
// A block of an arranged definition.
struct ArrangedBlock {
// Indices into `ArrangedDef::steps`.
llvm::SmallVector<int> steps;
bool is_loop_block = false;
};
// A definition plan's steps, arranged in blocks. The first block is the
// definition's top-level block.
struct ArrangedDef {
llvm::SmallVector<ArrangedStep> steps;
llvm::SmallVector<ArrangedBlock> blocks;
};
// Randomly arranges the steps of `plan`, along with a `ReadParams` step at
// depth 0. Each block gets at least one step, and ends with a step that isn't
// a local declaration, so that the step after each local declaration can read
// its locals. Guard exits go in distinct loop blocks.
//
// The steps beyond the first in each block go in a block chosen with
// probability proportional to its steps so far, counting loop blocks
// `LoopBlockWeight` times. This gives a wider spread of block sizes than
// choosing uniformly, and larger loop blocks, as in real definitions.
static auto ArrangeDef(const DefPlan& plan, absl::BitGen& rng) -> ArrangedDef {
ArrangedDef def;
def.blocks.emplace_back();
llvm::SmallVector<int> level_blocks = {0};
int num_depths = std::max<int>(plan.levels.size(), 1);
for (int depth : llvm::seq(num_depths)) {
llvm::SmallVector<PlanStep> exits;
llvm::SmallVector<PlanStep> others;
llvm::SmallVector<PlanStep> locals;
if (depth < static_cast<int>(plan.levels.size())) {
for (PlanStep step : plan.levels[depth]) {
switch (step.kind) {
case PlanStepKind::GuardExit:
exits.push_back(step);
break;
default:
(IsLocalDeclaration(step.kind) ? locals : others).push_back(step);
break;
}
}
}
if (depth == 0) {
others.push_back({.kind = PlanStepKind::ReadParams, .spelling_index = 0});
}
std::shuffle(others.begin(), others.end(), rng);
// The block of each step placed at this depth.
llvm::SmallVector<int> placed_blocks;
auto place = [&](int block, PlanStep step) {
def.blocks[block].steps.push_back(def.steps.size());
def.steps.push_back({.plan = step});
placed_blocks.push_back(block);
};
llvm::SmallVector<int> loop_blocks;
for (int block : level_blocks) {
if (def.blocks[block].is_loop_block) {
loop_blocks.push_back(block);
}
}
CARBON_CHECK(exits.size() <= loop_blocks.size());
std::shuffle(loop_blocks.begin(), loop_blocks.end(), rng);
for (auto [block, exit] : llvm::zip(loop_blocks, exits)) {
place(block, exit);
}
for (int block : level_blocks) {
if (def.blocks[block].steps.empty()) {
CARBON_CHECK(!others.empty(), "Not enough steps to end blocks.");
place(block, others.pop_back_val());
}
}
for (int block : loop_blocks) {
placed_blocks.append(LoopBlockWeight - 1, block);
}
for (PlanStep step : llvm::concat<PlanStep>(others, locals)) {
int block =
placed_blocks[absl::Uniform<int>(rng, 0, placed_blocks.size())];
place(block, step);
if (def.blocks[block].is_loop_block) {
placed_blocks.append(LoopBlockWeight - 1, block);
}
}
for (int block : level_blocks) {
llvm::SmallVector<int>& steps = def.blocks[block].steps;
std::shuffle(steps.begin(), steps.end(), rng);
auto is_local = [&](int step) {
return IsLocalDeclaration(def.steps[step].plan.kind);
};
if (is_local(steps.back())) {
llvm::SmallVector<int> non_locals;
for (int i : llvm::seq<int>(steps.size())) {
if (!is_local(steps[i])) {
non_locals.push_back(i);
}
}
std::swap(
steps.back(),
steps[non_locals[absl::Uniform<int>(rng, 0, non_locals.size())]]);
}
}
// Open the blocks for the next depth.
llvm::SmallVector<int> next_blocks;
for (int block : level_blocks) {
for (int i : llvm::seq<int>(def.blocks[block].steps.size())) {
ArrangedStep& step = def.steps[def.blocks[block].steps[i]];
int num_blocks = NumPlanBlocks(step.plan.kind);
if (num_blocks == 0) {
continue;
}
step.first_block = def.blocks.size();
for (auto _ : llvm::seq(num_blocks)) {
next_blocks.push_back(def.blocks.size());
def.blocks.push_back(
{.is_loop_block = step.plan.kind == PlanStepKind::While});
}
}
}
level_blocks = std::move(next_blocks);
}
CARBON_CHECK(level_blocks.empty());
return def;
}
// Arranges and emits the steps of `state.def_plans()[def_plan]` after the
// accumulator declarations that every function begins with, indented beyond
// `indent`. Locals take their names from `locals` in turn, the `ReadParams`
// step adds `reads`, `Call` and `DestructureCall` steps draw calls from
// `state`, and guards return `return_value`.
//
// The first operand of each step after a local declaration reads the locals
// that it declares, so that every local is read. Every other operand is a
// random `i32` local in scope, and the target of each update a random `var`
// one.
auto SourceGen::EmitArrangedDef(ClassGenState& state, int def_plan,
llvm::ArrayRef<llvm::StringRef> locals,
llvm::StringRef reads,
llvm::StringRef return_value,
llvm::StringRef indent, llvm::raw_ostream& os)
-> void {
const DefPlan& plan = state.def_plans()[def_plan];
ArrangedDef def = ArrangeDef(plan, rng_);
// The `i32` locals in scope that later steps can update, and the ones that
// they can only read.
llvm::SmallVector<llvm::StringRef> var_scope = {"acc"};
llvm::SmallVector<llvm::StringRef> let_scope;
// An expression reading the locals that the last step declared.
std::string pending = "acc";
int next_local = 0;
int next_class_type_local = 0;
auto pick_local = [&]() {
int index =
absl::Uniform<int>(rng_, 0, var_scope.size() + let_scope.size());
return index < static_cast<int>(var_scope.size())
? var_scope[index]
: let_scope[index - var_scope.size()];
};
auto pick_var = [&]() {
return var_scope[absl::Uniform<int>(rng_, 0, var_scope.size())];
};
auto emit_expr_to = [&](llvm::StringRef format, llvm::raw_ostream& out) {
while (!format.empty()) {
if (format.starts_with("{0}") || format.starts_with("{1}")) {
out << (pending.empty() ? pick_local() : std::exchange(pending, {}));
format = format.drop_front(3);
} else {
out << format.front();
format = format.drop_front();
}
}
};
auto emit_expr = [&](llvm::StringRef format) { emit_expr_to(format, os); };
// Substitutes the locals `a` and `b` into `format`.
auto substitute = [](llvm::StringRef format, llvm::StringRef a,
llvm::StringRef b) {
std::string text = format.str();
for (auto [placeholder, name] : {std::pair{"{a}", a}, {"{b}", b}}) {
text.replace(text.find(placeholder), 3, name);
}
return text;
};
auto emit_destructure = [&](const DestructureForm& form) {
llvm::StringRef a = locals[next_local++];
llvm::StringRef b = locals[next_local++];
emit_expr(substitute(IsCpp() ? form.cpp_decl : form.carbon_decl, a, b));
os << "\n";
auto& scope = form.is_var ? var_scope : let_scope;
if (form.a_is_i32) {
scope.push_back(a);
}
if (form.b_is_i32) {
scope.push_back(b);
}
pending = substitute(IsCpp() ? form.cpp_read : form.carbon_read, a, b);
};
auto emit_cond = [&](int spelling_index) {
emit_expr(PlanConds[spelling_index % std::size(PlanConds)]);
};
auto indent_to = [&](int depth) {
os << indent;
os.indent(2 * depth);
};
// The blocks being emitted, with the index of the next step in each, and the
// sizes of the scopes outside it.
struct Frame {
int block;
int next_step;
int outer_var_scope_size;
int outer_let_scope_size;
};
auto open_block = [&](int block) -> Frame {
return {.block = block,
.next_step = 0,
.outer_var_scope_size = static_cast<int>(var_scope.size()),
.outer_let_scope_size = static_cast<int>(let_scope.size())};
};
llvm::SmallVector<Frame> stack = {open_block(0)};
while (!stack.empty()) {
Frame& frame = stack.back();
int depth = stack.size();
const ArrangedBlock& block = def.blocks[frame.block];
if (frame.next_step == static_cast<int>(block.steps.size())) {
// End the block, and start the second block of an `else` or `else if`.
var_scope.truncate(frame.outer_var_scope_size);
let_scope.truncate(frame.outer_let_scope_size);
int ended = frame.block;
stack.pop_back();
if (stack.empty()) {
break;
}
const Frame& owner_frame = stack.back();
const ArrangedStep& owner =
def.steps[def.blocks[owner_frame.block]
.steps[owner_frame.next_step - 1]];
indent_to(depth - 1);
if (NumPlanBlocks(owner.plan.kind) == 1 || ended != owner.first_block) {
os << "}\n";
continue;
}
if (owner.plan.kind == PlanStepKind::IfElse) {
os << "} else {\n";
} else {
os << "} else if (";
emit_cond(owner.plan.spelling_index + 1);
os << ") {\n";
}
stack.push_back(open_block(ended + 1));
continue;
}
const ArrangedStep& step = def.steps[block.steps[frame.next_step]];
++frame.next_step;
PlanStep planned = step.plan;
indent_to(depth);
switch (planned.kind) {
case PlanStepKind::ScalarLocal: {
llvm::StringRef name = locals[next_local++];
os << (IsCpp() ? "int " : "var ") << name
<< (IsCpp() ? " = " : ": i32 = ");
emit_expr(PlanExprs[planned.spelling_index]);
os << ";\n";
var_scope.push_back(name);
pending = name;
break;
}
case PlanStepKind::ClassTypeLocal: {
llvm::StringRef name = locals[next_local++];
llvm::StringRef type =
plan.class_type_local_types[next_class_type_local++];
if (type.empty()) {
os << (IsCpp() ? "Self " : "var ") << name
<< (IsCpp() ? " = Self();\n" : ": Self = Make();\n");
} else if (IsCpp()) {
os << type << " " << name << " = " << type << "();\n";
} else {
os << "var " << name << ": " << type << " = " << type << ".Make();\n";
}
RawStringOstream read;
emit_expr_to(("({0} + " + name + ".Checksum())").str(), read);
pending = read.TakeStr();
break;
}
case PlanStepKind::Destructure:
emit_destructure(DestructureForms[planned.spelling_index]);
break;
case PlanStepKind::DestructureCall: {
std::string call;
const CallDestructureForm* form = state.NextDestructureCall(call);
if (!form) {
emit_destructure(DestructureForms[planned.spelling_index]);
break;
}
llvm::StringRef a = locals[next_local++];
llvm::StringRef b = locals[next_local++];
if (IsCpp()) {
os << "const auto [" << a << ", " << b << "]";
} else {
os << substitute(form->carbon_pattern, a, b);
}
os << " = " << call << ";\n";
RawStringOstream read;
emit_expr_to(
substitute(IsCpp() ? form->cpp_read : form->carbon_read, a, b),
read);
if (form->a_is_i32) {
let_scope.push_back(a);
}
if (form->b_is_i32) {
let_scope.push_back(b);
}
pending = read.TakeStr();
break;
}
case PlanStepKind::Update:
os << pick_var() << " = ";
emit_expr(PlanExprs[planned.spelling_index]);
os << ";\n";
break;
case PlanStepKind::ReadParams:
os << pick_var() << " = ";
emit_expr("{0}");
os << " + " << reads << ";\n";
break;
case PlanStepKind::Call:
os << pick_var() << " = ";
emit_expr("{0}");
os << " + ";
state.EmitCall(os);
os << ";\n";
break;
case PlanStepKind::If:
case PlanStepKind::IfElse:
case PlanStepKind::ElseIf:
case PlanStepKind::While:
os << (planned.kind == PlanStepKind::While ? "while (" : "if (");
emit_cond(planned.spelling_index);
os << ") {\n";
stack.push_back(open_block(step.first_block));
break;
case PlanStepKind::GuardReturn:
case PlanStepKind::GuardExit:
os << "if (";
emit_cond(planned.spelling_index);
os << ") {\n";
indent_to(depth + 1);
if (planned.kind == PlanStepKind::GuardReturn) {
os << "return " << return_value << ";\n";
} else {
os << (planned.spelling_index % 2 == 0 ? "break;\n" : "continue;\n");
}
indent_to(depth);
os << "}\n";
break;
}
}
}
// Generates the statements of a plan-based function or method definition (used
// for both `inline_function_defs` and out-of-line definitions) with signature
// `sig`, from its `acc` declaration through its `return`, indented beyond
// `indent`.
//
// Unlike the single-expression member idioms (`GenerateGetter`,
// `GeneratePredicate`, and `GenerateForwarder`), which have fixed signatures
// coupled to a class field or private `<name>Impl` declaration, a plan-based
// definition works with any `FunctionSig`: after declaring `acc`, it arranges
// its `DefPlan` steps randomly along with a depth-0 step that reads every
// parameter with its consumer template (and `self` in a method with
// `Checksum`), and ends with `return`. That parameter-read statement is on one
// line however long it is, which the line estimates assume.
//
// Both inline and out-of-line definitions spell their enclosing class `Self`
// (represented as the empty string) in class-typed locals. For other class
// types, inline definitions only use classes defined prior to the enclosing
// class, whereas out-of-line definitions follow every class and can use any
// other class; see `GetValidReturnType`.
//
// A definition with calls or destructured calls first declares `acc64`, which
// pointer arguments can point at along with `acc`, and initializes `acc` from
// it so that it is read.
auto SourceGen::GenerateFunctionDef(ClassGenState& state,
const FunctionSig& sig,
llvm::StringRef indent,
llvm::raw_ostream& os) -> void {
const DefPlan& plan = state.def_plans()[sig.def_plan];
if (plan.calls + plan.destructure_calls == 0) {
os << indent << (IsCpp() ? " int acc = 0;\n" : " var acc: i32 = 0;\n");
} else if (!IsCpp()) {
os << indent << " var acc64: i64 = 0;\n";
os << indent << " var acc: i32 = acc64 as i32;\n";
} else {
os << indent << " std::int64_t acc64 = 0;\n";
os << indent << " int acc = static_cast<int>(acc64);\n";
}
RawStringOstream reads;
llvm::ListSeparator read_sep(" + ");
if (sig.is_method) {
reads << read_sep << (IsCpp() ? "Checksum()" : "self.Checksum()");
}
for (auto [param, consumer] : llvm::zip(sig.params, sig.param_consumers)) {
reads << read_sep;
EmitTemplate(consumer, param.name, reads);
}
// Locals and parameters draw from the same identifiers of each length, so
// exclude this function's parameter names: C++ rejects a local that
// has the same name as a parameter.
Set<llvm::StringRef> param_names;
for (const TypedName& param : sig.params) {
param_names.Insert(param.name);
}
UniqueIdentifierPopper unique_local_names(*this, state.local_names(),
&param_names);
llvm::SmallVector<llvm::StringRef> locals;
locals.reserve(plan.locals);
for (auto _ : llvm::seq(plan.locals)) {
locals.push_back(unique_local_names.Pop());
}
RawStringOstream return_value;
state.ProduceValue(sig.return_type, return_value);
std::string return_value_str = return_value.TakeStr();
EmitArrangedDef(state, sig.def_plan, locals, reads.TakeStr(),
return_value_str, indent, os);
os << indent << " return " << return_value_str << ";\n";
}
// Generates an out-of-line definition for the declaration `sig`, and writes it
// to the provided stream. The signature is on one line however long it is,
// which the line estimates assume.
auto SourceGen::GenerateOutOfLineDef(ClassGenState& state,
llvm::StringRef class_name,
const FunctionSig& sig,
llvm::raw_ostream& os) -> void {
os << "// TODO: make better comment text\n";
if (!IsCpp()) {
os << "fn " << class_name << "." << sig.name;
} else {
os << "auto " << class_name << "::" << sig.name;
}
os << "(";
// For Carbon methods, `self` is the first parameter, as in the declaration.
bool is_carbon_method = sig.is_method && !IsCpp();
if (is_carbon_method) {
os << "self";
}
bool first_param = !is_carbon_method;
for (const TypedName& param : sig.params) {
if (!first_param) {
os << ", ";
}
first_param = false;
if (!IsCpp()) {
os << param.name << ": " << param.type;
} else {
os << param.type << " " << param.name;
}
}
os << ") -> " << sig.return_type << " {\n";
GenerateFunctionDef(state, sig, /*indent=*/"", os);
os << "}\n";
}
// Generates an inline function definition and writes it to the provided
// stream. Its statements follow the plan paired with its return type; see
// `GenerateFunctionDef`.
auto SourceGen::GenerateInlineFunctionDef(ClassGenState& state,
llvm::StringRef name, int param_count,
llvm::StringRef indent,
llvm::raw_ostream& os) -> void {
// Add the extra parameter; see `BuildClassAndTypeNames`.
param_count += 1;
// Exclude class and free function names so that a parameter can't shadow one
// that the definition names.
UniqueIdentifierPopper unique_param_names(*this, state.inline_param_names(),
&state.file_scope_name_set());
FunctionSig sig = {.name = name, .is_method = false};
sig.params.reserve(param_count);
sig.param_consumers.reserve(param_count);
for (auto _ : llvm::seq(param_count)) {
llvm::StringRef param = unique_param_names.Pop();
ClassGenState::TypeUse type = state.GetInlineParamType();
sig.params.push_back({.name = param, .type = type.name});
sig.param_consumers.push_back(type.consumer);
}
ClassGenState::TypeUse return_type = state.GetInlineReturnType();
sig.return_type = return_type.name;
sig.def_plan = state.inline_def_plans_start() + return_type.index;
os << indent << "// TODO: make better comment text\n";
os << indent << (IsCpp() ? "static auto " : "fn ") << name << "(";
EmitParams(/*is_method=*/false, sig.params, indent, os);
os << ") -> " << sig.return_type << " {\n";
GenerateFunctionDef(state, sig, indent, os);
os << indent << "}\n";
}
// Generates an inline method that returns `field`.
auto SourceGen::GenerateGetter(llvm::StringRef name, TypedName field,
llvm::raw_ostream& os) -> void {
os << " // TODO: make better comment text\n";
if (!IsCpp()) {
os << " fn " << name << "(self) -> " << field.type << " { return self."
<< field.name << "; }\n";
} else {
os << " auto " << name << "() const -> " << field.type << " { return "
<< field.name << "; }\n";
}
}
// Generates an inline method that returns the `predicate` template applied to
// `field`.
auto SourceGen::GeneratePredicate(llvm::StringRef name, TypedName field,
llvm::StringRef predicate,
llvm::raw_ostream& os) -> void {
os << " // TODO: make better comment text\n";
if (!IsCpp()) {
os << " fn " << name << "(self) -> bool { return ";
EmitTemplate(predicate, ("self." + field.name).str(), os);
} else {
os << " auto " << name << "() const -> bool { return ";
EmitTemplate(predicate, field.name, os);
}
os << "; }\n";
}
// Generates an inline method that passes its parameters on to `<name>Impl`, a
// private method with the same signature.
auto SourceGen::GenerateForwarder(llvm::StringRef name,
llvm::ArrayRef<TypedName> params,
llvm::StringRef return_type,
llvm::raw_ostream& os) -> void {
os << " // TODO: make better comment text\n";
os << " " << (IsCpp() ? "auto " : "fn ") << name << "(";
EmitParams(/*is_method=*/true, params, /*indent=*/" ", os);
os << ") -> " << return_type << " {\n";
os << " return " << (IsCpp() ? "" : "self.") << name << "Impl(";
llvm::ListSeparator sep;
for (const TypedName& param : params) {
os << sep << param.name;
}
os << ");\n }\n";
}
// Generates a class's `Make` function, which returns a value of the class with
// every field initialized. C++ classes idiomatically initialize with a
// constructor, so C++ gets a default constructor with the same initializers. A
// class-typed field calls an earlier class's `Make`, so these calls never
// recurse.
auto SourceGen::GenerateMakeFunction(ClassGenState& state,
llvm::StringRef class_name,
llvm::ArrayRef<TypedName> fields,
llvm::raw_ostream& os) -> void {
os << " // TODO: make better comment text\n";
if (IsCpp()) {
os << " " << class_name << "()\n";
os << " : tag(0)";
for (const TypedName& field : fields) {
os << ", " << field.name << "(";
state.ProduceValue(field.type, os);
os << ")";
}
os << " {}\n";
return;
}
os << " fn Make() -> " << class_name << " {\n";
os << " return {.tag = 0";
for (const TypedName& field : fields) {
os << ", ." << field.name << " = ";
state.ProduceValue(field.type, os);
}
os << "};\n }\n";
}
// Generates a class's `Checksum` method, which the class consumer templates
// call to read a value of the class. It reads only `tag`, so it is the same in
// every class without needing consumer expressions for the other fields.
auto SourceGen::GenerateChecksumFunction(llvm::raw_ostream& os) -> void {
os << " // TODO: make better comment text\n";
if (!IsCpp()) {
os << " fn Checksum(self) -> i32 {\n";
os << " return self.tag + 1;\n";
} else {
os << " auto Checksum() -> int {\n";
os << " return tag + 1;\n";
}
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. With out-of-line definitions, it appends the
// declarations to define to `out_of_line_defs`.
auto SourceGen::GenerateClassDef(
const ClassParams& params, ClassGenState& state, llvm::raw_ostream& os,
llvm::SmallVectorImpl<OutOfLineDef>& out_of_line_defs) -> 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";
if (state.has_defs()) {
os << " using Self = " << name << ";\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 (auto _ : llvm::seq(params.private_field_decls)) {
field_type_names.push_back(state.GetFieldType().name);
}
// 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);
// Name the getter and predicate fields first, so that member names can avoid
// them.
UniqueIdentifierPopper unique_accessed_names(
*this, state.accessed_field_names(), &state.file_scope_name_set());
llvm::SmallVector<TypedName> getter_fields;
getter_fields.reserve(params.inline_getters);
for (auto _ : llvm::seq(params.inline_getters)) {
getter_fields.push_back(
{.name = unique_accessed_names.Pop(),
.type = state.getter_field_types().pop_back_val()});
}
llvm::SmallVector<std::pair<TypedName, llvm::StringRef>> predicate_fields;
predicate_fields.reserve(params.inline_predicates);
for (auto _ : llvm::seq(params.inline_predicates)) {
ClassGenState::PredicateField field =
state.predicate_fields().pop_back_val();
predicate_fields.push_back(
{{.name = unique_accessed_names.Pop(), .type = field.type},
field.predicate});
}
// Definitions in this class can name any class or free function, so exclude
// their names from member names. Inline function names are too short to be
// one.
UniqueIdentifierPopper unique_member_names(*this, state.decl_names(),
&state.file_scope_name_set());
unique_member_names.Reserve(unique_accessed_names.used());
UniqueIdentifierPopper unique_inline_names(*this,
state.inline_function_names());
// Capture each declaration's signature for its out-of-line definition.
bool define_out_of_line = state.define_decls_out_of_line();
auto capture_sig = [&]() -> FunctionSig* {
if (!define_out_of_line) {
return nullptr;
}
return &out_of_line_defs.emplace_back(OutOfLineDef{.class_name = name}).sig;
};
llvm::ListSeparator line_sep("\n");
for (auto _ : llvm::seq(params.public_function_decls)) {
os << line_sep;
GenerateFunctionDecl(state, unique_member_names.Pop(), /*is_private=*/false,
/*is_method=*/false,
state.public_function_param_counts().pop_back_val(),
/*indent=*/" ", os, capture_sig());
}
for (auto _ : llvm::seq(params.inline_function_defs)) {
os << line_sep;
GenerateInlineFunctionDef(
state, unique_inline_names.Pop(),
state.inline_function_param_counts().pop_back_val(), /*indent=*/" ",
os);
}
for (auto _ : llvm::seq(params.public_method_decls)) {
os << line_sep;
GenerateFunctionDecl(state, unique_member_names.Pop(), /*is_private=*/false,
/*is_method=*/true,
state.public_method_param_counts().pop_back_val(),
/*indent=*/" ", os, capture_sig());
}
for (TypedName field : getter_fields) {
os << line_sep;
GenerateGetter(unique_inline_names.Pop(), field, os);
}
for (auto [field, predicate] : predicate_fields) {
os << line_sep;
GeneratePredicate(unique_inline_names.Pop(), field, predicate, os);
}
// Forwarder names are as short as other inline function names, so exclude
// those.
UniqueIdentifierPopper unique_forwarder_names(*this, state.forwarder_names());
unique_forwarder_names.Reserve(unique_inline_names.used());
struct Forwarder {
llvm::StringRef name;
llvm::SmallVector<TypedName> params;
llvm::StringRef return_type;
};
llvm::SmallVector<Forwarder> forwarders;
forwarders.reserve(params.inline_forwarders);
for (auto _ : llvm::seq(params.inline_forwarders)) {
Forwarder& forwarder = forwarders.emplace_back();
forwarder.name = unique_forwarder_names.Pop();
// Exclude class and free function names so that a parameter can't shadow a
// class in the signature.
UniqueIdentifierPopper unique_param_names(
*this, state.forwarder_param_names(), &state.file_scope_name_set());
for (auto _ : llvm::seq(state.forwarder_param_counts().pop_back_val())) {
forwarder.params.push_back({.name = unique_param_names.Pop(),
.type = state.GetForwardType().name});
}
forwarder.return_type = state.GetForwardType().name;
os << line_sep;
GenerateForwarder(forwarder.name, forwarder.params, forwarder.return_type,
os);
}
if (IsCpp()) {
os << "\n private:\n";
// Reset the separator.
line_sep = llvm::ListSeparator("\n");
}
for (auto _ : llvm::seq(params.private_function_decls)) {
os << line_sep;
GenerateFunctionDecl(state, unique_member_names.Pop(), /*is_private=*/true,
/*is_method=*/false,
state.private_function_param_counts().pop_back_val(),
/*indent=*/" ", os, capture_sig());
}
for (auto _ : llvm::seq(params.private_method_decls)) {
os << line_sep;
GenerateFunctionDecl(state, unique_member_names.Pop(), /*is_private=*/true,
/*is_method=*/true,
state.private_method_param_counts().pop_back_val(),
/*indent=*/" ", os, capture_sig());
}
for (const Forwarder& forwarder : forwarders) {
os << line_sep;
EmitFunctionDecl((forwarder.name + "Impl").str(), /*is_private=*/true,
/*is_method=*/true, forwarder.params,
forwarder.return_type, /*indent=*/" ", os);
}
// Field names can't repeat this class's function, method, or field names, or
// be a class or free function name.
UniqueIdentifierPopper unique_field_names(*this, state.field_names(),
&state.file_scope_name_set());
unique_field_names.Reserve(unique_member_names.used());
llvm::SmallVector<TypedName> fields;
fields.reserve(field_type_names.size() + getter_fields.size() +
predicate_fields.size());
for (llvm::StringRef type_name : field_type_names) {
fields.push_back({.name = unique_field_names.Pop(), .type = type_name});
}
llvm::append_range(fields, getter_fields);
for (auto [field, _] : predicate_fields) {
fields.push_back(field);
}
// With definitions, C++ puts `Make` and `Checksum` in a second public
// section, followed by a private section for the fields.
bool has_defs = state.has_defs();
if (IsCpp() && has_defs) {
os << "\n public:\n";
line_sep = llvm::ListSeparator("\n");
}
// Only definitions call `Make` and `Checksum`.
if (has_defs) {
os << line_sep;
GenerateMakeFunction(state, name, fields, os);
os << line_sep;
GenerateChecksumFunction(os);
if (IsCpp()) {
os << "\n private:\n";
line_sep = llvm::ListSeparator("\n");
}
}
os << line_sep;
// `tag` comes first, matching its position in `Make`.
if (has_defs) {
os << (IsCpp() ? " int tag;\n" : " private var tag: i32;\n");
}
for (const TypedName& field : fields) {
if (!IsCpp()) {
os << " private var " << field.name << ": " << field.type << ";\n";
} else {
os << " " << field.type << " " << field.name << ";\n";
}
}
os << "}" << (IsCpp() ? ";" : "") << "\n";
}
} // namespace Carbon::Testing