Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compute and cache the value representation of a type when it becomes complete. #3271

Merged
merged 16 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
239 changes: 239 additions & 0 deletions toolchain/check/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,235 @@ auto Context::ParamOrArgEnd(Parse::NodeKind start_kind) -> SemIR::NodeBlockId {
return ParamOrArgPop();
}

// Attempts to complete the given type.
auto Context::TryToCompleteType(SemIR::TypeId type_id) -> bool {
auto node_id = semantics_ir().GetTypeAllowBuiltinTypes(type_id);
auto node = semantics_ir().GetNode(node_id);

auto set_empty_representation = [&]() {
semantics_ir().CompleteType(
type_id, {.kind = SemIR::ValueRepresentation::None,
.type_id = CanonicalizeTupleType(node.parse_node(), {})});
return true;
};

auto set_copy_representation = [&](SemIR::TypeId rep_id) {
semantics_ir().CompleteType(
type_id, {.kind = SemIR::ValueRepresentation::Copy, .type_id = rep_id});
return true;
};

auto set_pointer_representation = [&](SemIR::TypeId pointee_id) {
// TODO: Should we add `const` qualification to `pointee_id`?
semantics_ir().CompleteType(
type_id, {.kind = SemIR::ValueRepresentation::Pointer,
.type_id = GetPointerType(node.parse_node(), pointee_id)});
return true;
};

// clang warns on unhandled enum values; clang-tidy is incorrect here.
// NOLINTNEXTLINE(bugprone-switch-missing-default-case)
switch (node.kind()) {
case SemIR::AddressOf::Kind:
case SemIR::ArrayIndex::Kind:
case SemIR::ArrayInit::Kind:
case SemIR::Assign::Kind:
case SemIR::BinaryOperatorAdd::Kind:
case SemIR::BindName::Kind:
case SemIR::BindValue::Kind:
case SemIR::BlockArg::Kind:
case SemIR::BoolLiteral::Kind:
case SemIR::Branch::Kind:
case SemIR::BranchIf::Kind:
case SemIR::BranchWithArg::Kind:
case SemIR::Call::Kind:
case SemIR::Dereference::Kind:
case SemIR::FunctionDeclaration::Kind:
case SemIR::InitializeFrom::Kind:
case SemIR::IntegerLiteral::Kind:
case SemIR::NameReference::Kind:
case SemIR::Namespace::Kind:
case SemIR::NoOp::Kind:
case SemIR::Parameter::Kind:
case SemIR::RealLiteral::Kind:
case SemIR::Return::Kind:
case SemIR::ReturnExpression::Kind:
case SemIR::SpliceBlock::Kind:
case SemIR::StringLiteral::Kind:
case SemIR::StructAccess::Kind:
case SemIR::StructTypeField::Kind:
case SemIR::StructLiteral::Kind:
case SemIR::StructInit::Kind:
case SemIR::StructValue::Kind:
case SemIR::Temporary::Kind:
case SemIR::TemporaryStorage::Kind:
case SemIR::TupleAccess::Kind:
case SemIR::TupleIndex::Kind:
case SemIR::TupleLiteral::Kind:
case SemIR::TupleInit::Kind:
case SemIR::TupleValue::Kind:
case SemIR::UnaryOperatorNot::Kind:
case SemIR::ValueAsReference::Kind:
case SemIR::VarStorage::Kind:
CARBON_FATAL() << "Type refers to non-type node " << node;

case SemIR::CrossReference::Kind: {
auto xref = node.As<SemIR::CrossReference>();
auto xref_node =
semantics_ir().GetCrossReferenceIR(xref.ir_id).GetNode(xref.node_id);

// The canonical description of a type should only have cross-references
// for entities owned by another File, such as builtins, which are owned
// by the prelude, and named entities like classes and interfaces, which
// we don't support yet.
CARBON_CHECK(xref_node.kind() == SemIR::Builtin::Kind)
<< "TODO: Handle other kinds of node cross-references";

// clang warns on unhandled enum values; clang-tidy is incorrect here.
// NOLINTNEXTLINE(bugprone-switch-missing-default-case)
switch (xref_node.As<SemIR::Builtin>().builtin_kind) {
case SemIR::BuiltinKind::TypeType:
case SemIR::BuiltinKind::Error:
case SemIR::BuiltinKind::Invalid:
case SemIR::BuiltinKind::BoolType:
case SemIR::BuiltinKind::IntegerType:
case SemIR::BuiltinKind::FloatingPointType:
case SemIR::BuiltinKind::NamespaceType:
case SemIR::BuiltinKind::FunctionType:
return set_copy_representation(type_id);

case SemIR::BuiltinKind::StringType:
// TODO: Decide on string value semantics. This should probably be a
// custom value representation carrying a pointer and size or
// similar.
return set_pointer_representation(type_id);
}
llvm_unreachable("All builtin kinds were handled above");
}

case SemIR::ArrayType::Kind:
// For arrays, it's convenient to always use a pointer representation,
// even when the array has zero or one element, in order to support
// indexing.
return set_pointer_representation(type_id);

case SemIR::StructType::Kind: {
auto fields =
semantics_ir().GetNodeBlock(node.As<SemIR::StructType>().fields_id);
if (fields.empty()) {
return set_empty_representation();
}

// Find the value representation for each field, and construct a struct
// of value representations.
llvm::SmallVector<SemIR::NodeId> value_rep_fields;
value_rep_fields.reserve(fields.size());
bool same_as_object_rep = true;
for (auto field_id : fields) {
auto field = semantics_ir().GetNodeAs<SemIR::StructTypeField>(field_id);

// A struct is complete if and only if all its fields are complete.
auto field_value_rep =
semantics_ir().GetValueRepresentation(field.type_id);
if (field_value_rep.kind == SemIR::ValueRepresentation::Unknown) {
// TODO: If the field type might have become complete after we formed
// it, we should attempt to complete its type.
return false;
}
if (field_value_rep.type_id != field.type_id) {
same_as_object_rep = false;
field.type_id = field_value_rep.type_id;
field_id = AddNode(field);
}
value_rep_fields.push_back(field_id);
}

auto value_rep = same_as_object_rep
? type_id
: CanonicalizeStructType(
node.parse_node(),
semantics_ir().AddNodeBlock(value_rep_fields));
if (fields.size() == 1) {
// The value representation for a struct with a single field is a struct
// containing the value representation of the field.
// TODO: Consider doing the same for structs with multiple small fields.
return set_copy_representation(value_rep);
}
// For a struct with multiple fields, we use a pointer representation.
return set_pointer_representation(value_rep);
}

case SemIR::TupleType::Kind: {
zygoloid marked this conversation as resolved.
Show resolved Hide resolved
// TODO: Extract and share code with structs and maybe arrays.
auto elements =
semantics_ir().GetTypeBlock(node.As<SemIR::TupleType>().elements_id);
if (elements.empty()) {
return set_empty_representation();
}

// Find the value representation for each element, and construct a tuple
// of value representations.
llvm::SmallVector<SemIR::TypeId> value_rep_elements;
value_rep_elements.reserve(elements.size());
bool same_as_object_rep = true;
for (auto element_type_id : elements) {
// A tuple is complete if and only if all its elements are complete.
auto element_value_rep =
semantics_ir().GetValueRepresentation(element_type_id);
if (element_value_rep.kind == SemIR::ValueRepresentation::Unknown) {
// TODO: If the element type might have become complete after we
// formed it, we should attempt to complete its type.
return false;
}
if (element_value_rep.type_id != element_type_id) {
same_as_object_rep = false;
}
value_rep_elements.push_back(element_value_rep.type_id);
}

auto value_rep =
same_as_object_rep
? type_id
: CanonicalizeTupleType(node.parse_node(), value_rep_elements);
if (elements.size() == 1) {
// The value representation for a tuple with a single element is a tuple
// containing the value representation of that element.
// TODO: Consider doing the same for tuples with multiple small
// elements.
return set_copy_representation(value_rep);
}
// For a tuple with multiple elements, we use a pointer representation.
return set_pointer_representation(value_rep);
}

case SemIR::ClassDeclaration::Kind: {
// TODO: Pick the default value representation in a smarter way.
// TODO: Allow the value representation for a class to be customized.
return set_pointer_representation(type_id);
}

case SemIR::Builtin::Kind:
CARBON_FATAL() << "Builtins should be named as cross-references";

case SemIR::PointerType::Kind:
return set_copy_representation(type_id);

case SemIR::ConstType::Kind: {
// The value representation of `const T` is the same as that of `T`.
// Objects are not modifiable through their value representations.
auto inner_value_rep = semantics_ir().GetValueRepresentation(
node.As<SemIR::ConstType>().inner_id);
if (inner_value_rep.kind == SemIR::ValueRepresentation::Unknown) {
return false;
}
semantics_ir().CompleteType(type_id, inner_value_rep);
return true;
}
}

llvm_unreachable("All node kinds were handled above");
}

auto Context::CanonicalizeTypeImpl(
SemIR::NodeKind kind,
llvm::function_ref<void(llvm::FoldingSetNodeID& canonical_id)> profile_type,
Expand Down Expand Up @@ -310,6 +539,16 @@ auto Context::CanonicalizeTypeImpl(
}()) << "Type was created recursively during canonicalization";

canonical_type_nodes_.InsertNode(type_node_storage_.back().get(), insert_pos);

// Now we've formed the type, try to complete it and build its value
// representation.
// TODO: Delay doing this until a complete type is required, and issue a
// diagnostic if it fails.
zygoloid marked this conversation as resolved.
Show resolved Hide resolved
// TODO: Consider emitting this into the file's global node block
// (or somewhere else that better reflects the definition of the type
// rather than the coincidental first use).
bool complete = TryToCompleteType(type_id);
CARBON_CHECK(complete) << "Incomplete types should not exist yet";
return type_id;
}

Expand Down
7 changes: 6 additions & 1 deletion toolchain/check/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ class Context {
auto is_current_position_reachable() -> bool;

// Canonicalizes a type which is tracked as a single node.
// TODO: This should eventually return a type ID.
auto CanonicalizeType(SemIR::NodeId node_id) -> SemIR::TypeId;

// Handles canonicalization of struct types. This may create a new struct type
Expand All @@ -134,6 +133,12 @@ class Context {
llvm::ArrayRef<SemIR::TypeId> type_ids)
-> SemIR::TypeId;

// Attempts to complete the type `type_id`. Returns `true` if the type is
// complete, or `false` if it could not be completed. A complete type has
// known object and value representations.
// TODO: For now, all types are always complete.
auto TryToCompleteType(SemIR::TypeId type_id) -> bool;

// Returns a pointer type whose pointee type is `pointee_type_id`.
auto GetPointerType(Parse::Node parse_node, SemIR::TypeId pointee_type_id)
-> SemIR::TypeId;
Expand Down
11 changes: 7 additions & 4 deletions toolchain/check/testdata/array/array_in_place.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ fn G() {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %.loc10_25: (type, type, type) = tuple_literal (i32, i32, i32)
// CHECK:STDOUT: %.loc10_28: i32 = int_literal 2
// CHECK:STDOUT: %.loc10_29: type = array_type %.loc10_28, (i32, i32, i32)
// CHECK:STDOUT: %.loc10_29.1: type = array_type %.loc10_28, (i32, i32, i32)
// CHECK:STDOUT: %.loc10_29.2: type = ptr_type [(i32, i32, i32); 2]
// CHECK:STDOUT: %v: ref [(i32, i32, i32); 2] = var "v"
// CHECK:STDOUT: %F.ref.loc10_34: <function> = name_reference "F", package.%F
// CHECK:STDOUT: %.loc10_42.3: ref (i32, i32, i32) = splice_block %.loc10_42.2 {
Expand All @@ -36,8 +37,10 @@ fn G() {
// CHECK:STDOUT: }
// CHECK:STDOUT: %.loc10_40: init (i32, i32, i32) = call %F.ref.loc10_39() to %.loc10_42.6
// CHECK:STDOUT: %.loc10_42.7: type = tuple_type ((i32, i32, i32), (i32, i32, i32))
// CHECK:STDOUT: %.loc10_42.8: ((i32, i32, i32), (i32, i32, i32)) = tuple_literal (%.loc10_35, %.loc10_40)
// CHECK:STDOUT: %.loc10_42.9: init [(i32, i32, i32); 2] = array_init %.loc10_42.8, (%.loc10_35, %.loc10_40) to %v
// CHECK:STDOUT: assign %v, %.loc10_42.9
// CHECK:STDOUT: %.loc10_42.8: type = tuple_type ((i32, i32, i32)*, (i32, i32, i32)*)
// CHECK:STDOUT: %.loc10_42.9: type = ptr_type ((i32, i32, i32)*, (i32, i32, i32)*)
// CHECK:STDOUT: %.loc10_42.10: ((i32, i32, i32), (i32, i32, i32)) = tuple_literal (%.loc10_35, %.loc10_40)
// CHECK:STDOUT: %.loc10_42.11: init [(i32, i32, i32); 2] = array_init %.loc10_42.10, (%.loc10_35, %.loc10_40) to %v
// CHECK:STDOUT: assign %v, %.loc10_42.11
// CHECK:STDOUT: return
// CHECK:STDOUT: }
3 changes: 2 additions & 1 deletion toolchain/check/testdata/array/assign_return_value.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ fn Run() {
// CHECK:STDOUT: fn @Run() {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %.loc10_16: i32 = int_literal 1
// CHECK:STDOUT: %.loc10_17: type = array_type %.loc10_16, i32
// CHECK:STDOUT: %.loc10_17.1: type = array_type %.loc10_16, i32
// CHECK:STDOUT: %.loc10_17.2: type = ptr_type [i32; 1]
// CHECK:STDOUT: %t: ref [i32; 1] = var "t"
// CHECK:STDOUT: %F.ref: <function> = name_reference "F", package.%F
// CHECK:STDOUT: %.loc10_22.1: init (i32,) = call %F.ref()
Expand Down
9 changes: 6 additions & 3 deletions toolchain/check/testdata/array/assign_var.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ var b: [i32; 3] = a;

// CHECK:STDOUT: file "assign_var.carbon" {
// CHECK:STDOUT: %.loc7_22.1: type = tuple_type (type, type, type)
// CHECK:STDOUT: %.loc7_22.2: (type, type, type) = tuple_literal (i32, i32, i32)
// CHECK:STDOUT: %.loc7_22.3: type = tuple_type (i32, i32, i32)
// CHECK:STDOUT: %.loc7_22.2: type = ptr_type (type, type, type)
// CHECK:STDOUT: %.loc7_22.3: (type, type, type) = tuple_literal (i32, i32, i32)
// CHECK:STDOUT: %.loc7_22.4: type = tuple_type (i32, i32, i32)
// CHECK:STDOUT: %.loc7_22.5: type = ptr_type (i32, i32, i32)
// CHECK:STDOUT: %a: ref (i32, i32, i32) = var "a"
// CHECK:STDOUT: %.loc7_27: i32 = int_literal 1
// CHECK:STDOUT: %.loc7_30: i32 = int_literal 2
Expand All @@ -25,7 +27,8 @@ var b: [i32; 3] = a;
// CHECK:STDOUT: %.loc7_34.8: init (i32, i32, i32) = tuple_init %.loc7_34.1, (%.loc7_34.3, %.loc7_34.5, %.loc7_34.7)
// CHECK:STDOUT: assign %a, %.loc7_34.8
// CHECK:STDOUT: %.loc8_14: i32 = int_literal 3
// CHECK:STDOUT: %.loc8_15: type = array_type %.loc8_14, i32
// CHECK:STDOUT: %.loc8_15.1: type = array_type %.loc8_14, i32
// CHECK:STDOUT: %.loc8_15.2: type = ptr_type [i32; 3]
// CHECK:STDOUT: %b: ref [i32; 3] = var "b"
// CHECK:STDOUT: %a.ref: ref (i32, i32, i32) = name_reference "a", %a
// CHECK:STDOUT: %.loc8_19.1: ref i32 = tuple_access %a.ref, member0
Expand Down
35 changes: 20 additions & 15 deletions toolchain/check/testdata/array/base.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ var c: [(); 5] = ((), (), (), (), (),);

// CHECK:STDOUT: file "base.carbon" {
// CHECK:STDOUT: %.loc7_14: i32 = int_literal 1
// CHECK:STDOUT: %.loc7_15: type = array_type %.loc7_14, i32
// CHECK:STDOUT: %.loc7_15.1: type = array_type %.loc7_14, i32
// CHECK:STDOUT: %.loc7_15.2: type = ptr_type [i32; 1]
// CHECK:STDOUT: %a: ref [i32; 1] = var "a"
// CHECK:STDOUT: %.loc7_20: i32 = int_literal 1
// CHECK:STDOUT: %.loc7_22.1: type = tuple_type (i32)
Expand All @@ -21,37 +22,41 @@ var c: [(); 5] = ((), (), (), (), (),);
// CHECK:STDOUT: %.loc7_22.6: init [i32; 1] = array_init %.loc7_22.2, (%.loc7_22.5) to %a
// CHECK:STDOUT: assign %a, %.loc7_22.6
// CHECK:STDOUT: %.loc8_14: i32 = int_literal 2
// CHECK:STDOUT: %.loc8_15: type = array_type %.loc8_14, f64
// CHECK:STDOUT: %.loc8_15.1: type = array_type %.loc8_14, f64
// CHECK:STDOUT: %.loc8_15.2: type = ptr_type [f64; 2]
// CHECK:STDOUT: %b: ref [f64; 2] = var "b"
// CHECK:STDOUT: %.loc8_20: f64 = real_literal 111e-1
// CHECK:STDOUT: %.loc8_26: f64 = real_literal 22e-1
// CHECK:STDOUT: %.loc8_30.1: type = tuple_type (f64, f64)
// CHECK:STDOUT: %.loc8_30.2: (f64, f64) = tuple_literal (%.loc8_20, %.loc8_26)
// CHECK:STDOUT: %.loc8_30.3: i32 = int_literal 0
// CHECK:STDOUT: %.loc8_30.4: ref f64 = array_index %b, %.loc8_30.3
// CHECK:STDOUT: %.loc8_30.5: init f64 = initialize_from %.loc8_20 to %.loc8_30.4
// CHECK:STDOUT: %.loc8_30.6: i32 = int_literal 1
// CHECK:STDOUT: %.loc8_30.7: ref f64 = array_index %b, %.loc8_30.6
// CHECK:STDOUT: %.loc8_30.8: init f64 = initialize_from %.loc8_26 to %.loc8_30.7
// CHECK:STDOUT: %.loc8_30.9: init [f64; 2] = array_init %.loc8_30.2, (%.loc8_30.5, %.loc8_30.8) to %b
// CHECK:STDOUT: assign %b, %.loc8_30.9
// CHECK:STDOUT: %.loc8_30.2: type = ptr_type (f64, f64)
// CHECK:STDOUT: %.loc8_30.3: (f64, f64) = tuple_literal (%.loc8_20, %.loc8_26)
// CHECK:STDOUT: %.loc8_30.4: i32 = int_literal 0
// CHECK:STDOUT: %.loc8_30.5: ref f64 = array_index %b, %.loc8_30.4
// CHECK:STDOUT: %.loc8_30.6: init f64 = initialize_from %.loc8_20 to %.loc8_30.5
// CHECK:STDOUT: %.loc8_30.7: i32 = int_literal 1
// CHECK:STDOUT: %.loc8_30.8: ref f64 = array_index %b, %.loc8_30.7
// CHECK:STDOUT: %.loc8_30.9: init f64 = initialize_from %.loc8_26 to %.loc8_30.8
// CHECK:STDOUT: %.loc8_30.10: init [f64; 2] = array_init %.loc8_30.3, (%.loc8_30.6, %.loc8_30.9) to %b
// CHECK:STDOUT: assign %b, %.loc8_30.10
// CHECK:STDOUT: %.loc9_10.1: type = tuple_type ()
// CHECK:STDOUT: %.loc9_10.2: () = tuple_literal ()
// CHECK:STDOUT: %.loc9_13: i32 = int_literal 5
// CHECK:STDOUT: %.loc9_14: type = array_type %.loc9_13, ()
// CHECK:STDOUT: %.loc9_14.1: type = array_type %.loc9_13, ()
// CHECK:STDOUT: %.loc9_14.2: type = ptr_type [(); 5]
// CHECK:STDOUT: %c: ref [(); 5] = var "c"
// CHECK:STDOUT: %.loc9_20.1: () = tuple_literal ()
// CHECK:STDOUT: %.loc9_24.1: () = tuple_literal ()
// CHECK:STDOUT: %.loc9_28.1: () = tuple_literal ()
// CHECK:STDOUT: %.loc9_32.1: () = tuple_literal ()
// CHECK:STDOUT: %.loc9_36.1: () = tuple_literal ()
// CHECK:STDOUT: %.loc9_38.1: type = tuple_type ((), (), (), (), ())
// CHECK:STDOUT: %.loc9_38.2: ((), (), (), (), ()) = tuple_literal (%.loc9_20.1, %.loc9_24.1, %.loc9_28.1, %.loc9_32.1, %.loc9_36.1)
// CHECK:STDOUT: %.loc9_38.2: type = ptr_type ((), (), (), (), ())
// CHECK:STDOUT: %.loc9_38.3: ((), (), (), (), ()) = tuple_literal (%.loc9_20.1, %.loc9_24.1, %.loc9_28.1, %.loc9_32.1, %.loc9_36.1)
// CHECK:STDOUT: %.loc9_20.2: init () = tuple_init %.loc9_20.1, ()
// CHECK:STDOUT: %.loc9_24.2: init () = tuple_init %.loc9_24.1, ()
// CHECK:STDOUT: %.loc9_28.2: init () = tuple_init %.loc9_28.1, ()
// CHECK:STDOUT: %.loc9_32.2: init () = tuple_init %.loc9_32.1, ()
// CHECK:STDOUT: %.loc9_36.2: init () = tuple_init %.loc9_36.1, ()
// CHECK:STDOUT: %.loc9_38.3: init [(); 5] = array_init %.loc9_38.2, (%.loc9_20.2, %.loc9_24.2, %.loc9_28.2, %.loc9_32.2, %.loc9_36.2) to %c
// CHECK:STDOUT: assign %c, %.loc9_38.3
// CHECK:STDOUT: %.loc9_38.4: init [(); 5] = array_init %.loc9_38.3, (%.loc9_20.2, %.loc9_24.2, %.loc9_28.2, %.loc9_32.2, %.loc9_36.2) to %c
// CHECK:STDOUT: assign %c, %.loc9_38.4
// CHECK:STDOUT: }
Loading