mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 19:30:12 +01:00
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.
470 lines
18 KiB
C++
470 lines
18 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/impl_validation.h"
|
|
|
|
#include <utility>
|
|
|
|
#include "llvm/ADT/ArrayRef.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "toolchain/base/kind_switch.h"
|
|
#include "toolchain/check/diagnostic_helpers.h"
|
|
#include "toolchain/check/impl_lookup.h"
|
|
#include "toolchain/check/import_ref.h"
|
|
#include "toolchain/check/type_structure.h"
|
|
#include "toolchain/sem_ir/entity_with_params_base.h"
|
|
#include "toolchain/sem_ir/ids.h"
|
|
#include "toolchain/sem_ir/type_iterator.h"
|
|
|
|
namespace Carbon::Check {
|
|
|
|
namespace {
|
|
|
|
struct ImplAndInterface {
|
|
SemIR::ImplId impl_id;
|
|
SemIR::SpecificInterface interface;
|
|
};
|
|
|
|
// We avoid holding a reference into the ImplStore, as the act of verifying the
|
|
// diagnostic checks can invalidate such a reference. We collect what we need to
|
|
// know about the the `Impl` for validation into this struct.
|
|
struct ImplInfo {
|
|
bool is_final;
|
|
SemIR::InstId witness_id;
|
|
SemIR::TypeInstId self_id;
|
|
SemIR::InstId latest_decl_id;
|
|
SemIR::SpecificInterface interface;
|
|
// Whether the `impl` decl was imported or from the local file.
|
|
bool is_local;
|
|
// If imported, the IR from which the `impl` decl was imported.
|
|
SemIR::ImportIRId ir_id;
|
|
TypeStructure type_structure;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
static auto GetIRId(Context& context, SemIR::InstId owning_inst_id)
|
|
-> SemIR::ImportIRId {
|
|
if (!owning_inst_id.has_value()) {
|
|
return SemIR::ImportIRId::None;
|
|
}
|
|
return GetCanonicalImportIRInst(context, owning_inst_id).ir_id();
|
|
}
|
|
|
|
auto GetImplInfo(Context& context, SemIR::ImplId impl_id) -> ImplInfo {
|
|
const auto& impl = context.impls().Get(impl_id);
|
|
auto ir_id = GetIRId(context, impl.first_owning_decl_id);
|
|
return {.is_final = impl.is_final,
|
|
.witness_id = impl.witness_id,
|
|
.self_id = impl.self_id,
|
|
.latest_decl_id = impl.latest_decl_id(),
|
|
.interface = impl.interface,
|
|
.is_local = !ir_id.has_value(),
|
|
.ir_id = ir_id,
|
|
.type_structure =
|
|
BuildTypeStructure(context, impl.self_id, impl.interface)};
|
|
}
|
|
|
|
// A final impl must be in the same file as either its root self type or the
|
|
// interface in its constraint.
|
|
//
|
|
// Returns true if an error was diagnosed.
|
|
static auto DiagnoseFinalImplNotInSameFileAsRootSelfTypeOrInterface(
|
|
Context& context, const ImplInfo& impl, SemIR::ImportIRId interface_ir_id)
|
|
-> bool {
|
|
bool self_type_same_file = false;
|
|
|
|
auto type_iter = SemIR::TypeIterator(&context.sem_ir());
|
|
type_iter.Add(impl.self_id);
|
|
auto step = type_iter.Next();
|
|
|
|
using Step = SemIR::TypeIterator::Step;
|
|
CARBON_KIND_SWITCH(step.any) {
|
|
case CARBON_KIND(Step::ClassStart start): {
|
|
auto inst_id = context.classes().Get(start.class_id).first_owning_decl_id;
|
|
if (!GetIRId(context, inst_id).has_value()) {
|
|
self_type_same_file = true;
|
|
}
|
|
break;
|
|
}
|
|
case CARBON_KIND(Step::ClassStartOnly start): {
|
|
auto inst_id = context.classes().Get(start.class_id).first_owning_decl_id;
|
|
if (!GetIRId(context, inst_id).has_value()) {
|
|
self_type_same_file = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CARBON_KIND(Step::Done _):
|
|
CARBON_FATAL("self type is empty?");
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
bool interface_same_file = !interface_ir_id.has_value();
|
|
|
|
if (!self_type_same_file && !interface_same_file) {
|
|
CARBON_DIAGNOSTIC(FinalImplInvalidFile, Error,
|
|
"`final impl` found in file that does not contain "
|
|
"the root self type nor the interface definition");
|
|
context.emitter().Emit(impl.latest_decl_id, FinalImplInvalidFile);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// The type structure each non-final `impl` must differ from all other non-final
|
|
// `impl` for the same interface visible from the file.
|
|
//
|
|
// Returns true if an error was diagnosed.
|
|
static auto DiagnoseNonFinalImplsWithSameTypeStructure(Context& context,
|
|
const ImplInfo& impl_a,
|
|
const ImplInfo& impl_b)
|
|
-> bool {
|
|
if (impl_a.type_structure == impl_b.type_structure) {
|
|
CARBON_DIAGNOSTIC(ImplNonFinalSameTypeStructure, Error,
|
|
"found non-final `impl` with the same type "
|
|
"structure as another non-final `impl`");
|
|
auto builder = context.emitter().Build(impl_b.latest_decl_id,
|
|
ImplNonFinalSameTypeStructure);
|
|
CARBON_DIAGNOSTIC(ImplNonFinalSameTypeStructureNote, Note,
|
|
"other `impl` here");
|
|
builder.Note(impl_a.latest_decl_id, ImplNonFinalSameTypeStructureNote);
|
|
builder.Emit();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// An impl's self type and constraint can not match (as a lookup query)
|
|
// against any final impl, or it would never be used instead of that
|
|
// final impl.
|
|
//
|
|
// Returns true if an error was diagnosed.
|
|
static auto DiagnoseUnmatchableNonFinalImplWithFinalImpl(
|
|
Context& context, SemIR::ImplId impl_a_id, const ImplInfo& impl_a,
|
|
SemIR::ImplId impl_b_id, const ImplInfo& impl_b) -> bool {
|
|
auto diagnose_unmatchable_impl = [&](const ImplInfo& query_impl,
|
|
SemIR::ImplId final_impl_id,
|
|
const ImplInfo& final_impl) -> bool {
|
|
if (LookupMatchesImpl(context, SemIR::LocId(query_impl.latest_decl_id),
|
|
context.constant_values().Get(query_impl.self_id),
|
|
query_impl.interface, final_impl_id)) {
|
|
CARBON_DIAGNOSTIC(ImplFinalOverlapsNonFinal, Error,
|
|
"`impl` will never be used");
|
|
auto builder = context.emitter().Build(query_impl.latest_decl_id,
|
|
ImplFinalOverlapsNonFinal);
|
|
CARBON_DIAGNOSTIC(
|
|
ImplFinalOverlapsNonFinalNote, Note,
|
|
"`final impl` declared here would always be used instead");
|
|
builder.Note(final_impl.latest_decl_id, ImplFinalOverlapsNonFinalNote);
|
|
builder.Emit();
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
CARBON_CHECK(impl_a.is_final || impl_b.is_final);
|
|
|
|
if (impl_b.is_final) {
|
|
return diagnose_unmatchable_impl(impl_a, impl_b_id, impl_b);
|
|
} else {
|
|
return diagnose_unmatchable_impl(impl_b, impl_a_id, impl_a);
|
|
}
|
|
}
|
|
|
|
// Final impls that overlap in their type structure must be in the
|
|
// same file.
|
|
//
|
|
// Returns true if an error was diagnosed.
|
|
static auto DiagnoseFinalImplsOverlapInDifferentFiles(Context& context,
|
|
const ImplInfo& impl_a,
|
|
const ImplInfo& impl_b)
|
|
-> bool {
|
|
if (impl_a.ir_id != impl_b.ir_id) {
|
|
CARBON_DIAGNOSTIC(
|
|
FinalImplOverlapsDifferentFile, Error,
|
|
"`final impl` overlaps with `final impl` from another file");
|
|
CARBON_DIAGNOSTIC(FinalImplOverlapsDifferentFileNote, Note,
|
|
"imported `final impl` here");
|
|
if (impl_a.is_local) {
|
|
auto builder = context.emitter().Build(impl_a.latest_decl_id,
|
|
FinalImplOverlapsDifferentFile);
|
|
builder.Note(impl_b.latest_decl_id, FinalImplOverlapsDifferentFileNote);
|
|
builder.Emit();
|
|
} else {
|
|
auto builder = context.emitter().Build(impl_b.latest_decl_id,
|
|
FinalImplOverlapsDifferentFile);
|
|
builder.Note(impl_a.latest_decl_id, FinalImplOverlapsDifferentFileNote);
|
|
builder.Emit();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Two final impls in the same file can not overlap in their type
|
|
// structure if they are not in the same match_first block.
|
|
//
|
|
// TODO: Support for match_first needed here when they exist in the
|
|
// toolchain.
|
|
//
|
|
// Returns true if an error was diagnosed.
|
|
static auto DiagnoseFinalImplsOverlapOutsideMatchFirst(Context& context,
|
|
const ImplInfo& impl_a,
|
|
const ImplInfo& impl_b)
|
|
-> bool {
|
|
if (impl_a.is_local && impl_b.is_local) {
|
|
CARBON_DIAGNOSTIC(FinalImplOverlapsSameFile, Error,
|
|
"`final impl` overlaps with `final impl` from same file "
|
|
"outside a `match_first` block");
|
|
auto builder = context.emitter().Build(impl_b.latest_decl_id,
|
|
FinalImplOverlapsSameFile);
|
|
CARBON_DIAGNOSTIC(FinalImplOverlapsSameFileNote, Note,
|
|
"other `final impl` here");
|
|
builder.Note(impl_a.latest_decl_id, FinalImplOverlapsSameFileNote);
|
|
builder.Emit();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static auto ValidateImplsForInterface(
|
|
Context& context, llvm::ArrayRef<ImplAndInterface> impls_and_interface)
|
|
-> void {
|
|
// Range over `SemIR::ImplId` only. Caller has ensured all of these are
|
|
// for the same interface.
|
|
auto impl_ids = llvm::map_range(impls_and_interface,
|
|
[=](ImplAndInterface impl_and_interface) {
|
|
return impl_and_interface.impl_id;
|
|
});
|
|
|
|
// All `impl`s we look at here have the same `InterfaceId` (though different
|
|
// `SpecificId`s in their `SpecificInterface`s). So we can grab the
|
|
// `ImportIRId` for the interface a single time up front.
|
|
auto interface_decl_id =
|
|
context.interfaces()
|
|
.Get(impls_and_interface[0].interface.interface_id)
|
|
.first_owning_decl_id;
|
|
auto interface_ir_id = GetIRId(context, interface_decl_id);
|
|
|
|
for (auto impl_id : impl_ids) {
|
|
auto impl = GetImplInfo(context, impl_id);
|
|
if (impl.is_final && impl.is_local) {
|
|
// =======================================================================
|
|
/// Rules for an individual final impl.
|
|
// =======================================================================
|
|
DiagnoseFinalImplNotInSameFileAsRootSelfTypeOrInterface(context, impl,
|
|
interface_ir_id);
|
|
}
|
|
}
|
|
|
|
// TODO: We should revisit this and look for a way to do these checks in less
|
|
// than quadratic time. From @zygoloid: Possibly by converting the set of
|
|
// impls into a decision tree.
|
|
//
|
|
// For each impl, we compare it pair-wise which each impl found before it, so
|
|
// that diagnostics are attached to the later impl, as the earlier impl on its
|
|
// own does not generate a diagnostic.
|
|
size_t num_impl_ids = impls_and_interface.size();
|
|
for (auto [split_point, impl_b_id] :
|
|
llvm::drop_begin(llvm::enumerate(impl_ids))) {
|
|
auto impl_b = GetImplInfo(context, impl_b_id);
|
|
|
|
// Prevent diagnosing the same error multiple times for the same `impl_b`
|
|
// against different impls before it. But still ensure we do give one of
|
|
// each diagnostic when they are different errors.
|
|
bool did_diagnose_non_final_impls_with_same_type_structure = false;
|
|
bool did_diagnose_unmatchable_non_final_impl_with_final_impl = false;
|
|
bool did_diagnose_final_impls_overlap_in_different_files = false;
|
|
bool did_diagnose_final_impls_overlap_outside_match_first = false;
|
|
|
|
auto impls_before = llvm::drop_end(impl_ids, num_impl_ids - split_point);
|
|
for (auto impl_a_id : impls_before) {
|
|
auto impl_a = GetImplInfo(context, impl_a_id);
|
|
|
|
// Only enforce rules when at least one of the impls was written in this
|
|
// file.
|
|
if (!impl_a.is_local && !impl_b.is_local) {
|
|
continue;
|
|
}
|
|
|
|
if (!impl_a.is_final && !impl_b.is_final) {
|
|
// =====================================================================
|
|
// Rules between two non-final impls.
|
|
// =====================================================================
|
|
if (!did_diagnose_non_final_impls_with_same_type_structure) {
|
|
// Two impls in separate files will need to have some different
|
|
// concrete element in their type structure, as enforced by the orphan
|
|
// rule. So we don't need to check against non-local impls.
|
|
if (impl_a.is_local && impl_b.is_local) {
|
|
if (DiagnoseNonFinalImplsWithSameTypeStructure(context, impl_a,
|
|
impl_b)) {
|
|
// The same final `impl_a` may overlap with multiple `impl_b`s,
|
|
// and we want to diagnose each `impl_b`.
|
|
did_diagnose_non_final_impls_with_same_type_structure = true;
|
|
}
|
|
}
|
|
}
|
|
} else if (!impl_a.is_final || !impl_b.is_final) {
|
|
// =====================================================================
|
|
// Rules between final impl and non-final impl.
|
|
// =====================================================================
|
|
if (!did_diagnose_unmatchable_non_final_impl_with_final_impl) {
|
|
if (DiagnoseUnmatchableNonFinalImplWithFinalImpl(
|
|
context, impl_a_id, impl_a, impl_b_id, impl_b)) {
|
|
did_diagnose_unmatchable_non_final_impl_with_final_impl = true;
|
|
}
|
|
}
|
|
} else if (impl_a.type_structure.CompareStructure(
|
|
TypeStructure::CompareTest::HasOverlap,
|
|
impl_b.type_structure)) {
|
|
// =====================================================================
|
|
// Rules between two overlapping final impls.
|
|
// =====================================================================
|
|
CARBON_CHECK(impl_a.is_final && impl_b.is_final);
|
|
if (!did_diagnose_final_impls_overlap_in_different_files) {
|
|
if (DiagnoseFinalImplsOverlapInDifferentFiles(context, impl_a,
|
|
impl_b)) {
|
|
did_diagnose_final_impls_overlap_in_different_files = true;
|
|
}
|
|
}
|
|
if (!did_diagnose_final_impls_overlap_outside_match_first) {
|
|
if (DiagnoseFinalImplsOverlapOutsideMatchFirst(context, impl_a,
|
|
impl_b)) {
|
|
did_diagnose_final_impls_overlap_outside_match_first = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// For each `impl` seen in this file, ensure that we import every available
|
|
// `final impl` for the same interface, so that we can to check for
|
|
// diagnostics about the relationship between them and the `impl`s in this
|
|
// file.
|
|
static auto ImportFinalImplsWithImplInFile(Context& context) -> void {
|
|
struct InterfaceToImport {
|
|
SemIR::ImportIRId ir_id;
|
|
SemIR::InterfaceId interface_id;
|
|
|
|
constexpr auto operator==(const InterfaceToImport& rhs) const
|
|
-> bool = default;
|
|
constexpr auto operator<=>(const InterfaceToImport& rhs) const -> auto {
|
|
if (ir_id != rhs.ir_id) {
|
|
return ir_id.index <=> rhs.ir_id.index;
|
|
}
|
|
return interface_id.index <=> rhs.interface_id.index;
|
|
}
|
|
};
|
|
|
|
llvm::SmallVector<InterfaceToImport> interfaces_to_import;
|
|
for (const auto& impl : context.impls().values()) {
|
|
if (impl.witness_id == SemIR::ErrorInst::InstId) {
|
|
continue;
|
|
}
|
|
auto impl_import_ir_id = GetIRId(context, impl.first_owning_decl_id);
|
|
if (impl_import_ir_id.has_value()) {
|
|
// Only import `impl`s of interfaces for which there is a local `impl` of
|
|
// that that interface.
|
|
continue;
|
|
}
|
|
|
|
auto interface_id = impl.interface.interface_id;
|
|
const auto& interface = context.interfaces().Get(interface_id);
|
|
auto import_ir_id = GetIRId(context, interface.first_owning_decl_id);
|
|
if (!import_ir_id.has_value()) {
|
|
continue;
|
|
}
|
|
interfaces_to_import.push_back(
|
|
{.ir_id = import_ir_id, .interface_id = interface_id});
|
|
}
|
|
|
|
llvm::sort(interfaces_to_import);
|
|
llvm::unique(interfaces_to_import);
|
|
|
|
struct ImplToImport {
|
|
SemIR::ImportIRId ir_id;
|
|
SemIR::ImplId import_impl_id;
|
|
|
|
constexpr auto operator==(const ImplToImport& rhs) const -> bool = default;
|
|
constexpr auto operator<=>(const ImplToImport& rhs) const -> auto {
|
|
if (ir_id != rhs.ir_id) {
|
|
return ir_id.index <=> rhs.ir_id.index;
|
|
}
|
|
return import_impl_id.index <=> rhs.import_impl_id.index;
|
|
}
|
|
};
|
|
|
|
llvm::SmallVector<ImplToImport> impls_to_import;
|
|
for (auto [ir_id, interface_id] : interfaces_to_import) {
|
|
const SemIR::File& sem_ir = *context.import_irs().Get(ir_id).sem_ir;
|
|
for (auto [impl_id, impl] : sem_ir.impls().enumerate()) {
|
|
if (impl.is_final && impl.interface.interface_id == interface_id) {
|
|
impls_to_import.push_back({.ir_id = ir_id, .import_impl_id = impl_id});
|
|
}
|
|
}
|
|
}
|
|
|
|
llvm::sort(impls_to_import);
|
|
llvm::unique(impls_to_import);
|
|
|
|
for (auto [ir_id, import_impl_id] : impls_to_import) {
|
|
ImportImpl(context, ir_id, import_impl_id);
|
|
}
|
|
}
|
|
|
|
auto ValidateImplsInFile(Context& context) -> void {
|
|
ImportFinalImplsWithImplInFile(context);
|
|
|
|
// Collect all of the impls sorted into contiguous segments by their
|
|
// interface. We only need to compare impls within each such segment. We don't
|
|
// keep impls with an Error in them, as they may be missing other values
|
|
// needed to check the diagnostics and they already have a diagnostic printed
|
|
// about them anyhow. We also verify the impl has an `InterfaceId` since it
|
|
// can be missing, in which case a diagnostic would have been generated
|
|
// already as well.
|
|
//
|
|
// Don't hold Impl pointers here because the process of looking for
|
|
// diagnostics may cause imports and may invalidate pointers into the
|
|
// ImplStore.
|
|
llvm::SmallVector<ImplAndInterface> impl_ids_by_interface(llvm::map_range(
|
|
llvm::make_filter_range(
|
|
context.impls().enumerate(),
|
|
[](std::pair<SemIR::ImplId, const SemIR::Impl&> pair) {
|
|
return pair.second.witness_id != SemIR::ErrorInst::InstId &&
|
|
pair.second.interface.interface_id.has_value();
|
|
}),
|
|
[](std::pair<SemIR::ImplId, const SemIR::Impl&> pair) {
|
|
return ImplAndInterface{.impl_id = pair.first,
|
|
.interface = pair.second.interface};
|
|
}));
|
|
llvm::stable_sort(impl_ids_by_interface, [](const ImplAndInterface& lhs,
|
|
const ImplAndInterface& rhs) {
|
|
return lhs.interface.interface_id.index < rhs.interface.interface_id.index;
|
|
});
|
|
|
|
const auto* it = impl_ids_by_interface.begin();
|
|
while (it != impl_ids_by_interface.end()) {
|
|
const auto* segment_begin = it;
|
|
auto begin_interface_id = segment_begin->interface.interface_id;
|
|
do {
|
|
++it;
|
|
} while (it != impl_ids_by_interface.end() &&
|
|
it->interface.interface_id == begin_interface_id);
|
|
const auto* segment_end = it;
|
|
|
|
ValidateImplsForInterface(context, {segment_begin, segment_end});
|
|
}
|
|
}
|
|
|
|
} // namespace Carbon::Check
|