// slang-ast-base.h #pragma once #include "slang-ast-forward-declarations.h" #include "slang-ast-support-types.h" #include "slang-capability.h" // #include "slang-ast-base.h.fiddle" // This file defines the primary base classes for the hierarchy of // AST nodes and related objects. For example, this is where the // basic `Decl`, `Stmt`, `Expr`, `type`, etc. definitions come from. FIDDLE() namespace Slang { class ASTBuilder; struct SemanticsVisitor; FIDDLE(abstract) class NodeBase { FIDDLE(...) // MUST be called before used. Called automatically via the ASTBuilder. // Note that the astBuilder is not stored in the NodeBase derived types by default. SLANG_FORCE_INLINE void init(ASTNodeType inAstNodeType, ASTBuilder* inAstBuilder) { astNodeType = inAstNodeType; _astBuilder = inAstBuilder; #ifdef _DEBUG _initDebug(inAstNodeType, inAstBuilder); #endif } void _initDebug(ASTNodeType inAstNodeType, ASTBuilder* inAstBuilder); SyntaxClass getClass() const { return SyntaxClass(astNodeType); } /// The type of the node. ASTNodeType(-1) is an invalid node type, and shouldn't appear on any /// correctly constructed (through ASTBuilder) NodeBase derived class. /// The actual type is set when constructed on the ASTBuilder. ASTNodeType astNodeType = ASTNodeType(-1); #ifdef _DEBUG int32_t _debugUID = 0; #endif /// Get the AST builder that was used to allocate this node. ASTBuilder* getASTBuilder() { return _astBuilder; } private: friend class ASTBuilder; /// The AST builder that was used to allocate this node. ASTBuilder* _astBuilder = nullptr; }; // Casting of NodeBase template SLANG_FORCE_INLINE T* dynamicCast(NodeBase* node) { return (node && node->getClass().isSubClassOf()) ? static_cast(node) : nullptr; } template SLANG_FORCE_INLINE const T* dynamicCast(const NodeBase* node) { return (node && node->getClass().isSubClassOf()) ? static_cast(node) : nullptr; } template SLANG_FORCE_INLINE T* as(NodeBase* node) { return (node && node->getClass().isSubClassOf()) ? static_cast(node) : nullptr; } template SLANG_FORCE_INLINE const T* as(const NodeBase* node) { return (node && node->getClass().isSubClassOf()) ? static_cast(node) : nullptr; } // Because DeclRefBase is now a `Val`, we prevent casting it directly into other nodes // to avoid confusion and bugs. Instead, use the `as<>()` method on `DeclRefBase` to // get a `DeclRef` for a specific node type. template T* as( DeclRefBase* declRefBase, typename EnableIf::Value, void*>::type arg = nullptr) = delete; template T* as( DeclRefBase* declRefBase, typename EnableIf::Value, void*>::type arg = nullptr) { SLANG_UNUSED(arg); return dynamicCast(declRefBase); } template DeclRef as(DeclRef declRef) { return DeclRef(declRef); } FIDDLE() class Scope : public NodeBase { FIDDLE(...) // The container to use for lookup // // Note(tfoley): This is kept as an unowned pointer // so that a scope can't keep parts of the AST alive, // but the opposite it allowed. ContainerDecl* containerDecl = nullptr; // The parent of this scope (where lookup should go if nothing is found locally) Scope* parent = nullptr; // The next sibling of this scope (a peer for lookup) Scope* nextSibling = nullptr; }; // Base class for all nodes representing actual syntax // (thus having a location in the source code) FIDDLE(abstract) class SyntaxNodeBase : public NodeBase { FIDDLE(...) // The primary source location associated with this AST node FIDDLE() SourceLoc loc; }; enum class ValNodeOperandKind { ConstantValue, ValNode, ASTNode, }; struct ValNodeOperand { ValNodeOperandKind kind = ValNodeOperandKind::ConstantValue; union { NodeBase* nodeOperand; int64_t intOperand; } values; ValNodeOperand() { values.intOperand = 0; } int64_t getIntConstant() const { SLANG_ASSERT(kind == ValNodeOperandKind::ConstantValue); return values.intOperand; } Val* getVal() const { SLANG_ASSERT(kind == ValNodeOperandKind::ValNode); return (Val*)values.nodeOperand; } Decl* getDecl() const { SLANG_ASSERT(kind == ValNodeOperandKind::ASTNode); return (Decl*)values.nodeOperand; } explicit ValNodeOperand(NodeBase* node) { if constexpr (sizeof(values.nodeOperand) < sizeof(values.intOperand)) values.intOperand = 0; if (as(node)) { values.nodeOperand = (NodeBase*)node; kind = ValNodeOperandKind::ValNode; } else { values.nodeOperand = node; kind = ValNodeOperandKind::ASTNode; } } template explicit ValNodeOperand(DeclRef declRef) { if constexpr (sizeof(values.nodeOperand) < sizeof(values.intOperand)) values.intOperand = 0; values.nodeOperand = declRef.declRefBase; kind = ValNodeOperandKind::ValNode; } template explicit ValNodeOperand(T* node) { if constexpr (sizeof(values.nodeOperand) < sizeof(values.intOperand)) values.intOperand = 0; if constexpr (std::is_base_of::value) { values.nodeOperand = (NodeBase*)node; kind = ValNodeOperandKind::ValNode; } else if constexpr (std::is_base_of::value) { values.nodeOperand = node; kind = ValNodeOperandKind::ASTNode; } else { static_assert( std::is_base_of::value || std::is_base_of::value, "pointer used as Val operand must be an AST node."); } } template explicit ValNodeOperand(EnumType intVal) { static_assert( std::is_trivial::value, "Type to construct NodeOperand must be trivial."); static_assert( sizeof(EnumType) <= sizeof(values), "size of operand must be less than pointer size."); values.intOperand = 0; memcpy(&values, &intVal, sizeof(intVal)); kind = ValNodeOperandKind::ConstantValue; } }; struct ValNodeDesc { private: HashCode hashCode = 0; public: SyntaxClass type; ShortList operands; inline bool operator==(ValNodeDesc const& that) const { if (hashCode != that.hashCode) return false; if (type != that.type) return false; if (operands.getCount() != that.operands.getCount()) return false; for (Index i = 0; i < operands.getCount(); ++i) { // Note: we are comparing the operands directly for identity // (pointer equality) rather than doing the `Val`-level // equality check. // // The rationale here is that nodes that will be created // via a `NodeDesc` *should* all be going through the // deduplication path anyway, as should their operands. // if (operands[i].values.intOperand != that.operands[i].values.intOperand) return false; } return true; } HashCode getHashCode() const { return hashCode; } void init(); }; template static void addOrAppendToNodeList(ShortList&) { } template static void addOrAppendToNodeList( ShortList& list, ExpandedSpecializationArgs e, Ts... ts) { for (auto arg : e) { list.add(ValNodeOperand(arg.val)); list.add(ValNodeOperand(arg.witness)); } addOrAppendToNodeList(list, ts...); } template static void addOrAppendToNodeList(ShortList& list, T t, Ts... ts) { list.add(ValNodeOperand(t)); addOrAppendToNodeList(list, ts...); } template static void addOrAppendToNodeList(ShortList& list, const List& l, Ts... ts) { for (auto t : l) list.add(ValNodeOperand(t)); addOrAppendToNodeList(list, ts...); } template static void addOrAppendToNodeList(ShortList& list, ConstArrayView l, Ts... ts) { for (auto t : l) list.add(ValNodeOperand(t)); addOrAppendToNodeList(list, ts...); } template static void addOrAppendToNodeList(ShortList& list, ArrayView l, Ts... ts) { for (auto t : l) list.add(ValNodeOperand(t)); addOrAppendToNodeList(list, ts...); } inline void addOrAppendToNodeList(List&) {} template static void addOrAppendToNodeList( List& list, ExpandedSpecializationArgs e, Ts... ts) { for (auto arg : e) { list.add(ValNodeOperand(arg.val)); list.add(ValNodeOperand(arg.witness)); } addOrAppendToNodeList(list, ts...); } template static void addOrAppendToNodeList(List& list, T t, Ts... ts) { list.add(ValNodeOperand(t)); addOrAppendToNodeList(list, ts...); } template static void addOrAppendToNodeList(List& list, const List& l, Ts... ts) { for (auto t : l) list.add(ValNodeOperand(t)); addOrAppendToNodeList(list, ts...); } template static void addOrAppendToNodeList(List& list, ConstArrayView l, Ts... ts) { for (auto t : l) list.add(ValNodeOperand(t)); addOrAppendToNodeList(list, ts...); } template static void addOrAppendToNodeList(List& list, ArrayView l, Ts... ts) { for (auto t : l) list.add(ValNodeOperand(t)); addOrAppendToNodeList(list, ts...); } // Base class for compile-time values (most often a type). // These are *not* syntax nodes, because they do not have // a unique location, and any two `Val`s representing // the same value should be conceptually equal. FIDDLE(abstract) class Val : public NodeBase { FIDDLE(...) template struct OperandView { const Val* val; Index offset; Index count; OperandView() { val = nullptr; offset = 0; count = 0; } OperandView(const Val* val, Index offset, Index count) : val(val), offset(offset), count(count) { } Index getCount() { return count; } T* operator[](Index index) const { return as(val->getOperand(index + offset)); } struct ConstIterator { const Val* val; Index i; bool operator==(ConstIterator other) const { return val == other.val && i == other.i; } bool operator!=(ConstIterator other) const { return val != other.val || i != other.i; } T* const& operator*() const { return *(this->operator->()); } T* const* operator->() const { return reinterpret_cast(&val->m_operands[i].values.nodeOperand); } ConstIterator& operator++() { i++; return *this; } }; ConstIterator begin() const { return ConstIterator{val, offset}; } ConstIterator end() const { return ConstIterator{val, offset + count}; } }; // construct a new value by applying a set of parameter // substitutions to this one Val* substitute(ASTBuilder* astBuilder, SubstitutionSet subst); // Lower-level interface for substitution. Like the basic // `Substitute` above, but also takes a by-reference // integer parameter that should be incremented when // returning a modified value (this can help the caller // decide whether they need to do anything). Val* substituteImpl(ASTBuilder* astBuilder, SubstitutionSet subst, int* ioDiff); bool equals(Val* val) const { return this == val || (val && const_cast(this)->resolve() == val->resolve()); } // Appends as text to the end of the builder void toText(StringBuilder& out); String toString(); HashCode getHashCode(); bool operator==(const Val& v) const { return equals(const_cast(&v)); } // Overrides should be public so base classes can access Val* _substituteImplOverride(ASTBuilder* astBuilder, SubstitutionSet subst, int* ioDiff); void _toTextOverride(StringBuilder& out); Val* _resolveImplOverride(); Val* resolveImpl(); Val* resolve(); Val* getOperand(Index index) const { return m_operands[index].getVal(); } Decl* getDeclOperand(Index index) const { return m_operands[index].getDecl(); } int64_t getIntConstOperand(Index index) const { return m_operands[index].getIntConstant(); } Index getOperandCount() const { return m_operands.getCount(); } template void setOperands(TArgs... args) { m_operands.clear(); addOrAppendToNodeList(m_operands, args...); } template void addOperands(TArgs... args) { addOrAppendToNodeList(m_operands, args...); } template void addOperands(OperandView operands) { for (auto v : operands) m_operands.add(ValNodeOperand(v)); } FIDDLE() List m_operands; // Private use by the core module deserialization only. Since we know the Vals serialized into // the core module is already unique, we can just use `this` pointer as the `m_resolvedVal` so // we don't need to resolve them again. void _setUnique(); protected: Val* defaultResolveImpl(); private: mutable Val* m_resolvedVal = nullptr; mutable Index m_resolvedValEpoch = 0; }; template static void addOrAppendToNodeList( ShortList& list, Val::OperandView l, Ts... ts) { for (auto t : l) list.add(ValNodeOperand(t)); addOrAppendToNodeList(list, ts...); } struct ValSet { struct ValItem { Val* val = nullptr; ValItem() = default; ValItem(Val* v) : val(v) { } HashCode getHashCode() const { return val ? val->getHashCode() : 0; } bool operator==(const ValItem other) const { if (val == other.val) return true; if (val) return val->equals(other.val); else if (other.val) return other.val->equals(val); return false; } }; HashSet set; bool add(Val* val) { return set.add(ValItem(val)); } bool contains(Val* val) { return set.contains(ValItem(val)); } }; SLANG_FORCE_INLINE StringBuilder& operator<<(StringBuilder& io, Val* val) { SLANG_ASSERT(val); val->toText(io); return io; } /// Given a `value` that refers to a `param` of some generic, attempt to apply /// the `subst` to it and produce a new `Val` as a result. /// /// If the `subst` does not include anything to replace `value`, then this function /// returns null. /// Val* maybeSubstituteGenericParam(Val* value, Decl* param, SubstitutionSet subst, int* ioDiff); class Type; template SLANG_FORCE_INLINE T* as(Type* obj); template SLANG_FORCE_INLINE const T* as(const Type* obj); // A type, representing a classifier for some term in the AST. // // Types can include "sugar" in that they may refer to a // `typedef` which gives them a good name when printed as // part of diagnostic messages. // // In order to operate on types, though, we often want // to look past any sugar, and operate on an underlying // "canonical" type. The representation caches a pointer to // a canonical type on every type, so we can easily // operate on the raw representation when needed. FIDDLE(abstract) class Type : public Val { FIDDLE(...) /// Type derived types store the AST builder they were constructed on. The builder calls this /// function after constructing. SLANG_FORCE_INLINE void init(ASTNodeType inAstNodeType, ASTBuilder* inAstBuilder) { Val::init(inAstNodeType, inAstBuilder); m_astBuilderForReflection = inAstBuilder; } // Overrides should be public so base classes can access Val* _substituteImplOverride(ASTBuilder* astBuilder, SubstitutionSet subst, int* ioDiff); Type* _createCanonicalTypeOverride(); Val* _resolveImplOverride(); Type* getCanonicalType() { return as(resolve()); } ASTBuilder* getASTBuilderForReflection() const { return m_astBuilderForReflection; } protected: Type* createCanonicalType(); // We store the ASTBuilder to support reflection API only. // It should not be used for anything else, especially not for constructing new AST nodes during // semantic checking, since Val deduplication requires the entire semantic checking process to // stick with one ASTBuilder. // Call getCurrentASTBuilder() to obtain the right ASTBuilder for semantic checking. ASTBuilder* m_astBuilderForReflection; }; struct TypePair { Type* type0; Type* type1; HashCode getHashCode() const { return combineHash(Slang::getHashCode(type0), Slang::getHashCode(type1)); } bool operator==(const TypePair& other) const { return type0 == other.type0 && type1 == other.type1; } }; template SLANG_FORCE_INLINE T* as(Type* obj) { return obj ? dynamicCast(obj->getCanonicalType()) : nullptr; } template SLANG_FORCE_INLINE const T* as(const Type* obj) { return obj ? dynamicCast(const_cast(obj)->getCanonicalType()) : nullptr; } class Decl; // A reference to a declaration, which may include // substitutions for generic parameters. FIDDLE(abstract) class DeclRefBase : public Val { FIDDLE(...) Decl* getDecl() const { return getDeclOperand(0); } // Apply substitutions to this declaration reference DeclRefBase* substituteImpl(ASTBuilder* astBuilder, SubstitutionSet subst, int* ioDiff); DeclRefBase* _substituteImplOverride(ASTBuilder* astBuilder, SubstitutionSet subst, int* ioDiff) { SLANG_UNUSED(astBuilder); SLANG_UNUSED(subst); SLANG_UNUSED(ioDiff); SLANG_UNREACHABLE("DeclRefBase::_substituteImplOverride not overrided."); } void _toTextOverride(StringBuilder& out) { SLANG_UNUSED(out); SLANG_UNREACHABLE("DeclRefBase::_toTextOverride not overrided."); } Val* _resolveImplOverride() { SLANG_UNREACHABLE("DeclRefBase::_resolveImplOverride not overrided."); } DeclRefBase* _getBaseOverride() { SLANG_UNREACHABLE("DeclRefBase::_getBaseOverride not overrided."); } // Returns true if 'as' will return a valid cast template bool is() const { return Slang::as(getDecl()) != nullptr; } // Convenience accessors for common properties of declarations Name* getName() const; SourceLoc getNameLoc() const; SourceLoc getLoc() const; DeclRefBase* getParent(); String toString() const; DeclRefBase* getBase(); void toText(StringBuilder& out); }; SLANG_FORCE_INLINE StringBuilder& operator<<(StringBuilder& io, const DeclRefBase* declRef) { if (declRef) const_cast(declRef)->toText(io); return io; } SLANG_FORCE_INLINE StringBuilder& operator<<(StringBuilder& io, Decl* decl) { if (decl) makeDeclRef(decl).declRefBase->toText(io); return io; } FIDDLE(abstract) class SyntaxNode : public SyntaxNodeBase { FIDDLE(...) }; // // All modifiers are represented as full-fledged objects in the AST // (that is, we don't use a bitfield, even for simple/common flags). // This ensures that we can track source locations for all modifiers. // FIDDLE(abstract) class Modifier : public SyntaxNode { FIDDLE(...) // Next modifier in linked list of modifiers on same piece of syntax Modifier* next = nullptr; // The keyword that was used to introduce t that was used to name this modifier. FIDDLE() Name* keywordName = nullptr; Name* getKeywordName() { return keywordName; } NameLoc getKeywordNameAndLoc() { return NameLoc(keywordName, loc); } }; // A syntax node which can have modifiers applied FIDDLE(abstract) class ModifiableSyntaxNode : public SyntaxNode { FIDDLE(...) FIDDLE() Modifiers modifiers; template FilteredModifierList getModifiersOfType() { return FilteredModifierList(modifiers.first); } // Find the first modifier of a given type, or return `nullptr` if none is found. template T* findModifier() { return *getModifiersOfType().begin(); } template bool hasModifier() { return findModifier() != nullptr; } }; struct ProvenenceNodeWithLoc { NodeBase* referencedNode; SourceLoc referenceLoc; }; // An intermediate type to represent either a single declaration, or a group of declarations FIDDLE(abstract) class DeclBase : public ModifiableSyntaxNode { FIDDLE(...) }; FIDDLE(abstract) class Decl : public DeclBase { FIDDLE(...) public: FIDDLE() ContainerDecl* parentDecl = nullptr; DeclRefBase* getDefaultDeclRef(); FIDDLE() NameLoc nameAndLoc; FIDDLE() CapabilitySet inferredCapabilityRequirements; RefPtr markup; Name* getName() const { return nameAndLoc.name; } SourceLoc getNameLoc() const { return nameAndLoc.loc; } NameLoc getNameAndLoc() const { return nameAndLoc; } DeclCheckStateExt checkState = DeclCheckState::Unchecked; /// The previous declaration defined in the same `ContainerDecl` /// that has the same name as this declaration. /// /// Note: it is not recommended to ever access this member directly; /// instead, code should use the `ContainerDecl::getPrevDeclWithSameName()` /// method, which ensures that the `_prevInContainerWithSameName` fields /// have been properly set for all declarations in that container. /// FIDDLE() Decl* _prevInContainerWithSameName = nullptr; bool isChecked(DeclCheckState state) const { return checkState >= state; } void setCheckState(DeclCheckState state) { SLANG_RELEASE_ASSERT(state >= checkState.getState()); checkState.setState(state); } bool isChildOf(Decl* other) const; // Track the decl reference that caused the requirement of a capability atom. List capabilityRequirementProvenance; bool hiddenFromLookup = false; private: DeclRefBase* m_defaultDeclRef = nullptr; }; FIDDLE(abstract) class Expr : public SyntaxNode { FIDDLE(...) FIDDLE() QualType type; bool checked = false; }; FIDDLE(abstract) class Stmt : public ModifiableSyntaxNode { FIDDLE(...) }; template void DeclRef::init(DeclRefBase* base) { if (base && !Slang::as(base->getDecl())) declRefBase = nullptr; else declRefBase = base; } template DeclRef::DeclRef(Decl* decl) { DeclRefBase* declRef = nullptr; if (decl) { declRef = decl->getDefaultDeclRef(); } init(declRef); } template T* DeclRef::getDecl() const { return declRefBase ? (T*)declRefBase->getDecl() : nullptr; } template Name* DeclRef::getName() const { if (declRefBase) return declRefBase->getName(); return nullptr; } template SourceLoc DeclRef::getNameLoc() const { if (declRefBase) return declRefBase->getNameLoc(); return SourceLoc(); } template SourceLoc DeclRef::getLoc() const { if (declRefBase) return declRefBase->getLoc(); return SourceLoc(); } template DeclRef DeclRef::getParent() const { if (declRefBase) return DeclRef(declRefBase->getParent()); return DeclRef((DeclRefBase*)nullptr); } template HashCode DeclRef::getHashCode() const { if (declRefBase) return declRefBase->getHashCode(); return 0; } template Type* DeclRef::substitute(ASTBuilder* astBuilder, Type* type) const { SLANG_UNUSED(astBuilder); if (!declRefBase) return type; return SubstitutionSet(*this).applyToType(astBuilder, type); } template SubstExpr DeclRef::substitute(ASTBuilder* astBuilder, Expr* expr) const { SLANG_UNUSED(astBuilder); if (!declRefBase) return expr; return applySubstitutionToExpr(SubstitutionSet(*this), expr); } // Apply substitutions to a type or declaration template template DeclRef DeclRef::substitute(ASTBuilder* astBuilder, DeclRef declRef) const { SLANG_UNUSED(astBuilder); if (!declRefBase) return declRef; return DeclRef(SubstitutionSet(*this).applyToDeclRef(astBuilder, declRef.declRefBase)); } // Apply substitutions to this declaration reference template DeclRef DeclRef::substituteImpl(ASTBuilder* astBuilder, SubstitutionSet subst, int* ioDiff) const { SLANG_UNUSED(astBuilder); if (!declRefBase) return *this; return DeclRef(declRefBase->substituteImpl(astBuilder, subst, ioDiff)); } Val::OperandView tryGetGenericArguments(SubstitutionSet substSet, Decl* genericDecl); } // namespace Slang