Toolchain development
Toolchain structure
- Under
toolchain/:
base/: Base infrastructure and common utilities.
check/: Semantic analysis (SemIR generation).
lex/: Lexing (Source -> Tokens).
lower/: Lowering to LLVM IR.
parse/: Parsing (Token -> Parse Tree).
sem_ir/: Semantic Intermediate Representation
(SemIR) definitions.
Toolchain architecture
- Documentation: Refer to
toolchain/docs for detailed
architecture design and patterns.
- Refer to Toolchain Idioms for a
comprehensive list of patterns (for example,
ValueStore, formatting
.def files, struct reflection) used throughout the implementation.
- Builtin Functions: Refer to the Builtin functions skill
(SKILL.md) for guidelines on registering, mapping,
constant evaluating, and lowering compiler builtin primitives (e.g.
"int.convert_float").
- Phases: Lex -> Parse -> Check -> Lower.
- Definitions: Many kinds (tokens, parse nodes, SemIR instructions) are
defined in
.def files and expanded by way of macros.
- Handlers:
- Parser:
Handle<StateName> in parse/handle_*.cpp.
- Checker:
HandleParseNode in check/handle_*.cpp.
- Lowering:
HandleInst in lower/handle_*.cpp.
- Iteration: Prefer iterative algorithms over recursive ones to prevent
stack exhaustion on complex codebases.
Essential commands
- Test everything:
bazelisk test //...
- Test specific target:
bazelisk test //toolchain/testing:file_test
- Test specific file:
bazelisk test //toolchain/testing:file_test --test_arg=--file_tests=<path_to_carbon_file>
- Build toolchain:
bazelisk build //toolchain/...
Updating test data
Carbon tests often use file_test (for example,
//toolchain/testing/file_test). For detailed guidelines on authoring tests,
including file splits, naming conventions (fail_, todo_), and generating
minimal output with SemIR dumps, please refer to the Toolchain tests skill.
If you change compiler behavior, you likely need to update expected test
outputs. Do not manually edit thousands of lines of expected output. Use the
script:
./toolchain/autoupdate_testdata.py
# Or for a specific file:
./toolchain/autoupdate_testdata.py toolchain/check/testdata/my_test.carbon
Debugging and diagnostics
- Compiler Diagnostics: Refer to the Diagnostics skill
(SKILL.md) for strict rules on declaring,
formatting, emitting, testing, and styling compiler diagnostic messages
(errors, warnings, notes).
- Printing to stderr: Use
llvm::errs() << "debug info\n";.
- Avoid
std::cout (it may interfere with tool output).
- SemIR Stringification:
- SemIR objects often have a
Print method or operator<<.
inst.Print(llvm::errs())
- Debugging Crashes:
- Bazel sandboxing can hide artifacts. Use
--sandbox_debug if needed,
but often running the binary directly from bazel-bin/ is easier for
debugging.
Error handling
- No exceptions: Do not use C++ exceptions.
ErrorOr<T>: Return ErrorOr<T> for fallible operations.
- Check with
if (auto result = Function(); result) { Use(*result); }
llvm::Expected<T>: Similar to ErrorOr, used when interfacing with
LLVM.
Context-Aware Diagnostics
When declaring and emitting errors, ensure semantic wording matches the exact
context:
- Semantic Precision: Do not reference "types" when raising errors for
unsized expressions like
IntLiteral or FloatLiteral. For example, use
RealLiteralTooLargeForUnsizedInt instead of a diagnostic referencing an
"integer type".
- Wording Consistency: Before declaring a new diagnostic in
kind.def, search for existing
diagnostics in the targeted implementation files (for example, other uses of
MaxIntWidth) to align message structures and parameter expectations.
Casting (LLVM style)
- Use
llvm::cast<T>(obj) (checked, asserts on failure).
- Use
llvm::dyn_cast<T>(obj) (returns null on failure).
- Use
llvm::isa<T>(obj) (boolean check).
- Avoid
dynamic_cast and standard RTTI.
Leverage LLVM APIs
Before implementing custom algorithms for mathematical, logical, or bitwise
operations, inspect target LLVM ADT class APIs:
- Builtin APIs: Verify if LLVM classes (such as
APInt, APFloat, or
APSInt) already offer native equivalents (for example, .pow(),
ilogb(), .changeSign(), convertFromAPInt()). Avoid duplicate, naive,
or inefficient custom loops.
Data structures
- Prefer APIs in
common/ and toolchain/base/ over LLVM ADTs. For example,
use Map instead of llvm::DenseMap.
- If no Carbon API exists, prefer LLVM ADTs over standard library ones (for
example
llvm::SmallVector, llvm::StringRef).
StringRef is a view; be careful with lifetimes.
Common pitfalls
- Legacy
explorer references: The explorer prototype has been moved.
Ignore references to it in proposals or old docs; focus on toolchain.
- Manually updating test files: Always check if
autoupdate_testdata.py
can do it for you.
- Using
std::string unnecessarily: Prefer llvm::StringRef for
arguments.
- Header includes: Use specific include orders (often enforced by
clang-format).
- Parse node order: Semantics processes parse nodes in post-order; ensure
your parser transitions support this.
- Builtin implementation gaps: If adding a primitive builtin function,
make sure you address all phases of the lifecycle: macro definition
registration, signature validation, compile-time constant evaluation
(interpreter), LLVM IR lowering, and prelude modular implementation bindings
(avoiding orphan rules). Refer to the Builtin functions skill
(SKILL.md) for details.
- Premature helper abstraction: Avoid extracting tiny helper functions
that are called from exactly one place and do not significantly modularize
complex code. Prefer inlining directly to keep the implementation compact,
readable, and localized.
- Redundant bounds calculations: Avoid repeating calculations of complex
boundary estimations (such as lower and upper bound estimations). Refactor
the logic to calculate unified values once, preserving compactness.
1---2name: toolchain-development3description: Instructions for checking, building, debugging, and understanding the Carbon toolchain.4---56# Toolchain development78<!--9Part of the Carbon Language project, under the Apache License v2.0 with LLVM10Exceptions. See /LICENSE for license information.11SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception12-->1314## Toolchain structure1516- Under [`toolchain/`](/toolchain/):17 - [`base/`](/toolchain/base/): Base infrastructure and common utilities.18 - [`check/`](/toolchain/check/): Semantic analysis (SemIR generation).19 - [`lex/`](/toolchain/lex/): Lexing (Source -> Tokens).20 - [`lower/`](/toolchain/lower/): Lowering to LLVM IR.21 - [`parse/`](/toolchain/parse/): Parsing (Token -> Parse Tree).22 - [`sem_ir/`](/toolchain/sem_ir/): Semantic Intermediate Representation23 (SemIR) definitions.2425## Toolchain architecture2627- **Documentation**: Refer to [`toolchain/docs`](/toolchain/docs) for detailed28 architecture design and patterns.29 - Refer to [Toolchain Idioms](/toolchain/docs/idioms.md) for a30 comprehensive list of patterns (for example, `ValueStore`, formatting31 `.def` files, struct reflection) used throughout the implementation.32- **Builtin Functions**: Refer to the **Builtin functions** skill33 ([SKILL.md](../builtins/SKILL.md)) for guidelines on registering, mapping,34 constant evaluating, and lowering compiler builtin primitives (e.g.35 `"int.convert_float"`).36- **Phases**: Lex -> Parse -> Check -> Lower.37- **Definitions**: Many kinds (tokens, parse nodes, SemIR instructions) are38 defined in `.def` files and expanded by way of macros.39- **Handlers**:40 - Parser: `Handle<StateName>` in `parse/handle_*.cpp`.41 - Checker: `HandleParseNode` in `check/handle_*.cpp`.42 - Lowering: `HandleInst` in `lower/handle_*.cpp`.43- **Iteration**: Prefer iterative algorithms over recursive ones to prevent44 stack exhaustion on complex codebases.4546### Essential commands4748- **Test everything**: `bazelisk test //...`49- **Test specific target**: `bazelisk test //toolchain/testing:file_test`50- **Test specific file**: `bazelisk test //toolchain/testing:file_test51 --test_arg=--file_tests=<path_to_carbon_file>`52- **Build toolchain**: `bazelisk build //toolchain/...`5354### Updating test data5556Carbon tests often use `file_test` (for example,57`//toolchain/testing/file_test`). For detailed guidelines on authoring tests,58including file splits, naming conventions (`fail_`, `todo_`), and generating59minimal output with SemIR dumps, please refer to the **Toolchain tests** skill.6061If you change compiler behavior, you likely need to update expected test62outputs. **Do not manually edit thousands of lines of expected output.** Use the63script:6465```bash66./toolchain/autoupdate_testdata.py67# Or for a specific file:68./toolchain/autoupdate_testdata.py toolchain/check/testdata/my_test.carbon69```7071## Debugging and diagnostics7273- **Compiler Diagnostics**: Refer to the **Diagnostics** skill74 ([SKILL.md](../diagnostics/SKILL.md)) for strict rules on declaring,75 formatting, emitting, testing, and styling compiler diagnostic messages76 (errors, warnings, notes).77- **Printing to stderr**: Use `llvm::errs() << "debug info\n";`.78 - Avoid `std::cout` (it may interfere with tool output).79- **SemIR Stringification**:80 - SemIR objects often have a `Print` method or `operator<<`.81 - `inst.Print(llvm::errs())`82- **Debugging Crashes**:83 - Bazel sandboxing can hide artifacts. Use `--sandbox_debug` if needed,84 but often running the binary directly from `bazel-bin/` is easier for85 debugging.8687## Error handling8889- **No exceptions**: Do not use C++ exceptions.90- **`ErrorOr<T>`**: Return `ErrorOr<T>` for fallible operations.91 - Check with `if (auto result = Function(); result) { Use(*result); }`92- **`llvm::Expected<T>`**: Similar to `ErrorOr`, used when interfacing with93 LLVM.9495### Context-Aware Diagnostics9697When declaring and emitting errors, ensure semantic wording matches the exact98context:99100- **Semantic Precision**: Do not reference "types" when raising errors for101 unsized expressions like `IntLiteral` or `FloatLiteral`. For example, use102 `RealLiteralTooLargeForUnsizedInt` instead of a diagnostic referencing an103 "integer type".104- **Wording Consistency**: Before declaring a new diagnostic in105 [kind.def](../../../toolchain/diagnostics/kind.def), search for existing106 diagnostics in the targeted implementation files (for example, other uses of107 `MaxIntWidth`) to align message structures and parameter expectations.108109### Casting (LLVM style)110111- Use `llvm::cast<T>(obj)` (checked, asserts on failure).112- Use `llvm::dyn_cast<T>(obj)` (returns null on failure).113- Use `llvm::isa<T>(obj)` (boolean check).114- **Avoid** `dynamic_cast` and standard RTTI.115116### Leverage LLVM APIs117118Before implementing custom algorithms for mathematical, logical, or bitwise119operations, inspect target LLVM ADT class APIs:120121- **Builtin APIs**: Verify if LLVM classes (such as `APInt`, `APFloat`, or122 `APSInt`) already offer native equivalents (for example, `.pow()`,123 `ilogb()`, `.changeSign()`, `convertFromAPInt()`). Avoid duplicate, naive,124 or inefficient custom loops.125126### Data structures127128- Prefer APIs in `common/` and `toolchain/base/` over LLVM ADTs. For example,129 use `Map` instead of `llvm::DenseMap`.130- If no Carbon API exists, prefer LLVM ADTs over standard library ones (for131 example `llvm::SmallVector`, `llvm::StringRef`).132- `StringRef` is a view; be careful with lifetimes.133134## Common pitfalls1351361. **Legacy `explorer` references**: The `explorer` prototype has been moved.137 Ignore references to it in proposals or old docs; focus on `toolchain`.1382. **Manually updating test files**: Always check if `autoupdate_testdata.py`139 can do it for you.1403. **Using `std::string` unnecessarily**: Prefer `llvm::StringRef` for141 arguments.1424. **Header includes**: Use specific include orders (often enforced by143 `clang-format`).1445. **Parse node order**: Semantics processes parse nodes in post-order; ensure145 your parser transitions support this.1466. **Builtin implementation gaps**: If adding a primitive builtin function,147 make sure you address all phases of the lifecycle: macro definition148 registration, signature validation, compile-time constant evaluation149 (interpreter), LLVM IR lowering, and prelude modular implementation bindings150 (avoiding orphan rules). Refer to the **Builtin functions** skill151 ([SKILL.md](../builtins/SKILL.md)) for details.1527. **Premature helper abstraction**: Avoid extracting tiny helper functions153 that are called from exactly one place and do not significantly modularize154 complex code. Prefer inlining directly to keep the implementation compact,155 readable, and localized.1568. **Redundant bounds calculations**: Avoid repeating calculations of complex157 boundary estimations (such as lower and upper bound estimations). Refactor158 the logic to calculate unified values once, preserving compactness.