// 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 #ifndef CARBON_TOOLCHAIN_SEM_IR_NODE_H_ #define CARBON_TOOLCHAIN_SEM_IR_NODE_H_ #include #include "common/check.h" #include "common/ostream.h" #include "toolchain/base/index_base.h" #include "toolchain/parse/tree.h" #include "toolchain/sem_ir/builtin_kind.h" #include "toolchain/sem_ir/node_kind.h" namespace Carbon::SemIR { // The ID of a node. struct NodeId : public IndexBase, public Printable { // An explicitly invalid node ID. static const NodeId Invalid; // Builtin node IDs. #define CARBON_SEMANTICS_BUILTIN_KIND_NAME(Name) \ static const NodeId Builtin##Name; #include "toolchain/sem_ir/builtin_kind.def" using IndexBase::IndexBase; auto Print(llvm::raw_ostream& out) const -> void { out << "node"; if (!is_valid()) { IndexBase::Print(out); } else if (index < BuiltinKind::ValidCount) { out << BuiltinKind::FromInt(index); } else { // Use the `+` as a small reminder that this is a delta, rather than an // absolute index. out << "+" << index - BuiltinKind::ValidCount; } } }; constexpr NodeId NodeId::Invalid = NodeId(NodeId::InvalidIndex); // Uses the cross-reference node ID for a builtin. This relies on File // guarantees for builtin cross-reference placement. #define CARBON_SEMANTICS_BUILTIN_KIND_NAME(Name) \ constexpr NodeId NodeId::Builtin##Name = NodeId(BuiltinKind::Name.AsInt()); #include "toolchain/sem_ir/builtin_kind.def" // The ID of a function. struct FunctionId : public IndexBase, public Printable { using IndexBase::IndexBase; auto Print(llvm::raw_ostream& out) const -> void { out << "function"; IndexBase::Print(out); } }; // The ID of a cross-referenced IR. struct CrossReferenceIRId : public IndexBase, public Printable { using IndexBase::IndexBase; auto Print(llvm::raw_ostream& out) const -> void { out << "ir"; IndexBase::Print(out); } }; // A boolean value. struct BoolValue : public IndexBase, public Printable { static const BoolValue False; static const BoolValue True; using IndexBase::IndexBase; auto Print(llvm::raw_ostream& out) const -> void { switch (index) { case 0: out << "false"; break; case 1: out << "true"; break; default: CARBON_FATAL() << "Invalid bool value " << index; } } }; constexpr BoolValue BoolValue::False = BoolValue(0); constexpr BoolValue BoolValue::True = BoolValue(1); // The ID of an integer literal. struct IntegerLiteralId : public IndexBase, public Printable { using IndexBase::IndexBase; auto Print(llvm::raw_ostream& out) const -> void { out << "int"; IndexBase::Print(out); } }; // The ID of a name scope. struct NameScopeId : public IndexBase, public Printable { // An explicitly invalid ID. static const NameScopeId Invalid; using IndexBase::IndexBase; auto Print(llvm::raw_ostream& out) const -> void { out << "name_scope"; IndexBase::Print(out); } }; constexpr NameScopeId NameScopeId::Invalid = NameScopeId(NameScopeId::InvalidIndex); // The ID of a node block. struct NodeBlockId : public IndexBase, public Printable { // All File instances must provide the 0th node block as empty. static const NodeBlockId Empty; // An explicitly invalid ID. static const NodeBlockId Invalid; // An ID for unreachable code. static const NodeBlockId Unreachable; using IndexBase::IndexBase; auto Print(llvm::raw_ostream& out) const -> void { if (index == Unreachable.index) { out << "unreachable"; } else { out << "block"; IndexBase::Print(out); } } }; constexpr NodeBlockId NodeBlockId::Empty = NodeBlockId(0); constexpr NodeBlockId NodeBlockId::Invalid = NodeBlockId(NodeBlockId::InvalidIndex); constexpr NodeBlockId NodeBlockId::Unreachable = NodeBlockId(NodeBlockId::InvalidIndex - 1); // The ID of a real literal. struct RealLiteralId : public IndexBase, public Printable { using IndexBase::IndexBase; auto Print(llvm::raw_ostream& out) const -> void { out << "real"; IndexBase::Print(out); } }; // The ID of a string. struct StringId : public IndexBase, public Printable { using IndexBase::IndexBase; auto Print(llvm::raw_ostream& out) const -> void { out << "str"; IndexBase::Print(out); } }; // The ID of a node block. struct TypeId : public IndexBase, public Printable { // The builtin TypeType. static const TypeId TypeType; // The builtin Error. static const TypeId Error; // An explicitly invalid ID. static const TypeId Invalid; using IndexBase::IndexBase; auto Print(llvm::raw_ostream& out) const -> void { out << "type"; if (index == TypeType.index) { out << "TypeType"; } else if (index == Error.index) { out << "Error"; } else { IndexBase::Print(out); } } }; constexpr TypeId TypeId::TypeType = TypeId(TypeId::InvalidIndex - 2); constexpr TypeId TypeId::Error = TypeId(TypeId::InvalidIndex - 1); constexpr TypeId TypeId::Invalid = TypeId(TypeId::InvalidIndex); // The ID of a type block. struct TypeBlockId : public IndexBase, public Printable { using IndexBase::IndexBase; auto Print(llvm::raw_ostream& out) const -> void { out << "typeBlock"; IndexBase::Print(out); } }; // An index for member access. struct MemberIndex : public IndexBase, public Printable { using IndexBase::IndexBase; auto Print(llvm::raw_ostream& out) const -> void { out << "member"; IndexBase::Print(out); } }; // The standard structure for Node. This is trying to provide a minimal // amount of information for a node: // // - parse_node for error placement. // - kind for run-time logic when the input Kind is unknown. // - type_id for quick type checking. // - Up to two Kind-specific members. // // For each Kind in NodeKind, a typical flow looks like: // // - Create a `Node` using `Node::Kind::Make()` // - Access cross-Kind members using `node.type_id()` and similar. // - Access Kind-specific members using `node.GetAsKind()`, which depending on // the number of members will return one of NoArgs, a single value, or a // `std::pair` of values. // - Using the wrong `node.GetAsKind()` is a programming error, and should // CHECK-fail in debug modes (opt may too, but it's not an API guarantee). // // Internally, each Kind uses the `Factory*` types to provide a boilerplate // `Make` and `Get` methods. class Node : public Printable { public: struct NoArgs {}; // Factory base classes are private, then used for public classes. This class // has two public and two private sections to prevent accidents. private: // Provides Make and Get to support 0, 1, or 2 arguments for a Node. // These are protected so that child factories can opt in to what pieces they // want to use. template class FactoryBase { protected: static auto Make(Parse::Node parse_node, TypeId type_id, ArgTypes... arg_ids) -> Node { return Node(parse_node, NodeKind::Create(Kind), type_id, arg_ids.index...); } static auto Get(Node node) { struct Unused {}; return GetImpl(node); } private: // GetImpl handles the different return types based on ArgTypes. template static auto GetImpl(Node node) -> std::pair { CARBON_CHECK(node.kind() == Kind); return {Arg0Type(node.arg0_), Arg1Type(node.arg1_)}; } template static auto GetImpl(Node node) -> Arg0Type { CARBON_CHECK(node.kind() == Kind); return Arg0Type(node.arg0_); } template static auto GetImpl(Node node) -> NoArgs { CARBON_CHECK(node.kind() == Kind); return NoArgs(); } }; // Provide Get along with a Make that requires a type. template class Factory : public FactoryBase { public: using FactoryBase::Make; using FactoryBase::Get; }; // Provides Get along with a Make that assumes the node doesn't produce a // typed value. template class FactoryNoType : public FactoryBase { public: static auto Make(Parse::Node parse_node, ArgTypes... args) { return FactoryBase::Make(parse_node, TypeId::Invalid, args...); } using FactoryBase::Get; }; public: // Invalid is in the NodeKind enum, but should never be used. class Invalid { public: static auto Get(Node /*node*/) -> Node::NoArgs { CARBON_FATAL() << "Invalid access"; } }; using AddressOf = Node::Factory; using ArrayIndex = Factory; using ArrayType = Node::Factory; using ArrayValue = Factory; using Assign = Node::FactoryNoType; using BinaryOperatorAdd = Node::Factory; using BindValue = Factory; using BlockArg = Factory; using BoolLiteral = Factory; using Branch = FactoryNoType; using BranchIf = FactoryNoType; using BranchWithArg = FactoryNoType; class Builtin { public: static auto Make(BuiltinKind builtin_kind, TypeId type_id) -> Node { // Builtins won't have a Parse::Tree node associated, so we provide the // default invalid one. // This can't use the standard Make function because of the `AsInt()` cast // instead of `.index`. return Node(Parse::Node::Invalid, NodeKind::Builtin, type_id, builtin_kind.AsInt()); } static auto Get(Node node) -> BuiltinKind { return BuiltinKind::FromInt(node.arg0_); } }; using Call = Factory; using ConstType = Factory; class CrossReference : public FactoryBase { public: static auto Make(TypeId type_id, CrossReferenceIRId ir_id, NodeId node_id) -> Node { // A node's parse tree node must refer to a node in the current parse // tree. This cannot use the cross-referenced node's parse tree node // because it will be in a different parse tree. return FactoryBase::Make(Parse::Node::Invalid, type_id, ir_id, node_id); } using FactoryBase::Get; }; using Dereference = Factory; using FunctionDeclaration = FactoryNoType; using IntegerLiteral = Factory; using Namespace = FactoryNoType; using NoOp = FactoryNoType; using Parameter = Factory; using PointerType = Factory; using RealLiteral = Factory; using Return = FactoryNoType; using ReturnExpression = FactoryNoType; using StringLiteral = Factory; using StructAccess = Factory; using StructType = Factory; using StructTypeField = FactoryNoType; using StructValue = Factory; using StubReference = Factory; using Temporary = Factory; using TemporaryStorage = Factory; using TupleIndex = Factory; using TupleType = Factory; using TupleValue = Factory; using UnaryOperatorNot = Factory; using VarStorage = Factory; explicit Node() : Node(Parse::Node::Invalid, NodeKind::Invalid, TypeId::Invalid) {} // Provide `node.GetAsKind()` as an instance method for all kinds, essentially // an alias for`Node::Kind::Get(node)`. #define CARBON_SEMANTICS_NODE_KIND(Name) \ auto GetAs##Name() const { return Name::Get(*this); } #include "toolchain/sem_ir/node_kind.def" auto parse_node() const -> Parse::Node { return parse_node_; } auto kind() const -> NodeKind { return kind_; } // Gets the type of the value produced by evaluating this node. auto type_id() const -> TypeId { return type_id_; } auto Print(llvm::raw_ostream& out) const -> void; private: // Builtins have peculiar construction, so they are a friend rather than using // a factory base class. friend struct NodeForBuiltin; explicit Node(Parse::Node parse_node, NodeKind kind, TypeId type_id, int32_t arg0 = NodeId::InvalidIndex, int32_t arg1 = NodeId::InvalidIndex) : parse_node_(parse_node), kind_(kind), type_id_(type_id), arg0_(arg0), arg1_(arg1) {} Parse::Node parse_node_; NodeKind kind_; TypeId type_id_; // Use GetAsKind to access arg0 and arg1. int32_t arg0_; int32_t arg1_; }; // TODO: This is currently 20 bytes because we sometimes have 2 arguments for a // pair of Nodes. However, NodeKind is 1 byte; if args // were 3.5 bytes, we could potentially shrink Node by 4 bytes. This // may be worth investigating further. static_assert(sizeof(Node) == 20, "Unexpected Node size"); // Provides base support for use of Id types as DenseMap/DenseSet keys. // Instantiated below. template struct IdMapInfo { static inline auto getEmptyKey() -> Id { return Id(llvm::DenseMapInfo::getEmptyKey()); } static inline auto getTombstoneKey() -> Id { return Id(llvm::DenseMapInfo::getTombstoneKey()); } static auto getHashValue(const Id& val) -> unsigned { return llvm::DenseMapInfo::getHashValue(val.index); } static auto isEqual(const Id& lhs, const Id& rhs) -> bool { return lhs == rhs; } }; } // namespace Carbon::SemIR // Support use of Id types as DenseMap/DenseSet keys. template <> struct llvm::DenseMapInfo : public Carbon::SemIR::IdMapInfo {}; template <> struct llvm::DenseMapInfo : public Carbon::SemIR::IdMapInfo {}; template <> struct llvm::DenseMapInfo : public Carbon::SemIR::IdMapInfo {}; #endif // CARBON_TOOLCHAIN_SEM_IR_NODE_H_