Files
carbon-lang/toolchain/sem_ir/generic.cpp
T
Dana Jansens 02fc484f23 Make pointers in ValueStore stable across insertions (#5576)
This avoids reallocating the backing buffer in ValueStore so that
references into the ValueStore are never invalidated when adding new
values. This works especially well since we never delete values from a
ValueStore.

The strategy used is to allocate chunks of a fixed size, and inserting
into each chunk until it is full before allocating the next. The
ValueStore starts with an initial allocated chunk in all cases, so that
there is only a single indirection for adding and accessing values from
this chunk. After it's full, additional chunks are allocated in a
vector, so two indirections are required to add or access values in
these chunks.

This obviates the need for
https://github.com/carbon-language/carbon-lang/pull/5529 as we no longer
need to worry about holding pointers into a ValueStore.

We introduce a Flatten operation for ranges. It flattens a "range over
ranges over Ts" down to a "range over Ts". This allows us to make an
range over the values in the ValueStore from a range over the chunks in
the ValueStore. See
https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html#method.flatten
for inspiration for this name choice. Flatten is used in one other case
where we were writing two levels of for loops to do the same thing.

The `array_ref()` accessor is changed to `values()` and its now a range
(typed as a `ValueStoreRange`) over all values as references (like
ArrayRef was, but without random access).

As pointers to a ValueStore can no longer be invalidated, we remove the
ASAN poisoning feature and support from ValueStore.

This may cause a regression in our compile benchmark of up to 5%, though
that is close to or within the noise of the benchmark. We can look at
ways to optimize things further in the future. Perhaps by tuning the
chunk size further, or by making later chunks larger than earlier
chunks, or other strategies.
2025-06-02 19:16:31 +00:00

113 lines
4.1 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/sem_ir/generic.h"
#include "toolchain/sem_ir/file.h"
#include "toolchain/sem_ir/typed_insts.h"
namespace Carbon::SemIR {
class SpecificStore::KeyContext : public TranslatingKeyContext<KeyContext> {
public:
// A lookup key for a specific.
struct Key {
GenericId generic_id;
InstBlockId args_id;
friend auto operator==(const Key&, const Key&) -> bool = default;
};
explicit KeyContext(const ValueStore<SpecificId>* specifics)
: specifics_(specifics) {}
auto TranslateKey(SpecificId id) const -> Key {
const auto& specific = specifics_->Get(id);
return {.generic_id = specific.generic_id, .args_id = specific.args_id};
}
private:
const ValueStore<SpecificId>* specifics_;
};
auto SpecificStore::GetOrAdd(GenericId generic_id, InstBlockId args_id)
-> SpecificId {
CARBON_CHECK(generic_id.has_value());
return lookup_table_
.Insert(
KeyContext::Key{.generic_id = generic_id, .args_id = args_id},
[&] {
return specifics_.Add(
{.generic_id = generic_id, .args_id = args_id});
},
KeyContext(&specifics_))
.key();
}
auto SpecificStore::CollectMemUsage(MemUsage& mem_usage,
llvm::StringRef label) const -> void {
mem_usage.Collect(MemUsage::ConcatLabel(label, "specifics_"), specifics_);
mem_usage.Collect(MemUsage::ConcatLabel(label, "lookup_table_"),
lookup_table_, KeyContext(&specifics_));
}
static auto GetConstantInSpecific(const File& sem_ir, SpecificId specific_id,
ConstantId const_id) -> ConstantId {
if (!const_id.is_symbolic()) {
// The constant does not depend on a generic parameter.
return const_id;
}
const auto& symbolic = sem_ir.constant_values().GetSymbolicConstant(const_id);
if (!symbolic.generic_id.has_value()) {
// The constant is an unattached symbolic constant, not associated with some
// particular generic.
return const_id;
}
if (!specific_id.has_value()) {
// We have a generic constant but no specific. We treat this as a request
// for the value that should be used within the generic itself, which is the
// unattached constant.
return sem_ir.constant_values().Get(symbolic.inst_id);
}
const auto& specific = sem_ir.specifics().Get(specific_id);
CARBON_CHECK(specific.generic_id == symbolic.generic_id,
"Given a specific for the wrong generic");
auto value_block_id = specific.GetValueBlock(symbolic.index.region());
if (!value_block_id.has_value()) {
// For the self specific, we can see queries before the definition is
// resolved. Return the unattached constant value.
CARBON_CHECK(
sem_ir.generics().GetSelfSpecific(symbolic.generic_id) == specific_id,
"Queried {0} in {1} for {2} before it was resolved.", symbolic.index,
specific_id,
sem_ir.insts().Get(sem_ir.generics().Get(specific.generic_id).decl_id));
// TODO: Make sure this is the same value that we put in the self specific
// when it's resolved. Consider not building value blocks for a self
// specific.
return sem_ir.constant_values().Get(symbolic.inst_id);
}
return sem_ir.constant_values().Get(
sem_ir.inst_blocks().Get(value_block_id)[symbolic.index.index()]);
}
auto GetConstantValueInSpecific(const File& sem_ir, SpecificId specific_id,
InstId inst_id) -> ConstantId {
return GetConstantInSpecific(sem_ir, specific_id,
sem_ir.constant_values().GetAttached(inst_id));
}
auto GetTypeOfInstInSpecific(const File& sem_ir, SpecificId specific_id,
InstId inst_id) -> TypeId {
auto type_id = sem_ir.insts().GetAttachedType(inst_id);
auto const_id = sem_ir.types().GetConstantId(type_id);
auto specific_const_id = GetConstantInSpecific(sem_ir, specific_id, const_id);
return TypeId::ForTypeConstant(specific_const_id);
}
} // namespace Carbon::SemIR