⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 248339 in webkit


Ignore:
Timestamp:
Aug 6, 2019, 9:17:29 PM (7 years ago)
Author:
sbarati@apple.com
Message:

[WHLSL] Make resolveFunction in Checker faster
https://bugs.webkit.org/show_bug.cgi?id=200287

Reviewed by Robin Morisset.

This patch makes compute_boids faster by making function overload
resolution faster inside the Checker. It's a ~6ms speedup in the
checker. The main idea is to limit the number of overloads we need
to look for by using a hash table that describes a function's type
instead of just using a hash table keyed by a function's name.

The interesting implementation detail here is we must construct entries
in the hash table such that they still allow constants to be resolved to
various types. This means that the key in the hash table must normalize
the vector of types it uses to express a function's identity. The normalization
rules are:

  • int => float
  • uint => float
  • T* => float*
  • T[] => float*

The first two rules are because int constants can be matched against
the float and uint types. The latter two rules are because the null
literal can be matched against any pointer or any array reference
(we pick float* arbitrarily). Even though it seems like these
normalization rules would drastically broaden the efficacy of the hash
table, we still see a 100x reduction in the number of overloads we must
resolve inside compute_boids. We go from having to resolve 400,000
overloads to just resolving 4,000.

  • Modules/webgpu/WHLSL/WHLSLChecker.cpp:

(WebCore::WHLSL::FunctionKey::FunctionKey):
(WebCore::WHLSL::FunctionKey::isEmptyValue const):
(WebCore::WHLSL::FunctionKey::isHashTableDeletedValue const):
(WebCore::WHLSL::FunctionKey::hash const):
(WebCore::WHLSL::FunctionKey::operator== const):
(WebCore::WHLSL::FunctionKey::Hash::hash):
(WebCore::WHLSL::FunctionKey::Hash::equal):
(WebCore::WHLSL::FunctionKey::Traits::isEmptyValue):
(WebCore::WHLSL::Checker::Checker):
(WebCore::WHLSL::Checker::wrappedFloatType):
(WebCore::WHLSL::Checker::genericPointerType):
(WebCore::WHLSL::Checker::normalizedTypeForFunctionKey):
(WebCore::WHLSL::Checker::resolveFunction):
(WebCore::WHLSL::Checker::finishVisiting):
(WebCore::WHLSL::Checker::visit):
(WebCore::WHLSL::resolveFunction): Deleted.

Location:
trunk/Source/WebCore
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r248335 r248339  
     12019-08-06  Saam Barati  <sbarati@apple.com>
     2
     3        [WHLSL] Make resolveFunction in Checker faster
     4        https://bugs.webkit.org/show_bug.cgi?id=200287
     5
     6        Reviewed by Robin Morisset.
     7
     8        This patch makes compute_boids faster by making function overload
     9        resolution faster inside the Checker. It's a ~6ms speedup in the
     10        checker. The main idea is to limit the number of overloads we need
     11        to look for by using a hash table that describes a function's type
     12        instead of just using a hash table keyed by a function's name.
     13       
     14        The interesting implementation detail here is we must construct entries
     15        in the hash table such that they still allow constants to be resolved to
     16        various types. This means that the key in the hash table must normalize
     17        the vector of types it uses to express a function's identity. The normalization
     18        rules are:
     19        - int => float
     20        - uint => float
     21        - T* => float*
     22        - T[] => float*
     23       
     24        The first two rules are because int constants can be matched against
     25        the float and uint types. The latter two rules are because the null
     26        literal can be matched against any pointer or any array reference
     27        (we pick float* arbitrarily). Even though it seems like these
     28        normalization rules would drastically broaden the efficacy of the hash
     29        table, we still see a 100x reduction in the number of overloads we must
     30        resolve inside compute_boids. We go from having to resolve 400,000
     31        overloads to just resolving 4,000.
     32
     33        * Modules/webgpu/WHLSL/WHLSLChecker.cpp:
     34        (WebCore::WHLSL::FunctionKey::FunctionKey):
     35        (WebCore::WHLSL::FunctionKey::isEmptyValue const):
     36        (WebCore::WHLSL::FunctionKey::isHashTableDeletedValue const):
     37        (WebCore::WHLSL::FunctionKey::hash const):
     38        (WebCore::WHLSL::FunctionKey::operator== const):
     39        (WebCore::WHLSL::FunctionKey::Hash::hash):
     40        (WebCore::WHLSL::FunctionKey::Hash::equal):
     41        (WebCore::WHLSL::FunctionKey::Traits::isEmptyValue):
     42        (WebCore::WHLSL::Checker::Checker):
     43        (WebCore::WHLSL::Checker::wrappedFloatType):
     44        (WebCore::WHLSL::Checker::genericPointerType):
     45        (WebCore::WHLSL::Checker::normalizedTypeForFunctionKey):
     46        (WebCore::WHLSL::Checker::resolveFunction):
     47        (WebCore::WHLSL::Checker::finishVisiting):
     48        (WebCore::WHLSL::Checker::visit):
     49        (WebCore::WHLSL::resolveFunction): Deleted.
     50
    1512019-08-06  Loïc Yhuel  <loic.yhuel@softathome.com>
    252
  • trunk/Source/WebCore/Modules/webgpu/WHLSL/WHLSLChecker.cpp

    r248303 r248339  
    117117        checkErrorAndVisit(typeReference.resolvedType());
    118118    }
     119};
     120
     121class FunctionKey {
     122public:
     123    FunctionKey() = default;
     124    FunctionKey(WTF::HashTableDeletedValueType)
     125    {
     126        m_castReturnType = bitwise_cast<AST::NamedType*>(static_cast<uintptr_t>(1));
     127    }
     128
     129    FunctionKey(String name, Vector<std::reference_wrapper<AST::UnnamedType>> types, AST::NamedType* castReturnType = nullptr)
     130        : m_name(WTFMove(name))
     131        , m_types(WTFMove(types))
     132        , m_castReturnType(castReturnType)
     133    { }
     134
     135    bool isEmptyValue() const { return m_name.isNull(); }
     136    bool isHashTableDeletedValue() const { return m_castReturnType == bitwise_cast<AST::NamedType*>(static_cast<uintptr_t>(1)); }
     137
     138    unsigned hash() const
     139    {
     140        unsigned hash = IntHash<size_t>::hash(m_types.size());
     141        hash ^= m_name.hash();
     142        for (size_t i = 0; i < m_types.size(); ++i)
     143            hash ^= m_types[i].get().hash() + i;
     144
     145        if (m_castReturnType)
     146            hash ^= WTF::PtrHash<AST::Type*>::hash(&m_castReturnType->unifyNode());
     147
     148        return hash;
     149    }
     150
     151    bool operator==(const FunctionKey& other) const
     152    {
     153        if (m_types.size() != other.m_types.size())
     154            return false;
     155
     156        if (m_name != other.m_name)
     157            return false;
     158
     159        for (size_t i = 0; i < m_types.size(); ++i) {
     160            if (!matches(m_types[i].get(), other.m_types[i].get()))
     161                return false;
     162        }
     163
     164        if (!!m_castReturnType != !!other.m_castReturnType)
     165            return false;
     166
     167        if (!m_castReturnType)
     168            return true;
     169
     170        if (&m_castReturnType->unifyNode() == &other.m_castReturnType->unifyNode())
     171            return true;
     172
     173        return false;
     174    }
     175
     176    struct Hash {
     177        static unsigned hash(const FunctionKey& key)
     178        {
     179            return key.hash();
     180        }
     181
     182        static bool equal(const FunctionKey& a, const FunctionKey& b)
     183        {
     184            return a == b;
     185        }
     186
     187        static const bool safeToCompareToEmptyOrDeleted = false;
     188        static const bool emptyValueIsZero = false;
     189    };
     190
     191    struct Traits : public WTF::SimpleClassHashTraits<FunctionKey> {
     192        static const bool hasIsEmptyValueFunction = true;
     193        static bool isEmptyValue(const FunctionKey& key) { return key.isEmptyValue(); }
     194    };
     195
     196private:
     197    String m_name;
     198    Vector<std::reference_wrapper<AST::UnnamedType>> m_types;
     199    AST::NamedType* m_castReturnType;
    119200};
    120201
     
    218299}
    219300
    220 static AST::FunctionDeclaration* resolveFunction(Program& program, Vector<std::reference_wrapper<AST::FunctionDeclaration>, 1>* possibleOverloads, Vector<std::reference_wrapper<ResolvingType>>& types, const String& name, CodeLocation location, const Intrinsics& intrinsics, AST::NamedType* castReturnType = nullptr)
    221 {
    222     if (possibleOverloads) {
    223         if (AST::FunctionDeclaration* function = resolveFunctionOverload(*possibleOverloads, types, castReturnType))
    224             return function;
    225     }
    226 
    227     if (auto newFunction = resolveByInstantiation(name, location, types, intrinsics)) {
    228         program.append(WTFMove(*newFunction));
    229         return &program.nativeFunctionDeclarations().last();
    230     }
    231 
    232     return nullptr;
    233 }
    234 
    235301static bool checkSemantics(Vector<EntryPointItem>& inputItems, Vector<EntryPointItem>& outputItems, const Optional<AST::EntryPointType>& entryPointType, const Intrinsics& intrinsics)
    236302{
     
    455521        , m_program(program)
    456522    {
     523        auto addFunction = [&] (AST::FunctionDeclaration& function) {
     524            AST::NamedType* castReturnType = nullptr;
     525            if (function.isCast() && is<AST::NamedType>(function.type().unifyNode()))
     526                castReturnType = &downcast<AST::NamedType>(function.type().unifyNode());
     527
     528            Vector<std::reference_wrapper<AST::UnnamedType>> types;
     529            types.reserveInitialCapacity(function.parameters().size());
     530
     531            for (auto& param : function.parameters())
     532                types.uncheckedAppend(normalizedTypeForFunctionKey(*param->type()));
     533
     534            auto addResult = m_functions.add(FunctionKey { function.name(), WTFMove(types), castReturnType }, Vector<std::reference_wrapper<AST::FunctionDeclaration>, 1>());
     535            addResult.iterator->value.append(function);
     536        };
     537
     538        for (auto& function : m_program.functionDefinitions())
     539            addFunction(function.get());
     540        for (auto& function : m_program.nativeFunctionDeclarations())
     541            addFunction(function.get());
    457542    }
    458543
     
    512597    void finishVisiting(AST::PropertyAccessExpression&, ResolvingType* additionalArgumentType = nullptr);
    513598
     599    AST::FunctionDeclaration* resolveFunction(Vector<std::reference_wrapper<ResolvingType>>& types, const String& name, CodeLocation, AST::NamedType* castReturnType = nullptr);
     600
     601    AST::UnnamedType& wrappedFloatType()
     602    {
     603        if (!m_wrappedFloatType)
     604            m_wrappedFloatType = AST::TypeReference::wrap({ }, m_intrinsics.floatType());
     605        return *m_wrappedFloatType;
     606    }
     607
     608    AST::UnnamedType& genericPointerType()
     609    {
     610        if (!m_genericPointerType)
     611            m_genericPointerType = AST::PointerType::create({ }, AST::AddressSpace::Thread, AST::TypeReference::wrap({ }, m_intrinsics.floatType()));
     612        return *m_genericPointerType;
     613    }
     614
     615    AST::UnnamedType& normalizedTypeForFunctionKey(AST::UnnamedType& type)
     616    {
     617        auto* unifyNode = &type.unifyNode();
     618        if (unifyNode == &m_intrinsics.uintType() || unifyNode == &m_intrinsics.intType())
     619            return wrappedFloatType();
     620
     621        if (is<AST::ReferenceType>(type))
     622            return genericPointerType();
     623
     624        return type;
     625    }
     626
     627    RefPtr<AST::TypeReference> m_wrappedFloatType;
     628    RefPtr<AST::UnnamedType> m_genericPointerType;
    514629    HashMap<AST::Expression*, std::unique_ptr<ResolvingType>> m_typeMap;
    515630    HashSet<String> m_vertexEntryPoints;
     
    519634    Program& m_program;
    520635    AST::FunctionDefinition* m_currentFunction { nullptr };
     636    HashMap<FunctionKey, Vector<std::reference_wrapper<AST::FunctionDeclaration>, 1>, FunctionKey::Hash, FunctionKey::Traits> m_functions;
    521637};
    522638
     
    637753        return &resolvableTypeReference->resolvableType().resolvedType();
    638754    }));
     755}
     756
     757AST::FunctionDeclaration* Checker::resolveFunction(Vector<std::reference_wrapper<ResolvingType>>& types, const String& name, CodeLocation location, AST::NamedType* castReturnType)
     758{
     759    Vector<std::reference_wrapper<AST::UnnamedType>> unnamedTypes;
     760    unnamedTypes.reserveInitialCapacity(types.size());
     761
     762    for (auto resolvingType : types) {
     763        AST::UnnamedType* type = resolvingType.get().visit(WTF::makeVisitor([&](Ref<AST::UnnamedType>& unnamedType) -> AST::UnnamedType* {
     764            return unnamedType.ptr();
     765        }, [&](RefPtr<ResolvableTypeReference>& resolvableTypeReference) -> AST::UnnamedType* {
     766            if (resolvableTypeReference->resolvableType().maybeResolvedType())
     767                return &resolvableTypeReference->resolvableType().resolvedType();
     768
     769            if (resolvableTypeReference->resolvableType().isFloatLiteralType()
     770                || resolvableTypeReference->resolvableType().isIntegerLiteralType()
     771                || resolvableTypeReference->resolvableType().isUnsignedIntegerLiteralType())
     772                return &wrappedFloatType();
     773
     774            if (resolvableTypeReference->resolvableType().isNullLiteralType())
     775                return &genericPointerType();
     776
     777            return commit(resolvableTypeReference->resolvableType()).get();
     778        }));
     779
     780        if (!type) {
     781            setError(Error("Could not resolve the type of a constant."));
     782            return nullptr;
     783        }
     784
     785        unnamedTypes.uncheckedAppend(normalizedTypeForFunctionKey(*type));
     786    }
     787
     788    {
     789        auto iter = m_functions.find(FunctionKey { name, WTFMove(unnamedTypes), castReturnType });
     790        if (iter != m_functions.end()) {
     791            if (AST::FunctionDeclaration* function = resolveFunctionOverload(iter->value, types, castReturnType))
     792                return function;
     793        }
     794    }
     795
     796    if (auto newFunction = resolveByInstantiation(name, location, types, m_intrinsics)) {
     797        m_program.append(WTFMove(*newFunction));
     798        return &m_program.nativeFunctionDeclarations().last();
     799    }
     800
     801    return nullptr;
    639802}
    640803
     
    9621125            getterArgumentTypes.append(*additionalArgumentType);
    9631126        auto getterName = propertyAccessExpression.getterFunctionName();
    964         auto* getterFunctions = m_program.nameContext().getFunctions(getterName);
    965         getterFunction = resolveFunction(m_program, getterFunctions, getterArgumentTypes, getterName, propertyAccessExpression.codeLocation(), m_intrinsics);
     1127        getterFunction = resolveFunction(getterArgumentTypes, getterName, propertyAccessExpression.codeLocation());
     1128        if (hasError())
     1129            return;
    9661130        if (getterFunction)
    9671131            getterReturnType = &getterFunction->type();
     
    9781142                anderArgumentTypes.append(*additionalArgumentType);
    9791143            auto anderName = propertyAccessExpression.anderFunctionName();
    980             auto* anderFunctions = m_program.nameContext().getFunctions(anderName);
    981             anderFunction = resolveFunction(m_program, anderFunctions, anderArgumentTypes, anderName, propertyAccessExpression.codeLocation(), m_intrinsics);
     1144            anderFunction = resolveFunction(anderArgumentTypes, anderName, propertyAccessExpression.codeLocation());
     1145            if (hasError())
     1146                return;
    9821147            if (anderFunction)
    9831148                anderReturnType = &downcast<AST::PointerType>(anderFunction->type()).elementType(); // FIXME: https://bugs.webkit.org/show_bug.cgi?id=198164 Enforce the return of anders will always be a pointer
     
    9931158            threadAnderArgumentTypes.append(*additionalArgumentType);
    9941159        auto anderName = propertyAccessExpression.anderFunctionName();
    995         auto* anderFunctions = m_program.nameContext().getFunctions(anderName);
    996         threadAnderFunction = resolveFunction(m_program, anderFunctions, threadAnderArgumentTypes, anderName, propertyAccessExpression.codeLocation(), m_intrinsics);
     1160        threadAnderFunction = resolveFunction(threadAnderArgumentTypes, anderName, propertyAccessExpression.codeLocation());
     1161        if (hasError())
     1162            return;
    9971163        if (threadAnderFunction)
    9981164            threadAnderReturnType = &downcast<AST::PointerType>(threadAnderFunction->type()).elementType(); // FIXME: https://bugs.webkit.org/show_bug.cgi?id=198164 Enforce the return of anders will always be a pointer
     
    10401206        setterArgumentTypes.append(fieldResolvingType);
    10411207        auto setterName = propertyAccessExpression.setterFunctionName();
    1042         auto* setterFunctions = m_program.nameContext().getFunctions(setterName);
    1043         setterFunction = resolveFunction(m_program, setterFunctions, setterArgumentTypes, setterName, propertyAccessExpression.codeLocation(), m_intrinsics);
     1208        setterFunction = resolveFunction(setterArgumentTypes, setterName, propertyAccessExpression.codeLocation());
     1209        if (hasError())
     1210            return;
    10441211        if (setterFunction)
    10451212            setterReturnType = &setterFunction->type();
     
    14211588    // We don't want to recurse to the same node twice.
    14221589
    1423     NameContext& nameContext = m_program.nameContext();
    1424     auto* functions = nameContext.getFunctions(callExpression.name());
    1425     if (!functions) {
    1426         if (auto* types = nameContext.getTypes(callExpression.name())) {
    1427             if (types->size() == 1) {
    1428                 if ((functions = nameContext.getFunctions("operator cast"_str)))
    1429                     callExpression.setCastData((*types)[0].get());
     1590    auto* function = resolveFunction(types, callExpression.name(), callExpression.codeLocation());
     1591    if (hasError())
     1592        return;
     1593
     1594    if (!function) {
     1595        NameContext& nameContext = m_program.nameContext();
     1596        if (auto* castTypes = nameContext.getTypes(callExpression.name())) {
     1597            if (castTypes->size() == 1) {
     1598                AST::NamedType& castType = (*castTypes)[0].get();
     1599                function = resolveFunction(types, "operator cast"_str, callExpression.codeLocation(), &castType);
     1600                if (hasError())
     1601                    return;
     1602                if (function)
     1603                    callExpression.setCastData(castType);
    14301604            }
    14311605        }
    14321606    }
    1433     if (!functions) {
    1434         setError(Error("Could not find any functions with appropriate name.", callExpression.codeLocation()));
    1435         return;
    1436     }
    1437 
    1438     auto* function = resolveFunction(m_program, functions, types, callExpression.name(), callExpression.codeLocation(), m_intrinsics, callExpression.castReturnType());
     1607
    14391608    if (!function) {
    14401609        // FIXME: Add better error messages for why we can't resolve to one of the overrides.
Note: See TracChangeset for help on using the changeset viewer.