mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-04 22:02:52 +01:00
This is reducing ValueStore inference of types from `using`, and removes `using ValueType = ...` from affected id types. I'm adding a number of `using FooStore = ValueStore<FooId, Foo>` because I think it's a little repetitive otherwise; often 4 cases where I'm doing this: getter, const getter, member, and getter on `Context`. Note we also have a number of `-> decltype(auto)` that were added I think mainly to avoid repeating the type, but I'm not sure whether there'll be agreement on replacing those and so am not changing them here. I'm placing these aliases with the value type in general, because I think it's probably easier to view that way. An alternative would be to put all the types on `File`, but: - That would be inconsistent with things like `InstStore`, which are very `ValueStore`-adjacent and put with their value type. - `File` would have a _lot_ of using's, and the accessors are already noisy -- I think it would just make the file harder to skim. Note this is the heart of what I'd brought up [on Discord](https://discord.com/channels/655572317891461132/655578254970716160/1388199282250613019). This PR still leaves CanonicalValueStore and BlockValueStore as things to also add parameters to, but I thought it best to try breaking the set of changes apart by type. Both of those rely on ValueStore, so ValueStore needs to change first.
402 lines
13 KiB
Markdown
402 lines
13 KiB
Markdown
# Idioms
|
|
|
|
<!--
|
|
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
|
|
-->
|
|
|
|
<!-- toc -->
|
|
|
|
## Table of contents
|
|
|
|
- [Overview](#overview)
|
|
- [C++ dialect](#c-dialect)
|
|
- [Abbreviations used in the code (AKA Carbon abbreviation decoder ring)](#abbreviations-used-in-the-code-aka-carbon-abbreviation-decoder-ring)
|
|
- [`.def` files](#def-files)
|
|
- [EnumBase types](#enumbase-types)
|
|
- [Index types](#index-types)
|
|
- [ValueStore](#valuestore)
|
|
- [Template metaprogramming](#template-metaprogramming)
|
|
- [Struct reflection](#struct-reflection)
|
|
- [Field detection](#field-detection)
|
|
- [Local lambdas to reduce duplicate code](#local-lambdas-to-reduce-duplicate-code)
|
|
- [Immediately invoked function expressions (IIFE)](#immediately-invoked-function-expressions-iife)
|
|
- [Declarations in conditions](#declarations-in-conditions)
|
|
- [CRTP or "Curiously recurring template pattern"](#crtp-or-curiously-recurring-template-pattern)
|
|
- [Multiple inheritance](#multiple-inheritance)
|
|
- [Defining constants usable in constexpr contexts](#defining-constants-usable-in-constexpr-contexts)
|
|
|
|
<!-- tocstop -->
|
|
|
|
## Overview
|
|
|
|
The toolchain implementation uses some implementation techniques that may not be
|
|
commonly found in typical C++ code.
|
|
|
|
## C++ dialect
|
|
|
|
The toolchain implementation does not use some C++ features, following
|
|
[Google's C++ style guide](https://google.github.io/styleguide/cppguide.html):
|
|
|
|
- [Exceptions](https://google.github.io/styleguide/cppguide.html#Exceptions)
|
|
- [Virtual base classes](https://google.github.io/styleguide/cppguide.html#Inheritance)
|
|
- [RTTI](https://google.github.io/styleguide/cppguide.html#Run-Time_Type_Information__RTTI_)
|
|
|
|
## Abbreviations used in the code (AKA Carbon abbreviation decoder ring)
|
|
|
|
Note that abbreviations are typically only used in code, not comments (except
|
|
when referring to an entity from the code).
|
|
|
|
- **Addr**: "address"
|
|
- **Arg**: "argument"
|
|
- **Decl**: "declaration"
|
|
- **Expr**: "expression"
|
|
- **SubExpr**: "subexpression"
|
|
- **Float**: "floating point"
|
|
- **Init**: "initialization"
|
|
- **Inst**: "instruction"
|
|
- **Int**: "integer"
|
|
- **Loc**: "location"
|
|
- **Param**: "parameter"
|
|
- **Paren**: "parenthesis"
|
|
- **Ref**: "reference"
|
|
- **Deref**: "dereference"
|
|
- **Subst**: "substitute"
|
|
|
|
Phrase abbreviations (where we have an abbreviation for a phrase, where we
|
|
wouldn't perform all of the abbreviations of those words individually):
|
|
|
|
- **InitRepr**: "initializing representation"
|
|
- **ObjectRepr**: "object representation"
|
|
- **SemIR**: "semantics intermediate representation"
|
|
- **ValueRepr**: "value representation"
|
|
|
|
## `.def` files
|
|
|
|
The Carbon toolchain uses a technique related to
|
|
[X-macros](https://en.wikipedia.org/wiki/X_macro) to generate code that operates
|
|
over a collection of types, enumerators, or another similar list of names. This
|
|
works as follows:
|
|
|
|
- A `.def` file is provided, that is intended to be repeatedly included by way
|
|
of `#include`.
|
|
- The user of the `.def` defines a macro, with a name and a form specified by
|
|
the `.def` file, for example
|
|
`#define CARBON_EACH_WIDGET(Name) Scope::Name,`.
|
|
- A `#include` of the `.def` file expands to `CARBON_EACH_WIDGET(Name1)`,
|
|
`CARBON_EACH_WIDGET(Name2)`, ... for each widget name, and then `#undef`s
|
|
the `CARBON_EACH_WIDGET` macro.
|
|
|
|
For example:
|
|
|
|
```cpp
|
|
enum Widgets {
|
|
#define CARBON_EACH_WIDGET(Name) Name,
|
|
#include "widgets.def"
|
|
}
|
|
```
|
|
|
|
... would expand to an enumeration definition with one enumerator per widget
|
|
name.
|
|
|
|
### EnumBase types
|
|
|
|
Most `.def` files will have a corresponding [EnumBase](/common/enum_base.h)
|
|
child class (if `widgets.def` has X-macros, `widgets.h` and `widgets.cpp` has
|
|
the `EnumBase` child class). These work similarly to an `enum class`, with the
|
|
addition of a `name()` function and `<<` stream operator support. Many also have
|
|
further utility functions for information related to the enum value.
|
|
|
|
In code, these types and values can be used directly in a `switch`. They will
|
|
convert to an internal _actual_ `enum class` for the `switch`, and receive
|
|
corresponding compiler safety checks that all enum values are handled.
|
|
|
|
## Index types
|
|
|
|
Carbon makes frequent use of
|
|
[IndexBase and IdBase](/toolchain/base/index_base.h). The `IndexBase` and
|
|
`IdBase` types are small wrappers around `int32_t` to provide a measure of
|
|
type-checking when passing around indices to vector-like storage types. The only
|
|
difference is that `IndexBase` supports all comparison operators, whereas
|
|
`IdBase` only supports equality comparison.
|
|
|
|
Variable naming will often have `_id` at the end to indicate that it corresponds
|
|
to an `IdBase`. This may include the full type, as in `operand_inst_id` being an
|
|
`InstId` for an operand.
|
|
|
|
A block is an array of ids. These will be indicated with either a `_block`
|
|
suffix or pluralization (for example, `param_refs` pluralizing `refs`).
|
|
|
|
The `ref` concept in a name means that there is an underlying instruction block,
|
|
but only a subset of instructions are present in the `refs` block. For example,
|
|
function parameters have a sequence, and also have a `refs` block with one entry
|
|
per parameter. The `refs` block allows parameters to be counted and accessed
|
|
directly, rather than through vector iteration.
|
|
|
|
## ValueStore
|
|
|
|
Many of Carbon's data types are stored in a
|
|
[ValueStore](/toolchain/base/value_store.h) or related type with similar
|
|
semantics (`sem_ir` has [several such classes](/toolchain/base/value_store.h)).
|
|
`ValueStore` links an indexing type to a value type with vector-like storage.
|
|
The indices typically use `IdBase`.
|
|
|
|
`ValueStore`s APIs follow the shape of simple array access and mutation:
|
|
|
|
- `Add` which takes a value and returns the index.
|
|
- `Get` takes an index and returns a reference to the value (possibly a
|
|
constant reference).
|
|
- Other vector-like functionality, including `size` or `Reserve`
|
|
|
|
Each `ValueStore` instance should be named after its value type. When taking a
|
|
return of one of these functions, it's common to use `auto` and rely on the name
|
|
of the storage type to imply the returned type.
|
|
|
|
Some name mirroring examples are:
|
|
|
|
- `ints` is a `ValueStore<IntId, llvm::APInt>`; values are integers.
|
|
- `functions` is a `ValueStore<SemIR::FunctionId, SemIR::Function>`; values
|
|
are functions.
|
|
- `string_literals` is a `ValueStore<StringLiteralValueId, llvm::StringRef>`;
|
|
values are string literals.
|
|
- A reference is used in order to avoid string copies.
|
|
|
|
There are also a number of wrappers around `ValueStore` that provide some
|
|
additional functionality and which are named with the `Store` suffix, such as
|
|
`InstStore` or `CanonicalValueStore`.
|
|
|
|
A fairly complete list of `ValueStore` (and `ValueStore` wrapper) uses should be
|
|
available on [checking's Context class].
|
|
|
|
<!-- google-doc-style-ignore -->
|
|
|
|
[checking's Context class]:
|
|
https://github.com/search?q=repo%3Acarbon-language%2Fcarbon-lang+path%3Atoolchain%2Fcheck%2Fcontext.h+%2F%5Cw%2BStore%2F&type=code
|
|
|
|
<!-- google-doc-style-resume -->
|
|
|
|
## Template metaprogramming
|
|
|
|
TODO: show example patterns
|
|
|
|
- InstLikeTypeInfo from toolchain/sem_ir/inst.h
|
|
- templated using
|
|
- std::declval
|
|
- decltype
|
|
- static_assert
|
|
- if constexpr
|
|
- template specialization, for example `Inst::FromRaw<T>` (maybe also type
|
|
traits?)
|
|
|
|
### Struct reflection
|
|
|
|
The toolchain uses a primitive form of struct reflection to operate generically
|
|
over the fields in a typed `SemIR` instruction. This is implemented in
|
|
`common/struct_reflection.h`, and the interface to the functionality is
|
|
`StructReflection::AsTuple(your_struct)`, which converts the given struct into a
|
|
`std::tuple` containing the same fields in the same order.
|
|
|
|
### Field detection
|
|
|
|
The presence of specific fields in a struct with a specified type is detected
|
|
using the following idiom:
|
|
|
|
```cpp
|
|
// HasField<T> is true if T has a `U field` field of type FieldType.
|
|
template <typename T> concept HasField = requires (T x) {
|
|
{ &T::field } -> std::same_as<FieldType T::*>;
|
|
};
|
|
```
|
|
|
|
See `HasKindMemberAsField` in
|
|
[`toolchain/sem_ir/typed_insts.h`](/toolchain/sem_ir/typed_insts.h) for an
|
|
example.
|
|
|
|
## Local lambdas to reduce duplicate code
|
|
|
|
Sometimes code that would be repeated in a function is factored into a local
|
|
variable containing a
|
|
[lambda](https://en.cppreference.com/w/cpp/language/lambda):
|
|
|
|
```cpp
|
|
auto common_code = [&](AType param1, AnotherType param2) {
|
|
// code that would otherwise be repeated
|
|
...
|
|
}
|
|
if (something) {
|
|
common_code(...);
|
|
}
|
|
if (something_else) {
|
|
common_code(...)
|
|
}
|
|
```
|
|
|
|
Compared to defining a new function, this has the advantage of being able to be
|
|
declared in context and access the local variables of the enclosing function.
|
|
|
|
## Immediately invoked function expressions (IIFE)
|
|
|
|
Instead of creating a separate function with its own name that will be called
|
|
once to produce the initial value for a variable, the function can be declared
|
|
inline and then immediately called.
|
|
|
|
This can be used for complex initialization, as in:
|
|
|
|
```cpp
|
|
// variable declaration
|
|
static const llvm::ArrayRef<std::byte> entropy_bytes =
|
|
// initializer starts with a lambda
|
|
[]() -> llvm::ArrayRef<std::byte> {
|
|
static llvm::SmallVector<std::byte> bytes;
|
|
|
|
// a bunch of code
|
|
|
|
// return the value to initialize the variable with
|
|
return bytes;
|
|
|
|
// finish defining the lambda, and then immediately invoke it
|
|
}();
|
|
```
|
|
|
|
It can also be used inside a `CARBON_DCHECK` to avoid computation that is only
|
|
needed in debug builds:
|
|
|
|
```cpp
|
|
CARBON_DCHECK([&] {
|
|
// a bunch of code
|
|
|
|
// condition that will be tested by CARBON_DCHECK
|
|
return complicated && multiple_parts;
|
|
|
|
// finish defining the lambda, and then immediately invoke it
|
|
}(), "Complicated things went wrong");
|
|
```
|
|
|
|
See a description of this technique on
|
|
[wikipedia](https://en.wikipedia.org/wiki/Immediately_invoked_function_expression).
|
|
|
|
## Declarations in conditions
|
|
|
|
The condition part of an `if` statement may contain a declaration with an
|
|
initializer followed by a semicolon (`;`) and then the proper boolean condition
|
|
expression, as in:
|
|
|
|
```cpp
|
|
if (auto verify = tree.Verify(); !verify.ok()) {
|
|
```
|
|
|
|
The condition can be replaced by a declaration entirely, as in:
|
|
|
|
```cpp
|
|
if (auto equals = context.ConsumeIf(Lex::TokenKind::Equal)) {
|
|
// Equivalent to:
|
|
if (auto equals = context.ConsumeIf(Lex::TokenKind::Equal); equals) {
|
|
```
|
|
|
|
or
|
|
|
|
```cpp
|
|
if (auto literal = bound_inst.TryAs<SemIR::IntegerLiteral>()) {
|
|
// Equivalent to:
|
|
if (auto literal = bound_inst.TryAs<SemIR::IntegerLiteral>(); literal) {
|
|
```
|
|
|
|
This is a common way of handling a function that returns an optional value.
|
|
|
|
See
|
|
[https://en.cppreference.com/w/cpp/language/if](https://en.cppreference.com/w/cpp/language/if)
|
|
|
|
## CRTP or "Curiously recurring template pattern"
|
|
|
|
[Curiously Recurring Template Pattern - cppreference.com](https://en.cppreference.com/w/cpp/language/crtp)
|
|
|
|
[Curiously recurring template pattern - Wikipedia](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern)
|
|
|
|
[Google search](https://www.google.com/search?q=crtp+c%2B%2B)
|
|
|
|
Examples:
|
|
|
|
- `template <typename DerivedT, ...>` in [enum_base.h](/common/enum_base.h)
|
|
- `template <typename DerivedT>` in [ostream.h](/common/ostream.h)
|
|
|
|
## Multiple inheritance
|
|
|
|
We use multiple inheritance to support uses of
|
|
[CRTP](#crtp-or-curiously-recurring-template-pattern).
|
|
|
|
Example:
|
|
|
|
```cpp
|
|
struct NameScopeId : public IndexBase, public Printable<NameScopeId> {
|
|
```
|
|
|
|
## Defining constants usable in constexpr contexts
|
|
|
|
To declare a constant usable at compile time in `constexpr` contexts as a static
|
|
class member, we use this pattern:
|
|
|
|
Declaration:
|
|
|
|
```cpp
|
|
class Foo {
|
|
// ...
|
|
static const std::array<ElementType, ElementCount> MyTable;
|
|
static constexpr auto ComputeMyTable()
|
|
-> std::array<ElementType, ElementCount> { ... }
|
|
};
|
|
```
|
|
|
|
Definition:
|
|
|
|
```cpp
|
|
constexpr std::array<ElementType, ElementCount>
|
|
Foo::MyTable = Foo::ComputeMyTable();
|
|
```
|
|
|
|
Note the `const` on the declaration does not match the `constexpr` on
|
|
definition, and that the definition is outside of the class body. This allows
|
|
the initializer to depend on the definition of the class.
|
|
|
|
Further note that this only works with static members of classes, not static
|
|
variables in functions.
|
|
|
|
Due to [a Clang bug](https://github.com/llvm/llvm-project/issues/85461), this
|
|
technique does not work in a class template. The following pattern can be used
|
|
instead:
|
|
|
|
```cpp
|
|
template <typename T>
|
|
class Foo {
|
|
// ...
|
|
template <typename Self = Foo>
|
|
static constexpr auto MyValueImpl = Self();
|
|
static constexpr const Foo& MyValue = MyValueImpl<>;
|
|
// ...
|
|
};
|
|
```
|
|
|
|
The parameters of the variable template can be chosen to allow reuse of the same
|
|
variable template for multiple static data members.
|
|
|
|
For example, see `NodeStack::IdKindTable` in
|
|
[check/node_stack.h](/toolchain/check/node_stack.h).
|
|
|
|
A global constant may use a single definition without a separate declaration:
|
|
|
|
```cpp
|
|
static constexpr std::array<bool, 256> IsIdStartByteTable = [] {
|
|
std::array<bool, 256> table = {};
|
|
// ...
|
|
return table;
|
|
}();
|
|
```
|
|
|
|
Note this example is using an
|
|
[immediately invoked function expression](#immediately-invoked-function-expressions-iife)
|
|
to compute the initial value, which is common.
|
|
|
|
Examples:
|
|
|
|
- [lex/lex.cpp](/toolchain/lex/lex.cpp)
|