Files
carbon-lang/toolchain/check/handle_index.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

185 lines
7.0 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 <optional>
#include "toolchain/base/kind_switch.h"
#include "toolchain/check/context.h"
#include "toolchain/check/convert.h"
#include "toolchain/check/handle.h"
#include "toolchain/check/inst.h"
#include "toolchain/check/literal.h"
#include "toolchain/check/name_lookup.h"
#include "toolchain/check/operator.h"
#include "toolchain/check/type.h"
#include "toolchain/diagnostics/diagnostic.h"
#include "toolchain/sem_ir/expr_info.h"
#include "toolchain/sem_ir/inst.h"
#include "toolchain/sem_ir/typed_insts.h"
namespace Carbon::Check {
auto HandleParseNode(Context& /*context*/, Parse::IndexExprStartId /*node_id*/)
-> bool {
// Leave the expression on the stack for IndexExpr.
return true;
}
// Returns the argument values of the `IndexWith` interface. Arguments
// correspond to the `SubscriptType` and the `ElementType`. If no arguments are
// used to define `IndexWith`, this returns an empty array reference. If the
// class does not implement the said interface, this returns a `std::nullopt`.
// TODO: Switch to using an associated type instead of a parameter for the
// `ElementType`.
static auto GetIndexWithArgs(Context& context, Parse::NodeId node_id,
SemIR::TypeId self_id)
-> std::optional<llvm::ArrayRef<SemIR::InstId>> {
auto index_with_inst_id = LookupNameInCore(context, node_id, "IndexWith");
// If the `IndexWith` interface doesn't have generic arguments then return an
// empty reference.
if (context.insts().Is<SemIR::FacetType>(index_with_inst_id)) {
return llvm::ArrayRef<SemIR::InstId>();
}
auto index_with_inst =
context.insts().TryGetAsIfValid<SemIR::StructValue>(index_with_inst_id);
if (!index_with_inst) {
return std::nullopt;
}
auto index_with_interface =
context.types().TryGetAs<SemIR::GenericInterfaceType>(
index_with_inst->type_id);
if (!index_with_interface) {
return std::nullopt;
}
for (const auto& impl : context.impls().values()) {
auto impl_self_type_id =
context.types().GetTypeIdForTypeInstId(impl.self_id);
auto impl_constraint_type_id =
context.types().GetTypeIdForTypeInstId(impl.constraint_id);
if (impl_self_type_id != self_id) {
continue;
}
auto facet_type =
context.types().TryGetAs<SemIR::FacetType>(impl_constraint_type_id);
if (!facet_type) {
continue;
}
const auto& facet_type_info =
context.facet_types().Get(facet_type->facet_type_id);
auto interface_type = facet_type_info.TryAsSingleInterface();
if (!interface_type) {
continue;
}
if (index_with_interface->interface_id != interface_type->interface_id) {
continue;
}
return context.inst_blocks().GetOrEmpty(
context.specifics().Get(interface_type->specific_id).args_id);
}
return std::nullopt;
}
// Performs an index with base expression `operand_inst_id` and
// `operand_type_id` for types that are not an array. This checks if
// the base expression implements the `IndexWith` interface; if so, uses the
// `At` associative method, otherwise prints a diagnostic.
static auto PerformIndexWith(Context& context, Parse::NodeId node_id,
SemIR::InstId operand_inst_id,
SemIR::TypeId operand_type_id,
SemIR::InstId index_inst_id) -> SemIR::InstId {
auto args = GetIndexWithArgs(context, node_id, operand_type_id);
// If the type does not implement the `IndexWith` interface, then return
// an error.
if (!args) {
CARBON_DIAGNOSTIC(TypeNotIndexable, Error,
"type {0} does not support indexing", SemIR::TypeId);
context.emitter().Emit(node_id, TypeNotIndexable, operand_type_id);
return SemIR::ErrorInst::InstId;
}
Operator op{
.interface_name = "IndexWith",
.interface_args_ref = *args,
.op_name = "At",
};
// IndexWith is defined without generic arguments.
if (args->empty()) {
return BuildBinaryOperator(context, node_id, op, operand_inst_id,
index_inst_id);
}
// The first argument of the `IndexWith` interface corresponds to the
// `SubscriptType`, so first cast `index_inst_id` to that type.
auto subscript_type_id = context.types().GetTypeIdForTypeInstId((*args)[0]);
auto cast_index_id =
ConvertToValueOfType(context, node_id, index_inst_id, subscript_type_id);
return BuildBinaryOperator(context, node_id, op, operand_inst_id,
cast_index_id);
}
auto HandleParseNode(Context& context, Parse::IndexExprId node_id) -> bool {
auto index_inst_id = context.node_stack().PopExpr();
auto operand_inst_id = context.node_stack().PopExpr();
operand_inst_id = ConvertToValueOrRefExpr(context, operand_inst_id);
auto operand_inst = context.insts().Get(operand_inst_id);
auto operand_type_id = operand_inst.type_id();
CARBON_KIND_SWITCH(context.types().GetAsInst(operand_type_id)) {
case CARBON_KIND(SemIR::ArrayType array_type): {
auto cast_index_id = ConvertToValueOfType(
context, SemIR::LocId(index_inst_id), index_inst_id,
// TODO: Replace this with impl lookup rather than hardcoding `i32`.
MakeIntType(context, node_id, SemIR::IntKind::Signed,
context.ints().Add(32)));
auto array_cat =
SemIR::GetExprCategory(context.sem_ir(), operand_inst_id);
if (array_cat == SemIR::ExprCategory::Value) {
// If the operand is an array value, convert it to an ephemeral
// reference to an array so we can perform a primitive indexing into it.
operand_inst_id = AddInst<SemIR::ValueAsRef>(
context, node_id,
{.type_id = operand_type_id, .value_id = operand_inst_id});
}
// Constant evaluation will perform a bounds check on this array indexing
// if the index is constant.
auto elem_id = AddInst<SemIR::ArrayIndex>(
context, node_id,
{.type_id = context.types().GetTypeIdForTypeInstId(
array_type.element_type_inst_id),
.array_id = operand_inst_id,
.index_id = cast_index_id});
if (array_cat != SemIR::ExprCategory::DurableRef) {
// Indexing a durable reference gives a durable reference expression.
// Indexing anything else gives a value expression.
// TODO: This should be replaced by a choice between using `IndexWith`
// and `IndirectIndexWith`.
elem_id = ConvertToValueExpr(context, elem_id);
}
context.node_stack().Push(node_id, elem_id);
return true;
}
default: {
auto elem_id = SemIR::ErrorInst::InstId;
if (operand_type_id != SemIR::ErrorInst::TypeId) {
elem_id = PerformIndexWith(context, node_id, operand_inst_id,
operand_type_id, index_inst_id);
}
context.node_stack().Push(node_id, elem_id);
return true;
}
}
}
} // namespace Carbon::Check