blob: 77eccbd723930c0dbad45e12aadf903dce1949a8 [file] [log] [blame]
/*
* Copyright (C) 2019 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "WHLSLPrepare.h"
#if ENABLE(WEBGPU)
#include "WHLSLASTDumper.h"
#include "WHLSLCheckDuplicateFunctions.h"
#include "WHLSLCheckReferenceTypes.h"
#include "WHLSLCheckTextureReferences.h"
#include "WHLSLChecker.h"
#include "WHLSLComputeDimensions.h"
#include "WHLSLFunctionStageChecker.h"
#include "WHLSLHighZombieFinder.h"
#include "WHLSLLiteralTypeChecker.h"
#include "WHLSLMetalCodeGenerator.h"
#include "WHLSLNameResolver.h"
#include "WHLSLNameSpace.h"
#include "WHLSLParser.h"
#include "WHLSLPreserveVariableLifetimes.h"
#include "WHLSLProgram.h"
#include "WHLSLPropertyResolver.h"
#include "WHLSLPruneUnreachableStandardLibraryFunctions.h"
#include "WHLSLRecursionChecker.h"
#include "WHLSLRecursiveTypeChecker.h"
#include "WHLSLSemanticMatcher.h"
#include "WHLSLStandardLibraryUtilities.h"
#include "WHLSLStatementBehaviorChecker.h"
#include "WHLSLSynthesizeConstructors.h"
#include "WHLSLSynthesizeEnumerationFunctions.h"
#include <wtf/MonotonicTime.h>
#include <wtf/Optional.h>
namespace WebCore {
namespace WHLSL {
struct ShaderModule {
WTF_MAKE_FAST_ALLOCATED;
public:
ShaderModule(const String& whlslSource)
: whlslSource(whlslSource)
{
}
String whlslSource;
};
}
}
namespace std {
void default_delete<WebCore::WHLSL::ShaderModule>::operator()(WebCore::WHLSL::ShaderModule* shaderModule) const
{
delete shaderModule;
}
}
namespace WebCore {
namespace WHLSL {
static constexpr bool dumpASTBeforeEachPass = false;
static constexpr bool dumpASTAfterParsing = false;
static constexpr bool dumpASTAtEnd = false;
static constexpr bool alwaysDumpPassFailures = false;
static constexpr bool dumpPassFailure = dumpASTBeforeEachPass || dumpASTAfterParsing || dumpASTAtEnd || alwaysDumpPassFailures;
static constexpr bool dumpPhaseTimes = false;
static constexpr bool parseFullStandardLibrary = false;
static bool dumpASTIfNeeded(bool shouldDump, Program& program, const char* message)
{
if (shouldDump) {
dataLogLn(message);
dumpAST(program);
return true;
}
return false;
}
static bool dumpASTAfterParsingIfNeeded(Program& program)
{
return dumpASTIfNeeded(dumpASTAfterParsing, program, "AST after parsing");
}
static bool dumpASTBetweenEachPassIfNeeded(Program& program, const char* message)
{
return dumpASTIfNeeded(dumpASTBeforeEachPass, program, message);
}
static bool dumpASTAtEndIfNeeded(Program& program)
{
return dumpASTIfNeeded(dumpASTAtEnd, program, "AST at end");
}
using PhaseTimes = Vector<std::pair<String, Seconds>>;
static void logPhaseTimes(PhaseTimes& phaseTimes)
{
if (!dumpPhaseTimes)
return;
for (auto& entry : phaseTimes)
dataLogLn(entry.first, ": ", entry.second.milliseconds(), " ms");
}
class PhaseTimer {
public:
PhaseTimer(const char* phaseName, PhaseTimes& phaseTimes)
: m_phaseTimes(phaseTimes)
{
if (dumpPhaseTimes) {
m_phaseName = phaseName;
m_start = MonotonicTime::now();
}
}
~PhaseTimer()
{
if (dumpPhaseTimes) {
auto totalTime = MonotonicTime::now() - m_start;
m_phaseTimes.append({ m_phaseName, totalTime });
}
}
private:
String m_phaseName;
PhaseTimes& m_phaseTimes;
MonotonicTime m_start;
};
UniqueRef<ShaderModule> createShaderModule(const String& whlslSource)
{
// FIXME: https://bugs.webkit.org/show_bug.cgi?id=200872 We should consider moving as much work from prepare() into here as possible.
return makeUniqueRef<ShaderModule>(whlslSource);
}
#define CHECK_PASS(pass, ...) \
do { \
dumpASTBetweenEachPassIfNeeded(program, "AST before " # pass); \
PhaseTimer phaseTimer(#pass, phaseTimes); \
auto result = pass(__VA_ARGS__); \
if (!result) { \
if (dumpPassFailure) \
dataLogLn("failed pass: " # pass, Lexer::errorString(result.error(), whlslSource1, whlslSource2)); \
return makeUnexpected(Lexer::errorString(result.error(), whlslSource1, whlslSource2)); \
} \
} while (0)
#define RUN_PASS(pass, ...) \
do { \
PhaseTimer phaseTimer(#pass, phaseTimes); \
dumpASTBetweenEachPassIfNeeded(program, "AST before " # pass); \
pass(__VA_ARGS__); \
} while (0)
static Expected<Program, String> prepareShared(PhaseTimes& phaseTimes, const String& whlslSource1, const String* whlslSource2 = nullptr)
{
Program program;
Parser parser;
{
program.nameContext().setCurrentNameSpace(AST::NameSpace::NameSpace1);
PhaseTimer phaseTimer("parse", phaseTimes);
auto parseResult = parser.parse(program, whlslSource1, ParsingMode::User, AST::NameSpace::NameSpace1);
if (!parseResult) {
if (dumpPassFailure)
dataLogLn("failed to parse the program: ", Lexer::errorString(parseResult.error(), whlslSource1, whlslSource2));
return makeUnexpected(Lexer::errorString(parseResult.error(), whlslSource1, whlslSource2));
}
if (whlslSource2) {
program.nameContext().setCurrentNameSpace(AST::NameSpace::NameSpace2);
auto parseResult = parser.parse(program, *whlslSource2, ParsingMode::User, AST::NameSpace::NameSpace2);
if (!parseResult) {
if (dumpPassFailure)
dataLogLn("failed to parse the program: ", Lexer::errorString(parseResult.error(), whlslSource1, whlslSource2));
return makeUnexpected(Lexer::errorString(parseResult.error(), whlslSource1, whlslSource2));
}
}
program.nameContext().setCurrentNameSpace(AST::NameSpace::StandardLibrary);
}
{
PhaseTimer phaseTimer("includeStandardLibrary", phaseTimes);
includeStandardLibrary(program, parser, parseFullStandardLibrary);
}
if (!dumpASTBetweenEachPassIfNeeded(program, "AST after parsing"))
dumpASTAfterParsingIfNeeded(program);
NameResolver nameResolver(program.nameContext());
CHECK_PASS(resolveNamesInTypes, program, nameResolver);
CHECK_PASS(checkRecursiveTypes, program);
CHECK_PASS(synthesizeEnumerationFunctions, program);
CHECK_PASS(resolveTypeNamesInFunctions, program, nameResolver);
CHECK_PASS(synthesizeConstructors, program);
CHECK_PASS(checkDuplicateFunctions, program);
CHECK_PASS(check, program);
RUN_PASS(pruneUnreachableStandardLibraryFunctions, program);
RUN_PASS(checkLiteralTypes, program);
CHECK_PASS(checkTextureReferences, program);
CHECK_PASS(checkReferenceTypes, program);
RUN_PASS(resolveProperties, program);
RUN_PASS(findHighZombies, program);
CHECK_PASS(checkStatementBehavior, program);
CHECK_PASS(checkRecursion, program);
CHECK_PASS(checkFunctionStages, program);
RUN_PASS(preserveVariableLifetimes, program);
dumpASTAtEndIfNeeded(program);
return program;
}
Expected<RenderPrepareResult, String> prepare(const ShaderModule& vertexShaderModule, const ShaderModule* fragmentShaderModule, RenderPipelineDescriptor& renderPipelineDescriptor)
{
PhaseTimes phaseTimes;
Metal::RenderMetalCode generatedCode;
{
PhaseTimer phaseTimer("prepare total", phaseTimes);
const String* secondShader = nullptr;
bool distinctFragmentShader = false;
if (fragmentShaderModule && fragmentShaderModule != &vertexShaderModule) {
secondShader = &fragmentShaderModule->whlslSource;
distinctFragmentShader = true;
}
auto program = prepareShared(phaseTimes, vertexShaderModule.whlslSource, secondShader);
if (!program)
return makeUnexpected(program.error());
Optional<MatchedRenderSemantics> matchedSemantics;
{
PhaseTimer phaseTimer("matchSemantics", phaseTimes);
matchedSemantics = matchSemantics(*program, renderPipelineDescriptor, distinctFragmentShader, fragmentShaderModule);
if (!matchedSemantics)
return makeUnexpected(Lexer::errorString(Error("Could not match semantics"_str), vertexShaderModule.whlslSource, secondShader));
}
{
PhaseTimer phaseTimer("generateMetalCode", phaseTimes);
generatedCode = Metal::generateMetalCode(*program, WTFMove(*matchedSemantics), renderPipelineDescriptor.layout);
}
}
logPhaseTimes(phaseTimes);
RenderPrepareResult result;
result.metalSource = WTFMove(generatedCode.metalSource);
result.mangledVertexEntryPointName = WTFMove(generatedCode.mangledVertexEntryPointName);
result.mangledFragmentEntryPointName = WTFMove(generatedCode.mangledFragmentEntryPointName);
return result;
}
Expected<ComputePrepareResult, String> prepare(const ShaderModule& shaderModule, ComputePipelineDescriptor& computePipelineDescriptor)
{
PhaseTimes phaseTimes;
Metal::ComputeMetalCode generatedCode;
Optional<ComputeDimensions> computeDimensions;
{
PhaseTimer phaseTimer("prepare total", phaseTimes);
auto program = prepareShared(phaseTimes, shaderModule.whlslSource);
if (!program)
return makeUnexpected(program.error());
Optional<MatchedComputeSemantics> matchedSemantics;
{
PhaseTimer phaseTimer("matchSemantics", phaseTimes);
matchedSemantics = matchSemantics(*program, computePipelineDescriptor);
if (!matchedSemantics)
return makeUnexpected(Lexer::errorString(Error("Could not match semantics"_str), shaderModule.whlslSource));
}
{
PhaseTimer phaseTimer("computeDimensions", phaseTimes);
computeDimensions = WHLSL::computeDimensions(*program, *matchedSemantics->shader);
if (!computeDimensions)
return makeUnexpected(Lexer::errorString(Error("Could not match compute dimensions"_str), shaderModule.whlslSource));
}
{
PhaseTimer phaseTimer("generateMetalCode", phaseTimes);
generatedCode = Metal::generateMetalCode(*program, WTFMove(*matchedSemantics), computePipelineDescriptor.layout);
}
}
logPhaseTimes(phaseTimes);
ComputePrepareResult result;
result.metalSource = WTFMove(generatedCode.metalSource);
result.mangledEntryPointName = WTFMove(generatedCode.mangledEntryPointName);
result.computeDimensions = WTFMove(*computeDimensions);
return result;
}
} // namespace WHLSL
} // namespace WebCore
#endif // ENABLE(WEBGPU)