Files
carbon-lang/toolchain/sem_ir/file.cpp
T
Jon Ross-Perkins 1c748c0f14 Split semantics into check and sem_ir directories (#3176)
Continuing along with #3070. Note this is just a file rename, with BUILD
edits; every file previously in semantics/ should show as moved (except
maybe BUILDs, which split).
2023-08-31 19:54:32 +00:00

621 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/sem_ir/file.h"
#include "common/check.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "toolchain/sem_ir/builtin_kind.h"
#include "toolchain/sem_ir/node.h"
#include "toolchain/sem_ir/node_kind.h"
namespace Carbon::SemIR {
File::File()
// Builtins are always the first IR, even when self-referential.
: cross_reference_irs_({this}),
// Default entry for NodeBlockId::Empty.
node_blocks_(1) {
nodes_.reserve(BuiltinKind::ValidCount);
// Error uses a self-referential type so that it's not accidentally treated as
// a normal type. Every other builtin is a type, including the
// self-referential TypeType.
#define CARBON_SEMANTICS_BUILTIN_KIND(Name, ...) \
nodes_.push_back(Node::Builtin::Make(BuiltinKind::Name, \
BuiltinKind::Name == BuiltinKind::Error \
? TypeId::Error \
: TypeId::TypeType));
#include "toolchain/sem_ir/builtin_kind.def"
CARBON_CHECK(nodes_.size() == BuiltinKind::ValidCount)
<< "Builtins should produce " << BuiltinKind::ValidCount
<< " nodes, actual: " << nodes_.size();
}
File::File(const File* builtins)
// Builtins are always the first IR.
: cross_reference_irs_({builtins}),
// Default entry for NodeBlockId::Empty.
node_blocks_(1) {
CARBON_CHECK(builtins != nullptr);
CARBON_CHECK(builtins->cross_reference_irs_[0] == builtins)
<< "Not called with builtins!";
// Copy builtins over.
nodes_.reserve(BuiltinKind::ValidCount);
static constexpr auto BuiltinIR = CrossReferenceIRId(0);
for (auto [i, node] : llvm::enumerate(builtins->nodes_)) {
// We can reuse builtin type IDs because they're special-cased values.
nodes_.push_back(Node::CrossReference::Make(node.type_id(), BuiltinIR,
SemIR::NodeId(i)));
}
}
auto File::Verify() const -> ErrorOr<Success> {
// Invariants don't necessarily hold for invalid IR.
if (has_errors_) {
return Success();
}
// Check that every code block has a terminator sequence that appears at the
// end of the block.
for (const Function& function : functions_) {
for (NodeBlockId block_id : function.body_block_ids) {
TerminatorKind prior_kind = TerminatorKind::NotTerminator;
for (NodeId node_id : GetNodeBlock(block_id)) {
TerminatorKind node_kind = GetNode(node_id).kind().terminator_kind();
if (prior_kind == TerminatorKind::Terminator) {
return Error(llvm::formatv("Node {0} in block {1} follows terminator",
node_id, block_id));
}
if (prior_kind > node_kind) {
return Error(
llvm::formatv("Non-terminator node {0} in block {1} follows "
"terminator sequence",
node_id, block_id));
}
prior_kind = node_kind;
}
if (prior_kind != TerminatorKind::Terminator) {
return Error(llvm::formatv("No terminator in block {0}", block_id));
}
}
}
// TODO: Check that a node only references other nodes that are either global
// or that dominate it.
return Success();
}
static constexpr int Indent = 2;
template <typename T>
static auto PrintList(llvm::raw_ostream& out, llvm::StringLiteral name,
const llvm::SmallVector<T>& list) {
out << name << ": [\n";
for (const auto& element : list) {
out.indent(Indent);
out << element << ",\n";
}
out << "]\n";
}
template <typename T>
static auto PrintBlock(llvm::raw_ostream& out, llvm::StringLiteral block_name,
const llvm::SmallVector<T>& blocks) {
out << block_name << ": [\n";
for (const auto& block : blocks) {
out.indent(Indent);
out << "[\n";
for (const auto& node : block) {
out.indent(2 * Indent);
out << node << ",\n";
}
out.indent(Indent);
out << "],\n";
}
out << "]\n";
}
auto File::Print(llvm::raw_ostream& out, bool include_builtins) const -> void {
out << "cross_reference_irs_size: " << cross_reference_irs_.size() << "\n";
PrintList(out, "functions", functions_);
PrintList(out, "integer_literals", integer_literals_);
PrintList(out, "real_literals", real_literals_);
PrintList(out, "strings", strings_);
PrintList(out, "types", types_);
PrintBlock(out, "type_blocks", type_blocks_);
out << "nodes: [\n";
for (int i = include_builtins ? 0 : BuiltinKind::ValidCount;
i < static_cast<int>(nodes_.size()); ++i) {
const auto& element = nodes_[i];
out.indent(Indent);
out << element << ",\n";
}
out << "]\n";
PrintBlock(out, "node_blocks", node_blocks_);
}
// Map a node kind representing a type into an integer describing the
// precedence of that type's syntax. Higher numbers correspond to higher
// precedence.
static auto GetTypePrecedence(NodeKind kind) -> int {
// clang warns on unhandled enum values; clang-tidy is incorrect here.
// NOLINTNEXTLINE(bugprone-switch-missing-default-case)
switch (kind) {
case NodeKind::ArrayType:
case NodeKind::Builtin:
case NodeKind::StructType:
case NodeKind::TupleType:
return 0;
case NodeKind::ConstType:
return -1;
case NodeKind::PointerType:
return -2;
case NodeKind::CrossReference:
// TODO: Once we support stringification of cross-references, we'll need
// to determine the precedence of the target of the cross-reference. For
// now, all cross-references refer to builtin types from the prelude.
return 0;
case NodeKind::AddressOf:
case NodeKind::ArrayIndex:
case NodeKind::ArrayValue:
case NodeKind::Assign:
case NodeKind::BinaryOperatorAdd:
case NodeKind::BindValue:
case NodeKind::BlockArg:
case NodeKind::BoolLiteral:
case NodeKind::Branch:
case NodeKind::BranchIf:
case NodeKind::BranchWithArg:
case NodeKind::Call:
case NodeKind::Dereference:
case NodeKind::FunctionDeclaration:
case NodeKind::IntegerLiteral:
case NodeKind::Invalid:
case NodeKind::Namespace:
case NodeKind::NoOp:
case NodeKind::Parameter:
case NodeKind::RealLiteral:
case NodeKind::Return:
case NodeKind::ReturnExpression:
case NodeKind::StringLiteral:
case NodeKind::StructAccess:
case NodeKind::StructTypeField:
case NodeKind::StructValue:
case NodeKind::StubReference:
case NodeKind::Temporary:
case NodeKind::TemporaryStorage:
case NodeKind::TupleIndex:
case NodeKind::TupleValue:
case NodeKind::UnaryOperatorNot:
case NodeKind::VarStorage:
CARBON_FATAL() << "GetTypePrecedence for non-type node kind " << kind;
}
}
auto File::StringifyType(TypeId type_id, bool in_type_context) const
-> std::string {
std::string str;
llvm::raw_string_ostream out(str);
struct Step {
// The node to print.
NodeId node_id;
// The index into node_id to print. Not used by all types.
int index = 0;
auto Next() const -> Step {
return {.node_id = node_id, .index = index + 1};
}
};
auto outer_node_id = GetTypeAllowBuiltinTypes(type_id);
llvm::SmallVector<Step> steps = {{.node_id = outer_node_id}};
while (!steps.empty()) {
auto step = steps.pop_back_val();
// Invalid node IDs will use the default invalid printing.
if (!step.node_id.is_valid()) {
out << step.node_id;
continue;
}
// Builtins have designated labels.
if (step.node_id.index < BuiltinKind::ValidCount) {
out << BuiltinKind::FromInt(step.node_id.index).label();
continue;
}
auto node = GetNode(step.node_id);
// clang warns on unhandled enum values; clang-tidy is incorrect here.
// NOLINTNEXTLINE(bugprone-switch-missing-default-case)
switch (node.kind()) {
case NodeKind::ArrayType: {
auto [bound_id, type_id] = node.GetAsArrayType();
if (step.index == 0) {
out << "[";
steps.push_back(step.Next());
steps.push_back({.node_id = GetTypeAllowBuiltinTypes(type_id)});
} else if (step.index == 1) {
out << "; " << GetArrayBoundValue(bound_id) << "]";
}
break;
}
case NodeKind::ConstType: {
if (step.index == 0) {
out << "const ";
// Add parentheses if required.
auto inner_type_node_id =
GetTypeAllowBuiltinTypes(node.GetAsConstType());
if (GetTypePrecedence(GetNode(inner_type_node_id).kind()) <
GetTypePrecedence(node.kind())) {
out << "(";
steps.push_back(step.Next());
}
steps.push_back({.node_id = inner_type_node_id});
} else if (step.index == 1) {
out << ")";
}
break;
}
case NodeKind::PointerType: {
if (step.index == 0) {
steps.push_back(step.Next());
steps.push_back(
{.node_id = GetTypeAllowBuiltinTypes(node.GetAsPointerType())});
} else if (step.index == 1) {
out << "*";
}
break;
}
case NodeKind::StructType: {
auto refs = GetNodeBlock(node.GetAsStructType());
if (refs.empty()) {
out << "{}";
break;
} else if (step.index == 0) {
out << "{";
} else if (step.index < static_cast<int>(refs.size())) {
out << ", ";
} else {
out << "}";
break;
}
steps.push_back(step.Next());
steps.push_back({.node_id = refs[step.index]});
break;
}
case NodeKind::StructTypeField: {
auto [name_id, type_id] = node.GetAsStructTypeField();
out << "." << GetString(name_id) << ": ";
steps.push_back({.node_id = GetTypeAllowBuiltinTypes(type_id)});
break;
}
case NodeKind::TupleType: {
auto refs = GetTypeBlock(node.GetAsTupleType());
if (refs.empty()) {
out << "()";
break;
} else if (step.index == 0) {
out << "(";
} else if (step.index < static_cast<int>(refs.size())) {
out << ", ";
} else {
// A tuple of one element has a comma to disambiguate from an
// expression.
if (step.index == 1) {
out << ",";
}
out << ")";
break;
}
steps.push_back(step.Next());
steps.push_back(
{.node_id = GetTypeAllowBuiltinTypes(refs[step.index])});
break;
}
case NodeKind::AddressOf:
case NodeKind::ArrayIndex:
case NodeKind::ArrayValue:
case NodeKind::Assign:
case NodeKind::BinaryOperatorAdd:
case NodeKind::BindValue:
case NodeKind::BlockArg:
case NodeKind::BoolLiteral:
case NodeKind::Branch:
case NodeKind::BranchIf:
case NodeKind::BranchWithArg:
case NodeKind::Builtin:
case NodeKind::Call:
case NodeKind::CrossReference:
case NodeKind::Dereference:
case NodeKind::FunctionDeclaration:
case NodeKind::IntegerLiteral:
case NodeKind::Namespace:
case NodeKind::NoOp:
case NodeKind::Parameter:
case NodeKind::RealLiteral:
case NodeKind::Return:
case NodeKind::ReturnExpression:
case NodeKind::StringLiteral:
case NodeKind::StructAccess:
case NodeKind::StructValue:
case NodeKind::StubReference:
case NodeKind::Temporary:
case NodeKind::TemporaryStorage:
case NodeKind::TupleIndex:
case NodeKind::TupleValue:
case NodeKind::UnaryOperatorNot:
case NodeKind::VarStorage:
// We don't need to handle stringification for nodes that don't show up
// in errors, but make it clear what's going on so that it's clearer
// when stringification is needed.
out << "<cannot stringify " << step.node_id << ">";
break;
case NodeKind::Invalid:
llvm_unreachable("NodeKind::Invalid is never used.");
}
}
// For `{}` or any tuple type, we've printed a non-type expression, so add a
// conversion to type `type` if it's not implied by the context.
if (!in_type_context) {
auto outer_node = GetNode(outer_node_id);
if (outer_node.kind() == NodeKind::TupleType ||
(outer_node.kind() == NodeKind::StructType &&
GetNodeBlock(outer_node.GetAsStructType()).empty())) {
out << " as type";
}
}
return str;
}
auto GetExpressionCategory(const File& file, NodeId node_id)
-> ExpressionCategory {
const File* ir = &file;
while (true) {
auto node = ir->GetNode(node_id);
// clang warns on unhandled enum values; clang-tidy is incorrect here.
// NOLINTNEXTLINE(bugprone-switch-missing-default-case)
switch (node.kind()) {
case NodeKind::Invalid:
case NodeKind::Assign:
case NodeKind::Branch:
case NodeKind::BranchIf:
case NodeKind::BranchWithArg:
case NodeKind::FunctionDeclaration:
case NodeKind::Namespace:
case NodeKind::NoOp:
case NodeKind::Return:
case NodeKind::ReturnExpression:
case NodeKind::StructTypeField:
return ExpressionCategory::NotExpression;
case NodeKind::CrossReference: {
auto [xref_id, xref_node_id] = node.GetAsCrossReference();
ir = &ir->GetCrossReferenceIR(xref_id);
node_id = xref_node_id;
continue;
}
case NodeKind::AddressOf:
case NodeKind::ArrayType:
case NodeKind::BinaryOperatorAdd:
case NodeKind::BindValue:
case NodeKind::BlockArg:
case NodeKind::BoolLiteral:
case NodeKind::Builtin:
case NodeKind::ConstType:
case NodeKind::IntegerLiteral:
case NodeKind::Parameter:
case NodeKind::PointerType:
case NodeKind::RealLiteral:
case NodeKind::StringLiteral:
case NodeKind::StructType:
case NodeKind::TupleType:
case NodeKind::UnaryOperatorNot:
return ExpressionCategory::Value;
case NodeKind::ArrayIndex: {
auto [base_id, index_id] = node.GetAsArrayIndex();
node_id = base_id;
continue;
}
case NodeKind::StructAccess: {
auto [base_id, member_index] = node.GetAsStructAccess();
node_id = base_id;
continue;
}
case NodeKind::TupleIndex: {
auto [base_id, index_id] = node.GetAsTupleIndex();
node_id = base_id;
continue;
}
case NodeKind::StubReference: {
node_id = node.GetAsStubReference();
continue;
}
case NodeKind::ArrayValue:
case NodeKind::StructValue:
case NodeKind::TupleValue:
// TODO: Eventually these will depend on the context in which the value
// is used, and could be either Value or Initializing. We may want
// different node kinds for a struct/tuple initializer versus a
// struct/tuple value construction.
return ExpressionCategory::Value;
case NodeKind::Call:
return ExpressionCategory::Initializing;
case NodeKind::Dereference:
case NodeKind::VarStorage:
return ExpressionCategory::DurableReference;
case NodeKind::Temporary:
case NodeKind::TemporaryStorage:
return ExpressionCategory::EphemeralReference;
}
}
}
auto GetValueRepresentation(const File& file, TypeId type_id)
-> ValueRepresentation {
const File* ir = &file;
NodeId node_id = ir->GetTypeAllowBuiltinTypes(type_id);
while (true) {
auto node = ir->GetNode(node_id);
// clang warns on unhandled enum values; clang-tidy is incorrect here.
// NOLINTNEXTLINE(bugprone-switch-missing-default-case)
switch (node.kind()) {
case NodeKind::AddressOf:
case NodeKind::ArrayIndex:
case NodeKind::ArrayValue:
case NodeKind::Assign:
case NodeKind::BinaryOperatorAdd:
case NodeKind::BindValue:
case NodeKind::BlockArg:
case NodeKind::BoolLiteral:
case NodeKind::Branch:
case NodeKind::BranchIf:
case NodeKind::BranchWithArg:
case NodeKind::Call:
case NodeKind::Dereference:
case NodeKind::FunctionDeclaration:
case NodeKind::IntegerLiteral:
case NodeKind::Invalid:
case NodeKind::Namespace:
case NodeKind::NoOp:
case NodeKind::Parameter:
case NodeKind::RealLiteral:
case NodeKind::Return:
case NodeKind::ReturnExpression:
case NodeKind::StringLiteral:
case NodeKind::StructAccess:
case NodeKind::StructTypeField:
case NodeKind::StructValue:
case NodeKind::Temporary:
case NodeKind::TemporaryStorage:
case NodeKind::TupleIndex:
case NodeKind::TupleValue:
case NodeKind::UnaryOperatorNot:
case NodeKind::VarStorage:
CARBON_FATAL() << "Type refers to non-type node " << node;
case NodeKind::CrossReference: {
auto [xref_id, xref_node_id] = node.GetAsCrossReference();
ir = &ir->GetCrossReferenceIR(xref_id);
node_id = xref_node_id;
continue;
}
case NodeKind::StubReference: {
node_id = node.GetAsStubReference();
continue;
}
case NodeKind::ArrayType:
// 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 {.kind = ValueRepresentation::Pointer, .type = type_id};
case NodeKind::StructType: {
const auto& fields = ir->GetNodeBlock(node.GetAsStructType());
if (fields.empty()) {
// An empty struct has an empty representation.
return {.kind = ValueRepresentation::None, .type = TypeId::Invalid};
}
if (fields.size() == 1) {
// A struct with one field has the same representation as its field.
auto [field_name_id, field_type_id] =
ir->GetNode(fields.front()).GetAsStructTypeField();
node_id = ir->GetTypeAllowBuiltinTypes(field_type_id);
continue;
}
// For any other struct, use a pointer representation.
return {.kind = ValueRepresentation::Pointer, .type = type_id};
}
case NodeKind::TupleType: {
const auto& elements = ir->GetTypeBlock(node.GetAsTupleType());
if (elements.empty()) {
// An empty tuple has an empty representation.
return {.kind = ValueRepresentation::None, .type = TypeId::Invalid};
}
if (elements.size() == 1) {
// A one-tuple has the same representation as its sole element.
node_id = ir->GetTypeAllowBuiltinTypes(elements.front());
continue;
}
// For any other tuple, use a pointer representation.
return {.kind = ValueRepresentation::Pointer, .type = type_id};
}
case NodeKind::Builtin:
// clang warns on unhandled enum values; clang-tidy is incorrect here.
// NOLINTNEXTLINE(bugprone-switch-missing-default-case)
switch (node.GetAsBuiltin()) {
case BuiltinKind::TypeType:
case BuiltinKind::Error:
case BuiltinKind::Invalid:
return {.kind = ValueRepresentation::None, .type = TypeId::Invalid};
case BuiltinKind::BoolType:
case BuiltinKind::IntegerType:
case BuiltinKind::FloatingPointType:
return {.kind = ValueRepresentation::Copy, .type = type_id};
case BuiltinKind::StringType:
// TODO: Decide on string value semantics. This should probably be a
// custom value representation carrying a pointer and size or
// similar.
return {.kind = ValueRepresentation::Pointer, .type = type_id};
}
case NodeKind::PointerType:
return {.kind = ValueRepresentation::Copy, .type = type_id};
case NodeKind::ConstType:
node_id = ir->GetTypeAllowBuiltinTypes(node.GetAsConstType());
continue;
}
}
}
auto GetInitializingRepresentation(const File& file, TypeId type_id)
-> InitializingRepresentation {
auto value_rep = GetValueRepresentation(file, type_id);
switch (value_rep.kind) {
case ValueRepresentation::None:
return {.kind = InitializingRepresentation::None};
case ValueRepresentation::Copy:
// TODO: Use in-place initialization for types that have non-trivial
// destructive move.
return {.kind = InitializingRepresentation::ByCopy};
case ValueRepresentation::Pointer:
case ValueRepresentation::Custom:
return {.kind = InitializingRepresentation::InPlace};
}
}
} // namespace Carbon::SemIR