mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 22:02:55 +01:00
lazy field index (#4514)
We considered a couple of other options for this: * https://github.com/carbon-language/carbon-lang/pull/4515 Keep the `ElementIndex` numbering vptr-ignorant, and do +1 offsets as needed - seems subtle/easy to miss * https://github.com/carbon-language/carbon-lang/pull/4517 Always have a zeroth element in the object representation, make it zero-size in the case of no-vptr - @zygoloid was concerned this would add overhead especially to stateless objects used in type-trait-like things. But currently moving forward with this direction - of initializing field indexes with an invalid value until the end of the class definition, then assigning field indexes during construction of the class's object representation struct type. This direction might reinforce/help avoid premature access to the object representation before the class is complete, and give a single place where class layout is done (at class completion) if we want to add more options there, such as class layout optimizations, etc. This patch still has problems with object initialization (that #4515 does not have/does address) but does address normal `obj.member` access correctly. --------- Co-authored-by: Richard Smith <richard@metafoo.co.uk>
This commit is contained in:
co-authored by
Richard Smith
parent
9c71151e34
commit
f921923b4b
@@ -295,7 +295,7 @@ auto HandleParseNode(Context& context, Parse::ClassDefinitionStartId node_id)
|
||||
|
||||
context.inst_block_stack().Push();
|
||||
context.node_stack().Push(node_id, class_id);
|
||||
context.struct_type_fields_stack().PushArray();
|
||||
context.field_decls_stack().PushArray();
|
||||
|
||||
// TODO: Handle the case where there's control flow in the class body. For
|
||||
// example:
|
||||
@@ -512,7 +512,7 @@ auto HandleParseNode(Context& context, Parse::BaseDeclId node_id) -> bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!context.struct_type_fields_stack().PeekArray().empty()) {
|
||||
if (!context.field_decls_stack().PeekArray().empty()) {
|
||||
// TODO: Add note that includes the first field location as an example.
|
||||
CARBON_DIAGNOSTIC(
|
||||
BaseDeclAfterFieldDecl, Error,
|
||||
@@ -523,6 +523,8 @@ auto HandleParseNode(Context& context, Parse::BaseDeclId node_id) -> bool {
|
||||
|
||||
auto base_info = CheckBaseType(context, base_type_node_id, base_type_expr_id);
|
||||
|
||||
// TODO: Should we diagnose if there are already any fields?
|
||||
|
||||
// The `base` value in the class scope has an unbound element type. Instance
|
||||
// binding will be performed when it's found by name lookup into an instance.
|
||||
auto field_type_id =
|
||||
@@ -530,8 +532,7 @@ auto HandleParseNode(Context& context, Parse::BaseDeclId node_id) -> bool {
|
||||
class_info.base_id = context.AddInst<SemIR::BaseDecl>(
|
||||
node_id, {.type_id = field_type_id,
|
||||
.base_type_id = base_info.type_id,
|
||||
.index = SemIR::ElementIndex(
|
||||
context.struct_type_fields_stack().PeekArray().size())});
|
||||
.index = SemIR::ElementIndex::Invalid});
|
||||
|
||||
if (base_info.type_id != SemIR::TypeId::Error) {
|
||||
auto base_class_info = context.classes().Get(
|
||||
@@ -539,12 +540,6 @@ auto HandleParseNode(Context& context, Parse::BaseDeclId node_id) -> bool {
|
||||
class_info.is_dynamic |= base_class_info.is_dynamic;
|
||||
}
|
||||
|
||||
// Add a corresponding field to the object representation of the class.
|
||||
// TODO: Consider whether we want to use `partial T` here.
|
||||
// TODO: Should we diagnose if there are already any fields?
|
||||
context.struct_type_fields_stack().AppendToTop(
|
||||
{.name_id = SemIR::NameId::Base, .type_id = base_info.type_id});
|
||||
|
||||
// Bind the name `base` in the class to the base field.
|
||||
context.decl_name_stack().AddNameOrDiagnoseDuplicate(
|
||||
context.decl_name_stack().MakeUnqualifiedName(node_id,
|
||||
@@ -567,8 +562,7 @@ auto HandleParseNode(Context& context, Parse::BaseDeclId node_id) -> bool {
|
||||
// returns a corresponding complete type witness instruction.
|
||||
static auto CheckCompleteAdapterClassType(Context& context,
|
||||
Parse::NodeId node_id,
|
||||
SemIR::ClassId class_id,
|
||||
SemIR::StructTypeFieldsId fields_id)
|
||||
SemIR::ClassId class_id)
|
||||
-> SemIR::InstId {
|
||||
const auto& class_info = context.classes().Get(class_id);
|
||||
if (class_info.base_id.is_valid()) {
|
||||
@@ -581,17 +575,14 @@ static auto CheckCompleteAdapterClassType(Context& context,
|
||||
return SemIR::InstId::BuiltinErrorInst;
|
||||
}
|
||||
|
||||
if (auto fields = context.struct_type_fields().Get(fields_id);
|
||||
!fields.empty()) {
|
||||
auto [first_field_inst_id, _] = context.LookupNameInExactScope(
|
||||
node_id, fields.front().name_id, class_info.scope_id,
|
||||
context.name_scopes().Get(class_info.scope_id));
|
||||
auto field_decls = context.field_decls_stack().PeekArray();
|
||||
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(first_field_inst_id, AdaptWithFieldHere)
|
||||
.Note(field_decls.front(), AdaptWithFieldHere)
|
||||
.Emit();
|
||||
return SemIR::InstId::BuiltinErrorInst;
|
||||
}
|
||||
@@ -637,6 +628,31 @@ static auto CheckCompleteAdapterClassType(Context& context,
|
||||
{.type_id = context.GetBuiltinType(SemIR::BuiltinInstKind::WitnessType),
|
||||
.object_repr_id = adapted_type_id});
|
||||
}
|
||||
static auto AddStructTypeFields(
|
||||
Context& context,
|
||||
llvm::SmallVector<SemIR::StructTypeField>& struct_type_fields)
|
||||
-> SemIR::StructTypeFieldsId {
|
||||
for (auto field_decl_id : context.field_decls_stack().PeekArray()) {
|
||||
auto field_decl = context.insts().GetAs<SemIR::FieldDecl>(field_decl_id);
|
||||
field_decl.index =
|
||||
SemIR::ElementIndex{static_cast<int>(struct_type_fields.size())};
|
||||
context.ReplaceInstPreservingConstantValue(field_decl_id, field_decl);
|
||||
if (field_decl.type_id == SemIR::TypeId::Error) {
|
||||
struct_type_fields.push_back(
|
||||
{.name_id = field_decl.name_id, .type_id = SemIR::TypeId::Error});
|
||||
continue;
|
||||
}
|
||||
auto unbound_element_type =
|
||||
context.sem_ir().types().GetAs<SemIR::UnboundElementType>(
|
||||
field_decl.type_id);
|
||||
struct_type_fields.push_back(
|
||||
{.name_id = field_decl.name_id,
|
||||
.type_id = unbound_element_type.element_type_id});
|
||||
}
|
||||
auto fields_id =
|
||||
context.struct_type_fields().AddCanonical(struct_type_fields);
|
||||
return fields_id;
|
||||
}
|
||||
|
||||
// Checks that the specified finished class definition is valid and builds and
|
||||
// returns a corresponding complete type witness instruction.
|
||||
@@ -644,39 +660,47 @@ static auto CheckCompleteClassType(Context& context, Parse::NodeId node_id,
|
||||
SemIR::ClassId class_id) -> SemIR::InstId {
|
||||
auto& class_info = context.classes().Get(class_id);
|
||||
if (class_info.adapt_id.is_valid()) {
|
||||
auto fields_id = context.struct_type_fields().AddCanonical(
|
||||
context.struct_type_fields_stack().PeekArray());
|
||||
context.struct_type_fields_stack().PopArray();
|
||||
|
||||
return CheckCompleteAdapterClassType(context, node_id, class_id, fields_id);
|
||||
return CheckCompleteAdapterClassType(context, node_id, class_id);
|
||||
}
|
||||
|
||||
bool defining_vtable_ptr = class_info.is_dynamic;
|
||||
bool defining_vptr = class_info.is_dynamic;
|
||||
if (class_info.base_id.is_valid()) {
|
||||
auto base_info = context.insts().GetAs<SemIR::BaseDecl>(class_info.base_id);
|
||||
// TODO: If the base class is template dependent, we will need to decide
|
||||
// whether to add a vptr as part of instantiation.
|
||||
if (auto* base_class_info = TryGetAsClass(context, base_info.base_type_id);
|
||||
base_class_info && base_class_info->is_dynamic) {
|
||||
defining_vtable_ptr = false;
|
||||
defining_vptr = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (defining_vtable_ptr) {
|
||||
context.struct_type_fields_stack().PrependToTop(
|
||||
auto field_decls = context.field_decls_stack().PeekArray();
|
||||
llvm::SmallVector<SemIR::StructTypeField> struct_type_fields;
|
||||
struct_type_fields.reserve(defining_vptr + class_info.base_id.is_valid() +
|
||||
field_decls.size());
|
||||
if (defining_vptr) {
|
||||
struct_type_fields.push_back(
|
||||
{.name_id = SemIR::NameId::Vptr,
|
||||
.type_id = context.GetPointerType(
|
||||
context.GetBuiltinType(SemIR::BuiltinInstKind::VtableType))});
|
||||
}
|
||||
|
||||
auto fields_id = context.struct_type_fields().AddCanonical(
|
||||
context.struct_type_fields_stack().PeekArray());
|
||||
context.struct_type_fields_stack().PopArray();
|
||||
if (class_info.base_id.is_valid()) {
|
||||
auto base_decl = context.insts().GetAs<SemIR::BaseDecl>(class_info.base_id);
|
||||
base_decl.index =
|
||||
SemIR::ElementIndex{static_cast<int>(struct_type_fields.size())};
|
||||
context.ReplaceInstPreservingConstantValue(class_info.base_id, base_decl);
|
||||
struct_type_fields.push_back(
|
||||
{.name_id = SemIR::NameId::Base,
|
||||
.type_id = context.insts()
|
||||
.GetAs<SemIR::BaseDecl>(class_info.base_id)
|
||||
.base_type_id});
|
||||
}
|
||||
|
||||
return context.AddInst<SemIR::CompleteTypeWitness>(
|
||||
node_id,
|
||||
{.type_id = context.GetBuiltinType(SemIR::BuiltinInstKind::WitnessType),
|
||||
.object_repr_id = context.GetStructType(fields_id)});
|
||||
.object_repr_id = context.GetStructType(
|
||||
AddStructTypeFields(context, struct_type_fields))});
|
||||
}
|
||||
|
||||
auto HandleParseNode(Context& context, Parse::ClassDefinitionId node_id)
|
||||
@@ -691,6 +715,7 @@ auto HandleParseNode(Context& context, Parse::ClassDefinitionId node_id)
|
||||
class_info.complete_type_witness_id = complete_type_witness_id;
|
||||
|
||||
context.inst_block_stack().Pop();
|
||||
context.field_decls_stack().PopArray();
|
||||
|
||||
FinishGenericDefinition(context, class_info.generic_id);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user