Files
carbon-lang/toolchain/check/class.cpp
T
Chandler Carruth ae4be1be14 Use inline small storage for small SemIR ID sets in toolchain (#7796)
Apply SmallSize = 16 to frequent identifier and instruction/function
sets in ScopeStack, Class, FacetType, and SpecificCoalescer to avoid
dynamic heap allocations on small scopes. Also defer dest_field_names
set allocation in struct conversion and provide default KeyContext for
SetBase. This was found by inspection, but does seem to be a clear 1.5%
win on overall compile time.

```
Ran baseline and experiment 10 times on 128 x 2450 MHz CPUs
CPU caches:
  L1 Data 32Ki
  L1 Instruction 32Ki
  L2 Unified 512Ki
  L3 Unified 32Mi
Load avg: 1.2041 1.16895 2.86133
Computing statistically significant deltas only wherethe P-value < 𝛂 of 0.05
Metric key:
   BenchmarkName... 👍 <delta>    p=<U-test P-value>
          baseline:    <median> ± <% at 95th conf>
        experiment:    <median> ± <% at 95th conf>

 Benchmark                                                      ┃          CPU Time          ┃           CYCLES           ┃       INSTRUCTIONS        ┃          Lines
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━
 BM_CompileApiFileDenseDecls<Lang::Carbon, Phase::Check>/256... │ 👍  -1.768%      p=0.0346  │ 👍  -1.683%      p=0.0346  │ 👍   0.160%    p=0.000428 │ ~
                                                      baseline: │     49.11  ms  ±   1.363%  │    155.6   M   ±   1.875%  │    293.9   M ±   0.015%   │     4.093 k ±   1.360%
                                                    experiment: │     48.24  ms  ±   2.213%  │    153     M   ±   2.465%  │    293.4   M ±   0.081%   │     4.166 k ±   2.165%
                                                                │                            │                            │                           │
 BM_CompileApiFileDenseDecls<Lang::Carbon, Phase::Check>/1024.. │      ??          p=0.159   │      ??          p=0.067   │ 👍   0.153%    p=0.000249 │ ~
                                                      baseline: │     50.61  ms  ±   2.379%  │    160.7   M   ±   2.777%  │    309.7   M ±   0.015%   │    19.96  k ±   2.326%
                                                    experiment: │     50.32  ms  ±   0.971%  │    159.6   M   ±   0.787%  │    309.2   M ±   0.086%   │    20.07  k ±   0.980%
                                                                │                            │                            │                           │
 BM_CompileApiFileDenseDecls<Lang::Carbon, Phase::Check>/4096.. │ 👍  -1.593%      p=0.0112  │ 👍  -1.632%      p=0.00743 │ 👍   0.138%    p=0.000328 │ ~
                                                      baseline: │     58.58  ms  ±   1.673%  │    186.5   M   ±   1.487%  │    371.2   M ±   0.102%   │    70.96  k ±   1.645%
                                                    experiment: │     57.65  ms  ±   1.032%  │    183.5   M   ±   1.079%  │    370.7   M ±   0.091%   │    72.11  k ±   1.043%
                                                                │                            │                            │                           │
 BM_CompileApiFileDenseDecls<Lang::Carbon, Phase::Check>/16384. │ 👍  -1.695%      p=0.029   │ 👍  -1.823%      p=0.0411  │ 👍   0.221%    p=0.000428 │ ~
                                                      baseline: │     90.07  ms  ±   3.686%  │    287.1   M   ±   3.694%  │    618.9   M ±   0.106%   │   186.9   k ±   3.555%
                                                    experiment: │     88.55  ms  ±   1.891%  │    281.8   M   ±   2.142%  │    617.6   M ±   0.158%   │   190.1   k ±   1.856%
                                                                │                            │                            │                           │
 BM_CompileApiFileDenseDecls<Lang::Carbon, Phase::Check>/65536. │ 👍  -1.797%      p=0.0201  │ 👍  -1.762%      p=0.0201  │ 👍   0.222%    p=0.000328 │ ~
                                                      baseline: │    222.1   ms  ±   2.560%  │    711     M   ±   2.417%  │      1.613 G ±   0.158%   │   304.2   k ±   2.496%
                                                    experiment: │    218.1   ms  ±   1.950%  │    698.5   M   ±   2.010%  │      1.61  G ±   0.075%   │   309.7   k ±   1.989%
                                                                │                            │                            │                           │
 BM_CompileApiFileDenseDecls<Lang::Carbon, Phase::Check>/262144 │      ??          p=0.398   │      ??          p=0.36    │ 👍   0.166%    p=0.000931 │ ~
                                                      baseline: │    782.2   ms  ±   2.666%  │      2.502 G   ±   2.546%  │      5.596 G ±   0.038%   │   345.8   k ±   2.597%
                                                    experiment: │    780.6   ms  ±   4.249%  │      2.483 G   ±   4.691%  │      5.587 G ±   0.024%   │   346.5   k ±   4.075%
                                                                │                            │                            │                           │
```

Assisted-by: Antigravity with Gemini
2026-09-17 15:49:43 +00:00

458 lines
20 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/class.h"
#include "toolchain/check/context.h"
#include "toolchain/check/convert.h"
#include "toolchain/check/cpp/export.h"
#include "toolchain/check/eval.h"
#include "toolchain/check/function.h"
#include "toolchain/check/generic.h"
#include "toolchain/check/impl.h"
#include "toolchain/check/import_ref.h"
#include "toolchain/check/inst.h"
#include "toolchain/check/name_lookup.h"
#include "toolchain/check/pattern.h"
#include "toolchain/check/pattern_match.h"
#include "toolchain/check/thunk.h"
#include "toolchain/check/type.h"
#include "toolchain/diagnostics/format_providers.h"
#include "toolchain/parse/node_ids.h"
#include "toolchain/sem_ir/builtin_function_kind.h"
#include "toolchain/sem_ir/function.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/typed_insts.h"
namespace Carbon::Check {
auto SetClassSelfType(Context& context, SemIR::ClassId class_id) -> void {
auto& class_info = context.classes().Get(class_id);
auto specific_id = context.generics().GetSelfSpecific(class_info.generic_id);
class_info.self_type_id = GetClassType(context, class_id, specific_id);
}
auto StartClassDefinition(Context& context, SemIR::Class& class_info,
SemIR::InstId definition_id) -> void {
// Track that this declaration is the definition.
CARBON_CHECK(!class_info.has_definition_started());
class_info.definition_id = definition_id;
class_info.scope_id = context.name_scopes().Add(
definition_id, SemIR::NameId::None, class_info.parent_scope_id);
// Introduce `Self`.
auto self_type_inst_id =
context.types().GetTypeInstId(class_info.self_type_id);
context.name_scopes().AddRequiredName(
class_info.scope_id, SemIR::NameId::SelfType, self_type_inst_id);
context.name_scopes()
.Get(class_info.scope_id)
.set_self_type_id(self_type_inst_id);
}
// Checks that the specified finished adapter definition is valid and builds and
// returns a corresponding complete type witness instruction.
static auto CheckCompleteAdapterClassType(
Context& context, Parse::NodeId node_id, SemIR::ClassId class_id,
llvm::ArrayRef<SemIR::InstId> field_decls,
llvm::ArrayRef<SemIR::InstId> body) -> SemIR::InstId {
const auto& class_info = context.classes().Get(class_id);
if (class_info.base_id.has_value()) {
CARBON_DIAGNOSTIC(AdaptWithBase, Error, "adapter with base class");
CARBON_DIAGNOSTIC(AdaptWithBaseHere, Note, "`base` declaration is here");
context.emitter()
.Build(class_info.adapt_id, AdaptWithBase)
.Note(class_info.base_id, AdaptWithBaseHere)
.Emit();
return SemIR::ErrorInst::InstId;
}
if (!field_decls.empty()) {
CARBON_DIAGNOSTIC(AdaptWithFields, Error, "adapter with fields");
CARBON_DIAGNOSTIC(AdaptWithFieldHere, Note,
"first field declaration is here");
context.emitter()
.Build(class_info.adapt_id, AdaptWithFields)
.Note(field_decls.front(), AdaptWithFieldHere)
.Emit();
return SemIR::ErrorInst::InstId;
}
for (auto inst_id : body) {
if (auto function_decl =
context.insts().TryGetAs<SemIR::FunctionDecl>(inst_id)) {
auto& function = context.functions().Get(function_decl->function_id);
if (function.virtual_modifier ==
SemIR::Function::VirtualModifier::Virtual) {
CARBON_DIAGNOSTIC(AdaptWithVirtual, Error,
"adapter with virtual function");
CARBON_DIAGNOSTIC(AdaptWithVirtualHere, Note,
"first virtual function declaration is here");
context.emitter()
.Build(class_info.adapt_id, AdaptWithVirtual)
.Note(inst_id, AdaptWithVirtualHere)
.Emit();
return SemIR::ErrorInst::InstId;
}
}
}
// The object representation of the adapter is the object representation
// of the adapted type.
auto adapted_type_id =
class_info.GetAdaptedType(context.sem_ir(), SemIR::SpecificId::None);
auto object_repr_id = context.types().GetObjectRepr(adapted_type_id);
return AddInst<SemIR::CompleteTypeWitness>(
context, node_id,
{.type_id = GetSingletonType(context, SemIR::WitnessType::TypeInstId),
// TODO: Use InstId from the adapt declaration.
.object_repr_type_inst_id =
context.types().GetTypeInstId(object_repr_id)});
}
static auto AddStructTypeFields(
Context& context,
llvm::SmallVector<SemIR::StructTypeField>& struct_type_fields,
llvm::ArrayRef<SemIR::InstId> field_decls) -> SemIR::StructTypeFieldsId {
for (auto field_decl_id : field_decls) {
auto field_decl = context.insts().GetAs<SemIR::FieldDecl>(field_decl_id);
auto& field = context.fields().Get(field_decl.field_id);
field.index =
SemIR::ElementIndex{static_cast<int>(struct_type_fields.size())};
if (field_decl.type_id == SemIR::ErrorInst::TypeId) {
struct_type_fields.push_back(
{.name_id = field.name_id,
.type_inst_id = SemIR::ErrorInst::TypeInstId});
continue;
}
auto unbound_element_type =
context.sem_ir().types().GetAs<SemIR::UnboundElementType>(
field_decl.type_id);
struct_type_fields.push_back(
{.name_id = field.name_id,
.type_inst_id = unbound_element_type.element_type_inst_id});
}
auto fields_id =
context.struct_type_fields().AddCanonical(struct_type_fields);
return fields_id;
}
// Result of comparing a virtual function in a base class with a potential
// overrider in a derived class.
enum class OverrideMatchResult : uint8_t {
// The functions match.
Match,
// The potential overrider is not marked `override`.
NotAnOverride,
// The names do not match.
NameMismatch,
// The arity (number of explicit parameters) does not match.
ArityMismatch,
};
// Compares a virtual function in a base class with a potential overrider in a
// derived class.
static auto CompareVirtualWithOverrider(const SemIR::Function& base_fn,
const SemIR::Function& derived_fn)
-> OverrideMatchResult {
if (derived_fn.virtual_modifier !=
SemIR::FunctionFields::VirtualModifier::Override) {
return OverrideMatchResult::NotAnOverride;
}
if (derived_fn.name_id != base_fn.name_id) {
return OverrideMatchResult::NameMismatch;
}
if (derived_fn.call_param_ranges.explicit_size() !=
base_fn.call_param_ranges.explicit_size()) {
return OverrideMatchResult::ArityMismatch;
}
// TODO: We should check more thoroughly for compatibility between the two
// functions here, so that we can determine which function is being overridden
// if the base function is in a C++ overload set.
return OverrideMatchResult::Match;
}
// Builds and returns a vtable for the current class. Assumes that the virtual
// functions for the class are listed as the top element of the `vtable_stack`.
static auto BuildVtable(Context& context, Parse::ClassDefinitionId node_id,
SemIR::ClassId class_id,
std::optional<SemIR::ClassType> base_class_type,
llvm::ArrayRef<SemIR::InstId> vtable_contents)
-> SemIR::VtableId {
auto base_vtable_id = SemIR::VtableId::None;
auto base_class_specific_id = SemIR::SpecificId::None;
// Get some base class/type/specific info.
if (base_class_type) {
auto& base_class_info = context.classes().Get(base_class_type->class_id);
auto base_vtable_decl_inst_id = base_class_info.vtable_decl_id;
if (base_vtable_decl_inst_id.has_value()) {
LoadImportRef(context, base_vtable_decl_inst_id);
auto canonical_base_vtable_inst_id =
context.constant_values().GetConstantInstId(base_vtable_decl_inst_id);
const auto& base_vtable_decl_inst =
context.insts().GetAs<SemIR::VtableDecl>(
canonical_base_vtable_inst_id);
base_vtable_id = base_vtable_decl_inst.vtable_id;
base_class_specific_id = base_class_type->specific_id;
}
}
const auto& class_info = context.classes().Get(class_id);
auto class_generic_id = class_info.generic_id;
// Wrap vtable entries in SpecificFunctions as needed/in generic classes.
auto build_specific_function =
[&](SemIR::InstId fn_decl_id) -> SemIR::InstId {
if (!class_generic_id.has_value()) {
return fn_decl_id;
}
const auto& fn_decl =
context.insts().GetAs<SemIR::FunctionDecl>(fn_decl_id);
const auto& function = context.functions().Get(fn_decl.function_id);
return GetOrAddInst<SemIR::SpecificFunction>(
context, node_id,
{.type_id =
GetSingletonType(context, SemIR::SpecificFunctionType::TypeInstId),
.callee_id = fn_decl_id,
.specific_id =
context.generics().GetSelfSpecific(function.generic_id)});
};
llvm::SmallVector<SemIR::InstId> vtable;
Set<SemIR::FunctionId, 16> implemented_impls;
bool carbon_native_vtable = true;
// Add vtable entries from the base class, updating them to point to a derived
// class overrider if there is one.
if (base_vtable_id.has_value()) {
const auto& base_vtable = context.vtables().Get(base_vtable_id);
carbon_native_vtable = base_vtable.carbon_native_vtable;
auto base_vtable_inst_block =
context.inst_blocks().Get(base_vtable.virtual_functions_id);
// TODO: Avoid quadratic search. Perhaps build a map from `NameId` to the
// elements of the top of `vtable_stack`.
for (auto base_vtable_entry_id : base_vtable_inst_block) {
if (!base_vtable_entry_id.has_value()) {
// Foreign vtables may have holes in them for information that we don't
// use. Just skip those entries.
CARBON_CHECK(
!context.vtables().Get(base_vtable_id).carbon_native_vtable);
vtable.push_back(SemIR::InstId::None);
continue;
}
auto [derived_vtable_entry_id, derived_vtable_entry_const_id, fn_id,
specific_id] =
DecomposeVirtualFunction(context.sem_ir(), base_vtable_entry_id,
base_class_specific_id);
const auto& fn = context.sem_ir().functions().Get(fn_id);
const auto* i = llvm::find_if(
vtable_contents, [&](SemIR::InstId override_fn_decl_id) -> bool {
const auto& override_fn = context.functions().Get(
context.insts()
.GetAs<SemIR::FunctionDecl>(override_fn_decl_id)
.function_id);
return CompareVirtualWithOverrider(fn, override_fn) ==
OverrideMatchResult::Match;
});
if (i != vtable_contents.end()) {
auto override_fn_id =
context.insts().GetAs<SemIR::FunctionDecl>(*i).function_id;
implemented_impls.Insert(override_fn_id);
// TODO: When the base class is a C++ class, we could have multiple
// potential functions to override. Check against each of them rather
// than trying to override them all.
auto override_or_thunk_id =
BuildThunk(context, fn_id, specific_id, class_info.self_type_id, *i,
/*defer_definition=*/true);
if (override_or_thunk_id != SemIR::ErrorInst::InstId) {
auto override_or_thunk_fn_id =
context.insts()
.GetAs<SemIR::FunctionDecl>(override_or_thunk_id)
.function_id;
auto& override_or_thunk_fn =
context.functions().Get(override_or_thunk_fn_id);
derived_vtable_entry_id =
build_specific_function(override_or_thunk_id);
override_or_thunk_fn.virtual_index = vtable.size();
CARBON_CHECK(override_or_thunk_fn.virtual_index == fn.virtual_index);
}
} else if (auto base_vtable_specific_function =
context.insts().TryGetAs<SemIR::SpecificFunction>(
derived_vtable_entry_id)) {
if (derived_vtable_entry_const_id.is_symbolic()) {
// Create a new instruction here that is otherwise identical to
// `derived_vtable_entry_id` but is dependent within the derived
// class. This ensures we can `GetConstantValueInSpecific` for it
// with the derived class's specific (when forming further derived
// classes, lowering the vtable, etc).
derived_vtable_entry_id = GetOrAddInst<SemIR::SpecificFunction>(
context, node_id,
{.type_id = GetSingletonType(
context, SemIR::SpecificFunctionType::TypeInstId),
.callee_id = base_vtable_specific_function->callee_id,
.specific_id = base_vtable_specific_function->specific_id});
}
}
vtable.push_back(derived_vtable_entry_id);
}
}
// Add any remaining virtual functions from the derived class to the vtable,
// and diagnose any `override fn`s that didn't override anything.
for (auto inst_id : vtable_contents) {
auto fn_decl = context.insts().GetAs<SemIR::FunctionDecl>(inst_id);
auto& fn = context.functions().Get(fn_decl.function_id);
if (fn.virtual_modifier !=
SemIR::FunctionFields::VirtualModifier::Override) {
fn.virtual_index = vtable.size();
vtable.push_back(build_specific_function(inst_id));
} else if (!implemented_impls.Lookup(fn_decl.function_id)) {
CARBON_DIAGNOSTIC(OverrideWithoutVirtualInBase, Error,
"override without compatible virtual in base class");
CARBON_DIAGNOSTIC(OverrideCandidateArityMismatch, Note,
"base class function has {2:more|fewer} parameters "
"({0} vs {1} excluding `self`)",
Diagnostics::IntAsSelect, Diagnostics::IntAsSelect,
Diagnostics::BoolAsSelect);
auto builder = context.emitter().Build(SemIR::LocId(inst_id),
OverrideWithoutVirtualInBase);
if (base_vtable_id.has_value()) {
const auto& base_vtable = context.vtables().Get(base_vtable_id);
auto base_vtable_inst_block =
context.inst_blocks().Get(base_vtable.virtual_functions_id);
for (auto base_vtable_entry_id : base_vtable_inst_block) {
if (!base_vtable_entry_id.has_value()) {
continue;
}
auto [derived_vtable_entry_id, derived_vtable_entry_const_id, fn_id,
specific_id] =
DecomposeVirtualFunction(context.sem_ir(), base_vtable_entry_id,
base_class_specific_id);
const auto& base_fn = context.sem_ir().functions().Get(fn_id);
switch (CompareVirtualWithOverrider(base_fn, fn)) {
case OverrideMatchResult::ArityMismatch:
builder.Note(base_fn.first_owning_decl_id,
OverrideCandidateArityMismatch,
base_fn.call_param_ranges.explicit_size() - 1,
fn.call_param_ranges.explicit_size() - 1,
base_fn.call_param_ranges.explicit_size() >
fn.call_param_ranges.explicit_size());
break;
case OverrideMatchResult::NameMismatch:
// TODO: If the name is similar enough and the overrider otherwise
// matches, emit a note about the potential misspelling.
break;
case OverrideMatchResult::Match:
CARBON_FATAL("Unexpectedly found a matching overrider");
case OverrideMatchResult::NotAnOverride:
CARBON_FATAL("Should only consider `override fn`s here");
}
}
}
builder.Emit();
}
}
return context.vtables().Add(
{{.class_id = class_id,
.virtual_functions_id = context.inst_blocks().Add(vtable),
.carbon_native_vtable = carbon_native_vtable}});
}
// Checks that the specified finished class definition is valid and builds and
// returns a corresponding complete type witness instruction.
static auto CheckCompleteClassType(
Context& context, Parse::ClassDefinitionId node_id, SemIR::ClassId class_id,
llvm::ArrayRef<SemIR::InstId> field_decls,
llvm::ArrayRef<SemIR::InstId> vtable_contents,
llvm::ArrayRef<SemIR::InstId> body) -> SemIR::InstId {
auto& class_info = context.classes().Get(class_id);
if (class_info.adapt_id.has_value()) {
return CheckCompleteAdapterClassType(context, node_id, class_id,
field_decls, body);
}
bool defining_vptr = class_info.is_dynamic;
auto base_type_id =
class_info.GetBaseType(context.sem_ir(), SemIR::SpecificId::None);
// TODO: Use InstId from base declaration.
auto base_type_inst_id = context.types().GetTypeInstId(base_type_id);
std::optional<SemIR::ClassType> base_class_type;
if (base_type_id.has_value()) {
// TODO: If the base class is template dependent, we will need to decide
// whether to add a vptr as part of instantiation.
base_class_type = context.types().TryGetAs<SemIR::ClassType>(base_type_id);
if (base_class_type &&
context.classes().Get(base_class_type->class_id).is_dynamic) {
defining_vptr = false;
}
}
llvm::SmallVector<SemIR::StructTypeField> struct_type_fields;
struct_type_fields.reserve(defining_vptr + class_info.base_id.has_value() +
field_decls.size());
if (defining_vptr) {
struct_type_fields.push_back(
{.name_id = SemIR::NameId::Vptr,
.type_inst_id = context.types().GetTypeInstId(
GetPointerType(context, SemIR::VtableType::TypeInstId))});
}
if (base_type_id.has_value()) {
auto base_decl = context.insts().GetAs<SemIR::BaseDecl>(class_info.base_id);
base_decl.index =
SemIR::ElementIndex{static_cast<int>(struct_type_fields.size())};
ReplaceInstPreservingConstantValue(context, class_info.base_id, base_decl);
struct_type_fields.push_back(
{.name_id = SemIR::NameId::Base, .type_inst_id = base_type_inst_id});
}
if (class_info.is_dynamic) {
auto vtable_id = BuildVtable(context, node_id, class_id, base_class_type,
vtable_contents);
auto vptr_type_id = GetPointerType(context, SemIR::VtableType::TypeInstId);
class_info.vtable_decl_id = AddInst<SemIR::VtableDecl>(
context, node_id, {.type_id = vptr_type_id, .vtable_id = vtable_id});
}
auto struct_type_id = GetStructType(
context, AddStructTypeFields(context, struct_type_fields, field_decls));
return AddInst<SemIR::CompleteTypeWitness>(
context, node_id,
{.type_id = GetSingletonType(context, SemIR::WitnessType::TypeInstId),
.object_repr_type_inst_id =
context.types().GetTypeInstId(struct_type_id)});
}
auto ComputeClassObjectRepr(Context& context, Parse::ClassDefinitionId node_id,
SemIR::ClassId class_id,
llvm::ArrayRef<SemIR::InstId> field_decls,
llvm::ArrayRef<SemIR::InstId> vtable_contents,
llvm::ArrayRef<SemIR::InstId> body) -> void {
auto complete_type_witness_id = CheckCompleteClassType(
context, node_id, class_id, field_decls, vtable_contents, body);
auto& class_info = context.classes().Get(class_id);
class_info.complete_type_witness_id = complete_type_witness_id;
}
auto InNonStaticFieldDecl(Context& context) -> bool {
return context.full_pattern_stack().IsCurrentKindClassScopeVarDecl() &&
!context.decl_introducer_state_stack()
.innermost()
.modifier_set.HasAnyOf(KeywordModifierSet::Static);
}
auto InStaticClassScopeVar(Context& context) -> bool {
return context.full_pattern_stack().IsCurrentKindClassScopeVarDecl() &&
context.decl_introducer_state_stack()
.innermost()
.modifier_set.HasAnyOf(KeywordModifierSet::Static);
}
} // namespace Carbon::Check