Edit

kc3-lang/angle/src/compiler/translator/ValidateAST.cpp

Branch :

  • Show log

    Commit

  • Author : Shahbaz Youssefi
    Date : 2021-05-08 22:09:38
    Hash : daeac238
    Message : Translator: Ensure structs and blocks are uniquely defined A new AST validation is added to ensure that the same TStructure or TInterfaceBlock is not redundantly defined. This helps with SPIR-V generation by allowing the id to be used as key in a hash map that looks up the corresponding SPIR-V type id. A bug is fixed where the Vulkan driver uniform declaration created two identical declarations for ANGLEDepthRangeParams. A number of other bugs are also fixed in this change, where if a variable declaration is eliminated (for example due to constant folding, or inactive interface variable removal) and it contained a struct specifier, the struct declaration was also removed. OutputGLSLBase had a hack where structs were declared on first encounter, which was incorrect as the scope of the declaration could change. Those bugs are fixed and this hack is removed. Bug: angleproject:2733 Bug: angleproject:4889 Bug: angleproject:5936 Change-Id: I8e13748c0bf552ae8b052249282769a1f0775603 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2881942 Reviewed-by: Charlie Lao <cclao@google.com> Reviewed-by: Jamie Madill <jmadill@chromium.org> Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>

  • src/compiler/translator/ValidateAST.cpp
  • //
    // Copyright 2019 The ANGLE Project Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.
    //
    
    #include "compiler/translator/ValidateAST.h"
    
    #include "compiler/translator/Diagnostics.h"
    #include "compiler/translator/Symbol.h"
    #include "compiler/translator/tree_util/IntermTraverse.h"
    #include "compiler/translator/tree_util/SpecializationConstant.h"
    
    namespace sh
    {
    
    namespace
    {
    
    class ValidateAST : public TIntermTraverser
    {
      public:
        static bool validate(TIntermNode *root,
                             TDiagnostics *diagnostics,
                             const ValidateASTOptions &options);
    
        void visitSymbol(TIntermSymbol *node) override;
        void visitConstantUnion(TIntermConstantUnion *node) override;
        bool visitSwizzle(Visit visit, TIntermSwizzle *node) override;
        bool visitBinary(Visit visit, TIntermBinary *node) override;
        bool visitUnary(Visit visit, TIntermUnary *node) override;
        bool visitTernary(Visit visit, TIntermTernary *node) override;
        bool visitIfElse(Visit visit, TIntermIfElse *node) override;
        bool visitSwitch(Visit visit, TIntermSwitch *node) override;
        bool visitCase(Visit visit, TIntermCase *node) override;
        void visitFunctionPrototype(TIntermFunctionPrototype *node) override;
        bool visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node) override;
        bool visitAggregate(Visit visit, TIntermAggregate *node) override;
        bool visitBlock(Visit visit, TIntermBlock *node) override;
        bool visitGlobalQualifierDeclaration(Visit visit,
                                             TIntermGlobalQualifierDeclaration *node) override;
        bool visitDeclaration(Visit visit, TIntermDeclaration *node) override;
        bool visitLoop(Visit visit, TIntermLoop *node) override;
        bool visitBranch(Visit visit, TIntermBranch *node) override;
        void visitPreprocessorDirective(TIntermPreprocessorDirective *node) override;
    
      private:
        ValidateAST(TIntermNode *root, TDiagnostics *diagnostics, const ValidateASTOptions &options);
    
        // Visit as a generic node
        void visitNode(Visit visit, TIntermNode *node);
        // Visit a structure or interface block, and recursively visit its fields of structure type.
        void visitStructOrInterfaceBlockDeclaration(const TType &type, const TSourceLoc &location);
        void visitStructInDeclarationUsage(const TType &type, const TSourceLoc &location);
    
        void scope(Visit visit);
        bool isVariableDeclared(const TVariable *variable);
        bool variableNeedsDeclaration(const TVariable *variable);
        const TFieldListCollection *getStructOrInterfaceBlock(const TType &type,
                                                              ImmutableString *typeNameOut);
    
        void expectNonNullChildren(Visit visit, TIntermNode *node, size_t least_count);
    
        bool validateInternal();
    
        ValidateASTOptions mOptions;
        TDiagnostics *mDiagnostics;
    
        // For validateSingleParent:
        std::map<TIntermNode *, TIntermNode *> mParent;
        bool mSingleParentFailed = false;
    
        // For validateVariableReferences:
        std::vector<std::set<const TVariable *>> mDeclaredVariables;
        std::set<const TInterfaceBlock *> mNamelessInterfaceBlocks;
        bool mVariableReferencesFailed = false;
    
        // For validateNullNodes:
        bool mNullNodesFailed = false;
    
        // For validateStructUsage:
        std::vector<std::map<ImmutableString, const TFieldListCollection *>> mStructsAndBlocksByName;
        bool mStructUsageFailed = false;
    
        // For validateMultiDeclarations:
        bool mMultiDeclarationsFailed = false;
    };
    
    bool ValidateAST::validate(TIntermNode *root,
                               TDiagnostics *diagnostics,
                               const ValidateASTOptions &options)
    {
        ValidateAST validate(root, diagnostics, options);
        root->traverse(&validate);
        return validate.validateInternal();
    }
    
    ValidateAST::ValidateAST(TIntermNode *root,
                             TDiagnostics *diagnostics,
                             const ValidateASTOptions &options)
        : TIntermTraverser(true, false, true, nullptr), mOptions(options), mDiagnostics(diagnostics)
    {
        bool isTreeRoot = root->getAsBlock() && root->getAsBlock()->isTreeRoot();
    
        // Some validations are not applicable unless run on the entire tree.
        if (!isTreeRoot)
        {
            mOptions.validateVariableReferences = false;
        }
    
        if (mOptions.validateSingleParent)
        {
            mParent[root] = nullptr;
        }
    }
    
    void ValidateAST::visitNode(Visit visit, TIntermNode *node)
    {
        if (visit == PreVisit && mOptions.validateSingleParent)
        {
            size_t childCount = node->getChildCount();
            for (size_t i = 0; i < childCount; ++i)
            {
                TIntermNode *child = node->getChildNode(i);
                if (mParent.find(child) != mParent.end())
                {
                    // If child is visited twice but through the same parent, the problem is in one of
                    // the ancestors.
                    if (mParent[child] != node)
                    {
                        mDiagnostics->error(node->getLine(), "Found child with two parents",
                                            "<validateSingleParent>");
                        mSingleParentFailed = true;
                    }
                }
    
                mParent[child] = node;
            }
        }
    }
    
    void ValidateAST::visitStructOrInterfaceBlockDeclaration(const TType &type,
                                                             const TSourceLoc &location)
    {
        if (type.getStruct() == nullptr && type.getInterfaceBlock() == nullptr)
        {
            return;
        }
    
        // Make sure the structure or interface block is not doubly defined.
        ImmutableString typeName("");
        const TFieldListCollection *structOrBlock = getStructOrInterfaceBlock(type, &typeName);
    
        if (structOrBlock)
        {
            ASSERT(!typeName.empty());
    
            if (mStructsAndBlocksByName.back().find(typeName) != mStructsAndBlocksByName.back().end())
            {
                mDiagnostics->error(location,
                                    "Found redeclaration of struct or interface block with the same "
                                    "name in the same scope <validateStructUsage>",
                                    typeName.data());
                mStructUsageFailed = true;
            }
            else
            {
                // First encounter.
                mStructsAndBlocksByName.back()[typeName] = structOrBlock;
            }
        }
    
        // Recurse the fields of the structure or interface block and check members of structure type.
        // Note that structOrBlock was previously only set for named structures, so make sure nameless
        // structs are also recursed.
        if (structOrBlock == nullptr)
        {
            structOrBlock = type.getStruct();
        }
        ASSERT(structOrBlock != nullptr);
    
        for (const TField *field : structOrBlock->fields())
        {
            visitStructInDeclarationUsage(*field->type(), field->line());
        }
    }
    
    void ValidateAST::visitStructInDeclarationUsage(const TType &type, const TSourceLoc &location)
    {
        if (type.getStruct() == nullptr)
        {
            return;
        }
    
        // Make sure the structure being referenced has the same pointer as the closest (in scope)
        // definition.
        const TStructure *structure     = type.getStruct();
        const ImmutableString &typeName = structure->name();
    
        bool foundDeclaration = false;
        for (size_t scopeIndex = mStructsAndBlocksByName.size(); scopeIndex > 0; --scopeIndex)
        {
            const std::map<ImmutableString, const TFieldListCollection *> &scopeDecls =
                mStructsAndBlocksByName[scopeIndex - 1];
    
            auto iter = scopeDecls.find(typeName);
            if (iter != scopeDecls.end())
            {
                foundDeclaration = true;
    
                if (iter->second != structure)
                {
                    mDiagnostics->error(location,
                                        "Found reference to struct or interface block with doubly "
                                        "created type <validateStructUsage>",
                                        typeName.data());
                    mStructUsageFailed = true;
                }
            }
        }
    
        if (!foundDeclaration)
        {
            mDiagnostics->error(location,
                                "Found reference to struct or interface block with no declaration "
                                "<validateStructUsage>",
                                typeName.data());
            mStructUsageFailed = true;
        }
    }
    
    void ValidateAST::scope(Visit visit)
    {
        if (mOptions.validateVariableReferences)
        {
            if (visit == PreVisit)
            {
                mDeclaredVariables.push_back({});
            }
            else if (visit == PostVisit)
            {
                mDeclaredVariables.pop_back();
            }
        }
    
        if (mOptions.validateStructUsage)
        {
            if (visit == PreVisit)
            {
                mStructsAndBlocksByName.push_back({});
            }
            else if (visit == PostVisit)
            {
                mStructsAndBlocksByName.pop_back();
            }
        }
    }
    
    bool ValidateAST::isVariableDeclared(const TVariable *variable)
    {
        ASSERT(mOptions.validateVariableReferences);
    
        for (const std::set<const TVariable *> &scopeVariables : mDeclaredVariables)
        {
            if (scopeVariables.count(variable) > 0)
            {
                return true;
            }
        }
    
        return false;
    }
    
    bool ValidateAST::variableNeedsDeclaration(const TVariable *variable)
    {
        // Don't expect declaration for built-in variables.
        if (variable->name().beginsWith("gl_"))
        {
            return false;
        }
    
        // Additionally, don't expect declaration for Vulkan specialization constants.  There is no
        // representation for them in the AST.
        if (variable->symbolType() == SymbolType::AngleInternal &&
            SpecConst::IsSpecConstName(variable->name()))
        {
            return false;
        }
    
        return true;
    }
    
    const TFieldListCollection *ValidateAST::getStructOrInterfaceBlock(const TType &type,
                                                                       ImmutableString *typeNameOut)
    {
        const TStructure *structure           = type.getStruct();
        const TInterfaceBlock *interfaceBlock = type.getInterfaceBlock();
    
        ASSERT(structure != nullptr || interfaceBlock != nullptr);
    
        // Make sure the structure or interface block is not doubly defined.
        const TFieldListCollection *structOrBlock = nullptr;
        if (structure != nullptr && structure->symbolType() != SymbolType::Empty)
        {
            structOrBlock = structure;
            *typeNameOut  = structure->name();
        }
        else if (interfaceBlock != nullptr)
        {
            structOrBlock = interfaceBlock;
            *typeNameOut  = interfaceBlock->name();
        }
    
        return structOrBlock;
    }
    
    void ValidateAST::expectNonNullChildren(Visit visit, TIntermNode *node, size_t least_count)
    {
        if (visit == PreVisit && mOptions.validateNullNodes)
        {
            size_t childCount = node->getChildCount();
            if (childCount < least_count)
            {
                mDiagnostics->error(node->getLine(), "Too few children", "<validateNullNodes>");
                mNullNodesFailed = true;
            }
    
            for (size_t i = 0; i < childCount; ++i)
            {
                if (node->getChildNode(i) == nullptr)
                {
                    mDiagnostics->error(node->getLine(), "Found nullptr child", "<validateNullNodes>");
                    mNullNodesFailed = true;
                }
            }
        }
    }
    
    void ValidateAST::visitSymbol(TIntermSymbol *node)
    {
        visitNode(PreVisit, node);
    
        const TVariable *variable = &node->variable();
        const TType &type         = node->getType();
    
        if (mOptions.validateVariableReferences && variableNeedsDeclaration(variable))
        {
            // If it's a reference to a field of a nameless interface block, match it by index and name.
            if (type.getInterfaceBlock() && !type.isInterfaceBlock())
            {
                const TInterfaceBlock *interfaceBlock = type.getInterfaceBlock();
                const TFieldList &fieldList           = interfaceBlock->fields();
                const size_t fieldIndex               = type.getInterfaceBlockFieldIndex();
    
                if (mNamelessInterfaceBlocks.count(interfaceBlock) == 0)
                {
                    mDiagnostics->error(node->getLine(),
                                        "Found reference to undeclared or inconsistenly redeclared "
                                        "nameless interface block <validateVariableReferences>",
                                        node->getName().data());
                    mVariableReferencesFailed = true;
                }
                else if (fieldIndex >= fieldList.size() ||
                         node->getName() != fieldList[fieldIndex]->name())
                {
                    mDiagnostics->error(node->getLine(),
                                        "Found reference to inconsistenly redeclared nameless "
                                        "interface block field <validateVariableReferences>",
                                        node->getName().data());
                    mVariableReferencesFailed = true;
                }
            }
            else
            {
                const bool isStructDeclaration =
                    type.isStructSpecifier() && variable->symbolType() == SymbolType::Empty;
    
                if (!isStructDeclaration && !isVariableDeclared(variable))
                {
                    mDiagnostics->error(node->getLine(),
                                        "Found reference to undeclared or inconsistently redeclared "
                                        "variable <validateVariableReferences>",
                                        node->getName().data());
                    mVariableReferencesFailed = true;
                }
            }
        }
    }
    
    void ValidateAST::visitConstantUnion(TIntermConstantUnion *node)
    {
        visitNode(PreVisit, node);
    }
    
    bool ValidateAST::visitSwizzle(Visit visit, TIntermSwizzle *node)
    {
        visitNode(visit, node);
        return true;
    }
    
    bool ValidateAST::visitBinary(Visit visit, TIntermBinary *node)
    {
        visitNode(visit, node);
        return true;
    }
    
    bool ValidateAST::visitUnary(Visit visit, TIntermUnary *node)
    {
        visitNode(visit, node);
        return true;
    }
    
    bool ValidateAST::visitTernary(Visit visit, TIntermTernary *node)
    {
        visitNode(visit, node);
        return true;
    }
    
    bool ValidateAST::visitIfElse(Visit visit, TIntermIfElse *node)
    {
        visitNode(visit, node);
        return true;
    }
    
    bool ValidateAST::visitSwitch(Visit visit, TIntermSwitch *node)
    {
        visitNode(visit, node);
        return true;
    }
    
    bool ValidateAST::visitCase(Visit visit, TIntermCase *node)
    {
        visitNode(visit, node);
        return true;
    }
    
    void ValidateAST::visitFunctionPrototype(TIntermFunctionPrototype *node)
    {
        visitNode(PreVisit, node);
    }
    
    bool ValidateAST::visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node)
    {
        visitNode(visit, node);
        scope(visit);
    
        if (mOptions.validateVariableReferences && visit == PreVisit)
        {
            const TFunction *function = node->getFunction();
    
            size_t paramCount = function->getParamCount();
            for (size_t paramIndex = 0; paramIndex < paramCount; ++paramIndex)
            {
                const TVariable *variable = function->getParam(paramIndex);
    
                if (isVariableDeclared(variable))
                {
                    mDiagnostics->error(node->getLine(),
                                        "Found two declarations of the same function argument "
                                        "<validateVariableReferences>",
                                        variable->name().data());
                    mVariableReferencesFailed = true;
                    break;
                }
    
                mDeclaredVariables.back().insert(variable);
            }
        }
    
        return true;
    }
    
    bool ValidateAST::visitAggregate(Visit visit, TIntermAggregate *node)
    {
        visitNode(visit, node);
        expectNonNullChildren(visit, node, 0);
        return true;
    }
    
    bool ValidateAST::visitBlock(Visit visit, TIntermBlock *node)
    {
        visitNode(visit, node);
        scope(visit);
        expectNonNullChildren(visit, node, 0);
        return true;
    }
    
    bool ValidateAST::visitGlobalQualifierDeclaration(Visit visit,
                                                      TIntermGlobalQualifierDeclaration *node)
    {
        visitNode(visit, node);
        return true;
    }
    
    bool ValidateAST::visitDeclaration(Visit visit, TIntermDeclaration *node)
    {
        visitNode(visit, node);
        expectNonNullChildren(visit, node, 0);
    
        const TIntermSequence &sequence = *(node->getSequence());
    
        if (mOptions.validateMultiDeclarations && sequence.size() > 1)
        {
            mMultiDeclarationsFailed = true;
        }
    
        if (visit == PreVisit)
        {
            bool validateStructUsage = mOptions.validateStructUsage;
    
            for (TIntermNode *instance : sequence)
            {
                TIntermSymbol *symbol = instance->getAsSymbolNode();
                if (symbol == nullptr)
                {
                    TIntermBinary *init = instance->getAsBinaryNode();
                    ASSERT(init && init->getOp() == EOpInitialize);
                    symbol = init->getLeft()->getAsSymbolNode();
                }
                ASSERT(symbol);
    
                const TVariable *variable = &symbol->variable();
    
                if (mOptions.validateVariableReferences)
                {
                    if (isVariableDeclared(variable))
                    {
                        mDiagnostics->error(
                            node->getLine(),
                            "Found two declarations of the same variable <validateVariableReferences>",
                            variable->name().data());
                        mVariableReferencesFailed = true;
                        break;
                    }
    
                    mDeclaredVariables.back().insert(variable);
    
                    const TInterfaceBlock *interfaceBlock = variable->getType().getInterfaceBlock();
    
                    if (variable->symbolType() == SymbolType::Empty && interfaceBlock != nullptr)
                    {
                        // Nameless interface blocks can only be declared at the top level.  Their
                        // fields are matched by field index, and then verified to match by name.
                        // Conflict in names should have already generated a compile error.
                        ASSERT(mDeclaredVariables.size() == 1);
                        ASSERT(mNamelessInterfaceBlocks.count(interfaceBlock) == 0);
    
                        mNamelessInterfaceBlocks.insert(interfaceBlock);
                    }
                }
    
                if (validateStructUsage)
                {
                    // Only declare the struct once.
                    validateStructUsage = false;
    
                    const TType &type = variable->getType();
                    if (type.isStructSpecifier() || type.isInterfaceBlock())
                        visitStructOrInterfaceBlockDeclaration(type, node->getLine());
                }
            }
        }
    
        return true;
    }
    
    bool ValidateAST::visitLoop(Visit visit, TIntermLoop *node)
    {
        visitNode(visit, node);
        return true;
    }
    
    bool ValidateAST::visitBranch(Visit visit, TIntermBranch *node)
    {
        visitNode(visit, node);
        return true;
    }
    
    void ValidateAST::visitPreprocessorDirective(TIntermPreprocessorDirective *node)
    {
        visitNode(PreVisit, node);
    }
    
    bool ValidateAST::validateInternal()
    {
        return !mSingleParentFailed && !mVariableReferencesFailed && !mNullNodesFailed &&
               !mStructUsageFailed && !mMultiDeclarationsFailed;
    }
    
    }  // anonymous namespace
    
    bool ValidateAST(TIntermNode *root, TDiagnostics *diagnostics, const ValidateASTOptions &options)
    {
        return ValidateAST::validate(root, diagnostics, options);
    }
    
    }  // namespace sh