mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 08:41:06 +01:00
Add documentation describing the problem and algorithm for coalescing the LLVM functions generated from Carbon generic functions into fewer LLVM functions, where the LLVM types permit it.
203 lines
8.0 KiB
Markdown
203 lines
8.0 KiB
Markdown
# Lower
|
|
|
|
<!--
|
|
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)
|
|
- [Generic lowering](#generic-lowering)
|
|
- [Cross-file lowering](#cross-file-lowering)
|
|
- [Specific deduplication and fingerprinting](#specific-deduplication-and-fingerprinting)
|
|
- [Mangling](#mangling)
|
|
- [Examples](#examples)
|
|
|
|
<!-- tocstop -->
|
|
|
|
## Overview
|
|
|
|
Lowering takes the SemIR and produces LLVM IR. At present, this is done in a
|
|
single pass, although it's possible we may need to do a second pass so that we
|
|
can first generate type information for function arguments.
|
|
|
|
The lowering context is split into three layers:
|
|
|
|
- The `Context` object holds state for an overall lowering process that
|
|
produces a single LLVM module.
|
|
- The `FileContext` object holds state for lowering from a particular
|
|
`SemIR::File`, and holds a pointer to its enclosing `Context`. Multiple
|
|
files may be involved in a single lowering process when lowering a generic,
|
|
where the definition of the generic and the specific may be owned by
|
|
distinct files. This setup would also allow us to lower an entire library
|
|
into a single LLVM module if we chose to do so.
|
|
- The `FunctionContext` object holds state for lowering a particular function,
|
|
including an `IRBuilder` and mappings from the local `InstId`s to their
|
|
lowered `llvm::Value*`s and from the local `InstBlockId`s to their lowered
|
|
`llvm::BasicBlock*`s.
|
|
|
|
Lowering is done per `SemIR::InstBlock`. This minimizes changes to the
|
|
`IRBuilder` insertion point, something that is both expensive and potentially
|
|
fragile.
|
|
|
|
## Generic lowering
|
|
|
|
In order to support lowering generic functions, the `FunctionContext` tracks
|
|
both the `FunctionId` of the function being lowered and a corresponding
|
|
`SpecificId`. Whenever `FunctionContext` or a `HandleInst` function inspects a
|
|
property of an instruction that can vary between specifics -- in particular, the
|
|
type or constant value of an instruction -- that value is looked up in the
|
|
current specific, and the corresponding type or value is used instead.
|
|
|
|
`FunctionContext::GetTypeOfInst` and `FunctionContext::GetTypeIdOfInst` do this
|
|
mapping for the type of an instruction, and should be used instead of directly
|
|
looking at the `type_id` field of a typed instruction throughout function
|
|
lowering. Similarly, `FunctionContext::GetValue` does this mapping when looking
|
|
up the constant value of an instruction.
|
|
|
|
## Cross-file lowering
|
|
|
|
`FunctionContext` lowering may draw information used to lower the function from
|
|
two different files:
|
|
|
|
- The file in which the function was defined.
|
|
- For a generic function, the file in which the specific was formed.
|
|
|
|
Each of these files has its own `FileContext`, which tracks its corresponding
|
|
`SemIR::File`, as well as mappings from its constant values to
|
|
`llvm::Constant*`s and mappings from its functions to `llvm::Function*`s, and so
|
|
on.
|
|
|
|
When querying the type of an instruction using
|
|
`FunctionContext::GetTypeIdOfInst`, the resulting type may be owned by either of
|
|
these files. The type is represented as a `TypeInFile`, which is a pair of the
|
|
owning `SemIR::File*` and the `SemIR::TypeId` within that file. Care must be
|
|
taken to only pass the `TypeId` in a `TypeInFile` to code that expects a
|
|
`TypeId` within the corresponding `SemIR::File*`. To reduce the risk of errors,
|
|
code within `FunctionContext` and `HandleInst` functions should not directly
|
|
interact with `TypeId`s, and should instead always use `TypeInFile`.
|
|
|
|
Similarly, other type properties have `FunctionContext` wrappers that track the
|
|
file that owns the `TypeId`s:
|
|
|
|
- `FunctionContext::GetValueRepr` returns a `ValueReprInFile` which is a pair
|
|
of a `SemIR::File*` and a `SemIR::ValueRepr`.
|
|
- `FunctionContext::GetReturnTypeInfo` returns a `ReturnTypeInfoInFile` which
|
|
is a pair of a `SemIR::File*` and a `SemIR::ReturnTypeInfo`.
|
|
|
|
These pairs are kept wrapped in the `*InFile` structs wherever possible, in
|
|
order to minimize the chance of an ID being used with the wrong file.
|
|
|
|
## Specific deduplication and fingerprinting
|
|
|
|
Specifics for the same generic are deduplicated by detecting whether we
|
|
generated the same LLVM IR for all the portions of the specific that depend on
|
|
generic arguments. This is accomplished in part by computing a fingerprint for
|
|
each specific. The fingerprint contains:
|
|
|
|
- For each symbolic constant value used while lowering, the lowered LLVM value
|
|
in the specific.
|
|
- For each symbolic type used while lowering, the lowered LLVM type in the
|
|
specific.
|
|
- For each called function, information about the specific callee. TODO:
|
|
Describe how we handle deduplicating strongly-connected components of the
|
|
call graph.
|
|
- For each other property of the specific that lowering depends on, the value
|
|
of that property.
|
|
|
|
These fingerprinted values are tracked by the `FunctionContext` accessors that
|
|
obtain the information from SemIR:
|
|
|
|
- `FunctionContext::GetType` adds the `llvm::Type*` produced for a symbolic
|
|
type to the fingerprint.
|
|
- `FunctionContext::GetValue` adds the `llvm::Value*` produced for a symbolic
|
|
constant to the fingerprint.
|
|
- `FunctionContext::GetValueRepr` adds the kind of the value representation,
|
|
but not the value representation type, to the fingerprint.
|
|
- `FunctionContext::GetInitRepr` adds the kind of the initializing
|
|
representation to the fingerprint.
|
|
- `FunctionContext::GetReturnTypeInfo` adds the kind of the return
|
|
representation, but not the type, to the fingerprint.
|
|
|
|
For `GetValueRepr` and `GetReturnTypeInfo`, the corresponding type is
|
|
represented as a `TypeInFile`. The convention in use is that `TypeInFile` values
|
|
represent types that have not yet been added to the fingerprint for the
|
|
specific, and the mapping from `TypeInFile` to `llvm::Type*` is the point where
|
|
the type is added to the fingerprint, but other data such as the enumeration
|
|
values stored on `ReturnTypeInfoInFile` have already been added to the
|
|
fingerprint.
|
|
|
|
Additional information queried from SemIR by `FunctionContext` or a `HandleInst`
|
|
function should follow the same pattern, adding a getter on `FunctionContext`
|
|
that adds the information to the fingerprint, and returns a `*InFile` wrapper
|
|
struct if the result contains any `TypeId`s.
|
|
|
|
Additional details can be found in:
|
|
[Coalescing generic functions emitted when lowering to LLVM IR](coalesce_generic_lowering.md).
|
|
|
|
## Mangling
|
|
|
|
Part of lowering is choosing deterministically unique identifiers for each
|
|
lowered entity to use in platform object files. Any feature of an entity (such
|
|
as parent namespaces or overloaded function parameters) that would create a
|
|
distinct entity must be included in some way in the generated identifier.
|
|
|
|
The current rudimentary name mangling scheme is as follows:
|
|
|
|
- As a special case, `Main.Run` is emitted as `main`.
|
|
|
|
Otherwise the resulting name consists of:
|
|
|
|
1. `_C`
|
|
2. The unqualified function name (function name mangling is the only thing
|
|
implemented at the moment).
|
|
3. If the function is a thunk, `:thunk` to distinguish it from the function it
|
|
invokes.
|
|
4. `.`
|
|
5. If the function being mangled is a member of:
|
|
- an `impl`, then add:
|
|
1. The implementing type, per the scope mangling.
|
|
2. `:`
|
|
3. The interface type, per the scope mangling.
|
|
- a type or namespace, then add:
|
|
1. The scope, per the scope mangling.
|
|
|
|
The scope mangling scheme is as follows:
|
|
|
|
1. The unqualified name of the type or namespace.
|
|
2. If the type or namespace is within another type or namespace:
|
|
1. `.`
|
|
2. The enclosing scope, per the scope mangling.
|
|
3. `.`
|
|
4. The package name.
|
|
|
|
### Examples
|
|
|
|
```carbon
|
|
package P1;
|
|
interface Interface {
|
|
fn Op[self: Self]();
|
|
}
|
|
```
|
|
|
|
```carbon
|
|
namespace NameSpace;
|
|
class NameSpace.Implementation {
|
|
// Mangled as:
|
|
// `_COp.Implementation.NameSpace.Main:Interface.P1`
|
|
impl as P1.Interface {
|
|
fn Op[self: Self]() {
|
|
}
|
|
}
|
|
}
|
|
// Mangled as `main`.
|
|
fn Run() {
|
|
var v: NameSpace.Implementation;
|
|
v.(P1.Interface.Op)();
|
|
}
|
|
```
|