Files
Chandler Carruth d037848a96 Replace hashtable ForEach callback with range-based iteration (#7806)
Replaces the callback-based `ForEach` methods on `RawHashtable`, `Map`,
and
`Set` with a range object supporting range-for loops, structured
bindings, and
the standard range concepts.

- Adds `.entries()` on `Map`, `Set`, and `RawHashtable`, returning a
range that
  models `std::ranges::forward_range` and `std::ranges::common_range`.
  Obtaining one is an explicit call rather than `begin()`/`end()` on the
container, as scanning a whole table is costly and shouldn't be hidden.
- Iterating a `Map` yields a `std::pair` of key and value references,
which
fits in two registers and is returned without being materialized in
memory.
- `Map::Range` and `Set::Range` are aliases of the raw hashtable's range
rather
than wrappers around it. The raw iterator produces the user-facing
reference
itself -- a `KeyT&` for a set, a pair of references for a map -- picked
by
`StorageEntry`, which is already specialized on whether there is a value
  type. That leaves one iterator to reason about instead of three.
- Deletes the rvalue `.entries()` overloads on the owning containers, as
a
  range built from a temporary table would dangle. Views don't own their
  storage, so the operation remains available on them.
- In release builds, the walk over the groups is a single induction
variable: a
  negative byte offset counting up to zero, anchored at the ends of the
metadata and entry arrays. Both arrays are then reached by indexed
addressing
off a base that stays put, and the entry pointer is formed only once a
group
  with a present entry has been found.
- In debug builds, the range hashes the table's metadata when it is
built and
re-checks that hash when it is destroyed, catching mutation of the table
while a range is live. It also picks a random starting group and a
random odd
group stride, which varies the traversal order between ranges while
still
visiting every group exactly once. That entropy is drawn when the range
is
built rather than in `begin()`, so `begin()` stays a pure function of
the
  range and the multi-pass guarantee holds.
- Removes `ForEachEntry` and all of its callers.

Measured against the iteration benchmark added in its own commit, a
traversal is at or ahead of what the callback compiled to across nearly
the
whole size range. The largest tables spend 3-5% fewer cycles, small
`Set`s as
much as 24% fewer, and instruction counts stay within about 1%. What
remains
behind is a handful of mid-sized `Map`s by up to 1%, and `Set` at 65536,
which
sits at exactly half its load factor, by 2%.

Both revisions were built with `-c opt --copt=-march=x86-64-v3` and
compared
with:

```
./scripts/bench_runner.py --exp_benchmark=... --base_benchmark=... \
    --benchmark_args=--benchmark_perf_counters=INSTRUCTIONS,CYCLES \
    --benchmark_args='--benchmark_filter=(Set|Map)Iterate<(Set|Map)<' \
    --extra_metrics_filter='(INSTRUCTIONS|CYCLES)'
```

Trimmed below to the primary integer configurations and to the two
counters;
the pointer- and string-keyed configurations follow the same pattern.

```
 Benchmark                             ┃           CYCLES            ┃        INSTRUCTIONS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 BM_MapIterate<Map<int, int>>/1....... │ 👍  -6.032%      p=1.14e-05 │      ??          p=0.752
                             baseline: │     12.06      ±   1.520%   │     64         ±   3.125%
                           experiment: │     11.33      ±   2.765%   │     65.5       ±   3.817%
                                       │                             │
 BM_MapIterate<Map<int, int>>/2....... │      ??          p=0.155    │      ??          p=0.343
                             baseline: │      7.587     ±   1.285%   │     41         ±   0.000%
                           experiment: │      7.652     ±   0.865%   │     41         ±   2.439%
                                       │                             │
 BM_MapIterate<Map<int, int>>/3....... │      ??          p=0.343    │ 👍  -1.020%      p=0.0039
                             baseline: │      6.663     ±   4.260%   │     32.67      ±   2.041%
                           experiment: │      6.368     ±  12.224%   │     32.33      ±   2.062%
                                       │                             │
 BM_MapIterate<Map<int, int>>/4....... │      ??          p=0.343    │ 👍  -1.786%      p=0.0297
                             baseline: │      6.091     ±  15.470%   │     28         ±   3.571%
                           experiment: │      5.957     ±   8.932%   │     27.5       ±   3.636%
                                       │                             │
 BM_MapIterate<Map<int, int>>/8....... │      ??          p=0.323    │ 👍  -1.220%      p=0.000148
                             baseline: │      4.845     ±   0.800%   │     20.5       ±   0.000%
                           experiment: │      4.814     ±   3.585%   │     20.25      ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/16...... │ 👍  -2.195%      p=0.00908  │ 👍   0.769%      p=6.58e-06
                             baseline: │      4.312     ±   0.187%   │     16.25      ±   0.000%
                           experiment: │      4.218     ±   2.368%   │     16.13      ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/32...... │      ??          p=0.236    │ 👍   0.442%      p=9.53e-06
                             baseline: │      4.051     ±   1.084%   │     14.13      ±   0.000%
                           experiment: │      4.063     ±   0.737%   │     14.06      ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/64...... │      ??          p=0.693    │ 👎   0.227%      p=4.52e-06
                             baseline: │      4.021     ±   0.239%   │     13.75      ±   0.000%
                           experiment: │      4.019     ±   0.417%   │     13.78      ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/256..... │ 👍   0.360%      p=0.00119  │ 👎   0.754%      p=1.37e-05
                             baseline: │      3.996     ±   0.173%   │     13.47      ±   0.000%
                           experiment: │      3.982     ±   0.272%   │     13.57      ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/4096.... │ 👍   0.581%      p=1.96e-05 │ 👎   0.923%      p=1.96e-05
                             baseline: │      4.005     ±   0.816%   │     13.38      ±   0.000%
                           experiment: │      3.981     ±   0.192%   │     13.5       ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/65536... │ 👍  -4.957%      p=1.14e-05 │ 👎   0.934%      p=1.14e-05
                             baseline: │      5.307     ±   0.501%   │     13.38      ±   0.000%
                           experiment: │      5.044     ±   1.746%   │     13.5       ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/1048576. │ 👍  -3.947%      p=9.09e-05 │ 👎   0.935%      p=3.3e-05
                             baseline: │      6.074     ±   0.807%   │     13.38      ±   0.000%
                           experiment: │      5.834     ±   2.159%   │     13.5       ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/16777216 │      ??          p=0.155    │ 👎   0.935%      p=2.11e-05
                             baseline: │      5.082     ±   3.650%   │     13.38      ±   0.000%
                           experiment: │      5.012     ±   1.316%   │     13.5       ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/56...... │ 👎   0.825%      p=0.0268   │ 👍   0.270%      p=1.14e-05
                             baseline: │      3.918     ±   0.501%   │     13.21      ±   0.000%
                           experiment: │      3.951     ±   0.342%   │     13.18      ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/224..... │ 👎   0.788%      p=0.000504 │ 👎   0.346%      p=1.64e-05
                             baseline: │      3.895     ±   0.111%   │     12.89      ±   0.000%
                           experiment: │      3.926     ±   0.285%   │     12.94      ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/3584.... │ 👎   1.028%      p=0.000148 │ 👎   0.545%      p=1.14e-05
                             baseline: │      3.913     ±   0.427%   │     12.79      ±   0.000%
                           experiment: │      3.954     ±   0.325%   │     12.86      ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/57344... │      ??          p=0.236    │ 👎   0.558%      p=2.55e-06
                             baseline: │      4.574     ±   0.721%   │     12.79      ±   0.000%
                           experiment: │      4.51      ±   3.709%   │     12.86      ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/917504.. │ 👍  -3.826%      p=6.58e-06 │ 👎   0.559%      p=2.33e-05
                             baseline: │      5.221     ±   0.507%   │     12.79      ±   0.000%
                           experiment: │      5.021     ±   0.556%   │     12.86      ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/14680064 │ 👍  -3.839%      p=1.37e-05 │ 👎   0.559%      p=3.31e-05
                             baseline: │      5.129     ±   1.194%   │     12.79      ±   0.000%
                           experiment: │      4.932     ±   1.475%   │     12.86      ±   0.000%
                                       │                             │

 Benchmark                        ┃           CYCLES            ┃        INSTRUCTIONS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 BM_SetIterate<Set<int>>/1....... │ 👍  -3.104%      p=0.000583 │      ??          p=0.206
                        baseline: │     11.2       ±   6.323%   │     60         ±   3.333%
                      experiment: │     10.85      ±   5.820%   │     61         ±   3.279%
                                  │                             │
 BM_SetIterate<Set<int>>/2....... │ 👍  -7.037%      p=0.0362   │      ??          p=0.155
                        baseline: │      7.086     ±  16.857%   │     37         ±   0.000%
                      experiment: │      6.587     ±   0.479%   │     36         ±   4.167%
                                  │                             │
 BM_SetIterate<Set<int>>/3....... │ 👎   1.400%      p=2.34e-05 │ 👍  -1.163%      p=0.00136
                        baseline: │      5.363     ±   0.463%   │     28.67      ±   2.326%
                      experiment: │      5.438     ±  32.763%   │     28.33      ±   1.176%
                                  │                             │
 BM_SetIterate<Set<int>>/4....... │      ??          p=0.968    │      ??          p=0.286
                        baseline: │      4.642     ±  32.751%   │     23.5       ±   2.128%
                      experiment: │      4.658     ±  38.416%   │     23.63      ±   3.704%
                                  │                             │
 BM_SetIterate<Set<int>>/8....... │ 👍 -23.823%      p=3.74e-06 │ 👍  -1.515%      p=5.52e-05
                        baseline: │      4.701     ±   6.589%   │     16.5       ±   0.000%
                      experiment: │      3.581     ±   7.790%   │     16.25      ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/16...... │ 👍  -4.502%      p=1.37e-05 │ 👍  -1.020%      p=3.31e-05
                        baseline: │      3.124     ±   0.585%   │     12.25      ±   0.000%
                      experiment: │      2.983     ±   0.625%   │     12.13      ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/32...... │ 👍  -4.032%      p=5.46e-06 │ 👍   0.617%      p=1.96e-05
                        baseline: │      2.957     ±   0.260%   │     10.13      ±   0.000%
                      experiment: │      2.838     ±   0.434%   │     10.06      ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/64...... │ 👍  -5.054%      p=4.52e-06 │ 👎   0.321%      p=1.37e-05
                        baseline: │      2.937     ±   0.301%   │      9.75      ±   0.000%
                      experiment: │      2.788     ±   1.143%   │      9.781     ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/256..... │ 👍  -5.325%      p=1.14e-05 │ 👎   1.073%      p=6.58e-06
                        baseline: │      2.916     ±   0.220%   │      9.469     ±   0.000%
                      experiment: │      2.761     ±   0.142%   │      9.57      ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/4096.... │ 👍  -4.865%      p=4.52e-06 │ 👎   1.317%      p=2.34e-05
                        baseline: │      2.921     ±   0.194%   │      9.381     ±   0.000%
                      experiment: │      2.779     ±   0.224%   │      9.504     ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/65536... │ 👎   1.961%      p=3.93e-05 │ 👎   1.332%      p=1.49e-05
                        baseline: │      4.015     ±   0.482%   │      9.375     ±   0.000%
                      experiment: │      4.094     ±   0.613%   │      9.5       ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/1048576. │ 👍  -4.843%      p=1.14e-05 │ 👎   1.333%      p=5.38e-06
                        baseline: │      5.239     ±   0.144%   │      9.375     ±   0.000%
                      experiment: │      4.986     ±   0.139%   │      9.5       ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/16777216 │ 👍   0.840%      p=0.0362   │ 👎   1.333%      p=2.52e-06
                        baseline: │      3.719     ±   1.420%   │      9.375     ±   0.000%
                      experiment: │      3.688     ±   1.308%   │      9.5       ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/56...... │ 👍  -2.857%      p=9.53e-06 │ 👍   0.388%      p=3.31e-05
                        baseline: │      2.942     ±   0.439%   │      9.214     ±   0.000%
                      experiment: │      2.858     ±   0.619%   │      9.179     ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/224..... │ 👍  -2.161%      p=2.34e-05 │ 👎   0.502%      p=4.52e-06
                        baseline: │      2.888     ±   0.347%   │      8.893     ±   0.000%
                      experiment: │      2.826     ±   0.450%   │      8.938     ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/3584.... │ 👍  -1.750%      p=6.58e-06 │ 👎   0.793%      p=2.34e-05
                        baseline: │      2.89      ±   0.261%   │      8.792     ±   0.000%
                      experiment: │      2.84      ±   0.411%   │      8.862     ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/57344... │      ??          p=0.502    │ 👎   0.812%      p=2.78e-05
                        baseline: │      3.684     ±   4.246%   │      8.786     ±   0.000%
                      experiment: │      3.644     ±   4.431%   │      8.857     ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/917504.. │ 👍  -2.629%      p=0.000148 │ 👎   0.813%      p=3.08e-06
                        baseline: │      4.372     ±   0.693%   │      8.786     ±   0.000%
                      experiment: │      4.257     ±   0.210%   │      8.857     ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/14680064 │ 👍  -2.927%      p=0.0219   │ 👎   0.813%      p=3.03e-06
                        baseline: │      4.154     ±   3.286%   │      8.786     ±   0.000%
                      experiment: │      4.032     ±   3.198%   │      8.857     ±   0.000%
                                  │                             │
```

Assisted-by: Antigravity with Opus
2026-09-18 20:19:46 +00:00

435 lines
17 KiB
C++

// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "toolchain/check/scope_stack.h"
#include <utility>
#include "common/check.h"
#include "common/find.h"
#include "toolchain/check/context.h"
#include "toolchain/check/control_flow.h"
#include "toolchain/check/unused.h"
#include "toolchain/sem_ir/ids.h"
namespace Carbon::Check {
ScopeStack::ScopeStack(Context& context)
: context_(&context),
lexical_lookup_(context.sem_ir().identifiers()),
full_pattern_stack_(&lexical_lookup_) {}
auto ScopeStack::sem_ir() const -> const SemIR::File& {
return context_->sem_ir();
}
auto ScopeStack::VerifyOnFinish() const -> void {
CARBON_CHECK(return_scope_stack_.empty(), "{0}", return_scope_stack_.size());
CARBON_CHECK(break_continue_stack_.empty(), "{0}",
break_continue_stack_.size());
CARBON_CHECK(scope_stack_.empty(), "{0}", scope_stack_.size());
CARBON_CHECK(destroy_id_stack_.empty(), "{0}", destroy_id_stack_.size());
CARBON_CHECK(non_lexical_scope_stack_.empty(), "{0}",
non_lexical_scope_stack_.size());
CARBON_CHECK(compile_time_binding_stack_.empty(), "{0}",
compile_time_binding_stack_.all_values_size());
full_pattern_stack_.VerifyOnFinish();
}
auto ScopeStack::VerifyNextCompileTimeBindIndex(llvm::StringLiteral label,
const ScopeStackEntry& scope)
-> void {
CARBON_CHECK(
static_cast<int32_t>(compile_time_binding_stack_.all_values_size()) ==
scope.next_compile_time_bind_index.index,
"Wrong number of entries in compile-time binding stack after {0}: have "
"{1}, expected {2}",
label, compile_time_binding_stack_.all_values_size(),
scope.next_compile_time_bind_index.index);
}
auto ScopeStack::Push(SemIR::InstId scope_inst_id, SemIR::NameScopeId scope_id,
SemIR::SpecificId specific_id,
CleanupScopeKind cleanup_scope_kind,
bool lexical_lookup_has_load_error) -> void {
// If this scope doesn't have a specific of its own, it lives in the enclosing
// scope's specific, if any.
auto enclosing_specific_id = specific_id;
if (!specific_id.has_value() && !scope_stack_.empty()) {
enclosing_specific_id = PeekSpecificId();
}
compile_time_binding_stack_.PushArray();
scope_stack_.push_back(
{.index = next_scope_index_,
.scope_inst_id = scope_inst_id,
.scope_id = scope_id,
.specific_id = enclosing_specific_id,
.next_compile_time_bind_index = SemIR::CompileTimeBindIndex(
compile_time_binding_stack_.all_values_size()),
.lexical_lookup_has_load_error =
LexicalLookupHasLoadError() || lexical_lookup_has_load_error,
.cleanup_scope_kind = cleanup_scope_kind,
.cleanup_scope_depth = CleanupScopeDepth(destroy_id_stack_.size())});
if (scope_stack_.back().is_lexical_scope()) {
// For lexical lookups, unqualified lookup doesn't know how to find the
// associated specific, so if we start adding lexical scopes associated with
// specifics, we'll need to somehow track them in lookup. The self specific
// is an exception to this, as its generic will always lexically enclose the
// point of use of any looked up name.
CARBON_CHECK(!specific_id.has_value() ||
sem_ir().generics().GetSelfSpecific(
sem_ir().specifics().Get(specific_id).generic_id) ==
specific_id,
"Lexical scopes can only have an associated specific if it is "
"a self specific.");
} else {
non_lexical_scope_stack_.push_back({.scope_index = next_scope_index_,
.name_scope_id = scope_id,
.specific_id = enclosing_specific_id});
}
// TODO: Handle this case more gracefully.
CARBON_CHECK(next_scope_index_.index != std::numeric_limits<int32_t>::max(),
"Ran out of scopes");
++next_scope_index_.index;
VerifyNextCompileTimeBindIndex("Push", scope_stack_.back());
}
auto ScopeStack::PushForDeclName() -> void {
Push(SemIR::InstId::None, SemIR::NameScopeId::None, SemIR::SpecificId::None,
CleanupScopeKind::None, /*lexical_lookup_has_load_error=*/false);
MarkNestingIfInReturnScope();
}
auto ScopeStack::PushForEntity(SemIR::InstId scope_inst_id,
SemIR::NameScopeId scope_id,
SemIR::SpecificId specific_id,
bool lexical_lookup_has_load_error) -> void {
CARBON_CHECK(scope_inst_id.has_value());
CARBON_DCHECK(!sem_ir().insts().Is<SemIR::FunctionDecl>(scope_inst_id));
Push(scope_inst_id, scope_id, specific_id, CleanupScopeKind::None,
lexical_lookup_has_load_error);
MarkNestingIfInReturnScope();
}
auto ScopeStack::PushForSameRegion(CleanupScopeKind cleanup_scope_kind)
-> void {
if (cleanup_scope_kind == CleanupScopeKind::Inherited &&
Peek().cleanup_scope_kind == CleanupScopeKind::None) {
cleanup_scope_kind = CleanupScopeKind::None;
}
Push(SemIR::InstId::None, SemIR::NameScopeId::None, SemIR::SpecificId::None,
cleanup_scope_kind, /*lexical_lookup_has_load_error=*/false);
}
auto ScopeStack::PushForFunctionBody(SemIR::InstId scope_inst_id) -> void {
const auto& function_decl =
sem_ir().insts().GetAs<SemIR::FunctionDecl>(scope_inst_id);
const auto& function = sem_ir().functions().Get(function_decl.function_id);
auto self_specific = sem_ir().generics().GetSelfSpecific(function.generic_id);
Push(scope_inst_id, SemIR::NameScopeId::None, self_specific,
CleanupScopeKind::Owned, /*lexical_lookup_has_load_error=*/false);
return_scope_stack_.push_back(
{.decl_id = scope_inst_id, .cleanup_scope_depth = cleanup_scope_depth()});
}
auto ScopeStack::Pop(bool check_unused) -> void {
auto scope = scope_stack_.pop_back_val();
// TODO: Multiple diagnostics on same line has non-deterministic order.
// Add second sort key in diagnostics sorting.
for (SemIR::NameId name_id : scope.names.entries()) {
auto& lexical_results = lexical_lookup_.Get(name_id);
CARBON_CHECK(lexical_results.back().scope_index == scope.index,
"Inconsistent scope index for name {0}", name_id);
if (check_unused) {
CheckUnusedBinding(*context_, name_id, lexical_results.back());
}
lexical_results.pop_back();
}
if (!scope.is_lexical_scope()) {
CARBON_CHECK(non_lexical_scope_stack_.back().scope_index == scope.index);
non_lexical_scope_stack_.pop_back();
}
CARBON_CHECK(scope.cleanup_scope_kind == CleanupScopeKind::Inherited ||
static_cast<size_t>(scope.cleanup_scope_depth.index) ==
destroy_id_stack_.size(),
"Popping scope with cleanups: have {0} but expected {1}",
destroy_id_stack_.size(), scope.cleanup_scope_depth.index);
if (!return_scope_stack_.empty()) {
if (scope.has_returned_var) {
CARBON_CHECK(return_scope_stack_.back().returned_var.has_value());
return_scope_stack_.back().returned_var = SemIR::InstId::None;
}
if (return_scope_stack_.back().decl_id == scope.scope_inst_id) {
// Leaving the function scope.
return_scope_stack_.pop_back();
} else {
if (return_scope_stack_.back().nested_scope_index == scope.index) {
// Returned to a function scope from a non-function nested entity scope.
return_scope_stack_.back().nested_scope_index = ScopeIndex::None;
}
}
}
VerifyNextCompileTimeBindIndex("Pop", scope);
compile_time_binding_stack_.PopArray();
}
auto ScopeStack::PopTo(ScopeIndex index, bool check_unused) -> void {
while (PeekIndex() > index) {
Pop(check_unused);
}
CARBON_CHECK(PeekIndex() == index,
"Scope index {0} does not enclose the current scope {1}", index,
PeekIndex());
}
auto ScopeStack::MergeTopScopeIntoGrandparentAndPop() -> void {
CARBON_CHECK(scope_stack_.size() >= 3);
auto& grandparent = scope_stack_[scope_stack_.size() - 3];
auto& parent = scope_stack_[scope_stack_.size() - 2];
auto& current = scope_stack_[scope_stack_.size() - 1];
CARBON_CHECK(current.num_names == 0);
CARBON_CHECK(grandparent.cleanup_scope_kind != CleanupScopeKind::None &&
parent.cleanup_scope_kind == CleanupScopeKind::Owned &&
current.cleanup_scope_kind == CleanupScopeKind::Owned);
// NOLINTNEXTLINE(readability-qualified-auto)
auto new_mid =
std::rotate(destroy_id_stack_.begin() + parent.cleanup_scope_depth.index,
destroy_id_stack_.begin() + current.cleanup_scope_depth.index,
destroy_id_stack_.end());
parent.cleanup_scope_depth =
CleanupScopeDepth(new_mid - destroy_id_stack_.begin());
current.cleanup_scope_depth = CleanupScopeDepth(destroy_id_stack_.size());
Pop();
}
auto ScopeStack::MarkUsed(SemIR::NameId name_id, SemIR::LocId loc_id,
bool is_reachable) -> void {
auto& lexical_results = lexical_lookup_.Get(name_id);
if (lexical_results.empty()) {
return;
}
auto& result = lexical_results.back();
if (result.use_loc_id.has_value()) {
return;
}
// Determine if we should set use_loc_id.
if (result.inst_id.has_value() &&
result.inst_id != SemIR::InstId::InitTombstone) {
if (auto binding =
context_->insts().TryGetAs<SemIR::AnyBinding>(result.inst_id)) {
const auto& entity_name =
context_->entity_names().Get(binding->entity_name_id);
if (entity_name.is_unused && !is_reachable) {
return;
}
}
}
// For non-bindings (like namespaces), we just mark them as used.
// If the instruction is not valid (e.g. InitTombstone), we mark it as used
// to avoid spurious "unused" warnings, assuming the invalid state will be
// diagnosed elsewhere (e.g. used before init).
result.use_loc_id = loc_id;
}
auto ScopeStack::LookupInLexicalScopesWithin(SemIR::NameId name_id,
ScopeIndex scope_index,
SemIR::LocId use_loc_id,
bool is_reachable)
-> SemIR::InstId {
llvm::ArrayRef<LexicalLookup::Result> lexical_results =
lexical_lookup_.Get(name_id);
if (lexical_results.empty()) {
return SemIR::InstId::None;
}
auto result = lexical_results.back();
if (result.scope_index < scope_index) {
return SemIR::InstId::None;
}
if (use_loc_id.has_value()) {
MarkUsed(name_id, use_loc_id, is_reachable);
}
return result.inst_id;
}
auto ScopeStack::LookupInLexicalScopes(SemIR::NameId name_id,
SemIR::LocId use_loc_id,
bool is_reachable)
-> std::pair<SemIR::InstId, llvm::ArrayRef<NonLexicalScope>> {
// Find the results from lexical scopes. These will be combined with results
// from non-lexical scopes such as namespaces and classes.
llvm::ArrayRef<LexicalLookup::Result> lexical_results =
lexical_lookup_.Get(name_id);
// If we have no lexical results, check all non-lexical scopes.
if (lexical_results.empty()) {
return {LexicalLookupHasLoadError() ? SemIR::ErrorInst::InstId
: SemIR::InstId::None,
non_lexical_scope_stack_};
}
if (use_loc_id.has_value()) {
MarkUsed(name_id, use_loc_id, is_reachable);
}
// Find the first non-lexical scope that is within the scope of the lexical
// lookup result.
auto* first_non_lexical_scope = llvm::lower_bound(
non_lexical_scope_stack_, lexical_results.back().scope_index,
[](const NonLexicalScope& scope, ScopeIndex index) {
return scope.scope_index < index;
});
return {
lexical_results.back().inst_id,
llvm::ArrayRef(first_non_lexical_scope, non_lexical_scope_stack_.end())};
}
auto ScopeStack::LookupOrAddName(SemIR::NameId name_id, SemIR::InstId target_id,
ScopeIndex scope_index, bool is_decl_reachable)
-> SemIR::InstId {
// Find the corresponding scope depth.
//
// TODO: Consider passing in the depth rather than performing a scan for it.
// We only do this scan when declaring an entity such as a class within a
// function, so it should be relatively rare, but it's still not necesasry to
// recompute this.
int scope_depth = scope_stack_.size() - 1;
if (scope_index.has_value()) {
scope_depth =
llvm::lower_bound(scope_stack_, scope_index,
[](const ScopeStackEntry& entry, ScopeIndex index) {
return entry.index < index;
}) -
scope_stack_.begin();
CARBON_CHECK(scope_stack_[scope_depth].index == scope_index,
"Declaring name in scope that has already ended");
} else {
scope_index = scope_stack_[scope_depth].index;
}
// If this name has already been declared in this scope or an inner scope,
// return the existing result.
auto& lexical_results = lexical_lookup_.Get(name_id);
if (!lexical_results.empty() &&
lexical_results.back().scope_index >= scope_index) {
return lexical_results.back().inst_id;
}
// Add the name into the scope.
bool inserted = scope_stack_[scope_depth].names.Insert(name_id).is_inserted();
CARBON_CHECK(inserted, "Name in scope but not in lexical lookups");
++scope_stack_[scope_depth].num_names;
// Add a corresponding lexical lookup result.
lexical_results.push_back({.inst_id = target_id,
.scope_index = scope_index,
.is_decl_reachable = is_decl_reachable,
.use_loc_id = SemIR::LocId::None});
return SemIR::InstId::None;
}
auto ScopeStack::SetReturnedVarOrGetExisting(SemIR::InstId inst_id,
SemIR::NameId name_id)
-> SemIR::InstId {
CARBON_CHECK(!return_scope_stack_.empty(), "`returned var` in no function");
auto& return_scope = return_scope_stack_.back();
if (return_scope.returned_var.has_value()) {
return return_scope.returned_var;
}
return_scope.returned_var = inst_id;
CARBON_CHECK(!scope_stack_.back().has_returned_var,
"Scope has returned var but none is set");
if (inst_id.has_value()) {
scope_stack_.back().has_returned_var = true;
MarkUsed(name_id, SemIR::LocId(inst_id),
context_->inst_block_stack().is_current_block_reachable());
}
return SemIR::InstId::None;
}
auto ScopeStack::Suspend() -> SuspendedScope {
CARBON_CHECK(!scope_stack_.empty(), "No scope to suspend");
SuspendedScope result = {.entry = scope_stack_.pop_back_val(),
.suspended_items = {}};
if (!result.entry.is_lexical_scope()) {
non_lexical_scope_stack_.pop_back();
}
auto peek_compile_time_bindings = compile_time_binding_stack_.PeekArray();
result.suspended_items.reserve(result.entry.num_names +
peek_compile_time_bindings.size());
for (SemIR::NameId name_id : result.entry.names.entries()) {
auto suspended = lexical_lookup_.Suspend(name_id);
CARBON_CHECK(suspended.index !=
SuspendedScope::ScopeItem::IndexForCompileTimeBinding);
result.suspended_items.push_back(
{.index = suspended.index,
.inst_id = suspended.inst_id,
.is_decl_reachable = suspended.is_decl_reachable,
.use_loc_id = suspended.use_loc_id});
}
CARBON_CHECK(static_cast<int>(result.suspended_items.size()) ==
result.entry.num_names);
// Move any compile-time bindings into the suspended scope.
for (auto inst_id : peek_compile_time_bindings) {
result.suspended_items.push_back(
{.index = SuspendedScope::ScopeItem::IndexForCompileTimeBinding,
.inst_id = inst_id,
.is_decl_reachable = true,
.use_loc_id = SemIR::LocId::None});
}
compile_time_binding_stack_.PopArray();
// This would be easy to support if we had a need, but currently we do not.
CARBON_CHECK(result.entry.cleanup_scope_kind == CleanupScopeKind::None,
"Should not suspend a function definition scope.");
CARBON_CHECK(!result.entry.has_returned_var,
"Should not suspend a scope with a returned var.");
return result;
}
auto ScopeStack::Restore(SuspendedScope&& scope) -> void {
compile_time_binding_stack_.PushArray();
for (auto item : scope.suspended_items) {
if (item.index == SuspendedScope::ScopeItem::IndexForCompileTimeBinding) {
compile_time_binding_stack_.AppendToTop(item.inst_id);
} else {
lexical_lookup_.Restore({.index = item.index,
.inst_id = item.inst_id,
.is_decl_reachable = item.is_decl_reachable,
.use_loc_id = item.use_loc_id},
scope.entry.index);
}
}
VerifyNextCompileTimeBindIndex("Restore", scope.entry);
if (!scope.entry.is_lexical_scope()) {
non_lexical_scope_stack_.push_back(
{.scope_index = scope.entry.index,
.name_scope_id = scope.entry.scope_id,
.specific_id = scope.entry.specific_id});
}
scope_stack_.push_back(std::move(scope.entry));
}
} // namespace Carbon::Check