Compare commits

...
Author SHA1 Message Date
Richard Smith 19f1bf94db If multiple files lower the same variable, use the same GlobalVariable. (#8101)
We can lower the same variable from multiple `SemIR::File`s if we reach
it through a specific for a generic in a different file. When this
happens, reuse the same global variable in lowering. This matches how we
handle the same problem for functions.

Assisted-by: Claude via Antigravity
2026-11-10 00:25:46 +00:00
Nicholas Bishop ca1898506b Fix C++ access to Carbon friend members from an unrelated class (#8097)
See access_private_parent_member_in_unrelated_class_with_friend.carbon
for an example of this fix.

The problem with that example, prior to this fix, is that when
`Derived::F` is accessed, clang tries to find `F` via
`CarbonExternalASTSource::FindExternalVisibleDeclsByName`, but
`LookupQualifiedName` doesn't find anything because it (correctly) sees
that `F` is a private member of `Base`, so it's not accessible. What it
doesn't know is that it will be accessed from the context of `A`, which
is a friend of `Base`, and so privacy doesn't matter.

`FindExternalVisibleDeclsByName` is really supposed to report visible
items, regardless of whether they can be accessed. But changing it to
simply ignore access (e.g. by modifying `LookupQualifiedName` to
optionally ignore access) doesn't quite work here. This can make a
private member of a base class appear within the `DeclContext` of a
derived class with access set to private, when it should be set to none.
This causes an assert in clang (this case is tested in
fail_access_private_parent_member_in_unrelated_fn.carbon).

Instead, change `FindExternalVisibleDeclsByName` to call
`LookupNameInExactScope` instead of `LookupQualifiedName`. This prevents
extended scopes from being searched, so members of a base class are not
found when filling in the `DeclContext` of a derived class. Clang will
call `FindExternalVisibleDeclsByName` in the context of the base class,
and the base class members are added at that time.
2026-11-06 20:09:14 +00:00
dependabot[bot] 4e4b35e6e7 Bump the github-actions-major group across 5 directories with 9 updates (#8098)
- `actions/cache` 5.0.3 -> 6.1.0 (major)
- `actions/checkout` 6.0.2 -> 7.0.1 (major)
- `actions/configure-pages` 5.0.0 -> 6.0.0 (major)
- `actions/deploy-pages` 4.0.5 -> 5.0.1 (major)
- `actions/stale` 9.0.0 -> 11.0.0 (major)
- `actions/upload-pages-artifact` 4.0.0 -> 5.0.0 (major)
- `astral-sh/setup-uv` 8.1.0 -> 10.3.3 (major)
- `dorny/paths-filter` 3.0.2 -> 4.0.3 (major)
- `jlumbroso/free-disk-space` 1.3.1 -> 2.0.0 (major)

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-06 03:12:37 +00:00
Richard Smith d50576ca16 Handle references to global lets and enclosing locals. (#8061)
When building an expression referencing the result of name lookup, if
the lookup result is non-constant then check we can access it from the
current scope.

* If it is a global, then use a new `GlobalRef` instruction to get the
value of that global. In lowering, create global variables to hold the
values of global `let` bindings so that `GlobalRef` can access those
values.
* If it is a local, walk the lexical scopes between it and the point of
use and ensure it is usable through from all of them. Error if there's
an intervening declarative scope such as a class, add a TODO to handle
captures for intervening functions, and allow `typeof(...)` to name
things it can't read the value of since it's unevaluated.

Global `let`s (which includes `let`s at class scope) now also have their
initializers collected into the global inits block. If the initializer
is non-constant, the wrapper binding also lives in the global inits
block; otherwise it continues to live in the file block and the variable
emitted for it is a global constant.

Assisted-by: Gemini and Claude via Antigravity
2026-11-06 02:53:03 +00:00
Nicholas BishopandDavid Blaikie bb6486b553 Add various access tests related to inheritance (#8100)
These tests were originally generated by David with Claude as part of
reviewing #8075; see
https://github.com/carbon-language/carbon-lang/pull/8075#issuecomment-6616160559
for more background. I've gone through the tests and made some changes
and cleanups. There are a lot of tests, and sometimes the logic of what
should be accessible can be a little subtle, so putting this up for
review separately before trying to fix any of the tests.

Co-authored-by: David Blaikie <dblaikie@gmail.com>
2026-11-06 00:01:56 +00:00
dependabot[bot]andCarbonInfraBot 5b1a9311ad Bump the bazel group across 1 directory with 13 updates (#8082)
- `abseil-cpp` 20260107.1 -> 20260817.0 (major)
- `bazel_skylib` 1.9.0 -> 1.9.2
- `google_benchmark` 1.9.5 -> 1.10.0
- `googletest` 1.17.0.bcr.2 -> 1.18.0.bcr.1
- `libxml2` 2.15.2 -> 2.15.3
- `platforms` 1.0.0 -> 1.1.0
- `protobuf` 34.0.bcr.1 -> 36.2 (major)
- `rules_cc` 0.2.25 -> 0.2.27
- `rules_pkg` 1.2.0 -> 1.3.0
- `rules_python` 1.9.0 -> 2.4.5 (major)
- `tcmalloc` 0.0.0-20250927-12f2552 -> 0.0.0-20260818-15db23d
- `zlib-ng` 2.3.3 -> 2.3.3.bcr.2
- `zstd` 1.5.7.bcr.1 -> 1.5.7.bcr.3

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: CarbonInfraBot <64870956+CarbonInfraBot@users.noreply.github.com>
2026-11-05 23:46:42 +00:00
Lucile Rose Nihlen a567040d77 Update LLVM to c744694c6ae1 (#8096)
Moves Carbon code away from deprecated `llvm::IRBuilder`
and `llvm::PassBuilder` constructors  

Removes llvm patch 0002 as it landed upstream in
[#234482](https://github.com/llvm/llvm-project/pull/234482)

Removes llvm patch 0004 as it landed upstream in
[#234485](https://github.com/llvm/llvm-project/pull/234485)

Removes llvm patch 0005 as it landed upstream in
[#234489](https://github.com/llvm/llvm-project/pull/234489)

Removes llvm patch 0006 as it landed upstream in
[#234486](https://github.com/llvm/llvm-project/pull/234486)

Adds llvm patch 0013, also uploaded as an upstream
PR in [#239321](https://github.com/llvm/llvm-project/pull/239321)

Updates tests in lower and driver to suit changes to LLVM.
2026-11-05 20:33:12 +00:00
Chandler Carruth 3741e72c8a Clean up environment variables after deleting Android SDK (#8099)
Without this, new versions of Bazel tools break on our runners.

Assisted-by: Antigravity with Opus
2026-11-05 20:09:08 +00:00
Chandler Carruth a1305604f2 Get Bazel and CI ready for updated protobuf and rules_python (#8094)
Newer versions of `protobuf` depend on Rust to a limited extent. Builds
don't use it, and _our_ builds rarely even touch on `protobuf`. But the
way CI was running `bazel mod` commands in `action.yml` forced fully
evaluating all the module extensions, including Rust. This ends up both
hitting end points we didn't allow and being really expensive. We can
skip that and keep the more targeted `cquery` that doesn't do anything
beyond what our builds need. We also still have a test that the lock
file is accurate, so we don't lose any coverage here, it just makes
things faster.

That said, we _do_ want to be able to hit the Rust dependency if and
where we (transitively) need it, and we'll at least need our `prek`
checks to be able to resolve them and rebuilding the lock file after an
update, so the relevant end points are added to our hardened runner as
allowed.

The second issue is that `rules_python` in version 2.0 and newer is
using a Bazel feature we disable. There is an escape hatch though that
we can enable in our `.bazelrc` so it keeps working, and that works even
with the current version.

Once this lands, the Dependabot update of the Bazel module versions
should be able to make more progress.

Assisted-by: Antigravity with Claude Opus
2026-11-05 18:11:31 +00:00
Chandler Carruth bfd7f7ae56 Force inlining of Hasher::HashSizedBytes (#8092)
This routine is large enough that it sometimes exceeds the inlining
threshold. However, when out-of-line, it forces `Hasher` to be spilled
to the stack in addition to the register pressure of the out-of-line
call. The performance here is really engineered around it being inlined,
so force the issue with an attribute.

This also requires force inlining of the wrappers that bottom out at
this routine, or we'd just move the problem up one layer without fully
solving it.

I don't have specific numbers for this, but it seems like a generally
good change to ensure consistent performance here.

Assisted-by: Antigravity with Gemini
2026-11-05 15:41:39 +00:00
Chandler Carruth d0badecd2a Switch tests to use the rendered form of diagnostics (#8070)
Historically, we disabled the rendered form in tests outside of
`toolchain/diagnostics`. The idea was to keep tests shorter and avoid
very long chains of autoupdate before stabilizing due to all the line
numbers and other artifacts in the rendered form.

This PR adds `--diagnostic-line-numbers=[all|first]` and defaults to
`first` in tests. This keeps the rendered form so that we can easily
code review that form and the visual cues it does or doesn't provide.
But it removes all but the first location's line numbers to provide
significantly more stability in tests.

By default, tests get the full unicode line-drawing form of diagnostics
so we can ensure all of the artifacts there make sense with the specific
case being diagnosed, but we do retain some focused ASCII-rendering
tests to ensure that fallback remains usable.

There are some issues here that will be fixed in subsequent PRs.
Notably, we try to keep the frame together in the check lines, and so
force some parts to use the first location. This largely works, but
fails in some edge cases, causing the entire frame to be located
incorrectly. There is a TODO about this that we need to fix, and it only
occurs 6 times across the test suite, so it didn't seem worth
front-loading a fix for here.

Assisted-by: Claude Code
2026-11-05 07:19:59 +00:00
dependabot[bot]andCarbonInfraBot 0b7059a360 Bump the pre-commit group across 1 directory with 3 updates (#8079)
- `astral-sh/ruff-pre-commit` 0.15.14 -> 0.17.2
- `codespell-project/codespell` 2.4.2 -> 2.4.3
- `rvben/rumdl-pre-commit` 0.2.58 -> 0.3.0

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: CarbonInfraBot <64870956+CarbonInfraBot@users.noreply.github.com>
2026-11-05 01:57:07 +00:00
Chandler Carruth 45ed77af3b Switch from rumdl disable to more general fmt annotations (#8091)
The tool actually doing the formatting of our fenced code regions isn't
`rumdl`, but `ruff` which now supports that (nice!). It needs a
different, more general marker though. Hopefully other language
formatters that grow this support also respect the same annotation. This
seems preferable to disabling `rumdl` as we are fine with it moving the
fenced code region around as part of formatting, it is only the
formatting _within_ the code region that should be preserved.
2026-11-05 01:32:43 +00:00
Richard Smith 28d15a6e78 Disable autoformatting in examples of weird formatting. (#8090)
Rumdl has grown the ability to format python snippets. That's not what
we want in an example whose purpose is to show off some
weirdly-formatted python code! So turn it off. Also turn it off for the
weirdly formatted Swift and Kotlin code here which rumdl doesn't
reformat today, but might reformat tomorrow.
2026-11-05 00:25:53 +00:00
Nicholas Bishop 05de707c4c Remove semir dumps from class/access tests (#8089)
These are all pretty straightforward tests of whether something can be
accessed or not; the semir isn't adding a lot.
2026-11-04 21:38:26 +00:00
Chandler Carruth 9f2f906b85 Add support for direct-commit prek fixes for dependabot (#8084)
Suggested edits don't work well for dependabot, and this is a case where
it's especially useful to have the fixes automatically applied. So this
creates a dependabot-specific flow that directly commits the fixes
rather than merely attaching them as suggested edits.

Because this directly commits the fixes, it also adds some restrictive
filtering to only apply fixes that are edits to existing non-binary
files, and files outside of `.github/`. The intent here is to minimize
the risk, as the changes suggested are from a just-updated `prek` run.

Also adds some extra safety to the `tests` action by omitting a secret
there -- it isn't needed anyways. And it pins an additional version.

Assisted-by: Antigravity with Opus
2026-11-04 21:35:07 +00:00
oli-ej 5ae3dcaa92 allow trailing commas in a struct pattern after an _ field (#8074)
Following a leads decision
(https://github.com/carbon-language/carbon-lang/issues/7404), this PR
adds support for trailing commas in a struct pattern where it was
previously only allowed after a full field.
2026-11-03 22:02:33 +00:00
Chandler Carruth 80a4a4d005 Fix detection of dependabot-authored descriptions (#8083)
Instead of looking for a marker within the description, look at the
editor directly using the `gh` API querying capabilities. This lets us
detect when we have the initial description, as well as when the
description is updated by dependabot rebasing or recreating a PR.

Also prunes URL prefixes that aren't needed.

Assisted-by: Antigravity with Opus
2026-11-03 21:55:47 +00:00
Nicholas Bishop 66f03aecb1 Export class friends to C++ (#8068) 2026-11-03 18:28:21 +00:00
Chandler Carruth 5af3b7c47a Try to silence a zizmor warning (#8078) 2026-11-03 16:47:54 +00:00
Dana Jansens 9fb428d713 Ensure specific in ImplWitness has resolved definition before accessing witness table (#8040)
When impl lookup results in an `ImplWitness`, either directly or later
through `LookupImplWitness` evaluating to an `ImplWitness` inside some
specific, it represents a dependency on the eval blocks of the
`ImplDecl` that the `ImplWitness` comes from. The `ImplWitness` contains
a `SpecificId` of the `ImplDecl`, and the constants in the witness table
must be viewed through that `SpecificId`. The constants may depend on
the declaration or the definition block of the `ImplDecl`'s specific, so
any use of the witness table from `ImplWitness` requires the specific
from the `ImplWitness` to be fully resolved: both the decl and
definition blocks.

The specific declaration block is always resolved immediately when
forming a specific id, so there's nothing to do there. And things are
simple when the `ImplWitness` returned from impl lookup is concrete. We
would simply resolve the specific definition directly in the evaluation
of `LookupImplWitness`.

But when the `ImplWitness` is symbolic, meaning there's a dependency on
generic parameters in its referenced specific, then we have a dependency
on resolving the specific definition in the `ImplWitness` for every
specific that gets applied to the `ImplWitness`, eventually resulting in
a concrete specific.

Previously we attempted to do this by representing a dependency edge
from the eval block containing the `ImplWitness` instruction to the
definition block of the specific inside the `ImplWitness`. We
represented this dependency edge with the `RequireSpecificDefinition`
instruction.

The challenge with the `RequireSpecificDefinition` instruction was
ensuring it always landed in the _same eval block_ as the one holding
the symbolic `ImplWitness`. Ultimately, we've found a way to do this
without the `RequireSpecificDefinition`. The witness table is accessed
through `ImplWitnessAccess`, so we resolve the specific definition
before each access to the witness table. If the definition is already
resolved, this just costs a lookup of the Specific from the value store.

We document the other things we tried, which don't work or were too
complex, below.

In PR #6236, we added the `RequireSpecificDefinition` instruction in the
eval of `LookupImplWitness`, but this is too late. If impl lookup
resolved to a symbolic `LookupImplWitness`, the `LookupImplWitness` is
added some eval block. Later, we form specifics and re-evaluate that
eval block, and the `LookupImplWitness` can evaluate to an
`ImplWitness`. Adding the `RequireSpecificDefinition` instruction at
this point inserts the instruction into whatever generic's eval block
happens to be on the top of the generic stack, not the eval block of the
generic we are building a specific for. And indeed we can't add
instructions to a generic's eval block later like this, as it would be
missing from specifics that were already resolved for the generic. This
incorrect insertion shows up as a crash in eval at the CHECK introduced
but commented out in [eval.cpp in
#7981](https://github.com/carbon-language/carbon-lang/blob/56d9bbc40add1e9263f2f9b2555c3a27292d1725/toolchain/check/eval.cpp#L3507-L3512).
This is covered by the
`require_specific_impl_definition_through_specific_instantiation.carbon`
test.

We tried moving the insertion of `RequireSpecificDefinition` to be
earlier - at the point where the `LookupImplWitness` is constructed, in
the `LookupImplWitness()` function, instead of inside evaluation. This
seemed like a straightforwardly correct answer, as the eval block that
contains the `LookupImplWitness` will then also contain the
`RequireSpecificDefinition`. However, this can cause us to lose the
`RequireSpecificDefinition` entirely. Because it's a separate
instruction, not an operand of `LookupImplWitness`, it does not get
treated as a dependency of the witness. When we do an impl lookup inside
an `alias`, we build a generic for the alias and then discard it. This
discards the eval block containing the `LookupImplWitness`. Then, in an
enclosing generic, we refer to the constructed `AliasDecl` instruction,
which pulls all its dependent operands into the enclosing eval block.
This pulls in the `LookupImplWitness` into the enclosing eval block, but
it does not preserve the `RequireSpecificDefinition`. This is covered by
the
`require_specific_impl_definition_from_lookup_impl_witness_in_class_definition.carbon`
test.

We tried having `ImplWitness` evaluation resolve the specific definition
of the specific `ImplDecl` it refers to. This seems like the simplest
approach by far as we should always be working with the correct eval
block and nothing can get lost. However it does not work because of the
recursive nature of `ImplWitness`. The `ImplWitness` instruction appears
in the declaration of an `ImplDecl`, and the `ImplWitness` holds a
`SpecificId` for that same `ImplDecl`. When building a specific
`ImplDecl`, we evaluate the `ImplWitness` in the `ImplDecl`'s
declaration block which then immediately resolves the specific
definition before the declaration is finished. This leads to missing
instructions in the specific definition. In short, this puts the
dependency edge to the `ImplDecl`'s specific inside the `ImplDecl`
itself, which is problematic.

We tried wrapping `ImplWitness` in another instruction,
`ImplWitnessUse`. Inside the `ImplDecl` declaration there is still
`ImplWitness`, but then in the definition there is `ImplWitnessUse` and
that is what is returned from the evaluation of `LookupImplWitness`. The
`ImplWitnessUse` is an `ImplWitness` plus it ensures the specific
referred to by the witness has its definition resolved. However this
breaks constant comparisons, as we get different witnesses inside an
impl versus outside.

The first iteration of this PR's solution had us insert the
`RequireSpecificDefinition` into the eval block when we sniffed a
`LookupImplWitness` instruction, however that missed cases where impl
lookup immediately resolved to a symbolic `ImplWitness`, which is why we
now sniff for the `ImplWitness` in the eval block. This is covered by
the
`require_specific_impl_definition_from_symbolic_impl_in_class_definition.carbon`
test.

We also originally made a mistake of not inserting
`RequireSpecificDefinition` when inside the impl definition that the
`ImplWitness` refers to. But we can form specifics ImplWitness values
through function signatures, and these must add the dependency edge on
that specific ImplDecl definition. This is covered by the
`require_specific_impl_definition_from_inside_impl_definition.carbon`
test.

Finally, we did have a working solution. It was to sniff instructions
added to the eval block of a generic, and when an `ImplWitness` is added
to the eval block (it is implied that it is symbolic), also add a
`RequireSpecificDefinition` to that eval block immediately. We also
resolved the definition of a specific referred to by `ImplWitness` when
`LookupImplWitness` evaluates to `ImplWitness` to cover the concrete
witness case. This complicated the construction of the eval block
immensely, however. It caused a situation where adding one inst to the
eval block actually adds two, which we needed to avoid when importing.
And it required inst-kind specific behaviour in the very general
construction of an eval block, completely outside of eval itself.
2026-11-03 14:15:14 +00:00
dependabot[bot] b7919e8f50 Bump the github-actions group across 1 directory with 4 updates (#8080)
- `actions/download-artifact` 8.0.0 -> 8.0.2
- `actions/upload-artifact` 7.0.0 -> 7.0.2
- `reviewdog/action-setup` 1.3.0 -> 1.5.0
- `ruby/setup-ruby` 1.290.0 -> 1.335.0

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-03 01:56:08 +00:00
Chandler Carruth a7e2f3418f Setup dependabot updates for more layers, and improve commits (#8073)
This configures dependabot to update our GitHub actions, our pre-commit
steps, and our Bazel modules.

It also adds a workflow to replace PR descriptions from Dependabot with
ones that work well with our squash-merge setup.

Last but not least, a couple of cases were missing SHA-pinning and it is
added here.

Assisted-by: Antigravity with Gemini
2026-11-02 22:12:42 +00:00
David BlaikieandChristopher Di Bella d99b767dde Export fields of imported Carbon classes to C++ (#8021)
When a Carbon class from another library was used from C++, its fields
were never exported, so C++ member access failed with 'no member named'.
The class scope of an imported class holds import references rather than
`FieldDecl`s, so `LookupClassFieldByStructField` didn't find them.
Resolve scope entries to their constant `FieldDecl`, load the import
references before exporting fields, and consistently key exported fields
by the `FieldDecl`.

Unfortunately `SemIR::LookupClassFieldByStructField` can't call
`LoadImportRef` because `SemIR::LookupClassFieldByStructField` is in
`SemIR` and `LoadImportRef` needs a `Check::Context` (and `check`
depends on `sem_ir`, not the other way around, so `sem_ir` can't use
check). So instead, make a `Check::LookupClassFieldByStructField` that
wraps the `SemIR` version, and does the `LoadImportRef`

Assisted-By: Claude

---------

Co-authored-by: Christopher Di Bella <cjdb.ns@gmail.com>
2026-11-02 20:37:01 +00:00
Dana Jansens be0f11be95 Build a generic scope around where expressions (#8062)
A `where` expression can have symbolic references to `.Self` inside it,
but those references do not represent symbolic instructions that should
be part of an enclosing generic eval block. They are internal details of
the facet type, and as such they are PeriodSelfSymbolic, not Symbolic.
The WhereExpr evaluation is careful to produce a Symbolic constant value
only if it has symbolic dependencies on the LHS of the `where`, which
avoids leaking the symbolic nature of `.Self` into he facet type's
constant value.

But until now, those PeriodSelfSymbolic insts could still leak into the
enclosing eval block if they ended up in an operand of a Call
expression. A compile-time Call expr evaluates to a TemplateSymbolic
phase constant if it's not concrete, which includes PeriodSelfSymbolic,
because we don't want to evaluate the Call expr until the `.Self` gets
replaced during identify. But that means the Call constant value, and
its operands including PeriodSelfSymbolic constants, all end up in the
eval block. We fix this by making Call evaluate to a PeriodSelfSymbolic
phase if its input phase is PeriodSelfSymbolic. This keeps the RHS of
the `where` expression self-contained. If the facet type is concrete,
then it doesn't add any instructions to the enclosing eval block (which
may not exist at all in a non-generic context). Regardless, when we
identify the facet type and replace `.Self`, the dependent Call
expression will be rebuilt as well and it will evaluate as appropriate.

We also enclose the `where` expression in its own generic scope. This
means that instructions which are not part of the facet type do not end
up in an eval block. They may be symbolic instructions, and they will
not get updated by a specific when it's applied the enclosing generic
anymore. But since they are not part of the facet type their
substitution is not required.

The new test had an incorrectly elided SpecificImplFunction instruction
in the resulting semir, as SubstPeriodSelf was using the location of the
input canonical instructions as the location for the results which
resulted in an instruction without a location. We fix that by using
`loc_id_` in the subst rebuild.

Formatting now saw the initialization of a value from an `eval fn` call
to a function in a witness table, so it needed to be taught to look
through ImplWitnessAssociatedConstant (which is what we find in the
witness table).
2026-11-02 20:05:00 +00:00
David Blaikie 5c9ab26d74 Give Carbon thunks for C++ callers linkonce_odr linkage (#8022)
This allows two C++ callers to the same Carbon function, written in
different Carbon files, to generate thunks that won't collide/duplicate
symbol at link time - instead the thunks get deduplicated silently by
the linker.
2026-11-02 18:53:01 +00:00
bingletigerandChristopher Di Bella 8fc87ebf76 Add manual installation commands for syntax highlighting in Neovim (#8038)
[Carbon Syntax Highlighting for Vim & Neovim][1] is missing manual
installation commands for Neovim.

Custom syntax files have to be present in `~/.config/nvim/syntax/`
([source][2]) and filetypes in `~/.config/nvim/ftdetect` ([source][3]).


[1]: https://github.com/carbon-language/carbon-lang/tree/trunk/utils/vim
[2]: https://neovim.io/doc/user/syntax/#mysyntaxfile
[3]: https://neovim.io/doc/user/filetype/#new-filetype

Co-authored-by: Christopher Di Bella <cjdb@google.com>
2026-11-02 18:52:53 +00:00
Chandler CarruthandRichard Smith 68569d234d Improve lexer and parser diagnostic locations (#7990)
Lexer diagnostics used a single `const char*` as their source location,
which only allowed pointing at a single character rather than a run of
characters. This limited the underlining of unrecognized tokens, and
also made escape sequence diagnostics particularly strange by stopping
at whatever point the lexer discovered the error and pointing only at
that location. For example: `"\w"` was reported at the `w`, and `"\x"`
at the closing `"`.

This change tries to always keep the primary location of a diagnostic
the point at which typing (inserting or deleting) might fix the error.
So for `"\x"`, `\x` is underlined, but the position is at the `"` as
that's where you would type a hex code.

The lexer's emitter now takes a special lexing source location that
represents either a single position _between_ two characters, a single
character, or a range of the source buffer. The lexer diagnostics are
updated to construct these for the character range as appropriate.

Parser diagnostics for a missing token were located at the _next_ token,
which is particularly confusing for terminators like missing `;`s. Now
they are located at a position after the previous token, with or without
a space depending on the token, but typically on the same line.
Diagnostics still sort by how far the parser has read.

The parser diagnostics for unclosed groups also now attaches a label to
the opening token when it is on a separate line. And the diagnostics for
mismatched indent in a multi-line string attaches a label to the closing
`'''` that establishes that indent.

There are likely more location improvements we should do here, but this
seemed like a reasonable incremental improvement and is what Claude
found when doing an initial audit. One example where I think we should
make further improvements is around suggesting raw identifiers when we
find an unexpected `Cpp` or `Core` or `base`. We do this consistently
now, but raw identifiers should be rare. A TODO is added for these
cases.

Assisted-by: Claude Code

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
2026-11-01 06:22:00 +00:00
dependabot[bot] 611e6a210e Bump the npm_and_yarn group across 1 directory with 3 updates (#8067)
Bumps the npm_and_yarn group with 3 updates in the /utils/vscode
directory:
[@azure/msal-node](https://github.com/AzureAD/microsoft-authentication-library-for-js),
[qs](https://github.com/ljharb/qs) and
[undici](https://github.com/nodejs/undici).

Updates `@azure/msal-node` from 5.1.4 to 7.1.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/AzureAD/microsoft-authentication-library-for-js/releases">@​azure/msal-node's
releases</a>.</em></p>
<blockquote>
<h2><code>@​azure/msal-node</code> v7.1.2</h2>
<h2>7.1.2</h2>
<p>Tue, 27 Oct 2026 18:15:10 GMT</p>
<h3>Patches</h3>
<ul>
<li>Bump <code>@​azure/msal-common</code> to v16.15.0 (beachball)</li>
</ul>
<h2><code>@​azure/msal-node</code> v7.1.1</h2>
<h2>7.1.1</h2>
<p>Wed, 21 Oct 2026 18:37:17 GMT</p>
<h3>Patches</h3>
<ul>
<li>Bump <code>@​azure/msal-common</code> to v16.14.3 (beachball)</li>
</ul>
<h2><code>@​azure/msal-node</code> v7.1.0</h2>
<h2>7.1.0</h2>
<p>Tue, 13 Oct 2026 22:26:37 GMT</p>
<h3>Minor changes</h3>
<ul>
<li>Sanitize authorization code payloads and reject unsupported
instance-aware inputs for confidential clients. <a
href="https://redirect.github.com/AzureAD/microsoft-authentication-library-for-js/pull/8901">#8901</a>
(<a href="mailto:hemoral@microsoft.com">hemoral@microsoft.com</a>)</li>
</ul>
<h2><code>@​azure/msal-node</code> v7.0.2</h2>
<h2>7.0.2</h2>
<p>Tue, 13 Oct 2026 19:04:36 GMT</p>
<h3>Patches</h3>
<ul>
<li>Sanitize authorization code payloads and reject unsupported
instance-aware inputs for confidential clients. <a
href="https://redirect.github.com/AzureAD/microsoft-authentication-library-for-js/pull/8901">#8901</a>
(<a
href="mailto:rginsburg@microsoft.com">rginsburg@microsoft.com</a>)</li>
</ul>
<h2><code>@​azure/msal-node</code> v7.0.1</h2>
<h2>7.0.1</h2>
<p>Fri, 02 Oct 2026 19:03:42 GMT</p>
<h3>Patches</h3>
<ul>
<li>Bump <code>@​azure/msal-common</code> to v16.14.2 (beachball)</li>
</ul>
<h2><code>@​azure/msal-node</code> v7.0.0</h2>
<h2>7.0.0</h2>
<p>Wed, 23 Sep 2026 15:49:44 GMT</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/AzureAD/microsoft-authentication-library-for-js/commit/96488ec6e583adbfd2940374c62d9c70e4327165"><code>96488ec</code></a>
Clarify authority discovery trust model (<a
href="https://redirect.github.com/AzureAD/microsoft-authentication-library-for-js/issues/8938">#8938</a>)</li>
<li><a
href="https://github.com/AzureAD/microsoft-authentication-library-for-js/commit/95e5031808e1e290468a7d9d783fdeea1d7ec3a4"><code>95e5031</code></a>
Handle response headers case-insensitively (<a
href="https://redirect.github.com/AzureAD/microsoft-authentication-library-for-js/issues/8916">#8916</a>)</li>
<li><a
href="https://github.com/AzureAD/microsoft-authentication-library-for-js/commit/01b6a8fdda1bf6373ca87085df47ba392eab3f57"><code>01b6a8f</code></a>
Introduce &quot;popup origin check done&quot; param (<a
href="https://redirect.github.com/AzureAD/microsoft-authentication-library-for-js/issues/8793">#8793</a>)</li>
<li><a
href="https://github.com/AzureAD/microsoft-authentication-library-for-js/commit/0f98b8ca3718486015e2b13600943e92fd10d1fb"><code>0f98b8c</code></a>
Improve redirect response URL cleanup (<a
href="https://redirect.github.com/AzureAD/microsoft-authentication-library-for-js/issues/8918">#8918</a>)</li>
<li><a
href="https://github.com/AzureAD/microsoft-authentication-library-for-js/commit/4cf9a6156da7967efbaac18501a8de94269a38b1"><code>4cf9a61</code></a>
Post-release PR (<a
href="https://redirect.github.com/AzureAD/microsoft-authentication-library-for-js/issues/8925">#8925</a>)</li>
<li><a
href="https://github.com/AzureAD/microsoft-authentication-library-for-js/commit/16a4714f38ba2844f34d154dd59dd8f1402457c9"><code>16a4714</code></a>
Fix web silent-request deduplication for refresh policies and extra
parameter...</li>
<li><a
href="https://github.com/AzureAD/microsoft-authentication-library-for-js/commit/bd8b2cdfb4d476b008442902fd799d321f1c5b2b"><code>bd8b2cd</code></a>
Fallback from EAR to auth code flow in Safari (<a
href="https://redirect.github.com/AzureAD/microsoft-authentication-library-for-js/issues/8903">#8903</a>)</li>
<li><a
href="https://github.com/AzureAD/microsoft-authentication-library-for-js/commit/a9a1221ac325f30e73fd4d6cfbb4c0636a3ffcbe"><code>a9a1221</code></a>
Native Auth V2 entry and validation fixes (<a
href="https://redirect.github.com/AzureAD/microsoft-authentication-library-for-js/issues/8898">#8898</a>)</li>
<li><a
href="https://github.com/AzureAD/microsoft-authentication-library-for-js/commit/8bd17ffb63577f2b0ccd9013b20a3ab93b8e08e7"><code>8bd17ff</code></a>
chore: update basic-ftp to patched 6.2.2 (<a
href="https://redirect.github.com/AzureAD/microsoft-authentication-library-for-js/issues/8915">#8915</a>)</li>
<li><a
href="https://github.com/AzureAD/microsoft-authentication-library-for-js/commit/4d1e6f9516df2d0d21b8537010bbd8afd5bffe4c"><code>4d1e6f9</code></a>
build(deps): bump dorny/paths-filter from 3.0.4 to 4.0.3 (<a
href="https://redirect.github.com/AzureAD/microsoft-authentication-library-for-js/issues/8841">#8841</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/AzureAD/microsoft-authentication-library-for-js/compare/msal-node-v5.1.4...msal-node-v7.1.2">compare
view</a></li>
</ul>
</details>
<br />

Updates `qs` from 6.15.2 to 6.17.1
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/ljharb/qs/blob/main/CHANGELOG.md">qs's
changelog</a>.</em></p>
<blockquote>
<h2><strong>6.17.1</strong></h2>
<ul>
<li>[Fix] <code>parse</code>: <code>interpretNumericEntities</code>:
decode code points above U+FFFF as surrogate pairs (<a
href="https://redirect.github.com/ljharb/qs/issues/640">#640</a>)</li>
<li>[Fix] <code>parse</code>: respect <code>plainObjects</code> for
<code>[]</code> with <code>parseArrays: false</code> (<a
href="https://redirect.github.com/ljharb/qs/issues/639">#639</a>)</li>
<li>[Fix] <code>utils</code>: <code>merge</code>: count the elements of
a sparse array against <code>arrayLimit</code>, not its holes</li>
<li>[Fix] <code>parse</code>: guard bracket-wrapped prototype keys at
depth 0 (<a
href="https://redirect.github.com/ljharb/qs/issues/638">#638</a>)</li>
<li>[Refactor] <code>parse</code>: extract wrapping-bracket stripping
helper</li>
<li>[readme] document that <code>depth: 0</code> and <code>depth:
false</code> keep keys flat</li>
<li>[Tests] <code>parse</code>: pin depth-0 bracket stripping and
prototype keys kept by
<code>allowPrototypes</code>/<code>plainObjects</code></li>
</ul>
<h2><strong>6.17.0</strong></h2>
<ul>
<li>[New] <code>stringify</code>: add <code>WHATWG</code> format (<a
href="https://redirect.github.com/ljharb/qs/issues/623">#623</a>)</li>
<li>[Fix] <code>stringify</code>: do not sort the caller's
<code>filter</code> array in place (<a
href="https://redirect.github.com/ljharb/qs/issues/637">#637</a>)</li>
<li>[Fix] <code>parse</code>: copy an own <code>__proto__</code> key as
data (<a
href="https://redirect.github.com/ljharb/qs/issues/636">#636</a>)</li>
<li>[Fix] <code>parse</code>: decode percent-encoded dots
case-insensitively (<a
href="https://redirect.github.com/ljharb/qs/issues/635">#635</a>)</li>
<li>[Fix] <code>stringify</code>: encode keys when
<code>allowEmptyArrays</code> is set (<a
href="https://redirect.github.com/ljharb/qs/issues/561">#561</a>)</li>
<li>[Fix] <code>parse</code>: preserve comma arrays when interpreting
numeric entities (<a
href="https://redirect.github.com/ljharb/qs/issues/630">#630</a>)</li>
<li>[Fix] <code>parse</code>: allow scalar fields with a zero array
limit (<a
href="https://redirect.github.com/ljharb/qs/issues/627">#627</a>)</li>
<li>[Fix] <code>parse</code>: do not count an existing comma array
toward <code>arrayLimit</code> when <code>duplicates</code> discards or
replaces it</li>
<li>[Fix] <code>utils</code>: <code>encode</code>: encode lone
surrogates as U+FFFD instead of consuming the next code unit (<a
href="https://redirect.github.com/ljharb/qs/issues/624">#624</a>)</li>
<li>[eslint] fix indentation</li>
<li>[readme] document the encoder's <code>format</code> argument</li>
<li>[meta] add changelog acknowledgement</li>
<li>[meta] fix changelog indents</li>
<li>[Dev Deps] update <code>eslint</code>, <code>evalmd</code>,
<code>npmignore</code></li>
<li>[Tests] <code>stringify</code>: pin <code>allowEmptyArrays</code>
output for keys that need no encoding</li>
<li>[Tests] <code>stringify</code>: pin <code>sort</code> order for
nested keys selected by an array <code>filter</code></li>
<li>[Tests] <code>parse</code>: cover
<code>interpretNumericEntities</code> across value shapes, keys, and the
charset sentinel</li>
<li>[Tests] <code>parse</code>: cover more input that builds no array at
a zero or negative <code>arrayLimit</code></li>
<li>[Tests] <code>parse</code>: cover <code>comma</code> with a zero or
negative <code>arrayLimit</code>, and combining a full comma array</li>
<li>[Tests] <code>parse</code>: pin the behavior of a zero or negative
<code>arrayLimit</code></li>
<li>[Tests] <code>utils</code>/<code>stringify</code>: pin
<code>~</code>, <code>*</code>, and parens encoding in RFC3986 and
RFC1738</li>
</ul>
<h2><strong>6.16.2</strong></h2>
<ul>
<li>[Fix] <code>stringify</code>: encode keys when
<code>allowEmptyArrays</code> is set (<a
href="https://redirect.github.com/ljharb/qs/issues/561">#561</a>)</li>
<li>[meta] use double quotes for better OS support</li>
<li>[Dev Deps] update <code>evalmd</code>, <code>npmignore</code></li>
<li>[Tests] <code>stringify</code>: pin <code>allowEmptyArrays</code>
output for keys that need no encoding</li>
</ul>
<h2><strong>6.16.1</strong></h2>
<ul>
<li>[Fix] <code>parse</code>: do not mutate an object input</li>
<li>[Fix] <code>stringify</code>: stop re-encoding structural separator
dots when <code>encodeDotInKeys</code> is set (<a
href="https://redirect.github.com/ljharb/qs/issues/564">#564</a>)</li>
<li>[Fix] <code>stringify</code>: <code>arrayFormat: 'comma'</code>:
pass the full encoder arguments with <code>encodeValuesOnly</code></li>
<li>[Fix] <code>utils</code>: encode <code>+</code> as <code>%2B</code>
in iso-8859-1 mode so values round-trip (<a
href="https://redirect.github.com/ljharb/qs/issues/565">#565</a>)</li>
<li>[Fix] parse: only convert top-level dots to brackets when allowDots
is on (<a
href="https://redirect.github.com/ljharb/qs/issues/621">#621</a>)</li>
<li>[Fix] <code>stringify</code>: ignore inherited array format names
(<a
href="https://redirect.github.com/ljharb/qs/issues/622">#622</a>)</li>
<li>[Fix] <code>stringify</code>: <code>arrayFormat: 'comma'</code>: do
not throw on elements with no callable <code>toString</code></li>
<li>[Fix] <code>utils</code>: <code>merge</code>: seed an overflow
container's index from its own keys, not just the source</li>
<li>[Fix] <code>utils</code>: <code>merge</code>: keep a
<code>strictMerge</code> conflict container's index and
<code>arrayLimit</code> correct</li>
<li>[Fix] <code>parse</code>: only treat <code>[]</code> as array syntax
when it ends the key</li>
<li>[Perf] <code>decode</code>: skip decoders when there is no percent
sign (<a
href="https://redirect.github.com/ljharb/qs/issues/583">#583</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/ljharb/qs/commit/43baea4655cb9b543fb1c5cc0db95cde42852140"><code>43baea4</code></a>
v6.17.1</li>
<li><a
href="https://github.com/ljharb/qs/commit/54623b64dcef77a436b834b0413cf4cbb6bc6927"><code>54623b6</code></a>
[Fix] <code>parse</code>: <code>interpretNumericEntities</code>: decode
code points above U+FFFF as...</li>
<li><a
href="https://github.com/ljharb/qs/commit/7924b65958d41cd91dbaf688f61e7029673eb33f"><code>7924b65</code></a>
[Fix] <code>parse</code>: respect <code>plainObjects</code> for
<code>[]</code> with <code>parseArrays: false</code></li>
<li><a
href="https://github.com/ljharb/qs/commit/7d4b440de3c028e8497544d0474d5264d9d2bad9"><code>7d4b440</code></a>
[readme] document that <code>depth: 0</code> and <code>depth:
false</code> keep keys flat</li>
<li><a
href="https://github.com/ljharb/qs/commit/101492a5c4d9cb72f6510ecc15483759ec8e45fa"><code>101492a</code></a>
[Fix] <code>utils</code>: <code>merge</code>: count the elements of a
sparse array against `arrayLi...</li>
<li><a
href="https://github.com/ljharb/qs/commit/42eccf0f6292c502fb4bc175191f0812d5476769"><code>42eccf0</code></a>
[Fix] <code>parse</code>: guard bracket-wrapped prototype keys at depth
0</li>
<li><a
href="https://github.com/ljharb/qs/commit/ae0b5c7481fcc6483c4d8ff875e290b2387f87b6"><code>ae0b5c7</code></a>
[Refactor] <code>parse</code>: extract wrapping-bracket stripping
helper</li>
<li><a
href="https://github.com/ljharb/qs/commit/6459f9fc5e5036cfdb6e120fcb94df8e48f584df"><code>6459f9f</code></a>
[Tests] <code>parse</code>: pin depth-0 bracket stripping and prototype
keys kept by `al...</li>
<li><a
href="https://github.com/ljharb/qs/commit/cb6855c1eb72eb0a3ef549b3cf9fce904e6263ec"><code>cb6855c</code></a>
Merge backport release tags v6.12.6–v6.16.2 into main</li>
<li><a
href="https://github.com/ljharb/qs/commit/136912a619c2d81c090ca196ea02b6c193580d1d"><code>136912a</code></a>
v6.16.2</li>
<li>Additional commits viewable in <a
href="https://github.com/ljharb/qs/compare/v6.15.2...v6.17.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `undici` from 7.30.0 to 7.30.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/nodejs/undici/releases">undici's
releases</a>.</em></p>
<blockquote>
<h2>v7.30.1</h2>
<h2>Security fixes</h2>
<h3>High severity</h3>
<h4>Denial of service via TLS 1.2 certificate-changing
renegotiation</h4>
<p><strong>CVE-2026-87018 · <a
href="https://github.com/nodejs/undici/security/advisories/GHSA-qrxm-fqhp-jwvm">GHSA-qrxm-fqhp-jwvm</a></strong></p>
<p>A malicious or compromised HTTPS server could crash the Node.js
process by initiating TLS 1.2 renegotiation and presenting a different,
CA-trusted certificate whose hostname did not match the requested host.
The resulting socket error reached an assertion in undici's HTTP/1.1 and
HTTP/2 connection handling. This release handles post-connect
certificate errors without terminating the process.</p>
<h4>TLS certificate validation bypass through dropped <code>Agent</code>
options</h4>
<p><strong>CVE-2026-109383 · <a
href="https://github.com/nodejs/undici/security/advisories/GHSA-2m5v-pch6-74hx">GHSA-2m5v-pch6-74hx</a></strong></p>
<p><code>Agent</code> used a JSON-based clone for constructor options,
which silently removed function-valued TLS options such as
<code>checkServerIdentity</code>. Applications relying on custom
certificate or public-key verification could therefore accept a
certificate that the callback was intended to reject. This release
preserves TLS options when creating per-origin dispatchers.</p>
<h4>Cache poisoning and cross-user disclosure through cross-origin
redirects</h4>
<p><strong>CVE-2026-109829 · <a
href="https://github.com/nodejs/undici/security/advisories/GHSA-4w7w-5xrc-9h26">GHSA-4w7w-5xrc-9h26</a></strong></p>
<p>When an originless <code>Agent</code> combined redirect and
shared-cache interceptors as <code>compose(redirect(), cache())</code>,
a response from a cross-origin redirect could be stored under the
original URL. This could allow persistent cache poisoning or disclosure
of user-specific responses to later callers. This release verifies the
response origin before caching and preserves redirect-hop cache-control
policy.</p>
<h4>Denial of service through excessive <code>Content-Encoding</code>
values</h4>
<p><strong>CVE-2026-109869 · <a
href="https://github.com/nodejs/undici/security/advisories/GHSA-xjj6-f33q-929c">GHSA-xjj6-f33q-929c</a></strong></p>
<p>A malicious HTTP server could crash the Node.js process by returning
more than five content codings and closing the connection before sending
the body. Although <code>fetch()</code> rejected the response, an error
from an orphaned internal stream could remain unhandled. This release
validates the content-encoding chain before creating the body stream and
aborts oversized chains safely.</p>
<h3>Medium severity</h3>
<h4>Denial of service through the <code>decompress</code>
interceptor</h4>
<p><strong>CVE-2026-87029 · <a
href="https://github.com/nodejs/undici/security/advisories/GHSA-55wc-3pg8-jfw6">GHSA-55wc-3pg8-jfw6</a></strong></p>
<p>An exception thrown by a downstream response handler from an
asynchronous decompression callback could escape as an uncaught
exception. A server could trigger this when <code>decompress</code> was
composed with a handler such as <code>dump</code> with a size limit.
This release converts downstream handler exceptions into per-request
errors.</p>
<h4>Subresource Integrity bypass with non-lowercase algorithm
tokens</h4>
<p><strong>CVE-2026-109841 · <a
href="https://github.com/nodejs/undici/security/advisories/GHSA-f72v-6w45-q472">GHSA-f72v-6w45-q472</a></strong></p>
<p><code>fetch()</code> treated valid mixed-case or uppercase SRI
algorithm tokens, such as <code>SHA256</code>, as unknown and could
accept a response whose body did not match the configured digest. This
release normalizes algorithm tokens before validation and digest
selection.</p>
<h4>Denial of service through unbounded EventSource parser state</h4>
<p><strong>CVE-2026-109924 · <a
href="https://github.com/nodejs/undici/security/advisories/GHSA-4qv8-2p9j-2685">GHSA-4qv8-2p9j-2685</a></strong></p>
<p>A malicious Server-Sent Events endpoint could exhaust process memory
by sending many tiny <code>data:</code> fields without completing an
event, or by splitting an unfinished line across many chunks. This
release bounds retained EventSource parser state, including field
counts, buffered chunks, unfinished-line length, and retained field
data.</p>
<h4>Denial of service through unbounded automatic WebSocket PONG
buffering</h4>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/nodejs/undici/commit/7507102a675b082a71437f0a00681e537875556c"><code>7507102</code></a>
Bumped v7.30.1 (<a
href="https://redirect.github.com/nodejs/undici/issues/6022">#6022</a>)</li>
<li><a
href="https://github.com/nodejs/undici/commit/7d60c7b6ee342d3b5489beef02a88e3da72d6463"><code>7d60c7b</code></a>
[v7.x] fix(cookies): backport <a
href="https://redirect.github.com/nodejs/undici/issues/5663">#5663</a>,
<a
href="https://redirect.github.com/nodejs/undici/issues/5808">#5808</a>
and <a
href="https://redirect.github.com/nodejs/undici/issues/5832">#5832</a>
(<a
href="https://redirect.github.com/nodejs/undici/issues/6019">#6019</a>)</li>
<li><a
href="https://github.com/nodejs/undici/commit/0227d5de51ed14d88a95e2cd4c01abddfce9b5fc"><code>0227d5d</code></a>
fix: avoid unbounded recursion in Set-Cookie attribute parser (<a
href="https://redirect.github.com/nodejs/undici/issues/5757">#5757</a>)
(<a
href="https://redirect.github.com/nodejs/undici/issues/5997">#5997</a>)</li>
<li><a
href="https://github.com/nodejs/undici/commit/53944624462b6797560288c55dbd8c66857bb495"><code>5394462</code></a>
fix(websocket): bound automatic PONG writes under backpressure</li>
<li><a
href="https://github.com/nodejs/undici/commit/299162740d5f39120092277e70efacb4d151c1c6"><code>2991627</code></a>
fix(eventsource): bound retained SSE parser state</li>
<li><a
href="https://github.com/nodejs/undici/commit/18bc8a1e09d15bc45815154a289dcf6fd5254d00"><code>18bc8a1</code></a>
fix(sri): accept non-lowercase algorithm tokens</li>
<li><a
href="https://github.com/nodejs/undici/commit/63cba7712ca012b2603c6aeeb4c72d8692332cd0"><code>63cba77</code></a>
fix(decompress): converge downstream handler throws to per-request
errors</li>
<li><a
href="https://github.com/nodejs/undici/commit/07ded4941b1e72afec862958c3b8ddf468f3b429"><code>07ded49</code></a>
fix(fetch): abort excessive content encoding responses</li>
<li><a
href="https://github.com/nodejs/undici/commit/b20b82ad80a405a12b6ce1f341bcd418de390aa0"><code>b20b82a</code></a>
fix(cache): prevent cross-origin redirect cache confusion</li>
<li><a
href="https://github.com/nodejs/undici/commit/04cb74b1db131ff8f5b4557dce811cbf5f4664b4"><code>04cb74b</code></a>
fix: preserve Agent connection options</li>
<li>Additional commits viewable in <a
href="https://github.com/nodejs/undici/compare/v7.30.0...v7.30.1">compare
view</a></li>
</ul>
</details>
<br />

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-01 01:25:31 +00:00
Chandler Carruth ff0d3ff107 Add another C++ keyword to avoid: enum (#8069)
I've seen at least one failure on a PR because of randomly producing
this as an identifier.
2026-10-31 22:55:52 +00:00
Chandler Carruth c0eee3e0bd Updates harden-runner and tries to stabilize (#8071)
2.22.3 appears to fix blocking legitimate connections to GitHub
infrastructure, which aligns with numerous flakes we've seen in our
actions recently.

There is also a fix to `prek`'s configuration to allow a URL that we
already allow in other workflows.

I'm also looking at how to automate all af this.

Assisted-by: Antigravity with Opus
2026-10-31 20:34:29 +00:00
Chandler Carruth c608c35e12 Rework the initial terminal rendering into the form the design landed on (#7994)
There were a number of iterations on the design that weren't reflected
in the initial port to the terminal rendering library. This PR tries to
complete the rendering side of things to match the design we landed on:
code-focused, with messages attached by lines to the code, and condensed
as much as possible to avoid the vertical space.

It also improves the styling to make things more visually
distinguishable such as adding a `->` to the main error line, and making
the underlines and connectors for it have a heavy weight (when the font
supports that).

Another major change is having multiple messages on a single line be
drawn in source lexical order. This requires the connectors to cross
without joining, but makes it easier to read the messages.

This PR also has some refactorings to enable these improvements, and a
_number_ of improvements to comment prose and terminology to try and
avoid jargon and make things read more naturally. Sorry for the fact
that Claude left a bunch of this mannerly language in comments despite
my efforts, but I'm continuing to try and drive the prose towards
something direct and simple to read. Some of the terminology
improvements also needed to be made in the design doc and those have
been made here.

Note that I don't think this rendering is perfect or should be viewed as
final. I'm happy to iterate on it further, and have a few adjustments in
mind already, but would like to focus this on getting the
_implementation_ of the current rendering design in place, and then we
can make targeted changes or tweaks as needed. For example, I'm not yet
fully happy with the rendering when messages are too long and we move
them all the way to the left, but am leaving that for a follow-up.

Assisted-by: Claude Code
2026-10-31 06:10:03 +00:00
Nicholas Bishop 5a614167c2 Update macos runners from 14 to 26 (#8064)
The macos-14 runners are being deprecated:
https://github.com/actions/runner-images/issues/13518

Add `formulae.brew.sh` and `pkg-containers.githubusercontent.com` to the
list of allowed endpoints. These are needed for `brew` on the new
runners.
2026-10-30 21:28:24 +00:00
oli-ej c6b54b57e2 Add check support for struct patterns (#7876)
Implements check and pattern matching support for struct patterns.

This PR implements complete patterns, and leaves `_` field handling for
future work.
2026-10-30 21:03:16 +00:00
Nicholas Bishop 3ee012b86a Upgrade j178/prek-action to v3.1.0 (#8065)
The previous "v2" commit hash was pointed at a commit that is no longer
in the repository. There is a v2 tag, but it points at a different
commit.

Upgrade to the latest release. (Starting in 3.0.1, the releases are now
immutable.)
2026-10-30 20:26:01 +00:00
Nicholas Bishop 54f37b35bd Fix Carbon methods always being exported as public to C++ (#8063)
Add access tests for private fields and methods, in both regular classes
and generic.

Modify `BuildCppToCarbonThunkDecl` to properly set the access rather
than always using public.

Also add `static` to a method that isn't used outside export.cpp.
2026-10-30 20:06:21 +00:00
Chandler Carruth fda41783e5 Extend and adjust the terminal API for the diagnostics rendering (#7993)
The rendering we're trying to implement based on
`toolchain/docs/diagnostics_rendering.md` needs some features the
initial terminal API didn't support:

- Heavy stroke weight on lines
- Vertical lines that cross earlier lines without joining them
- Wrapping that keeps quoted code whole

This PR adds support for these and removes the two `buffer.h`
TODOs that described them.

This PR also tries to improve the ASCII fallback quality a bit by using
`^`-lines for "heavy" weight, and using `.` and `'` when drawing tees.
Still not perfect of course, but seems reasonable given the constraints
of ASCII rendering.

Assisted-by: Claude Code
2026-10-30 07:25:03 +00:00
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
David Blaikie 4abb6100e7 Define exported C++ thunks in the LLVM function Clang uses for them (#8060)
Carbon gives the C++ declaration of an exported Carbon thunk an asm
label that
includes the target's global symbol prefix (`_` on Mach-O). Clang names
a
declaration with an asm label `\01<label>` in the IR, so that LLVM uses
the
label verbatim as the symbol name, while Carbon lowered the thunk
definition
under its unprefixed name. Both name the same symbol, but they were
distinct
globals in the IR, so LLVM couldn't see that the C++ calls refer to the
Carbon
definition. That only works while the definition is never discarded.

Mark the Carbon thunk as a `CppThunk` (as described in
`toolchain/docs/check/cpp/thunks.md`), record its C++ declaration in
`clang_decls`, and when lowering the thunk, ask Clang for its function
(creating it if necessary) and define the thunk in that function. This
means
that the thunk and the C++ calls to it always use the same IR global,
with
Clang's name and signature.

The thunk takes all of its parameters by reference, so Clang lowers its
parameters the same way Carbon does, but some C++ ABIs (such as Apple
arm64)
return `this` from constructors. In that case, return the parameter that
Clang
marks `returned`.

On targets without a global prefix, the IR names already matched, so the
only
change is that the thunk definitions now carry Clang's parameter and
function
attributes.
2026-10-30 01:22:12 +00:00
Chandler Carruth 9192ee0032 Make TokenInfo use a single 32-bit store for initialization. (#8054)
We need to zero-initialize the `token_payload_` bits in order for the
normal creation to just be zeroing the whole structure. We do this only
when MSan isn't enabled so we don't lose bit-accurate bug detection.

Assisted-by: Antigravity with Gemini
2026-10-28 20:51:45 +00:00
Chandler Carruth 07718a8329 Switch symbol token to be templated on its character to simplify its dispatch (#8041)
Instead of using a table to select a bool parameter, this embeds the
first character as a template parameter and uses `if constexpr` in the
dispatch body to only test multi-character symbols with that start with
that character.

Also template the one-char symbol lexing dispatch routines to remove
runtime lookup in the bool table, and use constexpr property queries of
the token kinds.

Last but not least, add a table to precompute a table at compile time to
synthesize `LexError` based on the last byte.

Benchmark results (./scripts/bench_runner.py --runs=12,
-march=x86-64-v3):

- BM_CompileApiFileDenseDecls<Lang::Carbon, Phase::Lex>/65536: CPU Time:
-5.297%
- BM_CompileApiFileDenseDecls<Lang::Carbon, Phase::Lex>/262144: CPU
Time: -4.936%
- BM_RandomSource: CPU Time: -4.475%, tokens_per_second: +4.684%
- BM_GroupingSymbols/32/32/32: CPU Time: -3.215%, tokens_per_second:
+3.321%

Assisted-by: Antigravity with Gemini
2026-10-28 16:48:52 +00:00
Chandler Carruth 22f3cd3539 Factor hot functions in the parser's Context to allow fast path inlining (#8053)
This moves the fast path of several functions into the header, and
extracts and sinks the slow paths into cold-attributed out-of-line
functions. The result is that the large cold code is separated and the
fast path is easily inlined into callers.

Also does some factoring to have the simplest and most minimal dispatch
to the cold paths throughout.

Benchmark results (./scripts/bench_runner.py --runs=12,
-march=x86-64-v3):

- BM_CompileApiFileDenseDecls<Lang::Carbon, Phase::Parse>/16384: CPU
Time: -7.285%
- BM_CompileApiFileDenseDecls<Lang::Carbon, Phase::Parse>/65536: CPU
Time: -6.995%
- BM_CompileImplFile<Lang::Carbon, Phase::Parse>/16384: CPU Time:
-7.341%
- BM_CompileImplFile<Lang::Carbon, Phase::Parse>/65536: CPU Time:
-5.805%

Assisted-by: Antigravity with various models
2026-10-28 16:23:27 +00:00
Richard Smith 6d51427790 Add tests for generic terse function declarations. (#8051)
We have test coverage for both features in isolation, but it seems
worthwhile having coverage for the combination of the two.
2026-10-28 15:33:40 +00:00
Chandler Carruth 0421bf3b43 Optimize token kinds and whitespace flags (#8044)
Currently, these lookup the token in its `ValueStore`, and then access
the kind and flag from that. This is near-optimal in the lexer while
_writing_ the tokens, but ends up being considerably slower in parsing
when the overhead of digging the token out of the value store dominates
the actual use case.

This PR does an struct-of-arrays reorganization of the kinds and
whitespace flags, but only after lexing. This lets lexing write a linear
series of info structs, and only afterward re-organize into a
query-optimized representation that is a struct of arrays.

However, to make this a net performance gain, we have to create the
arrays _very_ efficiently. We can avoid hand-written assembly here by
very carefully and precisely writing the inner loop to build these
arrays so that LLVM will vectorize it into high-quality SIMD code for us
across both x86 and Arm. We also need the arrays to have a particular
shape (for example, using a `SmallVector<bool>` rather than a
`BitVector`) in order to preserve this vectorized creation. It burns a
tiny amount of memory, but is key to the whole thing being a net win of
5% to 10% across parsing.

Benchmark results (./scripts/bench_runner.py --runs=12,
-march=x86-64-v3):

- BM_CompileApiFileDenseDecls<Lang::Carbon, Phase::Parse>/16384: CPU
Time: -9.045%
- BM_CompileApiFileDenseDecls<Lang::Carbon, Phase::Parse>/65536: CPU
Time: -6.526%
- BM_CompileImplFile<Lang::Carbon, Phase::Parse>/16384: CPU Time:
-9.275%
- BM_CompileImplFile<Lang::Carbon, Phase::Parse>/65536: CPU Time:
-8.382%

Assisted-by: Antigravity with both Gemini and Claude
2026-10-28 05:36:57 +00:00
Dana Jansens e6fddcf975 Add all stores in File to CollectMemUsage (#8050)
A number of stores have been added to File without updating
CollectMemUsage. Add all the stores that were missed, ordering
everything by the order the fields appear in the class. Also include the
filename_ string, which requires a new overload of Collect, since
strings are like vectors of chars.
2026-10-27 22:16:05 +00:00
Chandler Carruth c008bec1a5 Fix clang-tidy issue with the u suffix (#8047)
Assisted-by: Antigravity with Gemini
2026-10-27 21:35:31 +00:00
Chandler Carruth f27a977d86 Fix ASan CI failures due to preserve_none (#8045)
These only show up post-merge as we're only running ASan there.

This disables the recent `[[clang::preserve_none]]` optimization when
ASan is enabled as there is an LLVM bug on AArch64 that causes register
corruption with that calling convention. This is factored into a common
helper library so we can use it in other places as well.

Assisted-by: Antigravity with Gemini
2026-10-27 21:05:53 +00:00
Dana Jansens a450026679 Clarify the infinitely recursive types through interop test (#8046)
This test generates an infinite recursion between Carbon and C++ making
a new type at each step through the recursion, producing an infinite
number of types. We improve the test in a few ways:
- Remove direct dependencies on min_prelude parts, we should just depend
on a single min_prelude, and not `import Core` explicitly.
- Remove a dependency on `i32` so we rely on less prelude.
- Remove the use of pointers, which currently introduce a dependency on
`Optional`, so we rely on less prelude. Instead use a generic `Wrap(T)`
type that we nest repeatedly to generate types.
- Add some comments and expand function names to help explain the
behaviour being tested

Now the test just depends on the convert.carbon prelude, which makes it
much simpler when running under a debugger.
2026-10-27 20:58:00 +00:00
Chandler CarruthandGeoff Romer 33863ed07c Enhance benchmark source to have more call expressions (#8029)
This adds free function declarations to the top of the source and
adjusts many of the synthesized expressions in the benchmark source to
emit calls to them. This gets the distribution of expressions in our
generated definitions to be significantly more realistic compared to
existing C++ function bodies here and in LLVM.

This still doesn't get us to start calling more methods, have class
types in the signatures of the calls, or have more interesting call
graphs, but seems like a reasonable first step and still an improvement
in the overall fidelity of our benchmarks.

Illustrative generated Carbon:

```carbon
fn Abcde(x: i32, y: i64*) -> (bool, i64);
fn Fghij(p: i32*) -> i32;
// ...
fn MyClass.Method(self, p: i32) -> i32 {
  var acc64: i64 = 0;
  var acc: i32 = acc64 as i32;
  acc = acc + self.Checksum() + p + 1;
  let (abc: bool, def: i64) = Abcde(0, &acc64);
  acc = (acc + (if abc then 1 else 0) + (def as i32)) + Fghij(&acc) * 2;
  return 0;
}
```

Assisted-by: Claude Code and Antigravity with Gemini

---------

Co-authored-by: Geoff Romer <gromer@google.com>
2026-10-27 06:34:44 +00:00
Chandler Carruthandjosh11b 4c5734a8cc Make tag application and removal a compile time property (#8042)
For stores with untagged ID types, `Apply` and `Remove` still did (some)
of the work, despite the fact that it could never be needed. Because
both depend on runtime parts of the class, we need to explicitly check
whether the store's IDs are tagged at compile time with `if constexpr`.

This is important because the hottest value stores aren't tagged, and we
get significant performance uplift in lexing and parsing.

Benchmark results (./scripts/bench_runner.py --runs=12,
-march=x86-64-v3):
- BM_CompileApiFileDenseDecls<Lang::Carbon, Phase::Lex>/65536: CPU Time:
-4.713%
- BM_CompileApiFileDenseDecls<Lang::Carbon, Phase::Parse>/65536: CPU
Time: -7.251%
- BM_CompileImplFile<Lang::Carbon, Phase::Lex>/65536: CPU Time: -8.016%
- BM_CompileImplFile<Lang::Carbon, Phase::Parse>/65536: CPU Time:
-9.477%

Assisted-by: Antigravity with various models

---------

Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com>
2026-10-26 23:39:35 +00:00
Chandler CarruthandGeoff Romer 5f1dd4941a Optimize the horizontal space skip fast path for x86 (#8033)
This uses SSE2 to optimize the horizontal space skipping path when there
is more than a single character. That helps with indentation and other
edge cases and doesn't cost much because we only begin when >1
horizontal space to skip.

We use SSE2 here which is available ubiquitously as AVX2 doesn't provide
any significant advantage.

Agy's benchmarks (`./scripts/bench_runner.py --runs=12`, `-c opt
--copt=-march=x86-64-v3`, vs. `trunk`):
- `BM_HorizontalWhitespace/16`: -4.610% CPU time, +4.832% bytes/s &
tokens/s
- `BM_HorizontalWhitespace/64`: -45.977% CPU time, +85.106% bytes/s &
tokens/s
- `BM_HorizontalWhitespace/128`: 0.428x CPU time, 2.338x bytes/s &
tokens/s

Assisted-by: Antigravity with Gemini

---------

Co-authored-by: Geoff Romer <gromer@google.com>
2026-10-26 22:46:56 +00:00
Chandler Carruth 7cc1d08989 Add an AVX2 optimized newline scanner (#8032)
The `memchr` approach didn't really fully capture what we can do with
AVX2, so build a dedicated optimized path for it.

The optimized path does carefully optimized 64-byte block scanning and
decodes the newline positions. I've tried to ensure there are enough
comments to understand the different micro-optimizations that went into
it getting the full performance potential.

My agent's benchmarks (`./scripts/bench_runner.py --runs=12`, `-c opt
--copt=-march=x86-64-v3`, vs. `trunk`):
- `BM_BlankLines/16`: -11.158% CPU time, +12.557% bytes/s & lines/s
- `BM_BlankLines/64`: -21.623% CPU time, +27.587% bytes/s & lines/s
- `BM_BlankLines/128`: -26.332% CPU time, +35.742% bytes/s & lines/s
- `BM_CommentLines/128/0/0`: -22.198% CPU time, +28.531% bytes/s &
lines/s

Assisted-by: Antigravity with Gemini
2026-10-26 21:37:49 +00:00
Christopher Di Bella 05a7ecbd34 Move SubobjectDestroy and SelfDestruct out of interface Destroy (#7942)
Determining that a type has trivial destruction requires checking
whether all of its subobjects have trivial destruction and whether its
type has a custom witness for `Destroy.Op`. This isn't possible when
`Destroy` is the custodian for `Destroy.Op` and `SubobjectDestroy.Op`,
so we need to make `SubobjectDestroy` its own interface.

`Destroy.SelfDestruct` becomes a standalone function because it depends
on `SubobjectDestroy` and `Destroy` being implemented, and is also
responsible for generating any custom witnesses for both interfaces.
Making `SelfDestruct` an interface was experimented with, but this
required a blanket impl that caused issues for facet types.

`Destroy` depends on `SubobjectDestroy` to be implemented. We'd ideally
use `require impls SubobjectDestroy`, but it isn't complete yet, and it
also causes problems when `Destroy` is implemented inside the class
definition. We work around this by temporarily renaming the `Destroy`
interface to `DestroyImpl`, and introducing a constraint called
`Destroy` so that user code doesn't need to write `Core.SubobjectDestroy
& Core.Destroy`.
2026-10-26 21:35:01 +00:00
Christopher Di Bella 57bf903ef1 Keep resolving generics and templates until they're all resolved (#7957)
`FinishAst` resolves C++ template instantiation. Template instantiation
can complete Carbon types, generate thunks, and call generic functions.
These operations can add new entries to `definitions_required_by_use()`.
Similarly, resolving any of those can generate more C++ templates that
need instantiating. This change moves `FinishAst` ahead of
`CheckRequiredDefinitions` and loops until no new Carbon definitions
have been added. This prevents later passes from crashing when
evaluating specifics on unresolved generics or uninstantiated templates.

`FinishAst` also disconnects the AST. Subsequent definitions and `impl`s
may depend on C++ interop, so the AST must remain connected until all
the Carbon end-of-file checks complete. We achieve this by decoupling
AST disconnection from finalisation.

Assisted-by: Antigravity with Gemini
Assisted-by: Antigravity with Claude Opus 5.5 Max
2026-10-26 20:52:43 +00:00
dependabot[bot] dd30c76556 Bump fast-uri from 3.1.8 to 3.1.9 in /utils/vscode in the npm_and_yarn group across 1 directory (#8028)
Bumps the npm_and_yarn group with 1 update in the /utils/vscode
directory: [fast-uri](https://github.com/fastify/fast-uri).

Updates `fast-uri` from 3.1.8 to 3.1.9
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/fastify/fast-uri/releases">fast-uri's
releases</a>.</em></p>
<blockquote>
<h2>v3.1.9</h2>
<h2>Security</h2>
<p>This security release fixes four URI parsing and normalization
vulnerabilities in fast-uri 3.x. Applications that process untrusted URI
input should upgrade to v3.1.9.</p>
<ul>
<li><strong>Medium — URI scheme spoofing through percent-encoded
schemes</strong> (<a
href="https://github.com/fastify/fast-uri/security/advisories/GHSA-w74x-v6f9-j3xg">GHSA-w74x-v6f9-j3xg</a>,
CVE-2026-110306): percent-encoded scheme input is now rejected instead
of being decoded into a live scheme.</li>
<li><strong>Medium — Host confusion during domain-host
canonicalization</strong> (<a
href="https://github.com/fastify/fast-uri/security/advisories/GHSA-wjr9-j795-jxcr">GHSA-wjr9-j795-jxcr</a>,
CVE-2026-110180): malformed domain hosts containing ambiguous delimiters
or stripped control characters are now rejected instead of being
silently rewritten.</li>
<li><strong>Low — Incorrect relative-reference normalization</strong>
(<a
href="https://github.com/fastify/fast-uri/security/advisories/GHSA-xhq7-xmqj-769h">GHSA-xhq7-xmqj-769h</a>,
CVE-2026-110095): dot-segment removal now preserves the resolution
semantics of relative references.</li>
<li><strong>Low — Scheme injection in suffix references</strong> (<a
href="https://github.com/fastify/fast-uri/security/advisories/GHSA-333x-w4r6-6crp">GHSA-333x-w4r6-6crp</a>,
CVE-2026-110725): first-segment colons in path-only suffix references
are now escaped so the output is not reparsed as a scheme.</li>
</ul>
<p>Upgrade with:</p>
<pre lang="sh"><code>npm install fast-uri@3.1.9
</code></pre>
<p>Thanks to <a href="https://github.com/zekth"><code>@​zekth</code></a>
for reporting the scheme, host, and relative-reference issues, and to <a
href="https://github.com/UlisesGascon"><code>@​UlisesGascon</code></a>
for finding the suffix-reference issue and reviewing the
remediations.</p>
<h2>What's Changed</h2>
<ul>
<li>ci: enable trusted publishing releases (v3.x) by <a
href="https://github.com/mcollina"><code>@​mcollina</code></a> in <a
href="https://redirect.github.com/fastify/fast-uri/pull/229">fastify/fast-uri#229</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/fastify/fast-uri/compare/v3.1.8...v3.1.9">https://github.com/fastify/fast-uri/compare/v3.1.8...v3.1.9</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/fastify/fast-uri/commit/0def70338a0fa2f12a10c83ae8eafc2139ae9a07"><code>0def703</code></a>
Bumped v3.1.9</li>
<li><a
href="https://github.com/fastify/fast-uri/commit/a1d0da2f5c703244027c1b204c1aebd0516da338"><code>a1d0da2</code></a>
test: generate only relative path references</li>
<li><a
href="https://github.com/fastify/fast-uri/commit/9eb8759a9825c046f57f7f695f1944bc39b88abd"><code>9eb8759</code></a>
fix: preserve relative paths in suffix and relative resolution</li>
<li><a
href="https://github.com/fastify/fast-uri/commit/54c30f75891b74e5a4393b44be9ea7ba43dd1e4b"><code>54c30f7</code></a>
docs: note suffix-reference authority ambiguity</li>
<li><a
href="https://github.com/fastify/fast-uri/commit/39e6493c1356f30a0084171627eb266786e1620e"><code>39e6493</code></a>
fix: escape a first-segment colon in suffix references</li>
<li><a
href="https://github.com/fastify/fast-uri/commit/745183c1d9d98d113bed8a57bb1c9ceaf0147b7a"><code>745183c</code></a>
test: cover first-segment colon in suffix references</li>
<li><a
href="https://github.com/fastify/fast-uri/commit/ddfc6a51122dfce0365655f04479fc3adb7f6b3c"><code>ddfc6a5</code></a>
fix(SEC-03): reject non reg-name characters in domain hosts</li>
<li><a
href="https://github.com/fastify/fast-uri/commit/d86a8d29f45187819dac2d4fe171c35e3e488264"><code>d86a8d2</code></a>
fix: keep a colon-first segment behind &quot;./&quot; in relative
normalization</li>
<li><a
href="https://github.com/fastify/fast-uri/commit/eb483b5da2580bdecb8af644bbfee878d5424850"><code>eb483b5</code></a>
test: cover colon-first relative references in normalization</li>
<li><a
href="https://github.com/fastify/fast-uri/commit/b110a3571c37dfcf9d1c72ce2e2e178c78d1a424"><code>b110a35</code></a>
fix(SEC-02): preserve meaning of relative references during
normalization</li>
<li>Additional commits viewable in <a
href="https://github.com/fastify/fast-uri/compare/v3.1.8...v3.1.9">compare
view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a
href="https://www.npmjs.com/~GitHub%20Actions">GitHub Actions</a>, a new
releaser for fast-uri since your current version.</p>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=fast-uri&package-manager=npm_and_yarn&previous-version=3.1.8&new-version=3.1.9)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/carbon-language/carbon-lang/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-26 20:28:43 +00:00
Chandler CarruthandGeoff Romer 600fd55ba7 Add a realistic header compile benchmark (#8023)
Add `BM_CompileApiFileRealisticHeader`, which models a typical C++
header. Each class defines 2 getters, 1 predicate, 2 forwarders, and 2
function definitions inline, and declares 2 methods along with the
private methods that the forwarders call. The dense declarations
benchmarks keep modeling a declaration-heavy API file.

The counts come from classifying, with libclang, the member functions
defined in class definitions in the hand-written headers that the
toolchain includes: 1,612 in the Carbon toolchain's headers, 10,406 in
LLVM's, and 13,366 in Clang's. Classified the same way, the generated
C++ defines 69% of its member functions inline in the class definition,
against 61-70% in the real headers. 78% of its inline definitions are a
single statement, against 72-82%, and 21% return the result of a member
call, against 19-21%. Only 2% have control flow, against 7-13%, because
most inline function definitions have only the three statements that
every plan-based definition has.

Assisted-by: Claude Code and Antigravity with Gemini

---------

Co-authored-by: Geoff Romer <gromer@google.com>
2026-10-26 20:20:06 +00:00
Nicholas Bishop 56d9bbc40a Support C++ friend declarations for Carbon generic classes (#8026) 2026-10-26 17:09:43 +00:00
Chandler Carruth 8779d15e18 Optimize the musttail-dispatch in the lexer (#8034)
The primary performance challenge with `musttail` dispatch is register
allocation. We can use the `[[clang::preserve_none]]` calling convention
to address this, dramatically improving the register pressure across
these dispatched routines.

However, to be effective, we need to very carefully manage inlining to
keep out-of-line functions from breaking the ideal register allocation,
but keep the right functions out-of-line. A bunch of annontations are
added to push things in one direction or another.

This also requires us to move error handling out of the tight dispatch
loop in more places, for example by returning `LexResult::NoMatch()` and
letting the outer loop handle it.

Agy's benchmarks (`./scripts/bench_runner.py --runs=12`, `-c opt
--copt=-march=x86-64-v3`, vs. `trunk`):
- `BM_CompileApiFileDenseDecls<Lang::Carbon, Phase::Lex>/65536`: -9.594%
CPU time
- `BM_CompileApiFileDenseDecls<Lang::Carbon, Phase::Lex>/262144`:
-7.786% CPU time
- `BM_ValidIdentifiers<3, 5, true>`: -9.194% CPU time, +10.125% bytes/s
& tokens/s
- `BM_RandomSource`: -5.062% CPU time, +5.332% bytes/s, lines/s &
tokens/s

Assisted-by: Antigravity with Gemini
2026-10-26 16:41:19 +00:00
Nicholas Bishop e9d8d06f92 Add lifetime comments to CanonicalValueStore and IdentifierStore (#8037)
Followup to https://github.com/carbon-language/carbon-lang/pull/8025.
It's easy to accidentally put a string into the `IdentifierStore` that
doesn't live long enough. Add some comments to document this pitfall.
2026-10-26 16:35:11 +00:00
Lucile Rose Nihlen c7de727575 Adds support for exporting default values for concrete parameters of generic functions. (#8019)
Adds support for exporting both the generic function decl with concrete
default arguments to clang, as well as exporting the monomorphized
concrete function def.

This required adding the `SpecificId` to the exported global variable
name, to avoid duplicate names in the global vars. I also now set up an
evaluated context in `clang_sema` when declaring the default arg
expression with clang, to avoid the problem of emitting concrete AST
expressions in an unevaluated context from the in-progress template
function decl context when exporting default values for generics.

Note: Default values for generic parameters of generic functions are not
yet supported.
2026-10-26 16:21:41 +00:00
Chandler Carruth 21c825a3f7 Hot-path optimizations for (Canonical)ValueStore::Add (#8031)
Extract the allocation path (which is inherently cold due to growth).
Making the outlining effective requires both blocking further inlining
and using `preserve_most` so that the existence of the call doesn't
disrupt the register allocation.

We also mark the insertion statements with callbacks as `always_inline`
so it being cold doesn't cause it to be left out-of-line and further
disrupt register allocation. It's a bit strange to force inlining of a
cold-path function, but the register impact of the call extends back
into the hot-path.

Agy's benchmarks (`./scripts/bench_runner.py --runs=12`, `-c opt
--copt=-march=x86-64-v3`, vs. `trunk`):
- `BM_ValidIdentifiers<3, 5, true>`: -6.277% CPU time
- `BM_ValidIdentifiers<3, 16, true>`: -4.525% CPU time
- `BM_CompileApiFileDenseDecls<Lang::Carbon, Phase::Lex>/262144`:
-2.012% CPU time (47.5 ms ± 6.358% -> 46.54 ms ± 1.773%, p=0.0167)

Assisted-by: Antigravity with Gemini
2026-10-26 06:38:17 +00:00
Nicholas Bishop 8fdc8ebc37 Fix use-after-free of var name in BuildDefaultValueExpr (#8025)
The `var_name` string was built on the stack and then added to the
`IdentifierStore`. This lead to use-after-free, since `IdentifierStore`
just stores a `llvm::StringRef`.

Add it to `ast_context().Idents` to provide a longer-lived reference.
2026-10-25 15:26:19 +00:00
dependabot[bot] 224a22ef30 Bump markdown-it from 14.3.2 to 14.3.3 in /utils/vscode in the npm_and_yarn group across 1 directory (#8014)
Bumps the npm_and_yarn group with 1 update in the /utils/vscode
directory: [markdown-it](https://github.com/markdown-it/markdown-it).

Updates `markdown-it` from 14.3.2 to 14.3.3
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/markdown-it/markdown-it/blob/14.3.3/CHANGELOG.md">markdown-it's
changelog</a>.</em></p>
<blockquote>
<h2>[14.3.3] - 2026-10-16</h2>
<h3>Security</h3>
<ul>
<li>Backported 15.0.3 fixes.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/markdown-it/markdown-it/commit/d372f0d8a68bef0b0acaa68868ce8ae8ba85b384"><code>d372f0d</code></a>
14.3.3 released</li>
<li><a
href="https://github.com/markdown-it/markdown-it/commit/d99f48839d37af6fd8a2617516c43dab90755503"><code>d99f488</code></a>
Backported 15.0.3 fixes</li>
<li>See full diff in <a
href="https://github.com/markdown-it/markdown-it/compare/14.3.2...14.3.3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=markdown-it&package-manager=npm_and_yarn&previous-version=14.3.2&new-version=14.3.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/carbon-language/carbon-lang/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-24 02:49:46 +00:00
Lucile Rose Nihlen 427c448dcd Simplify the generic function default values test. (#8027)
As demonstrated in #8020, removes an extraneous
dependency on `Core.Copy` in favour of a locally
declared interface.
2026-10-24 01:34:40 +00:00
dependabot[bot] 7601be9666 Bump certifi from 2024.7.4 to 2026.10.19 in /github_tools in the pip group across 1 directory (#8024)
Bumps the pip group with 1 update in the /github_tools directory:
[certifi](https://github.com/certifi/python-certifi).

Updates `certifi` from 2024.7.4 to 2026.10.19
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/certifi/python-certifi/commit/662df63d7906df66f1f07c0751a3dfe9da14d914"><code>662df63</code></a>
Restore unaffected emSign roots (<a
href="https://redirect.github.com/certifi/python-certifi/issues/439">#439</a>)
(<a
href="https://redirect.github.com/certifi/python-certifi/issues/440">#440</a>)</li>
<li><a
href="https://github.com/certifi/python-certifi/commit/5cb238caeeb6c30ec1a7309ad3e16a5aa7b10ce8"><code>5cb238c</code></a>
Remove emsign (<a
href="https://redirect.github.com/certifi/python-certifi/issues/437">#437</a>)</li>
<li><a
href="https://github.com/certifi/python-certifi/commit/69494a764d0a08f90838d53d4b0fefd3e84045d6"><code>69494a7</code></a>
Bump actions/download-artifact from 8.0.1 to 8.0.2 (<a
href="https://redirect.github.com/certifi/python-certifi/issues/436">#436</a>)</li>
<li><a
href="https://github.com/certifi/python-certifi/commit/029c7b0913d79e2efb73a216fc5c5e13ef958536"><code>029c7b0</code></a>
Bump actions/upload-artifact from 7.0.1 to 7.0.2 (<a
href="https://redirect.github.com/certifi/python-certifi/issues/435">#435</a>)</li>
<li><a
href="https://github.com/certifi/python-certifi/commit/9d0a8f1f3a0d6b2e38d5773db7afe1a37e4527b6"><code>9d0a8f1</code></a>
Add support for Python 3.15 (<a
href="https://redirect.github.com/certifi/python-certifi/issues/432">#432</a>)</li>
<li><a
href="https://github.com/certifi/python-certifi/commit/db472abf0a37067cb4488f036f4c19adf6b6ec3c"><code>db472ab</code></a>
Bump pypa/gh-action-pypi-publish from 1.14.1 to 1.14.2 (<a
href="https://redirect.github.com/certifi/python-certifi/issues/431">#431</a>)</li>
<li><a
href="https://github.com/certifi/python-certifi/commit/f4bc676bc101fe2235846e37044e8c693d6cbaf4"><code>f4bc676</code></a>
2026.07.22 (<a
href="https://redirect.github.com/certifi/python-certifi/issues/428">#428</a>)</li>
<li><a
href="https://github.com/certifi/python-certifi/commit/4c91f9c5f9b4676d40d2930025ba04d80dd536f6"><code>4c91f9c</code></a>
Bump actions/setup-python from 6.3.0 to 7.0.0 (<a
href="https://redirect.github.com/certifi/python-certifi/issues/427">#427</a>)</li>
<li><a
href="https://github.com/certifi/python-certifi/commit/01a66c5cf32eadb8f03a0504c24cb44f6d581248"><code>01a66c5</code></a>
Bump actions/checkout from 7.0.0 to 7.0.1 (<a
href="https://redirect.github.com/certifi/python-certifi/issues/426">#426</a>)</li>
<li><a
href="https://github.com/certifi/python-certifi/commit/eb355f41cb71f71012ecf1a4d27a57d0ee2b1337"><code>eb355f4</code></a>
Bump pypa/gh-action-pypi-publish from 1.14.0 to 1.14.1 (<a
href="https://redirect.github.com/certifi/python-certifi/issues/425">#425</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/certifi/python-certifi/compare/2024.07.04...2026.10.19">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=certifi&package-manager=pip&previous-version=2024.7.4&new-version=2026.10.19)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/carbon-language/carbon-lang/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-23 20:01:15 +00:00
Dana Jansens bcb236e6cd Some comment fixups from #7401 (#7421) 2026-10-23 19:59:09 +00:00
Nicholas Bishop 6f6a01f859 Support C++ friend declarations for Carbon classes (#8018)
When importing a C++ class, check for friend declarations for types and
add them to the Carbon class's `friend_scopes`.
2026-10-23 17:07:13 +00:00
Chandler CarruthandGeoff Romer 789f582eb1 Extend plan-based definition synthesis with class-typed locals and destructuring (#8016)
This re-lands #8001 (reverted in #8009 after accidentally merging with
earlier unmerged PRs in the stack).

Build on the plan-based emission to make function definitions cover a
more representative breadth of the language: class-typed local
variables, and local variables declared with destructuring. This
requires some complexity to initialize and manage the class type
variables, but ultimately should make all of our compile time benchmark
data more representative by covering a more realistic breadth of the
language surface.

Claude's rough sampling of Carbon, Clang, and LLVM source suggests class
types range from 30% to 80% of the locals, and structured bindings range
from 0.2% to 2%. When synthesizing, we target approximately 50% of
declarations as class types and 2% as destructurings as we anticipate
destructuring to be of growing popularity and so want to ensure we're
measuring any overhead there.

Assisted-by: Claude Code and Antigravity with Gemini

Co-authored-by: Geoff Romer <gromer@google.com>
2026-10-23 15:58:42 +00:00
Nicholas BishopandChristopher Di Bella 434c564a94 Fix crash when diagnosing an invalid qualified name access (#8012)
If a generic class inherits from a private parent (as allowed in C++),
and attempts to access a member of that private parent, an error should
be diagnosed via `DiagnoseInvalidQualifiedNameAccess`.

A couple changes are needed to make that work, because the access
constant in this case is not a `ClassType`. Instead, it's a
`StructValue` of type `ParameterizedClassType`. So, use
`TryGetTypeIdForTypeConstantId` instead of `GetTypeIdForTypeConstantId`
(to get `None` instead of asserting on a non-type constant), and allow
getting the class ID from both `ClassType` and `ParameterizedClassType`.

---------

Co-authored-by: Christopher Di Bella <cjdb.ns@gmail.com>
2026-10-22 19:13:10 +00:00
Dana Jansens 2c39bd57d3 Simplify the generic_facet.carbon test a bit (#8020)
Drop dependencies on the prelude so it can be debugged with no prelude
import if needed. Ensure the return type of MakeC is used in C++ since
it's part of the imported function signature.
2026-10-22 18:37:13 +00:00
Chandler CarruthandDana Jansens c50e21695f Allow detecting benchmark dry runs and reduce benchmark test costs (#8013)
We use the dry-run mode for running benchmarks in a test. This is
important, but doesn't need to use large batches of files. Doing so
would cause the compilation benchmark to become excessively slow,
especially on GitHub runners.

Sadly, Google Benchmark doesn't expose its dry-run mode, but we already
have our own `main` function that injects custom logic for our
benchmarks. Extend that to detect the dry run flags and environment
variables so that we can customize the benchmark behavior in that mode.
Then we use that detection to reduce the batch size.

Also cleans up the counters computation to both be a bit better
organized (I think), and to avoid keeping the tokenized buffers we build
to count tokens around during the timed running of the benchmark.

Also cleans up a bug in some clamping code spotted while fixing this.

Assisted-by: Antigravity with Gemini

---------

Co-authored-by: Dana Jansens <danakj@orodu.net>
2026-10-22 18:00:21 +00:00
Lucile Rose Nihlen 3dbf76af0d Add some support for exporting constrained generic types to C++ (#8017)
Adds a check for constrained facet types and extracts the type from the
constraints before supplying it for conversion to C++ types. Adds a test
for this functionality.
2026-10-22 17:26:25 +00:00
Chandler Carruth 5aa7f35a1f Add another C++ keyword we need to avoid (#8015)
The enhancements to benchmarking means we can (when _very_ unlucky) hit
this now.
2026-10-22 13:22:12 +00:00
Chandler Carruth 57ba07a978 Switch inline function definition synthesis to use plans (#8000)
Now that we have a robust plan-based layer for generating function
definitions, we can use that when generating inline function definitions
rather than the very ad-hoc previous inline function definition
synthesis. This also requires switching some of the more ad-hoc
heuristics for governing the size of inline function definitions to more
robust histograms representing the desired distributions.

Some rough analysis of existing C++ code in Clang, Carbon, and LLVM
suggests that multi-statement inline function definitions and
out-of-line
definitions in implementation files have compatible distributions of
shape and statement kinds when their sizes overlap, and so we don't end
up needing to overly customize that part of the plan generation.

Getters, predicates, and forwarding functions continue to be separate
constructs from multi-statement function definitions. I looked at trying
to unify them, but that adds significantly more complexity. For now, the
plan-based synthesis is only used for definitions where we have >1
statements (happens to be >=3 because of the structure of these
definitions).

Assisted-by: Claude Code and Antigravity with Gemini
2026-10-22 07:25:01 +00:00
Chandler Carruth ebc1557345 Switch the field types to a separate type pool (#7999)
Fields have constraints on what types can be used distinct from other
uses. Previously, they shared a pool with inline function return types
when function definitions are included, the more general type pool used
for function declarations otherwise. However, fields have a unique
constraint: they cannot have the type currently being defined.

Sharing a type pool for both function returns and fields either
requires us to not use the last class in the pool (including in any
function returns), or it requires us to have a large number of function
returns inside the last class as every reference to that class can only
come from function returns inside the class. So we end up either with
an artificial constraint on return types, or being unable to have
a small number of functions per class. The latter is a pattern we want
to be able to use though, as it comes up frequently in implementation
files.

The other problem is that as we allow inline function definitions of
increasing complexity (similar to the out-of-line definitions from
plans in #7998), we end up needing different mechanisms for ensuring
return types (when class types) are spelled out a seed-independent
number of times compared to fields. Today, both inline return types
and field types are spelled out twice when class types. But inline
return types will need a more complex way of ensuring seed invariant
references once they use plan-based definitions as well.

This PR splits the shared pool into distinct pools, and does so
regardless of whether there are definitions being emitted. This in turn
allows us to simplify some things. We can omit the last class from the
pool entirely, and switch the pool to follow `declared_types_weight` in
fields. Previously it had deviated sharply from that weighting.

Assisted-by: Claude Code and Antigravity with Gemini
2026-10-22 02:07:50 +00:00
Chandler Carruthandjosh11b 9319bb0369 Add more complex function definition generation and an impl-file benchmark (#7998)
Previously, we only had the dense API declarations benchmark. This adds
a benchmark that aims to emulate what a realistic implementation file in
Carbon would look like, based on what `.cpp` files look like in Carbon,
Clang, and LLVM. It's still a little artificial in order to provide
stability, but it should give us coverage of compile time performance of
statements and function definitions at more realistic scales.

In order to generate more interesting and longer function definitions,
this introduces a plan-based approach to guide how each feature of a
definition is produced. Initially, it identifies these as roughly:

- local variables
- updates to variables
- `if`, `else`, `while` blocks
- "guard" statements -- conditions with a return, break, or continue
- read of a parameter

The plan also manages the nesting depth of these constructs.

Because the plan establishes how many of these constructs exist in a
function definition, and at what nesting depth, the emission can
randomize the order and arrangement without changing the size of the
function. This follows the pattern in all our benchmark source
generation of getting byte-identical stability while avoiding
predictable structures. Technically, I think the current approach goes a
bit too far because function definitions I suspect are somewhat
predictable in their structure in practice. But a conservative benchmark
I think is a good starting point. =]

The target distributions for definition sizes, maximum depths, and
statement mixes come from a very rough Claude-powered analysis of 10,697
function definitions in 533 Carbon toolchain, LLVM, and Clang
implementation files. These were analyzed and classified by Claude using
a one-off Python script and libclang. I haven't reviewed any of the
analysis carefully, the resulting distributions look plausible enough as
a starting point. Comparing real code against generated C++ from the
plans, each number per-definition average:

| statements | definitions | max depth   | ifs         | loops       |
|------------|-------------|-------------|-------------|-------------|
| 7-9        | 10.9 / 11%  | 1.25 / 1.23 | 1.74 / 1.86 | 0.23 / 0.15 |
| 14-19      | 7.3 / 7%    | 1.89 / 1.88 | 3.53 / 3.71 | 0.61 / 0.42 |
| 28-39      | 3.3 / 3%    | 2.76 / 2.75 | 7.75 / 8.46 | 1.26 / 1.27 |
| 60+        | 1.5 / 2%    | 4.03 / 3.80 | 25.0 / 27.1 | 3.72 / 4.04 |

To maintain stability, every definition has an accumulator, a statement
reading its parameters, and a `return`, so real definitions of one to
three statements become three.

Note that updates are currently also used instead of calls to other
functions -- a later PR will switch those to calls so that we form more
plausible call graphs. In general, subsequent PRs will refine and expand
the plan to generate increasingly more realistic function definitions.

There are probably lots of improvements that could be made here, both to
generate higher quality definitions and to improve the generation logic
to be more efficient, etc. But already this has allowed _much_ more
extensive benchmarking of Carbon's compile-time performance so it seems
likely a reasonable increment.

Assisted-by: Claude Code and Antigravity with Gemini

---------

Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com>
2026-10-21 23:02:26 +00:00
Lucile Rose Nihlen 503f09037d Add support for default values for non-generic parameters of generic functions. (#8011)
A first step in expanding support for default values for parameters in
generic functions.

Adds code to parameter type deduction to include default values for
absent call arguments. In support of that, this PR also generalizes a
function from `export.cpp` that is useful to extract a default
`ConstantId` value from an arbitrary function paramater pattern.
2026-10-21 21:03:39 +00:00
Christopher Di Bella 04f456dddd Handle SpecificConstant in FindStorageArgForInitializer (#8010)
`FindStorageArgForInitializerInSpecific` is missing a `SpecificConstant`
case, and crashes due to the function not expecting `SpecificConstant`.
`SpecificConstant` should be handled by this function, as it derives its
expression category from the instruction it wraps.

Consider this example:

```carbon
interface I {}
interface J {}
constraint C {
  extend require impls I & J;
}
```

Calls, such as `I & J`, are initializing expressions. `extend require
impls` wraps the call in a `SpecificConstant`. This code crashes when
asking for its storage, such as during formatting for `--dump-sem-ir`.

This commit adds a case for `SpecificConstant`, deferring to its wrapped
instruction when transitive lookup is allowed, and reports no storage
otherwise.

Assisted-by: Antigravity with Claude Opus 5.5
2026-10-21 20:04:27 +00:00
Karnajeet Gosaviandkg290 bc45989b28 Reply to document requests for unknown Carbon files (#7853)
Fixes #7802.

If a document-symbol or formatting request refers to an unknown Carbon
file, return an LSP `InvalidParams` error instead of leaving the client
waiting. This aligns both handlers with existing behavior for
position-based requests.

---------

Co-authored-by: kg290 <210736705+kg290@users.noreply.github.com>
2026-10-21 19:10:34 +00:00
Dana Jansens ac31fd1985 Give IdentifiedFacetType a more foolproof API (#8008)
The values in `IdentifiedFacetType` may contain `IdentifiedWitness`
instructions, and those insts need to be either converted to
`LookupImplWitness` and evaluated as such, or substituted by the user of
the `IdentifiedFacetType` with witnesses of its own.

This changes the API so that the user must go through a method doing one
of those two things to get to the underlying values in the
`IdentifiedFacetType`, unless they explcitly request to bypass them.

Then we split extend impls requirements out into their own set so that
the user can determine what is being extended _after_ evaluation or
substitution of `IdentifiedWitness` insts. This is critical because we
can not dedupe SpecificInterfaces until after that takes places, as
demonstrated by the test fixed by this PR.
2026-10-21 18:32:30 +00:00
Chandler Carruth 70c696e782 Revert "Extend plan-based definition synthesis with class-typed locals and destructuring" (#8009)
Reverts carbon-language/carbon-lang#8001

This depended on 3 unmerged PRs.
2026-10-21 18:24:43 +00:00
Chandler CarruthandGeoff Romer c3877cf701 Extend plan-based definition synthesis with class-typed locals and destructuring (#8001)
Build on the plan-based emission to make function definitions cover a
more representative breadth of the language: class-typed local
variables, and local variables declared with destructuring. This
requires some complexity to initialize and manage the class type
variables, but ultimately should make all of our compile time benchmark
data more representative by covering a more realistic breadth of the
language surface.

Claude's rough sampling of Carbon, Clang, and LLVM source suggests class
types range from 30% to 80% of the locals, and structured bindings range
from 0.2% to 2%. When synthesizing, we target approximately 50% of
declarations as class types and 2% as destructurings as we anticipate
destructuring to be of growing popularity and so want to ensure we're
measuring any overhead there.

Assisted-by: Claude Code and Antigravity with Gemini

---------

Co-authored-by: Geoff Romer <gromer@google.com>
2026-10-21 17:43:23 +00:00
Dana Jansens fb7eff130d Move IdentifiedFacetType to check (#8004)
`IdentifiedFacetType` is a concept that is only used by check. It does
not appear in the semir output, unlike `DeclaredFacetType`s.
`IdentifiedFacetType` is used in check to collapse named constraints
together, building a single structure of requirements from a
`DeclaredFacetType` and any referenced named constraints. This is used
for operations like impl lookup, and building `FacetValue`s.

The outputs of `IdentifiedFacetType` are now in an intermediate state,
where they contain `IdentifiedWitness`es, and need to be processed
before they are used. We'd like to build that into the API of
`IdentifiedFacetType` so that their use is more robust. But the
`IdentifiedWitnesses` need to be replaced by subst, which is a part of
check, so we need to move `IdentifiedFacetType` to check in order to
build the type on top of subst.
2026-10-21 13:20:54 +00:00
Chandler Carruth 0546b48a7d Support --target-cpu, --target-cpu-tune, and --target-cpu-features (#7903)
Add support to the driver for CLI flags that select which CPU to target,
which CPU to tune performance for, and which CPU features to enable.

- `--target-cpu` selects the architecture of CPU to target, similar to
`-march` on x86. This combines `-march` with `armv*` strings and `-mcpu`
on Arm for specific CPU names. Similarly for RISC-V (if/when we support
and test it), etc. Notably, `--target-cpu=native` targets the current
native CPU.
- `--target-cpu-tune` maps to `-mtune`. This is expected to be rarely if
ever desirable, and I'm open to removing it, but if folks are relying on
this for performance I didn't want to hide the option.
- `--target-cpu-features` allows a "standard" (for LLVM) comma-separated
list of LLVM subtarget features, including `+feature` and `-feature`
syntax.

These flags, when provided, establish the default baseline for CPU
targeting and control how on-demand runtimes are built. They can be
changed or overridden by additional Clang flags when using
`--clang-arg`.

A critical part of this is building runtimes on demand using these
features. For that, we need to do some amount of canonicalization
(re-using Clang's canonicalization to ensure consistent behavior), and
extend the runtimes cache features to recognize these customizations.
Note that only the top-level Carbon flags impact runtimes, any
particular Clang arguments are only used by the specific compiles that
receive those arguments.

There is some nascent RISC-V support here, but it is incomplete and only
minimally tested. Still, it seemed better to record the parts we could
test than remove them.

Assisted-by: Antigravity with Gemini
2026-10-21 00:43:54 +00:00
Geoff RomerandDana Jansens 4441c53404 Simplify GetConstantValue (#8005)
- Resolve Specifics eagerly during evaluation. Previous import changes
have made deferral unnecessary. This simplifies the code and unblocks
the next item.
- Overload `GetConstantValue` for all ID kinds, rather than having a
separate `GetConstantValueOrPassThrough` function, so that callers don't
have to choose between them.

---------

Co-authored-by: Dana Jansens <danakj@orodu.net>
2026-10-20 23:21:00 +00:00
Dana Jansens db87a84b69 Improve tests and clarify design for observe equivalence chains (#7987)
Update tests for observe equivalence chains to cover more cases and to
better test the things they meant to. The design is clarified to explain
that in an `interface` the types in an equivalence chain must all be
related to that interface except for one.

This is based on #7484 with some followup changes by me.

Co-authored by: ozguronsoy@gmail.com
2026-10-20 21:29:09 +00:00
Dana Jansens eb66bebc35 New specifics formed during identify need to be marked identified (#8003)
We were explicitly marking the specifics of `require` decls as
`identified` when creating them during identify. But the require may
contain calls to other generics internally. And forming the specific for
the `require` can also form new specifics for those calls, if they
depend on `Self`. The resulting specific can have `IdentifiedWitness`
instructions in its arguments, in accesses or FacetValues, etc. Or they
may appear in the decl block as it's resolved. So it's very important
that the specifics be marked `identified`, as those witnesses may only
appear in `identified` specifics. This allows the new
`access_through_nested_specific_in_named_constraint.carbon` test to
succeed without any poisoning errors.

SemIR formatting is changed so that `identified` specifics are more
clearly differentiated where they are used, so that we can see if
`identified` specifics are misused outside of identifying.

Now that we construct more `identified` specifics, we also need to
ensure we remove the `identified` state from specifics that come out of
`EvaluateIdentifiedWitnesses` and `SubstIdentifiedWitnesses`. To do that
we add a new callback method on `SubstInstCallbacks` that we use to
build specifics during subst. Then during those operations, we drop the
`identified` flag from any specific that we form, because we know that
we have also removed any `IdentifiedWitness` instructions because:
- We replace them all from arguments during subst.
- We CHECK that we're not in the middle of identifying before starting
the subst.

However now an `IdentifiedFacetType` is not fully deduped, because it
may contain a non-identified specific that comes from a
`DeclaredFacetType` along with an identified specific that comes from a
named constraint, where they have the same arguments. That results in
the `IdentifiedFacetType` having two specific interfaces when it really
only refers to one. The
`fail_todo_impl_as_constraint_duplicates_interface.carbon` test
demonstrates this, and that we then diagnose an `impl` as invalid since
we find more than one specific interface being extended. To fix this we
need to dedupe and count specific interfaces _after_
substituting/evaluating away the IdentifiedWitnesses. This is left to
future work.
2026-10-20 21:18:50 +00:00
Nicholas Bishop 2e21255855 Partial support for a generic class inheriting from a virtual C++ class (#8007)
This allows a generic class to inherit from a virtual C++ class as long
as the subclass doesn't override any methods. See
toolchain/lower/testdata/interop/cpp/class/import/inherit.carbon for a
working example.

The TODO in `CheckCompleteClassType` is now in
`CarbonExternalASTSource::CompleteType`, and only triggers if virtual
function thunks need to be defined.

A fix in `FileContext::BuildVtable` is also needed: when looking up the
clang decl associated with the vtable, if the class is generic then the
`ClassTemplateSpecializationDecl` associated with the specific is
needed. This required adding `ConstantStore::Get`, since only `const`
access is allowed during lowering.
2026-10-20 20:43:34 +00:00
Lucile Rose Nihlen 9799a23262 Export function default values to C++ (#7988)
Adds code to track default values for function parameters in C++ export,
and to export them as global variables to C++ that are attached to
default value expressions in the clang AST.

Modifes lower to export global variables from another block, and adds
the variable initialization to the `global_init` block so their value
gets set at load time.
2026-10-20 20:38:22 +00:00
Bartłomiej Rosa 505c4ae308 Fix crash building impl witness thunks for constrained generic methods (#7956)
When building an impl witness thunk for a generic associated function,
trailing self-specific arguments could still mention outer interface
parameters (or `Self`) in their types. That left a SymbolicBinding that
did not belong to the thunk's generic, and evaluation aborted instead of
succeeding.

Substitute the outer specific arguments into those trailing arguments
with `SubstConstant` before adjusting bind indices.

Fixes #7919.
2026-10-19 21:10:56 +00:00
Dana Jansens 2b5f90cd74 Diagnose a witness table entry being set to multiple different values (#7996)
If the entry is set to the same value, accept it. If it's a different
value, diagnose it.
2026-10-19 19:35:11 +00:00
josh11b f21c62524e Interface as base class use case supported (#8002)
Previously forgot to update the "Use cases" section of classes.md to
reflect `virtual` being added.
2026-10-19 16:42:32 +00:00
nicole mazzuca 3056e7e318 nvim: Only enable the integration in the carbon repo (#7995)
This fixes an error that would happen when opening neovim in a
non-git-controlled directory, where `root` is `nil` and therefore one
cannot `..` it as a string.
2026-10-19 06:07:22 +00:00
David Blaikie 309d3d63c5 Use IndirectIndexWith.Ref for subscripting when available (#7975)
When checking `a[i]` for a non-array type, first try
`IndirectIndexWith.Ref`, falling back to `IndexWith.At`, per the
indexing design. This allows subscripting to produce a reference
expression, so that `a[i] = v` works and non-copyable elements can be
accessed.

Extracted from #7536 (the `Core.Slice` proposal).
2026-10-17 00:14:10 +00:00
Richard Smith e8ffeef7b5 Add tests for generic functions with ref, val, and var auto returns. (#7997)
Assisted-by: Claude via Antigravity
2026-10-16 22:42:21 +00:00
Richard Smith b766aa4d16 Support for terse => function bodies (#7895)
Treat the function as having a `-> auto` return type. Reject calls to
functions that still have a non-deduced `-> auto` return type, and
rewrite the function to have the actual return type once it is known.

Assisted-by: Gemini via Antigravity
2026-10-16 21:48:48 +00:00
Richard Smith d08805f1a6 Fix Carbon:: name leakage across files. (#7973)
Split the `Carbon::` namespace into one semantic namespace tree, plus
one separate lookup-only namespace tree per Carbon file. Use the
semantic namespaces as the parents of entities, but use the lookup-only
namespaces when C++ name lookup names a Carbon namespace, in order to
use a different C++ name lookup table in each Carbon file.

Assisted-by: Gemini via Antigravity
2026-10-16 20:59:16 +00:00
Richard Smith 0d3b606a56 Support calls to generic functions with deduced return types. (#7981)
When a deduced return type depends on the function's generic parameters,
it's computed by the definition's eval block, so resolve the specific's
definition at the call. Calling a specific during its own instantiation,
before the point where its return type is deduced, is diagnosed like any
other use of the function before its return type is known. Also allow
exporting such functions to C++.

Resolving such a specific from within a checked generic can perform
template actions with symbolic arguments. Perform delayed actions in a
discarded generic region so the instructions they create aren't attached
to the generic being checked, and check that evaluating a specific
doesn't add anything to an enclosing generic region.

Assisted-by: Claude via Antigravity
2026-10-16 19:41:02 +00:00
David Blaikie 6d80ceb58e Add the IndirectIndexWith interface to the prelude (#7974)
Matching the API as described in the update in #5434, with a `Ref`
rather than `Addr` function (and `ref` rather than pointer return form).
Also added a blanket `final` impl of `IndexWith` when the element type
impls `Copy` (since `IndexWith` requires copyable elements) - which
differs slightly from the design wording, which doesn't account for the
copyability requirement.

Proposal #5389 (in review) would rework these interfaces, but this
should do for now until that's finalized.

Tested using the interface directly - follow-up change will add the
subscript support to check.

Extracted from #7536 (the `Core.Slice` proposal).
2026-10-16 18:58:39 +00:00
Richard Smith f9c21d769e Improve code and tests for exporting of names to C++ after a lookup failure. (#7984)
Address some review comments from the review of PR#7935 on the changes
from PR#7933.

Consolidate `const_cast`s to a single place with an explanatory comment.
Improve test clarity and organization. Rename lookup-specific helper
function to avoid suggesting it is more broadly applicable.
2026-10-16 16:33:29 +00:00
Dana Jansens 77fc0a3ef3 IdentifiedWitness is not a final witness (#7985)
If we perform impl lookup during identify by resolving a specific, we
can run into IdentifiedWitnesses in a FacetValue. If the witness is the
only one in the FacetValue we may use it as a final witness, which is
not correct. IdentifiedWitness is not a final witness.

To test this, we add a test that places an IdentifiedWitness into a
FacetValue and then uses that facet value to get an associated constant.
If treated as a final witness, we try to do a lookup where the query
self contains the IdentifiedWitness and we crash coming across it in the
TypeIterator. We should never come across an IdentifiedWitness in an
impl lookup query.

The test also shows a case that should work but does not because of
`impl_lookup_no_symbolic_final_lookups`. We set
`impl_lookup_no_symbolic_final_lookups` while collecting witnesses to
avoid cycles in impl lookup. This test fails because of that. It
prevents us from forming a correct specific for the class `C` by
creating a specific where `C.C1` is a symbolic value. Later we try to
form the specific again, where we would see a concrete value for `C.C1`,
but the specific already exists, so we can't get to the concrete value.
2026-10-16 14:09:32 +00:00
Richard Smith 14a3de9ff1 Fix import of val and ref return forms. (#7989)
Importing a function with a `-> val` return form, such as `fn F() -> val
i32`, crashed because `TryResolveInstCanonical` had no case for
`ValueForm`. Also, an imported `RefForm` was given type `type` rather
than `Core.Form`.

Import all three primitive forms with a single function template.

Assisted-by: Claude via Antigravity
2026-10-15 23:54:56 +00:00
Dana Jansens fea77fe598 Be robust against an ErrorInst as the self when identifying (#7986) 2026-10-15 22:19:39 +00:00
Richard Smith 3af25cc1ba Support for -> ref/var/val auto. (#7977)
Extend support for return type deduction to also handle top-level
category specifiers.

Assisted-by: Claude via Antigravity
2026-10-15 21:13:38 +00:00
Dana Jansens f0b030f2c7 Allow the use of early impls constraints when forming a specific using .Self (#7980)
The rules in early impls are stored with frozen `.Self`. However when
forming a specific, we can perform an impl lookup query against a facet
type written outside of the current facet type, which means any `.Self`
in that facet type will _not_ be frozen. In order to compare a witness
from early impls with the non-frozen requirement, we need to thaw what
we get out of the early impls.

However there are also cases where we are doing a lookup against
requirements that are part of the current facet type, so the
requirements are frozen.

Rather than picking one, and having to thaw all impl lookup queries, for
example, we store the frozen and thawed copy of the early impls rule so
that we can satisfy lookup queries that involve either.
2026-10-15 21:03:34 +00:00
Richard Smith 09b79784dc Include the Carbon package name in the exported C++ namespace. (#7935)
When exporting `X` from Carbon package `Foo`, expose it to C++ with
fully-qualified name `Carbon::Foo::X`, not simply `Carbon::X`. This
ensures that C++ mangled names properly distinguish between same-named
Carbon entities in different packages.

Name *lookup* into `Carbon::` still uses the same rules as before: it
finds what a `package.` lookup would find in Carbon. So `X` is named
from C++ as `Carbon::X` still, and a lookup for `Carbon::Foo` fails,
even though `X`'s "true name" is now `Carbon::Foo::X`.

Assisted-by: Gemini via Antigravity
2026-10-15 20:26:05 +00:00
Richard Smith d87891f3a6 Fix diagnostic style issues (#7982)
Remove trailing `.`s and leading capitalization from diagnostics, and
separate clauses with `;` rather than `.`, per
https://docs.carbon-lang.dev/toolchain/docs/diagnostics.html#diagnostic-message-style-guide
2026-10-15 20:15:28 +00:00
Dana Jansens 7beb29f8b2 Use a fallback loc for diagnostics generated on canonical insts (#7967)
We allow using `InstId` as a `LocId` for diagnostics, which works great
in `handle_*` functions, where we are working with non-canonical insts.
And convert and pattern matching often are given non-canonical insts,
which they can use, or pull apart to find some other inst, and use those
as a location for diagnostics. However these operations can also be
called with canonical insts, which have no location. In that case if we
need to diagnose an error, it ends up not being attached to any code at
all. Since we always have a `LocId` somewhere up the call stack, pass it
along and in these places that handle canonical instructions too, use
`GetInstLocationOrFallback()` to get a valid location for diagnostics.

This is also similar to what eval does with its fallback `LocId`.

The result is that many diagnostics now have a location where they had
none before. It also means some insts don't get `<elided>` in formatted
semir for not having a location at all and thus not being in range.
2026-10-15 19:03:10 +00:00
Dana Jansens 3a1db6633c Verify the types in an impls constraint first (#7978)
When the LHS is not a type, or the RHS is not a facet type, that is the
most important error to give, so prioritize that. Only fall back to the
"no .Self present" when the types are correct.

This was especially confusing when the RHS _did_ contain a `.Self` but
was not a facet type.

Add TypeExpr::SetError, as the lack of this was the main reason for the
bad error checking ordering, as setting multiple fields was awkward when
an error was found.
2026-10-15 18:11:48 +00:00
Dana Jansens 690a050550 Add a dc alias for dump context in lldb (#7972)
This handles the common case where there is a `context` variable or
method available in scope.

Users can't provide this alias in their own config files since the
`dump` command does not yet exist when user configs are parsed.
2026-10-15 16:28:20 +00:00
Dana Jansens d649699682 Disallow copy of SaveAndRestore (#7966)
An accidental copy would cause an extra save/restore. This is meant as
an RAII object to denote a save for a given scope. Also disallow move so
we don't need to add branches to the dtor.
2026-10-15 15:30:08 +00:00
Nicholas Bishop 08b752e888 Add missing fields to ClangDecl::Print (#7949) 2026-10-15 15:23:17 +00:00
Dana Jansens e39bdb7afa Resolve ImplWitnessAccess through the IdentifiedFacetType (#7947)
Instead of only looking for rewrite constraints in the declared facet
type, identify the facet type and collect rewrite constraints from named
constraints as well. This allows rewrites in a named constraint to be
found such as `T: N, U: T.X` where `N` is a named constraint that does
`extend require impls Z where .X = ...`. The type of `U` will be
whatever is specified in the rewrite of `.X` inside `N`.

Note this removes some more uses of SubstPeriodSelf from outside of
identify, which is a progression, as identifying is meant to be the way
to substitute `.Self` generally.
2026-10-15 15:13:47 +00:00
Dana Jansens 2f808e0d0b Add debugger dumping for SpecificInterface (#7971)
This allows you to `dump` the SpecificInterface directly instead of
having to dump its components always, for the common cases where we have
a SpecificInterface rather than a SpecificInterfaceId.

It looks like:
```
interface: interface50000000: {name: name0, parent_scope: name_scope0, require_impls_block_id: require_block_empty, core_interface: Copy} `Copy`
  - specific_id: specific<none>
```
2026-10-15 14:59:11 +00:00
Chandler Carruth 2c9739799b Render diagnostics through the terminal library (#7683)
Diagnostic rendering was part of the data model: `Loc::FormatLocation`
and
`Loc::FormatSnippet` wrote plain text to a stream, so no capability,
color, or
terminal width could reach them.

A diagnostic is now one message with labels, contexts, and location
information
attached, and `Diagnostics::Renderer` draws it into a
`Terminal::Buffer`. The
design is in `toolchain/docs/diagnostics_rendering.md` (#7668),
authoring in
`toolchain/docs/diagnostics.md`.

Before:

```
example.carbon:6:3: error: 1 argument passed to function expecting 0 arguments
  Run0(1);
  ^~~~~~~
example.carbon:3:1: note: calling function declared here
fn Run0() {}
^~~~~~~~~~~
```

After:

```
error: 1 argument passed to function expecting 0 arguments
       ╭─┤ example.carbon:6:3
       ┆
     3 │ fn Run0() {}
       · ─────┬─────
       ·      ╰──┤ calling function declared here
       ┆
->   6 │   Run0(1);
       ·   ───────
       ╰────────────
```

`--no-diagnostic-snippets` gives a compact form, `example.carbon:6:3-9`.
File
tests use it; the rendering tests under `toolchain/diagnostics/testdata`
ask for
snippets.

C++ interop diagnostics are data now rather than text Clang drew:
`SemIR::ClangSourceLocId` names a `clang::SourceRange`, and a Clang
location is
marked as the point it names.

`StreamConsumer` moves to a `:renderer` target that `:emitter` does not
depend
on, keeping `common/terminal` out of `consumer.h`. Capabilities come
from the
file behind the error stream, so tests get plain ASCII.

`renderer_fuzzer` draws diagnostics built from fuzzer bytes with every
capability combination, since a shape the renderer doesn't expect must
never be
why the compiler dies while reporting a problem.

TODOs mark the follow-ups: line weights and crossing lines, wrapping
that keeps
a quoted type whole, and ordering diagnostics that share a byte offset.

Also: `IncorrectExtension`'s hint moves into its message, since its note
had no
location of its own, and `strip_noise` in `check_diagnostics.py` blanks
multi-line `#define`s instead of deleting them.

Assisted-by: Claude Code
2026-10-15 06:08:57 +00:00
Richard Smith e1c8c1f855 Use specific_constant when referring to name in enclosing generic (#7936)
When unqualified lookup finds a name from an enclosing generic, create a
`specific_constant` if it's a constant, or a `specific_inst` otherwise,
to refer to that name from the inner generic. This allows consumers of
generic instructions to always use a specific of the current scope's
generic when accessing the constant value or type of a dependent
instruction.

Fixes crashes in dominance and in `GetExprCategory` caused by using
specifics for the wrong generic.

Assisted-by: Gemini via Antigravity
2026-10-15 02:00:24 +00:00
Geoff Romer 893cce070f Canonicalize ImportIRInstIds (#7976)
This reduces SemIR bloat, and makes it easier to canonicalize things
that contain `ImportIRInstId`s.
2026-10-15 00:37:06 +00:00
Richard Smith bf5d2a2ace Add support for -> auto deduced return types. (#7931)
The return type is deduced from the function's `return` statements
(currently required to all have the same type), or from its first
`returned var`. Calls to generic functions with deduced return types are
not yet supported.

When a function has a deduced return type, we defer the return type
portion of the callee pattern match until deduction, and during
deduction we extend the decl block and the pattern block with the
return-related instructions.

Conversions for `return expr;` statements are deferred until the return
type has been deduced. The design requires that we take the common type
of the returned types as the deduced return type, so we can't in general
perform any conversions for returned expressions until we reach the end
of the function. This is accomplished by inserting a `splice_block`
where the conversions will go, and later updating that instruction to
contain the conversion.

Assisted-by: Claude Opus 5.5 via Antigravity
2026-10-15 00:30:09 +00:00
David Blaikieandjosh11b 1c69fe3447 Support -> val T and -> var T return forms (#7912)
Proposal p5434 specifies `-> val T`, `-> ref T`, and `-> var T` as
return forms. `-> ref T` is parsed as a `RefPrimitiveForm`; extend that
to parse a leading `val` or `var` after `->` as a `ValPrimitiveForm` or
`VarPrimitiveForm`, reusing the nodes produced by `form(val T)` and
`form(var T)`, and check them as forms.

---------

Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com>
2026-10-14 23:19:16 +00:00
Dana Jansens 831a2aee9f Use IncrementScope in impl lookup (#7968)
Use IncrementScope to increment/decrement the
`context.impl_lookup_no_symbolic_final_lookups()` value.
2026-10-14 22:30:23 +00:00
Chandler Carruth 3f5c187557 Generate inline function definitions with bodies (#7918)
Add four kinds of inline definition to generated classes, each with its
own count in `ClassParams`. Three are among the most common kinds of
inline definition in the Carbon, LLVM, and Clang headers: methods that
return a field, test a field, or forward their parameters to a private
method. The fourth computes over its parameters, and stands in for
longer bodies. Here is one class, without the comment before each
declaration:

    class ADGjE {
      fn Zotm() -> bool;

      fn fx(y: ADGjE) -> ADGjE {
        var acc: i32 = 0;
        acc = acc + y.Checksum();
        var aIx: i32 = 1;
        var aTc: i32 = aIx + 1;
        aIx = aTc;
        return ADGjE.Make();
      }

      fn GJ2m(self, N: c125y) -> ADGjE;

      fn Mg(self) -> i64 { return self.zGrc; }

      fn UE(self) -> bool { return self.LBYM == 0; }

      fn sU(self, z: i64, J: i32) -> ADGjE {
        return self.sUImpl(z, J);
      }

      private fn FYct(self) -> ADGjE;

      private fn sUImpl(self, z: i64, J: i32) -> ADGjE;

      fn Make() -> ADGjE {
return {.tag = 0, .pV0t = 0, .zxcQ = c125y.Make(), .zGrc = 0, .LBYM =
0};
      }

      fn Checksum(self) -> i32 {
        return self.tag + 1;
      }

      private var tag: i32;
      private var pV0t: i32;
      private var zxcQ: c125y;
      private var zGrc: i64;
      private var LBYM: i32;
    }

A computation reads each parameter with a consumer template for its
type, which calls `Checksum` for a class type, and returns a literal or
the class's `Make`. Getters and predicates read fields of fixed types,
because Carbon can't copy a class value that doesn't implement
`Core.Copy`.

The dense declarations benchmarks now have a computation, a getter, and
a forwarder in each class, so their results aren't comparable with
earlier runs.

A file still has the same size for every seed. Type uses and names are
split into pools whose entries are spelled the same number of times, and
getter and predicate field types cycle through the eligible types. Each
computation gets one parameter beyond its random count so that each
class has parameters that can use its own type. The line estimates now
count the slot that `self` takes when a method's parameters wrap, and
round to the nearest whole class.

Assisted-by: Claude Code
2026-10-14 20:52:05 +00:00
Geoff Romer 2b2a78ca12 Recover from errors while importing a generic (#7932)
Fixes #7927
2026-10-14 19:40:37 +00:00
Dana Jansens 5402b4c56e Get equality requirements from the IdentifiedFacetType in impl lookup (#7944)
Instead of only getting rewrite/equality requirements from the declared
facet type, impl lookup now uses the full IdentifiedFacetType of the
query's constraint to get rewrite/equality requirements from named
constraints as well.
2026-10-14 19:07:06 +00:00
Dana Jansens 51e2c76a8e Add a HasError helper to SpecificStore (#7970)
SpecificStore::HasError can return false when the specific ID is None,
which avoids having to check the ID in the caller. This is similar to
the GetArgsOrEmpty() helper.
2026-10-14 18:31:23 +00:00
Dana Jansens e41862d55f Handle ImplSelfWitness appearing in the RHS of a rewrite constraint in impl decl (#7943)
Normally ImplSelfWitness would be replaced in an impl decl by the time
we are constructing the witness table. If it was part of an access to an
associated constant, rewrite constraint resolution would have replaced
that access with the value assigned to the associated constant already.
But we can use `.Self` in places where it converts and forms a
FacetValue, which preserves the ImplSelfWitness in the witness block of
the FacetValue. In that case, we want to replace it with the impl's
ImplWitness before we put it into the witness table.

This is a followup fix for
https://github.com/carbon-language/carbon-lang/pull/7930 which
introduces the CARBON_FATAL incorrectly, now that we know how to run
into it.

Add some tests that cover these situations, including one that tries to
access using the FacetValue and its witness, which fails, as a TODO
test.
2026-10-14 17:54:41 +00:00
Dana Jansens f0bae96373 Verify non-extend require constraints are satisfied at impl as definition (#7937)
Rewrites in `extend requires` are used to set the witness table values
of the impl. Rewrites in non-extend `requires` are treated as same-type
constraints and must be true at the point of the impl definition.
2026-10-14 16:44:37 +00:00
Dana Jansens 24766e213e Defer evaluation of witnesses substituted while identifying a facet type (#7930)
When identifying a facet type, defer evaluation of LookupImplWitnesses
by redirecting them to evaluate into IdentifiedWitness. Then the caller
of identify will need to substitute the IdentifiedWitnesses, either with
witnesses of their own, or with LookupImplWitness to cause a lookup
operation.

This allows:
- An `impl as` on a named constraint can subst the witnesses in
designators from the named constraint with a witness to the impl itself.
This prevents them from looking for and finding some other overlapping
impl. (This PR)
- Impl lookup to collect rewrite/equality requirements from all named
constraints and then use the impls it has already found to evaluate any
designators in those requirements instead of performing impl lookup
again (creating a cycle). (Done in
https://github.com/carbon-language/carbon-lang/pull/7944)
- Similarly, an `impl as` can collect impls and equality requirements
from all named constraints and verify them correctly by replacing
witnesses that should refer to the impl with the correct witness instead
of performing a lookup and finding an overlapping impl. (Done in
https://github.com/carbon-language/carbon-lang/pull/7937)
- ImplWitnessAccess to be able to find rewrites from a named constraint,
not just from the initial DeclaredFacetType. (TBD)

To do this we need to introduce the concept of "identified" specifics.
In order to identify a facet type, we must construct a specific for each
require decl in a named constraint. These specifics will have
IdentifiedWitnesses inside them, but their args would otherwise match a
specific for the require decl constructed in another context. To avoid
polluting the canonical specific namespace, we introduce a second
namespace of specifics that are "identified" since they are created by
identifying.

As we have introduced a new global state that modifies eval behaviour,
we must disable that state when resolving a specific (as per
https://github.com/carbon-language/carbon-lang/pull/7923). But inside an
"identified" specific, we preserve the behaviour so that the witnesses
modified by identify (through SubstPeriodSelf or through evaluating a
require specific) have their evaluation deferred.
2026-10-14 14:12:56 +00:00
Christopher Di Bella 66a250ad96 Support importing CustomLayoutType (#7958)
`ImportRefResolver` wasn't resolving imported `CustomLayoutType`. The
toolchain would crash due to an unhandled instruction kind whenever a
`CustomLayoutType` crossed a library boundary. This change adds the
necessary infrastructure to properly import `CustomLayoutType`.

Assisted-by: Antigravity with Gemini
2026-10-14 01:46:04 +00:00
Dana Jansens d4f1ec203d Introduce a CheckedSaveAndRestore like llvm::SaveAndRestore (#7965)
Sets a variable to a new value, saving a copy of that new value. Then
CHECKs that the variable is still set to that new value before restoring
the original value over top of it. This is meant to catch data loss
where the variable holds some change that would be overwritten by the
restore. In particular, this would catch bad bracketing of sets and
restores.
2026-10-13 22:47:19 +00:00
Richard Smith 4ab46d448c Rename entities to avoid name collisions in -share-cpp-ast mode. (#7934)
Assisted-by: Gemini via Antigravity
2026-10-13 19:00:43 +00:00
Richard Smith e476a2bd3b Fix exporting of names to C++ after a lookup failure. (#7933)
If Clang encounters a lookup failure, it caches that result, creating
problems with `-share-cpp-ast` where a lookup failure in one file
effectively poisons the same name when looked up in a different file.

Also, if Clang triggers loading a complete name table (which happens,
for example, during typo correction, but Clang could do that any time it
wants to), it clears out the "has external visible results" flag, which
means that introducing new names in Carbon and then attempting to use
them from C++ after that point does not work.

Fix both of these problems by re-enabling the "has external visible
results" flag on callbacks that indicate Clang just cleared it, and by
not caching failed name lookups into Carbon scopes in Clang's lookup
table. (They still get cached in *our* lookup table.)

Assisted-by: Gemini via Antigravity
2026-10-13 18:43:50 +00:00
Geoff Romer a045da8e67 Document the discrepancy between checking and execution for variadics (#7963)
This is drawn from proposals/p002240-variadics.md#generalize-expand, but
rewritten to fit this context and audience.
2026-10-13 17:56:53 +00:00
Dana Jansens 46e7eacba8 Don't plumb IdentifiedFacetTypeId through impl lookup functions (#7928)
We plumb through the ID so that we can later see if a requirement was in
the IdentifiedFacetType, and potentially grab a final witness from the
witness source if it's a FacetValue.

Instead of plumbing through the IdentifiedFacetTypeId, collect all the
requirements up front, and the final witnesses if they exist.

We want to do this because in future work we will need to subst/evaluate
the outputs of IdentifiedFacetType to use it since it will contain
IdentifiedWitnesses, and we'd like to do that once up front instead of
multiple places. That is more efficient and ensures less time/code with
access to the intermediate state of the IdentifiedFacetTypeId.
2026-10-13 15:57:37 +00:00
Dana Jansens b1de0e037d Give the facet type as a TypeId to IdentifyFacetType functions (#7926)
This statically ensures that identify always works with a constant
value, in the most restrictive way possible (ensuring it's a type not
just a constant).
2026-10-13 15:02:58 +00:00
popcoder565andpopcoder565 f396762e22 Fix destroying function-local vars of choice types (#7941)
Since #7840, `SubobjectDestroy.Op` for a class destroys each field by
looking it up by name. A choice's discriminant field has a special name
that isn't in the class's scope, so any function-local `var` of choice
type failed with ``member name `discriminant` not found``. Skip the
discriminant the same way as the vptr, since neither is a named member
and both are trivially destructible.

Assisted-by: Claude Code with Claude Opus 5.5

---------

Co-authored-by: popcoder565 <339285150+popcoder565@users.noreply.github.com>
2026-10-13 14:19:49 +00:00
Dana Jansens 6a0a1f3e85 Plumb through IdentifiedFacetTypeId in impl checking (#7924)
We plumb the IdentifiedFacetTypeId through instead of identifying the
constraint twice, even though the second one should result in a cache
hit, as this makes things more clear and explicit.

Also a few other smaller cleanups and comments around forming the facet
type and identifying it, that I am trying to pull out of a much larger
change to identifying, so as to reduce noise there.
2026-10-13 13:41:15 +00:00
Dana Jansens 71af207401 Disable global state that modifies eval while resolving a specific (#7923)
We set the `declaring_impl_decls` state to modify the evaluation of
LookupImplWitness instructions formed inside an `impl as` decl's
constraint. However while checking that constraint, we may form other
specifics through calls to generic classes, etc. Specifics are canonical
and are keyed by their arguments and the generic ID. This means every
time we see the same arguments for a specific, we must produce the same
instructions. The `declaring_impl_decls` state can break that, causing
instructions inside the specific to be formed in a way they wouldn't at
another time.

To fix this, remove the effect of `declaring_impl_decls` while resolving
a specific. This keeps the specific's evaluation consistent so that the
key truly represents the canonical specific for its arguments.

This doesn't break anything. Existing LookupImplWitnesses (such as in a
class decl) are not guaranteed to point to an `impl as` when they are
modified by a specific. It is designators in the `impl as` constraint
that are guaranteed as such. Designators in an `extend require impls`
statement in a named constraint will be pointed to the `impl as` through
another mechanism in a followup, which will make the
`impl_as_named_constraint.carbon` test pass again.
2026-10-13 13:13:57 +00:00
David Blaikie 1429d1566e Parse lambda -> ref T as a primitive return form (#7962)
This got lost when I rebased #7911 on top of #7893/#7894's lambda
parsing
rework.

Assisted-by: Claude
2026-10-12 23:53:54 +00:00
Geoff Romer df4ef552ad Add missing case for non-parameterized generic classes. (#7952)
Fixes #7950
2026-10-12 19:02:57 +00:00
Nicholas BishopandPo-Chun Lee 9a4d4b332c Roll llvm to b08e005 (2026-10-08) (#7960)
Includes fixes for
https://github.com/llvm/llvm-project/commit/53f3709522af and
https://github.com/llvm/llvm-project/commit/2a6247359ee5.

---------

Co-authored-by: Po-Chun Lee <pochunlee@google.com>
2026-10-12 18:05:05 +00:00
popcoder565andpopcoder565 6c1d2a86e6 Fix crashes on impl lookup for bound method types (#7940)
`TypeIterator` didn't handle `BoundMethodType`, so any impl lookup whose
self type is a bound method crashed, for example `c.Get != 0` or `var x:
i32 = c.Get;` after forgetting the call parentheses. Treat it as a
concrete type, and as trivially destructible when checking `Destroy`,
since bound methods are empty at runtime. These cases now produce the
usual missing-impl diagnostics.

Fixes #7884

Assisted-by: Claude Code with Claude Opus 5.5

Co-authored-by: popcoder565 <339285150+popcoder565@users.noreply.github.com>
2026-10-12 16:11:26 +00:00
Geoff Romer 62f1cc6a35 Rename GenericClassType to ParameterizedClassType (#7953)
The old name was potentially confusing, because non-parameterized
generic classes do not use this inst kind.
2026-10-11 23:09:12 +00:00
Nicholas Bishop 5d442a5b0a Drop obsolete comments about DumpNoNewline (#7948)
Fixup for 3ae62f8130.
2026-10-09 22:36:47 +00:00
Nicholas Bishop 04443a9a14 Improve debugability of clang decls (#7945)
Add `Dump` functions for `ClangDecl`/`ClangDeclSignature`, and add
`MakeClangDeclId`/`MakeClangDeclSignatureId` and add those to
`lldbinit.py`.

Also change the label for `ClangDeclId` and `ClangDeclSignatureId` to
not have an "_id" suffix, to make them consistent with other ID types.
2026-10-09 20:24:23 +00:00
Nicholas BishopandDana Jansens 9887d23b3f Add comments with guidance on adding debug utilities for ID types (#7946)
Co-authored-by: Dana Jansens <danakj@orodu.net>
2026-10-09 20:17:22 +00:00
Chandler Carruth c578085840 Add determinism checks for generated source size and fix a crash (#7917)
Benchmarks are only comparable if the source generator produces the same
number of lines and bytes for a given configuration with any seed. Test
this across many seeds and class shapes, and check it in the compile
benchmark for the files it generates. Also check that generation emits
every name in its name pools, which the byte count depends on.

With field-heavy classes, generation could crash depending on the
shuffle. A class becomes a valid type only after its field types are
chosen, so references to the last class can only go on its own return
and parameter types. When the shuffled pool left more references to it
than that, `GetValidTypeName` found no valid type and failed its check.
Cap the references to each class at its number of functions and methods,
each of which has a return type that can use it, and replace the removed
references with fixed types so the byte count stays seed-independent.

Assisted-by: Claude Code
2026-10-09 16:58:56 +00:00
Dana Jansens 06b3c69b7f Don't crash when dumping a fixed InstId (#7929) 2026-10-09 16:42:20 +00:00
popcoder565andpopcoder565 59d53ab0a3 Use ASCII quotes in character literal examples in p1964 (#7938)
The examples in the "Types" section of the character literals proposal
delimited character literals with typographic quotes (U+2018/U+2019)
instead of ASCII `'`, so copying them produces invalid Carbon. No other
Markdown file in the repository has typographic quotes inside code.

Fixes #7424

Assisted-by: Claude Code with Claude Opus 5.5

Co-authored-by: popcoder565 <339285150+popcoder565@users.noreply.github.com>
2026-10-09 16:24:22 +00:00
David Blaikie 2302272643 Only allocate return storage when the callee has a return parameter (#7886)
Calls to functions with a `ref` or `val` return form whose type might be
initialized in place (such as a class type) were given temporary return
storage, based only on the return type. Caller pattern matching then
matched that storage against the callee's `ref_return_pattern` or
`value_return_pattern`, which only supports the callee side, and
CHECK-failed. Instead, base the decision on
`call_param_ranges.return_size()`, which is computed once from the same
return pattern when the callee's declaration is built, and assert the
same property in `ConvertCallArgs` before caller pattern matching.

Assisted-By: Claude
2026-10-09 00:19:29 +00:00
Richard Smith 071c5daddf Fix handling of generic self: Self. (#7922)
We previously discarded a `generic` applied to `self`, resulting in
silently treating `generic self: Self` as a runtime parameter and
producing a weird error on `template self: Self`.

Instead of forcibly making all `self` parameters non-runtime, change the
*default* for a parameter named `self` to be runtime.

This is only a partial fix; more work is required to support `fn
F(generic self)` with an omitted type.
2026-10-09 00:15:38 +00:00
Nicholas Bishop 559602b9e8 Fix lowering crash in EmitAggregateInitializer (#7920)
When `FindStorageArgForInitializer` is called from
`EmitAggregateInitializer` on a `SpliceInst`, that `SpliceInst` is
expected to have a constant `InstValue`. That may only be true in the
function specific though.

The `SpecificId` in `FindStorageArgForInitializer` defaults to `None`
and can't be set by the caller. Add a new
`FindStorageArgForInitializerInSpecific` that takes the `SpecificId` and
corresponding `File`. The original `FindStorageArgForInitializer` is now
a wrapper around the new function. This allows
`EmitAggregateInitializer` to set the appropriate `SpecificId`.

Fixes https://github.com/carbon-language/carbon-lang/issues/7873
2026-10-08 22:44:41 +00:00
David Blaikie 9fd28f7304 Lower return expr; for value return forms of class type (#7908)
A function with a value return form, such as `->? form(val C)`, is
lowered to return the value representation of `C`: a pointer for types
with a pointer value representation. However, lowering of `ReturnExpr`
re-derived the return convention independently of the function's
signature, and assumed that any returned value expression was a
`returned var`, taking the initializing-return path. For types with an
in-place initializing representation, that tried to initialize a
nonexistent return slot and crashed. (This only appeared to work for
types like `i32`, whose by-copy initializing representation happens to
match the value representation.)

Fix this by recording how the function returns its result -- `void`, by
copy, by reference, in place via an `sret` parameter, or as the entry
point's implicit `i32` -- as a `ReturnKind` in `FunctionTypeInfo` and
`FunctionInfo` when the signature is lowered, and having the `Return`
and `ReturnExpr` handlers switch on it. This keeps the signature and the
`return` lowering in agreement by construction, and replaces the
`IsEntryPoint` check in `Return` lowering.
2026-10-08 19:52:12 +00:00
Geoff Romer 73c247f1ef Fix crash when importing a generic non-parameterized class (#7925)
Fixes #7913
2026-10-08 19:24:56 +00:00
Richard SmithandGeoff Romer df3cf92943 Adopt ref in Core.Iterate.Next (#7381)
Change the `cursor` parameter in `Core.Iterate.Next` to be a `ref`
parameter
instead of a pointer parameter.

Assisted-by: Gemini via Antigravity

---------

Co-authored-by: Geoff Romer <gromer@google.com>
2026-10-07 23:14:42 +00:00
Richard Smith 72e425fe5c Clean up some leftover references to the old :! syntax. (#7921) 2026-10-07 23:07:01 +00:00
David BlaikieandNicholas Bishop 059ee7b9ed Parse -> ref T as a primitive return form (#7911)
Previously `-> ref T` was parsed as a type expression whose outermost
node is a `ref` prefix operator (the same one used for `F(ref x)`
arguments), and `ReturnExprAsForm` recognized the resulting `RefTagExpr`
and turned it into a `RefForm`.

Per p5434, a return form that begins with `val`, `ref`, or `var` is not
a type expression. Parse a leading `ref` after `->` as a
`RefPrimitiveForm` node wrapping the type expression, reusing the node
produced by `form(ref T)`, and check it as a form. This prepares for
supporting `-> val T` and `-> var T` in the same way.

The only SemIR change is that the `ref_tag` instruction is no longer
emitted, and the return slot pattern refers to a `type_component_of` the
form, matching `->? form(ref T)`.

---------

Co-authored-by: Nicholas Bishop <nbishop@nbishop.net>
2026-10-07 23:05:55 +00:00
Richard Smith 72068410c2 On verifier failure, don't print a pointer to the File. (#7915)
If the verifier fails, we tried to dump the SemIR::File, but
accidentally dumped a pointer to it instead. However, the raw file dump
is huge and unwieldy, and I don't think it's really helpful, so instead
of including it here, we now just omit the pointer.
2026-10-07 16:50:19 +00:00
Richard Smith 5b587a885d List function name not NameId in dominance failure. (#7916) 2026-10-07 01:48:09 +00:00
Nicholas Bishop 4a8993af2d Fix "<unexpected>" inst when creating an InitializeAction (#7900)
Add the storage access block to `InitializeAction`, and then insert that
block when performing the action.
2026-10-07 01:10:33 +00:00
Chandler Carruth fccb349af4 Improve the TextMate grammar scopes and precision (again) (#7914)
This is a re-do of #7888

Outside a call, the grammar scoped a name as a type or a variable by its
capitalization. A theme could not distinguish a type from a value with
the same spelling, or a parameter, a member, or an implemented interface
from any other name. Pointer member access `->` was scoped as a result
arrow, which scoped `Lookup` in `regions->Lookup(x)` as a type.

`entity.name.type` now applies only in a type position (after `:`, `->`,
`as`, `adapt`, or `require`, or in an `impl`) and to the name a nominal
declaration declares. A qualifier, in a declared name like `fn N.Base(T:
type).F()` or in a type, is `entity.name.scope-resolution` nested in
`entity.name.type`. As in the parser, a name is a qualifier when its
parameter lists are followed by `.`. Parameters, `let` bindings, choice
alternatives, members, implemented interfaces, primitive types,
`import`, and the strings naming a builtin or a library have their own
scopes, and brackets use the C++ grammar's scopes. A `->` written
directly after its operand is member access; no result arrow in the
repository is written that way.

The sample palette renders each scope that depends on context
differently, and `tmlanguage.py` now supports nested captures.

In 17 of the 19 themes measured, the VS Code built-ins plus One Dark
Pro, Night Owl, Tokyo Night, and Nord, 13% to 27% of the characters in
hand-written Carbon change color or style. A theme loses a color where
`.` is now punctuation, where word operators like `as` are no longer
control-flow keywords, or where a name outside a type position is no
longer a type.

Assisted-by: Claude Code
2026-10-06 23:21:43 +00:00
Dana Jansens bf91a8b4dd Canonicalize the self in ImplSelfWitness (#7898)
We perform the same canonicalization as in LookupImplWitness, which
makes the output of eval more consistent across witness types, and
enables more straightforward constant comparisons between them when
needed.
2026-10-06 23:04:20 +00:00
Richard Smith f83518bfd7 Don't create two parse nodes for each => token. (#7894)
Instead of producing two parse nodes in a row for each `=>` token,
produce just one.

Assisted-by: Gemini via Antigravity
2026-10-06 21:43:36 +00:00
Chandler Carruth a5912b8b82 Render the TextMate samples in a palette built for the grammar (#7887)
The sample renderings used VS Code's Dark+, which renders many scopes
identically, so a grammar change often doesn't show up in the images.
Dark+ gives the 42 scope stacks the samples produce 10 distinct
renderings: types, namespaces, builtin types and classes share one
color, and operators and separators stay at the default foreground, so a
rule that moved a token between them changed nothing visible.

`render_sample.py` now uses its own palette. Each family of related
scopes, such as the numeric literals, gets one color, and bold and
italic distinguish the scopes within a family; the renderer now emits
font styles to support this. The samples' 34 scopes get 32 renderings;
only the two embedded C++ spellings and a string's opening and closing
quotes share one. Unscoped text is dimmer than any scope's color, so
`0o8` renders as an octal `0o` followed by an unscoped `8`. The colors
are Catppuccin Mocha's, and each has at least 4.4:1 contrast against the
background.

No existing theme does this: across 264 theme files, the 19 VS Code
built-ins plus 30 popular extensions, the best renders 19 of the 42
stacks distinctly.

Assisted-by: Claude Code
2026-10-06 21:18:36 +00:00
David Blaikie cb628110cf Avoid copying class info when lowering static class variables (#7910)
Fixes a clang-tidy `performance-for-range-copy` finding.
2026-10-06 21:17:33 +00:00
David Blaikie e588469fd6 Handle return expr; in functions with a value return form (#7907)
`BuildReturnWithExpr` handled init, ref, error, and symbolic return
forms, but hit `CARBON_FATAL("Unexpected inst kind")` for a `ValueForm`,
so a definition such as `fn F() ->? form(val i32) { return 1; }`
crashed. Convert the returned expression to a value of the form's type
component, mirroring the existing `RefForm` handling.
2026-10-06 20:36:26 +00:00
Chandler Carruth 25b77cd3d4 Revert "Improve the TextMate grammar scopes and precision (#7888)" (#7909)
This reverts commit 65a3c2b36a.

It was merged by accident with a dependent change. Will re-land
independently.
2026-10-06 20:17:51 +00:00
Richard Smith 1b0a5709e6 Initial support for fn expressions. (#7893)
Support for `fn` expressions with the same feature set as `fn`
declarations. In particular, there is no support for captures,
positional parameters, `=>` bodies, or `auto` return types.

Note that this results in two parse nodes for each `=>` token. That's a
bug, but it matches the behavior of function declarations, and will be
fixed in a follow-up change.

Assisted-by: Gemini via Antigravity
2026-10-06 20:12:58 +00:00
dependabot[bot] 66bf1415ac Bump urllib3 from 2.7.0 to 2.8.0 in /github_tools in the pip group across 1 directory (#7906)
Bumps the pip group with 1 update in the /github_tools directory:
[urllib3](https://github.com/urllib3/urllib3).

Updates `urllib3` from 2.7.0 to 2.8.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/urllib3/urllib3/releases">urllib3's
releases</a>.</em></p>
<blockquote>
<h2>2.8.0</h2>
<h2>🚀 urllib3 is fundraising for HTTP/2 support</h2>
<p><a
href="https://sethmlarson.dev/urllib3-is-fundraising-for-http2-support">urllib3
is raising ~$40,000 USD</a> to release HTTP/2 support and ensure
long-term sustainable maintenance of the project. If your company or
organization uses Python and would benefit from HTTP/2 support in
Requests, pip, cloud SDKs, and thousands of other projects <a
href="https://opencollective.com/urllib3">please consider contributing
financially</a> to ensure HTTP/2 support is developed sustainably and
maintained for the long-haul.</p>
<p>Thank you for your support.</p>
<h2>Security</h2>
<p>Fixed the following security issues:</p>
<ul>
<li>The TLS configuration for HTTPS proxies could be ignored or
overridden. (High severity, GHSA-8988-9cw3-xx77)</li>
<li><code>HTTPResponse.stream()</code> and <code>read_chunked()</code>
could buffer a chunk-size line of unbounded length in memory. (High
severity, GHSA-vxq7-64xx-v4gw)</li>
<li>Chunked Deflate streaming could enter an infinite loop. (Medium
severity, GHSA-gh4c-6fx4-qh6g)</li>
</ul>
<blockquote>
<p>[!IMPORTANT]
urllib3 2.8.0 fixes HTTPS proxy TLS configuration being ignored or
overridden by destination settings. Configurations relying on that
behavior may require changes.</p>
<p>Configure proxy CA certificates and client certificates in
<code>proxy_ssl_context</code>, and proxy identity checks with
<code>proxy_assert_hostname</code> or
<code>proxy_assert_fingerprint</code>. Destination client certificates
and identity overrides no longer apply to HTTPS forwarding proxy
connections.</p>
</blockquote>
<blockquote>
<p>[!NOTE]
CVE IDs had not yet been assigned to these advisories at the time of
release due to a backlog at GitHub's CNA.</p>
</blockquote>
<h2>Deprecations &amp; Removals</h2>
<ul>
<li>Deprecated using an empty collection as the <code>Retry</code>
option <code>allowed_methods</code> to retry any verb. (<a
href="https://redirect.github.com/urllib3/urllib3/issues/5044">#5044</a>)</li>
</ul>
<h2>Features</h2>
<ul>
<li>Added <code>Url.auth_decoded</code> and
<code>Url.auth_decoded_joined</code> convenience properties to the
result of <code>parse_url()</code>. (<a
href="https://redirect.github.com/urllib3/urllib3/issues/4945">#4945</a>)</li>
<li>Added <code>basic_auth_encoding</code> and
<code>proxy_basic_auth_encoding</code> parameters to
<code>urllib3.util.make_headers()</code>. (<a
href="https://redirect.github.com/urllib3/urllib3/issues/5092">#5092</a>)</li>
</ul>
<h2>Bugfixes</h2>
<ul>
<li>
<p>Fixed response header handling to replace obsolete folded header
lines (<code>obs-fold</code>) with spaces in accordance with RFC 9112,
preventing raw CRLF sequences from appearing in header values such as
<code>Set-Cookie</code>. (<a
href="https://redirect.github.com/urllib3/urllib3/issues/1362">#1362</a>)</p>
</li>
<li>
<p>Fixed usage of <code>proxy_ssl_context</code> with
<code>ProxyManager</code> when
<code>use_forwarding_for_https=True</code>. Passing
<code>ssl_context</code> instead of <code>proxy_ssl_context</code> for
HTTPS proxies in this configuration now emits a
<code>FutureWarning</code> and will raise an error in v3.0. (<a
href="https://redirect.github.com/urllib3/urllib3/issues/2577">#2577</a>)</p>
</li>
<li>
<p>Changed behavior of the default <code>ConnectionPool.pool</code>
initialization. <code>LifoQueue</code> is now resolved from the
<code>queue</code> module after the <code>ConnectionPool</code> is
instantiated instead of using the default cached <code>QueueCls</code>
class property. This is done because sometimes the
<code>queue.LifoQueue</code> is monkey-patched late in the program, such
as by gevent. (<a
href="https://redirect.github.com/urllib3/urllib3/issues/3289">#3289</a>)</p>
</li>
<li>
<p>Raised <code>UnrewindableBodyError</code> instead of
<code>ValueError</code> when retrying a request whose body had
<code>tell()</code> but not <code>seek()</code>. (<a
href="https://redirect.github.com/urllib3/urllib3/issues/3779">#3779</a>)</p>
</li>
<li>
<p>Decoded percent-encoded SOCKS proxy credentials before authenticating
with the proxy server. (<a
href="https://redirect.github.com/urllib3/urllib3/issues/3785">#3785</a>)</p>
</li>
<li>
<p>Fixed <code>HTTPResponse.drain_conn()</code> to discard unread
response data in 64 KiB chunks (same as the default <code>amt</code>
when doing <code>HTTPResponse.stream(...)</code>). (<a
href="https://redirect.github.com/urllib3/urllib3/issues/5019">#5019</a>)</p>
</li>
<li>
<p>Fixed <code>is_ipaddress()</code> to detect non-standard IPv4 forms
accepted by <code>socket.connect</code>, such as hex
(<code>0x7f000001</code>), octal (<code>0177.0.0.1</code>), and decimal
integers (<code>2130706433</code>), ensuring SSL certificate
verification uses the correct mode for these addresses. (<a
href="https://redirect.github.com/urllib3/urllib3/issues/5029">#5029</a>)</p>
</li>
<li>
<p>Fixed <code>HTTPConnectionPool.urlopen</code> raising a misleading
<code>FullPoolError</code> instead of <code>ValueError</code> when
called with an invalid <code>timeout</code> argument on a pool created
with <code>block=True</code>. (<a
href="https://redirect.github.com/urllib3/urllib3/issues/5059">#5059</a>)</p>
</li>
<li>
<p>Fixed port-zero handling to preserve explicit <code>:0</code> values
instead of substituting the default ports 80 or 443 in URL parsing, pool
selection, proxy configuration, <code>connection_from_url()</code>, and
HTTP/2 request authority. (<a
href="https://redirect.github.com/urllib3/urllib3/issues/5071">#5071</a>,
<a
href="https://redirect.github.com/urllib3/urllib3/issues/5101">#5101</a>)</p>
</li>
<li>
<p>Fixed a bug where <code>PoolManager</code> passed the
<code>assert_hostname</code> and <code>assert_fingerprint</code>
parameters to HTTP connection pools. (<a
href="https://redirect.github.com/urllib3/urllib3/issues/5077">#5077</a>)</p>
</li>
<li>
<p>Fixed <code>HTTPConnectionPool.urlopen()</code> and HTTP proxy
forwarding to strip URL fragments from absolute request targets before
sending requests. (<a
href="https://redirect.github.com/urllib3/urllib3/issues/5079">#5079</a>)</p>
</li>
<li>
<p>Added safeguards to the proxy tunneling code to prevent potential
security issues when handling invalid characters in the proxy host and
HTTP headers. This change affects users of Python 3.10, Python 3.11, and
Python 3.12 when the standard library does not contain the fix; those on
newer Python versions should upgrade to 3.13.14+ or 3.14.5+ to get the
same security fixes. (<a
href="https://redirect.github.com/urllib3/urllib3/issues/5091">#5091</a>)</p>
</li>
<li>
<p>Fixed <code>HTTPSConnection.connect()</code> overriding
<code>ProxyConfig.ssl_context</code>'s certificate policy and proxy
identity checks with the target connection's TLS settings when
forwarding through an HTTPS proxy.</p>
<p><code>HTTPSConnection</code> no longer applies target SNI,
assertions, or client credentials to forwarding proxy handshakes and
continues to use its <code>ssl_context</code> as a fallback when an
HTTPS proxy forwards an HTTP target. (<a
href="https://redirect.github.com/urllib3/urllib3/issues/5093">#5093</a>)</p>
</li>
<li>
<p>Fixed URL parsing to more strictly enforce RFC 3986 host syntax,
rejecting invalid host input such as raw spaces and control characters,
malformed percent-encodings, and percent-encoded control characters in
HTTP(S) hosts and IPv6 zone identifiers, including proxy CONNECT tunnel
targets. Host normalization now also follows RFC 3986 normalization
rules for percent-encoded octets by decoding percent-encoded unreserved
characters and uppercasing the hexadecimal digits of retained
percent-encoded octets. (<a
href="https://redirect.github.com/urllib3/urllib3/issues/5095">#5095</a>)</p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/urllib3/urllib3/blob/main/CHANGES.rst">urllib3's
changelog</a>.</em></p>
<blockquote>
<h1>2.8.0 (2026-09-15)</h1>
<h2>Security</h2>
<p>Fixed the following security issues:</p>
<ul>
<li>The TLS configuration for HTTPS proxies could be ignored or
overridden.
(High severity, <code>GHSA-8988-9cw3-xx77
&lt;https://github.com/urllib3/urllib3/security/advisories/GHSA-8988-9cw3-xx77&gt;</code>__)</li>
<li><code>HTTPResponse.stream()</code> and <code>read_chunked()</code>
could buffer a chunk-size
line of unbounded length in memory. (High severity,
<code>GHSA-vxq7-64xx-v4gw
&lt;https://github.com/urllib3/urllib3/security/advisories/GHSA-vxq7-64xx-v4gw&gt;</code>__)</li>
<li>Chunked Deflate streaming could enter an infinite loop. (Medium
severity,
<code>GHSA-gh4c-6fx4-qh6g
&lt;https://github.com/urllib3/urllib3/security/advisories/GHSA-gh4c-6fx4-qh6g&gt;</code>__)</li>
</ul>
<p>.. caution::</p>
<pre><code>urllib3 2.8.0 fixes HTTPS proxy TLS configuration being
ignored or
overridden by destination settings. Configurations relying on that
behavior may require changes.
<p>Configure proxy CA certificates and client certificates in
<code>proxy_ssl_context</code>, and proxy identity checks with
<code>proxy_assert_hostname</code> or
<code>proxy_assert_fingerprint</code>.
Destination client certificates and identity overrides no longer
apply to HTTPS forwarding proxy connections.
</code></pre></p>
<h2>Deprecations &amp; Removals</h2>
<ul>
<li>Deprecated using an empty collection as the <code>Retry</code>
option
<code>allowed_methods</code> to retry any verb.
(<code>[#5044](https://github.com/urllib3/urllib3/issues/5044)
&lt;https://github.com/urllib3/urllib3/issues/5044&gt;</code>__)</li>
</ul>
<h2>Features</h2>
<ul>
<li>Added <code>Url.auth_decoded</code> and
<code>Url.auth_decoded_joined</code> convenience
properties to the result of <code>parse_url()</code>.
(<code>[#4945](https://github.com/urllib3/urllib3/issues/4945)
&lt;https://github.com/urllib3/urllib3/issues/4945&gt;</code>__)</li>
<li>Added <code>basic_auth_encoding</code> and
<code>proxy_basic_auth_encoding</code> parameters to
<code>urllib3.util.make_headers()</code>.
(<code>[#5092](https://github.com/urllib3/urllib3/issues/5092)
&lt;https://github.com/urllib3/urllib3/issues/5092&gt;</code>__)</li>
</ul>
<h2>Bugfixes</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/urllib3/urllib3/commit/b1d30ab61fe0db8f11092805e8c5ac43e091064a"><code>b1d30ab</code></a>
Release 2.8.0</li>
<li><a
href="https://github.com/urllib3/urllib3/commit/9016d7e8afc68185496ef07f3c3a4a743d04922e"><code>9016d7e</code></a>
Skip <code>test_read_chunked_with_trailing_data_does_not_hang</code> for
brotlicffi (<a
href="https://redirect.github.com/urllib3/urllib3/issues/5258">#5258</a>)</li>
<li><a
href="https://github.com/urllib3/urllib3/commit/9101f581a8b3659af23b6ff335ae77200ca33533"><code>9101f58</code></a>
Fix <code>nox -s docs</code> warning (<a
href="https://redirect.github.com/urllib3/urllib3/issues/5256">#5256</a>)</li>
<li><a
href="https://github.com/urllib3/urllib3/commit/cd770b059b543be29298ea5c52afb0b1b090f5ed"><code>cd770b0</code></a>
Merge commit from fork</li>
<li><a
href="https://github.com/urllib3/urllib3/commit/ea2ad7b21a80da3632f80016526a18864586077f"><code>ea2ad7b</code></a>
Merge commit from fork</li>
<li><a
href="https://github.com/urllib3/urllib3/commit/0716e31534345dc1599ea95d903c79f276239bd8"><code>0716e31</code></a>
Fix loading unencrypted client keys with a password in pyOpenSSL (<a
href="https://redirect.github.com/urllib3/urllib3/issues/5255">#5255</a>)</li>
<li><a
href="https://github.com/urllib3/urllib3/commit/43c68c8b43a9dcb44ed2cf4ec91384ca0d46b37d"><code>43c68c8</code></a>
Test pickling of <code>InvalidChunkLength</code> (<a
href="https://redirect.github.com/urllib3/urllib3/issues/5247">#5247</a>)</li>
<li><a
href="https://github.com/urllib3/urllib3/commit/308b279b3fb28e7bee952e152ec5baeb5bfd0817"><code>308b279</code></a>
Share security policy between GitHub and Read the Docs (<a
href="https://redirect.github.com/urllib3/urllib3/issues/5253">#5253</a>)</li>
<li><a
href="https://github.com/urllib3/urllib3/commit/53fa0731b27d4b71ab0755ea5b896422d005d706"><code>53fa073</code></a>
Add policy on duplicate pull requests (<a
href="https://redirect.github.com/urllib3/urllib3/issues/5252">#5252</a>)</li>
<li><a
href="https://github.com/urllib3/urllib3/commit/5f2a6a843d0100d1351c3f94d58581ca98d17267"><code>5f2a6a8</code></a>
Assert on the ALPN extension in test_tunnel_sets_http_11_alpn (<a
href="https://redirect.github.com/urllib3/urllib3/issues/5232">#5232</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/urllib3/urllib3/compare/2.7.0...2.8.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=urllib3&package-manager=pip&previous-version=2.7.0&new-version=2.8.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/carbon-language/carbon-lang/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-06 20:07:39 +00:00
Chandler Carruth 65a3c2b36a Improve the TextMate grammar scopes and precision (#7888)
Outside a call, the grammar scoped a name as a type or a variable by its
capitalization. A theme could not distinguish a type from a value with
the same
spelling, or a parameter, a member, or an implemented interface from any
other
name. Pointer member access `->` was scoped as a result arrow, which
scoped
`Lookup` in `regions->Lookup(x)` as a type.

`entity.name.type` now applies only in a type position (after `:`, `->`,
`as`,
`adapt`, or `require`, or in an `impl`) and to the name a nominal
declaration
declares. A qualifier, in a declared name like `fn N.Base(T: type).F()`
or in a
type, is `entity.name.scope-resolution` nested in `entity.name.type`. As
in the
parser, a name is a qualifier when its parameter lists are followed by
`.`.
Parameters, `let` bindings, choice alternatives, members, implemented
interfaces, primitive types, `import`, and the strings naming a builtin
or a
library have their own scopes, and brackets use the C++ grammar's
scopes. A
`->` written directly after its operand is member access; no result
arrow in
the repository is written that way.

The sample palette renders each scope that depends on context
differently, and
`tmlanguage.py` now supports nested captures.

In 17 of the 19 themes measured, the VS Code built-ins plus One Dark
Pro, Night
Owl, Tokyo Night, and Nord, 13% to 27% of the characters in hand-written
Carbon
change color or style. A theme loses a color where `.` is now
punctuation, where
word operators like `as` are no longer control-flow keywords, or where a
name
outside a type position is no longer a type.

Assisted-by: Claude Code
2026-10-06 17:38:12 +00:00
Richard Smith 947c1eb527 Add a Core.Array class type, and make array refer to it. (#7892)
As described in proposal #4682, make `array(T, N)` be syntactic sugar
for the type `Core.Array` defined in the standard library. This provides
a home for impls that are specific to `Core.Array`.

Assisted-by: Gemini and Claude via Antigravity
2026-10-06 17:23:49 +00:00
Chandler Carruth a50feb64ce Simplify proposal labels and fix GitHub label workflows (#7904)
We now only use a single `proposal` label on proposal PRs rather than
separate state labels (`proposal draft`, `proposal rfc`, `proposal
accepted`, `proposal declined`, and `proposal deferred`), as draft,
open, merged, and closed PR states already capture the proposal
lifecycle.

- Remove the `proposal_labeled.yaml` and `proposal_ready.yaml`
workflows.
- Update `new_proposal.py`, `template.md`, `evolution.md`, and
`CONTRIBUTING.md` to only use the `proposal` label and filter non-draft
proposal PRs with `draft:false`.
- Fix `auto_label_prs.yaml` to apply the `bot` label instead of the
non-existent `automated` label and fix the `proposals/scripts/**` path
filter typo.
- Update `triage_inactive.yaml` to exempt `needs design doc update`
instead of the old `design update` label.

Assisted-by: Antigravity with Gemini
2026-10-06 17:18:20 +00:00
Chandler Carruth 46b750bf90 Compile the generated C++ in source_gen_test (#7905)
`source_gen_test` checked the line count of the generated C++ but never
compiled it. Compile it with `ClangRunner`, the same way the C++ compile
benchmarks do, which resolves the TODO.

Also require both languages to compile with no diagnostic output at all,
rather than only without errors. A warning in generated code would add
diagnostic emission to what the compile benchmarks measure.

Assisted-by: Claude Code
2026-10-06 17:14:54 +00:00
Chandler Carruth 13ecad24be Remove the hashtable seed (#7871)
Hashtables seed every hash with the address of a global,
`ComputeSeed()`, so that ASLR varies layout and iteration order between
runs. Debug builds already vary iteration order directly in `entries()`,
so the seed only matters in release builds. There it costs a register
and an XOR on the latency path of every integer or pointer hash, and it
redraws a table's collisions in each process.

Remove the seed from `HashValue`, `Hasher`, `CarbonHashValue`,
`CustomHashValue::Hash`, and `HashKey`. Hashing starts from a zero
buffer, so integer keys hash as `WeakMix(key)`. `global_addr_seed`
remains in debug builds to seed the iteration-order entropy.
`ClangDeclSignature` chained its hash through the seed, and now hashes
its fields with one `Hasher`.

The seed also kept some short-input paths from collapsing. Without it,
every `{i, 0, 0}` array of `int32_t` hashed the same, all-zero 4-8 byte
inputs lost their size, and the 17-32 byte path multiplied the size
sample by a byte-shifted copy of itself. Each `Mix` operand of the
short-input paths now XORs in a 32-bit constant from
`ImmediateRandomData` in place of a 64-bit chunk of `StaticRandomData`,
which they now only sample to encode sizes. `XorImmediate` keeps these
as `xor $imm32` on x86-64; without its `asm` barrier, a read feeding the
XOR is folded in instead and the constant ends up in a register that
loops hoist.

Cycles against trunk on Zen 5, from `bench_runner.py` with 48 runs per
side, each run at a random code layout, and table sizes of 64, 3584, and
57344:
- Integer and pointer hits: -3.0% to -5.7% in `Map`, -0.8% to -2.4% in
`Set`.
- Integer and pointer misses: up to -12.6%, except `Set<int*>` at +1.4%
(64) and +6.1% (3584).
- String keys: -2.8% to +2.1%, with `Map` updates at +1.1% to +1.6%.
- Hash latency, without layout randomization: integers and pointers
-9.8%, empty strings +5.4% (0.1 cycles), other strings not significant.

Same method on an Apple M1, with #7870 on both sides. Without it, this
change moves benchmark loop counters between `sp`- and `x29`-relative
stack slots, which alone makes `Set<int>` lookups up to 49% slower on
the M1.
- Integer and pointer hits: -1.0% to -10.3% in `Map`, -24.5% to +1.2% in
`Set`.
- Integer and pointer misses: -3.9% to -48.0%, except +7.2% to +59.3% at
3584, from longer probes (instructions +6.4% to +70.6%).
- Inserting 64 integer keys: +12.1% to +15.0%, from store memory-order
violations.
- String keys: -6.6% to +3.9%.
- Hash latency: integers and pointers -9.0% to -10.0%, empty strings
+48.8% (1 cycle).

Release builds now iterate a given table in the same order in every run.
Code that depends on that order still fails in debug builds.

Assisted-by: Claude Code
2026-10-06 15:13:10 +00:00
josh11b bf64992afb Update index of principles (#7902)
Add missing principles and alphabetize
2026-10-06 04:17:28 +00:00
2031 changed files with 173921 additions and 136998 deletions

No files matched your search

+59 -22
View File
@@ -2,7 +2,8 @@
name: Diagnostics
description:
Instructions for declaring, formatting, emitting, testing, and styling
diagnostic messages (errors, warnings, notes) in the Carbon toolchain.
diagnostic messages (errors, warnings, and the labels that explain them) in
the Carbon toolchain.
---
# Diagnostics in the Carbon Toolchain
@@ -15,9 +16,9 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
The Carbon compiler features a highly-engineered, context-aware diagnostics
framework designed to deliver precise, readable, and highly targetable
diagnostic output (errors, warnings, notes). This document establishes strict
rules for declaring, formatting, emitting, testing, and styling compiler
diagnostics.
diagnostic output (errors, warnings, and the labels that explain them). This
document establishes strict rules for declaring, formatting, emitting, testing,
and styling compiler diagnostics.
---
@@ -39,8 +40,8 @@ Diagnostics are handled via three decoupled core components:
[kind.def](../../../toolchain/diagnostics/kind.def).
2. **Emitters**: Specialized formatting pipelines (parameterized on custom
phase location types `LocT` like `Token` or `LocId`) that convert raw tokens
to standardized physical source locations (file, line, column, and text
snippet).
to standardized physical source locations (file, line, column, and the text
of the line).
3. **Consumers**: Pipelines that process, track, filter, and sort diagnostics.
The default `SortingConsumer` buffers and stable-sorts diagnostics based on
their `last_byte_offset` matching compiler traversal order to ensure perfect
@@ -86,7 +87,7 @@ declaration (`CARBON_DIAGNOSTIC` or `CARBON_DIAGNOSTIC_ON_SCOPE`).
- **File Scope**: If the diagnostic is shared among multiple functions inside
the _same_ file, declare it at **file scope** inside the anonymous namespace
of the `.cpp` file.
- **Global Scope**: If a diagnostic (such as a shared helper note) is reused
- **Global Scope**: If a diagnostic (such as a shared helper label) is reused
_across different physical files_, define it in a shared header (e.g.
context/check helpers) and mark it `extern` where applicable, ensuring the
macro is only invoked once.
@@ -155,40 +156,66 @@ Custom structures can define how they serialize inside diagnostics using the
### Fluent Builder Pattern
For compound diagnostics requiring multiple sub-notes, carets, or custom code
overrides, use `Build` to chain actions fluently:
A diagnostic is one message plus the labels that mark the source explaining it.
Use `Build` to state the message and `Attach` to mark each place the reader has
to look, chained fluently:
```cpp
CARBON_DIAGNOSTIC_LABEL(ModifierPrevious, Info, "`{0}` previously appeared here",
Lex::TokenKind);
context.emitter()
.Build(second_node, ModifierRepeated, context.token_kind(second_node))
.Note(first_node, ModifierPrevious, context.token_kind(first_node))
.OverrideSnippet("custom snippet...")
.Attach(first_node, ModifierPrevious, context.token_kind(first_node))
.Emit();
```
Labels are declared with `CARBON_DIAGNOSTIC_LABEL` where they are attached, and
are not registered in `kind.def`; `check_diagnostics.py` checks that each one is
attached and covered. A label is either `Primary` -- the range the message
itself is about -- or `Info`, for somewhere else the reader must look; those two
are the only categories, because anything not read against the code it names is
not a label. `Attach(loc)` with no label marks a range wordlessly, which is what
to use when a label would only restate the message.
Every diagnostic should attach at least one label; a message with nothing
attached has nothing to show. See
[diagnostics.md](../../../toolchain/docs/diagnostics.md) for which range to pick
and when a label earns words.
> [!SAFETY] Emitter builders are marked `[[nodiscard]]`. To prevent a developer
> from creating a builder but failing to terminal-chain `.Emit()`, the builder
> uses an rvalue overload `Emit() &&` that triggers a compile-time
> `static_assert(false)`. You must save the builder to an lvalue or execute the
> chain exactly as `emitter.Build(...).Note(...).Emit()`.
> chain exactly as `emitter.Build(...).Attach(...).Emit()`.
### RAII Context & Annotation Scopes
Manage large checking structures requiring blanket note context using RAII block
Manage large checking structures requiring blanket context using RAII block
scopes:
- `ContextScope`: Automatically converts any diagnostics emitted within its
scope into sub-notes under a high-level operation descriptor:
- `ContextScope`: Attaches a context to every diagnostic emitted within its
scope, describing the higher-level operation that failed because of it. A
context is declared with `CARBON_DIAGNOSTIC_CONTEXT`, or
`CARBON_DIAGNOSTIC_SOFT_CONTEXT` for one dropped when the diagnostic
already has a context:
```cpp
ContextScope context_scope(&context.emitter(), [&](ContextBuilder& builder) {
builder.Context(eval_loc, InCallToEvalFn);
});
// any checker error emitted here will automatically append the 'InCallToEvalFn' note
Diagnostics::ContextScope diagnostic_context(
&context.emitter(), [&](auto& builder) {
CARBON_DIAGNOSTIC_CONTEXT(
QualifiedDeclInIncompleteClassScope,
"cannot declare a member of incomplete class {0}", SemIR::TypeId);
builder.Attach(loc_id, QualifiedDeclInIncompleteClassScope,
context.classes().Get(class_id).self_type_id);
});
```
- `AnnotationScope`: RAII block scope that automatically attaches blanket note
annotations to all scoped diagnostics.
A context is not a label: its text stands alone as a sentence, which is why
it leads the diagnostic rather than following it.
- `AnnotationScope`: RAII block scope that attaches labels to every diagnostic
emitted within it, using the same `Builder` as a direct emission.
---
@@ -257,5 +284,15 @@ Carbon strictly enforces testing coverage at build-time.
```
3. **Build Enforcement**: Failing to provide a diagnostic test check matcher
triggers a build compilation error on the target test
triggers a failure on the target test
`//toolchain/diagnostics:coverage_test`.
4. **Label Enforcement**: A label is declared where it is attached rather than
in a central registry, so `coverage_test` has no list of labels to compare a
matched name against and leaves any name that isn't a kind alone.
[check_diagnostics.py](../../../toolchain/diagnostics/check_diagnostics.py)
is what checks them: it reads the declarations and the testdata directly, so
it catches a label with no test, a name matched on that is neither a kind
nor a label, and a kind registered in `kind.def` that nothing declares. It
runs as a pre-commit hook, which CI runs too, rather than as a bazel test --
reading every source file is what it needs and what a test in the sandbox
cannot do.
+5
View File
@@ -125,6 +125,11 @@ common:linux --define=pfm=1
# Disables `actions.declare_symlink`. Done for cross-environment support.
common --allow_unresolved_symlinks=false
# Starting with rules_python 2.0, Python binaries use venvs created with
# `actions.declare_symlink`, which we disable above. Have it create them at
# runtime instead.
common --@rules_python//python/config_settings:venvs_use_declare_symlink=no
# Removes the leading `/proc/self/cwd/` from file paths in the debug info. Some
# tools like VS Code don't understand `/proc/self/cwd` in places like terminal
# stack dumps, but do understand paths relative to the workspace root.
+1
View File
@@ -7,6 +7,7 @@ BasedOnStyle: Google
AllowShortBlocksOnASingleLine: 'false'
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: 'false'
AttributeMacros: ['CARBON_PRESERVE_NONE']
DerivePointerAlignment: 'false'
ExperimentalAutoDetectBinPacking: 'false'
FixNamespaceComments: 'true'
@@ -13,7 +13,7 @@ runs:
steps:
# Setup Python and related tools with uv.
- name: Set up uv and Python
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
with:
enable-cache: true
version: '0.11.15'
@@ -135,8 +135,9 @@ runs:
# more reliably succeed without retrying "permanent" errors.
#
# Disable lockfile updates, because some actions want to see
# differences.
./scripts/run_bazel.py --attempts=5 --retry-all-errors \
mod --lockfile_mode=off deps
# differences. This avoids `bazel mod deps`, which evaluates every
# module extension; without the lockfile, that includes expensive ones
# that builds don't use, like the Rust crates newer protobuf versions
# depend on.
./scripts/run_bazel.py --attempts=5 --retry-all-errors \
cquery --lockfile_mode=off //... | wc -l
+1 -1
View File
@@ -28,7 +28,7 @@ runs:
# minimum supported LLVM version.
- name: Cache Homebrew
id: cache-homebrew-macos
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
# Cover all the critical parts of Homebrew here. Homebrew on Arm macOS
# uses its own prefix making this easy to cover, but we need a few
+14 -2
View File
@@ -13,7 +13,7 @@ runs:
# everything provides a little flexibility to get space while trying
# to shrink the build.
- name: Free up disk space
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1
uses: jlumbroso/free-disk-space@ceedf095f4ec1a097402bc6bd80831f2e1a6fde6 # v2.0.0
with:
android: true
dotnet: true
@@ -22,6 +22,18 @@ runs:
# to save time.
large-packages: false
# The image's Android environment variables still point into the SDK and
# NDK deleted above, which confuses tools that look for them, such as
# rules_android. `$GITHUB_ENV` can't unset variables, so set them to empty,
# which rules_android treats as unset.
- name: Clear Android environment variables
shell: bash
run: |
for var in ANDROID_HOME ANDROID_SDK_ROOT ANDROID_NDK ANDROID_NDK_HOME \
ANDROID_NDK_LATEST_HOME ANDROID_NDK_ROOT; do
echo "$var=" >> "$GITHUB_ENV"
done
# Select the LLVM release - by the cache key and the download.
- name: Select LLVM release
shell: bash
@@ -37,7 +49,7 @@ runs:
# reliability.
- name: Cache LLVM and Clang installation
id: cache-llvm-ubuntu
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/llvm
key: LLVM-${{ env.LLVM_RELEASE }}-Cache-ubuntu-${{ runner.arch }}
+1 -1
View File
@@ -29,7 +29,7 @@ runs:
# action run for the merge queue. We filter steps based on the paths here,
# and condition steps on the output.
- id: filter
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
with:
filters: |
has_code:
+61
View File
@@ -0,0 +1,61 @@
# 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
# Dependabot version updates for our GitHub Actions, pre-commit hooks, and Bazel
# modules. See .github/workflows/README.md for how these PRs are handled.
#
# Updates keep our existing pinning: actions stay pinned to full commit SHAs
# with a `# vX.Y.Z` comment, and pre-commit hooks stay frozen to commit SHAs
# with a `# frozen: vX.Y.Z` comment.
#
# Security updates for other ecosystems, such as npm and pip, are enabled in the
# repository settings and don't need an entry here.
version: 2
updates:
- package-ecosystem: github-actions
# `/` only covers `.github/workflows`; list composite actions separately.
directories: ['/', '/.github/actions/*']
schedule:
interval: weekly
# Give new releases a week to be vetted before we adopt them.
cooldown:
default-days: 7
# Use our existing label instead of Dependabot's per-ecosystem labels.
labels: ['dependencies']
groups:
# Batch minor and patch updates, but put major updates in a separate PR so
# they get a closer look.
github-actions:
patterns: ['*']
update-types: ['minor', 'patch']
github-actions-major:
patterns: ['*']
update-types: ['major']
- package-ecosystem: pre-commit
directory: '/'
schedule:
interval: monthly
cooldown:
default-days: 7
labels: ['dependencies']
groups:
pre-commit:
patterns: ['*']
# Dependabot regenerates `MODULE.bazel.lock` on a single platform, so the test
# workflow may still ask for platform-specific lockfile updates. LLVM is
# pinned with `git_override`, which Dependabot doesn't update.
- package-ecosystem: bazel
directory: '/'
schedule:
interval: monthly
cooldown:
default-days: 7
labels: ['dependencies']
groups:
bazel:
patterns: ['*']
+7 -6
View File
@@ -4,6 +4,7 @@
name: 'Auto label PRs'
on:
# zizmor: ignore[dangerous-triggers] - {necessary to mutate the PR}
pull_request_target:
types: [opened, ready_for_review]
@@ -19,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
with:
disable-sudo: true
egress-policy: block
@@ -28,7 +29,7 @@ jobs:
api.github.com:443
- id: filter
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
with:
filters: |
documentation:
@@ -48,7 +49,7 @@ jobs:
- 'WORKSPACE'
- 'bazel/**'
- 'github_tools/**'
- 'proposal/scripts/**'
- 'proposals/scripts/**'
- 'scripts/**'
# Here we only want the `proposal` label when a *new* file is added
@@ -111,12 +112,12 @@ jobs:
# Note that this is not a path-based label, but an *author* based label,
# and it applies orthogonally to the others.
- id: automated
- id: bot
if:
contains(fromJSON('["CarbonInfraBot", "dependabot"]'),
contains(fromJSON('["CarbonInfraBot", "dependabot[bot]"]'),
github.event.pull_request.user.login)
run: |
gh pr edit "${PR}" --add-label "automated" --repo carbon-language/carbon-lang
gh pr edit "${PR}" --add-label "bot" --repo carbon-language/carbon-lang
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR: ${{ github.event.pull_request.html_url }}
+3 -3
View File
@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
with:
disable-sudo: true
egress-policy: block
@@ -33,12 +33,12 @@ jobs:
# Note: pull_request_target checks out the base branch by default.
# This is safe as it avoids running untrusted code from the PR branch.
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
with:
enable-cache: true
version: '0.11.15'
+8 -3
View File
@@ -26,7 +26,7 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
with:
egress-policy: block
# When adding endpoints, see README.md.
@@ -41,29 +41,34 @@ jobs:
download.gnome.org:443
downloads.sourceforge.net:443
files.pythonhosted.org:443
formulae.brew.sh:443
github.com:443
go.dev:443
index.crates.io:443
mirror.bazel.build:443
mirrors.kernel.org:443
nodejs.org:443
oauth2.googleapis.com:443
objects.githubusercontent.com:443
pkg-containers.githubusercontent.com:443
pypi.org:443
raw.githubusercontent.com:443
registry.npmjs.org:443
release-assets.githubusercontent.com:443
releases.astral.sh:443
releases.bazel.build:443
static.crates.io:443
static.rust-lang.org:443
storage.googleapis.com:443
uploads.github.com:443
www.googleapis.com:443
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- id: filter
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
with:
predicate-quantifier: 'every'
filters: |
@@ -0,0 +1,87 @@
# 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
# Replaces Dependabot's PR description with a short markdown list of the updated
# dependencies. We squash-merge using the PR description as the commit message,
# and Dependabot's descriptions are mostly HTML: release notes, commit lists,
# and command help. The original description remains in the PR's edit history.
name: 'Dependabot PR description'
on:
pull_request:
types: [opened, reopened, synchronize, edited]
# Dependabot can push and edit the description in quick succession.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions: {}
jobs:
rewrite:
if: github.event.pull_request.user.login == 'dependabot[bot]'
runs-on: ubuntu-latest
permissions:
contents: read # For dependabot/fetch-metadata to read the PR's commit.
pull-requests: write # For `gh pr edit`.
steps:
- name: Harden Runner
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
with:
disable-sudo: true
egress-policy: block
# prettier-ignore
allowed-endpoints: >
api.github.com:443
- id: metadata
uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0
- name: Replace the description
env:
DEPENDENCIES: ${{ steps.metadata.outputs.updated-dependencies-json }}
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_URL: ${{ github.event.pull_request.html_url }}
run: |
# Only replace a description that Dependabot wrote: either nobody has
# edited it, or Dependabot last did, such as when rebasing. This
# leaves human edits and our own earlier rewrites alone. Don't look
# for markers in the text, because Dependabot truncates descriptions
# that are too long.
# shellcheck disable=SC2016 # The `$`s are GraphQL variables.
editor="$(gh api graphql \
-f owner="${GITHUB_REPOSITORY%/*}" \
-f name="${GITHUB_REPOSITORY#*/}" \
-F number="$PR_NUMBER" \
-f query='
query($owner: String!, $name: String!, $number: Int!) {
repository(owner: $owner, name: $name) {
pullRequest(number: $number) { editor { login } }
}
}' \
--jq '.data.repository.pullRequest.editor.login // ""')"
if [[ -n "$editor" && "$editor" != "dependabot" ]]; then
echo "The description was last edited by $editor, so leaving it."
exit 0
fi
# One short line per dependency. GitHub hard-wraps squash commit
# messages at 72 columns, so avoid tables and long links, and drop
# the URL prefix from pre-commit hook names.
body="$(jq -r '
unique_by([.dependencyName, .prevVersion, .newVersion])[]
| "- `\(.dependencyName | ltrimstr("https://github.com/"))` "
+ (.prevVersion | ltrimstr("v")) + " -> "
+ (.newVersion | ltrimstr("v"))
+ if .updateType == "version-update:semver-major"
then " (major)" else "" end
' <<<"$DEPENDENCIES")"
if [[ -z "$body" ]]; then
echo "Found no updated dependencies, so leaving it alone."
exit 0
fi
gh pr edit "$PR_URL" --repo "$GITHUB_REPOSITORY" --body "$body"
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
with:
egress-policy: audit
+4 -4
View File
@@ -22,17 +22,17 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
with:
enable-cache: true
version: '0.11.15'
@@ -40,7 +40,7 @@ jobs:
- name: Prebuild actions
run: ./website/prebuild.py
- name: Setup Ruby
uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0
uses: ruby/setup-ruby@7f797815d8adceb1c412e7abefdd1ab4abc47a5f # v1.335.0
with:
# Runs 'bundle install' and caches installed gems automatically.
bundler-cache: true
+8 -8
View File
@@ -27,17 +27,17 @@ jobs:
contents: read
steps:
- name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
with:
enable-cache: true
version: '0.11.15'
@@ -45,9 +45,9 @@ jobs:
- name: Prebuild actions
run: ./website/prebuild.py
- name: Setup Pages
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
- name: Setup Ruby
uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0
uses: ruby/setup-ruby@7f797815d8adceb1c412e7abefdd1ab4abc47a5f # v1.335.0
with:
# Runs 'bundle install' and caches installed gems automatically.
bundler-cache: true
@@ -65,7 +65,7 @@ jobs:
- name: Upload artifact
# Automatically uploads an artifact from the './_site' directory by
# default.
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
deploy:
environment:
@@ -79,10 +79,10 @@ jobs:
id-token: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
with:
egress-policy: audit
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
+7 -2
View File
@@ -37,7 +37,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
with:
egress-policy: block
# When adding endpoints, see README.md.
@@ -52,24 +52,29 @@ jobs:
download.gnome.org:443
downloads.sourceforge.net:443
files.pythonhosted.org:443
formulae.brew.sh:443
github.com:443
go.dev:443
index.crates.io:443
mirror.bazel.build:443
mirrors.kernel.org:443
nodejs.org:443
oauth2.googleapis.com:443
objects.githubusercontent.com:443
pkg-containers.githubusercontent.com:443
pypi.org:443
raw.githubusercontent.com:443
registry.npmjs.org:443
release-assets.githubusercontent.com:443
releases.bazel.build:443
static.crates.io:443
static.rust-lang.org:443
storage.googleapis.com:443
uploads.github.com:443
www.googleapis.com:443
- name: Checkout branch
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
+13 -8
View File
@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
with:
egress-policy: block
# When adding endpoints, see README.md.
@@ -33,24 +33,29 @@ jobs:
download.gnome.org:443
downloads.sourceforge.net:443
files.pythonhosted.org:443
formulae.brew.sh:443
github.com:443
go.dev:443
index.crates.io:443
mirror.bazel.build:443
mirrors.kernel.org:443
nodejs.org:443
oauth2.googleapis.com:443
objects.githubusercontent.com:443
pkg-containers.githubusercontent.com:443
pypi.org:443
raw.githubusercontent.com:443
registry.npmjs.org:443
release-assets.githubusercontent.com:443
releases.astral.sh:443
releases.bazel.build:443
static.crates.io:443
static.rust-lang.org:443
storage.googleapis.com:443
uploads.github.com:443
www.googleapis.com:443
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
@@ -60,21 +65,21 @@ jobs:
matrix_runner: ubuntu-22.04
remote_cache_upload: '--remote_upload_local_results=false'
- uses: j178/prek-action@01345c78b7de7d79edf368729212760396ba9345 # v2
- uses: j178/prek-action@44924fe9ef4a6f88253c4b6b1bf11c689d3ce53d # v3.1.0
# We want to automatically create github suggestions for prek file
# changes for a pull request. But `pull_request` actions never have write
# permissions to the repository, so we create the suggestions in a separate
# privileged `workflow_run` action in prek_suggestions.yaml. Here,
# we upload the diffs and event configuration to an artifact for use by
# that action.
# permissions to the repository, so we create the suggestions (or, for
# Dependabot PRs, commit the fixes) in a separate privileged `workflow_run`
# action in prek_suggestions.yaml. Here, we upload the diffs and event
# configuration to an artifact for use by that action.
- name: Collect prek output
if: failure()
run: |
mkdir -p prek-output
git diff > prek-output/diff
cp $GITHUB_EVENT_PATH prek-output/event
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
if: failure()
with:
name: prek output
+117 -7
View File
@@ -11,9 +11,18 @@ name: 'Add prek suggestions'
# we use a separate privileged `workflow_run` action here to pick up its results
# and convert them into suggestion comments.
#
# For Dependabot PRs, prek's fixes usually come from an updated hook and are in
# files outside the PR's diff, where GitHub doesn't allow suggestions. So
# instead, we commit the fixes directly to Dependabot's branch.
#
# A privileged `workflow_run` action is inherently risky, because it could be
# tricked into running untrusted code from a PR. So neither job here runs
# anything from the PR, and both treat prek's output as untrusted.
#
# This action is only run from the workflow file on the trunk branch. Changes to
# this file will not take effect until they are merged to trunk.
on:
# zizmor: ignore[dangerous-triggers] - {necessary to act on prek's results}
workflow_run:
workflows: [prek]
types:
@@ -25,14 +34,16 @@ permissions:
jobs:
pull-request-suggestions:
# Only generate suggestions if prek for a PR failed.
# Only generate suggestions if prek for a PR failed. Dependabot's PRs are
# handled by `dependabot-fixes` below.
if: |
github.event.workflow_run.conclusion == 'failure' &&
github.event.workflow_run.event == 'pull_request'
github.event.workflow_run.event == 'pull_request' &&
!startsWith(github.event.workflow_run.head_branch, 'dependabot/')
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
with:
disable-sudo: true
egress-policy: block
@@ -42,17 +53,18 @@ jobs:
github.com:443
objects.githubusercontent.com:443
raw.githubusercontent.com:443
release-assets.githubusercontent.com:443
- uses: reviewdog/action-setup@3f401fe1d58fe77e10d665ab713057375e39b887 # v1.3.0
- uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # v1.5.0
with:
reviewdog_version: latest
reviewdog_version: v0.21.2
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Download prek output
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: prek output
github-token: ${{ secrets.GITHUB_TOKEN }}
@@ -68,3 +80,101 @@ jobs:
cat ./diff | \
GITHUB_EVENT_PATH=./event \
reviewdog -f=diff -f.diff.strip=1 -reporter=github-pr-review
dependabot-fixes:
# Only handle prek failures from Dependabot's own pushes to its branches in
# this repository. This also keeps us from reacting to our own fix commits.
if: |
github.event.workflow_run.conclusion == 'failure' &&
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.actor.login == 'dependabot[bot]' &&
github.event.workflow_run.head_repository.full_name == github.repository &&
startsWith(github.event.workflow_run.head_branch, 'dependabot/')
runs-on: ubuntu-latest
permissions:
actions: read # To download the artifact from the prek run.
contents: read # For actions/checkout.
pull-requests: read # To check the PR before pushing to it.
steps:
- name: Harden Runner
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
with:
disable-sudo: true
egress-policy: block
# prettier-ignore
allowed-endpoints: >
api.github.com:443
github.com:443
- name: Download prek output
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: prek output
path: ${{ runner.temp }}/prek-output
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.workflow_run.head_sha }}
persist-credentials: false
# The diff comes from running newly updated hooks, so treat it as
# untrusted: never run anything from it, and only accept edits to existing
# files outside of `.github`.
- name: Commit prek's fixes
env:
BRANCH: ${{ github.event.workflow_run.head_branch }}
DIFF: ${{ runner.temp }}/prek-output/diff
GH_TOKEN: ${{ github.token }}
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number }}
# A CarbonInfraBot token that can push to this repository. A push with
# `GITHUB_TOKEN` wouldn't trigger CI for the new commit.
PUSH_TOKEN: ${{ secrets.CARBON_INFRA_BOT_DEPENDABOT_FIXES }}
run: |
# Check the PR itself, rather than trusting the artifact.
if [[ -z "$PR_NUMBER" ]]; then
echo "No PR is associated with this run, so skipping."
exit 0
fi
pr="$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" \
--jq '[.user.login, .head.ref, .head.sha] | join(" ")')"
if [[ "$pr" != "dependabot[bot] $BRANCH $HEAD_SHA" ]]; then
echo "PR #$PR_NUMBER isn't Dependabot's or has moved on; skipping."
exit 0
fi
if [[ ! -s "$DIFF" ]]; then
echo "prek didn't change any files, so there's nothing to commit."
exit 0
fi
# Have git describe the diff: `--numstat` gives a tab-separated line
# per file with its added and removed line counts ("-" for binary
# files), and `--summary` adds a line for anything that isn't an edit,
# such as creating, deleting, or renaming a file. Only accept text
# edits to files outside of `.github`.
changes="$(git apply --numstat --summary "$DIFF")"
rejected="$(awk -F'\t' '
!($1 ~ /^[0-9]+$/ && $2 ~ /^[0-9]+$/ && $3 !~ /^\.github\//)
' <<<"$changes")"
if [[ -n "$rejected" ]]; then
echo "::error::Not applying prek's diff because of:"
echo "$rejected"
exit 1
fi
if ! git apply "$DIFF"; then
echo "::error::prek's diff doesn't apply; run prek on the branch."
exit 1
fi
if [[ -z "$PUSH_TOKEN" ]]; then
echo "::error::CARBON_INFRA_BOT_DEPENDABOT_FIXES isn't available."
exit 1
fi
git -c user.name=CarbonInfraBot \
-c user.email=64870956+CarbonInfraBot@users.noreply.github.com \
commit --all --quiet --message='Apply fixes from updated prek hooks'
remote="https://x-access-token:${PUSH_TOKEN}@github.com"
git push "$remote/$GITHUB_REPOSITORY.git" "HEAD:refs/heads/$BRANCH"
-115
View File
@@ -1,115 +0,0 @@
# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
# Exceptions. See /LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
# Applies mutual exclusivity between states labels:
# - proposal draft
# - proposal rfc
# - proposal accepted
# - proposal declined
# - proposal deferred
#
# The "proposal" label is always applied in order to make searching for all
# proposals (regardless of state) easy, although it will typically already be
# present.
name: Proposal labeled
on:
pull_request_target:
types:
- labeled
permissions:
pull-requests: write # For gh to edit labels.
# TODO: `--repo carbon-language/carbon-lang` is a temporary workaround for:
# https://github.com/cli/cli/issues/11055
# Once a later version is released on runners, maybe August 2025, we should be
# able to remove the extra flag.
jobs:
proposal_labeled:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
with:
disable-sudo: true
egress-policy: block
# prettier-ignore
allowed-endpoints: >
api.github.com:443
- name: draft
if: |
github.event.label.name == 'proposal draft'
run: |
gh pr edit "${PR}" \
--remove-label "proposal rfc" \
--remove-label "proposal accepted" \
--remove-label "proposal declined" \
--remove-label "proposal deferred" \
--add-label "proposal" \
--repo carbon-language/carbon-lang
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR: ${{ github.event.pull_request.html_url }}
- name: rfc
if: |
github.event.label.name == 'proposal rfc'
run: |
gh pr edit "${PR}" \
--remove-label "proposal draft" \
--remove-label "proposal accepted" \
--remove-label "proposal declined" \
--remove-label "proposal deferred" \
--add-label "proposal" \
--repo carbon-language/carbon-lang
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR: ${{ github.event.pull_request.html_url }}
- name: accepted
if: |
github.event.label.name == 'proposal accepted'
run: |
gh pr edit "${PR}" \
--remove-label "proposal draft" \
--remove-label "proposal rfc" \
--remove-label "proposal declined" \
--remove-label "proposal deferred" \
--add-label "proposal" \
--repo carbon-language/carbon-lang
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR: ${{ github.event.pull_request.html_url }}
- name: declined
if: |
github.event.label.name == 'proposal declined'
run: |
gh pr edit "${PR}" \
--remove-label "proposal draft" \
--remove-label "proposal rfc" \
--remove-label "proposal accepted" \
--remove-label "proposal deferred" \
--add-label "proposal" \
--repo carbon-language/carbon-lang
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR: ${{ github.event.pull_request.html_url }}
- name: deferred
if: |
github.event.label.name == 'proposal deferred'
run: |
gh pr edit "${PR}" \
--remove-label "proposal draft" \
--remove-label "proposal rfc" \
--remove-label "proposal accepted" \
--remove-label "proposal declined" \
--add-label "proposal" \
--repo carbon-language/carbon-lang
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR: ${{ github.event.pull_request.html_url }}
-45
View File
@@ -1,45 +0,0 @@
# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
# Exceptions. See /LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
# Applies the "proposal rfc" label when a proposal is marked ready for review.
name: Proposal ready for review
on:
pull_request_target:
types:
- ready_for_review
permissions:
pull-requests: write # For gh to edit labels.
# TODO: `--repo carbon-language/carbon-lang` is a temporary workaround for:
# https://github.com/cli/cli/issues/11055
# Once a later version is released on runners, maybe August 2025, we should be
# able to remove the extra flag.
jobs:
proposal_ready:
if: contains(github.event.pull_request.labels.*.name, 'proposal')
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
with:
disable-sudo: true
egress-policy: block
# prettier-ignore
allowed-endpoints: >
api.github.com:443
- name: rfc
run: |
gh pr edit "${PR}" \
--remove-label "proposal draft" \
--remove-label "proposal accepted" \
--remove-label "proposal declined" \
--remove-label "proposal deferred" \
--add-label "proposal rfc" \
--repo carbon-language/carbon-lang
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR: ${{ github.event.pull_request.html_url }}
+2 -2
View File
@@ -25,13 +25,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
with:
egress-policy: audit
# Checkout our main repository.
- name: Checkout the main repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
+14 -5
View File
@@ -29,7 +29,7 @@ jobs:
matrix:
# Test a recent version of each supported OS, covering both x86-64 and
# AArch64: Linux on x86-64 and AArch64, and macOS on AArch64.
runner: ['ubuntu-22.04', 'ubuntu-22.04-arm', 'macos-14']
runner: ['ubuntu-22.04', 'ubuntu-22.04-arm', 'macos-26']
# Create a synthetic matrix dimension with the event name for filtering.
event: ['${{ github.event_name }}']
config:
@@ -40,7 +40,7 @@ jobs:
- name: 'ASan'
flags: '--config=asan'
exclude:
- runner: 'macos-14'
- runner: 'macos-26'
config: { name: 'ASan', flags: '--config=asan' }
- event: 'pull_request'
config: { name: 'ASan', flags: '--config=asan' }
@@ -49,7 +49,7 @@ jobs:
runs-on: ${{ matrix.runner }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
with:
egress-policy: block
# When adding endpoints, see README.md.
@@ -64,24 +64,29 @@ jobs:
download.gnome.org:443
downloads.sourceforge.net:443
files.pythonhosted.org:443
formulae.brew.sh:443
github.com:443
go.dev:443
index.crates.io:443
mirror.bazel.build:443
mirrors.kernel.org:443
nodejs.org:443
oauth2.googleapis.com:443
objects.githubusercontent.com:443
pkg-containers.githubusercontent.com:443
pypi.org:443
raw.githubusercontent.com:443
registry.npmjs.org:443
release-assets.githubusercontent.com:443
releases.astral.sh:443
releases.bazel.build:443
static.crates.io:443
static.rust-lang.org:443
storage.googleapis.com:443
uploads.github.com:443
www.googleapis.com:443
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
@@ -93,7 +98,11 @@ jobs:
${{ github.event_name == 'pull_request' &&
github.event.pull_request.base.sha ||
github.event.merge_group.base_sha }}
remote_cache_key: ${{ secrets.CARBON_BUILDS_GITHUB }}
# The remote cache key is only used outside of PRs, so only make it
# available on those events. PRs get an empty string.
remote_cache_key:
${{ (github.event_name != 'pull_request' &&
secrets.CARBON_BUILDS_GITHUB) || '' }}
targets_file: ${{ runner.temp }}/targets
# Build and run just the tests impacted by the PR or merge group.
+4 -4
View File
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
with:
disable-sudo: true
egress-policy: block
@@ -24,7 +24,7 @@ jobs:
allowed-endpoints: >
api.github.com:443
- uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
- uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
with:
stale-issue-message: >
We triage inactive PRs and issues in order to make it easier to find
@@ -56,8 +56,8 @@ jobs:
stale-issue-label: 'inactive'
stale-pr-label: 'inactive'
exempt-issue-labels:
'long term issue,design idea,design update,good first issue,leads
question'
'long term issue,design idea,needs design doc update,good first
issue,leads question'
days-before-stale: 90
days-before-close: 14
days-before-issue-close: -1
+7 -5
View File
@@ -4,6 +4,7 @@
# Update versions with:
# pre-commit autoupdate --freeze && pre-commit run -a
# Dependabot also proposes monthly updates; see .github/dependabot.yml.
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
@@ -32,7 +33,7 @@ repos:
# Run markdown linting early so that doc style and table-of-contents see the
# linted state.
- repo: https://github.com/rvben/rumdl-pre-commit
rev: v0.2.58
rev: b0cff962ee693b9e612c097d22e71e0cba127447 # frozen: v0.3.2
hooks:
- id: rumdl
args: [--fix]
@@ -51,7 +52,7 @@ repos:
# Re-run markdown linting to fix any issues caused by doc style and TOC. This
# is very fast, so it shouldn't be problematic to run twice.
- repo: https://github.com/rvben/rumdl-pre-commit
rev: v0.2.58
rev: b0cff962ee693b9e612c097d22e71e0cba127447 # frozen: v0.3.2
hooks:
- id: rumdl
args: [--fix]
@@ -67,7 +68,7 @@ repos:
# Formatters should be run late so that they can re-format any prior changes.
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 0c7b6c989466a93942def1f84baf36ddfcd60c83 # frozen: v0.15.14
rev: 25bcf95026b96e2dd3c0aec3090c3d6ef6ca2e6c # frozen: v0.17.3
hooks:
- id: ruff-check
args: [--fix]
@@ -173,8 +174,9 @@ repos:
(?x)^(
toolchain/.*\.cpp|
toolchain/.*\.h|
toolchain/.*/testdata/.*|
toolchain/diagnostics/check_diagnostics\.py|
toolchain/diagnostics/diagnostic_kind\.def
toolchain/diagnostics/kind\.def
)$
pass_filenames: false
@@ -188,7 +190,7 @@ repos:
files: ^.*/BUILD$
pass_filenames: false
- repo: https://github.com/codespell-project/codespell
rev: 2ccb47ff45ad361a21071a7eedda4c37e6ae8c5a # frozen: v2.4.2
rev: 57b21406f092110c18776e39b0bda50d37c945c8 # frozen: v2.4.3
hooks:
- id: codespell
args: ['-I', '.codespell_ignore', '--uri-ignore-words-list', '*']
+1 -1
View File
@@ -72,7 +72,7 @@ If you're looking for a quick way to contribute, commenting on proposals is a
way to provide proposal authors with a breadth of feedback. The
["leads questions" label](https://github.com/carbon-language/carbon-lang/issues?q=is%3Aissue+is%3Aopen+label%3A%22leads+question%22)
has questions the community is looking for a decision on. The
[list of open proposals](https://github.com/carbon-language/carbon-lang/pulls?q=is%3Apr+is%3Aopen+label%3A%22proposal+rfc%22)
[list of open proposals](https://github.com/carbon-language/carbon-lang/pulls?q=is%3Apr+is%3Aopen+draft%3Afalse+label%3Aproposal)
will have more mature proposals that are nearing a decision. For more about the
difference, see the [evolution process](docs/project/evolution.md).
+21 -19
View File
@@ -20,23 +20,28 @@ of:
`MODULE.bazel.lock`; create a PR with those changes in order to include
the host platform.
`bazel mod deps` evaluates every module extension, but reuses results recorded
in `MODULE.bazel.lock`. Re-evaluating some extensions can be expensive; for
example, the Rust crates newer protobuf versions depend on require downloading
a Rust toolchain.
For updates, run `scripts/query_module_versions.py` to list the latest package
versions.
"""
module(name = "carbon")
bazel_dep(name = "abseil-cpp", version = "20260107.1")
bazel_dep(name = "bazel_skylib", version = "1.9.0")
bazel_dep(name = "abseil-cpp", version = "20260817.0")
bazel_dep(name = "bazel_skylib", version = "1.9.2")
bazel_dep(name = "boost.unordered", version = "1.90.0.bcr.1")
bazel_dep(name = "google_benchmark", version = "1.9.5")
bazel_dep(name = "googletest", version = "1.17.0.bcr.2")
bazel_dep(name = "google_benchmark", version = "1.10.0")
bazel_dep(name = "googletest", version = "1.18.0.bcr.1")
bazel_dep(name = "libpfm", version = "4.13.0")
bazel_dep(name = "re2", version = "2025-11-05.bcr.1")
bazel_dep(name = "rules_cc", version = "0.2.25")
bazel_dep(name = "rules_pkg", version = "1.2.0")
bazel_dep(name = "rules_cc", version = "0.2.27")
bazel_dep(name = "rules_pkg", version = "1.3.0")
bazel_dep(name = "rules_shell", version = "0.8.0")
bazel_dep(name = "tcmalloc", version = "0.0.0-20250927-12f2552")
bazel_dep(name = "tcmalloc", version = "0.0.0-20260818-15db23d")
bazel_dep(name = "tree-sitter-bazel", version = "0.26.5")
bazel_dep(name = "wolfd_bazel_compile_commands", version = "0.5.2", dev_dependency = True)
@@ -67,11 +72,11 @@ register_toolchains("@bazel_cc_toolchain//:all")
register_toolchains("//toolchain/install:all")
# Required for llvm-project.
bazel_dep(name = "libxml2", version = "2.15.2")
bazel_dep(name = "platforms", version = "1.0.0")
bazel_dep(name = "protobuf", version = "34.0.bcr.1", repo_name = "com_google_protobuf")
bazel_dep(name = "zlib-ng", version = "2.3.3", repo_name = "llvm_zlib")
bazel_dep(name = "zstd", version = "1.5.7.bcr.1", repo_name = "llvm_zstd")
bazel_dep(name = "libxml2", version = "2.15.3")
bazel_dep(name = "platforms", version = "1.1.0")
bazel_dep(name = "protobuf", version = "36.2", repo_name = "com_google_protobuf")
bazel_dep(name = "zlib-ng", version = "2.3.3.bcr.2", repo_name = "llvm_zlib")
bazel_dep(name = "zstd", version = "1.5.7.bcr.3", repo_name = "llvm_zstd")
bazel_dep(name = "xz", version = "5.4.5.bcr.9", repo_name = "llvm_xz")
###############################################################################
@@ -85,17 +90,14 @@ git_override(
build_file_content = "# empty",
# We pin to specific upstream commits and try to track top-of-tree
# reasonably closely rather than pinning to a specific release.
# HEAD as of 2026-09-25.
commit = "6ec87e7ec4b3ca6f665a02e05d0e5fe9c37cd6cc",
# HEAD as of 2026-11-02.
commit = "c744694c6ae1d12bbf7d0447b4bc91ee94a1904c",
patch_cmds = ["echo \"module(name='llvm-raw')\" > MODULE.bazel"],
patch_strip = 1,
patches = [
"//bazel/llvm_project:0001_Patch_for_mallinfo2_when_using_Bazel_build_system.patch",
"//bazel/llvm_project:0002_Added_Bazel_build_for_compiler_rt_fuzzer.patch",
"//bazel/llvm_project:0004_Introduce_basic_sources_exporting_for_libunwind.patch",
"//bazel/llvm_project:0005_Introduce_basic_sources_exporting_for_libcxx_and_libcxxabi.patch",
"//bazel/llvm_project:0006_Add_more_libc_math_excludes.patch",
"//bazel/llvm_project:0012_Drop_dependencies_on_linux_uapi_and_pyyaml.patch",
"//bazel/llvm_project:0013_Exclude_additional_libc_math_builtins.patch",
],
remote = "https://github.com/llvm/llvm-project.git",
)
@@ -111,7 +113,7 @@ use_repo(llvm_project, "llvm-project")
# Python
###############################################################################
bazel_dep(name = "rules_python", version = "1.9.0")
bazel_dep(name = "rules_python", version = "2.4.5")
python = use_extension("@rules_python//python/extensions:python.bzl", "python")
python.toolchain(
+1315 -96
View File
File diff suppressed because it is too large. Load diff
@@ -1,39 +0,0 @@
From 20d0f1fb854d9b7ce135d08575112fc58c4c7ce7 Mon Sep 17 00:00:00 2001
From: jonmeow <jperkins@google.com>
Date: Thu, 14 Sep 2023 20:31:11 +0000
Subject: [PATCH] Add libfuzzer target to compiler-rt.
---
.../compiler-rt/BUILD.bazel | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/utils/bazel/llvm-project-overlay/compiler-rt/BUILD.bazel b/utils/bazel/llvm-project-overlay/compiler-rt/BUILD.bazel
index 90264449de76..115da4cb77f6 100644
--- a/utils/bazel/llvm-project-overlay/compiler-rt/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/compiler-rt/BUILD.bazel
@@ -57,6 +57,23 @@ cc_library(
],
)
+cc_library(
+ name = "FuzzerMain",
+ srcs = glob(
+ ["lib/fuzzer/Fuzzer*.cpp"],
+ ),
+ hdrs = glob([
+ "lib/fuzzer/Fuzzer*.h",
+ "lib/fuzzer/Fuzzer*.def",
+ ]),
+ copts = [
+ # Not using no-sanitize=address per https://github.com/google/sanitizers/wiki/AddressSanitizerContainerOverflow#false-positives
+ "-fno-sanitize=memory,thread,undefined",
+ "-fsanitize-coverage=0",
+ ],
+ includes = ["lib/fuzzer"],
+)
+
cc_library(
name = "orc_rt_common_headers",
hdrs = [
--
2.42.0
@@ -1,64 +0,0 @@
Commit ID: 354e38c89f28e2cc284e655a9cde707f457dc02c
Change ID: sxspxmonsuvqzuvxvrvorlumwpwromsv
Author : Chandler Carruth <chandlerc@gmail.com> (2025-09-25 22:55:26)
Committer: Chandler Carruth <chandlerc@gmail.com> (2026-02-14 03:46:06)
Introduce basic sources exporting for libunwind
This exports the source files directly so that they can be used to build
this runtime library on demand.
diff --git a/utils/bazel/llvm-project-overlay/libunwind/BUILD.bazel b/utils/bazel/llvm-project-overlay/libunwind/BUILD.bazel
index c9fdc819c0..7d734c5a06 100644
--- a/utils/bazel/llvm-project-overlay/libunwind/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/libunwind/BUILD.bazel
@@ -21,3 +21,19 @@
],
strip_include_prefix = "include",
)
+
+filegroup(
+ name = "libunwind_hdrs",
+ srcs = glob(["include/**/*.h"]),
+)
+
+filegroup(
+ name = "libunwind_srcs",
+ srcs = glob([
+ "src/*.cpp",
+ "src/*.hpp",
+ "src/*.c",
+ "src/*.h",
+ "src/*.S",
+ ]),
+)
diff --git a/utils/bazel/llvm-project-overlay/libunwind/libunwind_library.bzl b/utils/bazel/llvm-project-overlay/libunwind/libunwind_library.bzl
new file mode 100644
index 0000000000..25675d3070
--- /dev/null
+++ b/utils/bazel/llvm-project-overlay/libunwind/libunwind_library.bzl
@@ -0,0 +1,24 @@
+# This file is licensed under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+"""Starlark variables and macros for building libunwind.
+
+Variables provide base line information for how to build libunwind source files.
+These can be used to generate non-Bazel builds of the library.
+
+Macros provide a convenient way to construct Bazel `cc_library` rules for
+libunwind.
+"""
+
+# TODO: Should libunwind use `-fvisibility-inlines-hidden` and
+# `-fvisibility=hidden`, similar to libc++?
+libunwind_copts = [
+ "-D_LIBUNWIND_IS_NATIVE_ONLY",
+ "-O3",
+ "-fPIC",
+ "-fno-exceptions",
+ "-fno-rtti",
+ "-funwind-tables",
+ "-nostdinc++",
+]
@@ -1,236 +0,0 @@
Commit ID: 1fd710ed69a0f47f454c386d39302ddb756a88b3
Change ID: mstnwoqruyypnoouksnyqssllrsozpos
Bookmarks: bz-libcxx* bz-libcxx@git
Author : Chandler Carruth <chandlerc@gmail.com> (2025-09-25 22:55:26)
Committer: Chandler Carruth <chandlerc@gmail.com> (2026-03-08 07:41:54)
Introduce basic sources exporting for libcxx and libcxxabi
This exports the source files directly so that they can be used to build
a libcxx runtime library on demand. It also differentiates between
normal sources and textual sources.
diff --git a/utils/bazel/llvm-project-overlay/libcxx/BUILD.bazel b/utils/bazel/llvm-project-overlay/libcxx/BUILD.bazel
new file mode 100644
index 0000000000..c8b517ab56
--- /dev/null
+++ b/utils/bazel/llvm-project-overlay/libcxx/BUILD.bazel
@@ -0,0 +1,139 @@
+# This file is licensed under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+licenses(["notice"])
+
+package(
+ default_visibility = ["//visibility:public"],
+)
+
+exports_files([
+ "include/__config_site.in",
+ "include/module.modulemap.in",
+ "vendor/llvm/default_assertion_handler.in",
+])
+
+filegroup(
+ name = "libcxx_hdrs",
+ srcs = glob(
+ [
+ # Top level includes and those in `experimental` and `ext` sometimes
+ # have no extension.
+ "include/*",
+ "include/experimental/*",
+ "include/ext/*",
+
+ # Implementation detail headers all use `.h` extensions
+ "include/**/*.h",
+ ],
+ exclude = [
+ # Omit CMake and CMake-configured files that get caught by the
+ # extension-less patterns.
+ "**/*.in",
+ "**/CMakeLists.txt",
+
+ # Omit C++03 compatibility headers as current users don't need them.
+ "include/__cxx03/**",
+ ],
+ ),
+)
+
+LIBCXX_SRCS_PSTL_LIBDISPATCH = [
+ "src/pstl/libdispatch.cpp",
+]
+
+filegroup(
+ name = "libcxx_srcs_pstl_libdispatch",
+ srcs = LIBCXX_SRCS_PSTL_LIBDISPATCH,
+)
+
+LIBCXX_SRCS_SUPPORT_IBM_PATTERNS = [
+ "src/support/ibm/**/*.cpp",
+]
+
+filegroup(
+ name = "libcxx_srcs_support_ibm",
+ srcs = glob(LIBCXX_SRCS_SUPPORT_IBM_PATTERNS),
+)
+
+LIBCXX_SRCS_SUPPORT_WIN32_PATTERNS = [
+ "src/support/win32/**/*.cpp",
+]
+
+filegroup(
+ name = "libcxx_srcs_support_win32",
+ srcs = glob(LIBCXX_SRCS_SUPPORT_WIN32_PATTERNS),
+)
+
+LIBCXX_SRCS_TZDB = [
+ "src/experimental/chrono_exception.cpp",
+ "src/experimental/time_zone.cpp",
+ "src/experimental/tzdb.cpp",
+ "src/experimental/tzdb_list.cpp",
+]
+
+filegroup(
+ name = "libcxx_srcs_tzdb",
+ srcs = LIBCXX_SRCS_TZDB,
+)
+
+# Exclude platform-dependent patterns that are provided by per-target filegroups
+# above.
+LIBCXX_SRCS_TARGET_EXCLUDES = (
+ LIBCXX_SRCS_PSTL_LIBDISPATCH +
+ LIBCXX_SRCS_SUPPORT_IBM_PATTERNS +
+ LIBCXX_SRCS_SUPPORT_WIN32_PATTERNS +
+ LIBCXX_SRCS_TZDB
+)
+
+filegroup(
+ name = "libcxx_srcs_generic",
+ srcs = glob(
+ [
+ "src/**/*.cpp",
+ "src/**/*.h",
+ "src/**/*.ipp",
+ ],
+ exclude = [
+ # Build is for use with libc++abi and so don't need 'new.cpp'.
+ "src/new.cpp",
+
+ # Build is for compiler-rt platforms so we have its int128 support.
+ "src/filesystem/int128_builtins.cpp",
+ ] + LIBCXX_SRCS_TARGET_EXCLUDES,
+ ),
+)
+
+filegroup(
+ name = "libcxx_linux_srcs",
+ srcs = [
+ ":libcxx_srcs_generic",
+ ":libcxx_srcs_tzdb",
+ ],
+)
+
+filegroup(
+ name = "libcxx_macos_srcs",
+ srcs = [
+ ":libcxx_srcs_generic",
+ # TODO: Include libdispatch sources here to enable that pstl backend.
+ ],
+)
+
+filegroup(
+ name = "libcxx_win32_srcs",
+ srcs = [
+ ":libcxx_srcs_generic",
+ ":libcxx_srcs_support_win32",
+ ],
+)
+
+filegroup(
+ name = "libcxx_all_srcs",
+ srcs = [
+ ":libcxx_linux_srcs",
+ ":libcxx_macos_srcs",
+ ":libcxx_win32_srcs",
+ ],
+)
diff --git a/utils/bazel/llvm-project-overlay/libcxx/libcxx_library.bzl b/utils/bazel/llvm-project-overlay/libcxx/libcxx_library.bzl
new file mode 100644
index 0000000000..66f64f3610
--- /dev/null
+++ b/utils/bazel/llvm-project-overlay/libcxx/libcxx_library.bzl
@@ -0,0 +1,37 @@
+# This file is licensed under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+"""Starlark variables and macros for building libc++ and libc++abi.
+
+Variables provide base line information for how to build libc++ and libc++abi
+source files. These can be used to generate non-Bazel builds of the library.
+
+TODO: Add macros that provide a convenient way to construct Bazel `cc_library`
+rules for libc++ and libc++abi.
+
+TODO: Add either sufficient usage in the macros, or add a how-to example here in
+the documentation so the use of these variables is more clear.
+"""
+
+_libcxx_base_copts = [
+ "-std=c++26",
+ "-O3",
+ "-fPIC",
+ "-fvisibility-inlines-hidden",
+ "-fvisibility=hidden",
+ "-nostdinc++",
+]
+
+_libcxx_defines = [
+ "-D_LIBCPP_BUILDING_LIBRARY",
+ "-D_LIBCPP_REMOVE_TRANSITIVE_INCLUDES",
+]
+
+_libcxxabi_defines = [
+ "-DLIBCXX_BUILDING_LIBCXXABI",
+]
+
+libcxx_copts = _libcxx_base_copts + _libcxx_defines
+libcxxabi_copts = _libcxx_base_copts + _libcxxabi_defines
+libcxx_and_abi_copts = _libcxx_base_copts + _libcxx_defines + _libcxxabi_defines
diff --git a/utils/bazel/llvm-project-overlay/libcxxabi/BUILD.bazel b/utils/bazel/llvm-project-overlay/libcxxabi/BUILD.bazel
new file mode 100644
index 0000000000..2db70b54e2
--- /dev/null
+++ b/utils/bazel/llvm-project-overlay/libcxxabi/BUILD.bazel
@@ -0,0 +1,30 @@
+# This file is licensed under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+licenses(["notice"])
+
+package(
+ default_visibility = ["//visibility:public"],
+)
+
+filegroup(
+ name = "libcxxabi_hdrs",
+ srcs = glob(["include/*.h"]),
+)
+
+filegroup(
+ name = "libcxxabi_srcs",
+ srcs = glob([
+ "src/**/*.cpp",
+ "src/**/*.h",
+ ]),
+)
+
+filegroup(
+ name = "libcxxabi_textual_srcs",
+ srcs = glob([
+ "src/**/*.def",
+ "src/**/*.inc",
+ ]),
+)
@@ -1,37 +0,0 @@
Removes additional libc-backed arithmetic builtins added
by https://github.com/llvm/llvm-project/pull/207092
and https://github.com/llvm/llvm-project/pull/209984
---
--- a/utils/bazel/llvm-project-overlay/compiler-rt/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/compiler-rt/BUILD.bazel
@@ -303,6 +303,30 @@
"lib/builtins/extendsfdf2.cpp",
"lib/builtins/extendsftf2.cpp",
"lib/builtins/extendxftf2.cpp",
+ "lib/builtins/fixdfdi.cpp",
+ "lib/builtins/fixdfsi.cpp",
+ "lib/builtins/fixdfti.cpp",
+ "lib/builtins/fixsfdi.cpp",
+ "lib/builtins/fixsfsi.cpp",
+ "lib/builtins/fixsfti.cpp",
+ "lib/builtins/fixunsdfdi.cpp",
+ "lib/builtins/fixunsdfsi.cpp",
+ "lib/builtins/fixunsdfti.cpp",
+ "lib/builtins/fixunssfdi.cpp",
+ "lib/builtins/fixunssfsi.cpp",
+ "lib/builtins/fixunssfti.cpp",
+ "lib/builtins/floatdidf.cpp",
+ "lib/builtins/floatdisf.cpp",
+ "lib/builtins/floatsidf.cpp",
+ "lib/builtins/floatsisf.cpp",
+ "lib/builtins/floattidf.cpp",
+ "lib/builtins/floattisf.cpp",
+ "lib/builtins/floatundidf.cpp",
+ "lib/builtins/floatundisf.cpp",
+ "lib/builtins/floatunsidf.cpp",
+ "lib/builtins/floatunsisf.cpp",
+ "lib/builtins/floatuntidf.cpp",
+ "lib/builtins/floatuntisf.cpp",
"lib/builtins/muldf3.cpp",
"lib/builtins/mulsf3.cpp",
"lib/builtins/multf3.cpp",
@@ -0,0 +1,33 @@
diff --git a/utils/bazel/llvm-project-overlay/compiler-rt/BUILD.bazel b/utils/bazel/llvm-project-overlay/compiler-rt/BUILD.bazel
index 2f3eeb68dd..16d3c92349 100644
--- a/utils/bazel/llvm-project-overlay/compiler-rt/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/compiler-rt/BUILD.bazel
@@ -317,12 +317,15 @@ BUILTINS_LIBC_MATH_EXCLUDES = [
BUILTINS_LIBC_MATH_EXCLUDES = [
# Sources requiring COMPILER_RT_USE_LIBC_MATH (e.g. LIBC_NAMESPACE::shared::*).
"lib/builtins/adddf3.cpp",
+ "lib/builtins/addsf3.cpp",
"lib/builtins/addtf3.cpp",
- "lib/builtins/addsf3.cpp",
"lib/builtins/divdf3.cpp",
"lib/builtins/divsf3.cpp",
"lib/builtins/divtf3.cpp",
"lib/builtins/extenddftf2.cpp",
+ "lib/builtins/extendhfdf2.cpp",
+ "lib/builtins/extendhfsf2.cpp",
+ "lib/builtins/extendhftf2.cpp",
"lib/builtins/extendsfdf2.cpp",
"lib/builtins/extendsftf2.cpp",
"lib/builtins/extendxftf2.cpp",
@@ -358,8 +361,11 @@ BUILTINS_LIBC_MATH_EXCLUDES = [
"lib/builtins/subdf3.cpp",
"lib/builtins/subsf3.cpp",
"lib/builtins/subtf3.cpp",
+ "lib/builtins/truncdfhf2.cpp",
"lib/builtins/truncdfsf2.cpp",
+ "lib/builtins/truncsfhf2.cpp",
"lib/builtins/trunctfdf2.cpp",
+ "lib/builtins/trunctfhf2.cpp",
"lib/builtins/trunctfsf2.cpp",
"lib/builtins/trunctfxf2.cpp",
]
+31
View File
@@ -113,6 +113,12 @@ cc_test(
],
)
cc_library(
name = "checked_save_and_restore",
hdrs = ["checked_save_and_restore.h"],
deps = [":check"],
)
cc_library(
name = "concepts",
hdrs = ["concepts.h"],
@@ -404,6 +410,12 @@ cc_test(
],
)
cc_library(
name = "increment_scope",
hdrs = ["increment_scope.h"],
deps = [":check"],
)
cc_library(
name = "init_llvm",
srcs = ["init_llvm.cpp"],
@@ -534,6 +546,25 @@ cc_test(
],
)
cc_library(
name = "preserve_none",
hdrs = ["preserve_none.h"],
deps = [
"@llvm-project//llvm:Support",
],
)
cc_test(
name = "preserve_none_test",
size = "small",
srcs = ["preserve_none_test.cpp"],
deps = [
":preserve_none",
"//testing/base:gtest_main",
"@googletest//:gtest",
],
)
cc_library(
name = "pretty_stack_trace_function",
hdrs = ["pretty_stack_trace_function.h"],
+51
View File
@@ -0,0 +1,51 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#ifndef CARBON_COMMON_CHECKED_SAVE_AND_RESTORE_H_
#define CARBON_COMMON_CHECKED_SAVE_AND_RESTORE_H_
#include <utility>
#include "common/check.h"
namespace Carbon {
// Sets a variable to a new value, and restores the original value when
// the CheckedSaveAndRestore is destroyed. CHECKs that the variable is still
// holding the new value when restoring the original value, to catch any data
// loss.
//
// The value must be copyable in order to compare with the new value at the time
// the CheckedSaveAndRestore is destroyed.
template <typename T>
class [[nodiscard]] CheckedSaveAndRestore {
public:
CheckedSaveAndRestore(T& var, const T& new_value)
: var_(&var),
new_value_(new_value),
orig_value_(std::exchange(var, new_value_)) {}
~CheckedSaveAndRestore() {
CARBON_CHECK(*var_ == new_value_);
*var_ = std::move(orig_value_);
}
CheckedSaveAndRestore(const CheckedSaveAndRestore&) = delete;
auto operator=(const CheckedSaveAndRestore&)
-> CheckedSaveAndRestore& = delete;
private:
T* var_;
T new_value_;
T orig_value_;
};
template <typename T>
CheckedSaveAndRestore(T&, const T&) -> CheckedSaveAndRestore<T>;
template <typename T>
CheckedSaveAndRestore(T&, T&&) -> CheckedSaveAndRestore<T>;
} // namespace Carbon
#endif // CARBON_COMMON_CHECKED_SAVE_AND_RESTORE_H_
+149 -126
View File
@@ -5,6 +5,7 @@
#ifndef CARBON_COMMON_HASHING_H_
#define CARBON_COMMON_HASHING_H_
#include <bit>
#include <concepts>
#include <string>
#include <tuple>
@@ -75,21 +76,16 @@ class HashCode : public Printable<HashCode> {
uint64_t value_ = 0;
};
// Computes a hash code for the provided value, incorporating the provided seed.
//
// The seed doesn't need to be of any particular high quality, but a zero seed
// has bad effects in several places. Prefer the unseeded routine rather than
// providing a zero here.
// Computes a hash code for the provided value.
//
// This **not** a cryptographically secure or stable hash -- it is only designed
// for use with in-memory hash table style data structures. Being fast and
// effective for that use case is the guiding principle of its design.
//
// There is no guarantee that the values produced are stable from execution to
// execution. For speed and quality reasons, the implementation does not
// introduce any variance to defend against accidental dependencies. As a
// consequence, it is strongly encouraged to use a seed that varies from
// execution to execution to avoid depending on specific values produced.
// There is no guarantee that the values produced are stable between versions
// of this code. Within one build, the hash of a value is the same in every
// execution: the function is unseeded, so hashtable layouts and collisions are
// reproducible between runs. Code must not depend on the specific values.
//
// The algorithm used is most heavily based on [Abseil's hashing algorithm][1],
// with some additional ideas and inspiration from the fallback hashing
@@ -100,13 +96,13 @@ class HashCode : public Printable<HashCode> {
// [2]: https://github.com/tkaitchuck/aHash/wiki/AHash-fallback-algorithm
// [3]: https://docs.rs/fxhash/latest/fxhash/
//
// This hash algorithm does *not* defend against hash flooding. While it can be
// viewed as "keyed" on the seed, it is expected to be possible to craft inputs
// for some data types that cancel out the seed used and manufacture endlessly
// colliding sets of keys. In general, this function works to be *fast* for hash
// tables. If you need to defend against hash flooding, either directly use a
// data structure with strong worst-case guarantees, or a hash table which
// detects catastrophic collisions and falls back to such a data structure.
// This hash algorithm does *not* defend against hash flooding. It is unkeyed,
// and it is expected to be possible to craft inputs for some data types that
// manufacture endlessly colliding sets of keys. In general, this function works
// to be *fast* for hash tables. If you need to defend against hash flooding,
// either directly use a data structure with strong worst-case guarantees, or a
// hash table which detects catastrophic collisions and falls back to such a
// data structure.
//
// This hash function is heavily optimized for *latency* over *quality*. Modern
// hash tables designs can efficiently handle reasonable collision rates,
@@ -147,7 +143,7 @@ class HashCode : public Printable<HashCode> {
// `CarbonHashValue` with the following signature:
//
// ```cpp
// auto CarbonHashValue(const YourType& value, uint64_t seed) -> HashCode;
// auto CarbonHashValue(const YourType& value) -> HashCode;
// ```
//
// The extension point needs to ensure that values that compare equal (including
@@ -173,16 +169,6 @@ class HashCode : public Printable<HashCode> {
//
// [4]: https://en.wikipedia.org/wiki/Avalanche_effect
template <typename T>
inline auto HashValue(const T& value, uint64_t seed) -> HashCode;
// The same as the seeded version of `HashValue` but without callers needing to
// provide a seed.
//
// Generally prefer the seeded version, but this is available if there is no
// reasonable seed. In particular, this will behave better than using a seed of
// `0`. One important use case is for recursive hashing of sub-objects where
// appropriate or needed.
template <typename T>
inline auto HashValue(const T& value) -> HashCode;
// Object and APIs that eventually produce a hash code.
@@ -199,8 +185,8 @@ inline auto HashValue(const T& value) -> HashCode;
//
// Example usage:
// ```cpp
// auto CarbonHashValue(const MyType& value, uint64_t seed) -> HashCode {
// Hasher hasher(seed);
// auto CarbonHashValue(const MyType& value) -> HashCode {
// Hasher hasher;
// hasher.HashTwo(value.x, value.y);
// return static_cast<HashCode>(hasher);
// }
@@ -221,7 +207,6 @@ inline auto HashValue(const T& value) -> HashCode;
class Hasher {
public:
Hasher() = default;
explicit Hasher(uint64_t seed) : buffer(seed) {}
Hasher(Hasher&& arg) = default;
Hasher(const Hasher& arg) = delete;
@@ -311,6 +296,13 @@ class Hasher {
static auto Read4(const std::byte* data) -> uint64_t;
static auto Read8(const std::byte* data) -> uint64_t;
// Returns `value` XOR-ed with `imm` sign-extended to 64 bits, keeping `imm`
// an immediate operand of the XOR on x86-64. When `value` comes directly from
// a read, instruction selection otherwise folds the read into the XOR and
// materializes `imm` in a register, which a loop then hoists and keeps live.
// Hiding `value` from the optimizer prevents that.
static auto XorImmediate(uint64_t value, int32_t imm) -> uint64_t;
// Similar to the `ReadN` functions, but supports reading a range of different
// bytes provided by the size *without branching on the size*. The lack of
// branches is often key, and the code in these routines works to be efficient
@@ -355,7 +347,9 @@ class Hasher {
// multiplication. However, it is not *necessary* as we do capture the
// complete 128-bit result. Where reasonable, the caller should XOR random
// data into operands before calling `Mix` to try and increase the
// distribution of bits feeding the multiply.
// distribution of bits feeding the multiply. The operands of each `Mix` use
// `ImmediateRandomData` for this, and use `StaticRandomData` only to encode
// a size.
static auto Mix(uint64_t lhs, uint64_t rhs) -> uint64_t;
// An alternative to `Mix` that is significantly weaker but also lower
@@ -392,10 +386,10 @@ class Hasher {
// Random data taken from the hexadecimal digits of Pi's fractional component,
// written in lexical order for convenience of reading. The resulting
// byte-stream will be different due to little-endian integers. These can be
// used directly for convenience rather than calling `SampleRandomData`, but
// be aware that this is the underlying pool. The goal is to reuse the same
// single cache-line of constant data.
// byte-stream will be different due to little-endian integers. This pool is
// sampled with `SampleRandomData` to encode sizes into the hash, and is used
// directly by the large-input routine. The goal is to reuse the same single
// cache-line of constant data.
//
// The initializers here can be generated with the following shell script,
// which will generate 8 64-bit values and one more digit. The `bc` command's
@@ -417,6 +411,33 @@ class Hasher {
0xc0ac'29b7'c97c'50dd, 0x3f84'd5b5'b547'0917,
};
// Random data XOR-ed into the operands of `Mix` so that common inputs, such
// as all-zero bytes, don't zero an operand, and to spread bits across the
// multiply. These are 32-bit values, sign-extended on use: 32 bits of
// random data is enough for this, and x86-64 can XOR a sign-extended 32-bit
// value in as an immediate operand, where a full 64-bit constant costs an
// extra instruction to materialize. They are also independent of
// `StaticRandomData`, so they can't correlate with a sample of that pool on
// the other operand of the same `Mix`.
//
// The values are successive 32-bit chunks of the hexadecimal digits of e's
// fractional component. The initializers can be generated with the
// following shell script:
//
// ```sh
// echo 'obase=16; scale=40; e(1)' | bc -l \
// | cut -c 3-34 | tr '[:upper:]' '[:lower:]' \
// | sed -e "s/.\{4\}/&'/g" \
// | sed -e "s/\(.\{4\}'.\{4\}\)'/0x\1,\n/g"
// ```
static constexpr std::array<int32_t, 4> ImmediateRandomData =
std::bit_cast<std::array<int32_t, 4>>(std::array<uint32_t, 4>{
0xb7e1'5162,
0x8aed'2a6a,
0xbf71'5880,
0x9cf4'f3c7,
});
// We need a multiplicative hashing constant for both 64-bit multiplicative
// hashing fast paths and some other 128-bit folded multiplies. We use an
// empirically better constant compared to Knuth's, Rust's FxHash, and others
@@ -448,7 +469,7 @@ class Hasher {
static constexpr uint64_t MulConstant = 0x79d5'f9e0'de1e'8cf5U;
private:
uint64_t buffer;
uint64_t buffer = 0;
};
// A dedicated namespace for `CarbonHashValue` overloads that are not found by
@@ -460,42 +481,41 @@ class Hasher {
namespace InternalHashDispatch {
template <typename T>
inline auto CarbonHashValue(llvm::ArrayRef<T> values, uint64_t seed)
-> HashCode {
Hasher hasher(seed);
inline auto CarbonHashValue(llvm::ArrayRef<T> values) -> HashCode {
Hasher hasher;
hasher.HashArray(values);
return static_cast<HashCode>(hasher);
}
inline auto CarbonHashValue(llvm::ArrayRef<std::byte> bytes, uint64_t seed)
-> HashCode {
Hasher hasher(seed);
[[clang::always_inline]] inline auto CarbonHashValue(
llvm::ArrayRef<std::byte> bytes) -> HashCode {
Hasher hasher;
hasher.HashSizedBytes(bytes);
return static_cast<HashCode>(hasher);
}
// Hashing implementation for `llvm::StringRef`. We forward all the other
// string-like types that support heterogeneous lookup to this one.
inline auto CarbonHashValue(llvm::StringRef value, uint64_t seed) -> HashCode {
return CarbonHashValue(
llvm::ArrayRef(reinterpret_cast<const std::byte*>(value.data()),
value.size()),
seed);
}
inline auto CarbonHashValue(std::string_view value, uint64_t seed) -> HashCode {
return CarbonHashValue(llvm::StringRef(value.data(), value.size()), seed);
}
inline auto CarbonHashValue(const std::string& value, uint64_t seed)
[[clang::always_inline]] inline auto CarbonHashValue(llvm::StringRef value)
-> HashCode {
return CarbonHashValue(llvm::StringRef(value.data(), value.size()), seed);
return CarbonHashValue(llvm::ArrayRef(
reinterpret_cast<const std::byte*>(value.data()), value.size()));
}
[[clang::always_inline]] inline auto CarbonHashValue(std::string_view value)
-> HashCode {
return CarbonHashValue(llvm::StringRef(value.data(), value.size()));
}
[[clang::always_inline]] inline auto CarbonHashValue(const std::string& value)
-> HashCode {
return CarbonHashValue(llvm::StringRef(value.data(), value.size()));
}
template <unsigned Length>
inline auto CarbonHashValue(const llvm::SmallString<Length>& value,
uint64_t seed) -> HashCode {
return CarbonHashValue(llvm::StringRef(value.data(), value.size()), seed);
inline auto CarbonHashValue(const llvm::SmallString<Length>& value)
-> HashCode {
return CarbonHashValue(llvm::StringRef(value.data(), value.size()));
}
// Support types that are array-like by building an `llvm::ArrayRef` out of
@@ -503,37 +523,32 @@ inline auto CarbonHashValue(const llvm::SmallString<Length>& value,
// because that type supports building a synthetic array out of any single
// element.
template <typename T>
inline auto CarbonHashValue(const std::vector<T>& arg, uint64_t seed)
-> HashCode {
return CarbonHashValue(llvm::ArrayRef(arg), seed);
inline auto CarbonHashValue(const std::vector<T>& arg) -> HashCode {
return CarbonHashValue(llvm::ArrayRef(arg));
}
template <typename T>
inline auto CarbonHashValue(const llvm::SmallVectorImpl<T>& arg, uint64_t seed)
-> HashCode {
return CarbonHashValue(llvm::ArrayRef(arg), seed);
inline auto CarbonHashValue(const llvm::SmallVectorImpl<T>& arg) -> HashCode {
return CarbonHashValue(llvm::ArrayRef(arg));
}
template <typename T, size_t N>
inline auto CarbonHashValue(const std::array<T, N>& arg, uint64_t seed)
-> HashCode {
return CarbonHashValue(llvm::ArrayRef(arg), seed);
inline auto CarbonHashValue(const std::array<T, N>& arg) -> HashCode {
return CarbonHashValue(llvm::ArrayRef(arg));
}
template <typename T, size_t N>
inline auto CarbonHashValue(const T (&arg)[N], uint64_t seed) -> HashCode {
return CarbonHashValue(llvm::ArrayRef(arg), seed);
inline auto CarbonHashValue(const T (&arg)[N]) -> HashCode {
return CarbonHashValue(llvm::ArrayRef(arg));
}
template <typename... Ts>
inline auto CarbonHashValue(const std::tuple<Ts...>& value, uint64_t seed)
-> HashCode {
Hasher hasher(seed);
inline auto CarbonHashValue(const std::tuple<Ts...>& value) -> HashCode {
Hasher hasher;
std::apply([&](const auto&... args) { hasher.Hash(args...); }, value);
return static_cast<HashCode>(hasher);
}
template <typename T, typename U>
inline auto CarbonHashValue(const std::pair<T, U>& value, uint64_t seed)
-> HashCode {
Hasher hasher(seed);
inline auto CarbonHashValue(const std::pair<T, U>& value) -> HashCode {
Hasher hasher;
hasher.Hash(value.first, value.second);
return static_cast<HashCode>(hasher);
}
@@ -550,8 +565,8 @@ concept HasCustomHashValue = requires { CustomHashValue<T>::Hash; };
// overload available for a particular type, either in this namespace or found
// via ADL. Note that this should not be moved above any overloads.
template <typename T>
concept HasCarbonHashValue = requires(const T& value, uint64_t seed) {
{ CarbonHashValue(value, seed) } -> std::same_as<HashCode>;
concept HasCarbonHashValue = requires(const T& value) {
{ CarbonHashValue(value) } -> std::same_as<HashCode>;
};
// C++ guarantees this is true for the unsigned variants, but we require it for
@@ -596,21 +611,21 @@ concept CanHashAsRawDataType = std::same_as<T, std::nullptr_t> ||
// overloads, either here, or via ADL. Note that similar to
// `HasCarbonHashValue`, this must not be moved above any of those overloads.
template <typename T>
inline auto DispatchImpl(const T& value, uint64_t seed) -> HashCode {
[[clang::always_inline]] inline auto DispatchImpl(const T& value) -> HashCode {
if constexpr (HasCarbonHashValue<T>) {
// If we have an explicit overload for `CarbonHashValue`, call it. This may
// be provided above or via ADL, and is preferred as it represents an
// explicit request for how the type is hashed.
return CarbonHashValue(value, seed);
return CarbonHashValue(value);
} else if constexpr (HasCustomHashValue<T>) {
// If we have an explicit specialization for `CustomHashValue`, call it.
// This is a fallback explicit hashing path that doesn't require ADL or
// being in this header.
return CustomHashValue<T>::Hash(value, seed);
return CustomHashValue<T>::Hash(value);
} else if constexpr (CanHashAsRawDataType<T>) {
// There was no explicit overload or specialization to call, but the type
// allows us to hash it as raw data, do so.
Hasher hasher(seed);
Hasher hasher;
hasher.HashRaw(MapToRawDataType(value));
return static_cast<HashCode>(hasher);
} else {
@@ -627,17 +642,8 @@ inline auto DispatchImpl(const T& value, uint64_t seed) -> HashCode {
} // namespace InternalHashDispatch
template <typename T>
inline auto HashValue(const T& value, uint64_t seed) -> HashCode {
return InternalHashDispatch::DispatchImpl(value, seed);
}
template <typename T>
inline auto HashValue(const T& value) -> HashCode {
// When a seed isn't provided, use the last 64-bit chunk of random data. Other
// chunks (especially the first) are more often XOR-ed with the seed and risk
// cancelling each other out and feeding a zero to a `Mix` call in a way that
// sharply increasing collisions.
return HashValue(value, Hasher::StaticRandomData[7]);
[[clang::always_inline]] inline auto HashValue(const T& value) -> HashCode {
return InternalHashDispatch::DispatchImpl(value);
}
constexpr auto HashCode::ExtractIndex() -> ssize_t { return value_; }
@@ -689,6 +695,13 @@ inline auto Hasher::Read8(const std::byte* data) -> uint64_t {
return result;
}
inline auto Hasher::XorImmediate(uint64_t value, int32_t imm) -> uint64_t {
#if defined(__x86_64__)
__asm__("" : "+r"(value));
#endif
return value ^ static_cast<uint64_t>(static_cast<int64_t>(imm));
}
inline auto Hasher::Read1To3(const std::byte* data, ssize_t size) -> uint64_t {
// Use carefully crafted indexing to avoid branches on the exact size while
// reading.
@@ -745,23 +758,16 @@ inline auto Hasher::HashDense(uint64_t data) -> void {
inline auto Hasher::HashDense(uint64_t data0, uint64_t data1) -> void {
// When hashing two chunks of data at the same time, we XOR it with random
// data to avoid common inputs from having especially bad multiplicative
// effects. We also XOR in the starting buffer as seed or to chain. Note that
// we don't use *consecutive* random data 64-bit values to avoid a common
// compiler "optimization" of loading both 64-bit chunks into a 128-bit vector
// and doing the XOR in the vector unit. The latency of extracting the data
// afterward eclipses any benefit. Callers will routinely have two consecutive
// data values here, but using non-consecutive keys avoids any vectorization
// being tempting.
// effects. We also XOR in the starting buffer to chain.
//
// XOR-ing both the incoming state and a random word over the second data is
// done to pipeline with materializing the constants and is observed to have
// better performance than XOR-ing after the mix.
// XOR-ing both the incoming state and random data over the second data is
// observed to have better performance than XOR-ing after the mix.
//
// This roughly matches the mix pattern used in the larger mixing routines
// from Abseil, which is a more minimal form than used in other algorithms
// such as AHash and seems adequate for latency-optimized use cases.
buffer =
Mix(data0 ^ StaticRandomData[1], data1 ^ StaticRandomData[3] ^ buffer);
buffer = Mix(XorImmediate(data0, ImmediateRandomData[3]),
XorImmediate(data1, ImmediateRandomData[2]) ^ buffer);
}
template <typename T>
@@ -818,7 +824,7 @@ inline auto Hasher::Hash(const Ts&... values) -> void {
auto map_value = []<typename T>(const T& value) -> uint64_t {
if constexpr (HasCarbonHashValue<T> || HasCustomHashValue<T>) {
// Use the top-level `HashValue` to re-dispatch to the custom
// implementation with a fixed seed.
// implementation.
return static_cast<uint64_t>(HashValue(value));
} else if constexpr (CanHashAsRawDataType<T>) {
auto raw_value = MapToRawDataType(value);
@@ -828,8 +834,7 @@ inline auto Hasher::Hash(const Ts&... values) -> void {
if constexpr (sizeof(raw_value) <= 8) {
return ReadSmall(raw_value);
} else {
// Use the top-level `HashValue` to pick up a good fixed seed and hash
// this large object as raw data.
// Use the top-level `HashValue` to hash this large object as raw data.
return static_cast<uint64_t>(HashValue(raw_value));
}
} else {
@@ -912,13 +917,14 @@ inline auto Hasher::HashRaw(const T& value) -> void {
if constexpr (16 < sizeof(T) && sizeof(T) <= 32) {
CARBON_MCA_BEGIN("fixed-32b");
// Essentially the same technique used for dynamically sized byte sequences
// of this size, but we start with a fixed XOR of random data.
buffer ^= StaticRandomData[0];
uint64_t m0 = Mix(Read8(data_ptr) ^ StaticRandomData[1],
Read8(data_ptr + 8) ^ buffer);
// of this size, but with a fixed XOR of random data in place of the size.
uint64_t m0 =
Mix(XorImmediate(Read8(data_ptr), ImmediateRandomData[3]),
XorImmediate(Read8(data_ptr + 8), ImmediateRandomData[1]) ^ buffer);
const std::byte* tail_16b_ptr = data_ptr + (sizeof(T) - 16);
uint64_t m1 = Mix(Read8(tail_16b_ptr) ^ StaticRandomData[3],
Read8(tail_16b_ptr + 8) ^ buffer);
uint64_t m1 = Mix(
XorImmediate(Read8(tail_16b_ptr), ImmediateRandomData[2]),
XorImmediate(Read8(tail_16b_ptr + 8), ImmediateRandomData[1]) ^ buffer);
buffer = m0 ^ m1;
CARBON_MCA_END("fixed-32b");
return;
@@ -929,7 +935,8 @@ inline auto Hasher::HashRaw(const T& value) -> void {
HashSizedBytesLarge(llvm::ArrayRef<std::byte>(data_ptr, sizeof(T)));
}
inline auto Hasher::HashSizedBytes(llvm::ArrayRef<std::byte> bytes) -> void {
[[clang::always_inline]] inline auto Hasher::HashSizedBytes(
llvm::ArrayRef<std::byte> bytes) -> void {
const std::byte* data_ptr = bytes.data();
const ssize_t size = bytes.size();
@@ -948,7 +955,11 @@ inline auto Hasher::HashSizedBytes(llvm::ArrayRef<std::byte> bytes) -> void {
// Note that we don't drop to the `WeakMix` routine here because we want
// to use sampled random data to encode the size, which may not be as
// effective without the full 128-bit folded result.
buffer = Mix(data ^ buffer, SampleAlignedRandomData(size - 1));
//
// The data is XOR-ed with random data so that all-zero data doesn't zero
// the multiply and lose the size.
buffer = Mix(XorImmediate(data, ImmediateRandomData[0]) ^ buffer,
SampleAlignedRandomData(size - 1));
CARBON_MCA_END("dynamic-8b");
return;
}
@@ -972,12 +983,13 @@ inline auto Hasher::HashSizedBytes(llvm::ArrayRef<std::byte> bytes) -> void {
if (size <= 16) {
CARBON_MCA_BEGIN("dynamic-16b");
// Similar to the above, we optimize primarily for latency here and spread
// the incoming data across both ends of the multiply. Note that this does
// have a drawback -- any time one half of the mix function becomes zero it
// will fail to incorporate any bits from the other half. However, there is
// exactly 1 in 2^64 values for each side that achieve this, and only when
// the size is exactly 16 -- for smaller sizes there is an overlapping byte
// that makes this impossible unless the seed is *also* incredibly unlucky.
// the incoming data across both ends of the multiply. Any time one half of
// the mix function becomes zero it fails to incorporate any bits from the
// other half, so both halves are XOR-ed with random data: a sample of the
// pool encoding the size, and a 32-bit constant. Otherwise the common case
// of trailing zero bytes, such as `{x, 0, 0}` as an array of `int32_t`,
// would zero the second half and hash every `x` the same. With the random
// data, exactly 1 in 2^64 values for each half is a problem.
//
// Because this hash function makes no attempt to defend against hash
// flooding, we accept this risk in order to keep the latency low. If this
@@ -985,7 +997,8 @@ inline auto Hasher::HashSizedBytes(llvm::ArrayRef<std::byte> bytes) -> void {
// the 16-byte case down the next tier of cost.
uint64_t size_hash = SampleRandomData(size);
auto data = Read8To16(data_ptr, size);
buffer = Mix(data.first ^ size_hash, data.second ^ buffer);
buffer = Mix(data.first ^ size_hash,
XorImmediate(data.second, ImmediateRandomData[1]) ^ buffer);
CARBON_MCA_END("dynamic-16b");
return;
}
@@ -994,13 +1007,23 @@ inline auto Hasher::HashSizedBytes(llvm::ArrayRef<std::byte> bytes) -> void {
CARBON_MCA_BEGIN("dynamic-32b");
// Do two mixes of overlapping 16-byte ranges in parallel to minimize
// latency. We also incorporate the size by sampling random data into the
// seed before both.
// buffer before both.
//
// The first operands use 32-bit constants rather than chunks of the pool.
// The sample reads bytes 17 through 39 of the pool, and a chunk that
// overlaps it is the same random data shifted by some bytes. Multiplying
// one by the other produces structured collisions.
//
// Both also go through `XorImmediate`, which keeps the two products at the
// same depth. Otherwise reassociation XORs the halves of `m0` together
// first, and on x86-64 the high half of a multiply arrives a cycle after
// the low half, lengthening the critical path by a cycle.
buffer ^= SampleRandomData(size);
uint64_t m0 = Mix(Read8(data_ptr) ^ StaticRandomData[1],
uint64_t m0 = Mix(XorImmediate(Read8(data_ptr), ImmediateRandomData[3]),
Read8(data_ptr + 8) ^ buffer);
const std::byte* tail_16b_ptr = data_ptr + (size - 16);
uint64_t m1 = Mix(Read8(tail_16b_ptr) ^ StaticRandomData[3],
uint64_t m1 = Mix(XorImmediate(Read8(tail_16b_ptr), ImmediateRandomData[2]),
Read8(tail_16b_ptr + 8) ^ buffer);
// Just an XOR mix at the end is quite weak here, but we prefer that for
// latency over a more robust approach. Doing another mix with the size (the
+5 -5
View File
@@ -158,9 +158,9 @@ struct HashBenchBase {
uint64_t seed;
HashBenchBase() {
// The real-world use case we care about is in a hash table where we'll mix
// in some seed state, likely some ASLR address. To simulate this for
// benchmarking, compute a seed from the address of a stack local variable.
// The Abseil and LLVM hash tables mix in some seed state, likely some ASLR
// address. To simulate this for benchmarking, compute a seed from the
// address of a stack local variable.
volatile char key;
key = 42;
// Rinse this through a volatile variable as well so returning it isn't
@@ -171,10 +171,10 @@ struct HashBenchBase {
}
};
struct CarbonHashBench : HashBenchBase {
struct CarbonHashBench {
template <typename T>
auto operator()(const T& value) -> uint64_t {
return static_cast<uint64_t>(HashValue(value, seed));
return static_cast<uint64_t>(HashValue(value));
}
};
+6 -6
View File
@@ -14,8 +14,8 @@ namespace Carbon::InternalHashDispatch {
template <>
struct CustomHashValue<llvm::APInt> {
static auto Hash(llvm::APInt value, uint64_t seed) -> HashCode {
Hasher hasher(seed);
static auto Hash(llvm::APInt value) -> HashCode {
Hasher hasher;
if (LLVM_LIKELY(value.isSingleWord())) {
hasher.Hash(value.getBitWidth(), value.getZExtValue());
} else {
@@ -29,12 +29,12 @@ struct CustomHashValue<llvm::APInt> {
template <>
struct CustomHashValue<llvm::APFloat> {
static auto Hash(llvm::APFloat value, uint64_t seed) -> HashCode {
Hasher hasher(seed);
static auto Hash(llvm::APFloat value) -> HashCode {
Hasher hasher;
// Hashing floating point numbers is complex and depends on the specific
// internal semantics of `APFloat`, so delegate to the LLVM hashing
// framework here. We re-hash the result to mix in our seed. All of this is
// a bit inefficient, and we can revisit this to provide a dedicated
// framework here. We re-hash the result to mix it into our hash. All of
// this is a bit inefficient, and we can revisit this to provide a dedicated
// implementation if it becomes a bottleneck.
using llvm::hash_value;
hasher.HashRaw(hash_value(value));
+5 -19
View File
@@ -98,15 +98,6 @@ TEST(HashingTest, Integers) {
}
}
TEST(HashingTest, BasicSeeding) {
auto unseeded_hash = HashValue(42);
EXPECT_THAT(unseeded_hash, Ne(HashValue(42, 1)));
EXPECT_THAT(unseeded_hash, Ne(HashValue(42, 2)));
EXPECT_THAT(unseeded_hash, Ne(HashValue(42, 3)));
EXPECT_THAT(unseeded_hash,
Ne(HashValue(42, static_cast<uint64_t>(unseeded_hash))));
}
TEST(HashingTest, Pointers) {
int object1 = 42;
std::string object2 =
@@ -366,8 +357,8 @@ struct HashableType {
// help ensure that the hashing framework doesn't accidentally override this.
template <typename T>
requires(std::same_as<T, HashableType>)
friend auto CarbonHashValue(const T& value, uint64_t seed) -> HashCode {
Hasher hasher(seed);
friend auto CarbonHashValue(const T& value) -> HashCode {
Hasher hasher;
hasher.Hash(value.x, value.y);
return static_cast<HashCode>(hasher);
}
@@ -468,11 +459,6 @@ TEST(HashingTest, TupleRecursion) {
Eq(HashValue(std::tuple{a, c, a})));
}
// The only significantly bad seed is zero, so pick a non-zero seed with a tiny
// amount of entropy to make sure that none of the testing relies on the entropy
// from this.
constexpr uint64_t TestSeed = 42ULL * 1024;
auto ToHexBytes(llvm::StringRef s) -> std::string {
RawStringOstream rendered;
rendered << "{";
@@ -637,7 +623,7 @@ auto AllByteStringsHashedAndSorted() {
bytes[j] = (static_cast<uint64_t>(i) >> (8 * j)) & 0xff;
}
std::string s(std::begin(bytes), std::end(bytes));
hashes.push_back({HashValue(s, TestSeed), s});
hashes.push_back({HashValue(s), s});
}
llvm::sort(hashes, [](const HashedString& lhs, const HashedString& rhs) {
@@ -748,7 +734,7 @@ struct SparseHashTest : ::testing::Test {
SetBitCount::Begin, std::min(bits, SetBitCount::End))) {
if (set_bit_count == 0) {
std::string s(byte_count, '\0');
hashes.push_back({HashValue(s, TestSeed), std::move(s)});
hashes.push_back({HashValue(s), std::move(s)});
continue;
}
for (int begin_set_bit : llvm::seq_inclusive(0, bits - set_bit_count)) {
@@ -793,7 +779,7 @@ struct SparseHashTest : ::testing::Test {
if (has_end_byte_bits) {
s[end_set_bit_byte_index] &= end_set_bit_byte;
}
hashes.push_back({HashValue(s, TestSeed), std::move(s)});
hashes.push_back({HashValue(s), std::move(s)});
}
}
}
+10 -11
View File
@@ -79,10 +79,10 @@ auto HashtableEq(const LeftT& lhs, const RightT& rhs) -> bool;
// llvm::ArrayRef<llvm::StringRef> interned_strings)
// : interned_strings_(interned_strings) {}
//
// auto HashKey(llvm::StringRef s, uint64_t seed) const -> HashCode {
// auto HashKey(llvm::StringRef s) const -> HashCode {
// return HashValue(s);
// }
// auto HashKey(int index_key, uint64_t seed) const -> HashCode {
// auto HashKey(int index_key) const -> HashCode {
// return HashKey(interned_strings_[index_key]);
// }
//
@@ -99,7 +99,7 @@ auto HashtableEq(const LeftT& lhs, const RightT& rhs) -> bool;
// ```
struct DefaultKeyContext {
template <typename AnyKeyT>
auto HashKey(const AnyKeyT& key, uint64_t seed) const -> HashCode;
auto HashKey(const AnyKeyT& key) const -> HashCode;
template <typename AnyKeyT, typename TableKeyT>
auto KeyEq(const AnyKeyT& lhs_key, const TableKeyT& rhs_key) const -> bool;
@@ -138,7 +138,7 @@ class TranslatingKeyContext {
}
template <typename AnyKeyT>
auto HashKey(const AnyKeyT& key, uint64_t seed) const -> HashCode;
auto HashKey(const AnyKeyT& key) const -> HashCode;
template <typename AnyKeyT, typename TableKeyT>
auto KeyEq(const AnyKeyT& lhs_key, const TableKeyT& rhs_key) const -> bool;
@@ -216,9 +216,8 @@ inline auto HashtableEq(const LeftT& lhs, const RightT& rhs) -> bool {
}
template <typename AnyKeyT>
auto DefaultKeyContext::HashKey(const AnyKeyT& key, uint64_t seed) const
-> HashCode {
return HashValue(key, seed);
auto DefaultKeyContext::HashKey(const AnyKeyT& key) const -> HashCode {
return HashValue(key);
}
template <typename AnyKeyT, typename TableKeyT>
@@ -229,13 +228,13 @@ auto DefaultKeyContext::KeyEq(const AnyKeyT& lhs_key,
template <typename DerivedT>
template <typename AnyKeyT>
auto TranslatingKeyContext<DerivedT>::HashKey(const AnyKeyT& key,
uint64_t seed) const -> HashCode {
auto TranslatingKeyContext<DerivedT>::HashKey(const AnyKeyT& key) const
-> HashCode {
const DerivedT& self = *static_cast<const DerivedT*>(this);
if constexpr (requires { self.TranslateKey(key); }) {
return HashValue(self.TranslateKey(key), seed);
return HashValue(self.TranslateKey(key));
} else {
return HashValue(key, seed);
return HashValue(key);
}
}
+17 -24
View File
@@ -121,9 +121,8 @@ TEST(HashtableKeyContextTest, HashtableEqAPFloat) {
struct CustomHash {
int x;
friend auto CarbonHashValue(const CustomHash& value, uint64_t seed)
-> HashCode {
return HashValue(value.x + 42, seed);
friend auto CarbonHashValue(const CustomHash& value) -> HashCode {
return HashValue(value.x + 42);
}
};
@@ -155,20 +154,15 @@ TEST(HashtableKeyContextTest, DefaultKeyContext) {
EXPECT_FALSE(HashtableEq(zero_float, neg_zero_float));
// Also check hash dispatching.
uint64_t seed = 1234;
EXPECT_THAT(context.HashKey(42, seed), Eq(HashValue(42, seed)));
EXPECT_THAT(context.HashKey(CustomHash{.x = 1234}, seed),
Eq(HashValue(CustomHash{.x = 1234}, seed)));
EXPECT_THAT(context.HashKey(one_64, seed), Eq(HashValue(one_64, seed)));
EXPECT_THAT(context.HashKey(one_128, seed), Eq(HashValue(one_128, seed)));
EXPECT_THAT(context.HashKey(one_64, seed),
Ne(context.HashKey(one_128, seed)));
EXPECT_THAT(context.HashKey(zero_float, seed),
Eq(HashValue(zero_float, seed)));
EXPECT_THAT(context.HashKey(neg_zero_float, seed),
Eq(HashValue(neg_zero_float, seed)));
EXPECT_THAT(context.HashKey(zero_float, seed),
Ne(context.HashKey(neg_zero_float, seed)));
EXPECT_THAT(context.HashKey(42), Eq(HashValue(42)));
EXPECT_THAT(context.HashKey(CustomHash{.x = 1234}),
Eq(HashValue(CustomHash{.x = 1234})));
EXPECT_THAT(context.HashKey(one_64), Eq(HashValue(one_64)));
EXPECT_THAT(context.HashKey(one_128), Eq(HashValue(one_128)));
EXPECT_THAT(context.HashKey(one_64), Ne(context.HashKey(one_128)));
EXPECT_THAT(context.HashKey(zero_float), Eq(HashValue(zero_float)));
EXPECT_THAT(context.HashKey(neg_zero_float), Eq(HashValue(neg_zero_float)));
EXPECT_THAT(context.HashKey(zero_float), Ne(context.HashKey(neg_zero_float)));
}
struct TestTranslatingKeyContext
@@ -191,13 +185,12 @@ TEST(HashtableKeyContextTest, TranslatingKeyContext) {
TestTranslatingKeyContext context = {.array = values};
uint64_t seed = 1234;
EXPECT_THAT(context.HashKey(0, seed), Eq(HashValue(one_64, seed)));
EXPECT_THAT(context.HashKey(1, seed), Eq(HashValue(two_64, seed)));
EXPECT_THAT(context.HashKey(2, seed), Eq(HashValue(one_128, seed)));
EXPECT_THAT(context.HashKey(3, seed), Eq(HashValue(two_128, seed)));
EXPECT_THAT(context.HashKey(4, seed), Eq(HashValue(one_64, seed)));
EXPECT_THAT(context.HashKey(5, seed), Eq(HashValue(one_64, seed)));
EXPECT_THAT(context.HashKey(0), Eq(HashValue(one_64)));
EXPECT_THAT(context.HashKey(1), Eq(HashValue(two_64)));
EXPECT_THAT(context.HashKey(2), Eq(HashValue(one_128)));
EXPECT_THAT(context.HashKey(3), Eq(HashValue(two_128)));
EXPECT_THAT(context.HashKey(4), Eq(HashValue(one_64)));
EXPECT_THAT(context.HashKey(5), Eq(HashValue(one_64)));
EXPECT_TRUE(context.KeyEq(one_64, 0));
EXPECT_TRUE(context.KeyEq(one_64, 4));
+36
View File
@@ -0,0 +1,36 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#ifndef CARBON_COMMON_INCREMENT_SCOPE_H_
#define CARBON_COMMON_INCREMENT_SCOPE_H_
#include <cstdint>
#include "common/check.h"
namespace Carbon {
// An RAII object that increments a value while it remains in scope,
// decrementing it when it is destroyed.
class [[nodiscard]] IncrementScope {
public:
explicit IncrementScope(int32_t& value [[clang::lifetimebound]])
: value_(&value), before_(value) {
(*value_)++;
}
~IncrementScope() {
(*value_)--;
CARBON_CHECK(*value_ == before_,
"IncrementScope value not restored - incorrect bracketing of "
"increments and decrements?");
}
private:
int32_t* value_;
int32_t before_;
};
} // namespace Carbon
#endif // CARBON_COMMON_INCREMENT_SCOPE_H_
+25
View File
@@ -0,0 +1,25 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#ifndef CARBON_COMMON_PRESERVE_NONE_H_
#define CARBON_COMMON_PRESERVE_NONE_H_
#include "llvm/Support/Compiler.h"
// Applies `[[clang::preserve_none]]` when supported and safe.
//
// Disabled when building with AddressSanitizer on AArch64 to work around an
// LLVM backend bug: when sanitizer stack instrumentation combines dynamic stack
// allocation with stack realignment, AArch64 reserves `x19` as the stack base
// pointer (`hasBasePointer`) and fails to save and restore `x19` across calls
// to `[[clang::preserve_none]]` callees that clobber `x19`.
#if __has_cpp_attribute(clang::preserve_none) && \
!((defined(__aarch64__) || defined(_M_ARM64)) && \
(LLVM_ADDRESS_SANITIZER_BUILD || LLVM_HWADDRESS_SANITIZER_BUILD))
#define CARBON_PRESERVE_NONE [[clang::preserve_none]]
#else
#define CARBON_PRESERVE_NONE
#endif
#endif // CARBON_COMMON_PRESERVE_NONE_H_
+48
View File
@@ -0,0 +1,48 @@
// 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 "common/preserve_none.h"
#include <gtest/gtest.h>
#include <array>
namespace Carbon {
namespace {
using DispatchFn = auto(int value, int remaining) CARBON_PRESERVE_NONE->int;
CARBON_PRESERVE_NONE static auto DispatchDone(int value, int /*remaining*/)
-> int {
return value;
}
CARBON_PRESERVE_NONE static auto DispatchStep(int value, int remaining) -> int;
constexpr std::array<DispatchFn*, 2> DispatchTable = {
&DispatchDone,
&DispatchStep,
};
CARBON_PRESERVE_NONE static auto DispatchStep(int value, int remaining) -> int {
// Touch enough temporaries that register allocation exercises caller-saved
// registers under `preserve_none`.
int next = (value * 3 + 1) ^ (remaining * 7);
int next_remaining = remaining - 1;
[[clang::musttail]] return DispatchTable[next_remaining != 0 ? 1 : 0](
next, next_remaining);
}
TEST(PreserveNoneTest, TailCallDispatchWithAlignedCallerStack) {
// Overalign a local buffer so sanitizer stack instrumentation exercises stack
// realignment alongside `CARBON_PRESERVE_NONE` calls.
alignas(64) std::array<int, 8> scratch = {1, 2, 3, 4, 5, 6, 7, 8};
DispatchFn* entry = &DispatchStep;
int result = entry(scratch[0], 16);
EXPECT_EQ(scratch[7], 8);
EXPECT_NE(result, 0);
}
} // namespace
} // namespace Carbon
+4 -1
View File
@@ -8,9 +8,12 @@
namespace Carbon::RawHashtable {
#ifndef NDEBUG
// A global variable whose address seeds the iteration entropy pool. This allows
// ASLR to introduce some variation in debug iteration order when enabled via
// the code model for globals.
volatile std::byte global_addr_seed{1};
#ifndef NDEBUG
std::atomic<HashCode> entropy_hash =
Carbon::HashValue(reinterpret_cast<uint64_t>(&global_addr_seed));
#endif
+9 -21
View File
@@ -78,6 +78,10 @@
// - The storage for an entry is an internal type that should not be exposed to
// users, and instead only the underlying keys and values.
//
// - Keys are hashed with the unseeded `HashValue`; there is no per-table seed.
// This makes a table's layout, and any collisions in it, reproducible
// between runs. Debug builds vary the iteration order in `entries()` instead.
//
// - The hash addressing and probing occurs over *groups* of slots rather than
// individual entries. When inserting a new entry, it can be added to the
// group it hashes to as long it is not full, and can even replace a slot with
@@ -868,19 +872,6 @@ class TableImpl : public InputBaseT {
//
////////////////////////////////////////////////////////////////////////////////
// Computes a seed that provides a small amount of entropy from ASLR where
// available with minimal cost. The priority is speed, and this computes the
// entropy in a way that doesn't require loading from memory, merely accessing
// entropy already available without accessing memory.
inline auto ComputeSeed() -> uint64_t {
// A global variable whose address is used as a seed. This allows ASLR to
// introduce some variation in hashtable ordering when enabled via the code
// model for globals.
extern volatile std::byte global_addr_seed;
return reinterpret_cast<uint64_t>(&global_addr_seed);
}
#ifndef NDEBUG
// A pool of entropy used to vary the iteration order of hashtables in debug
// builds. It is seeded from ASLR where available.
@@ -980,7 +971,7 @@ auto ViewImpl<InputKeyT, InputValueT, InputKeyContextT>::LookupEntry(
CARBON_DCHECK(local_size > 0);
uint8_t* local_metadata = metadata();
HashCode hash = key_context.HashKey(lookup_key, ComputeSeed());
HashCode hash = key_context.HashKey(lookup_key);
auto [hash_index, tag] = hash.ExtractIndexAndTag<7>();
EntryT* local_entries = entries_data();
@@ -1057,8 +1048,7 @@ auto ViewImpl<InputKeyT, InputValueT, InputKeyContextT>::ComputeMetricsImpl(
for (ssize_t byte_index : present_matched_range) {
++metrics.key_count;
ssize_t index = group_index + byte_index;
HashCode hash =
key_context.HashKey(local_entries[index].key(), ComputeSeed());
HashCode hash = key_context.HashKey(local_entries[index].key());
auto [hash_index, tag] = hash.ExtractIndexAndTag<7>();
ProbeSequence s(hash_index, local_size);
metrics.probed_key_count +=
@@ -1257,7 +1247,7 @@ auto BaseImpl<InputKeyT, InputValueT, InputKeyContextT>::InsertImpl(
uint8_t* local_metadata = metadata();
HashCode hash = key_context.HashKey(lookup_key, ComputeSeed());
HashCode hash = key_context.HashKey(lookup_key);
auto [hash_index, tag] = hash.ExtractIndexAndTag<7>();
// We re-purpose the empty control byte to signal no insert is needed to the
@@ -1383,8 +1373,7 @@ BaseImpl<InputKeyT, InputValueT, InputKeyContextT>::GrowToAllocSizeImpl(
for (ssize_t byte_index : present_matched_range) {
++count;
ssize_t index = group_index + byte_index;
HashCode hash =
key_context.HashKey(old_entries[index].key(), ComputeSeed());
HashCode hash = key_context.HashKey(old_entries[index].key());
EntryT* new_entry = InsertIntoEmpty(hash);
new_entry->MoveFrom(std::move(old_entries[index]));
}
@@ -1799,8 +1788,7 @@ auto BaseImpl<InputKeyT, InputValueT, InputKeyContextT>::GrowToNextAllocSize(
CARBON_DCHECK(new_metadata[old_index | old_size] ==
old_metadata[old_index]);
}
HashCode hash =
key_context.HashKey(old_entries[old_index].key(), ComputeSeed());
HashCode hash = key_context.HashKey(old_entries[old_index].key());
ssize_t old_hash_index = hash.ExtractIndexAndTag<7>().first &
ComputeProbeMaskFromSize(old_size);
if (LLVM_UNLIKELY(old_hash_index != group_index)) {
+3 -5
View File
@@ -372,9 +372,8 @@ auto DumpHashStatistics(llvm::ArrayRef<T> keys) -> void {
constexpr int GroupShift = llvm::ConstantLog2<GroupSize>();
size_t mask = ComputeProbeMaskFromSize(expected_size);
uint64_t salt = ComputeSeed();
auto get_hash_index = [mask, salt](auto x) -> ssize_t {
auto [hash_index, _] = HashValue(x, salt).template ExtractIndexAndTag<7>();
auto get_hash_index = [mask](auto x) -> ssize_t {
auto [hash_index, _] = HashValue(x).template ExtractIndexAndTag<7>();
return (hash_index & mask) >> GroupShift;
};
@@ -408,9 +407,8 @@ auto DumpHashStatistics(llvm::ArrayRef<T> keys) -> void {
for (auto i : llvm::ArrayRef(grouped_key_indices[max_group_index])
.take_front(2 * GroupSize)) {
auto k = keys[i];
auto hash = static_cast<uint64_t>(HashValue(k, salt));
auto hash = static_cast<uint64_t>(HashValue(k));
llvm::errs() << " key: " << k
<< " salt: " << llvm::formatv("{0:x16}", salt)
<< " hash: " << llvm::formatv("{0:x16}", hash) << "\n";
}
}
+2 -3
View File
@@ -79,9 +79,8 @@ struct LowZeroBitInt {
constexpr auto operator<=>(const LowZeroBitInt& rhs) const
-> std::strong_ordering = default;
friend auto CarbonHashValue(const LowZeroBitInt& value, uint64_t seed)
-> HashCode {
return HashValue(value.shifted_value, seed);
friend auto CarbonHashValue(const LowZeroBitInt& value) -> HashCode {
return HashValue(value.shifted_value);
}
template <typename H>
+8 -9
View File
@@ -38,8 +38,8 @@ struct TestData : Printable<TestData> {
return lhs.value <=> rhs.value;
}
friend auto CarbonHashValue(TestData data, uint64_t seed) -> HashCode {
return Carbon::HashValue(data.value, seed);
friend auto CarbonHashValue(TestData data) -> HashCode {
return Carbon::HashValue(data.value);
}
};
@@ -74,9 +74,8 @@ struct MoveOnlyTestData : Printable<TestData> {
const MoveOnlyTestData& rhs)
-> std::strong_ordering = default;
friend auto CarbonHashValue(const MoveOnlyTestData& data, uint64_t seed)
-> HashCode {
return Carbon::HashValue(data.value, seed);
friend auto CarbonHashValue(const MoveOnlyTestData& data) -> HashCode {
return Carbon::HashValue(data.value);
}
};
@@ -92,8 +91,8 @@ inline auto CarbonHashtableEq(int lhs, const MoveOnlyTestData& rhs) -> bool {
// fails to be used.
struct TestKeyContext : DefaultKeyContext {
template <typename KeyT>
auto HashKey(const KeyT& key, uint64_t seed) const -> HashCode {
Hasher hash(seed);
auto HashKey(const KeyT& key) const -> HashCode {
Hasher hash;
// Inject some other data to the hash.
hash.HashRaw(42);
hash.HashRaw(HashValue(key));
@@ -108,8 +107,8 @@ struct TestKeyContext : DefaultKeyContext {
template <int TagBits, bool FixIndexBits, bool FixTagBits, uint64_t FixedVal>
struct FixedHashKeyContext : DefaultKeyContext {
template <typename KeyT>
auto HashKey(const KeyT& key, uint64_t seed) const -> HashCode {
HashCode original_hash = HashValue(key, seed);
auto HashKey(const KeyT& key) const -> HashCode {
HashCode original_hash = HashValue(key);
auto raw_hash = static_cast<uint64_t>(original_hash);
constexpr uint64_t TagMask = (1U << TagBits) - 1;
+7 -2
View File
@@ -366,7 +366,9 @@ auto SetBase<InputKeyT, InputKeyContextT>::Insert(LookupKeyT lookup_key,
return Insert(
lookup_key,
[&key_cb](LookupKeyT /*lookup_key*/, void* key_storage) {
new (key_storage) KeyT(key_cb());
// Despite being on the cold path, inlining is essential to keep
// fast-path register allocation efficient.
[[clang::always_inline]] new (key_storage) KeyT(key_cb());
},
key_context);
}
@@ -386,7 +388,10 @@ auto SetBase<InputKeyT, InputKeyContextT>::Insert(LookupKeyT lookup_key,
return InsertResult(false, entry->key());
}
insert_cb(lookup_key, static_cast<void*>(&entry->key_storage));
// Despite being on the cold path, inlining is essential to keep fast-path
// register allocation efficient.
[[clang::always_inline]] insert_cb(lookup_key,
static_cast<void*>(&entry->key_storage));
return InsertResult(true, entry->key());
}
+193 -77
View File
@@ -7,14 +7,13 @@
#include <algorithm>
#include <array>
#include <cstdint>
#include <string_view>
#include <utility>
#include "common/check.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/Sequence.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/ConvertUTF.h"
#include "llvm/Support/Unicode.h"
namespace Carbon::Terminal {
@@ -23,59 +22,77 @@ namespace Carbon::Terminal {
// let a single column of output carry unbounded bytes.
static constexpr size_t MaxCombiningBytes = 32;
// Glyphs for every combination of line directions, indexed by the direction
// bits.
static constexpr std::array<char32_t, 16> Utf8LineGlyphs = {
U'·', // (none): a line between one center and itself, which is a point
U'╴', // left
U'╶', // right
U'─', // left, right
U'╵', // up
U'╯', // left, up
U'╰', // right, up
U'┴', // left, right, up
U'╷', // down
U'╮', // left, down
U'╭', // right, down
U'┬', // left, right, down
U'│', // up, down
U'┤', // left, up, down
U'├', // right, up, down
U'┼', // left, right, up, down
};
// The ASCII stand-ins. Each keeps the axis its line runs through, which leaves
// `+` meaning a crossing and nothing else:
// Glyphs for every combination of line directions and weights, indexed by the
// heavy bits and then the direction bits.
//
// - Running through horizontally is `-`, vertically `|`, and both ways `+`.
// - A tee keeps its through-stroke and leaves the branch to what is drawn
// beside it: the dashes either side of a `|` are what `├` and `┤` reach, and
// the line under a `-` is what a `┬` reaches. Drawing a tee as `+` reads as
// the crossing it is not.
// The first row is the all-light set, whose corners are the rounded forms; a
// corner with any heavy line is square, since Unicode has no rounded heavy
// corner. The other rows match the Unicode character names, which list the
// weight of the line in each direction. `DrawLine` never sets a heavy bit
// without its direction bit, and those entries repeat the glyph with the
// direction present.
static constexpr std::array<std::u32string_view, 16> Utf8LineGlyphs = {
U"·╴╶─╵╯╰┴╷╮╭┬│┤├┼", // All light.
U"╸╸╾╾┙┙┵┵┑┑┭┭┥┥┽┽", // Heavy left.
U"╺╼╺╼┕┶┕┶┍┮┍┮┝┾┝┾", // Heavy right.
U"━━━━┷┷┷┷┯┯┯┯┿┿┿┿", // Heavy left and right.
U"╹┚┖┸╹┚┖┸╿┦┞╀╿┦┞╀", // Heavy up.
U"┛┛┹┹┛┛┹┹┩┩╃╃┩┩╃╃", // Heavy left and up.
U"┗┺┗┺┗┺┗┺┡╄┡╄┡╄┡╄", // Heavy right and up.
U"┻┻┻┻┻┻┻┻╇╇╇╇╇╇╇╇", // Heavy left, right, and up.
U"╻┒┎┰╽┧┟╁╻┒┎┰╽┧┟╁", // Heavy down.
U"┓┓┱┱┪┪╅╅┓┓┱┱┪┪╅╅", // Heavy left and down.
U"┏┲┏┲┢╆┢╆┏┲┏┲┢╆┢╆", // Heavy right and down.
U"┳┳┳┳╈╈╈╈┳┳┳┳╈╈╈╈", // Heavy left, right, and down.
U"┃┨┠╂┃┨┠╂┃┨┠╂┃┨┠╂", // Heavy up and down.
U"┫┫╉╉┫┫╉╉┫┫╉╉┫┫╉╉", // Heavy left, up, and down.
U"┣╊┣╊┣╊┣╊┣╊┣╊┣╊┣╊", // Heavy right, up, and down.
U"╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋", // All heavy.
};
// Every row has a glyph for each set of direction bits.
static_assert(std::all_of(Utf8LineGlyphs.begin(), Utf8LineGlyphs.end(),
[](std::u32string_view glyphs) {
return glyphs.size() == 16;
}));
// The ASCII glyphs, which use `+` only for a crossing:
//
// - A line running through horizontally is `-`, vertically `|`, and both ways
// `+`.
// - A corner is `.` where its line leaves downward and `'` where it arrives
// from above, which is where those characters sit in their cells.
// - A tee where a line leaves a vertical line sideways, `├` or `┤`, is `|`,
// and the dash drawn beside it shows the line leaving. A tee where a line
// leaves a horizontal line upward or downward, `┴` or `┬`, is the corner
// glyph for that direction: the dashes either side show the horizontal line,
// and this cell must show the line leaving it, because the rest of that line
// can be rows away with only gaps between. `+` would look like a crossing.
// - A point, a line between one center and itself, is `.`.
//
// What a diagnostic draws is then still told apart: the rule closing a snippet
// from the one separating two, and the anchor opening a diagnostic from the one
// carrying it on.
// What a diagnostic draws is then still told apart: the line closing a snippet
// from the one separating two, and the line opening a diagnostic from the one
// continuing it.
//
// A cell with a heavy line leaving it is `^` instead, under the rule
// `LineWeight` describes. That covers the top cell of a heavy vertical line, so
// the line is `^` above `|`.
static constexpr std::array<char32_t, 16> AsciiLineGlyphs = {
U'.', // (none): a point
U'-', // left
U'-', // right
U'-', // left, right
U'|', // up
U'\'', // left, up
U'\'', // right, up
U'-', // left, right, up
U'|', // down
U'.', // left, down
U'.', // right, down
U'-', // left, right, down
U'|', // up, down
U'|', // left, up, down
U'|', // right, up, down
U'+', // left, right, up, down
U'.', // None: a point.
U'-', // Left.
U'-', // Right.
U'-', // Left and right.
U'|', // Up.
U'\'', // Left and up.
U'\'', // Right and up.
U'\'', // Left, right, and up.
U'|', // Down.
U'.', // Left and down.
U'.', // Right and down.
U'.', // Left, right, and down.
U'|', // Up and down.
U'|', // Left, up, and down.
U'|', // Right, up, and down.
U'+', // Left, right, up, and down.
};
// Returns the next tab stop after `x` on a line whose stops are `tab_width`
@@ -241,13 +258,8 @@ auto Buffer::PlaceCodePoint(int x, int y, char32_t code_point,
return x + width;
}
// Returns the glyphs a cell's directions are read from.
static auto LineGlyphs(Charset charset) -> const std::array<char32_t, 16>& {
return charset == Charset::Utf8 ? Utf8LineGlyphs : AsciiLineGlyphs;
}
auto Buffer::DrawLine(int x, int y, uint8_t directions, const Style& style)
-> void {
auto Buffer::DrawLine(int x, int y, uint8_t directions, LineWeight weight,
const Style& style) -> void {
CARBON_DCHECK(directions <= LineDirections,
"Direction bits {0} name no glyph.", directions);
EnsureColumn(x);
@@ -262,7 +274,22 @@ auto Buffer::DrawLine(int x, int y, uint8_t directions, const Style& style)
Cell& cell = CellAt(x, y);
cell.lines = existing | directions | LineCell;
cell.code_point = LineGlyphs(metrics_.charset())[cell.lines & LineDirections];
cell.heavy_lines &= ~directions;
if (weight == LineWeight::Heavy) {
cell.heavy_lines |= directions;
}
uint8_t cell_directions = cell.lines & LineDirections;
bool line_enters_from_above = (cell_directions & LineUp) != 0;
bool light_line_leaves_downward =
(cell_directions & LineDown) != 0 && (cell.heavy_lines & LineDown) == 0;
if (metrics_.charset() == Charset::Utf8) {
cell.code_point = Utf8LineGlyphs[cell.heavy_lines][cell_directions];
} else if (cell.heavy_lines != 0 && !line_enters_from_above &&
!light_line_leaves_downward) {
cell.code_point = U'^';
} else {
cell.code_point = AsciiLineGlyphs[cell_directions];
}
cell.style = style;
}
@@ -279,7 +306,8 @@ static auto CheckLineFits(int position, int length, int limit) -> void {
}
auto Buffer::DrawHorizontalLine(int x, int y, int length, const Style& style,
LineEnd start, LineEnd end) -> DrawEnd {
LineEnd start, LineEnd end, LineWeight weight)
-> DrawEnd {
CheckOrigin(x, y);
CheckLineFits(x, length, columns_);
for (int i : llvm::seq(length)) {
@@ -289,20 +317,76 @@ auto Buffer::DrawHorizontalLine(int x, int y, int length, const Style& style,
uint8_t directions =
(i > 0 || start == LineEnd::Edge ? LineLeft : 0) |
(i + 1 < length || end == LineEnd::Edge ? LineRight : 0);
DrawLine(x + i, y, directions, style);
DrawLine(x + i, y, directions, weight, style);
}
return {.x = x + length, .y = y};
}
auto Buffer::DrawVerticalLine(int x, int y, int length, const Style& style,
LineEnd start, LineEnd end) -> DrawEnd {
LineEnd start, LineEnd end, LineWeight weight)
-> DrawEnd {
CheckOrigin(x, y);
CheckLineFits(y, length, MaxRows);
for (int i : llvm::seq(length)) {
uint8_t directions =
(i > 0 || start == LineEnd::Edge ? LineUp : 0) |
(i + 1 < length || end == LineEnd::Edge ? LineDown : 0);
DrawLine(x, y + i, directions, style);
DrawLine(x, y + i, directions, weight, style);
}
return {.x = x, .y = y + length};
}
auto Buffer::HoldsText(int x, int y) const -> bool {
if (x < 0 || x >= width_ || y >= height()) {
return false;
}
const Cell& cell = CellAt(x, y);
if ((cell.lines & LineCell) != 0) {
return false;
}
if (cell.code_point != U' ' || cell.is_continuation) {
return true;
}
return !combining_marks_.empty() &&
combining_marks_.contains(CellIndex(x, y));
}
auto Buffer::DrawCrossingVerticalLine(int x, int y, int length,
const Style& style, LineEnd start)
-> DrawEnd {
CheckOrigin(x, y);
CheckLineFits(y, length, MaxRows);
for (int i : llvm::seq(length)) {
int cell_y = y + i;
bool is_center_start = i == 0 && start == LineEnd::Center;
uint8_t directions = is_center_start ? LineDown : (LineUp | LineDown);
if (cell_y >= height()) {
// Rows below the buffer hold nothing drawn earlier.
DrawLine(x, cell_y, directions, LineWeight::Light, style);
continue;
}
const Cell& cell = CellAt(x, cell_y);
if (is_center_start) {
// A line already leaving this cell downward is left as it is, keeping
// its weight. Otherwise this line joins the cell.
if ((cell.lines & LineDown) == 0) {
DrawLine(x, cell_y, LineDown, LineWeight::Light, style);
}
continue;
}
if ((cell.lines & LineCell) != 0) {
// An earlier line keeps its cell: a crossed line stays unbroken, and a
// line already running down this column keeps its weight.
continue;
}
// Skip text, and blank cells beside text, where a line could be mistaken
// for part of a word.
if (HoldsText(x, cell_y) || HoldsText(x - 1, cell_y) ||
HoldsText(x + 1, cell_y)) {
continue;
}
DrawLine(x, cell_y, directions, LineWeight::Light, style);
}
return {.x = x, .y = y + length};
}
@@ -377,18 +461,49 @@ auto Buffer::MeasureText(int x, int y, int margin, llvm::StringRef text) const
// Returns whether wrapped text can be broken at `c`.
//
// This is the one definition of where wrapping may introduce a break, so that
// measuring what text wraps into and drawing it wrapped agree about it.
// Carriage returns count so that a CRLF ending is whitespace rather than part
// of the word before it; what becomes of the `\r` is then up to the drawing.
static constexpr auto IsWrapBreak(char c) -> bool {
return c == ' ' || c == '\t' || c == '\r';
}
// Removes the word at the front of `text` and returns it: everything up to the
// next break or newline, except that with `backticks_are_quotes`, a pair of
// backticks on one line keeps everything between them in the word. When
// `text` starts with a break or a newline, returns an empty word and leaves
// `text` unchanged, so the caller has to step past that character itself.
//
// With `IsWrapBreak` this is the one definition of where wrapping may
// introduce a break, so that measuring what text wraps into and drawing it
// wrapped agree about it.
static auto TakeWord(llvm::StringRef& text, bool backticks_are_quotes)
-> llvm::StringRef {
size_t end = 0;
while (end < text.size()) {
char c = text[end];
if (c == '\n' || IsWrapBreak(c)) {
break;
}
if (backticks_are_quotes && c == '`') {
size_t close = text.find('`', end + 1);
if (close != llvm::StringRef::npos &&
text.slice(end + 1, close).find_first_of("\t\n\r") ==
llvm::StringRef::npos) {
end = close + 1;
continue;
}
}
++end;
}
llvm::StringRef word = text.take_front(end);
text = text.drop_front(end);
return word;
}
template <typename PlaceFn>
auto Buffer::WalkWrappedText(int x, int y, int margin, int max_width,
llvm::StringRef text, PlaceFn place) const
-> DrawEnd {
llvm::StringRef text, bool backticks_are_quotes,
PlaceFn place) const -> DrawEnd {
CheckTextSize(text);
// The block runs from the margin to `margin + max_width`, lies within the
// buffer, and holds the column the text starts in, which is every bound on
@@ -452,9 +567,7 @@ auto Buffer::WalkWrappedText(int x, int y, int margin, int max_width,
continue;
}
llvm::StringRef word =
text.take_until([](char c) { return c == '\n' || IsWrapBreak(c); });
text = text.drop_front(word.size());
llvm::StringRef word = TakeWord(text, backticks_are_quotes);
// Move a word that doesn't fit down to the next row, which minimizes the
// overhang when it doesn't fit there either. The word is drawn into the row
@@ -474,9 +587,9 @@ auto Buffer::WalkWrappedText(int x, int y, int margin, int max_width,
}
auto Buffer::DrawWrappedText(int x, int y, int margin, int max_width,
llvm::StringRef text, const Style& style)
-> DrawEnd {
return WalkWrappedText(x, y, margin, max_width, text,
llvm::StringRef text, const Style& style,
bool backticks_are_quotes) -> DrawEnd {
return WalkWrappedText(x, y, margin, max_width, text, backticks_are_quotes,
[&](int cur_x, int cur_y, char32_t code_point) {
return PlaceCodePoint(cur_x, cur_y, code_point,
style);
@@ -484,20 +597,23 @@ auto Buffer::DrawWrappedText(int x, int y, int margin, int max_width,
}
auto Buffer::MeasureWrappedText(int x, int y, int margin, int max_width,
llvm::StringRef text) const -> DrawEnd {
return WalkWrappedText(x, y, margin, max_width, text,
llvm::StringRef text,
bool backticks_are_quotes) const -> DrawEnd {
return WalkWrappedText(x, y, margin, max_width, text, backticks_are_quotes,
[&](int cur_x, int /*cur_y*/, char32_t code_point) {
return cur_x + metrics_.CodePointWidth(code_point);
});
}
auto Buffer::MeasureWrapWidth(llvm::StringRef text) const -> int {
auto Buffer::MeasureWrapWidth(llvm::StringRef text,
bool backticks_are_quotes) const -> int {
int width = 0;
while (!text.empty()) {
llvm::StringRef word =
text.take_until([](char c) { return c == '\n' || IsWrapBreak(c); });
llvm::StringRef word = TakeWord(text, backticks_are_quotes);
width = std::max(width, metrics_.Width(word));
text = text.drop_front(std::max<size_t>(word.size(), 1));
if (word.empty()) {
text = text.drop_front();
}
}
return width;
}
+71 -10
View File
@@ -47,6 +47,20 @@ enum class LineEnd : int8_t {
Edge,
};
// The stroke weight a line is drawn with.
//
// Weight is recorded for each direction a line leaves a cell in, and Unicode
// has a glyph for every mix of light and heavy lines meeting in a cell, so
// where a heavy line meets a light one each keeps its weight. ASCII has no
// weights: a cell with a heavy line leaving it is `^`, the underline `rustc`
// uses for a primary range, unless a line enters it from above or a light line
// leaves it downward. Those cells keep their light glyph, so the junction or
// corner stays visible.
enum class LineWeight : int8_t {
Light,
Heavy,
};
// A grid of styled cells staged for rendering to a terminal.
//
// Coordinates are 0-based with (0, 0) at the top left, `x` counting terminal
@@ -247,14 +261,17 @@ class Buffer {
// The block and the origin are checked as drawing checks them, so measuring
// answers only for arguments drawing would accept.
auto MeasureWrappedText(int x, int y, int margin, int max_width,
llvm::StringRef text) const -> DrawEnd;
llvm::StringRef text,
bool backticks_are_quotes = false) const -> DrawEnd;
// Returns the fewest columns `text` wraps into without overhanging them,
// which is the width of its widest word since wrapping never breaks one.
// `backticks_are_quotes` is as for `DrawWrappedText`.
//
// Wrapping into fewer columns still draws everything; the excess overhangs.
// So this is a layout preference rather than a minimum.
auto MeasureWrapWidth(llvm::StringRef text) const -> int;
auto MeasureWrapWidth(llvm::StringRef text,
bool backticks_are_quotes = false) const -> int;
// Draws `code_point` at (x, y), which must be a non-negative column and a row
// inside `MaxRows`, widening the buffer and adding rows as needed to reach
@@ -286,16 +303,42 @@ class Buffer {
// containing `-` or `+` is never redrawn as line art.
//
// A cell's style is whatever was drawn there last, so crossing lines of
// different styles do depend on order.
// different styles depend on order. Weight is kept per direction instead:
// each direction a line leaves a cell in has the weight of the line drawn
// there last, so crossing lines keep their own weights in either order, and
// a heavy line meeting a light one forms a mixed junction.
auto DrawHorizontalLine(int x, int y, int length, const Style& style,
LineEnd start = LineEnd::Center,
LineEnd end = LineEnd::Center) -> DrawEnd;
LineEnd end = LineEnd::Center,
LineWeight weight = LineWeight::Light) -> DrawEnd;
// Draws a vertical line down `length` rows starting at (x, y), with the same
// meaning for its ends. Returns the row after it, in the column it ran down.
auto DrawVerticalLine(int x, int y, int length, const Style& style,
LineEnd start = LineEnd::Center,
LineEnd end = LineEnd::Center) -> DrawEnd;
LineEnd end = LineEnd::Center,
LineWeight weight = LineWeight::Light) -> DrawEnd;
// Draws a light vertical line down `length` rows starting at (x, y), behind
// everything drawn before it. The line runs out through the bottom of its
// last cell.
//
// The line is drawn only into blank cells with no text directly left or
// right of them. Everything drawn earlier stays as it was, so where this
// line crosses another, the other line is unbroken and this one has a gap,
// and the two don't join. A line beside text could be mistaken for part of
// the text, which is why those cells are skipped too.
//
// With `start` at `LineEnd::Center`, the first cell is the exception: the
// line leaves it downward and joins what it holds, as `DrawVerticalLine`
// would. If a line already leaves that cell downward, the cell is left
// unchanged, so that line keeps its weight. With `start` at `LineEnd::Edge`,
// the line enters its first cell from above like the rest, which continues
// a line broken off above it.
//
// Returns the row after the line, in the column it ran down.
auto DrawCrossingVerticalLine(int x, int y, int length, const Style& style,
LineEnd start = LineEnd::Center) -> DrawEnd;
// Draws the outline of a box with its top-left corner at (x, y).
//
@@ -359,6 +402,12 @@ class Buffer {
// word, and one too long for a row of its own is moved down to one and then
// overhangs it rather than being broken.
//
// With `backticks_are_quotes`, backticks pair up from the left on each line
// and no break falls between a pair, so a quoted type with spaces in it
// wraps whole or overhangs, as a URL does. A backtick with no partner later
// on its line, or with a tab or carriage return before its partner, is an
// ordinary character.
//
// Whitespace stops at the block's edge rather than running past it, so the
// spaces between two words stay on the row the first of them ended and the
// row the second wraps onto begins at the margin. Spaces the text opens with,
@@ -392,7 +441,8 @@ class Buffer {
// or only those a previous wrapping introduced -- is a question about where
// that text came from.
auto DrawWrappedText(int x, int y, int margin, int max_width,
llvm::StringRef text, const Style& style) -> DrawEnd;
llvm::StringRef text, const Style& style,
bool backticks_are_quotes = false) -> DrawEnd;
// Renders the grid, appending the bytes that draw it to `out`.
//
@@ -434,7 +484,7 @@ class Buffer {
struct Cell {
// The code point rendered here. For a cell with `lines` set, this is
// derived from those bits and the charset.
// derived from `lines`, `heavy_lines`, and the charset.
char32_t code_point = ' ';
Style style;
@@ -443,6 +493,10 @@ class Buffer {
// or zero for a cell holding text.
uint8_t lines = 0;
// The directions in `lines` that a heavy line leaves in. Always a subset
// of the directions set there.
uint8_t heavy_lines = 0;
// Whether this cell is the right half of a double-width character, and so
// renders nothing of its own.
bool is_continuation = false;
@@ -501,7 +555,8 @@ class Buffer {
PlaceFn place) const -> DrawEnd;
template <typename PlaceFn>
auto WalkWrappedText(int x, int y, int margin, int max_width,
llvm::StringRef text, PlaceFn place) const -> DrawEnd;
llvm::StringRef text, bool backticks_are_quotes,
PlaceFn place) const -> DrawEnd;
// Adds rows until row `y` exists.
auto EnsureRow(int y) -> void;
@@ -518,8 +573,14 @@ class Buffer {
// Appends `code_point` to the marks rendered with the cell before column `x`.
auto AttachCombiningMark(int x, int y, char32_t code_point) -> void;
// Adds `directions` to the lines through (x, y) and updates its glyph.
auto DrawLine(int x, int y, uint8_t directions, const Style& style) -> void;
// Adds `directions` to the lines through (x, y) at `weight`, replacing the
// weight those directions had, and updates its glyph.
auto DrawLine(int x, int y, uint8_t directions, LineWeight weight,
const Style& style) -> void;
// Returns whether the cell at (x, y) holds text. Cells outside what has been
// drawn hold nothing.
auto HoldsText(int x, int y) const -> bool;
// Returns the last column in row `y` that renders anything under `mode`, or
// -1 when the row renders nothing.
+271 -5
View File
@@ -6,7 +6,6 @@
#include <gtest/gtest.h>
#include <limits>
#include <optional>
#include <string>
@@ -171,18 +170,222 @@ TEST(BufferTest, Tees) {
"│ │ │\n"
"╰─┴─╯\n");
// The same table in ASCII, where every tee keeps its through-stroke and only
// the crossing in the middle is a `+`.
// The same table in ASCII: only the crossing in the middle is a `+`, a tee
// where a line leaves sideways is `|`, and a tee where a line leaves upward
// or downward is the corner glyph for that direction.
Buffer ascii(5, Charset::Ascii);
ascii.DrawBox(0, 0, 5, 5, Style());
ascii.DrawHorizontalLine(0, 2, 5, Style());
ascii.DrawVerticalLine(2, 0, 5, Style());
EXPECT_EQ(Render(ascii),
".---.\n"
".-.-.\n"
"| | |\n"
"|-+-|\n"
"| | |\n"
"'---'\n");
"'-'-'\n");
}
TEST(BufferTest, HeavyLines) {
// A heavy line keeps the same geometry as a light one, drawn in the heavy
// glyphs, ends included.
Buffer heavy(4, Charset::Utf8);
heavy.DrawHorizontalLine(0, 0, 4, Style(), LineEnd::Center, LineEnd::Center,
LineWeight::Heavy);
heavy.DrawVerticalLine(0, 1, 3, Style(), LineEnd::Center, LineEnd::Center,
LineWeight::Heavy);
EXPECT_EQ(Render(heavy),
"╺━━╸\n"
"╻\n"
"┃\n"
"╹\n");
// Heavy lines join each other the way light ones do, into heavy junctions.
Buffer joined(5, Charset::Utf8);
joined.DrawHorizontalLine(0, 0, 5, Style(), LineEnd::Edge, LineEnd::Edge,
LineWeight::Heavy);
joined.DrawVerticalLine(2, 0, 3, Style(), LineEnd::Center, LineEnd::Center,
LineWeight::Heavy);
joined.DrawHorizontalLine(2, 2, 3, Style(), LineEnd::Center, LineEnd::Edge,
LineWeight::Heavy);
EXPECT_EQ(Render(joined),
"━━┳━━\n"
" ┃\n"
" ┗━━\n");
}
TEST(BufferTest, HeavyAndLightLinesMix) {
// Weight is recorded per direction, so a heavy line crossing a light one
// draws a junction that is heavy left and right and light up and down.
Buffer cross(3, Charset::Utf8);
cross.DrawHorizontalLine(0, 1, 3, Style(), LineEnd::Edge, LineEnd::Edge,
LineWeight::Heavy);
cross.DrawVerticalLine(1, 0, 3, Style(), LineEnd::Edge, LineEnd::Edge);
EXPECT_EQ(Render(cross),
" │\n"
"━┿━\n"
" │\n");
// A light line leaving a heavy one downward: each line keeps its own weight
// in the tee.
Buffer tee(4, Charset::Utf8);
tee.DrawHorizontalLine(0, 0, 4, Style(), LineEnd::Edge, LineEnd::Edge,
LineWeight::Heavy);
tee.DrawVerticalLine(1, 0, 2, Style(), LineEnd::Center, LineEnd::Center);
EXPECT_EQ(Render(tee),
"━┯━━\n"
" ╵\n");
// A line's weight in a cell is that of whatever drew it last, as a cell's
// style is: a heavy line drawn over a light one leaves it heavy, and a light
// one drawn over part of a heavy one lightens the stretch it covers and
// nothing else.
Buffer overdrawn(3, Charset::Utf8);
overdrawn.DrawHorizontalLine(0, 0, 3, Style());
overdrawn.DrawHorizontalLine(0, 0, 3, Style(), LineEnd::Center,
LineEnd::Center, LineWeight::Heavy);
EXPECT_EQ(Render(overdrawn), "╺━╸\n");
Buffer lightened(4, Charset::Utf8);
lightened.DrawHorizontalLine(0, 0, 4, Style(), LineEnd::Edge, LineEnd::Edge,
LineWeight::Heavy);
lightened.DrawHorizontalLine(2, 0, 2, Style(), LineEnd::Edge, LineEnd::Edge);
EXPECT_EQ(Render(lightened), "━━──\n");
}
TEST(BufferTest, HeavyLinesInAscii) {
// ASCII has no weights, so a heavy horizontal line becomes `^`, including
// the tee where a heavy vertical line leaves it.
Buffer underline(8, Charset::Ascii);
underline.DrawHorizontalLine(0, 0, 7, Style(), LineEnd::Edge, LineEnd::Edge,
LineWeight::Heavy);
underline.DrawVerticalLine(3, 0, 2, Style(), LineEnd::Center, LineEnd::Center,
LineWeight::Heavy);
EXPECT_EQ(Render(underline),
"^^^^^^^\n"
" |\n");
// So does the top cell of a heavy vertical line, which no line enters from
// above, so the line is `^` above `|`.
Buffer vertical(3, Charset::Ascii);
vertical.DrawVerticalLine(1, 0, 3, Style(), LineEnd::Center, LineEnd::Center,
LineWeight::Heavy);
EXPECT_EQ(Render(vertical),
" ^\n"
" |\n"
" |\n");
// A light line leaving the heavy underline downward keeps the light tee's
// `.`, which `^` would hide, while a heavy line leaving it downward keeps the
// `^`.
Buffer tees(7, Charset::Ascii);
tees.DrawHorizontalLine(0, 0, 7, Style(), LineEnd::Edge, LineEnd::Edge,
LineWeight::Heavy);
tees.DrawVerticalLine(2, 0, 2, Style(), LineEnd::Center, LineEnd::Center);
tees.DrawVerticalLine(5, 0, 2, Style(), LineEnd::Center, LineEnd::Center,
LineWeight::Heavy);
EXPECT_EQ(Render(tees),
"^^.^^^^\n"
" | |\n");
// A cell a line enters from above keeps its light glyph, so the crossing is
// `+`, and the heavy vertical line is drawn as a light one.
Buffer crossed(3, Charset::Ascii);
crossed.DrawHorizontalLine(0, 1, 3, Style(), LineEnd::Edge, LineEnd::Edge,
LineWeight::Heavy);
crossed.DrawVerticalLine(1, 0, 3, Style(), LineEnd::Edge, LineEnd::Edge,
LineWeight::Heavy);
EXPECT_EQ(Render(crossed),
" |\n"
"^+^\n"
" |\n");
}
TEST(BufferTest, CrossingLinePassesBehindEarlierLines) {
// A crossing line joins the line its first cell holds. Below that, it has a
// gap where it crosses an earlier line, which stays unbroken, and the two
// don't join.
Buffer buffer(5, Charset::Utf8);
buffer.DrawHorizontalLine(0, 0, 5, Style(), LineEnd::Edge, LineEnd::Edge);
buffer.DrawHorizontalLine(0, 2, 5, Style(), LineEnd::Edge, LineEnd::Edge);
buffer.DrawCrossingVerticalLine(2, 0, 4, Style());
EXPECT_EQ(Render(buffer),
"──┬──\n"
" │\n"
"─────\n"
" │\n");
// The same crossing in ASCII, where the tee is the corner glyph for the line
// leaving downward and the crossed line is unbroken.
Buffer ascii(5, Charset::Ascii);
ascii.DrawHorizontalLine(0, 0, 5, Style(), LineEnd::Edge, LineEnd::Edge);
ascii.DrawHorizontalLine(0, 2, 5, Style(), LineEnd::Edge, LineEnd::Edge);
ascii.DrawCrossingVerticalLine(2, 0, 4, Style());
EXPECT_EQ(Render(ascii),
"--.--\n"
" |\n"
"-----\n"
" |\n");
// Weight plays no part in it: a heavy line drawn first stays whole the
// same way.
Buffer heavy(5, Charset::Utf8);
heavy.DrawHorizontalLine(0, 0, 5, Style(), LineEnd::Edge, LineEnd::Edge);
heavy.DrawHorizontalLine(0, 1, 5, Style(), LineEnd::Edge, LineEnd::Edge,
LineWeight::Heavy);
heavy.DrawCrossingVerticalLine(2, 0, 4, Style());
EXPECT_EQ(Render(heavy),
"──┬──\n"
"━━━━━\n"
" │\n"
" │\n");
}
TEST(BufferTest, CrossingLineResumesFromAnEdge) {
// A crossing line starting at an edge enters its first cell from above, so
// it continues a line broken off above a stretch it skipped, and it still
// has a gap where it crosses an earlier line.
Buffer buffer(5, Charset::Utf8);
buffer.DrawHorizontalLine(0, 0, 5, Style(), LineEnd::Edge, LineEnd::Edge);
buffer.DrawText(0, 1, "words", Style());
buffer.DrawHorizontalLine(0, 3, 5, Style(), LineEnd::Edge, LineEnd::Edge);
buffer.DrawCrossingVerticalLine(2, 0, 1, Style());
buffer.DrawCrossingVerticalLine(2, 2, 3, Style(), LineEnd::Edge);
EXPECT_EQ(Render(buffer),
"──┬──\n"
"words\n"
" │\n"
"─────\n"
" │\n");
}
TEST(BufferTest, CrossingLineSharesAColumnWithoutRedrawing) {
// A crossing line leaves the cells of an earlier line in its column as they
// are, so that line keeps its weight.
Buffer buffer(3, Charset::Utf8);
buffer.DrawHorizontalLine(0, 0, 3, Style(), LineEnd::Edge, LineEnd::Edge);
buffer.DrawVerticalLine(1, 0, 3, Style(), LineEnd::Center, LineEnd::Center,
LineWeight::Heavy);
buffer.DrawCrossingVerticalLine(1, 0, 3, Style());
EXPECT_EQ(Render(buffer),
"─┰─\n"
" ┃\n"
" ╹\n");
}
TEST(BufferTest, CrossingLineStaysClearOfText) {
// The line skips text, and blank cells directly beside text, where a line
// could be mistaken for part of a word.
Buffer buffer(6, Charset::Utf8);
buffer.DrawHorizontalLine(0, 0, 6, Style(), LineEnd::Edge, LineEnd::Edge);
buffer.DrawText(0, 1, "ab", Style());
buffer.DrawText(0, 3, "abc", Style());
buffer.DrawCrossingVerticalLine(2, 0, 5, Style());
EXPECT_EQ(Render(buffer),
"──┬───\n"
"ab\n"
" │\n"
"abc\n"
" │\n");
}
TEST(BufferTest, ALineBetweenOneCenterAndItselfIsAPoint) {
@@ -673,6 +876,69 @@ TEST(BufferTest, TabWidthComesFromCapabilities) {
EXPECT_EQ(Render(clamped), "a b\n");
}
TEST(BufferTest, WrappedTextKeepsAQuotedRunWhole) {
// With backticks as quotes, the spaces between a pair of backticks are not
// breaks, so the quoted run wraps to the next row whole.
Buffer buffer(20, Charset::Utf8);
buffer.DrawWrappedText(0, 0, 0, 20, "type `{.a: i32}` is not", Style(),
/*backticks_are_quotes=*/true);
EXPECT_EQ(Render(buffer),
"type `{.a: i32}` is\n"
"not\n");
Buffer split(14, Charset::Utf8);
split.DrawWrappedText(0, 0, 0, 14, "type `{.a: i32}` is not", Style(),
/*backticks_are_quotes=*/true);
EXPECT_EQ(Render(split),
"type\n"
"`{.a: i32}` is\n"
"not\n");
// Otherwise, the same text breaks inside the quotes like anywhere else.
Buffer unquoted(14, Charset::Utf8);
unquoted.DrawWrappedText(0, 0, 0, 14, "type `{.a: i32}` is not", Style());
EXPECT_EQ(Render(unquoted),
"type `{.a:\n"
"i32}` is not\n");
// A backtick with no partner on its line is an ordinary character, so the
// text after it still breaks at its spaces.
Buffer stray(14, Charset::Utf8);
stray.DrawWrappedText(0, 0, 0, 14, "a `stray quote and more words", Style(),
/*backticks_are_quotes=*/true);
EXPECT_EQ(Render(stray),
"a `stray quote\n"
"and more words\n");
// Nor does a run reach across a newline the caller wrote.
Buffer lines(14, Charset::Utf8);
lines.DrawWrappedText(0, 0, 0, 14, "a `b\nc d` e f g h", Style(),
/*backticks_are_quotes=*/true);
EXPECT_EQ(Render(lines),
"a `b\n"
"c d` e f g h\n");
// A word can't hold a tab or carriage return, so a quoted run doesn't reach
// across one either.
Buffer tabbed(14, Charset::Utf8);
tabbed.DrawWrappedText(0, 0, 0, 14, "a `b\tc` d", Style(),
/*backticks_are_quotes=*/true);
EXPECT_EQ(Render(tabbed), "a `b c` d\n");
EXPECT_EQ(buffer.MeasureWrapWidth("`b\tc`", /*backticks_are_quotes=*/true),
2);
EXPECT_EQ(buffer.MeasureWrapWidth("`b\rc`", /*backticks_are_quotes=*/true),
2);
// Measuring agrees: the quoted run is the widest word.
EXPECT_EQ(buffer.MeasureWrapWidth("type `{.a: i32}` is not",
/*backticks_are_quotes=*/true),
11);
EXPECT_EQ(buffer.MeasureWrapWidth("type `{.a: i32}` is not"), 5);
EXPECT_EQ(buffer.MeasureWrappedText(0, 0, 0, 14, "type `{.a: i32}` is not",
/*backticks_are_quotes=*/true),
DrawEnd(3, 2));
}
TEST(BufferTest, WrappedTextKeepsTheBreaksItIsGiven) {
// Wrapping only adds breaks. Text that arrives wrapped to some other width
// keeps that wrapping rather than being reflowed into this one, even where
-2
View File
@@ -5,7 +5,6 @@
package Core library "prelude/default";
import library "prelude/types/bool";
import library "prelude/types/int_literal";
// Provides the default value of an object. If implemented for a type `T`, this
// is used to initialize declarations without an explicit initializer, such as
@@ -29,7 +28,6 @@ interface UnformedInit {
// own.
impl bool as UnformedInit {}
impl forall [T: type] T* as UnformedInit {}
impl forall [T: UnformedInit, N: IntLiteral] array(T, N) as UnformedInit {}
// TODO: Generalize these to apply to tuples and structs containing only
// `UnformedInit` types.
impl () as UnformedInit {}
+20 -16
View File
@@ -3,33 +3,37 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
package Core library "prelude/destroy";
// TODO: uncomment when `require impls` adds a witness table entry.
// interface SubobjectDestroy {
// final fn Op(ref self) = "subobject.destroy";
// }
import library "prelude/operators/as";
import library "prelude/operators/bitwise";
interface Destroy {
// TODO: uncomment when `require impls` adds a witness table entry.
interface SubobjectDestroy {
fn Op(ref self);
}
// TODO: rename to `Destroy` when `require impls` adds a witness table entry.
interface DestroyImpl {
// TODO: uncomment when `require impls` adds a witness table entry.
// require impls SubobjectDestroy;
// TODO: change `Self` to `partial Self`.
fn Op(ref self);
}
// TODO: remove when `require impls` adds a witness table entry.
final fn SubobjectDestroy(unused ref self) {
// This function body is ignored by the toolchain. We can't use
// a built-in because the compiler treats it as an error. We
// should address this at some point, but it's not a high
// priority right now.
}
impl forall [T: SubobjectDestroy] T as DestroyImpl {
fn Op(ref self) = "no_op";
}
final fn SelfDestruct(ref self) {
self.Op();
self.SubobjectDestroy();
}
constraint Destroy {
extend require impls SubobjectDestroy & DestroyImpl;
}
// TODO: uncomment when `require impls` adds a witness table entry.
// impl forall [T: SubobjectDestroy] partial T as Destroy {
// alias Op = T.Op;
// }
fn SelfDestruct[T: Destroy](ref t: T) {
t.(DestroyImpl.Op)();
t.(SubobjectDestroy.Op)();
}
+9 -9
View File
@@ -14,17 +14,17 @@ interface Iterate {
let ElementType: Copy & Destroy;
let CursorType: Destroy;
fn NewCursor(self) -> CursorType;
fn Next(self, cursor: CursorType*) -> Optional(ElementType);
fn Next(self, ref cursor: CursorType) -> Optional(ElementType);
}
impl forall [T: Copy & Destroy, N: IntLiteral]
array(T, N) as Iterate
where .ElementType = T and .CursorType = i32 {
fn NewCursor(unused self) -> i32 { return 0; }
fn Next(self, cursor: i32*) -> Optional(T) {
if (*cursor < N) {
++*cursor;
return Optional(T).Some(self[*cursor - 1]);
fn Next(self, ref cursor: i32) -> Optional(T) {
if (cursor < N) {
++cursor;
return Optional(T).Some(self[cursor - 1]);
} else {
return Optional(T).None();
}
@@ -71,10 +71,10 @@ impl forall [T: CppRange where .ValueType impls Copy]
return (self.Begin(), self.End());
}
fn Next(unused self, cursor: CursorType*) -> Optional(ElementType) {
if (cursor->0 != cursor->1) {
returned var result: Optional(ElementType) = cursor->0.(CppUnsafeDeref.Op)();
++cursor->0;
fn Next(unused self, ref cursor: CursorType) -> Optional(ElementType) {
if (cursor.0 != cursor.1) {
returned var result: Optional(ElementType) = cursor.0.(CppUnsafeDeref.Op)();
++cursor.0;
return var;
}
return Optional(ElementType).None();
+18
View File
@@ -4,7 +4,25 @@
package Core library "prelude/operators/index";
export import library "prelude/copy";
interface IndexWith(SubscriptType: type) {
let ElementType: type;
fn At(self, subscript: SubscriptType) -> ElementType;
}
interface IndirectIndexWith(SubscriptType: type) {
let ElementType: type;
fn Ref(self, subscript: SubscriptType) -> ref ElementType;
}
final impl forall [
SubscriptType: type,
T: IndirectIndexWith(SubscriptType)
where .ElementType impls Copy
] T as IndexWith(SubscriptType)
where .ElementType = T.ElementType {
fn At(self, subscript: SubscriptType) -> ElementType {
return self.Ref(subscript);
}
}
+3 -3
View File
@@ -17,10 +17,10 @@ class IntRange(N: IntLiteral) {
impl as Iterate where .CursorType = Int(N) and .ElementType = Int(N) {
fn NewCursor(self) -> Int(N) { return self.start; }
fn Next(self, cursor: Int(N)*) -> Optional(Int(N)) {
var value: Int(N) = *cursor;
fn Next(self, ref cursor: Int(N)) -> Optional(Int(N)) {
var value: Int(N) = cursor;
if (value < self.end) {
++*cursor;
++cursor;
return Optional(Int(N)).Some(value);
} else {
return Optional(Int(N)).None();
+1
View File
@@ -4,6 +4,7 @@
package Core library "prelude/types";
export import library "prelude/types/array";
export import library "prelude/types/bool";
export import library "prelude/types/char";
export import library "prelude/types/char_literal";
+17
View File
@@ -0,0 +1,17 @@
// 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
package Core library "prelude/types/array";
import library "prelude/default";
import library "prelude/types/int_literal";
private fn MakeArray(T: type, N: IntLiteral) -> type = "array.make_type";
// The type `array(T, N)`.
class Array(T: type, N: IntLiteral) {
adapt MakeArray(T, N);
}
impl forall [T: UnformedInit, N: IntLiteral] Array(T, N) as UnformedInit {}
+2 -2
View File
@@ -121,8 +121,8 @@ constant value.
The use cases for classes include both cases motivated by C++ interop, and cases
that we expect to be included in idiomatic Carbon-only code.
**This design currently only attempts to address the "data classes" and
"encapsulated types" use cases.** Addressing the "interface as base class",
**This design currently only attempts to address the "data classes",
"encapsulated types", and "interface as base class" use cases.** Addressing the
"interop with C++ multiple inheritance" and "mixin" use cases is future work.
### Data classes
+54 -28
View File
@@ -14,7 +14,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- [Details](#details)
- [`while`](#while)
- [`for`](#for)
- [Ranged-for for user-defined types](#ranged-for-for-user-defined-types)
- [`Iterate` interface](#iterate-interface)
- [Loop desugaring](#loop-desugaring)
- [`break`](#break)
- [`continue`](#continue)
- [Alternatives considered](#alternatives-considered)
@@ -54,9 +55,6 @@ is:
> `for (` _pattern_ `in` _expression_ `) {` _statements_ `}`
For consistency with function parameters and other pattern matching contexts,
the pattern defaults to value (immutable) bindings, like `let`.
For example, this prints all names in `names`:
```carbon
@@ -65,47 +63,73 @@ for (name: strbuf in names) {
}
```
This default can be overridden by adding the `var` keyword:
> **Note:** A name binding in the pattern of a `for` loop is a value binding by
> default. If a mutable loop variable is desired, the pattern can use `var`. For
> example: `for (var x: T in range)`.
```carbon
for (var name: strbuf in names) {
// `name` can be modified, but this will not modify the underlying `names` container.
}
```
To support iterating over user-defined types, the `for` loop is defined in terms
of the `Core.Iterate` interface. A container type can implement the
`Core.Iterate` interface to make it iterable with `for`.
Temporary entities on the right-hand side of `in` remain alive during the
execution of the `for` loop to prevent invalid memory access.
#### `Iterate` interface
#### Ranged-for for user-defined types
User types can enable support for ranged-for loops by implementing the `Iterate`
interface:
The `Core.Iterate` interface is defined as:
```carbon
interface Iterate {
let ElementType: type;
let CursorType: type;
let ElementType: Copy & Destroy;
let CursorType: Destroy;
fn NewCursor(self) -> CursorType;
fn Next(self, ref cursor: CursorType) -> Optional(ElementType);
}
```
The cursor tracks progression, and the `Next` method advances the cursor and
returns an `Optional` value. An empty `Optional` indicates that we have reached
the end.
- **`CursorType`**: A type that represents the current position in the
iteration.
- **`ElementType`**: The type of the elements returned by the iteration.
- **`NewCursor`**: Returns a new cursor initialized to the start of iteration.
- **`Next`**: Advances the cursor and returns the next element, or `None` if
the end of iteration has been reached. It takes the cursor by reference
(`ref cursor: CursorType`), allowing the method to modify the cursor
in-place.
A `for` loop on a container of a type that implements `Iterate` behaves
conceptually as (though an API for `Optional` has not been approved):
> **TODO:** Update this interface to be extended type generic once suitable
> language facilities exist.
#### Loop desugaring
A `for` loop of the form:
```carbon
var cursor: range.(Iterate.CursorType) = range.(Iterate.NewCursor)();
var iter: Optional(range.(Iterate.ElementType)) = range.(Iterate.Next)(&cursor);
while (iter.HasValue()) {
ExecuteForBlock(iter.Get());
iter = container.(Iterate.Next)(ref cursor);
for (<pattern> in <range>) {
<statements>
}
```
is desugared to a loop that manages the cursor and checks the optional return
values:
```carbon
{
let range:? auto = <range>;
var cursor: auto = range.(Iterate.NewCursor)();
while (true) {
match (range.(Iterate.Next)(cursor)) {
case .Some(<pattern>) => { <statements> }
default => { break; }
}
}
}
```
> **TODO:** The above uses the deduced extended type syntax `:? auto` from
> in-progress proposal
> [#5389](https://github.com/carbon-language/carbon-lang/pull/5389), and should
> be updated to match the final state of that proposal.
> **Note:** Any temporaries in `<range>` will remain live until the end of the
> loop.
### `break`
The `break` statement immediately ends a `while` or `for` loop. Execution will
@@ -173,3 +197,5 @@ while (!f.EOF()) {
[#623: Require braces](https://github.com/carbon-language/carbon-lang/pull/623)
- Proposal
[#1885: `for` statement and user types](https://github.com/carbon-language/carbon-lang/pull/1885)
- Proposal
[#7381: Adopt `ref` in `Core.Iterate.Next`](https://github.com/carbon-language/carbon-lang/pull/7381)
+2 -2
View File
@@ -1176,8 +1176,8 @@ Assert((r as __Binding_C_F).(Call(()).Op)() == 9);
How does this arise?
1. First the simple member access evaluates `typeof(a).F`, which is `C.F` (`__C_F`
with type `__TypeOf_C_F`).
1. First the simple member access evaluates `typeof(a).F`, which is `C.F`
(`__C_F` with type `__TypeOf_C_F`).
2. It then looks at the expression to the left of the `.`:
- If it is a reference expression, the "member binding to reference"
(`BindToRef`) operator is applied.
+6 -7
View File
@@ -3337,15 +3337,14 @@ the expression `G.E.N.E.N` is one equality away from `G.N.E.N` and so it is
allowed. This is true even though `G.N.E.N` isn't the type expression
immediately prior to `G.E.N.E.N`.
An `observe` declaration inside an `interface` definition may only refer to
names dependent on:
An `observe` declaration inside an `interface` definition refers to related
values, which are type expressions dependent on at least one of:
- `.Self`,
- generic parameters, and
- associated constants that are part of the enclosing `interface`.
- a generic parameter, including `Self`, or
- an associated constant that is part of the enclosing `interface`.
Equivalence chains may include at most one unrelated value.
`observe .. == .. impls` declarations may include unrelated values that
Inside an `interface`, equivalence chains may include at most one unrelated
value. `observe .. == .. impls` declarations may include unrelated values that
immediately satisfy the `impls` constraint.
The `observe` declaration in the example below is allowed because `X.T` and
+3 -2
View File
@@ -414,8 +414,9 @@ matches if all of these sub-matches succeed.
A struct can be matched with a struct pattern.
- _struct-pattern_ ::= `{` [_field-pattern_ [`,` _field-pattern_ ]\* ] `}`
- _struct-pattern_ ::= `{` [_field-pattern_ `,`]+ `_` `}`
- _struct-pattern_ ::= `{` [_field-pattern_ [`,` _field-pattern_ ]\* `,`? ]
`}`
- _struct-pattern_ ::= `{` [_field-pattern_ `,`]+ `_` `,`? `}`
- _field-pattern_ ::= _designator_ `=` _pattern_
- _field-pattern_ ::= _binding-pattern_
- _pattern_ ::= _struct-pattern_
+36
View File
@@ -23,6 +23,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- [Typechecking](#typechecking)
- [Tuples, packs, segments, and shapes](#tuples-packs-segments-and-shapes)
- [Iterative typechecking of pack expansions](#iterative-typechecking-of-pack-expansions)
- [Type versus execution semantics](#type-versus-execution-semantics)
- [Typechecking patterns](#typechecking-patterns)
- [Typechecking pattern matches](#typechecking-pattern-matches)
- [Appendix: Type system formalism](#appendix-type-system-formalism)
@@ -491,6 +492,41 @@ expansion itself is relatively straightforward:
> **TODO:** Discuss typechecking `...expand`.
#### Type versus execution semantics
Within a pack expansion, there's a subtle discrepancy between the type of an
expression and its execution semantics. As discussed in the previous section, an
expression within a pack expansion has a pack type rather than a scalar type, as
if it were evaluated once and produced a pack value. However, as discussed
[earlier](#expressions-and-statements), the execution of a pack expansion is
modeled as a procedural loop that successively evaluates the expansion body for
each element of the input pack, and expressions have scalar values within each
iteration. In effect, the type system treats pack expansions as if they were
SIMD code, with expressions operating on "vectors" of data in parallel, even
though they actually execute iteratively on a series of scalar values.
As a result, it can be misleading to use the type system as a guide to the
execution semantics, because the two models disagree in some corner cases.
For example, consider:
```carbon
fn F(i: i32) -> i64;
fn G(... (each c: bool, each x: i32)) {
...if each c then F(each x) else 0 as i64;
}
```
In that code, `F(each x)` has type `«i64; ‖each x‖»`, in other words, a pack
with the same arity as `each x`, whose elements are all `i64`. That may give the
impression that the expression `F(each x)` evaluates to a pack of `i64` values
with arity `‖each x‖`, and so `F` is called `‖each x‖` times, but that's not the
case. In fact, `F(each x)` is evaluated once for each `true` value in `each c`,
and each evaluation produces a scalar `i64` value. The results of those
evaluations are never actually assembled into a pack, and in fact they
_couldn't_ be, because the arity of such a pack isn't known until run time, and
typically wouldn't match the arity of the enclosing pack expansion.
### Typechecking patterns
A pack expansion pattern has _fixed arity_ if it contains at least one usage of
+1 -5
View File
@@ -72,8 +72,6 @@ language are well explained, justified, and reviewed by the community.
- Send the proposal as a broad RFC to the community.
- Add the "proposal rfc" label for tracking.
- Contributors are encouraged to react with a _thumbs-up_ to proposal PRs if
they are generally interested and supportive of the high-level direction
based on title and summary. Similarly, other reactions are encouraged to
@@ -143,8 +141,6 @@ issues for longer discussion:
- Send the proposal as a broad RFC to the community.
- Add the "proposal rfc" label for tracking.
- Address comments where you can and they make sense.
- If you don't see an obvious way to address comments, that's OK.
@@ -172,7 +168,7 @@ issues for longer discussion:
#### Community
- We use the
["proposal rfc" label](https://github.com/carbon-language/carbon-lang/pulls?q=is%3Apr+is%3Aopen+label%3A%22proposal+rfc%22)
["proposal" label on non-draft PRs](https://github.com/carbon-language/carbon-lang/pulls?q=is%3Apr+is%3Aopen+draft%3Afalse+label%3Aproposal)
to track proposals that are in RFC.
- Anyone that is interested can participate once a proposal is ready for
+6 -2
View File
@@ -21,10 +21,14 @@ principle can help achieve consistency across those multiple designs.
Note that these principles seek to establish both the approaches the project
wants to pursue, as well as those we want to exclude.
- [All APIs are library APIs](library_apis_only.md)
- [Errors are values](error_handling.md)
- [File concatenation](file_concatenation.md)
- [Information accumulation](information_accumulation.md)
- [Low context-sensitivity](low_context_sensitivity.md)
- [Prefer providing only one way to do a given thing](one_way.md)
- [Namespace cleanliness](namespace_cleanliness.md)
- [One static open extension mechanism](static_open_extension.md)
- [Prefer providing only one way to do a given thing](one_way.md)
- [Progressive disclosure](progressive_disclosure.md)
- [Safety strategy](safety_strategy.md)
- [Success criteria](success_criteria.md)
- [File concatenation](file_concatenation.md)
+6 -6
View File
@@ -4,9 +4,9 @@
#
# bazel run //:requirements.update
#
certifi==2024.7.4 \
--hash=sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b \
--hash=sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90
certifi==2026.10.19 \
--hash=sha256:035c1e85572b141bf1c3a3eff0e33a8eb74917522f2559075ff8d2fea780a1ae \
--hash=sha256:42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b
# via requests
charset-normalizer==3.3.2 \
--hash=sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027 \
@@ -131,7 +131,7 @@ six==1.16.0 \
# gql
# graphql-core
# promise
urllib3==2.7.0 \
--hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \
--hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897
urllib3==2.8.0 \
--hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3 \
--hash=sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63
# via requests
+3 -3
View File
@@ -59,11 +59,11 @@ A few syntaxes that are likely to influence `if`/`else` are:
```python
if x:
print("x is true");
print("x is true")
elif y:
print("y is true");
print("y is true")
else:
print("Neither was true");
print("Neither was true")
```
- Swift
+6 -6
View File
@@ -53,17 +53,17 @@ similar in many languages, even if details may change.
```python
while x:
DoSomething()
DoSomething()
while True:
DoSomethingElse()
if not y:
break
DoSomethingElse()
if not y:
break
while z:
print("z is true")
print("z is true")
else:
print("z was never true")
print("z was never true")
```
- Swift: Swift uses `repeat` instead of `do`.
+3 -2
View File
@@ -146,10 +146,11 @@ For exhaustive function examples:
```python
def sum(a, b):
return a + b
return a + b
def sum(a: int, b: int) -> int:
return a + b
return a + b
```
- R
@@ -116,14 +116,15 @@ as the next value to the `for` loop. Iteration ends when the function returns.
```python
def generate_fibonacci():
yield 0;
n1, n2 = 0, 1;
while True:
n1, n2 = n2, n1+n2;
yield n1;
yield 0
n1, n2 = 0, 1
while True:
n1, n2 = n2, n1 + n2
yield n1
for n in generate():
print(n);
print(n)
```
#### Rust
+5 -5
View File
@@ -147,25 +147,25 @@ are examples of character literals for each specific type:
`let allowed: Char8 = 'a'`
In this example, the character literal `’a’` corresponds to the Unicode code
In this example, the character literal `'a'` corresponds to the Unicode code
point `97`, which is within the valid range of `Char8` since `97` is less than
or equal to `0x7F`.
- `Char16`: The character literal represents a Unicode code point that can be
represented within 16 bits. Here’s an example:
`let smiley: Char16 = ‘\u{1F600}’`
`let smiley: Char16 = '\u{1F600}'`
The character literal `’\u{1F600}’` represents the smiley face emoji, which has
The character literal `'\u{1F600}'` represents the smiley face emoji, which has
the Unicode code point `128512`. Since `128512` can be represented within 16
bits, it can be assigned to a variable of type `Char16`.
- `Char32`: This character type allows the representation of Unicode code
points within 32 bits. Here’s an example:
`let musicalNote: Char32 = ‘🎵’`
`let musicalNote: Char32 = '🎵'`
In this case, the character literal `’🎵’` corresponds to the musical note emoji
In this case, the character literal `'🎵'` corresponds to the musical note emoji
with the Unicode code point `127925`. Since `127925` falls within the range that
can be represented by `Char32`, it can be assigned to a variable of type
`Char32`.
@@ -83,6 +83,8 @@ In Python, a line is a
parentheses are an idiomatic way to create multi-line statements. Semicolons may
be used to explicitly separate statements. For example:
<!-- fmt:off -->
```python
value = (
"text"
@@ -90,6 +92,8 @@ value = (
a = 1; b = 2; c = 3
```
<!-- fmt:on -->
Swift allows some statements to wrap lines, although multiple statements on the
same line (`x = 1 x = 1`) require a semicolon. The detailed rules aren't
documented so it's difficult to assess other than that Swift developers are
@@ -101,6 +105,8 @@ documents that line-breaks are treated as whitespace. However, there are
observable ways the behavior can lead to small mistakes; these may may often be
caught by the compiler, but will sometimes be missed. For example:
<!-- fmt:off -->
```swift
// One statement in Swift, but two in Python and Kotlin.
var x = 1
@@ -116,12 +122,16 @@ Make()
()
```
<!-- fmt:on -->
Kotlin permits a newline to be used to terminate statements instead of a
semicolon. Kotlin's grammar
[explicitly enumerates](https://kotlinlang.org/spec/syntax-and-grammar.html) all
the places where newlines can appear (see mentions of `NL` in the grammar), and
doesn't allow newlines in places where they would introduce ambiguity.
<!-- fmt:off -->
```kotlin
// This is unambiguously parsed as two statements, because
// a newline is not permitted before a `+` operator.
@@ -129,6 +139,8 @@ var x = 1
+ 1
```
<!-- fmt:on -->
In JavaScript and TypeScript, semicolons are part of the formal syntax, and
ECMAScript provides
[Automatic Semicolon Insertion (ASI)](https://tc39.es/ecma262/#sec-automatic-semicolon-insertion).
@@ -0,0 +1,79 @@
# Adopt `ref` in `Core.Iterate.Next`
<!--
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
-->
[Pull request](https://github.com/carbon-language/carbon-lang/pull/7381)
<!-- toc -->
## Table of contents
- [Abstract](#abstract)
- [Problem](#problem)
- [Background](#background)
- [Proposal](#proposal)
- [Rationale](#rationale)
- [Future work](#future-work)
- [Alternatives considered](#alternatives-considered)
<!-- tocstop -->
## Abstract
Change the `cursor` parameter in `Core.Iterate.Next` to be a `ref` parameter
instead of a pointer parameter.
## Problem
According to [p001885](p001885-for-statement-and-user-types.md),
`Core.Iterate.Next` is declared as follows:
```carbon
fn Next[self: Self](cursor: CursorType*) -> Optional(ElementType);
```
Aside from the old `self` syntax, this uses pass-by-pointer to pass a mutable
reference. This stopped being idiomatic Carbon when `ref` patterns were added,
but we still require it when interacting with this core language feature.
## Background
Proposal [p001885](p001885-for-statement-and-user-types.md) added the `Iterate`
interface.
Proposal [p002006](p002006-values-variables-pointers-and-references.md) affirmed
that Carbon does not have reference _types_, but does have reference
_expressions_.
Proposal [p005434](p005434-ref-parameters-arguments-returns-and-val-returns.md)
added support for `ref` _patterns_ to match reference expressions. These
replaced pass-by-pointer as the idiomatic way to pass a handle to a mutable
object into a function.
## Proposal
Change the declaration of `Core.Iterate.Next` to:
```carbon
fn Next(self, ref cursor: CursorType) -> Optional(ElementType);
```
## Rationale
- [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write)
- Makes the `Iterate` interface, and user-defined `impl`s of it, more
consistent with the rest of the language.
## Future work
We should consider making `Core.Iterate` a form-generic interface, and making
the `ElementType` instead be an associated form, with a suitable change to
return a form-generic `Optional`.
## Alternatives considered
None.
-2
View File
@@ -246,8 +246,6 @@ def main() -> None:
"--draft",
"--label",
"proposal",
"--label",
"proposal draft",
"--repo",
"carbon-language/carbon-lang",
"--title",
+1 -1
View File
@@ -29,7 +29,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
1. Copy this template to `new.md`, and create a commit.
2. Create a GitHub pull request, to get a pull request number.
- Add the `proposal draft` label to the pull request.
- Add the `proposal` label to the pull request.
3. Rename `new.md` to `/proposals/p####.md`, where `####` should be the pull
request number.
4. Update the title of the proposal (the `TODO` on line 1).
+9 -1
View File
@@ -64,6 +64,11 @@ Args:
can come with a `0x` prefix, allowing easier copy-paste from raw printed
hex values (such as via the `p/x` lldb command).
Aliases:
dc [<EXPR>|-- <EXPR>|<TYPE><ID>|<TYPE> <ID>]
- Shorthand for `dump context` which is the common case where there is a
`context` var or method in scope.
Example usage:
# Dumps the `inst_id` local variable, with a `context` local variable.
dump context inst_id
@@ -82,6 +87,8 @@ Example usage:
# The set of "Make" functions in dump.cpp.
id_types = {
"clang_decl": "SemIR::MakeClangDeclId",
"clang_decl_signature": "SemIR::MakeClangDeclSignatureId",
"class": "SemIR::MakeClassId",
"constant": "SemIR::MakeConstantId",
"constraint": "SemIR::MakeNamedConstraintId",
@@ -90,7 +97,7 @@ Example usage:
"function": "SemIR::MakeFunctionId",
"generated_function": "SemIR::MakeGeneratedFunctionId",
"generic": "SemIR::MakeGenericId",
"identified_facet_type": "SemIR::MakeIdentifiedFacetTypeId",
"identified_facet_type": "Check::MakeIdentifiedFacetTypeId",
"impl": "SemIR::MakeImplId",
"inst": "SemIR::MakeInstId",
"inst_block": "SemIR::MakeInstBlockId",
@@ -202,6 +209,7 @@ def format_carbon_id(
def __lldb_init_module(debugger: Any, internal_dict: Any) -> None:
RunCommand("command script add -f lldbinit.cmd_dump dump")
RunCommand("command alias dc dump context")
RunCommand(
"type summary add --python-function lldbinit.format_carbon_id"
+ " --recognizer-function lldbinit.is_carbon_id"
+3
View File
@@ -37,6 +37,7 @@ cc_library(
testonly = 1,
srcs = ["benchmark_main.cpp"],
deps = [
":benchmark_helpers",
":global_exe_path",
"//common:init_llvm",
"@abseil-cpp//absl/flags:parse",
@@ -48,7 +49,9 @@ cc_library(
cc_library(
name = "benchmark_helpers",
testonly = 1,
srcs = ["benchmark_helpers.cpp"],
hdrs = ["benchmark_helpers.h"],
deps = ["//common:check"],
)
cc_library(
+27
View File
@@ -0,0 +1,27 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "testing/base/benchmark_helpers.h"
#include <optional>
#include "common/check.h"
static constinit std::optional<bool> benchmark_dry_run = {};
namespace Carbon::Testing {
auto IsBenchmarkDryRun() -> bool {
CARBON_CHECK(benchmark_dry_run,
"queried `IsBenchmarkDryRun` before it was set");
return *benchmark_dry_run;
}
auto SetBenchmarkDryRun(bool is_dry_run) -> void {
CARBON_CHECK(!benchmark_dry_run,
"called `SetBenchmarkDryRun` more than once");
benchmark_dry_run = is_dry_run;
}
} // namespace Carbon::Testing
+14
View File
@@ -65,6 +65,20 @@ template <typename T>
DoNotOptimize(container.data());
}
// Returns true if the benchmark binary was invoked in dry-run mode
// (`--benchmark_dry_run` or `BENCHMARK_DRY_RUN`).
//
// Google Benchmark's `--benchmark_dry_run` caps `state.max_iterations` to `1`,
// so benchmarks that use `state.KeepRunningBatch(n)` or multi-input setup still
// execute a full batch of `n` unless they also clamp `n` on dry runs.
auto IsBenchmarkDryRun() -> bool;
// Sets whether the benchmark binary is running in dry-run mode.
//
// This function must only be called once for an execution, and before any
// callers to `IsBenchmarkDryRun`. Typically, it is called from within `main`.
auto SetBenchmarkDryRun(bool is_dry_run) -> void;
} // namespace Carbon::Testing
#endif // CARBON_TESTING_BASE_BENCHMARK_HELPERS_H_
+40
View File
@@ -4,15 +4,55 @@
#include <benchmark/benchmark.h>
#include <cstdlib>
#include "absl/flags/parse.h"
#include "common/init_llvm.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "testing/base/benchmark_helpers.h"
#include "testing/base/global_exe_path.h"
// Returns true if `value` is truthy under Google Benchmark's boolean flag
// rules: anything that isn't clearly _false_ is treated as truthy.
static auto IsTruthyBenchmarkFlagValue(llvm::StringRef value) -> bool {
if (value.empty()) {
return true;
}
if (value.size() == 1) {
char c = value[0];
return llvm::isAlnum(c) && !llvm::StringRef("0fFnN").contains(c);
}
return !value.equals_insensitive("false") &&
!value.equals_insensitive("no") && !value.equals_insensitive("off");
}
// Detects `--benchmark_dry_run` (and `BENCHMARK_DRY_RUN` in the environment)
// using the same rules as `benchmark::Initialize`, which consumes the flag
// without exposing its value through Google Benchmark's public API.
static auto DetectBenchmarkDryRun(llvm::ArrayRef<char*> args) -> bool {
bool dry_run = false;
if (const char* env = std::getenv("BENCHMARK_DRY_RUN")) {
dry_run = IsTruthyBenchmarkFlagValue(env);
}
for (llvm::StringRef arg : args) {
if (arg == "--benchmark_dry_run") {
dry_run = true;
} else if (arg.consume_front("--benchmark_dry_run=")) {
dry_run = IsTruthyBenchmarkFlagValue(arg);
}
}
return dry_run;
}
auto main(int orig_argc, char** orig_argv) -> int {
// Do LLVM's initialization first, this will also transform UTF-16 to UTF-8.
Carbon::InitLLVM init_llvm(orig_argc, orig_argv);
Carbon::Testing::SetExePath(orig_argv[0]);
Carbon::Testing::SetBenchmarkDryRun(
DetectBenchmarkDryRun(llvm::ArrayRef(orig_argv, orig_argc).drop_front()));
// Inject a flag to override the defaults for benchmarks. This can still be
// disabled by user arguments.
+2
View File
@@ -72,7 +72,9 @@ cc_library(
"@llvm-project//clang:basic",
"@llvm-project//clang:driver",
"@llvm-project//clang:frontend",
"@llvm-project//llvm:MC",
"@llvm-project//llvm:Support",
"@llvm-project//llvm:TargetParser",
],
)
+3 -1
View File
@@ -17,7 +17,9 @@ namespace Carbon {
// A wrapper for accumulating immutable values with deduplication, providing IDs
// to later retrieve the value.
//
// `ValueT` represents the type being stored.
// `ValueT` represents the type being stored. Be careful storing non-owned
// values (e.g. `llvm::StringRef`), as the store will not prevent the
// underlying data from being freed.
//
// `KeyT` can optionally be different from `ValueT`, and if so is used for the
// argument to `Lookup`. In this case, `ValueT` must provide a `GetAsKey` member
+57 -15
View File
@@ -7,14 +7,21 @@
#include <filesystem>
#include <string>
#include "clang/Basic/DiagnosticDriver.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Driver/CreateInvocationFromArgs.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/Utils.h"
#include "common/string_helpers.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/TargetParser/SubtargetFeature.h"
#include "llvm/TargetParser/Triple.h"
namespace Carbon {
@@ -50,17 +57,51 @@ auto ClangDriverDiagnosticConsumer::HandleDiagnostic(
}
}
// Validates `target_opts` (such as `-march`, `-mcpu`, `-mtune`, and
// `-target-feature`) and canonicalizes `target_opts.Features` via
// `clang::TargetInfo::CreateTargetInfo`. Returns `false` if any target option
// or feature is invalid.
static auto ValidateTargetOptions(clang::DiagnosticsEngine& driver_diags,
clang::TargetOptions& target_opts) -> bool {
llvm::IntrusiveRefCntPtr<clang::TargetInfo> target_info(
clang::TargetInfo::CreateTargetInfo(driver_diags, target_opts));
// `TargetInfo::CreateTargetInfo` expands Clang-level feature aliases into
// `TargetOptions::Features`, but passes unknown `+feature`/`-feature`
// entries through to the backend where `MCSubtargetInfo` only prints a raw
// stderr warning. Validate that every canonicalized backend feature is
// recognized by the target's `MCSubtargetInfo` when the target is registered.
std::string target_error;
llvm::Triple triple(target_opts.Triple);
if (const llvm::Target* target =
llvm::TargetRegistry::lookupTarget(triple, target_error)) {
std::unique_ptr<llvm::MCSubtargetInfo> subtarget_info(
target->createMCSubtargetInfo(triple, /*CPU=*/"", /*Features=*/""));
if (subtarget_info) {
llvm::ArrayRef<llvm::SubtargetFeatureKV> valid_features =
subtarget_info->getAllProcessorFeatures();
const auto& features =
target_info ? target_opts.Features : target_opts.FeaturesAsWritten;
for (llvm::StringRef feature : features) {
llvm::StringRef name = llvm::SubtargetFeatures::StripFlag(feature);
const auto* it = llvm::lower_bound(valid_features, name);
if (it == valid_features.end() || llvm::StringRef(it->key()) != name) {
driver_diags.Report(clang::diag::err_drv_unsupported_option_argument)
<< "-target-feature" << feature;
}
}
}
}
return target_info && !driver_diags.hasErrorOccurred();
}
auto BuildClangInvocation(Diagnostics::Consumer& consumer,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
const InstallPaths& install_paths,
llvm::StringRef target_str,
llvm::ArrayRef<llvm::StringRef> extra_args)
-> std::unique_ptr<clang::CompilerInvocation> {
Diagnostics::ErrorTrackingConsumer error_tracker(consumer);
Diagnostics::NoLocEmitter emitter(&error_tracker);
ClangDriverDiagnosticConsumer diagnostics_consumer(&emitter);
llvm::SmallVector<std::string> args;
args.push_back("--start-no-unused-arguments");
AppendDefaultClangArgs(install_paths, target_str, args);
@@ -83,6 +124,8 @@ auto BuildClangInvocation(Diagnostics::Consumer& consumer,
// Build a diagnostics engine. Note that we don't have any diagnostic options
// yet; they're produced by running the driver.
Diagnostics::NoLocEmitter emitter(&consumer);
ClangDriverDiagnosticConsumer diagnostics_consumer(&emitter);
clang::DiagnosticOptions driver_diag_opts;
llvm::IntrusiveRefCntPtr<clang::DiagnosticsEngine> driver_diags(
clang::CompilerInstance::createDiagnostics(*fs, driver_diag_opts,
@@ -91,23 +134,22 @@ auto BuildClangInvocation(Diagnostics::Consumer& consumer,
// Ask the driver to process the arguments and build a corresponding clang
// frontend invocation.
auto invocation =
std::unique_ptr<clang::CompilerInvocation> invocation =
clang::createInvocation(cstr_args, {.Diags = driver_diags, .VFS = fs});
// If Clang produced an error, throw away its invocation.
if (error_tracker.seen_error()) {
if (!invocation || driver_diags->hasErrorOccurred() ||
!ValidateTargetOptions(*driver_diags, invocation->getTargetOpts())) {
return nullptr;
}
if (invocation) {
// Track submodule visibility in the preprocessor and Sema.
invocation->getLangOpts().Modules = true;
invocation->getLangOpts().ModulesLocalVisibility = true;
// Track submodule visibility in the preprocessor and Sema.
invocation->getLangOpts().Modules = true;
invocation->getLangOpts().ModulesLocalVisibility = true;
// Do not emit Clang's name and version as the creator of the output file.
invocation->getCodeGenOpts().EmitVersionIdentMetadata = false;
invocation->getCodeGenOpts().DiscardValueNames = false;
}
// Do not emit Clang's name and version as the creator of the output file.
invocation->getCodeGenOpts().EmitVersionIdentMetadata = false;
invocation->getCodeGenOpts().DiscardValueNames = false;
return invocation;
}
+5 -2
View File
@@ -5,6 +5,7 @@
#ifndef CARBON_TOOLCHAIN_BASE_CLANG_INVOCATION_H_
#define CARBON_TOOLCHAIN_BASE_CLANG_INVOCATION_H_
#include <memory>
#include <string>
#include "clang/Basic/Diagnostic.h"
@@ -37,8 +38,10 @@ class ClangDriverDiagnosticConsumer : public clang::DiagnosticConsumer {
};
// Builds and returns a clang `CompilerInvocation` to use when building code for
// interop, from a list of clang driver arguments. Emits diagnostics to
// `consumer` if the arguments are invalid.
// interop, from a list of clang driver arguments. Validates the target options
// and canonicalizes `TargetOptions::Features` via
// `clang::TargetInfo::CreateTargetInfo`. Emits diagnostics to `consumer` if the
// arguments or target options are invalid.
auto BuildClangInvocation(Diagnostics::Consumer& consumer,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
const InstallPaths& install_paths,
+26 -14
View File
@@ -68,28 +68,40 @@ struct IdTag {
auto Apply(int32_t index) const -> IdT {
CARBON_DCHECK(index >= 0, "{0}", index);
if (index < initial_reserved_ids_) {
if constexpr (IdTagIsUntagged<IdTag>) {
// Untagged ids are the index itself, so return directly with no tag
// computation.
return IdT(index);
} else {
if (index < initial_reserved_ids_) {
return IdT(index);
}
// TODO: Assert that tag_ doesn't have the second highest bit set.
auto tagged_index = index ^ tag_;
CARBON_DCHECK(tagged_index >= 0, "{0}", tagged_index);
return IdT(tagged_index);
}
// TODO: Assert that tag_ doesn't have the second highest bit set.
auto tagged_index = index ^ tag_;
CARBON_DCHECK(tagged_index >= 0, "{0}", tagged_index);
return IdT(tagged_index);
}
auto Remove(IdT id) const -> int32_t {
CARBON_DCHECK(id.index >= 0, "{0}", id);
if (!HasTag(id.index)) {
CARBON_DCHECK(id.index < initial_reserved_ids_,
"This untagged index is outside the initial reserved ids "
"and should have been tagged.");
if constexpr (IdTagIsUntagged<IdTag>) {
// Untagged ids are the index itself, so return directly with no tag
// computation.
return id.index;
} else {
if (!HasTag(id.index)) {
CARBON_DCHECK(id.index < initial_reserved_ids_,
"This untagged index is outside the initial reserved ids "
"and should have been tagged.");
return id.index;
}
auto untagged_index = id.index ^ tag_;
CARBON_DCHECK(untagged_index >= initial_reserved_ids_,
"When removing tagging bits, found an index that "
"shouldn't've been tagged in the first place.");
return untagged_index;
}
auto untagged_index = id.index ^ tag_;
CARBON_DCHECK(untagged_index >= initial_reserved_ids_,
"When removing tagging bits, found an index that "
"shouldn't've been tagged in the first place.");
return untagged_index;
}
// Gets the value unique to this IdTag instance that is added to indices in
+7 -2
View File
@@ -33,9 +33,9 @@ namespace Carbon {
// data_.reserved_bytes());
// // Common library types like `Map` and `llvm::SmallVector` have
// // type-specific support.
// mem_usage.Add(MemUsage::Concat(label, "array_"), array_);
// mem_usage.Add(MemUsage::ConcatLabel(label, "array_"), array_);
// // Implementing `CollectMemUsage` allows use with the same interface.
// mem_usage.Collect(MemUsage::Concat(label, "obj_"), obj_);
// mem_usage.Collect(MemUsage::ConcatLabel(label, "obj_"), obj_);
// }
class MemUsage {
public:
@@ -104,6 +104,11 @@ class MemUsage {
Add(std::move(label), array.size_in_bytes(), array.capacity_in_bytes());
}
// Adds memory usage of a string's data.
auto Collect(std::string label, const std::string& string) -> void {
Add(std::move(label), string.size(), string.capacity());
}
// Adds memory usage for an object that provides `CollectMemUsage`.
//
// The expected signature of `CollectMemUsage` is above, in MemUsage class
+3
View File
@@ -25,6 +25,9 @@ class SharedValueStores : public Yaml::Printable<SharedValueStores> {
using IntStore = IntStore;
using RealStore = ValueStore<RealId, Real>;
using FloatStore = CanonicalValueStore<FloatId, llvm::APFloat>;
// Note that this stores string references. The underlying string data must
// be stored somewhere more durable (e.g. a string literal or
// `clang::ASTContext::Idents`).
using IdentifierStore = CanonicalValueStore<IdentifierId, llvm::StringRef>;
using StringLiteralStore =
CanonicalValueStore<StringLiteralValueId, llvm::StringRef>;
+19 -2
View File
@@ -15,6 +15,7 @@
#include "common/check.h"
#include "common/ostream.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/Sequence.h"
#include "llvm/ADT/SmallVector.h"
@@ -263,8 +264,10 @@ class ValueStore
CARBON_DCHECK(static_cast<size_t>(chunk_index) <= chunks_.size(),
"{0} <= {1}", chunk_index, chunks_.size());
if (static_cast<size_t>(chunk_index) == chunks_.size()) {
chunks_.emplace_back();
if (LLVM_UNLIKELY(static_cast<size_t>(chunk_index) == chunks_.size())) {
// Sink all the growth and allocation logic into an out-of-line helper as
// this is inherently a cold-path.
AllocateNextChunk();
}
CARBON_DCHECK(pos == chunks_[chunk_index].size());
@@ -349,6 +352,13 @@ class ValueStore
return EnumerateRange(*this);
}
// Returns the chunks of values in order. Each chunk is a contiguous range of
// values, so bulk operations over all values can iterate over contiguous
// memory rather than looking up each value by its ID.
auto chunks() const [[clang::lifetimebound]] -> auto {
return llvm::ArrayRef<Chunk>(chunks_);
}
auto GetIdTag() const -> IdTagType { return tag_; }
auto GetRawIndex(IdT id) const -> int32_t {
CARBON_DCHECK(id.index >= 0, "{0}", id.index);
@@ -481,7 +491,10 @@ class ValueStore
-> void
requires(std::is_copy_constructible_v<ValueT>);
auto data() const -> const ValueType* { return buf_; }
auto size() const -> int32_t { return num_; }
auto begin() const -> const ValueType* { return buf_; }
auto end() const -> const ValueType* { return buf_ + num_; }
private:
// Verify using an `int32_t` for `num_` is sound.
@@ -520,6 +533,10 @@ class ValueStore
return RawIndexToChunkIndices(GetRawIndex(id));
}
[[clang::noinline, clang::preserve_most]] auto AllocateNextChunk() -> void {
chunks_.emplace_back();
}
// Number of elements added to the store. The number should never exceed what
// fits in an `int32_t`, which is checked in non-optimized builds in Add().
int32_t size_ = 0;
+10 -4
View File
@@ -32,14 +32,20 @@ cc_test(
name = "source_gen_test",
size = "small",
srcs = ["source_gen_test.cpp"],
data = ["//toolchain/install:install_data"],
shard_count = 8,
deps = [
":source_gen_lib",
"//common:all_llvm_targets",
"//common:error",
"//common:raw_string_ostream",
"//common:set",
"//testing/base:capture_std_streams",
"//testing/base:global_exe_path",
"//testing/base:gtest_main",
"//toolchain/base:install_paths_test_helpers",
"//toolchain/driver",
"//toolchain/driver:clang_runner",
"@googletest//:gtest",
"@llvm-project//llvm:Support",
],
@@ -88,10 +94,10 @@ sh_test(
srcs = [":compile_benchmark"],
args = [
"--benchmark_dry_run",
# Dry-run the smallest (256-line) case of every benchmark. Also run just
# the lex benchmark that executes a subprocess to reduce the overhead
# and reduce timeouts. The `$$` is repeated for Bazel escaping of `$`.
"--benchmark_filter=^(BM_CompileApi.*/256|BM_CompileExec.*Phase::Lex.*/256/real_time)$$",
# Dry-run the smallest case of every benchmark. Also run just the lex
# benchmark that executes a subprocess to reduce the overhead and reduce
# timeouts. The `$$` is repeated for Bazel escaping of `$`.
"--benchmark_filter=^(BM_CompileApiFile(DenseDecls|RealisticHeader).*/256|BM_CompileImplFile.*/1024|BM_CompileExec.*Phase::Lex.*/256/real_time)$$",
],
env = cc_env(),
)
+159 -40
View File
@@ -255,6 +255,59 @@ using CompilerFor =
std::conditional_t<M == Mode::InProcess, InProcessCompiler<L>,
SubprocessCompiler<L>>;
// Parameters for the dense declarations pattern, which models an API file:
// classes of mostly function declarations, with a few small inline definitions.
static auto DenseDeclParams() -> SourceGen::DenseDeclParams {
SourceGen::DenseDeclParams params;
params.class_params.inline_function_defs = 1;
params.class_params.inline_getters = 1;
params.class_params.inline_forwarders = 1;
params.free_function_decls_per_class = 2;
return params;
}
// Parameters for the implementation file pattern: classes whose functions and
// methods are all defined out-of-line, with the default definition sizes. Two
// thirds of the functions are methods, and they average two parameters (`0..2`
// from `max_params = 2`, plus the extra parameter added to out-of-line
// definitions), as in the Carbon toolchain's, LLVM's, and Clang's
// implementation files.
static auto ImplFileParams() -> SourceGen::DenseDeclParams {
SourceGen::DenseDeclParams params;
params.class_params = {.public_function_decls = 2,
.public_function_decl_params = {.max_params = 2},
.public_method_decls = 6,
.public_method_decl_params = {.max_params = 2},
.private_function_decls = 2,
.private_function_decl_params = {.max_params = 2},
.private_method_decls = 2,
.private_method_decl_params = {.max_params = 2},
.private_field_decls = 4};
params.define_decls_out_of_line = true;
params.free_function_decls_per_class = 2;
return params;
}
// Parameters for the realistic header pattern, which models a typical API file.
// Its classes define about two thirds of their member functions inline, and
// the mix of inline definitions approximates the inline definitions in the
// Carbon toolchain's, LLVM's, and Clang's headers.
static auto RealisticHeaderParams() -> SourceGen::DenseDeclParams {
SourceGen::DenseDeclParams params;
params.class_params = {.public_function_decls = 0,
.public_method_decls = 2,
.private_function_decls = 0,
.private_method_decls = 0,
.private_field_decls = 3,
.inline_function_defs = 2,
.inline_function_decl_params = {.max_params = 2},
.inline_getters = 2,
.inline_predicates = 1,
.inline_forwarders = 2};
params.free_function_decls_per_class = 1;
return params;
}
// Benchmark on multiple files of the same size but with different source code
// in order to avoid branch prediction perfectly learning a particular file's
// structure and shape, and to get closer to a cache-cold benchmark number which
@@ -265,57 +318,75 @@ using CompilerFor =
// For simplicity, we compute a number of files from the target line count as a
// heuristic, clamped to the `[min_files, max_files]` range provided by the
// compilation harness.
static auto ComputeFileCount(int target_lines, int min_files,
static auto ComputeFileCount(Lang lang, int target_lines, int min_files,
[[maybe_unused]] int max_files) -> int {
if (IsBenchmarkDryRun()) {
// Dry runs only smoke-test that the benchmark runs cleanly, so generate and
// compile just two files. We do more than one to exercise our stability
// checks.
return 2;
}
int file_count = (1024 * 1024) / target_lines;
if (lang == Lang::Cpp) {
// Reduce the number of files with C++ to balance the longer compile times.
file_count /= 2;
}
#ifndef NDEBUG
// Use a smaller number of files in debug builds where compiles are slower,
// capping at the release-mode minimum.
return std::max(1, std::min(min_files, file_count));
file_count = std::min(min_files, file_count);
#else
return std::max(min_files, std::min(max_files, file_count));
file_count = std::min(max_files, file_count);
#endif
return std::max(min_files, file_count);
}
template <Lang L, Phase P, Mode M = Mode::InProcess>
static auto BM_CompileApiFileDenseDecls(benchmark::State& state) -> void {
static auto RunApiFileBenchmark(benchmark::State& state,
const SourceGen::DenseDeclParams& params)
-> void {
using Compiler = CompilerFor<L, M>;
Compiler bench;
CompileHelper carbon_compile_helper;
int target_lines = state.range(0);
int num_files =
ComputeFileCount(target_lines, Compiler::MinFiles, Compiler::MaxFiles);
if constexpr (L == Lang::Cpp) {
// Reduce the number of files with C++ to balance the longer compile times.
num_files /= 2;
}
ComputeFileCount(L, target_lines, Compiler::MinFiles, Compiler::MaxFiles);
llvm::SmallVector<std::string> sources;
sources.reserve(num_files);
double total_bytes = 0.0;
double total_lines = 0.0;
double total_tokens = 0.0;
for (auto _ : llvm::seq(num_files)) {
sources.push_back(bench.gen().GenApiFileDenseDecls(
target_lines, SourceGen::DenseDeclParams{}));
const auto& source = sources.back();
total_bytes += source.size();
total_lines += llvm::count(source, '\n');
if constexpr (L == Lang::Carbon) {
total_tokens += carbon_compile_helper.GetTokenizedBuffer(source).size();
}
sources.push_back(bench.gen().GenApiFileDenseDecls(target_lines, params));
}
ssize_t bytes = sources[0].size();
ssize_t lines = llvm::count(sources[0], '\n');
for (llvm::StringRef source : llvm::ArrayRef(sources).drop_front()) {
// The generator varies each file's content but not its size, which keeps
// results comparable across files and runs.
ssize_t file_bytes = source.size();
ssize_t file_lines = llvm::count(source, '\n');
CARBON_CHECK(file_bytes == bytes,
"Generated file has {0} bytes but expected {1}.", file_bytes,
bytes);
CARBON_CHECK(file_lines == lines,
"Generated file has {0} lines but expected {1}.", file_lines,
lines);
}
state.counters["Bytes"] =
benchmark::Counter(total_bytes / sources.size(),
benchmark::Counter::kIsIterationInvariantRate);
benchmark::Counter(bytes, benchmark::Counter::kIsIterationInvariantRate);
state.counters["Lines"] =
benchmark::Counter(total_lines / sources.size(),
benchmark::Counter::kIsIterationInvariantRate);
benchmark::Counter(lines, benchmark::Counter::kIsIterationInvariantRate);
// TODO: The token count should be stable as well, so we should be able to
// switch it to the pattern above.
if constexpr (L == Lang::Carbon) {
double total_tokens = 0.0;
CompileHelper carbon_compile_helper;
for (llvm::StringRef source : sources) {
total_tokens += carbon_compile_helper.GetTokenizedBuffer(source).size();
}
state.counters["Tokens"] =
benchmark::Counter(total_tokens / sources.size(),
benchmark::Counter::kIsIterationInvariantRate);
@@ -346,6 +417,21 @@ static auto BM_CompileApiFileDenseDecls(benchmark::State& state) -> void {
}
}
template <Lang L, Phase P, Mode M = Mode::InProcess>
static auto BM_CompileApiFileDenseDecls(benchmark::State& state) -> void {
RunApiFileBenchmark<L, P, M>(state, DenseDeclParams());
}
template <Lang L, Phase P, Mode M = Mode::InProcess>
static auto BM_CompileImplFile(benchmark::State& state) -> void {
RunApiFileBenchmark<L, P, M>(state, ImplFileParams());
}
template <Lang L, Phase P, Mode M = Mode::InProcess>
static auto BM_CompileApiFileRealisticHeader(benchmark::State& state) -> void {
RunApiFileBenchmark<L, P, M>(state, RealisticHeaderParams());
}
// A thin wrapper for the subprocess benchmarks: they reuse the shared
// implementation above, but register under their own (terser) name rather than
// spelling out `Mode::Subprocess` at each registration.
@@ -354,44 +440,77 @@ static auto BM_CompileExecApiFileDenseDecls(benchmark::State& state) -> void {
BM_CompileApiFileDenseDecls<L, P, Mode::Subprocess>(state);
}
// Applies the shared range configuration used by every compile benchmark:
// 256-line test cases through 256k-line test cases.
static auto ConfigureCompileBenchmark(benchmark::Benchmark* b) -> void {
// Applies the range configuration of the API file benchmarks (dense
// declarations and realistic header): 256-line test cases through 256k-line
// test cases.
static auto ConfigureApiFileBenchmark(benchmark::Benchmark* b) -> void {
b->RangeMultiplier(4)->Range(256, static_cast<int64_t>(256 * 1024));
}
// The implementation file benchmarks start at 1024 lines instead, because a
// class with all of its functions defined doesn't fit in 256 lines.
static auto ConfigureImplFileBenchmark(benchmark::Benchmark* b) -> void {
b->RangeMultiplier(4)->Range(1024, static_cast<int64_t>(256 * 1024));
}
// In-process benchmarks measure the compiler as a library across each phase of
// compilation. The mode defaults to `Mode::InProcess`.
BENCHMARK(BM_CompileApiFileDenseDecls<Lang::Carbon, Phase::Lex>)
->Apply(ConfigureCompileBenchmark);
->Apply(ConfigureApiFileBenchmark);
BENCHMARK(BM_CompileApiFileDenseDecls<Lang::Carbon, Phase::Parse>)
->Apply(ConfigureCompileBenchmark);
->Apply(ConfigureApiFileBenchmark);
BENCHMARK(BM_CompileApiFileDenseDecls<Lang::Carbon, Phase::Check>)
->Apply(ConfigureCompileBenchmark);
->Apply(ConfigureApiFileBenchmark);
BENCHMARK(BM_CompileApiFileDenseDecls<Lang::Cpp, Phase::Lex>)
->Apply(ConfigureCompileBenchmark);
->Apply(ConfigureApiFileBenchmark);
BENCHMARK(BM_CompileApiFileDenseDecls<Lang::Cpp, Phase::Check>)
->Apply(ConfigureCompileBenchmark);
->Apply(ConfigureApiFileBenchmark);
// Subprocess benchmarks measure the same work end-to-end by executing the
// installed compiler binary, including process startup cost. They use real
// (wall-clock) time because the compilation happens in a child process whose
// CPU time isn't attributed to the benchmark process.
BENCHMARK(BM_CompileExecApiFileDenseDecls<Lang::Carbon, Phase::Lex>)
->Apply(ConfigureCompileBenchmark)
->Apply(ConfigureApiFileBenchmark)
->UseRealTime();
BENCHMARK(BM_CompileExecApiFileDenseDecls<Lang::Carbon, Phase::Parse>)
->Apply(ConfigureCompileBenchmark)
->Apply(ConfigureApiFileBenchmark)
->UseRealTime();
BENCHMARK(BM_CompileExecApiFileDenseDecls<Lang::Carbon, Phase::Check>)
->Apply(ConfigureCompileBenchmark)
->Apply(ConfigureApiFileBenchmark)
->UseRealTime();
BENCHMARK(BM_CompileExecApiFileDenseDecls<Lang::Cpp, Phase::Lex>)
->Apply(ConfigureCompileBenchmark)
->Apply(ConfigureApiFileBenchmark)
->UseRealTime();
BENCHMARK(BM_CompileExecApiFileDenseDecls<Lang::Cpp, Phase::Check>)
->Apply(ConfigureCompileBenchmark)
->Apply(ConfigureApiFileBenchmark)
->UseRealTime();
// In-process benchmarks of the implementation file pattern.
BENCHMARK(BM_CompileImplFile<Lang::Carbon, Phase::Lex>)
->Apply(ConfigureImplFileBenchmark);
BENCHMARK(BM_CompileImplFile<Lang::Carbon, Phase::Parse>)
->Apply(ConfigureImplFileBenchmark);
BENCHMARK(BM_CompileImplFile<Lang::Carbon, Phase::Check>)
->Apply(ConfigureImplFileBenchmark);
BENCHMARK(BM_CompileImplFile<Lang::Cpp, Phase::Lex>)
->Apply(ConfigureImplFileBenchmark);
BENCHMARK(BM_CompileImplFile<Lang::Cpp, Phase::Check>)
->Apply(ConfigureImplFileBenchmark);
// In-process benchmarks of the realistic header pattern.
BENCHMARK(BM_CompileApiFileRealisticHeader<Lang::Carbon, Phase::Lex>)
->Apply(ConfigureApiFileBenchmark);
BENCHMARK(BM_CompileApiFileRealisticHeader<Lang::Carbon, Phase::Parse>)
->Apply(ConfigureApiFileBenchmark);
BENCHMARK(BM_CompileApiFileRealisticHeader<Lang::Carbon, Phase::Check>)
->Apply(ConfigureApiFileBenchmark);
BENCHMARK(BM_CompileApiFileRealisticHeader<Lang::Cpp, Phase::Lex>)
->Apply(ConfigureApiFileBenchmark);
BENCHMARK(BM_CompileApiFileRealisticHeader<Lang::Cpp, Phase::Check>)
->Apply(ConfigureApiFileBenchmark);
} // namespace
} // namespace Carbon::Testing
File diff suppressed because it is too large. Load diff
+230 -18
View File
@@ -69,6 +69,15 @@ class SourceGen {
int max_params = 4;
};
// A definition size in a histogram of definition sizes, with its weight.
// `statements` counts the statements in a plan-based definition beyond the
// three that every such definition has: an accumulator, a statement reading
// the parameters, and the final `return`.
struct DefSizeWeight {
int statements;
int weight;
};
// Parameters used to generate a class in a generated file.
//
// Currently, this uses a fixed number of each kind of declaration, with
@@ -76,9 +85,6 @@ class SourceGen {
// classes with lots of nested declarations.
// TODO: Switch these to distributions based on data.
//
// TODO: Add support for generating definitions and parameters to control
// them.
//
// TODO: Add heuristic for how many functions have return types.
struct ClassParams {
int public_function_decls = 4;
@@ -94,6 +100,46 @@ class SourceGen {
MethodDeclParams private_method_decl_params = {.max_params = 6};
int private_field_decls = 6;
// The number of general public class functions to define inline. Unlike the
// single-expression member idioms below (`inline_getters`,
// `inline_predicates`, and `inline_forwarders`), which have fixed
// signatures coupled to paired fields or private declarations on the class,
// these take arbitrary parameter lists and have multi-statement definitions
// generated from `DefPlan`s (sharing the plan builder with out-of-line
// definitions).
int inline_function_defs = 0;
FunctionDeclParams inline_function_decl_params = {.max_params = 4};
// The histogram of plan sizes for `inline_function_defs`. A file's inline
// function definitions follow it as closely as their number allows.
//
// The default weights are percentages (rounded to sum to 100) of the
// multi-statement function definitions defined in classes in the headers
// included by a sample of the Carbon toolchain's, LLVM's, and Clang's
// implementation files (single-statement inline definitions are modeled by
// `inline_getters`, `inline_predicates`, and `inline_forwarders` below).
// Each size is a bucket's median, less the three statements that every
// plan-based definition has. The 68% of multi-statement inline definitions
// that have two or three statements get `.statements = 0`.
llvm::SmallVector<DefSizeWeight> inline_function_def_sizes = {
{.statements = 0, .weight = 68}, {.statements = 1, .weight = 23},
{.statements = 4, .weight = 5}, {.statements = 8, .weight = 2},
{.statements = 13, .weight = 1}, {.statements = 23, .weight = 1},
};
// The number of public methods to define inline that return a field. Each
// one returns a field of its own, in addition to `private_field_decls`.
int inline_getters = 0;
// The number of public methods to define inline that test a field with the
// field type's predicate template. Each one tests a field of its own.
int inline_predicates = 0;
// The number of public methods to define inline that pass their parameters
// on to a private method declaration with the same signature.
int inline_forwarders = 0;
MethodDeclParams inline_forwarder_params = {.max_params = 4};
};
// Parameters used to select type _uses_, as opposed to definitions.
@@ -121,33 +167,115 @@ class SourceGen {
struct TypeUseParams {
// The weights in the histogram start with a sequence fixed types described
// with a Carbon and C++ string, and their associated weight.
//
// Each type also has a value expression, which a definition uses to produce
// a value of the type, such as a return value. Types without one, like
// pointers, are only used where no value is produced.
//
// Each type also has consumer templates, which a definition uses to read a
// parameter of the type so that no parameter is unused. A template is an
// `i32` expression (`int` in C++) with a `{0}` placeholder for the
// parameter name. Every type needs at least one.
//
// A type can also have a predicate template, a `bool` expression with a
// `{0}` placeholder for a value of the type. Only types with a predicate
// template and a value expression are used for the fields that predicates
// test.
//
// Each type can also have an argument expression, which definitions pass
// for parameters of the type when calling free functions. It defaults to
// the value expression, so only types without a value expression need to
// specify one. It can use the definition's `acc` (an `i32`) and `acc64`
// (an `i64`).
struct FixedTypeWeight {
llvm::StringRef carbon_spelling;
llvm::StringRef cpp_spelling;
int weight;
llvm::StringRef carbon_value;
llvm::StringRef cpp_value;
llvm::SmallVector<llvm::StringRef> carbon_consumers;
llvm::SmallVector<llvm::StringRef> cpp_consumers;
llvm::StringRef carbon_predicate;
llvm::StringRef cpp_predicate;
llvm::StringRef carbon_arg;
llvm::StringRef cpp_arg;
};
llvm::SmallVector<FixedTypeWeight> fixed_type_weights = {
// `FixedTypeWeight` exceeds `SmallVector`'s 256-byte limit for default
// inline storage.
llvm::SmallVector<FixedTypeWeight, 0> fixed_type_weights = {
// Combined weight of 65 for a core set of builtin types.
{.carbon_spelling = "bool", .cpp_spelling = "bool", .weight = 25},
{.carbon_spelling = "i32", .cpp_spelling = "int", .weight = 20},
{.carbon_spelling = "bool",
.cpp_spelling = "bool",
.weight = 25,
.carbon_value = "true",
.cpp_value = "true",
.carbon_consumers = {"(if {0} then 1 else 0)",
"(if {0} then 2 else 3)"},
.cpp_consumers = {"({0} ? 1 : 0)", "({0} ? 2 : 3)"},
.carbon_predicate = "not {0}",
.cpp_predicate = "!{0}"},
{.carbon_spelling = "i32",
.cpp_spelling = "int",
.weight = 20,
.carbon_value = "0",
.cpp_value = "0",
.carbon_consumers = {"{0} + 1", "{0} * 2", "({0} % 7) + 1"},
.cpp_consumers = {"{0} + 1", "{0} * 2", "({0} % 7) + 1"},
.carbon_predicate = "{0} == 0",
.cpp_predicate = "{0} == 0"},
{.carbon_spelling = "i64",
.cpp_spelling = "std::int64_t",
.weight = 10},
{.carbon_spelling = "i32*", .cpp_spelling = "int*", .weight = 5},
.weight = 10,
.carbon_value = "0",
.cpp_value = "0",
.carbon_consumers = {"({0} as i32) - 2", "(({0} + 1) as i32)"},
.cpp_consumers = {"static_cast<int>({0}) - 2",
"static_cast<int>({0} + 1)"},
.carbon_predicate = "{0} > 0",
.cpp_predicate = "{0} > 0"},
{.carbon_spelling = "i32*",
.cpp_spelling = "int*",
.weight = 5,
.carbon_consumers = {"*{0} + 1", "*{0} * 2"},
.cpp_consumers = {"*{0} + 1", "*{0} * 2"},
.carbon_arg = "&acc",
.cpp_arg = "&acc"},
{.carbon_spelling = "i64*",
.cpp_spelling = "std::int64_t*",
.weight = 5},
.weight = 5,
.carbon_consumers = {"(*{0} as i32) - 2", "((*{0} * 2) as i32)"},
.cpp_consumers = {"static_cast<int>(*{0}) - 2",
"static_cast<int>(*{0} * 2)"},
.carbon_arg = "&acc64",
.cpp_arg = "&acc64"},
// A weight of 5 distributed across tuple structures
{.carbon_spelling = "(bool, i64)",
.cpp_spelling = "std::pair<bool, std::int64_t>",
.weight = 2},
.weight = 2,
.carbon_value = "(true, 0)",
.cpp_value = "{true, 0}",
.carbon_consumers = {"(if {0}.0 then 1 else 0)", "({0}.1 as i32) + 1"},
.cpp_consumers = {"({0}.first ? 1 : 0)",
"static_cast<int>({0}.second) + 1"},
.carbon_predicate = "{0}.1 > 0",
.cpp_predicate = "{0}.second > 0"},
{.carbon_spelling = "(i32, i64*)",
.cpp_spelling = "std::pair<int, std::int64_t*>",
.weight = 3},
.weight = 3,
.carbon_consumers = {"{0}.0 + 1", "{0}.0 * 2", "(*{0}.1 as i32) - 1"},
.cpp_consumers = {"{0}.first + 1", "{0}.first * 2",
"static_cast<int>(*{0}.second) - 1"},
.carbon_arg = "(0, &acc64)",
.cpp_arg = "{0, &acc64}"},
};
// Consumer templates for class types, in both languages. These call the
// generated `Checksum` method, which is the same in every class.
llvm::SmallVector<llvm::StringRef> class_consumers = {
"{0}.Checksum()", "{0}.Checksum() + 1", "{0}.Checksum() * 2"};
// The weight for using types declared in the file. These will be randomly
// shuffled references, and when there are more type references than
// declared, include repeated references.
@@ -164,6 +292,34 @@ class SourceGen {
// Parameters used to guide the selection of types for use in declarations.
TypeUseParams type_use_params = {};
// Whether to define each declared function and method out-of-line, after
// every class.
bool define_decls_out_of_line = false;
// The histogram of sizes of out-of-line definitions. A file's definitions
// follow it as closely as their number allows.
//
// The default weights are percentages (rounded to sum to 100, with the
// 0.4% tail bucket rounded up to 1%) of the function definitions in a
// sample of the Carbon toolchain's, LLVM's, and Clang's implementation
// files, in buckets of their statement counts. Each size is a bucket's
// median, less the three statements that every generated definition has.
// The 43% of real definitions that have at most three statements get no
// more statements.
llvm::SmallVector<DefSizeWeight> out_of_line_def_sizes = {
{.statements = 0, .weight = 43}, {.statements = 2, .weight = 18},
{.statements = 5, .weight = 11}, {.statements = 8, .weight = 9},
{.statements = 13, .weight = 7}, {.statements = 20, .weight = 5},
{.statements = 29, .weight = 3}, {.statements = 43, .weight = 2},
{.statements = 67, .weight = 1}, {.statements = 125, .weight = 1},
};
// The number of free function declarations at the start of the file, per
// class. Definitions call these. They precede the classes, so their
// signatures only use fixed types.
int free_function_decls_per_class = 0;
FunctionDeclParams free_function_decl_params = {.max_params = 4};
};
// Access a global instance of this type to generate Carbon code for
@@ -276,14 +432,70 @@ class SourceGen {
auto GetShuffledInts(int number, int min, int max) -> llvm::SmallVector<int>;
auto GenerateFunctionDecl(
llvm::StringRef name, bool is_private, bool is_method, int param_count,
llvm::StringRef indent,
llvm::SmallVectorImpl<llvm::StringRef>& param_names,
llvm::function_ref<auto()->llvm::StringRef> get_type_name,
llvm::raw_ostream& os) -> void;
auto GenerateClassDef(const ClassParams& params, ClassGenState& state,
// A parameter or a field: its name, and the spelling of its type.
struct TypedName {
llvm::StringRef name;
llvm::StringRef type;
};
// A function's signature, and the index of its definition plan.
struct FunctionSig {
llvm::StringRef name;
bool is_method;
llvm::SmallVector<TypedName> params;
llvm::SmallVector<llvm::StringRef> param_consumers;
llvm::StringRef return_type;
int def_plan;
};
// A declaration to define out-of-line, after every class: the name of its
// class, and its signature.
struct OutOfLineDef {
llvm::StringRef class_name;
FunctionSig sig;
};
auto EmitParams(bool is_method, llvm::ArrayRef<TypedName> params,
llvm::StringRef indent, llvm::raw_ostream& os) -> void;
auto 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;
auto GenerateFunctionDecl(ClassGenState& state, llvm::StringRef name,
bool is_private, bool is_method, int param_count,
llvm::StringRef indent, llvm::raw_ostream& os,
FunctionSig* captured = nullptr) -> void;
auto GenerateFreeFunctionDecls(ClassGenState& state, llvm::raw_ostream& os)
-> void;
auto 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;
auto GenerateFunctionDef(ClassGenState& state, const FunctionSig& sig,
llvm::StringRef indent, llvm::raw_ostream& os)
-> void;
auto GenerateOutOfLineDef(ClassGenState& state, llvm::StringRef class_name,
const FunctionSig& sig, llvm::raw_ostream& os)
-> void;
auto GenerateInlineFunctionDef(ClassGenState& state, llvm::StringRef name,
int param_count, llvm::StringRef indent,
llvm::raw_ostream& os) -> void;
auto GenerateGetter(llvm::StringRef name, TypedName field,
llvm::raw_ostream& os) -> void;
auto GeneratePredicate(llvm::StringRef name, TypedName field,
llvm::StringRef predicate, llvm::raw_ostream& os)
-> void;
auto GenerateForwarder(llvm::StringRef name, llvm::ArrayRef<TypedName> params,
llvm::StringRef return_type, llvm::raw_ostream& os)
-> void;
auto GenerateMakeFunction(ClassGenState& state, llvm::StringRef class_name,
llvm::ArrayRef<TypedName> fields,
llvm::raw_ostream& os) -> void;
auto GenerateChecksumFunction(llvm::raw_ostream& os) -> void;
auto GenerateClassDef(const ClassParams& params, ClassGenState& state,
llvm::raw_ostream& os,
llvm::SmallVectorImpl<OutOfLineDef>& out_of_line_defs)
-> void;
absl::BitGen rng_;
llvm::BumpPtrAllocator storage_;
+520 -15
View File
@@ -10,11 +10,16 @@
#include <optional>
#include <string>
#include "common/error.h"
#include "common/raw_string_ostream.h"
#include "common/set.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/VirtualFileSystem.h"
#include "testing/base/capture_std_streams.h"
#include "testing/base/global_exe_path.h"
#include "toolchain/base/install_paths_test_helpers.h"
#include "toolchain/driver/clang_runner.h"
#include "toolchain/driver/driver.h"
namespace Carbon::Testing {
@@ -23,13 +28,20 @@ namespace {
using ::testing::AllOf;
using ::testing::ContainerEq;
using ::testing::Contains;
using ::testing::ContainsRegex;
using ::testing::Each;
using ::testing::Eq;
using ::testing::Ge;
using ::testing::Gt;
using ::testing::HasSubstr;
using ::testing::Le;
using ::testing::MatchesRegex;
using ::testing::Not;
using ::testing::SizeIs;
using ::testing::StrEq;
using ::testing::TestParamInfo;
using ::testing::TestWithParam;
using ::testing::Values;
// Tiny helper to sum the sizes of a range of ranges. Uses a template to avoid
// hard coding any specific types for the two ranges.
@@ -75,7 +87,7 @@ TEST(SourceGenTest, Identifiers) {
// Check that repeated calls are different in interesting ways, but have the
// exact same total bytes.
ssize_t idents_size_sum = SumSizes(idents);
for ([[maybe_unused]] auto _ : llvm::seq(10)) {
for (auto _ : llvm::seq(10)) {
auto idents2 = gen.GetShuffledIdentifiers(1000);
EXPECT_THAT(idents2, SizeIs(1000));
// Should be (at least) a different shuffle of identifiers.
@@ -229,23 +241,45 @@ TEST(SourceGenTest, UniqueIdentifiers) {
}
}
// Check that the source code doesn't have compiler errors.
auto TestCompile(llvm::StringRef source) -> bool {
// Compiles `source` and returns whether it compiled with no diagnostics at all.
// Generated code should be warning-free: warnings would add diagnostic
// emission to the compile benchmarks.
auto TestCompile(SourceGen::Language language, llvm::StringRef source) -> bool {
llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> fs =
new llvm::vfs::InMemoryFileSystem;
InstallPaths installation(
InstallPaths::MakeForBazelRunfiles(Testing::GetExePath()));
Driver driver(fs, &installation, /*input_stream=*/nullptr, &llvm::outs(),
&llvm::errs());
AddPreludeFilesToVfs(installation, fs);
if (language == SourceGen::Language::Carbon) {
RawStringOstream diagnostics;
Driver driver(fs, &installation, /*input_stream=*/nullptr, &llvm::outs(),
&diagnostics);
AddPreludeFilesToVfs(installation, fs);
fs->addFile("test.carbon", /*ModificationTime=*/0,
llvm::MemoryBuffer::getMemBuffer(source));
bool success = driver
.RunCommand({"compile", "--phase=check",
"--no-include-carbon-core", "test.carbon"})
.success;
std::string output = diagnostics.TakeStr();
EXPECT_THAT(output, StrEq(""));
return success && output.empty();
}
fs->addFile("test.carbon", /*ModificationTime=*/0,
// Clang reads the system headers from the real filesystem.
llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> overlay_fs =
new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem());
overlay_fs->pushOverlay(fs);
fs->addFile("test.cpp", /*ModificationTime=*/0,
llvm::MemoryBuffer::getMemBuffer(source));
return driver
.RunCommand({"compile", "--phase=check", "--no-include-carbon-core",
"test.carbon"})
.success;
ClangRunner runner(&installation, overlay_fs);
std::string out;
std::string err;
ErrorOr<bool> result = CallWithCapturedOutput(out, err, [&] {
return runner.RunWithNoRuntimes({"-fsyntax-only", "test.cpp"});
});
EXPECT_THAT(err, StrEq(""));
return result.ok() && *result && err.empty();
}
TEST(SourceGenTest, GenApiFileDenseDeclsTest) {
@@ -256,8 +290,7 @@ TEST(SourceGenTest, GenApiFileDenseDeclsTest) {
// Should be within 1% of the requested line count.
EXPECT_THAT(source, Contains('\n').Times(AllOf(Ge(950), Le(1050))));
// Make sure we generated valid Carbon code.
EXPECT_TRUE(TestCompile(source));
EXPECT_TRUE(TestCompile(SourceGen::Language::Carbon, source));
}
TEST(SourceGenTest, GenApiFileDenseDeclsCppTest) {
@@ -270,8 +303,480 @@ TEST(SourceGenTest, GenApiFileDenseDeclsCppTest) {
// Should be within 10% of the requested line count.
EXPECT_THAT(source, Contains('\n').Times(AllOf(Ge(900), Le(1100))));
// TODO: When the driver supports compiling C++ code as easily as Carbon, we
// should test that the generated C++ code is valid.
EXPECT_TRUE(TestCompile(SourceGen::Language::Cpp, source));
}
static auto CountLines(llvm::StringRef source) -> ssize_t {
return llvm::count(source, '\n');
}
// Matches a `SourceGen::Language` for which
// `GenApiFileDenseDecls(target_lines, params)` generates files with the same
// byte and line counts using each of `num_seeds` independently seeded
// generators. The files must not all have the same content, which would make
// that match vacuous.
// NOLINTNEXTLINE(modernize-use-trailing-return-type): Macro based function.
MATCHER_P3(GeneratesSeedIndependentSize, target_lines, params, num_seeds,
"generates files of the same size with every seed") {
SourceGen::Language language = arg;
std::string first =
SourceGen(language).GenApiFileDenseDecls(target_lines, params);
bool any_different = false;
for (int i : llvm::seq(1, num_seeds)) {
std::string source =
SourceGen(language).GenApiFileDenseDecls(target_lines, params);
if (source.size() != first.size() ||
CountLines(source) != CountLines(first)) {
*result_listener << "where seed iteration " << i << " generated "
<< source.size() << " bytes and " << CountLines(source)
<< " lines, but seed iteration 0 generated "
<< first.size() << " bytes and " << CountLines(first)
<< " lines";
return false;
}
any_different = any_different || source != first;
}
if (!any_different) {
*result_listener << "where all files have the same content";
return false;
}
return true;
}
// Each test generates files with one set of parameters, and checks that they
// compile cleanly and have the same size for any seed, which benchmarks need to
// be comparable. Each test runs for both languages.
using GenApiFileDenseDeclsParamsTest = TestWithParam<SourceGen::Language>;
INSTANTIATE_TEST_SUITE_P(
Languages, GenApiFileDenseDeclsParamsTest,
Values(SourceGen::Language::Carbon, SourceGen::Language::Cpp),
[](const TestParamInfo<SourceGen::Language>& info) -> std::string {
return info.param == SourceGen::Language::Carbon ? "Carbon" : "Cpp";
});
// Barely enough lines for one class.
TEST_P(GenApiFileDenseDeclsParamsTest, DefaultParamsAt200Lines) {
SourceGen::DenseDeclParams params;
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(200, params, 16));
EXPECT_TRUE(TestCompile(
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(200, params)));
}
TEST_P(GenApiFileDenseDeclsParamsTest, DefaultParamsAt1000Lines) {
SourceGen::DenseDeclParams params;
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(1000, params, 16));
EXPECT_TRUE(TestCompile(
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(1000, params)));
}
TEST_P(GenApiFileDenseDeclsParamsTest, DefaultParamsAt5000Lines) {
SourceGen::DenseDeclParams params;
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(5000, params, 16));
EXPECT_TRUE(TestCompile(
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(5000, params)));
}
TEST_P(GenApiFileDenseDeclsParamsTest, DefaultParamsAt20000Lines) {
SourceGen::DenseDeclParams params;
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(20000, params, 16));
EXPECT_TRUE(TestCompile(
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(20000, params)));
}
// Function declarations only: no methods and no fields.
TEST_P(GenApiFileDenseDeclsParamsTest, OnlyFunctions) {
SourceGen::DenseDeclParams params = {
.class_params = {.public_function_decls = 20,
.public_method_decls = 0,
.private_function_decls = 0,
.private_method_decls = 0,
.private_field_decls = 0}};
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(5000, params, 12));
EXPECT_TRUE(TestCompile(
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(5000, params)));
}
// Large parameter counts, which wrap onto several lines.
TEST_P(GenApiFileDenseDeclsParamsTest, WrappedParamLists) {
SourceGen::DenseDeclParams params = {
.class_params = {.public_function_decls = 2,
.public_function_decl_params = {.max_params = 16},
.public_method_decls = 4,
.public_method_decl_params = {.max_params = 16},
.private_function_decls = 0,
.private_method_decls = 0,
.private_field_decls = 0}};
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(5000, params, 12));
EXPECT_TRUE(TestCompile(
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(5000, params)));
}
// The default shape scaled up 2x.
TEST_P(GenApiFileDenseDeclsParamsTest, DoubledDefaultShape) {
SourceGen::DenseDeclParams params = {
.class_params = {.public_function_decls = 8,
.public_method_decls = 20,
.private_function_decls = 4,
.private_method_decls = 16,
.private_field_decls = 12}};
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(5000, params, 12));
EXPECT_TRUE(TestCompile(
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(5000, params)));
}
// A class's fields can't reference it or any later class, so field-heavy
// classes leave few type uses for references to a class. Whether the valid type
// names run out depends on the shuffle, so these tests use many seeds.
TEST_P(GenApiFileDenseDeclsParamsTest, ManyFieldsAndTwoFunctions) {
SourceGen::DenseDeclParams params = {
.class_params = {.public_function_decls = 1,
.public_method_decls = 1,
.private_function_decls = 0,
.private_method_decls = 0,
.private_field_decls = 30}};
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(3000, params, 32));
EXPECT_TRUE(TestCompile(
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(3000, params)));
}
TEST_P(GenApiFileDenseDeclsParamsTest, ManyFieldsAndOneMethod) {
SourceGen::DenseDeclParams params = {
.class_params = {.public_function_decls = 0,
.public_method_decls = 1,
.private_function_decls = 0,
.private_method_decls = 0,
.private_field_decls = 50}};
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(3000, params, 32));
EXPECT_TRUE(TestCompile(
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(3000, params)));
}
// Classes with only fields (no functions or methods). Because fields have their
// own type pool, fields can still reference earlier classes.
TEST_P(GenApiFileDenseDeclsParamsTest, OnlyFields) {
SourceGen::DenseDeclParams params = {
.class_params = {.public_function_decls = 0,
.public_method_decls = 0,
.private_function_decls = 0,
.private_method_decls = 0,
.private_field_decls = 16}};
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(3000, params, 32));
EXPECT_TRUE(TestCompile(
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(3000, params)));
}
// Definitions read class-typed parameters through `Checksum`, and return class
// values through `Make`.
static auto InlineDefsParams() -> SourceGen::DenseDeclParams {
return {.class_params = {.inline_function_defs = 3,
.inline_getters = 2,
.inline_predicates = 2,
.inline_forwarders = 2}};
}
TEST_P(GenApiFileDenseDeclsParamsTest, InlineDefs) {
SourceGen::DenseDeclParams params = InlineDefsParams();
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(2000, params, 16));
EXPECT_TRUE(TestCompile(
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(2000, params)));
}
TEST(SourceGenTest, GenApiFileDenseDeclsInlineDefs) {
SourceGen gen;
std::string source = gen.GenApiFileDenseDecls(2000, InlineDefsParams());
EXPECT_THAT(source, HasSubstr(".Checksum()"));
EXPECT_THAT(source, HasSubstr(".Make()"));
EXPECT_THAT(source, HasSubstr("Impl("));
}
// C++ initializes a class with a default constructor in place of `Make`, which
// leaves the fields private.
TEST(SourceGenTest, GenApiFileDenseDeclsInlineDefsCpp) {
SourceGen gen(SourceGen::Language::Cpp);
std::string source = gen.GenApiFileDenseDecls(2000, InlineDefsParams());
EXPECT_THAT(source, HasSubstr("()\n : tag(0)"));
EXPECT_THAT(source, HasSubstr("\n private:\n int tag;\n"));
}
// Many large inline definitions and few other declarations. Locals and
// parameters draw from the same identifiers of each length, and C++ rejects a
// local that has the same name as a parameter, so compile several seeds.
TEST_P(GenApiFileDenseDeclsParamsTest, LargeInlineDefs) {
SourceGen::DenseDeclParams params = {
.class_params = {
.public_function_decls = 1,
.public_method_decls = 1,
.private_function_decls = 0,
.private_method_decls = 0,
.private_field_decls = 4,
.inline_function_defs = 8,
.inline_function_def_sizes = {{.statements = 20, .weight = 1},
{.statements = 60, .weight = 1}}}};
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(3000, params, 24));
for (int _ : llvm::seq(4)) {
EXPECT_TRUE(TestCompile(
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(3000, params)));
}
}
// Many fields, which `Make` initializes, often with an earlier class's
// `Make`.
TEST_P(GenApiFileDenseDeclsParamsTest, ManyFieldsToMake) {
SourceGen::DenseDeclParams params = {
.class_params = {.public_function_decls = 1,
.public_method_decls = 1,
.private_function_decls = 0,
.private_method_decls = 0,
.private_field_decls = 24,
.inline_function_defs = 2}};
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(3000, params, 24));
EXPECT_TRUE(TestCompile(
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(3000, params)));
}
// Many getters and predicates, and no other fields.
TEST_P(GenApiFileDenseDeclsParamsTest, ManyGettersAndPredicates) {
SourceGen::DenseDeclParams params = {
.class_params = {.private_field_decls = 0,
.inline_getters = 8,
.inline_predicates = 8}};
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(3000, params, 24));
EXPECT_TRUE(TestCompile(
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(3000, params)));
}
// Many forwarders with many parameters, which can have class types.
TEST_P(GenApiFileDenseDeclsParamsTest, ManyForwarders) {
SourceGen::DenseDeclParams params = {
.class_params = {.inline_forwarders = 8,
.inline_forwarder_params = {.max_params = 8}}};
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(3000, params, 24));
EXPECT_TRUE(TestCompile(
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(3000, params)));
}
// Every declared function and method has an out-of-line definition, using the
// default size distribution (from 0-statement minimal definitions through
// deeply nested definitions).
TEST_P(GenApiFileDenseDeclsParamsTest, OutOfLineDefs) {
SourceGen::DenseDeclParams params = {
.class_params = {.inline_function_defs = 2},
.define_decls_out_of_line = true};
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(3000, params, 16));
EXPECT_TRUE(TestCompile(
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(3000, params)));
}
// Large out-of-line definitions, which nest deeply. A definition's block
// arrangement and which locals its statements read depend on the seed, so
// compile several seeds.
TEST_P(GenApiFileDenseDeclsParamsTest, LargeOutOfLineDefs) {
SourceGen::DenseDeclParams params = {
.class_params = {.public_function_decls = 1,
.public_method_decls = 2,
.private_function_decls = 0,
.private_method_decls = 1,
.private_field_decls = 2},
.define_decls_out_of_line = true,
.out_of_line_def_sizes = {{.statements = 40, .weight = 1},
{.statements = 150, .weight = 1}}};
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(3000, params, 24));
for (int _ : llvm::seq(4)) {
EXPECT_TRUE(TestCompile(
GetParam(), SourceGen(GetParam()).GenApiFileDenseDecls(3000, params)));
}
}
// Plans declare class-typed locals (using both `Self` and other classes in both
// inline and out-of-line definitions) and destructure tuples.
TEST(SourceGenTest, GenApiFileDenseDeclsPlanDeclarations) {
SourceGen::DenseDeclParams params = {
.class_params = {.public_function_decls = 1,
.public_method_decls = 2,
.private_function_decls = 0,
.private_method_decls = 1,
.private_field_decls = 2,
.inline_function_defs = 2,
.inline_function_def_sizes = {{.statements = 40,
.weight = 1}}},
.define_decls_out_of_line = true,
.out_of_line_def_sizes = {{.statements = 60, .weight = 1}}};
SourceGen carbon_gen;
std::string source = carbon_gen.GenApiFileDenseDecls(3000, params);
auto [carbon_inline, carbon_out_of_line] =
llvm::StringRef(source).split("\nfn ");
for (llvm::StringRef section : {carbon_inline, carbon_out_of_line}) {
EXPECT_THAT(section, HasSubstr(": Self = Make();"));
EXPECT_THAT(section, ContainsRegex("var [A-Za-z0-9_]+: [A-Za-z0-9_]{5,} = "
"[A-Za-z0-9_]+\\.Make\\(\\);"));
}
EXPECT_THAT(source, ContainsRegex("(let|var) \\([A-Za-z0-9_]+: "));
SourceGen cpp_gen(SourceGen::Language::Cpp);
source = cpp_gen.GenApiFileDenseDecls(3000, params);
auto [cpp_inline, cpp_out_of_line] = llvm::StringRef(source).split("\nauto ");
for (llvm::StringRef section : {cpp_inline, cpp_out_of_line}) {
EXPECT_THAT(section, HasSubstr(" = Self();"));
EXPECT_THAT(section, ContainsRegex("[A-Za-z0-9_]{5,} [A-Za-z0-9_]+ = "
"[A-Za-z0-9_]+\\(\\);"));
}
EXPECT_THAT(source, HasSubstr("auto ["));
}
// Definitions call free functions, passing `&acc` and `&acc64` for pointer
// parameters and destructuring tuple-returning calls.
TEST_P(GenApiFileDenseDeclsParamsTest, FreeFunctionCalls) {
SourceGen::DenseDeclParams params = {
.class_params = {.inline_function_defs = 2},
.define_decls_out_of_line = true,
.free_function_decls_per_class = 2};
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(3000, params, 16));
std::string source = SourceGen(GetParam()).GenApiFileDenseDecls(3000, params);
EXPECT_TRUE(TestCompile(GetParam(), source));
EXPECT_THAT(source, HasSubstr("&acc"));
EXPECT_THAT(source, HasSubstr("&acc64"));
}
// When no free function returns a tuple in `CallDestructureForms`,
// `DestructureCall` steps fall back to literal destructuring while keeping file
// sizes seed-independent.
TEST_P(GenApiFileDenseDeclsParamsTest, FreeFunctionCallsWithoutTupleReturns) {
SourceGen::DenseDeclParams params = {
.class_params = {.public_function_decls = 1,
.public_method_decls = 1,
.private_function_decls = 0,
.private_method_decls = 0,
.private_field_decls = 2},
.define_decls_out_of_line = true,
.out_of_line_def_sizes = {{.statements = 60, .weight = 1}},
.free_function_decls_per_class = 2};
// Drop the tuple entries at the end of `fixed_type_weights` so no free
// function returns a destructurable tuple.
llvm::erase_if(params.type_use_params.fixed_type_weights,
[](const SourceGen::TypeUseParams::FixedTypeWeight& fw) {
return fw.carbon_spelling.starts_with("(");
});
EXPECT_THAT(GetParam(), GeneratesSeedIndependentSize(3000, params, 16));
std::string source = SourceGen(GetParam()).GenApiFileDenseDecls(3000, params);
EXPECT_TRUE(TestCompile(GetParam(), source));
EXPECT_THAT(source, HasSubstr("&acc64"));
if (GetParam() == SourceGen::Language::Carbon) {
EXPECT_THAT(source,
ContainsRegex("(let|var) \\([A-Za-z0-9_]+: [^)]+\\) = \\("));
EXPECT_THAT(source, Not(ContainsRegex("let \\([^)]+\\) = [A-Za-z]")));
} else {
EXPECT_THAT(source, HasSubstr("] = std::pair{"));
EXPECT_THAT(
source,
Not(ContainsRegex("auto \\[[A-Za-z0-9_, ]+\\] = [A-Za-z0-9_]+\\(")));
}
}
TEST(SourceGenTest, GenApiFileDenseDeclsFreeFunctionCalls) {
SourceGen::DenseDeclParams params = {
.class_params = {.public_function_decls = 1,
.public_method_decls = 1,
.private_function_decls = 0,
.private_method_decls = 0,
.private_field_decls = 2},
.define_decls_out_of_line = true,
.out_of_line_def_sizes = {{.statements = 60, .weight = 1}},
.free_function_decls_per_class = 4};
auto carbon_destructure_call_regex = [](llvm::StringRef first_type,
llvm::StringRef second_type) {
return llvm::formatv(
"let \\([A-Za-z0-9_]+: {0}, [A-Za-z0-9_]+: {1}\\) = "
"[A-Za-z0-9_]{{5,}\\(",
first_type, second_type)
.str();
};
SourceGen carbon_gen;
std::string source = carbon_gen.GenApiFileDenseDecls(3000, params);
auto [carbon_free_decls, carbon_classes_and_defs] =
llvm::StringRef(source).split("\nclass ");
EXPECT_THAT(carbon_free_decls, ContainsRegex("\nfn [A-Za-z0-9_]{5,}\\("));
EXPECT_THAT(carbon_classes_and_defs,
ContainsRegex(carbon_destructure_call_regex("bool", "i64")));
EXPECT_THAT(carbon_classes_and_defs,
ContainsRegex(carbon_destructure_call_regex("i32", "i64\\*")));
SourceGen cpp_gen(SourceGen::Language::Cpp);
source = cpp_gen.GenApiFileDenseDecls(3000, params);
auto [cpp_free_decls, cpp_classes_and_defs] =
llvm::StringRef(source).split("\nclass ");
EXPECT_THAT(cpp_free_decls, ContainsRegex("\nauto [A-Za-z0-9_]{5,}\\("));
EXPECT_THAT(cpp_classes_and_defs,
ContainsRegex("const auto \\[[A-Za-z0-9_]+, [A-Za-z0-9_]+\\] = "
"[A-Za-z0-9_]{5,}\\("));
}
// The line estimates have to track the emitted lines closely, or files miss
// their target size. The target is large so that rounding to a whole number of
// classes is small next to the tolerance. C++ gets a larger tolerance for its
// access specifier lines, which the estimates don't count.
TEST(SourceGenTest, GenApiFileDenseDeclsLineTargetAccuracy) {
struct NamedParams {
llvm::StringLiteral name;
SourceGen::DenseDeclParams params;
};
llvm::SmallVector<NamedParams, 0> param_set;
param_set.push_back({.name = "dense_decls",
.params = {.class_params = {.inline_function_defs = 1,
.inline_getters = 1,
.inline_predicates = 1,
.inline_forwarders = 1},
.free_function_decls_per_class = 2}});
// The implementation file shape.
param_set.push_back(
{.name = "impl_file",
.params = {
.class_params = {.public_function_decls = 2,
.public_function_decl_params = {.max_params = 2},
.public_method_decls = 6,
.public_method_decl_params = {.max_params = 2},
.private_function_decls = 2,
.private_function_decl_params = {.max_params = 2},
.private_method_decls = 2,
.private_method_decl_params = {.max_params = 2},
.private_field_decls = 4},
.define_decls_out_of_line = true,
.free_function_decls_per_class = 2}});
// The realistic header shape.
param_set.push_back(
{.name = "realistic_header",
.params = {
.class_params = {.public_function_decls = 0,
.public_method_decls = 2,
.private_function_decls = 0,
.private_method_decls = 0,
.private_field_decls = 3,
.inline_function_defs = 2,
.inline_function_decl_params = {.max_params = 2},
.inline_getters = 2,
.inline_predicates = 1,
.inline_forwarders = 2},
.free_function_decls_per_class = 1}});
constexpr int TargetLines = 20000;
for (const auto& [name, params] : param_set) {
for (SourceGen::Language language :
{SourceGen::Language::Carbon, SourceGen::Language::Cpp}) {
SourceGen gen(language);
std::string source = gen.GenApiFileDenseDecls(TargetLines, params);
ssize_t lines = CountLines(source);
if (language == SourceGen::Language::Carbon) {
// Within 2% of the requested line count.
EXPECT_THAT(lines, AllOf(Ge(19600), Le(20400))) << name;
} else {
// Within 10% of the requested line count.
EXPECT_THAT(lines, AllOf(Ge(18000), Le(22000))) << name;
}
}
}
}
} // namespace
+8
View File
@@ -48,6 +48,7 @@ cc_library(
"function.cpp",
"generic.cpp",
"global_init.cpp",
"identified_facet_type.cpp",
"impl.cpp",
"impl_lookup.cpp",
"impl_validation.cpp",
@@ -72,6 +73,7 @@ cc_library(
"require_impls_stack.cpp",
"return.cpp",
"scope_stack.cpp",
"struct.cpp",
"subst.cpp",
"thunk.cpp",
"type.cpp",
@@ -116,6 +118,7 @@ cc_library(
"function.h",
"generic.h",
"global_init.h",
"identified_facet_type.h",
"impl.h",
"impl_lookup.h",
"impl_validation.h",
@@ -145,6 +148,7 @@ cc_library(
"return.h",
"scope_index.h",
"scope_stack.h",
"struct.h",
"subst.h",
"thunk.h",
"type.h",
@@ -157,11 +161,13 @@ cc_library(
":node_stack",
"//common:array_stack",
"//common:check",
"//common:checked_save_and_restore",
"//common:concepts",
"//common:emplace_by_calling",
"//common:enum_mask_base",
"//common:find",
"//common:growing_range",
"//common:increment_scope",
"//common:map",
"//common:move_only",
"//common:ostream",
@@ -186,6 +192,7 @@ cc_library(
"//toolchain/sem_ir:absolute_node_ref",
"//toolchain/sem_ir:clang_decl",
"//toolchain/sem_ir:cpp_file",
"//toolchain/sem_ir:diagnostic_loc_converter",
"//toolchain/sem_ir:expr_info",
"//toolchain/sem_ir:file",
"//toolchain/sem_ir:formatter",
@@ -259,6 +266,7 @@ cc_library(
"//toolchain/base:timings",
"//toolchain/diagnostics:emitter",
"//toolchain/diagnostics:format_providers",
"//toolchain/diagnostics:renderer",
"//toolchain/lex:token_index",
"//toolchain/lex:token_kind",
"//toolchain/lex:tokenized_buffer",
+38 -19
View File
@@ -67,6 +67,10 @@ static auto OperandDependenceInSpecific(Context& context,
SemIR::SpecificId specific_id,
SemIR::MetaInstId inst_id)
-> SemIR::ConstantDependence {
if (!inst_id.has_value()) {
return SemIR::ConstantDependence::None;
}
// A meta-instruction operand makes the instruction dependent if its type or
// constant value is dependent in this specific.
return std::max(
@@ -242,6 +246,10 @@ static auto RefineTypedOperand(Context& /*context*/, SemIR::LocId /*loc_id*/,
static auto RefineTypedOperand(Context& context, SemIR::LocId /*loc_id*/,
SemIR::MetaInstId inst_id) -> SemIR::MetaInstId {
if (!inst_id.has_value()) {
return SemIR::InstId::None;
}
// TODO: Can we delete this check?
if (context.insts().Is<SemIR::SpliceInst>(inst_id)) {
// The argument will evaluate to the spliced instruction, which is already
@@ -474,6 +482,12 @@ auto Internal::BeginPerformDelayedAction(Context& context) -> void {
// this changes, we should push a region too.
context.inst_block_stack().Push();
context.pattern_block_stack().Push();
// The action is being performed within a specific, so the instructions it
// creates belong to that specific, not to whatever generic we might be in the
// middle of checking. Track them in a separate generic region, which
// `EndPerformDelayedAction` discards, leaving them unattached.
StartGenericDecl(context);
}
auto Internal::EndPerformDelayedAction(Context& context,
@@ -487,27 +501,32 @@ auto Internal::EndPerformDelayedAction(Context& context,
(pattern_contents == llvm::ArrayRef(result_id) && contents.empty())) {
context.inst_block_stack().PopAndDiscard();
context.pattern_block_stack().PopAndDiscard();
return result_id;
} else {
// Otherwise, create a splice_block to represent the sequence of
// instructions created by the action.
auto block_id = SemIR::InstBlockId::None;
if (pattern_contents.empty()) {
block_id = context.inst_block_stack().Pop();
context.pattern_block_stack().PopAndDiscard();
} else {
// TODO: pattern insts can depend on non-pattern insts, so we'll probably
// eventually need to support actions that produce both.
CARBON_CHECK(contents.empty());
block_id = context.pattern_block_stack().Pop();
context.inst_block_stack().PopAndDiscard();
}
auto result = context.insts().GetWithLocId(result_id);
result_id =
AddInstInNoBlock(context, result.loc_id,
SemIR::SpliceBlock{.type_id = result.inst.type_id(),
.block_id = block_id,
.result_id = result_id});
}
// Otherwise, create a splice_block to represent the sequence of instructions
// created by the action.
auto block_id = SemIR::InstBlockId::None;
if (pattern_contents.empty()) {
block_id = context.inst_block_stack().Pop();
context.pattern_block_stack().PopAndDiscard();
} else {
// TODO: pattern insts can depend on non-pattern insts, so we'll probably
// eventually need to support actions that produce both.
CARBON_CHECK(contents.empty());
block_id = context.pattern_block_stack().Pop();
context.inst_block_stack().PopAndDiscard();
}
auto result = context.insts().GetWithLocId(result_id);
return AddInstInNoBlock(context, result.loc_id,
SemIR::SpliceBlock{.type_id = result.inst.type_id(),
.block_id = block_id,
.result_id = result_id});
// Discard the generic region pushed by `BeginPerformDelayedAction`. This is
// done last so that the `splice_block` above is also left unattached.
DiscardGenericDecl(context);
return result_id;
}
} // namespace Carbon::Check
+57 -31
View File
@@ -83,16 +83,17 @@ static auto ResolveCalleeInCall(Context& context, SemIR::LocId loc_id,
" expecting {2} argument{2:s}",
Diagnostics::IntAsSelect, Diagnostics::IntAsSelect,
Diagnostics::IntAsSelect);
CARBON_DIAGNOSTIC(InCallToEntity, Note,
"calling {0:=0:function|=1:generic class|=2:generic "
"interface|=3:generic constraint}"
" declared here",
Diagnostics::IntAsSelect);
CARBON_DIAGNOSTIC_LABEL(
InCallToEntity, Info,
"calling {0:=0:function|=1:generic class|=2:generic "
"interface|=3:generic constraint}"
" declared here",
Diagnostics::IntAsSelect);
context.emitter()
.Build(loc_id, CallArgCountMismatch, arg_ids.size(),
static_cast<int>(entity_kind_for_diagnostic), expected_args_size)
.Note(entity.latest_decl_id(), InCallToEntity,
static_cast<int>(entity_kind_for_diagnostic))
.Attach(entity.latest_decl_id(), InCallToEntity,
static_cast<int>(entity_kind_for_diagnostic))
.Emit();
return std::nullopt;
}
@@ -194,23 +195,21 @@ static auto BuildCalleeSpecificFunction(
// This is an associated function in an interface; the callee is the
// specific function in the impl that corresponds to the specific function
// we deduced.
callee_id =
GetOrAddInst(context, SemIR::LocId(generic_callee_id),
SemIR::SpecificImplFunction{
.type_id = GetSingletonType(
context, SemIR::SpecificFunctionType::TypeInstId),
.callee_id = generic_callee_id,
.specific_id = callee_specific_id});
callee_id = GetOrAddInst<SemIR::SpecificImplFunction>(
context, SemIR::LocId(generic_callee_id),
{.type_id =
GetSingletonType(context, SemIR::SpecificFunctionType::TypeInstId),
.callee_id = generic_callee_id,
.specific_id = callee_specific_id});
} else {
// This is a regular generic function. The callee is the specific function
// we deduced.
callee_id =
GetOrAddInst(context, SemIR::LocId(generic_callee_id),
SemIR::SpecificFunction{
.type_id = GetSingletonType(
context, SemIR::SpecificFunctionType::TypeInstId),
.callee_id = generic_callee_id,
.specific_id = callee_specific_id});
callee_id = GetOrAddInst<SemIR::SpecificFunction>(
context, SemIR::LocId(generic_callee_id),
{.type_id =
GetSingletonType(context, SemIR::SpecificFunctionType::TypeInstId),
.callee_id = generic_callee_id,
.specific_id = callee_specific_id});
}
// Add the `self` argument back if there was one.
@@ -249,6 +248,30 @@ auto PerformCallToFunction(Context& context, SemIR::LocId loc_id,
}
auto& callee = context.functions().Get(callee_function.function_id);
// We can't call a function before its return type is known. This happens for
// a function with a deduced return type that is called from within its own
// body, or from the body of a class member function that precedes it. Within
// a generic function's body, after its return type is deduced, it can still
// only be called with its own generic parameters as arguments. And when a
// generic function is instantiated, the instantiation can't call the same
// specific before reaching the point where its return type is deduced.
if (!ResolveReturnTypeInSpecific(context, loc_id, callee,
*callee_specific_id)) {
CARBON_DIAGNOSTIC(CallBeforeReturnTypeDeduced, Error,
"call to `{0}` before its return type is known",
SemIR::NameId);
CARBON_DIAGNOSTIC_LABEL(
ReturnTypeDeducedFromDefinition, Info,
"return type of `{0}` is deduced from its definition", SemIR::NameId);
context.emitter()
.Build(loc_id, CallBeforeReturnTypeDeduced, callee.name_id)
.Attach(callee.latest_decl_id(), ReturnTypeDeducedFromDefinition,
callee.name_id)
.Emit();
return SemIR::ErrorInst::InstId;
}
auto return_type_id =
callee.GetDeclaredReturnType(context.sem_ir(), *callee_specific_id);
if (!return_type_id.has_value()) {
@@ -259,26 +282,29 @@ auto PerformCallToFunction(Context& context, SemIR::LocId loc_id,
if (callee.return_pattern_id.has_value()) {
Diagnostics::AnnotationScope annotate_diagnostics(
&context.emitter(), [&](auto& builder) {
CARBON_DIAGNOSTIC(IncompleteReturnTypeHere, Note,
"return type declared here");
builder.Note(callee.return_pattern_id, IncompleteReturnTypeHere);
CARBON_DIAGNOSTIC_LABEL(IncompleteReturnTypeHere, Info,
"return type declared here");
builder.Attach(callee.return_pattern_id, IncompleteReturnTypeHere);
});
auto arg_type_id = CheckFunctionReturnPatternType(
context, loc_id, callee.return_pattern_id, *callee_specific_id);
if (arg_type_id == SemIR::ErrorInst::TypeId) {
return_type_id = SemIR::ErrorInst::TypeId;
} else if (SemIR::InitRepr::ForType(context.sem_ir(), arg_type_id)
} else if (callee.call_param_ranges.return_size() > 0 &&
SemIR::InitRepr::ForType(context.sem_ir(), arg_type_id)
.MightBeInPlace()) {
// Tentatively use storage for a temporary as the return argument.
// This will be replaced if necessary when we perform initialization.
// Only do this if the callee has a return parameter to receive it;
// functions that return by reference or by value don't.
return_arg_id = AddInst<SemIR::TemporaryStorage>(
context, loc_id, {.type_id = arg_type_id});
}
}
// Convert the arguments to match the parameters.
auto converted_args_id =
ConvertCallArgs(context, callee_function.self_id, arg_ids, return_arg_id,
callee, *callee_specific_id, is_desugared);
ConvertCallArgs(context, loc_id, callee_function.self_id, arg_ids,
return_arg_id, callee, *callee_specific_id, is_desugared);
switch (callee.special_function_kind) {
case SemIR::Function::SpecialFunctionKind::Thunk: {
// If we're about to form a direct call to a thunk, inline it.
@@ -379,9 +405,9 @@ static auto PerformCallToNonFunction(Context& context, SemIR::LocId loc_id,
.is_desugared = SemIR::BoolValue::From(false)},
SemIR::TypeInstId::None);
}
case CARBON_KIND(SemIR::GenericClassType generic_class): {
return PerformCallToGenericClass(context, loc_id, generic_class.class_id,
generic_class.enclosing_specific_id,
case CARBON_KIND(SemIR::ParameterizedClassType param_class): {
return PerformCallToGenericClass(context, loc_id, param_class.class_id,
param_class.enclosing_specific_id,
arg_ids);
}
case CARBON_KIND(SemIR::GenericInterfaceType generic_interface): {
@@ -461,7 +487,7 @@ static auto PerformCallHelper(Context& context, SemIR::LocId loc_id,
}
case CARBON_KIND(SemIR::CalleeCppOverloadSet overload): {
return PerformCallToCppFunction(context, loc_id,
return PerformCallToCppFunction(context, loc_id, callee_id,
overload.cpp_overload_set_id,
overload.self_id, arg_ids, is_desugared);
}
+10 -13
View File
@@ -123,9 +123,9 @@ static auto TrackImport(Map<ImportKey, UnitAndImports*>& api_map,
!insert_result.is_inserted()) {
CARBON_DIAGNOSTIC(RepeatedImport, Error,
"library imported more than once");
CARBON_DIAGNOSTIC(FirstImported, Note, "first import here");
CARBON_DIAGNOSTIC_LABEL(FirstImported, Info, "first import here");
unit_info.emitter.Build(import.node_id, RepeatedImport)
.Note(insert_result.value(), FirstImported)
.Attach(insert_result.value(), FirstImported)
.Emit();
return;
}
@@ -311,20 +311,17 @@ static auto BuildApiMapAndDiagnosePackaging(
bool is_api_with_impl_ext = !is_impl && filename.ends_with(ImplExt);
auto want_ext = is_impl ? ImplExt : ApiExt;
if (is_api_with_impl_ext || !filename.ends_with(want_ext)) {
// The diagnostic is about the file rather than anything in it, so
// there is no range to mark and the hint goes in the message: a label
// would have no location of its own to be drawn at.
CARBON_DIAGNOSTIC(
IncorrectExtension, Error,
"file extension of `{0:.impl|}.carbon` required for {0:`impl`|api}",
Diagnostics::BoolAsSelect);
auto diag = unit_info.emitter.Build(
"file extension of `{0:.impl|}.carbon` required for {0:`impl`|api}"
"{1:; `.impl.carbon` names an `impl` file|}",
Diagnostics::BoolAsSelect, Diagnostics::BoolAsSelect);
unit_info.emitter.Emit(
packaging ? packaging->names.node_id : Parse::NodeId::None,
IncorrectExtension, is_impl);
if (is_api_with_impl_ext) {
CARBON_DIAGNOSTIC(
IncorrectExtensionImplNote, Note,
"file extension of `.impl.carbon` only allowed for `impl`");
diag.Note(Parse::NodeId::None, IncorrectExtensionImplNote);
}
diag.Emit();
IncorrectExtension, is_impl, is_api_with_impl_ext);
}
}
}
+14 -3
View File
@@ -40,14 +40,25 @@ extern "C" int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size) {
llvm::raw_null_ostream null_ostream;
Driver driver(fs, install_paths, /*input_stream=*/nullptr, &null_ostream,
&null_ostream, /*fuzzing=*/true);
&null_ostream, /*error_file=*/{}, /*fuzzing=*/true);
// Force Unicode and color output. The output stream isn't a terminal, so
// diagnostics would otherwise render as plain ASCII without color, and the
// fuzzer would never reach the renderer's handling of UTF-8 and double-width
// characters.
static constexpr llvm::StringLiteral RenderArgs[] = {
"--terminal-unicode=always", "--color=always"};
// TODO: Get checking to a point where it can handle invalid parse trees
// without crashing.
if (!driver.RunCommand({"compile", "--phase=parse", TestFileName}).success) {
if (!driver
.RunCommand({RenderArgs[0], RenderArgs[1], "compile",
"--phase=parse", TestFileName})
.success) {
return 0;
}
driver.RunCommand({"compile", "--phase=check", TestFileName});
driver.RunCommand(
{RenderArgs[0], RenderArgs[1], "compile", "--phase=check", TestFileName});
return 0;
}
Loaded 100 of 2031 files, more files were not shown because too many files have changed in this diff. Show more