blob: 396f4b6268495c2ddf1f2ac1faced0843c6f0e14 [file] [log] [blame]
/*
* Copyright 2022 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkSpan.h"
#include "include/private/SkSLDefines.h"
#include "include/private/SkSLIRNode.h"
#include "include/private/SkSLLayout.h"
#include "include/private/SkSLModifiers.h"
#include "include/private/SkSLProgramElement.h"
#include "include/private/SkSLStatement.h"
#include "include/private/base/SkTArray.h"
#include "include/sksl/SkSLOperator.h"
#include "include/sksl/SkSLPosition.h"
#include "src/base/SkStringView.h"
#include "src/core/SkTHash.h"
#include "src/sksl/SkSLAnalysis.h"
#include "src/sksl/SkSLBuiltinTypes.h"
#include "src/sksl/SkSLCompiler.h"
#include "src/sksl/SkSLConstantFolder.h"
#include "src/sksl/SkSLContext.h"
#include "src/sksl/SkSLIntrinsicList.h"
#include "src/sksl/SkSLModifiersPool.h"
#include "src/sksl/codegen/SkSLRasterPipelineBuilder.h"
#include "src/sksl/codegen/SkSLRasterPipelineCodeGenerator.h"
#include "src/sksl/ir/SkSLBinaryExpression.h"
#include "src/sksl/ir/SkSLBlock.h"
#include "src/sksl/ir/SkSLBreakStatement.h"
#include "src/sksl/ir/SkSLChildCall.h"
#include "src/sksl/ir/SkSLConstructor.h"
#include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h"
#include "src/sksl/ir/SkSLConstructorMatrixResize.h"
#include "src/sksl/ir/SkSLConstructorSplat.h"
#include "src/sksl/ir/SkSLContinueStatement.h"
#include "src/sksl/ir/SkSLDoStatement.h"
#include "src/sksl/ir/SkSLExpression.h"
#include "src/sksl/ir/SkSLExpressionStatement.h"
#include "src/sksl/ir/SkSLFieldAccess.h"
#include "src/sksl/ir/SkSLForStatement.h"
#include "src/sksl/ir/SkSLFunctionCall.h"
#include "src/sksl/ir/SkSLFunctionDeclaration.h"
#include "src/sksl/ir/SkSLFunctionDefinition.h"
#include "src/sksl/ir/SkSLIfStatement.h"
#include "src/sksl/ir/SkSLIndexExpression.h"
#include "src/sksl/ir/SkSLLiteral.h"
#include "src/sksl/ir/SkSLPostfixExpression.h"
#include "src/sksl/ir/SkSLPrefixExpression.h"
#include "src/sksl/ir/SkSLProgram.h"
#include "src/sksl/ir/SkSLReturnStatement.h"
#include "src/sksl/ir/SkSLSwitchCase.h"
#include "src/sksl/ir/SkSLSwitchStatement.h"
#include "src/sksl/ir/SkSLSwizzle.h"
#include "src/sksl/ir/SkSLTernaryExpression.h"
#include "src/sksl/ir/SkSLType.h"
#include "src/sksl/ir/SkSLVarDeclarations.h"
#include "src/sksl/ir/SkSLVariable.h"
#include "src/sksl/ir/SkSLVariableReference.h"
#include "src/sksl/tracing/SkRPDebugTrace.h"
#include "src/sksl/tracing/SkSLDebugInfo.h"
#include "src/sksl/transform/SkSLTransform.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <float.h>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace SkSL {
namespace RP {
static bool unsupported() {
// If MakeRasterPipelineProgram returns false, set a breakpoint here for more information.
return false;
}
class SlotManager {
public:
SlotManager(std::vector<SlotDebugInfo>* i) : fSlotDebugInfo(i) {}
/** Used by `create` to add this variable to SlotDebugInfo inside SkRPDebugTrace. */
void addSlotDebugInfoForGroup(const std::string& varName,
const Type& type,
Position pos,
int* groupIndex,
bool isFunctionReturnValue);
void addSlotDebugInfo(const std::string& varName,
const Type& type,
Position pos,
bool isFunctionReturnValue);
/** Creates slots associated with an SkSL variable or return value. */
SlotRange createSlots(std::string name,
const Type& type,
Position pos,
bool isFunctionReturnValue);
/** Looks up the slots associated with an SkSL variable; creates the slot if necessary. */
SlotRange getVariableSlots(const Variable& v);
/**
* Looks up the slots associated with an SkSL function's return value; creates the range if
* necessary. Note that recursion is never supported, so we don't need to maintain return values
* in a stack; we can just statically allocate one slot per function call-site.
*/
SlotRange getFunctionSlots(const IRNode& callSite, const FunctionDeclaration& f);
/** Returns the total number of slots consumed. */
int slotCount() const { return fSlotCount; }
private:
SkTHashMap<const IRNode*, SlotRange> fSlotMap;
int fSlotCount = 0;
std::vector<SlotDebugInfo>* fSlotDebugInfo;
};
class AutoContinueMask;
class LValue;
class Generator {
public:
Generator(const SkSL::Program& program, SkRPDebugTrace* debugTrace)
: fProgram(program)
, fContext(fProgram.fContext->fTypes,
fProgram.fContext->fCaps,
*fProgram.fContext->fErrors)
, fDebugTrace(debugTrace)
, fProgramSlots(debugTrace ? &debugTrace->fSlotInfo : nullptr)
, fUniformSlots(debugTrace ? &debugTrace->fUniformInfo : nullptr) {
fContext.fModifiersPool = &fModifiersPool;
fContext.fConfig = fProgram.fConfig.get();
fContext.fModule = fProgram.fContext->fModule;
}
/** Converts the SkSL main() function into a set of Instructions. */
bool writeProgram(const FunctionDefinition& function);
/** Returns the generated program. */
std::unique_ptr<RP::Program> finish();
/**
* Converts an SkSL function into a set of Instructions. Returns nullopt if the function
* contained unsupported statements or expressions.
*/
std::optional<SlotRange> writeFunction(const IRNode& callSite,
const FunctionDefinition& function);
/**
* Returns the slot index of this function inside the FunctionDebugInfo array in SkRPDebugTrace.
* The FunctionDebugInfo slot will be created if it doesn't already exist.
*/
int getFunctionDebugInfo(const FunctionDeclaration& decl);
/** Looks up the slots associated with an SkSL variable; creates the slot if necessary. */
SlotRange getVariableSlots(const Variable& v) {
SkASSERT(!IsUniform(v));
return fProgramSlots.getVariableSlots(v);
}
/** Looks up the slots associated with an SkSL uniform; creates the slot if necessary. */
SlotRange getUniformSlots(const Variable& v) {
SkASSERT(IsUniform(v));
return fUniformSlots.getVariableSlots(v);
}
/**
* Looks up the slots associated with an SkSL function's return value; creates the range if
* necessary. Note that recursion is never supported, so we don't need to maintain return values
* in a stack; we can just statically allocate one slot per function call-site.
*/
SlotRange getFunctionSlots(const IRNode& callSite, const FunctionDeclaration& f) {
return fProgramSlots.getFunctionSlots(callSite, f);
}
/**
* Creates an additional stack for the program to push values onto. The stack will not become
* actively in-use until `setCurrentStack` is called.
*/
int createStack();
/** Frees a stack generated by `createStack`. The freed stack must be completely empty. */
void recycleStack(int stackID);
/** Redirects builder ops to point to a different stack (created by `createStack`). */
void setCurrentStack(int stackID);
/** Reports the currently active stack. */
int currentStack() {
return fCurrentStack;
}
/**
* Returns an LValue for the passed-in expression; if the expression isn't supported as an
* LValue, returns nullptr.
*/
std::unique_ptr<LValue> makeLValue(const Expression& e, bool allowScratch = false);
/** Copies the top-of-stack value into this lvalue, without discarding it from the stack. */
[[nodiscard]] bool store(LValue& lvalue);
/** Pushes the lvalue onto the top-of-stack. */
[[nodiscard]] bool push(LValue& lvalue);
/** The Builder stitches our instructions together into Raster Pipeline code. */
Builder* builder() { return &fBuilder; }
/** Appends a statement to the program. */
[[nodiscard]] bool writeStatement(const Statement& s);
[[nodiscard]] bool writeBlock(const Block& b);
[[nodiscard]] bool writeBreakStatement(const BreakStatement& b);
[[nodiscard]] bool writeContinueStatement(const ContinueStatement& b);
[[nodiscard]] bool writeDoStatement(const DoStatement& d);
[[nodiscard]] bool writeExpressionStatement(const ExpressionStatement& e);
[[nodiscard]] bool writeMasklessForStatement(const ForStatement& f);
[[nodiscard]] bool writeForStatement(const ForStatement& f);
[[nodiscard]] bool writeGlobals();
[[nodiscard]] bool writeIfStatement(const IfStatement& i);
[[nodiscard]] bool writeDynamicallyUniformIfStatement(const IfStatement& i);
[[nodiscard]] bool writeReturnStatement(const ReturnStatement& r);
[[nodiscard]] bool writeSwitchStatement(const SwitchStatement& s);
[[nodiscard]] bool writeVarDeclaration(const VarDeclaration& v);
/** Pushes an expression to the value stack. */
[[nodiscard]] bool pushBinaryExpression(const BinaryExpression& e);
[[nodiscard]] bool pushBinaryExpression(const Expression& left,
Operator op,
const Expression& right);
[[nodiscard]] bool pushChildCall(const ChildCall& c);
[[nodiscard]] bool pushConstructorCast(const AnyConstructor& c);
[[nodiscard]] bool pushConstructorCompound(const AnyConstructor& c);
[[nodiscard]] bool pushConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c);
[[nodiscard]] bool pushConstructorMatrixResize(const ConstructorMatrixResize& c);
[[nodiscard]] bool pushConstructorSplat(const ConstructorSplat& c);
[[nodiscard]] bool pushExpression(const Expression& e, bool usesResult = true);
[[nodiscard]] bool pushFieldAccess(const FieldAccess& f);
[[nodiscard]] bool pushFunctionCall(const FunctionCall& c);
[[nodiscard]] bool pushIndexExpression(const IndexExpression& i);
[[nodiscard]] bool pushIntrinsic(const FunctionCall& c);
[[nodiscard]] bool pushIntrinsic(IntrinsicKind intrinsic, const Expression& arg0);
[[nodiscard]] bool pushIntrinsic(IntrinsicKind intrinsic,
const Expression& arg0,
const Expression& arg1);
[[nodiscard]] bool pushIntrinsic(IntrinsicKind intrinsic,
const Expression& arg0,
const Expression& arg1,
const Expression& arg2);
[[nodiscard]] bool pushLiteral(const Literal& l);
[[nodiscard]] bool pushPostfixExpression(const PostfixExpression& p, bool usesResult);
[[nodiscard]] bool pushPrefixExpression(const PrefixExpression& p);
[[nodiscard]] bool pushPrefixExpression(Operator op, const Expression& expr);
[[nodiscard]] bool pushSwizzle(const Swizzle& s);
[[nodiscard]] bool pushTernaryExpression(const TernaryExpression& t);
[[nodiscard]] bool pushTernaryExpression(const Expression& test,
const Expression& ifTrue,
const Expression& ifFalse);
[[nodiscard]] bool pushDynamicallyUniformTernaryExpression(const Expression& test,
const Expression& ifTrue,
const Expression& ifFalse);
[[nodiscard]] bool pushVariableReference(const VariableReference& v);
/** Pops an expression from the value stack and copies it into slots. */
void popToSlotRange(SlotRange r) { fBuilder.pop_slots(r); }
void popToSlotRangeUnmasked(SlotRange r) { fBuilder.pop_slots_unmasked(r); }
/** Pops an expression from the value stack and discards it. */
void discardExpression(int slots) { fBuilder.discard_stack(slots); }
/** Zeroes out a range of slots. */
void zeroSlotRangeUnmasked(SlotRange r) { fBuilder.zero_slots_unmasked(r); }
/** Expression utilities. */
struct TypedOps {
BuilderOp fFloatOp;
BuilderOp fSignedOp;
BuilderOp fUnsignedOp;
BuilderOp fBooleanOp;
};
static BuilderOp GetTypedOp(const SkSL::Type& type, const TypedOps& ops);
[[nodiscard]] bool unaryOp(const SkSL::Type& type, const TypedOps& ops);
[[nodiscard]] bool binaryOp(const SkSL::Type& type, const TypedOps& ops);
[[nodiscard]] bool ternaryOp(const SkSL::Type& type, const TypedOps& ops);
[[nodiscard]] bool pushIntrinsic(const TypedOps& ops, const Expression& arg0);
[[nodiscard]] bool pushIntrinsic(const TypedOps& ops,
const Expression& arg0,
const Expression& arg1);
[[nodiscard]] bool pushIntrinsic(BuilderOp builderOp, const Expression& arg0);
[[nodiscard]] bool pushIntrinsic(BuilderOp builderOp,
const Expression& arg0,
const Expression& arg1);
[[nodiscard]] bool pushLengthIntrinsic(int slotCount);
[[nodiscard]] bool pushVectorizedExpression(const Expression& expr, const Type& vectorType);
[[nodiscard]] bool pushVariableReferencePartial(const VariableReference& v, SlotRange subset);
[[nodiscard]] bool pushLValueOrExpression(LValue* lvalue, const Expression& expr);
[[nodiscard]] bool pushMatrixMultiply(LValue* lvalue,
const Expression& left,
const Expression& right,
int leftColumns, int leftRows,
int rightColumns, int rightRows);
[[nodiscard]] bool pushStructuredComparison(LValue* left,
Operator op,
LValue* right,
const Type& type);
void foldWithMultiOp(BuilderOp op, int elements);
void foldComparisonOp(Operator op, int elements);
BuilderOp getTypedOp(const SkSL::Type& type, const TypedOps& ops) const;
Analysis::ReturnComplexity returnComplexity(const FunctionDefinition* func) {
Analysis::ReturnComplexity* complexity = fReturnComplexityMap.find(fCurrentFunction);
if (!complexity) {
complexity = fReturnComplexityMap.set(fCurrentFunction,
Analysis::GetReturnComplexity(*fCurrentFunction));
}
return *complexity;
}
bool needsReturnMask() {
return this->returnComplexity(fCurrentFunction) >=
Analysis::ReturnComplexity::kEarlyReturns;
}
bool needsFunctionResultSlots() {
return this->returnComplexity(fCurrentFunction) >
Analysis::ReturnComplexity::kSingleSafeReturn;
}
static bool IsUniform(const Variable& var) {
return var.modifiers().fFlags & Modifiers::kUniform_Flag;
}
static bool IsOutParameter(const Variable& var) {
return (var.modifiers().fFlags & (Modifiers::kIn_Flag | Modifiers::kOut_Flag)) ==
Modifiers::kOut_Flag;
}
static bool IsInoutParameter(const Variable& var) {
return (var.modifiers().fFlags & (Modifiers::kIn_Flag | Modifiers::kOut_Flag)) ==
(Modifiers::kIn_Flag | Modifiers::kOut_Flag);
}
private:
const SkSL::Program& fProgram;
SkSL::Context fContext;
SkSL::ModifiersPool fModifiersPool;
Builder fBuilder;
SkRPDebugTrace* fDebugTrace = nullptr;
SkTHashMap<const Variable*, int> fChildEffectMap;
SlotManager fProgramSlots;
SlotManager fUniformSlots;
const FunctionDefinition* fCurrentFunction = nullptr;
SlotRange fCurrentFunctionResult;
AutoContinueMask* fCurrentContinueMask = nullptr;
int fCurrentBreakTarget = -1;
int fCurrentStack = 0;
int fNextStackID = 0;
SkTArray<int> fRecycledStacks;
SkTHashMap<const FunctionDefinition*, Analysis::ReturnComplexity> fReturnComplexityMap;
static constexpr auto kAbsOps = TypedOps{BuilderOp::abs_float,
BuilderOp::abs_int,
BuilderOp::unsupported,
BuilderOp::unsupported};
static constexpr auto kAddOps = TypedOps{BuilderOp::add_n_floats,
BuilderOp::add_n_ints,
BuilderOp::add_n_ints,
BuilderOp::unsupported};
static constexpr auto kSubtractOps = TypedOps{BuilderOp::sub_n_floats,
BuilderOp::sub_n_ints,
BuilderOp::sub_n_ints,
BuilderOp::unsupported};
static constexpr auto kMultiplyOps = TypedOps{BuilderOp::mul_n_floats,
BuilderOp::mul_n_ints,
BuilderOp::mul_n_ints,
BuilderOp::unsupported};
static constexpr auto kDivideOps = TypedOps{BuilderOp::div_n_floats,
BuilderOp::div_n_ints,
BuilderOp::div_n_uints,
BuilderOp::unsupported};
static constexpr auto kLessThanOps = TypedOps{BuilderOp::cmplt_n_floats,
BuilderOp::cmplt_n_ints,
BuilderOp::cmplt_n_uints,
BuilderOp::unsupported};
static constexpr auto kLessThanEqualOps = TypedOps{BuilderOp::cmple_n_floats,
BuilderOp::cmple_n_ints,
BuilderOp::cmple_n_uints,
BuilderOp::unsupported};
static constexpr auto kEqualOps = TypedOps{BuilderOp::cmpeq_n_floats,
BuilderOp::cmpeq_n_ints,
BuilderOp::cmpeq_n_ints,
BuilderOp::cmpeq_n_ints};
static constexpr auto kNotEqualOps = TypedOps{BuilderOp::cmpne_n_floats,
BuilderOp::cmpne_n_ints,
BuilderOp::cmpne_n_ints,
BuilderOp::cmpne_n_ints};
static constexpr auto kMinOps = TypedOps{BuilderOp::min_n_floats,
BuilderOp::min_n_ints,
BuilderOp::min_n_uints,
BuilderOp::min_n_uints};
static constexpr auto kMaxOps = TypedOps{BuilderOp::max_n_floats,
BuilderOp::max_n_ints,
BuilderOp::max_n_uints,
BuilderOp::max_n_uints};
static constexpr auto kMixOps = TypedOps{BuilderOp::mix_n_floats,
BuilderOp::unsupported,
BuilderOp::unsupported,
BuilderOp::unsupported};
friend class AutoContinueMask;
};
class AutoStack {
public:
explicit AutoStack(Generator* g)
: fGenerator(g)
, fStackID(g->createStack()) {}
~AutoStack() {
fGenerator->recycleStack(fStackID);
}
void enter() {
fParentStackID = fGenerator->currentStack();
fGenerator->setCurrentStack(fStackID);
}
void exit() {
SkASSERT(fGenerator->currentStack() == fStackID);
fGenerator->setCurrentStack(fParentStackID);
}
void pushClone(int slots) {
this->pushClone(SlotRange{0, slots}, /*offsetFromStackTop=*/slots);
}
void pushClone(SlotRange range, int offsetFromStackTop) {
fGenerator->builder()->push_clone_from_stack(range, fStackID, offsetFromStackTop);
}
void pushCloneIndirect(SlotRange range, int dynamicStackID, int offsetFromStackTop) {
fGenerator->builder()->push_clone_indirect_from_stack(
range, dynamicStackID, /*otherStackID=*/fStackID, offsetFromStackTop);
}
int stackID() const {
return fStackID;
}
private:
Generator* fGenerator;
int fStackID = 0;
int fParentStackID = 0;
};
class AutoContinueMask {
public:
AutoContinueMask(Generator* gen) : fGenerator(gen) {}
~AutoContinueMask() {
if (fPreviousContinueMask) {
fGenerator->fCurrentContinueMask = fPreviousContinueMask;
}
}
void enable() {
SkASSERT(!fContinueMaskStack.has_value());
fContinueMaskStack.emplace(fGenerator);
fPreviousContinueMask = fGenerator->fCurrentContinueMask;
fGenerator->fCurrentContinueMask = this;
}
void enter() {
SkASSERT(fContinueMaskStack.has_value());
fContinueMaskStack->enter();
}
void exit() {
SkASSERT(fContinueMaskStack.has_value());
fContinueMaskStack->exit();
}
void enterLoopBody() {
if (fContinueMaskStack.has_value()) {
fContinueMaskStack->enter();
fGenerator->builder()->push_literal_i(0);
fContinueMaskStack->exit();
}
}
void exitLoopBody() {
if (fContinueMaskStack.has_value()) {
fContinueMaskStack->enter();
fGenerator->builder()->pop_and_reenable_loop_mask();
fContinueMaskStack->exit();
}
}
private:
std::optional<AutoStack> fContinueMaskStack;
Generator* fGenerator = nullptr;
AutoContinueMask* fPreviousContinueMask = nullptr;
};
class AutoLoopTarget {
public:
AutoLoopTarget(Generator* gen, int* targetPtr) : fGenerator(gen), fLoopTargetPtr(targetPtr) {
fLabelID = fGenerator->builder()->nextLabelID();
fPreviousLoopTarget = *fLoopTargetPtr;
*fLoopTargetPtr = fLabelID;
}
~AutoLoopTarget() {
*fLoopTargetPtr = fPreviousLoopTarget;
}
int labelID() {
return fLabelID;
}
private:
Generator* fGenerator = nullptr;
int* fLoopTargetPtr = nullptr;
int fPreviousLoopTarget;
int fLabelID;
};
class LValue {
public:
virtual ~LValue() = default;
/** Returns true if this lvalue is actually writable--temporaries and uniforms are not. */
virtual bool isWritable() const = 0;
/**
* Returns the fixed slot range of the lvalue, after it is winnowed down to the selected
* field/index. The range is calculated assuming every dynamic index will evaluate to zero.
*/
virtual SlotRange fixedSlotRange(Generator* gen) = 0;
/**
* Returns a stack which holds a single integer, representing the dynamic offset of the lvalue.
* This value does not incorporate the fixed offset. If null is returned, the lvalue doesn't
* have a dynamic offset. `evaluateDynamicIndices` must be called before this is used.
*/
virtual AutoStack* dynamicSlotRange() = 0;
/** Returns the swizzle components of the lvalue, or an empty span for non-swizzle LValues. */
virtual SkSpan<const int8_t> swizzle() { return {}; }
/** Pushes values directly onto the stack. */
[[nodiscard]] virtual bool push(Generator* gen,
SlotRange fixedOffset,
AutoStack* dynamicOffset,
SkSpan<const int8_t> swizzle) = 0;
/** Stores topmost values from the stack directly into the lvalue. */
[[nodiscard]] virtual bool store(Generator* gen,
SlotRange fixedOffset,
AutoStack* dynamicOffset,
SkSpan<const int8_t> swizzle) = 0;
/**
* Some lvalues refer to a temporary expression; these temps can be held in the
* scratch-expression field to ensure that they exist for the lifetime of the lvalue.
*/
std::unique_ptr<Expression> fScratchExpression;
};
class ScratchLValue final : public LValue {
public:
explicit ScratchLValue(const Expression& e)
: fExpression(&e)
, fNumSlots(e.type().slotCount()) {}
~ScratchLValue() override {
if (fGenerator && fDedicatedStack.has_value()) {
// Jettison the scratch expression.
fDedicatedStack->enter();
fGenerator->discardExpression(fNumSlots);
fDedicatedStack->exit();
}
}
bool isWritable() const override {
return false;
}
SlotRange fixedSlotRange(Generator* gen) override {
return SlotRange{0, fNumSlots};
}
AutoStack* dynamicSlotRange() override {
return nullptr;
}
[[nodiscard]] bool push(Generator* gen,
SlotRange fixedOffset,
AutoStack* dynamicOffset,
SkSpan<const int8_t> swizzle) override {
if (!fDedicatedStack.has_value()) {
// Push the scratch expression onto a dedicated stack.
fGenerator = gen;
fDedicatedStack.emplace(fGenerator);
fDedicatedStack->enter();
if (!fGenerator->pushExpression(*fExpression)) {
return unsupported();
}
fDedicatedStack->exit();
}
if (dynamicOffset) {
fDedicatedStack->pushCloneIndirect(fixedOffset, dynamicOffset->stackID(), fNumSlots);
} else {
fDedicatedStack->pushClone(fixedOffset, fNumSlots);
}
if (!swizzle.empty()) {
gen->builder()->swizzle(fixedOffset.count, swizzle);
}
return true;
}
[[nodiscard]] bool store(Generator*, SlotRange, AutoStack*, SkSpan<const int8_t>) override {
SkDEBUGFAIL("scratch lvalues cannot be stored into");
return unsupported();
}
private:
Generator* fGenerator = nullptr;
const Expression* fExpression = nullptr;
std::optional<AutoStack> fDedicatedStack;
int fNumSlots = 0;
};
class VariableLValue final : public LValue {
public:
explicit VariableLValue(const Variable* v) : fVariable(v) {}
bool isWritable() const override {
return !Generator::IsUniform(*fVariable);
}
SlotRange fixedSlotRange(Generator* gen) override {
return Generator::IsUniform(*fVariable) ? gen->getUniformSlots(*fVariable)
: gen->getVariableSlots(*fVariable);
}
AutoStack* dynamicSlotRange() override {
return nullptr;
}
[[nodiscard]] bool push(Generator* gen,
SlotRange fixedOffset,
AutoStack* dynamicOffset,
SkSpan<const int8_t> swizzle) override {
if (Generator::IsUniform(*fVariable)) {
if (dynamicOffset) {
gen->builder()->push_uniform_indirect(fixedOffset, dynamicOffset->stackID(),
this->fixedSlotRange(gen));
} else {
gen->builder()->push_uniform(fixedOffset);
}
} else {
if (dynamicOffset) {
gen->builder()->push_slots_indirect(fixedOffset, dynamicOffset->stackID(),
this->fixedSlotRange(gen));
} else {
gen->builder()->push_slots(fixedOffset);
}
}
if (!swizzle.empty()) {
gen->builder()->swizzle(fixedOffset.count, swizzle);
}
return true;
}
[[nodiscard]] bool store(Generator* gen,
SlotRange fixedOffset,
AutoStack* dynamicOffset,
SkSpan<const int8_t> swizzle) override {
SkASSERT(!Generator::IsUniform(*fVariable));
if (swizzle.empty()) {
if (dynamicOffset) {
gen->builder()->copy_stack_to_slots_indirect(fixedOffset, dynamicOffset->stackID(),
this->fixedSlotRange(gen));
} else {
gen->builder()->copy_stack_to_slots(fixedOffset);
}
} else {
if (dynamicOffset) {
// TODO: implement indirect swizzled store
return unsupported();
} else {
gen->builder()->swizzle_copy_stack_to_slots(fixedOffset, swizzle, swizzle.size());
}
}
return true;
}
private:
const Variable* fVariable;
};
class SwizzleLValue final : public LValue {
public:
explicit SwizzleLValue(std::unique_ptr<LValue> p, const ComponentArray& c)
: fParent(std::move(p))
, fComponents(c) {
SkASSERT(!fComponents.empty() && fComponents.size() <= 4);
}
bool isWritable() const override {
return fParent->isWritable();
}
SlotRange fixedSlotRange(Generator* gen) override {
return fParent->fixedSlotRange(gen);
}
AutoStack* dynamicSlotRange() override {
return fParent->dynamicSlotRange();
}
SkSpan<const int8_t> swizzle() override {
return fComponents;
}
[[nodiscard]] bool push(Generator* gen,
SlotRange fixedOffset,
AutoStack* dynamicOffset,
SkSpan<const int8_t> swizzle) override {
if (!swizzle.empty()) {
SkDEBUGFAIL("swizzle-of-a-swizzle should have been folded out in front end");
return unsupported();
}
return fParent->push(gen, fixedOffset, dynamicOffset, fComponents);
}
[[nodiscard]] bool store(Generator* gen,
SlotRange fixedOffset,
AutoStack* dynamicOffset,
SkSpan<const int8_t> swizzle) override {
if (!swizzle.empty()) {
SkDEBUGFAIL("swizzle-of-a-swizzle should have been folded out in front end");
return unsupported();
}
return fParent->store(gen, fixedOffset, dynamicOffset, fComponents);
}
private:
std::unique_ptr<LValue> fParent;
const ComponentArray& fComponents;
};
class UnownedLValueSlice : public LValue {
public:
explicit UnownedLValueSlice(LValue* p, int initialSlot, int numSlots)
: fParent(p)
, fInitialSlot(initialSlot)
, fNumSlots(numSlots) {
SkASSERT(fInitialSlot >= 0);
SkASSERT(fNumSlots > 0);
}
bool isWritable() const override {
return fParent->isWritable();
}
SlotRange fixedSlotRange(Generator* gen) override {
SlotRange range = fParent->fixedSlotRange(gen);
SlotRange adjusted = range;
adjusted.index += fInitialSlot;
adjusted.count = fNumSlots;
SkASSERT((adjusted.index + adjusted.count) <= (range.index + range.count));
return adjusted;
}
AutoStack* dynamicSlotRange() override {
return fParent->dynamicSlotRange();
}
[[nodiscard]] bool push(Generator* gen,
SlotRange fixedOffset,
AutoStack* dynamicOffset,
SkSpan<const int8_t> swizzle) override {
return fParent->push(gen, fixedOffset, dynamicOffset, swizzle);
}
[[nodiscard]] bool store(Generator* gen,
SlotRange fixedOffset,
AutoStack* dynamicOffset,
SkSpan<const int8_t> swizzle) override {
return fParent->store(gen, fixedOffset, dynamicOffset, swizzle);
}
protected:
LValue* fParent;
private:
int fInitialSlot = 0;
int fNumSlots = 0;
};
class LValueSlice final : public UnownedLValueSlice {
public:
explicit LValueSlice(std::unique_ptr<LValue> p, int initialSlot, int numSlots)
: UnownedLValueSlice(p.release(), initialSlot, numSlots) {}
~LValueSlice() override {
delete fParent;
}
};
class DynamicIndexLValue final : public LValue {
public:
explicit DynamicIndexLValue(std::unique_ptr<LValue> p, const IndexExpression& i)
: fParent(std::move(p))
, fIndexExpr(&i) {
SkASSERT(fIndexExpr->index()->type().isInteger());
}
~DynamicIndexLValue() override {
if (fDedicatedStack.has_value()) {
SkASSERT(fGenerator);
// Jettison the index expression.
fDedicatedStack->enter();
fGenerator->discardExpression(/*slots=*/1);
fDedicatedStack->exit();
}
}
bool isWritable() const override {
return fParent->isWritable();
}
[[nodiscard]] bool evaluateDynamicIndices(Generator* gen) {
// The index must only be computed once; the index-expression could have side effects.
// Once it has been computed, the offset lives on `fDedicatedStack`.
SkASSERT(!fDedicatedStack.has_value());
SkASSERT(!fGenerator);
fGenerator = gen;
fDedicatedStack.emplace(fGenerator);
if (!fParent->swizzle().empty()) {
SkDEBUGFAIL("an indexed-swizzle should have been handled by RewriteIndexedSwizzle");
return unsupported();
}
// Push the index expression onto the dedicated stack.
fDedicatedStack->enter();
if (!fGenerator->pushExpression(*fIndexExpr->index())) {
return unsupported();
}
// Multiply the index-expression result by the per-value slot count.
int slotCount = fIndexExpr->type().slotCount();
if (slotCount != 1) {
fGenerator->builder()->push_literal_i(fIndexExpr->type().slotCount());
fGenerator->builder()->binary_op(BuilderOp::mul_n_ints, 1);
}
// Check to see if a parent LValue already has a dynamic index. If so, we need to
// incorporate its value into our own.
if (AutoStack* parentDynamicIndexStack = fParent->dynamicSlotRange()) {
parentDynamicIndexStack->pushClone(/*slots=*/1);
fGenerator->builder()->binary_op(BuilderOp::add_n_ints, 1);
}
fDedicatedStack->exit();
return true;
}
SlotRange fixedSlotRange(Generator* gen) override {
// Compute the fixed slot range as if we are indexing into position zero.
SlotRange range = fParent->fixedSlotRange(gen);
range.count = fIndexExpr->type().slotCount();
return range;
}
AutoStack* dynamicSlotRange() override {
// We incorporated any parent dynamic offsets when `evaluateDynamicIndices` was called.
SkASSERT(fDedicatedStack.has_value());
return &*fDedicatedStack;
}
[[nodiscard]] bool push(Generator* gen,
SlotRange fixedOffset,
AutoStack* dynamicOffset,
SkSpan<const int8_t> swizzle) override {
return fParent->push(gen, fixedOffset, dynamicOffset, swizzle);
}
[[nodiscard]] bool store(Generator* gen,
SlotRange fixedOffset,
AutoStack* dynamicOffset,
SkSpan<const int8_t> swizzle) override {
return fParent->store(gen, fixedOffset, dynamicOffset, swizzle);
}
private:
Generator* fGenerator = nullptr;
std::unique_ptr<LValue> fParent;
std::optional<AutoStack> fDedicatedStack;
const IndexExpression* fIndexExpr = nullptr;
};
void SlotManager::addSlotDebugInfoForGroup(const std::string& varName,
const Type& type,
Position pos,
int* groupIndex,
bool isFunctionReturnValue) {
SkASSERT(fSlotDebugInfo);
switch (type.typeKind()) {
case Type::TypeKind::kArray: {
int nslots = type.columns();
const Type& elemType = type.componentType();
for (int slot = 0; slot < nslots; ++slot) {
this->addSlotDebugInfoForGroup(varName + "[" + std::to_string(slot) + "]", elemType,
pos, groupIndex, isFunctionReturnValue);
}
break;
}
case Type::TypeKind::kStruct: {
for (const Type::Field& field : type.fields()) {
this->addSlotDebugInfoForGroup(varName + "." + std::string(field.fName),
*field.fType, pos, groupIndex,
isFunctionReturnValue);
}
break;
}
default:
SkASSERTF(0, "unsupported slot type %d", (int)type.typeKind());
[[fallthrough]];
case Type::TypeKind::kScalar:
case Type::TypeKind::kVector:
case Type::TypeKind::kMatrix: {
Type::NumberKind numberKind = type.componentType().numberKind();
int nslots = type.slotCount();
for (int slot = 0; slot < nslots; ++slot) {
SlotDebugInfo slotInfo;
slotInfo.name = varName;
slotInfo.columns = type.columns();
slotInfo.rows = type.rows();
slotInfo.componentIndex = slot;
slotInfo.groupIndex = (*groupIndex)++;
slotInfo.numberKind = numberKind;
slotInfo.pos = pos;
slotInfo.fnReturnValue = isFunctionReturnValue ? 1 : -1;
fSlotDebugInfo->push_back(std::move(slotInfo));
}
break;
}
}
}
void SlotManager::addSlotDebugInfo(const std::string& varName,
const Type& type,
Position pos,
bool isFunctionReturnValue) {
int groupIndex = 0;
this->addSlotDebugInfoForGroup(varName, type, pos, &groupIndex, isFunctionReturnValue);
SkASSERT((size_t)groupIndex == type.slotCount());
}
SlotRange SlotManager::createSlots(std::string name,
const Type& type,
Position pos,
bool isFunctionReturnValue) {
size_t nslots = type.slotCount();
if (nslots == 0) {
return {};
}
if (fSlotDebugInfo) {
// Our debug slot-info table should have the same length as the actual slot table.
SkASSERT(fSlotDebugInfo->size() == (size_t)fSlotCount);
// Append slot names and types to our debug slot-info table.
fSlotDebugInfo->reserve(fSlotCount + nslots);
this->addSlotDebugInfo(name, type, pos, isFunctionReturnValue);
// Confirm that we added the expected number of slots.
SkASSERT(fSlotDebugInfo->size() == (size_t)(fSlotCount + nslots));
}
SlotRange result = {fSlotCount, (int)nslots};
fSlotCount += nslots;
return result;
}
SlotRange SlotManager::getVariableSlots(const Variable& v) {
SlotRange* entry = fSlotMap.find(&v);
if (entry != nullptr) {
return *entry;
}
SlotRange range = this->createSlots(std::string(v.name()),
v.type(),
v.fPosition,
/*isFunctionReturnValue=*/false);
fSlotMap.set(&v, range);
return range;
}
SlotRange SlotManager::getFunctionSlots(const IRNode& callSite, const FunctionDeclaration& f) {
SlotRange* entry = fSlotMap.find(&callSite);
if (entry != nullptr) {
return *entry;
}
SlotRange range = this->createSlots("[" + std::string(f.name()) + "].result",
f.returnType(),
f.fPosition,
/*isFunctionReturnValue=*/true);
fSlotMap.set(&callSite, range);
return range;
}
static bool is_sliceable_swizzle(SkSpan<const int8_t> components) {
// Determine if the swizzle rearranges its elements, or if it's a simple subset of its elements.
// (A simple subset would be a sequential non-repeating range of components, like `.xyz` or
// `.yzw` or `.z`, but not `.xx` or `.xz`, which can be accessed as a slice of the variable.)
for (size_t index = 1; index < components.size(); ++index) {
if (components[index] != int8_t(components[0] + index)) {
return false;
}
}
return true;
}
std::unique_ptr<LValue> Generator::makeLValue(const Expression& e, bool allowScratch) {
if (e.is<VariableReference>()) {
return std::make_unique<VariableLValue>(e.as<VariableReference>().variable());
}
if (e.is<Swizzle>()) {
const Swizzle& swizzleExpr = e.as<Swizzle>();
if (std::unique_ptr<LValue> base = this->makeLValue(*swizzleExpr.base(),
allowScratch)) {
const ComponentArray& components = swizzleExpr.components();
if (is_sliceable_swizzle(components)) {
// If the swizzle is a contiguous subset, we can represent it with a fixed slice.
return std::make_unique<LValueSlice>(std::move(base), components[0],
components.size());
}
return std::make_unique<SwizzleLValue>(std::move(base), components);
}
return nullptr;
}
if (e.is<FieldAccess>()) {
const FieldAccess& fieldExpr = e.as<FieldAccess>();
if (std::unique_ptr<LValue> base = this->makeLValue(*fieldExpr.base(),
allowScratch)) {
// Represent field access with a slice.
return std::make_unique<LValueSlice>(std::move(base), fieldExpr.initialSlot(),
fieldExpr.type().slotCount());
}
return nullptr;
}
if (e.is<IndexExpression>()) {
const IndexExpression& indexExpr = e.as<IndexExpression>();
// If the index base is swizzled (`vec.zyx[idx]`), rewrite it into an equivalent
// non-swizzled form (`vec[uint3(2,1,0)[idx]]`).
if (std::unique_ptr<Expression> rewritten = Transform::RewriteIndexedSwizzle(fContext,
indexExpr)) {
// Convert the rewritten expression into an lvalue.
std::unique_ptr<LValue> lvalue = this->makeLValue(*rewritten, allowScratch);
if (!lvalue) {
return nullptr;
}
// We need to hold onto the rewritten expression for the lifetime of the lvalue.
lvalue->fScratchExpression = std::move(rewritten);
return lvalue;
}
if (std::unique_ptr<LValue> base = this->makeLValue(*indexExpr.base(),
allowScratch)) {
// If the index is a compile-time constant, we can represent it with a fixed slice.
SKSL_INT indexValue;
if (ConstantFolder::GetConstantInt(*indexExpr.index(), &indexValue)) {
int numSlots = indexExpr.type().slotCount();
return std::make_unique<LValueSlice>(std::move(base), numSlots * indexValue,
numSlots);
}
// Represent non-constant indexing via a dynamic index.
auto dynLValue = std::make_unique<DynamicIndexLValue>(std::move(base), indexExpr);
return dynLValue->evaluateDynamicIndices(this) ? std::move(dynLValue)
: nullptr;
}
return nullptr;
}
if (allowScratch) {
// This path allows us to perform field- and index-accesses on an expression as if it were
// an lvalue, but is a temporary and shouldn't be written back to.
return std::make_unique<ScratchLValue>(e);
}
return nullptr;
}
bool Generator::push(LValue& lvalue) {
return lvalue.push(this,
lvalue.fixedSlotRange(this),
lvalue.dynamicSlotRange(),
/*swizzle=*/{});
}
bool Generator::store(LValue& lvalue) {
SkASSERT(lvalue.isWritable());
return lvalue.store(this,
lvalue.fixedSlotRange(this),
lvalue.dynamicSlotRange(),
/*swizzle=*/{});
}
int Generator::getFunctionDebugInfo(const FunctionDeclaration& decl) {
SkASSERT(fDebugTrace);
std::string name = decl.description();
// When generating the debug trace, we typically mark every function as `noinline`. This makes
// the trace more confusing, since this isn't in the source program, so remove it.
static constexpr std::string_view kNoInline = "noinline ";
if (skstd::starts_with(name, kNoInline)) {
name = name.substr(kNoInline.size());
}
// Look for a matching FunctionDebugInfo slot.
for (size_t index = 0; index < fDebugTrace->fFuncInfo.size(); ++index) {
if (fDebugTrace->fFuncInfo[index].name == name) {
return index;
}
}
// We've never called this function before; create a new slot to hold its information.
int slot = (int)fDebugTrace->fFuncInfo.size();
fDebugTrace->fFuncInfo.push_back(FunctionDebugInfo{std::move(name)});
return slot;
}
int Generator::createStack() {
if (!fRecycledStacks.empty()) {
int stackID = fRecycledStacks.back();
fRecycledStacks.pop_back();
return stackID;
}
return ++fNextStackID;
}
void Generator::recycleStack(int stackID) {
fRecycledStacks.push_back(stackID);
}
void Generator::setCurrentStack(int stackID) {
if (fCurrentStack != stackID) {
fCurrentStack = stackID;
fBuilder.set_current_stack(stackID);
}
}
std::optional<SlotRange> Generator::writeFunction(const IRNode& callSite,
const FunctionDefinition& function) {
[[maybe_unused]] int funcIndex = -1;
if (fDebugTrace) {
funcIndex = this->getFunctionDebugInfo(function.declaration());
SkASSERT(funcIndex >= 0);
// TODO(debugger): add trace for function-enter
}
SlotRange lastFunctionResult = fCurrentFunctionResult;
fCurrentFunctionResult = this->getFunctionSlots(callSite, function.declaration());
if (!this->writeStatement(*function.body())) {
return std::nullopt;
}
SlotRange functionResult = fCurrentFunctionResult;
fCurrentFunctionResult = lastFunctionResult;
if (fDebugTrace) {
// TODO(debugger): add trace for function-exit
}
return functionResult;
}
bool Generator::writeGlobals() {
for (const ProgramElement* e : fProgram.elements()) {
if (e->is<GlobalVarDeclaration>()) {
const GlobalVarDeclaration& gvd = e->as<GlobalVarDeclaration>();
const VarDeclaration& decl = gvd.varDeclaration();
const Variable* var = decl.var();
if (var->type().isEffectChild()) {
// Associate each child effect variable with its numeric index.
SkASSERT(!fChildEffectMap.find(var));
int childEffectIndex = fChildEffectMap.count();
fChildEffectMap[var] = childEffectIndex;
continue;
}
// Opaque types include child processors and GL objects (samplers, textures, etc).
// Of those, only child processors are legal variables.
SkASSERT(!var->type().isVoid());
SkASSERT(!var->type().isOpaque());
// Builtin variables are system-defined, with special semantics.
if (int builtin = var->modifiers().fLayout.fBuiltin; builtin >= 0) {
if (builtin == SK_FRAGCOORD_BUILTIN) {
fBuilder.store_device_xy01(this->getVariableSlots(*var));
continue;
}
// The only builtin variable exposed to runtime effects is sk_FragCoord.
return unsupported();
}
if (IsUniform(*var)) {
// Create the uniform slot map in first-to-last order.
(void)this->getUniformSlots(*var);
continue;
}
// Other globals are treated as normal variable declarations.
if (!this->writeVarDeclaration(decl)) {
return unsupported();
}
}
}
return true;
}
bool Generator::writeStatement(const Statement& s) {
switch (s.kind()) {
case Statement::Kind::kBlock:
return this->writeBlock(s.as<Block>());
case Statement::Kind::kBreak:
return this->writeBreakStatement(s.as<BreakStatement>());
case Statement::Kind::kContinue:
return this->writeContinueStatement(s.as<ContinueStatement>());
case Statement::Kind::kDo:
return this->writeDoStatement(s.as<DoStatement>());
case Statement::Kind::kExpression:
return this->writeExpressionStatement(s.as<ExpressionStatement>());
case Statement::Kind::kFor:
return this->writeForStatement(s.as<ForStatement>());
case Statement::Kind::kIf:
return this->writeIfStatement(s.as<IfStatement>());
case Statement::Kind::kNop:
return true;
case Statement::Kind::kReturn:
return this->writeReturnStatement(s.as<ReturnStatement>());
case Statement::Kind::kSwitch:
return this->writeSwitchStatement(s.as<SwitchStatement>());
case Statement::Kind::kVarDeclaration:
return this->writeVarDeclaration(s.as<VarDeclaration>());
default:
return unsupported();
}
}
bool Generator::writeBlock(const Block& b) {
for (const std::unique_ptr<Statement>& stmt : b.children()) {
if (!this->writeStatement(*stmt)) {
return unsupported();
}
}
return true;
}
bool Generator::writeBreakStatement(const BreakStatement&) {
// If all lanes have reached this break, we can just branch straight to the break target instead
// of updating masks.
fBuilder.branch_if_all_lanes_active(fCurrentBreakTarget);
fBuilder.mask_off_loop_mask();
return true;
}
bool Generator::writeContinueStatement(const ContinueStatement&) {
// This could be written as one hand-tuned RasterPipeline op, but for now, we reuse existing ops
// to assemble a continue op.
// Set any currently-executing lanes in the continue-mask to true via `select.`
fCurrentContinueMask->enter();
fBuilder.push_literal_i(~0);
fBuilder.select(/*slots=*/1);
// Disable any currently-executing lanes from the loop mask.
fBuilder.mask_off_loop_mask();
fCurrentContinueMask->exit();
return true;
}
bool Generator::writeDoStatement(const DoStatement& d) {
// Set up a break target.
AutoLoopTarget breakTarget(this, &fCurrentBreakTarget);
// Save off the original loop mask.
fBuilder.enableExecutionMaskWrites();
fBuilder.push_loop_mask();
// If `continue` is used in the loop...
Analysis::LoopControlFlowInfo loopInfo = Analysis::GetLoopControlFlowInfo(*d.statement());
AutoContinueMask autoContinueMask(this);
if (loopInfo.fHasContinue) {
// ... create a temporary slot for continue-mask storage.
autoContinueMask.enable();
}
// Write the do-loop body.
int labelID = fBuilder.nextLabelID();
fBuilder.label(labelID);
autoContinueMask.enterLoopBody();
if (!this->writeStatement(*d.statement())) {
return false;
}
autoContinueMask.exitLoopBody();
// Emit the test-expression, in order to combine it with the loop mask.
if (!this->pushExpression(*d.test())) {
return false;
}
// Mask off any lanes in the loop mask where the test-expression is false; this breaks the loop.
// We don't use the test expression for anything else, so jettison it.
fBuilder.merge_loop_mask();
this->discardExpression(/*slots=*/1);
// If any lanes are still running, go back to the top and run the loop body again.
fBuilder.branch_if_any_lanes_active(labelID);
// If we hit a break statement on all lanes, we will branch here to escape from the loop.
fBuilder.label(breakTarget.labelID());
// Restore the loop mask.
fBuilder.pop_loop_mask();
fBuilder.disableExecutionMaskWrites();
return true;
}
bool Generator::writeMasklessForStatement(const ForStatement& f) {
SkASSERT(f.unrollInfo());
SkASSERT(f.unrollInfo()->fCount > 0);
SkASSERT(f.initializer());
SkASSERT(f.test());
SkASSERT(f.next());
// If no lanes are active, skip over the loop entirely. This guards against looping forever;
// with no lanes active, we wouldn't be able to write the loop variable back to its slot, so
// we'd never make forward progress.
int loopExitID = fBuilder.nextLabelID();
int loopBodyID = fBuilder.nextLabelID();
fBuilder.branch_if_no_lanes_active(loopExitID);
// Run the loop initializer.
if (!this->writeStatement(*f.initializer())) {
return unsupported();
}
// Write the for-loop body. We know the for-loop has a standard ES2 unrollable structure, and
// that it runs for at least one iteration, so we can plow straight ahead into the loop body
// instead of running the loop-test first.
fBuilder.label(loopBodyID);
if (!this->writeStatement(*f.statement())) {
return unsupported();
}
// If the loop only runs for a single iteration, we are already done. If not...
if (f.unrollInfo()->fCount > 1) {
// ... run the next-expression, and immediately discard its result.
if (!this->pushExpression(*f.next(), /*usesResult=*/false)) {
return unsupported();
}
this->discardExpression(f.next()->type().slotCount());
// Run the test-expression, and repeat the loop until the test-expression evaluates false.
if (!this->pushExpression(*f.test())) {
return unsupported();
}
fBuilder.branch_if_no_active_lanes_on_stack_top_equal(0, loopBodyID);
// Jettison the test-expression.
this->discardExpression(/*slots=*/1);
}
fBuilder.label(loopExitID);
return true;
}
bool Generator::writeForStatement(const ForStatement& f) {
// If we've determined that the loop does not run, omit its code entirely.
if (f.unrollInfo() && f.unrollInfo()->fCount == 0) {
return true;
}
// If the loop doesn't escape early due to a `continue`, `break` or `return`, and the loop
// conforms to ES2 structure, we know that we will run the full number of iterations across all
// lanes and don't need to use a loop mask.
Analysis::LoopControlFlowInfo loopInfo = Analysis::GetLoopControlFlowInfo(*f.statement());
if (!loopInfo.fHasContinue && !loopInfo.fHasBreak && !loopInfo.fHasReturn && f.unrollInfo()) {
return this->writeMasklessForStatement(f);
}
// Set up a break target.
AutoLoopTarget breakTarget(this, &fCurrentBreakTarget);
// Run the loop initializer.
if (f.initializer() && !this->writeStatement(*f.initializer())) {
return unsupported();
}
AutoContinueMask autoContinueMask(this);
if (loopInfo.fHasContinue) {
// Acquire a temporary slot for continue-mask storage.
autoContinueMask.enable();
}
// Save off the original loop mask.
fBuilder.enableExecutionMaskWrites();
fBuilder.push_loop_mask();
int loopTestID = fBuilder.nextLabelID();
int loopBodyID = fBuilder.nextLabelID();
// Jump down to the loop test so we can fall out of the loop immediately if it's zero-iteration.
fBuilder.jump(loopTestID);
// Write the for-loop body.
fBuilder.label(loopBodyID);
autoContinueMask.enterLoopBody();
if (!this->writeStatement(*f.statement())) {
return unsupported();
}
autoContinueMask.exitLoopBody();
// Run the next-expression. Immediately discard its result.
if (f.next()) {
if (!this->pushExpression(*f.next(), /*usesResult=*/false)) {
return unsupported();
}
this->discardExpression(f.next()->type().slotCount());
}
fBuilder.label(loopTestID);
if (f.test()) {
// Emit the test-expression, in order to combine it with the loop mask.
if (!this->pushExpression(*f.test())) {
return unsupported();
}
// Mask off any lanes in the loop mask where the test-expression is false; this breaks the
// loop. We don't use the test expression for anything else, so jettison it.
fBuilder.merge_loop_mask();
this->discardExpression(/*slots=*/1);
}
// If any lanes are still running, go back to the top and run the loop body again.
fBuilder.branch_if_any_lanes_active(loopBodyID);
// If we hit a break statement on all lanes, we will branch here to escape from the loop.
fBuilder.label(breakTarget.labelID());
// Restore the loop mask.
fBuilder.pop_loop_mask();
fBuilder.disableExecutionMaskWrites();
return true;
}
bool Generator::writeExpressionStatement(const ExpressionStatement& e) {
if (!this->pushExpression(*e.expression(), /*usesResult=*/false)) {
return unsupported();
}
this->discardExpression(e.expression()->type().slotCount());
return true;
}
bool Generator::writeDynamicallyUniformIfStatement(const IfStatement& i) {
SkASSERT(Analysis::IsDynamicallyUniformExpression(*i.test()));
int falseLabelID = fBuilder.nextLabelID();
int exitLabelID = fBuilder.nextLabelID();
if (!this->pushExpression(*i.test())) {
return unsupported();
}
fBuilder.branch_if_no_active_lanes_on_stack_top_equal(~0, falseLabelID);
if (!this->writeStatement(*i.ifTrue())) {
return unsupported();
}
if (!i.ifFalse()) {
// We don't have an if-false condition at all.
fBuilder.label(falseLabelID);
} else {
// We do have an if-false condition. We've just completed the if-true block, so we need to
// jump past the if-false block to avoid executing it.
fBuilder.jump(exitLabelID);
// The if-false block starts here.
fBuilder.label(falseLabelID);
if (!this->writeStatement(*i.ifFalse())) {
return unsupported();
}
fBuilder.label(exitLabelID);
}
// Jettison the test-expression.
this->discardExpression(/*slots=*/1);
return true;
}
bool Generator::writeIfStatement(const IfStatement& i) {
// If the test condition is known to be uniform, we can skip over the untrue portion entirely.
if (Analysis::IsDynamicallyUniformExpression(*i.test())) {
return this->writeDynamicallyUniformIfStatement(i);
}
// Save the current condition-mask.
fBuilder.enableExecutionMaskWrites();
fBuilder.push_condition_mask();
// Push the test condition mask.
if (!this->pushExpression(*i.test())) {
return unsupported();
}
// Merge the current condition-mask with the test condition, then run the if-true branch.
fBuilder.merge_condition_mask();
if (!this->writeStatement(*i.ifTrue())) {
return unsupported();
}
if (i.ifFalse()) {
// Negate the test-condition, then reapply it to the condition-mask.
// Then, run the if-false branch.
fBuilder.unary_op(BuilderOp::bitwise_not_int, /*slots=*/1);
fBuilder.merge_condition_mask();
if (!this->writeStatement(*i.ifFalse())) {
return unsupported();
}
}
// Jettison the test-expression, and restore the the condition-mask.
this->discardExpression(/*slots=*/1);
fBuilder.pop_condition_mask();
fBuilder.disableExecutionMaskWrites();
return true;
}
bool Generator::writeReturnStatement(const ReturnStatement& r) {
if (r.expression()) {
if (!this->pushExpression(*r.expression())) {
return unsupported();
}
if (this->needsFunctionResultSlots()) {
this->popToSlotRange(fCurrentFunctionResult);
}
}
if (fBuilder.executionMaskWritesAreEnabled() && this->needsReturnMask()) {
fBuilder.mask_off_return_mask();
}
return true;
}
bool Generator::writeSwitchStatement(const SwitchStatement& s) {
const StatementArray& cases = s.cases();
SkASSERT(std::all_of(cases.begin(), cases.end(), [](const std::unique_ptr<Statement>& stmt) {
return stmt->is<SwitchCase>();
}));
// Set up a break target.
AutoLoopTarget breakTarget(this, &fCurrentBreakTarget);
// Save off the original loop mask.
fBuilder.enableExecutionMaskWrites();
fBuilder.push_loop_mask();
// Push the switch-case value, and write a default-mask that enables every lane which already
// has an active loop mask. As we match cases, the default mask will get pared down.
if (!this->pushExpression(*s.value())) {
return unsupported();
}
fBuilder.push_loop_mask();
// Zero out the loop mask; each case op will re-enable it as we go.
fBuilder.mask_off_loop_mask();
// Write each switch-case.
bool foundDefaultCase = false;
for (const std::unique_ptr<Statement>& stmt : cases) {
int skipLabelID = fBuilder.nextLabelID();
const SwitchCase& sc = stmt->as<SwitchCase>();
if (sc.isDefault()) {
foundDefaultCase = true;
if (stmt.get() != cases.back().get()) {
// We only support a default case when it is the very last case. If that changes,
// this logic will need to be updated.
return unsupported();
}
// Keep whatever lanes are executing now, and also enable any lanes in the default mask.
fBuilder.pop_and_reenable_loop_mask();
// Execute the switch-case block, if any lanes are alive to see it.
fBuilder.branch_if_no_lanes_active(skipLabelID);
if (!this->writeStatement(*sc.statement())) {
return unsupported();
}
} else {
// The case-op will enable the loop mask if the switch-value matches, and mask off lanes
// from the default-mask.
fBuilder.case_op(sc.value());
// Execute the switch-case block, if any lanes are alive to see it.
fBuilder.branch_if_no_lanes_active(skipLabelID);
if (!this->writeStatement(*sc.statement())) {
return unsupported();
}
}
fBuilder.label(skipLabelID);
}
// Jettison the switch value, and the default case mask if it was never consumed above.
this->discardExpression(/*slots=*/foundDefaultCase ? 1 : 2);
// If we hit a break statement on all lanes, we will branch here to escape from the switch.
fBuilder.label(breakTarget.labelID());
// Restore the loop mask.
fBuilder.pop_loop_mask();
fBuilder.disableExecutionMaskWrites();
return true;
}
bool Generator::writeVarDeclaration(const VarDeclaration& v) {
if (v.value()) {
if (!this->pushExpression(*v.value())) {
return unsupported();
}
this->popToSlotRangeUnmasked(this->getVariableSlots(*v.var()));
} else {
this->zeroSlotRangeUnmasked(this->getVariableSlots(*v.var()));
}
return true;
}
bool Generator::pushExpression(const Expression& e, bool usesResult) {
switch (e.kind()) {
case Expression::Kind::kBinary:
return this->pushBinaryExpression(e.as<BinaryExpression>());
case Expression::Kind::kChildCall:
return this->pushChildCall(e.as<ChildCall>());
case Expression::Kind::kConstructorArray:
case Expression::Kind::kConstructorArrayCast:
case Expression::Kind::kConstructorCompound:
case Expression::Kind::kConstructorStruct:
return this->pushConstructorCompound(e.asAnyConstructor());
case Expression::Kind::kConstructorCompoundCast:
case Expression::Kind::kConstructorScalarCast:
return this->pushConstructorCast(e.asAnyConstructor());
case Expression::Kind::kConstructorDiagonalMatrix:
return this->pushConstructorDiagonalMatrix(e.as<ConstructorDiagonalMatrix>());
case Expression::Kind::kConstructorMatrixResize:
return this->pushConstructorMatrixResize(e.as<ConstructorMatrixResize>());
case Expression::Kind::kConstructorSplat:
return this->pushConstructorSplat(e.as<ConstructorSplat>());
case Expression::Kind::kFieldAccess:
return this->pushFieldAccess(e.as<FieldAccess>());
case Expression::Kind::kFunctionCall:
return this->pushFunctionCall(e.as<FunctionCall>());
case Expression::Kind::kIndex:
return this->pushIndexExpression(e.as<IndexExpression>());
case Expression::Kind::kLiteral:
return this->pushLiteral(e.as<Literal>());
case Expression::Kind::kPrefix:
return this->pushPrefixExpression(e.as<PrefixExpression>());
case Expression::Kind::kPostfix:
return this->pushPostfixExpression(e.as<PostfixExpression>(), usesResult);
case Expression::Kind::kSwizzle:
return this->pushSwizzle(e.as<Swizzle>());
case Expression::Kind::kTernary:
return this->pushTernaryExpression(e.as<TernaryExpression>());
case Expression::Kind::kVariableReference:
return this->pushVariableReference(e.as<VariableReference>());
default:
return unsupported();
}
}
BuilderOp Generator::GetTypedOp(const SkSL::Type& type, const TypedOps& ops) {
switch (type.componentType().numberKind()) {
case Type::NumberKind::kFloat: return ops.fFloatOp;
case Type::NumberKind::kSigned: return ops.fSignedOp;
case Type::NumberKind::kUnsigned: return ops.fUnsignedOp;
case Type::NumberKind::kBoolean: return ops.fBooleanOp;
default: return BuilderOp::unsupported;
}
}
bool Generator::unaryOp(const SkSL::Type& type, const TypedOps& ops) {
BuilderOp op = GetTypedOp(type, ops);
if (op == BuilderOp::unsupported) {
return unsupported();
}
fBuilder.unary_op(op, type.slotCount());
return true;
}
bool Generator::binaryOp(const SkSL::Type& type, const TypedOps& ops) {
BuilderOp op = GetTypedOp(type, ops);
if (op == BuilderOp::unsupported) {
return unsupported();
}
fBuilder.binary_op(op, type.slotCount());
return true;
}
bool Generator::ternaryOp(const SkSL::Type& type, const TypedOps& ops) {
BuilderOp op = GetTypedOp(type, ops);
if (op == BuilderOp::unsupported) {
return unsupported();
}
fBuilder.ternary_op(op, type.slotCount());
return true;
}
void Generator::foldWithMultiOp(BuilderOp op, int elements) {
// Fold the top N elements on the stack using an op that supports multiple slots, e.g.:
// (A + B + C + D) -> add_2_floats $0..1 += $2..3
// add_float $0 += $1
for (; elements >= 8; elements -= 4) {
fBuilder.binary_op(op, /*slots=*/4);
}
for (; elements >= 6; elements -= 3) {
fBuilder.binary_op(op, /*slots=*/3);
}
for (; elements >= 4; elements -= 2) {
fBuilder.binary_op(op, /*slots=*/2);
}
for (; elements >= 2; elements -= 1) {
fBuilder.binary_op(op, /*slots=*/1);
}
}
bool Generator::pushLValueOrExpression(LValue* lvalue, const Expression& expr) {
return lvalue ? this->push(*lvalue)
: this->pushExpression(expr);
}
bool Generator::pushMatrixMultiply(LValue* lvalue,
const Expression& left,
const Expression& right,
int leftColumns,
int leftRows,
int rightColumns,
int rightRows) {
SkASSERT(left.type().isMatrix() || left.type().isVector());
SkASSERT(right.type().isMatrix() || right.type().isVector());
SkASSERT(leftColumns == rightRows);
int outColumns = rightColumns,
outRows = leftRows;
// Push the left matrix onto the adjacent-neighbor stack. We transpose it so that we can copy
// rows from it in a single op, instead of gathering one element at a time.
AutoStack matrixStack(this);
matrixStack.enter();
if (!this->pushLValueOrExpression(lvalue, left)) {
return unsupported();
}
fBuilder.transpose(leftColumns, leftRows);
// Push the right matrix as well, then go back to the primary stack.
if (!this->pushExpression(right)) {
return unsupported();
}
matrixStack.exit();
// Calculate the offsets of the left- and right-matrix, relative to the stack-top.
int leftMtxBase = left.type().slotCount() + right.type().slotCount();
int rightMtxBase = right.type().slotCount();
// Emit each matrix element.
for (int c = 0; c < outColumns; ++c) {
for (int r = 0; r < outRows; ++r) {
// Dot a vector from left[*][r] with right[c][*].
// (Because the left=matrix has been transposed, we actually pull left[r][*], which
// allows us to clone a column at once instead of cloning each slot individually.)
matrixStack.pushClone(SlotRange{r * leftColumns, leftColumns}, leftMtxBase);
matrixStack.pushClone(SlotRange{c * leftColumns, leftColumns}, rightMtxBase);
fBuilder.dot_floats(leftColumns);
}
}
// Dispose of the source matrices on the adjacent-neighbor stack.
matrixStack.enter();
this->discardExpression(left.type().slotCount());
this->discardExpression(right.type().slotCount());
matrixStack.exit();
// If this multiply was actually an assignment (via *=), write the result back to the lvalue.
return lvalue ? this->store(*lvalue)
: true;
}
void Generator::foldComparisonOp(Operator op, int elements) {
switch (op.kind()) {
case OperatorKind::EQEQ:
// equal(x,y) returns a vector; use & to fold into a scalar.
this->foldWithMultiOp(BuilderOp::bitwise_and_n_ints, elements);
break;
case OperatorKind::NEQ:
// notEqual(x,y) returns a vector; use | to fold into a scalar.
this->foldWithMultiOp(BuilderOp::bitwise_or_n_ints, elements);
break;
default:
SkDEBUGFAIL("comparison only allows == and !=");
break;
}
}
bool Generator::pushStructuredComparison(LValue* left,
Operator op,
LValue* right,
const Type& type) {
if (type.isStruct()) {
// Compare every field in the struct.
SkSpan<const Type::Field> fields = type.fields();
int currentSlot = 0;
for (size_t index = 0; index < fields.size(); ++index) {
const Type& fieldType = *fields[index].fType;
const int fieldSlotCount = fieldType.slotCount();
UnownedLValueSlice fieldLeft {left, currentSlot, fieldSlotCount};
UnownedLValueSlice fieldRight{right, currentSlot, fieldSlotCount};
if (!this->pushStructuredComparison(&fieldLeft, op, &fieldRight, fieldType)) {
return unsupported();
}
currentSlot += fieldSlotCount;
}
this->foldComparisonOp(op, fields.size());
return true;
}
if (type.isArray()) {
const Type& indexedType = type.componentType();
if (indexedType.numberKind() == Type::NumberKind::kNonnumeric) {
// Compare every element in the array.
const int indexedSlotCount = indexedType.slotCount();
int currentSlot = 0;
for (int index = 0; index < type.columns(); ++index) {
UnownedLValueSlice indexedLeft {left, currentSlot, indexedSlotCount};
UnownedLValueSlice indexedRight{right, currentSlot, indexedSlotCount};
if (!this->pushStructuredComparison(&indexedLeft, op, &indexedRight, indexedType)) {
return unsupported();
}
currentSlot += indexedSlotCount;
}
this->foldComparisonOp(op, type.columns());
return true;
}
}
// We've winnowed down to a single element, or an array of homogeneous numeric elements.
// Push the elements onto the stack, then compare them.
if (!this->push(*left) || !this->push(*right)) {
return unsupported();
}
switch (op.kind()) {
case OperatorKind::EQEQ:
if (!this->binaryOp(type, kEqualOps)) {
return unsupported();
}
break;
case OperatorKind::NEQ:
if (!this->binaryOp(type, kNotEqualOps)) {
return unsupported();
}
break;
default:
SkDEBUGFAIL("comparison only allows == and !=");
break;
}
this->foldComparisonOp(op, type.slotCount());
return true;
}
bool Generator::pushBinaryExpression(const BinaryExpression& e) {
return this->pushBinaryExpression(*e.left(), e.getOperator(), *e.right());
}
bool Generator::pushBinaryExpression(const Expression& left, Operator op, const Expression& right) {
switch (op.kind()) {
// Rewrite greater-than ops as their less-than equivalents.
case OperatorKind::GT:
return this->pushBinaryExpression(right, OperatorKind::LT, left);
case OperatorKind::GTEQ:
return this->pushBinaryExpression(right, OperatorKind::LTEQ, left);
// Handle struct and array comparisons.
case OperatorKind::EQEQ:
case OperatorKind::NEQ:
if (left.type().isStruct() || left.type().isArray()) {
SkASSERT(left.type().matches(right.type()));
std::unique_ptr<LValue> lvLeft = this->makeLValue(left, /*allowScratch=*/true);
std::unique_ptr<LValue> lvRight = this->makeLValue(right, /*allowScratch=*/true);
return this->pushStructuredComparison(lvLeft.get(), op, lvRight.get(), left.type());
}
break;
// Emit comma expressions.
case OperatorKind::COMMA:
if (Analysis::HasSideEffects(left)) {
if (!this->pushExpression(left, /*usesResult=*/false)) {
return unsupported();
}
this->discardExpression(left.type().slotCount());
}
return this->pushExpression(right);
default:
break;
}
// Handle binary expressions with mismatched types.
bool vectorizeLeft = false, vectorizeRight = false;
if (!left.type().matches(right.type())) {
if (left.type().componentType().numberKind() != right.type().componentType().numberKind()) {
return unsupported();
}
if (left.type().isScalar() && (right.type().isVector() || right.type().isMatrix())) {
vectorizeLeft = true;
} else if ((left.type().isVector() || left.type().isMatrix()) && right.type().isScalar()) {
vectorizeRight = true;
}
}
const Type& type = vectorizeLeft ? right.type() : left.type();
// If this is an assignment...
std::unique_ptr<LValue> lvalue;
if (op.isAssignment()) {
// ... turn the left side into an lvalue.
lvalue = this->makeLValue(left);
if (!lvalue) {
return unsupported();
}
// Handle simple assignment (`var = expr`).
if (op.kind() == OperatorKind::EQ) {
return this->pushExpression(right) &&
this->store(*lvalue);
}
// Strip off the assignment from the op (turning += into +).
op = op.removeAssignment();
}
// Handle matrix multiplication (MxM/MxV/VxM).
if (op.kind() == OperatorKind::STAR) {
// Matrix * matrix:
if (left.type().isMatrix() && right.type().isMatrix()) {
return this->pushMatrixMultiply(lvalue.get(), left, right,
left.type().columns(), left.type().rows(),
right.type().columns(), right.type().rows());
}
// Vector * matrix:
if (left.type().isVector() && right.type().isMatrix()) {
return this->pushMatrixMultiply(lvalue.get(), left, right,
left.type().columns(), 1,
right.type().columns(), right.type().rows());
}
// Matrix * vector:
if (left.type().isMatrix() && right.type().isVector()) {
return this->pushMatrixMultiply(lvalue.get(), left, right,
left.type().columns(), left.type().rows(),
1, right.type().columns());
}
}
if (!vectorizeLeft && !vectorizeRight && !type.matches(right.type())) {
// We have mismatched types but don't know how to handle them.
return unsupported();
}
// Handle binary ops which require short-circuiting.
switch (op.kind()) {
case OperatorKind::LOGICALAND:
if (Analysis::HasSideEffects(right)) {
// If the RHS has side effects, we rewrite `a && b` as `a ? b : false`. This
// generates pretty solid code and gives us the required short-circuit behavior.
SkASSERT(!op.isAssignment());
SkASSERT(type.componentType().isBoolean());
SkASSERT(type.slotCount() == 1); // operator&& only works with scalar types
Literal falseLiteral{Position{}, 0.0, &right.type()};
return this->pushTernaryExpression(left, right, falseLiteral);
}
break;
case OperatorKind::LOGICALOR:
if (Analysis::HasSideEffects(right)) {
// If the RHS has side effects, we rewrite `a || b` as `a ? true : b`.
SkASSERT(!op.isAssignment());
SkASSERT(type.componentType().isBoolean());
SkASSERT(type.slotCount() == 1); // operator|| only works with scalar types
Literal trueLiteral{Position{}, 1.0, &right.type()};
return this->pushTernaryExpression(left, trueLiteral, right);
}
break;
default:
break;
}
// Push the left- and right-expressions onto the stack.
if (!this->pushLValueOrExpression(lvalue.get(), left)) {
return unsupported();
}
if (vectorizeLeft) {
fBuilder.push_duplicates(right.type().slotCount() - 1);
}
if (!this->pushExpression(right)) {
return unsupported();
}
if (vectorizeRight) {
fBuilder.push_duplicates(left.type().slotCount() - 1);
}
switch (op.kind()) {
case OperatorKind::PLUS:
if (!this->binaryOp(type, kAddOps)) {
return unsupported();
}
break;
case OperatorKind::MINUS:
if (!this->binaryOp(type, kSubtractOps)) {
return unsupported();
}
break;
case OperatorKind::STAR:
if (!this->binaryOp(type, kMultiplyOps)) {
return unsupported();
}
break;
case OperatorKind::SLASH:
if (!this->binaryOp(type, kDivideOps)) {
return unsupported();
}
break;
case OperatorKind::LT:
case OperatorKind::GT:
if (!this->binaryOp(type, kLessThanOps)) {
return unsupported();
}
SkASSERT(type.slotCount() == 1); // operator< only works with scalar types
break;
case OperatorKind::LTEQ:
case OperatorKind::GTEQ:
if (!this->binaryOp(type, kLessThanEqualOps)) {
return unsupported();
}
SkASSERT(type.slotCount() == 1); // operator<= only works with scalar types
break;
case OperatorKind::EQEQ:
if (!this->binaryOp(type, kEqualOps)) {
return unsupported();
}
this->foldComparisonOp(op, type.slotCount());
break;
case OperatorKind::NEQ:
if (!this->binaryOp(type, kNotEqualOps)) {
return unsupported();
}
this->foldComparisonOp(op, type.slotCount());
break;
case OperatorKind::LOGICALAND:
case OperatorKind::BITWISEAND:
// For logical-and, we verified above that the RHS does not have side effects, so we
// don't need to worry about short-circuiting side effects.
fBuilder.binary_op(BuilderOp::bitwise_and_n_ints, type.slotCount());
break;
case OperatorKind::LOGICALOR:
case OperatorKind::BITWISEOR:
// For logical-or, we verified above that the RHS does not have side effects.
fBuilder.binary_op(BuilderOp::bitwise_or_n_ints, type.slotCount());
break;
case OperatorKind::LOGICALXOR:
case OperatorKind::BITWISEXOR:
// Logical-xor does not short circuit.
fBuilder.binary_op(BuilderOp::bitwise_xor_n_ints, type.slotCount());
break;
default:
return unsupported();
}
// If we have an lvalue, we need to write the result back into it.
return lvalue ? this->store(*lvalue)
: true;
}
bool Generator::pushConstructorCompound(const AnyConstructor& c) {
for (const std::unique_ptr<Expression> &arg : c.argumentSpan()) {
if (!this->pushExpression(*arg)) {
return unsupported();
}
}
return true;
}
bool Generator::pushChildCall(const ChildCall& c) {
int* childIdx = fChildEffectMap.find(&c.child());
SkASSERT(childIdx != nullptr);
SkASSERT(!c.arguments().empty());
// Save the dst.rgba fields; these hold our execution masks, and could potentially be
// clobbered by the child effect.
fBuilder.push_dst_rgba();
// All child calls have at least one argument.
const Expression* arg = c.arguments()[0].get();
if (!this->pushExpression(*arg)) {
return unsupported();
}
// Copy arguments from the stack into src/dst as required by this particular child-call.
switch (c.child().type().typeKind()) {
case Type::TypeKind::kShader: {
// The argument must be a float2.
SkASSERT(c.arguments().size() == 1);
SkASSERT(arg->type().matches(*fContext.fTypes.fFloat2));
fBuilder.pop_src_rg();
fBuilder.invoke_shader(*childIdx);
break;
}
case Type::TypeKind::kColorFilter: {
// The argument must be a half4/float4.
SkASSERT(c.arguments().size() == 1);
SkASSERT(arg->type().matches(*fContext.fTypes.fHalf4) ||
arg->type().matches(*fContext.fTypes.fFloat4));
fBuilder.pop_src_rgba();
fBuilder.invoke_color_filter(*childIdx);
break;
}
case Type::TypeKind::kBlender: {
// The first argument must be a half4/float4.
SkASSERT(c.arguments().size() == 2);
SkASSERT(arg->type().matches(*fContext.fTypes.fHalf4) ||
arg->type().matches(*fContext.fTypes.fFloat4));
// The second argument must also be a half4/float4.
arg = c.arguments()[1].get();
SkASSERT(arg->type().matches(*fContext.fTypes.fHalf4) ||
arg->type().matches(*fContext.fTypes.fFloat4));
if (!this->pushExpression(*arg)) {
return unsupported();
}
fBuilder.pop_dst_rgba();
fBuilder.pop_src_rgba();
fBuilder.invoke_blender(*childIdx);
break;
}
default: {
SkDEBUGFAILF("cannot sample from type '%s'", c.child().type().description().c_str());
}
}
// Restore dst.rgba so our execution masks are back to normal.
fBuilder.pop_dst_rgba();
// The child call has returned the result color via src.rgba; push it onto the stack.
fBuilder.push_src_rgba();
return true;
}
bool Generator::pushConstructorCast(const AnyConstructor& c) {
SkASSERT(c.argumentSpan().size() == 1);
const Expression& inner = *c.argumentSpan().front();
SkASSERT(inner.type().slotCount() == c.type().slotCount());
if (!this->pushExpression(inner)) {
return unsupported();
}
if (inner.type().componentType().numberKind() == c.type().componentType().numberKind()) {
// Since we ignore type precision, this cast is effectively a no-op.
return true;
}
if (inner.type().componentType().isSigned() && c.type().componentType().isUnsigned()) {
// Treat uint(int) as a no-op.
return true;
}
if (inner.type().componentType().isUnsigned() && c.type().componentType().isSigned()) {
// Treat int(uint) as a no-op.
return true;
}
if (c.type().componentType().isBoolean()) {
// Converting int or float to boolean can be accomplished via `notEqual(x, 0)`.
fBuilder.push_zeros(c.type().slotCount());
return this->binaryOp(inner.type(), kNotEqualOps);
}
if (inner.type().componentType().isBoolean()) {
// Converting boolean to int or float can be accomplished via bitwise-and.
if (c.type().componentType().isFloat()) {
fBuilder.push_literal_f(1.0f);
} else if (c.type().componentType().isSigned() || c.type().componentType().isUnsigned()) {
fBuilder.push_literal_i(1);
} else {
SkDEBUGFAILF("unexpected cast from bool to %s", c.type().description().c_str());
return unsupported();
}
fBuilder.push_duplicates(c.type().slotCount() - 1);
fBuilder.binary_op(BuilderOp::bitwise_and_n_ints, c.type().slotCount());
return true;
}
// We have dedicated ops to cast between float and integer types.
if (inner.type().componentType().isFloat()) {
if (c.type().componentType().isSigned()) {
fBuilder.unary_op(BuilderOp::cast_to_int_from_float, c.type().slotCount());
return true;
}
if (c.type().componentType().isUnsigned()) {
fBuilder.unary_op(BuilderOp::cast_to_uint_from_float, c.type().slotCount());
return true;
}
} else if (c.type().componentType().isFloat()) {
if (inner.type().componentType().isSigned()) {
fBuilder.unary_op(BuilderOp::cast_to_float_from_int, c.type().slotCount());
return true;
}
if (inner.type().componentType().isUnsigned()) {
fBuilder.unary_op(BuilderOp::cast_to_float_from_uint, c.type().slotCount());
return true;
}
}
SkDEBUGFAILF("unexpected cast from %s to %s",
c.type().description().c_str(), inner.type().description().c_str());
return unsupported();
}
bool Generator::pushConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c) {
fBuilder.push_zeros(1);
if (!this->pushExpression(*c.argument())) {
return unsupported();
}
fBuilder.diagonal_matrix(c.type().columns(), c.type().rows());
return true;
}
bool Generator::pushConstructorMatrixResize(const ConstructorMatrixResize& c) {
if (!this->pushExpression(*c.argument())) {
return unsupported();
}
fBuilder.matrix_resize(c.argument()->type().columns(),
c.argument()->type().rows(),
c.type().columns(),
c.type().rows());
return true;
}
bool Generator::pushConstructorSplat(const ConstructorSplat& c) {
if (!this->pushExpression(*c.argument())) {
return unsupported();
}
fBuilder.push_duplicates(c.type().slotCount() - 1);
return true;
}
bool Generator::pushFieldAccess(const FieldAccess& f) {
// If possible, get direct field access via the lvalue.
std::unique_ptr<LValue> lvalue = this->makeLValue(f, /*allowScratch=*/true);
return lvalue && this->push(*lvalue);
}
bool Generator::pushFunctionCall(const FunctionCall& c) {
if (c.function().isIntrinsic()) {
return this->pushIntrinsic(c);
}
// Keep track of the current function.
const FunctionDefinition* lastFunction = fCurrentFunction;
fCurrentFunction = c.function().definition();
// Skip over the function body entirely if there are no active lanes.
// (If the function call was trivial, it would likely have been inlined in the frontend, so this
// is likely to save a significant amount of work if the lanes are all dead.)
int skipLabelID = fBuilder.nextLabelID();
fBuilder.branch_if_no_lanes_active(skipLabelID);
// Save off the return mask.
if (this->needsReturnMask()) {
fBuilder.enableExecutionMaskWrites();
fBuilder.push_return_mask();
}
// Write all the arguments into their parameter's variable slots. Because we never allow
// recursion, we don't need to worry about overwriting any existing values in those slots.
// (In fact, we don't even need to apply the write mask.)
SkTArray<std::unique_ptr<LValue>> lvalues;
lvalues.resize(c.arguments().size());
for (int index = 0; index < c.arguments().size(); ++index) {
const Expression& arg = *c.arguments()[index];
const Variable& param = *c.function().parameters()[index];
// Use LValues for out-parameters and inout-parameters, so we can store back to them later.
if (IsInoutParameter(param) || IsOutParameter(param)) {
lvalues[index] = this->makeLValue(arg);
if (!lvalues[index]) {
return unsupported();
}
// There are no guarantees on the starting value of an out-parameter, so we only need to
// store the lvalues associated with an inout parameter.
if (IsInoutParameter(param)) {
if (!this->push(*lvalues[index])) {
return unsupported();
}
this->popToSlotRangeUnmasked(this->getVariableSlots(param));
}
} else {
// Copy input arguments into their respective parameter slots.
if (!this->pushExpression(arg)) {
return unsupported();
}
this->popToSlotRangeUnmasked(this->getVariableSlots(param));
}
}
// Emit the function body.
std::optional<SlotRange> r = this->writeFunction(c, *fCurrentFunction);
if (!r.has_value()) {
return unsupported();
}
// Restore the original return mask.
if (this->needsReturnMask()) {
fBuilder.pop_return_mask();
fBuilder.disableExecutionMaskWrites();
}
// If the function uses result slots, move its result from slots onto the stack.
if (this->needsFunctionResultSlots()) {
fBuilder.push_slots(*r);
}
// We've returned back to the last function.
fCurrentFunction = lastFunction;
// Copy out-parameters and inout-parameters back to their homes.
for (int index = 0; index < c.arguments().size(); ++index) {
if (lvalues[index]) {
// Only out- and inout-parameters should have an associated lvalue.
const Variable& param = *c.function().parameters()[index];
SkASSERT(IsInoutParameter(param) || IsOutParameter(param));
// Copy the parameter's slots directly into the lvalue.
fBuilder.push_slots(this->getVariableSlots(param));
if (!this->store(*lvalues[index])) {
return unsupported();
}
this->discardExpression(param.type().slotCount());
}
}
// Copy the function result from its slots onto the stack.
fBuilder.label(skipLabelID);
return true;
}
bool Generator::pushIndexExpression(const IndexExpression& i) {
std::unique_ptr<LValue> lvalue = this->makeLValue(i, /*allowScratch=*/true);
return lvalue && this->push(*lvalue);
}
bool Generator::pushIntrinsic(const FunctionCall& c) {
const ExpressionArray& args = c.arguments();
switch (args.size()) {
case 1:
return this->pushIntrinsic(c.function().intrinsicKind(), *args[0]);
case 2:
return this->pushIntrinsic(c.function().intrinsicKind(), *args[0], *args[1]);
case 3:
return this->pushIntrinsic(c.function().intrinsicKind(), *args[0], *args[1], *args[2]);
default:
break;
}
return unsupported();
}
bool Generator::pushLengthIntrinsic(int slotCount) {
if (slotCount > 1) {
// Implement `length(vec)` as `sqrt(dot(x, x))`.
fBuilder.push_clone(slotCount);
fBuilder.dot_floats(slotCount);
fBuilder.unary_op(BuilderOp::sqrt_float, 1);
} else {
// `length(scalar)` is `sqrt(x^2)`, which is equivalent to `abs(x)`.
fBuilder.unary_op(BuilderOp::abs_float, 1);
}
return true;
}
bool Generator::pushVectorizedExpression(const Expression& expr, const Type& vectorType) {
if (!this->pushExpression(expr)) {
return unsupported();
}
if (vectorType.slotCount() > expr.type().slotCount()) {
SkASSERT(expr.type().slotCount() == 1);
fBuilder.push_duplicates(vectorType.slotCount() - expr.type().slotCount());
}
return true;
}
bool Generator::pushIntrinsic(const TypedOps& ops, const Expression& arg0) {
if (!this->pushExpression(arg0)) {
return unsupported();
}
return this->unaryOp(arg0.type(), ops);
}
bool Generator::pushIntrinsic(BuilderOp builderOp, const Expression& arg0) {
if (!this->pushExpression(arg0)) {
return unsupported();
}
fBuilder.unary_op(builderOp, arg0.type().slotCount());
return true;
}
bool Generator::pushIntrinsic(IntrinsicKind intrinsic, const Expression& arg0) {
switch (intrinsic) {
case IntrinsicKind::k_abs_IntrinsicKind:
return this->pushIntrinsic(kAbsOps, arg0);
case IntrinsicKind::k_any_IntrinsicKind:
if (!this->pushExpression(arg0)) {
return unsupported();
}
this->foldWithMultiOp(BuilderOp::bitwise_or_n_ints, arg0.type().slotCount());
return true;
case IntrinsicKind::k_all_IntrinsicKind:
if (!this->pushExpression(arg0)) {
return unsupported();
}
this->foldWithMultiOp(BuilderOp::bitwise_and_n_ints, arg0.type().slotCount());
return true;
case IntrinsicKind::k_acos_IntrinsicKind:
return this->pushIntrinsic(BuilderOp::acos_float, arg0);
case IntrinsicKind::k_asin_IntrinsicKind:
return this->pushIntrinsic(BuilderOp::asin_float, arg0);
case IntrinsicKind::k_atan_IntrinsicKind:
return this->pushIntrinsic(BuilderOp::atan_float, arg0);
case IntrinsicKind::k_ceil_IntrinsicKind:
return this->pushIntrinsic(BuilderOp::ceil_float, arg0);
case IntrinsicKind::k_cos_IntrinsicKind:
return this->pushIntrinsic(BuilderOp::cos_float, arg0);
case IntrinsicKind::k_degrees_IntrinsicKind: {
Literal lit180OverPi{Position{}, 57.2957795131f, &arg0.type().componentType()};
return this->pushBinaryExpression(arg0, OperatorKind::STAR, lit180OverPi);
}
case IntrinsicKind::k_floatBitsToInt_IntrinsicKind:
case IntrinsicKind::k_floatBitsToUint_IntrinsicKind:
case IntrinsicKind::k_intBitsToFloat_IntrinsicKind:
case IntrinsicKind::k_uintBitsToFloat_IntrinsicKind:
return this->pushExpression(arg0);
case IntrinsicKind::k_exp_IntrinsicKind:
return this->pushIntrinsic(BuilderOp::exp_float, arg0);
case IntrinsicKind::k_floor_IntrinsicKind:
return this->pushIntrinsic(BuilderOp::floor_float, arg0);
case IntrinsicKind::k_fract_IntrinsicKind:
// Implement fract as `x - floor(x)`.
if (!this->pushExpression(arg0)) {
return unsupported();
}
fBuilder.push_clone(arg0.type().slotCount());
fBuilder.unary_op(BuilderOp::floor_float, arg0.type().slotCount());
return this->binaryOp(arg0.type(), kSubtractOps);
case IntrinsicKind::k_inversesqrt_IntrinsicKind: {
// Implement inversesqrt as `1.0 / sqrt(x)`.
Literal oneLiteral{Position{}, 1.0, &arg0.type().componentType()};
return this->pushVectorizedExpression(oneLiteral, arg0.type()) &&
this->pushIntrinsic(k_sqrt_IntrinsicKind, arg0) &&
this->binaryOp(arg0.type(), kDivideOps);
}
case IntrinsicKind::k_length_IntrinsicKind:
return this->pushExpression(arg0) &&
this->pushLengthIntrinsic(arg0.type().slotCount());
case IntrinsicKind::k_log_IntrinsicKind:
if (!this->pushExpression(arg0)) {
return unsupported();
}
fBuilder.unary_op(BuilderOp::log_float, arg0.type().slotCount());
return true;
case IntrinsicKind::k_log2_IntrinsicKind:
if (!this->pushExpression(arg0)) {
return unsupported();
}
fBuilder.unary_op(BuilderOp::log2_float, arg0.type().slotCount());
return true;
case IntrinsicKind::k_normalize_IntrinsicKind:
// Implement normalize as `x / length(x)`. First, push the expression.
if (!this->pushExpression(arg0)) {
return unsupported();
}
// Clone the expression and calculate its length.
fBuilder.push_clone(arg0.type().slotCount());
if (!this->pushLengthIntrinsic(arg0.type().slotCount())) {
return unsupported();
}
// Finally, vectorize the length and divide.
fBuilder.push_duplicates(arg0.type().slotCount() - 1);
return this->binaryOp(arg0.type(), kDivideOps);
case IntrinsicKind::k_not_IntrinsicKind:
return this->pushPrefixExpression(OperatorKind::LOGICALNOT, arg0);
case IntrinsicKind::k_radians_IntrinsicKind: {
Literal litPiOver180{Position{}, 0.01745329251f, &arg0.type().componentType()};
return this->pushBinaryExpression(arg0, OperatorKind::STAR, litPiOver180);
}
case IntrinsicKind::k_saturate_IntrinsicKind: {
// Implement saturate as clamp(arg, 0, 1).
Literal zeroLiteral{Position{}, 0.0, &arg0.type().componentType()};
Literal oneLiteral{Position{}, 1.0, &arg0.type().componentType()};
return this->pushIntrinsic(k_clamp_IntrinsicKind, arg0, zeroLiteral, oneLiteral);
}
case IntrinsicKind::k_sign_IntrinsicKind: {
// Implement floating-point sign() as `clamp(arg * FLT_MAX, -1, 1)`.
// FLT_MIN * FLT_MAX evaluates to 4, so multiplying any float value against FLT_MAX is
// sufficient to ensure that |value| is always 1 or greater (excluding zero and nan).
// Integer sign() doesn't need to worry about fractional values or nans, and can simply
// be `clamp(arg, -1, 1)`.
if (!this->pushExpression(arg0)) {
return unsupported();
}
if (arg0.type().componentType().isFloat()) {
Literal fltMaxLiteral{Position{}, FLT_MAX, &arg0.type().componentType()};
if (!this->pushVectorizedExpression(fltMaxLiteral, arg0.type())) {
return unsupported();
}
if (!this->binaryOp(arg0.type(), kMultiplyOps)) {
return unsupported();
}
}
Literal neg1Literal{Position{}, -1.0, &arg0.type().componentType()};
if (!this->pushVectorizedExpression(neg1Literal, arg0.type())) {
return unsupported();
}
if (!this->binaryOp(arg0.type(), kMaxOps)) {
return unsupported();
}
Literal pos1Literal{Position{}, 1.0, &arg0.type().componentType()};
if (!this->pushVectorizedExpression(pos1Literal, arg0.type())) {
return unsupported();
}
return this->binaryOp(arg0.type(), kMinOps);
}
case IntrinsicKind::k_sin_IntrinsicKind:
return this->pushIntrinsic(BuilderOp::sin_float, arg0);
case IntrinsicKind::k_sqrt_IntrinsicKind:
return this->pushIntrinsic(BuilderOp::sqrt_float, arg0);
case IntrinsicKind::k_tan_IntrinsicKind:
return this->pushIntrinsic(BuilderOp::tan_float, arg0);
case IntrinsicKind::k_transpose_IntrinsicKind:
SkASSERT(arg0.type().isMatrix());
if (!this->pushExpression(arg0)) {
return unsupported();
}
fBuilder.transpose(arg0.type().columns(), arg0.type().rows());
return true;
case IntrinsicKind::k_trunc_IntrinsicKind:
// Implement trunc as `float(int(x))`, since float-to-int rounds toward zero.
if (!this->pushExpression(arg0)) {
return unsupported();
}
fBuilder.unary_op(BuilderOp::cast_to_int_from_float, arg0.type().slotCount());
fBuilder.unary_op(BuilderOp::cast_to_float_from_int, arg0.type().slotCount());
return true;
case IntrinsicKind::k_fromLinearSrgb_IntrinsicKind:
case IntrinsicKind::k_toLinearSrgb_IntrinsicKind: {
// The argument must be a half3.
SkASSERT(arg0.type().matches(*fContext.fTypes.fHalf3));
if (!this->pushExpression(arg0)) {
return unsupported();
}
// The intrinsics accept a three-component value; add alpha for the push/pop_src_rgba
fBuilder.push_literal_f(1.0f);
// Copy arguments from the stack into src
fBuilder.pop_src_rgba();
if (intrinsic == IntrinsicKind::k_fromLinearSrgb_IntrinsicKind) {
fBuilder.invoke_from_linear_srgb();
} else {
fBuilder.invoke_to_linear_srgb();
}
// The xform has left the result color in src.rgba; push it onto the stack
fBuilder.push_src_rgba();
// The intrinsic returns a three-component value; discard alpha
this->discardExpression(/*slots=*/1);
return true;
}
default:
break;
}
return unsupported();
}
bool Generator::pushIntrinsic(const TypedOps& ops, const Expression& arg0, const Expression& arg1) {
if (!this->pushExpression(arg0) || !this->pushVectorizedExpression(arg1, arg0.type())) {
return unsupported();
}
return this->binaryOp(arg0.type(), ops);
}
bool Generator::pushIntrinsic(BuilderOp builderOp, const Expression& arg0, const Expression& arg1) {
if (!this->pushExpression(arg0) || !this->pushVectorizedExpression(arg1, arg0.type())) {
return unsupported();
}
fBuilder.binary_op(builderOp, arg0.type().slotCount());
return true;
}
bool Generator::pushIntrinsic(IntrinsicKind intrinsic,
const Expression& arg0,
const Expression& arg1) {
switch (intrinsic) {
case IntrinsicKind::k_atan_IntrinsicKind:
return this->pushIntrinsic(BuilderOp::atan2_n_floats, arg0, arg1);
case IntrinsicKind::k_cross_IntrinsicKind: {
// Implement cross as `arg0.yzx * arg1.zxy - arg0.zxy * arg1.yzx`. We use two stacks so
// that each subexpression can be multiplied separately.
SkASSERT(arg0.type().matches(arg1.type()));
SkASSERT(arg0.type().slotCount() == 3);
SkASSERT(arg1.type().slotCount() == 3);
// Push `arg0.yzx` onto this stack and `arg0.zxy` onto a separate subexpression stack.
AutoStack subexpressionStack(this);
subexpressionStack.enter();
if (!this->pushExpression(arg0)) {
return unsupported();
}
subexpressionStack.exit();
subexpressionStack.pushClone(/*slots=*/3);
fBuilder.swizzle(/*consumedSlots=*/3, {1, 2, 0});
subexpressionStack.enter();
fBuilder.swizzle(/*consumedSlots=*/3, {2, 0, 1});
subexpressionStack.exit();
// Push `arg1.zxy` onto this stack and `arg1.yzx` onto the next stack. Perform the
// multiply on each subexpression (`arg0.yzx * arg1.zxy` on the first stack, and
// `arg0.zxy * arg1.yzx` on the next).
subexpressionStack.enter();
if (!this->pushExpression(arg1)) {
return unsupported();
}
subexpressionStack.exit();
subexpressionStack.pushClone(/*slots=*/3);
fBuilder