blob: 939cc776de1a9e0586a955af211de860c229bf75 [file] [edit]
/*
* Copyright (c) 2026 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. ``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
* 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 "AliasAnalysis.h"
#include "AST.h"
#include "ASTScopedVisitorInlines.h"
#include "ASTIdentifier.h"
#include "ASTVisitor.h"
#include "CallGraph.h"
#include "WGSL.h"
#include "WGSLShaderModule.h"
#include <wtf/DataLog.h>
#include <wtf/HashMap.h>
#include <wtf/HashTraits.h>
namespace WGSL {
constexpr bool shouldLogAliasAnalysis = false;
class MemoryLocation {
friend struct MemoryLocationHashTraits;
public:
enum class Kind : uint8_t {
Invalid,
Local,
Global,
Parameter,
};
MemoryLocation()
: m_kind(Kind::Invalid)
{
}
static MemoryLocation NODELETE parameter(unsigned id) { return MemoryLocation { Kind::Parameter, id }; }
static MemoryLocation NODELETE global(unsigned id) { return MemoryLocation { Kind::Global, id }; }
static MemoryLocation NODELETE local(unsigned id) { return MemoryLocation { Kind::Local, id }; }
Kind NODELETE kind() const { return m_kind; }
unsigned NODELETE id() const { return m_id; }
bool NODELETE isParameter() const { return m_kind == Kind::Parameter; }
bool NODELETE isGlobal() const { return m_kind == Kind::Global; }
bool NODELETE operator==(const MemoryLocation& other) const
{
return m_kind == other.m_kind && m_id == other.m_id;
}
static constexpr bool safeToCompareToHashTableEmptyOrDeletedValue = true;
void dump(PrintStream& out) const
{
switch (m_kind) {
case Kind::Invalid:
out.print("invalid");
break;
case Kind::Local:
out.print("local");
break;
case Kind::Global:
out.print("global");
break;
case Kind::Parameter:
out.print("parameter");
break;
}
out.print(':', m_id);
}
private:
MemoryLocation(Kind kind, unsigned id)
: m_kind(kind)
, m_id(id)
{
}
static MemoryLocation NODELETE hashTableDeletedValue()
{
return MemoryLocation { Kind::Invalid, std::numeric_limits<unsigned>::max() };
}
Kind m_kind { Kind::Invalid };
unsigned m_id { 0 };
};
} // namespace WGSL
namespace WTF {
template<> class StringTypeAdapter<WGSL::MemoryLocation> {
public:
StringTypeAdapter(const WGSL::MemoryLocation& value)
{
StringPrintStream valueString;
value.dump(valueString);
m_string = valueString.toString();
}
unsigned NODELETE length() const { return m_string.length(); }
bool NODELETE is8Bit() const { return m_string.is8Bit(); }
template<typename CharacterType>
void writeTo(std::span<CharacterType> destination) const
{
StringView { m_string }.getCharacters(destination);
WTF_STRINGTYPEADAPTER_COPIED_WTF_STRING();
}
private:
String m_string;
};
} // namespace WTF
namespace WGSL {
inline void NODELETE add(Hasher& hasher, const MemoryLocation& input)
{
WTF::add(hasher, input.kind());
WTF::add(hasher, input.id());
}
struct MemoryLocationHashTraits : SimpleClassHashTraits<MemoryLocation> {
static const bool emptyValueIsZero = false;
static MemoryLocation NODELETE emptyValue() { return MemoryLocation(); }
static void NODELETE constructDeletedValue(MemoryLocation& slot) { new (NotNull, &slot) MemoryLocation { MemoryLocation::Kind::Invalid, std::numeric_limits<unsigned>::max() }; }
static bool NODELETE isDeletedValue(const MemoryLocation& value)
{
MemoryLocation deleted;
constructDeletedValue(deleted);
return value == deleted;
}
};
using MemoryLocationSet = HashSet<MemoryLocation, DefaultHash<MemoryLocation>, MemoryLocationHashTraits>;
struct FunctionInfo {
FunctionInfo() = default;
FunctionInfo(size_t parameterCount)
: parameters(parameterCount)
{
}
FixedVector<MemoryLocation> parameters;
MemoryLocationSet readParameters;
MemoryLocationSet writtenParameters;
MemoryLocationSet readGlobals;
MemoryLocationSet writtenGlobals;
};
class AliasAnalysis : public AST::ScopedVisitor<MemoryLocation> {
using Base = AST::ScopedVisitor<MemoryLocation>;
using Base::visit;
public:
AliasAnalysis(ShaderModule& shaderModule)
: Base()
, m_shaderModule(shaderModule)
{
}
std::optional<Error> run();
void visit(AST::Function&) override;
void visit(AST::AssignmentStatement&) override;
void visit(AST::VariableStatement&) override;
void visit(AST::CompoundAssignmentStatement&) override;
void visit(AST::DecrementIncrementStatement&) override;
void visit(AST::CallExpression&) override;
void visit(AST::IdentifierExpression&) override;
private:
void read(MemoryLocation);
void write(MemoryLocation);
void write(const AST::Expression&);
const MemoryLocation* getRootIdentifier(const AST::Expression&) const;
ShaderModule& m_shaderModule;
HashSet<AST::Function*> m_visitedFunctions;
HashMap<String, FunctionInfo> m_functions;
FunctionInfo* m_function;
Vector<MemoryLocation> m_globals;
unsigned m_globalID { 0 };
unsigned m_localID { 0 };
unsigned m_parameterID { 0 };
};
std::optional<Error> AliasAnalysis::run()
{
unsigned globalCount = 0;
unsigned functionCount = 0;
for (auto& declaration : m_shaderModule.declarations()) {
switch (declaration.kind()) {
case AST::NodeKind::Function:
++functionCount;
break;
case AST::NodeKind::Variable:
++globalCount;
break;
default:
break;
}
}
m_globals.resize(globalCount);
m_functions.reserveInitialCapacity(functionCount);
for (auto& declaration : m_shaderModule.declarations()) {
if (auto* globalVar = dynamicDowncast<AST::Variable>(declaration)) {
m_globals.append(MemoryLocation::global(m_globalID++));
introduceVariable(globalVar->name(), m_globals.last());
} else if (auto* function = dynamicDowncast<AST::Function>(declaration)) {
visit(*function);
if (hasError()) [[unlikely]]
return AST::Visitor::result().error();
}
}
return std::nullopt;
}
void AliasAnalysis::visit(AST::Function& function)
{
ContextScope functionScope(this);
dataLogLnIf(shouldLogAliasAnalysis, "visiting function `"_s, function.name(), '`');
auto parameterCount = function.parameters().size();
auto result = m_functions.add(function.name(), FunctionInfo(parameterCount));
m_function = &result.iterator->value;
for (unsigned i = 0; i < parameterCount; ++i) {
auto& parameter = function.parameters()[i];
m_function->parameters[i] = MemoryLocation::parameter(m_parameterID++);
introduceVariable(parameter.name(), m_function->parameters[i]);
dataLogLnIf(shouldLogAliasAnalysis, "+ parameter `"_s, function.name(), "` => "_s, RawPointer(&m_function->parameters[i]));
}
AST::Visitor::visit(function);
m_function = nullptr;
m_localID = 0;
m_parameterID = 0;
}
void AliasAnalysis::visit(AST::CallExpression& call)
{
auto* target = dynamicDowncast<AST::IdentifierExpression>(call.target());
if (!target) {
AST::Visitor::visit(call);
return;
}
auto it = m_functions.find(target->identifier().id());
if (it == m_functions.end()) {
static constexpr SortedArraySet atomicWriteFunctions { WTF::toArray<ComparableASCIILiteral>({
"atomicAdd"_s,
"atomicAnd"_s,
"atomicCompareExchangeWeak"_s,
"atomicExchange"_s,
"atomicMax"_s,
"atomicMin"_s,
"atomicOr"_s,
"atomicStore"_s,
"atomicSub"_s,
"atomicXor"_s,
}) };
if (atomicWriteFunctions.contains(target->identifier().id()))
write(call.arguments()[0]);
AST::Visitor::visit(call);
return;
}
auto& function = it->value;
auto argumentCount = call.arguments().size();
MemoryLocationSet writtenArguments;
MemoryLocationSet readArguments;
m_function->writtenGlobals.addAll(function.writtenGlobals);
m_function->readGlobals.addAll(function.readGlobals);
dataLogIf(shouldLogAliasAnalysis, "call "_s, target->identifier().id(), "(");
if constexpr (shouldLogAliasAnalysis) {
for (unsigned i = 0; i < argumentCount; ++i) {
if (i > 0)
dataLog(", ");
dataLog(RawPointer(&function.parameters[i]));
}
dataLogLn(")"_s);
}
for (unsigned i = 0; i < argumentCount; ++i) {
auto& argument = call.arguments()[i];
if (!std::holds_alternative<Types::Pointer>(*argument.inferredType()))
continue;
auto* argumentLocation = getRootIdentifier(argument);
if (!argumentLocation)
continue;
auto parameter = function.parameters[i];
bool isParameterRead = function.readParameters.contains(parameter);
bool isParameterWritten = function.writtenParameters.contains(parameter);
dataLogLnIf(shouldLogAliasAnalysis, '@', String::number(i), " arg:"_s, *argumentLocation, " param:"_s, parameter);
if (isParameterWritten)
write(*argumentLocation);
if (isParameterRead)
read(*argumentLocation);
bool alreadyWritten = isParameterWritten
? !writtenArguments.add(*argumentLocation).isNewEntry
: writtenArguments.contains(*argumentLocation);
bool alreadyRead = isParameterRead
? !readArguments.add(*argumentLocation).isNewEntry
: readArguments.contains(*argumentLocation);
if ((isParameterWritten && (alreadyWritten || alreadyRead)) || (isParameterRead && alreadyWritten)) [[unlikely]] {
setError({ "invalid aliased pointer argument"_s, argument.span() });
return;
}
if (argumentLocation->isGlobal()) {
bool isGlobalRead = function.readGlobals.contains(*argumentLocation);
bool isGlobalWritten = function.writtenGlobals.contains(*argumentLocation);
if ((isGlobalWritten && isParameterWritten) || (isGlobalWritten && isParameterRead) || (isGlobalRead && isParameterWritten)) {
setError({ "invalid aliased pointer argument"_s, argument.span() });
return;
}
}
}
}
void AliasAnalysis::visit(AST::CompoundAssignmentStatement& statement)
{
write(statement.leftExpression());
visit(statement.rightExpression());
}
void AliasAnalysis::visit(AST::DecrementIncrementStatement& statement)
{
write(statement.expression());
}
void AliasAnalysis::visit(AST::AssignmentStatement& statement)
{
write(statement.lhs());
visit(statement.rhs());
}
void AliasAnalysis::visit(AST::VariableStatement& statement)
{
introduceVariable(statement.variable().name(), MemoryLocation::local(m_localID++));
maybeCheckErrorAndVisit(statement.variable().maybeInitializer());
}
const MemoryLocation* AliasAnalysis::getRootIdentifier(const AST::Expression& expression) const
{
if (auto* identityExpression = dynamicDowncast<AST::IdentityExpression>(expression))
return getRootIdentifier(identityExpression->expression());
if (auto* unaryExpression = dynamicDowncast<AST::UnaryExpression>(expression))
return getRootIdentifier(unaryExpression->expression());
if (auto* fieldAccess = dynamicDowncast<AST::FieldAccessExpression>(expression))
return getRootIdentifier(fieldAccess->base());
if (auto* indexAccess = dynamicDowncast<AST::IndexAccessExpression>(expression))
return getRootIdentifier(indexAccess->base());
if (auto* identifierExpression = dynamicDowncast<AST::IdentifierExpression>(expression))
return readVariable(identifierExpression->identifier().id());
return nullptr;
}
void AliasAnalysis::visit(AST::IdentifierExpression& expression)
{
auto* rootIdentifier = readVariable(expression.identifier().id());
if (!rootIdentifier)
return;
read(*rootIdentifier);
}
void AliasAnalysis::read(MemoryLocation rootIdentifier)
{
if (rootIdentifier.isParameter()) {
dataLogLnIf(shouldLogAliasAnalysis, "- reading parameter "_s, rootIdentifier);
m_function->readParameters.add(rootIdentifier);
} else if (rootIdentifier.isGlobal()) {
dataLogLnIf(shouldLogAliasAnalysis, "- reading global "_s, rootIdentifier);
m_function->readGlobals.add(rootIdentifier);
}
}
void AliasAnalysis::write(const AST::Expression& expression)
{
auto* rootIdentifier = getRootIdentifier(expression);
if (!rootIdentifier)
return;
write(*rootIdentifier);
}
void AliasAnalysis::write(MemoryLocation rootIdentifier)
{
if (rootIdentifier.isParameter()) {
dataLogLnIf(shouldLogAliasAnalysis, "writing parameter "_s, rootIdentifier);
m_function->writtenParameters.add(rootIdentifier);
} else if (rootIdentifier.isGlobal())
dataLogLnIf(shouldLogAliasAnalysis, "writing global "_s, rootIdentifier);
m_function->writtenGlobals.add(rootIdentifier);
}
std::optional<FailedCheck> aliasAnalysis(ShaderModule& shaderModule)
{
if (auto error = AliasAnalysis(shaderModule).run())
return FailedCheck { Vector<Error> { *error }, { } };
return std::nullopt;
}
} // namespace WGSL