Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
Classes | Public Types | Public Member Functions | Static Public Member Functions | Friends | List of all members
SkSL::RP::Generator Class Reference

Classes

struct  TypedOps
 

Public Types

using ImmutableBits = int32_t
 

Public Member Functions

 Generator (const SkSL::Program &program, DebugTracePriv *debugTrace, bool writeTraceOps)
 
 ~Generator ()
 
bool writeProgram (const FunctionDefinition &function)
 
std::unique_ptr< RP::Programfinish ()
 
std::optional< SlotRangewriteFunction (const IRNode &callSite, const FunctionDefinition &function, SkSpan< std::unique_ptr< Expression > const > arguments)
 
int getFunctionDebugInfo (const FunctionDeclaration &decl)
 
bool hasVariableSlots (const Variable &v)
 
SlotRange getVariableSlots (const Variable &v)
 
SlotRange getImmutableSlots (const Variable &v)
 
SlotRange getUniformSlots (const Variable &v)
 
SlotRange getFunctionSlots (const IRNode &callSite, const FunctionDeclaration &f)
 
int createStack ()
 
void recycleStack (int stackID)
 
void setCurrentStack (int stackID)
 
int currentStack ()
 
std::unique_ptr< LValuemakeLValue (const Expression &e, bool allowScratch=false)
 
bool store (LValue &lvalue)
 
bool push (LValue &lvalue)
 
Builderbuilder ()
 
bool writeStatement (const Statement &s)
 
bool writeBlock (const Block &b)
 
bool writeBreakStatement (const BreakStatement &b)
 
bool writeContinueStatement (const ContinueStatement &b)
 
bool writeDoStatement (const DoStatement &d)
 
bool writeExpressionStatement (const ExpressionStatement &e)
 
bool writeMasklessForStatement (const ForStatement &f)
 
bool writeForStatement (const ForStatement &f)
 
bool writeGlobals ()
 
bool writeIfStatement (const IfStatement &i)
 
bool writeDynamicallyUniformIfStatement (const IfStatement &i)
 
bool writeReturnStatement (const ReturnStatement &r)
 
bool writeSwitchStatement (const SwitchStatement &s)
 
bool writeVarDeclaration (const VarDeclaration &v)
 
bool writeImmutableVarDeclaration (const VarDeclaration &d)
 
bool pushBinaryExpression (const BinaryExpression &e)
 
bool pushBinaryExpression (const Expression &left, Operator op, const Expression &right)
 
bool pushChildCall (const ChildCall &c)
 
bool pushConstructorCast (const AnyConstructor &c)
 
bool pushConstructorCompound (const AnyConstructor &c)
 
bool pushConstructorDiagonalMatrix (const ConstructorDiagonalMatrix &c)
 
bool pushConstructorMatrixResize (const ConstructorMatrixResize &c)
 
bool pushConstructorSplat (const ConstructorSplat &c)
 
bool pushExpression (const Expression &e, bool usesResult=true)
 
bool pushFieldAccess (const FieldAccess &f)
 
bool pushFunctionCall (const FunctionCall &c)
 
bool pushIndexExpression (const IndexExpression &i)
 
bool pushIntrinsic (const FunctionCall &c)
 
bool pushIntrinsic (IntrinsicKind intrinsic, const Expression &arg0)
 
bool pushIntrinsic (IntrinsicKind intrinsic, const Expression &arg0, const Expression &arg1)
 
bool pushIntrinsic (IntrinsicKind intrinsic, const Expression &arg0, const Expression &arg1, const Expression &arg2)
 
bool pushLiteral (const Literal &l)
 
bool pushPostfixExpression (const PostfixExpression &p, bool usesResult)
 
bool pushPrefixExpression (const PrefixExpression &p)
 
bool pushPrefixExpression (Operator op, const Expression &expr)
 
bool pushSwizzle (const Swizzle &s)
 
bool pushTernaryExpression (const TernaryExpression &t)
 
bool pushTernaryExpression (const Expression &test, const Expression &ifTrue, const Expression &ifFalse)
 
bool pushDynamicallyUniformTernaryExpression (const Expression &test, const Expression &ifTrue, const Expression &ifFalse)
 
bool pushVariableReference (const VariableReference &v)
 
bool pushImmutableData (const Expression &e)
 
std::optional< SlotRangefindPreexistingImmutableData (const TArray< ImmutableBits > &immutableValues)
 
std::optional< ImmutableBitsgetImmutableBitsForSlot (const Expression &expr, size_t slot)
 
bool getImmutableValueForExpression (const Expression &expr, TArray< ImmutableBits > *immutableValues)
 
void storeImmutableValueToSlots (const TArray< ImmutableBits > &immutableValues, SlotRange slots)
 
void popToSlotRange (SlotRange r)
 
void popToSlotRangeUnmasked (SlotRange r)
 
void discardExpression (int slots)
 
void zeroSlotRangeUnmasked (SlotRange r)
 
void emitTraceLine (Position pos)
 
void pushTraceScopeMask ()
 
void discardTraceScopeMask ()
 
void emitTraceScope (int delta)
 
void calculateLineOffsets ()
 
bool shouldWriteTraceOps ()
 
int traceMaskStackID ()
 
bool unaryOp (const SkSL::Type &type, const TypedOps &ops)
 
bool binaryOp (const SkSL::Type &type, const TypedOps &ops)
 
bool ternaryOp (const SkSL::Type &type, const TypedOps &ops)
 
bool pushIntrinsic (const TypedOps &ops, const Expression &arg0)
 
bool pushIntrinsic (const TypedOps &ops, const Expression &arg0, const Expression &arg1)
 
bool pushIntrinsic (BuilderOp builderOp, const Expression &arg0)
 
bool pushIntrinsic (BuilderOp builderOp, const Expression &arg0, const Expression &arg1)
 
bool pushAbsFloatIntrinsic (int slots)
 
bool pushLengthIntrinsic (int slotCount)
 
bool pushVectorizedExpression (const Expression &expr, const Type &vectorType)
 
bool pushVariableReferencePartial (const VariableReference &v, SlotRange subset)
 
bool pushLValueOrExpression (LValue *lvalue, const Expression &expr)
 
bool pushMatrixMultiply (LValue *lvalue, const Expression &left, const Expression &right, int leftColumns, int leftRows, int rightColumns, int rightRows)
 
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)
 
bool needsReturnMask (const FunctionDefinition *func)
 
bool needsFunctionResultSlots (const FunctionDefinition *func)
 

Static Public Member Functions

static BuilderOp GetTypedOp (const SkSL::Type &type, const TypedOps &ops)
 
static bool IsUniform (const Variable &var)
 
static bool IsOutParameter (const Variable &var)
 
static bool IsInoutParameter (const Variable &var)
 

Friends

class AutoContinueMask
 

Detailed Description

Definition at line 181 of file SkSLRasterPipelineCodeGenerator.cpp.

Member Typedef Documentation

◆ ImmutableBits

Support methods for immutable data, which trade more slots for smaller code size.

Definition at line 345 of file SkSLRasterPipelineCodeGenerator.cpp.

Constructor & Destructor Documentation

◆ Generator()

SkSL::RP::Generator::Generator ( const SkSL::Program program,
DebugTracePriv debugTrace,
bool  writeTraceOps 
)
inline

Definition at line 183 of file SkSLRasterPipelineCodeGenerator.cpp.

184 : fProgram(program)
185 , fContext(fProgram.fContext->fTypes, *fProgram.fContext->fErrors)
186 , fDebugTrace(debugTrace)
187 , fWriteTraceOps(writeTraceOps)
188 , fProgramSlots(debugTrace ? &debugTrace->fSlotInfo : nullptr)
189 , fUniformSlots(debugTrace ? &debugTrace->fUniformInfo : nullptr)
190 , fImmutableSlots(nullptr) {
191 fContext.fConfig = fProgram.fConfig.get();
192 fContext.fModule = fProgram.fContext->fModule;
193 }
const Module * fModule
Definition SkSLContext.h:44
ProgramConfig * fConfig
Definition SkSLContext.h:33
std::shared_ptr< Context > fContext
std::unique_ptr< ProgramConfig > fConfig

◆ ~Generator()

SkSL::RP::Generator::~Generator ( )
inline

Definition at line 195 of file SkSLRasterPipelineCodeGenerator.cpp.

195 {
196 // ~AutoStack calls into the Generator, so we need to make sure the trace mask is reset
197 // before the Generator is destroyed.
198 fTraceMask.reset();
199 }

Member Function Documentation

◆ binaryOp()

bool SkSL::RP::Generator::binaryOp ( const SkSL::Type type,
const TypedOps ops 
)

Definition at line 2262 of file SkSLRasterPipelineCodeGenerator.cpp.

2262 {
2264 if (op == BuilderOp::unsupported) {
2265 return unsupported();
2266 }
2267 fBuilder.binary_op(op, type.slotCount());
2268 return true;
2269}
SkPathOp ops[]
void binary_op(BuilderOp op, int32_t slots)
static BuilderOp GetTypedOp(const SkSL::Type &type, const TypedOps &ops)

◆ builder()

Builder * SkSL::RP::Generator::builder ( )
inline

The Builder stitches our instructions together into Raster Pipeline code.

Definition at line 287 of file SkSLRasterPipelineCodeGenerator.cpp.

287{ return &fBuilder; }

◆ calculateLineOffsets()

void SkSL::RP::Generator::calculateLineOffsets ( )

Prepares our position-to-line-offset conversion table (stored in fLineOffsets).

Definition at line 1572 of file SkSLRasterPipelineCodeGenerator.cpp.

1572 {
1573 SkASSERT(fLineOffsets.empty());
1574 fLineOffsets.push_back(0);
1575 for (size_t i = 0; i < fProgram.fSource->length(); ++i) {
1576 if ((*fProgram.fSource)[i] == '\n') {
1577 fLineOffsets.push_back(i);
1578 }
1579 }
1580 fLineOffsets.push_back(fProgram.fSource->length());
1581}
#define SkASSERT(cond)
Definition SkAssert.h:116
bool empty() const
Definition SkTArray.h:194
std::unique_ptr< std::string > fSource

◆ createStack()

int SkSL::RP::Generator::createStack ( )

Creates an additional stack for the program to push values onto. The stack will not become actively in-use until setCurrentStack is called.

Definition at line 1358 of file SkSLRasterPipelineCodeGenerator.cpp.

1358 {
1359 if (!fRecycledStacks.empty()) {
1360 int stackID = fRecycledStacks.back();
1361 fRecycledStacks.pop_back();
1362 return stackID;
1363 }
1364 return ++fNextStackID;
1365}

◆ currentStack()

int SkSL::RP::Generator::currentStack ( )
inline

Reports the currently active stack.

Definition at line 270 of file SkSLRasterPipelineCodeGenerator.cpp.

270 {
271 return fCurrentStack;
272 }

◆ discardExpression()

void SkSL::RP::Generator::discardExpression ( int  slots)
inline

Pops an expression from the value stack and discards it.

Definition at line 371 of file SkSLRasterPipelineCodeGenerator.cpp.

371{ fBuilder.discard_stack(slots); }
void discard_stack(int32_t count, int stackID)

◆ discardTraceScopeMask()

void SkSL::RP::Generator::discardTraceScopeMask ( )

Definition at line 1560 of file SkSLRasterPipelineCodeGenerator.cpp.

1560 {
1561 if (this->shouldWriteTraceOps()) {
1562 this->discardExpression(/*slots=*/1);
1563 }
1564}

◆ emitTraceLine()

void SkSL::RP::Generator::emitTraceLine ( Position  pos)

Emits a trace_line opcode. writeStatement does this, and statements that alter control flow may need to explicitly add additional traces.

Definition at line 1535 of file SkSLRasterPipelineCodeGenerator.cpp.

1535 {
1536 if (fDebugTrace && fWriteTraceOps && pos.valid() && fInsideCompoundStatement == 0) {
1537 // Binary search within fLineOffets to convert the position into a line number.
1538 SkASSERT(fLineOffsets.size() >= 2);
1539 SkASSERT(fLineOffsets[0] == 0);
1540 SkASSERT(fLineOffsets.back() == (int)fProgram.fSource->length());
1541 int lineNumber = std::distance(
1542 fLineOffsets.begin(),
1543 std::upper_bound(fLineOffsets.begin(), fLineOffsets.end(), pos.startOffset()));
1544
1545 fBuilder.trace_line(fTraceMask->stackID(), lineNumber);
1546 }
1547}
SkPoint pos
void trace_line(int traceMaskStackID, int line)
int size() const
Definition SkTArray.h:416

◆ emitTraceScope()

void SkSL::RP::Generator::emitTraceScope ( int  delta)

Definition at line 1566 of file SkSLRasterPipelineCodeGenerator.cpp.

1566 {
1567 if (this->shouldWriteTraceOps()) {
1568 fBuilder.trace_scope(this->currentStack(), delta);
1569 }
1570}
void trace_scope(int traceMaskStackID, int delta)

◆ findPreexistingImmutableData()

std::optional< SlotRange > SkSL::RP::Generator::findPreexistingImmutableData ( const TArray< ImmutableBits > &  immutableValues)

Definition at line 2715 of file SkSLRasterPipelineCodeGenerator.cpp.

2716 {
2718 slotArray.reserve_exact(immutableValues.size());
2719
2720 // Find all the slots associated with each immutable-value bit representation.
2721 // If a given bit-pattern doesn't exist anywhere in our program yet, we can stop searching.
2722 for (const ImmutableBits& immutableValue : immutableValues) {
2723 const THashSet<Slot>* slotsForValue = fImmutableSlotMap.find(immutableValue);
2724 if (!slotsForValue) {
2725 return std::nullopt;
2726 }
2727 slotArray.push_back(slotsForValue);
2728 }
2729
2730 // Look for the group with the fewest number of entries, since that can be searched in the
2731 // least amount of effort.
2732 int leastSlotIndex = 0, leastSlotCount = INT_MAX;
2733 for (int index = 0; index < slotArray.size(); ++index) {
2734 int currentCount = slotArray[index]->count();
2735 if (currentCount < leastSlotCount) {
2736 leastSlotIndex = index;
2737 leastSlotCount = currentCount;
2738 }
2739 }
2740
2741 // See if we can reconstitute the value that we want with any of the data we've already got.
2742 for (int slot : *slotArray[leastSlotIndex]) {
2743 int firstSlot = slot - leastSlotIndex;
2744 bool found = true;
2745 for (int index = 0; index < slotArray.size(); ++index) {
2746 if (!slotArray[index]->contains(firstSlot + index)) {
2747 found = false;
2748 break;
2749 }
2750 }
2751 if (found) {
2752 // We've found an exact match for the input value; return its slot-range.
2753 return SlotRange{firstSlot, slotArray.size()};
2754 }
2755 }
2756
2757 // We didn't find any reusable slot ranges.
2758 return std::nullopt;
2759}
static bool contains(const SkRect &r, SkPoint p)
void reserve_exact(int n)
Definition SkTArray.h:176
V * find(const K &key) const
Definition SkTHash.h:479

◆ finish()

std::unique_ptr< RP::Program > SkSL::RP::Generator::finish ( )

Returns the generated program.

Definition at line 4061 of file SkSLRasterPipelineCodeGenerator.cpp.

4061 {
4062 return fBuilder.finish(fProgramSlots.slotCount(),
4063 fUniformSlots.slotCount(),
4064 fImmutableSlots.slotCount(),
4065 fDebugTrace);
4066}
std::unique_ptr< Program > finish(int numValueSlots, int numUniformSlots, int numImmutableSlots, DebugTracePriv *debugTrace=nullptr)

◆ foldComparisonOp()

void SkSL::RP::Generator::foldComparisonOp ( Operator  op,
int  elements 
)

Definition at line 2328 of file SkSLRasterPipelineCodeGenerator.cpp.

2328 {
2329 switch (op.kind()) {
2330 case OperatorKind::EQEQ:
2331 // equal(x,y) returns a vector; use & to fold into a scalar.
2332 this->foldWithMultiOp(BuilderOp::bitwise_and_n_ints, elements);
2333 break;
2334
2335 case OperatorKind::NEQ:
2336 // notEqual(x,y) returns a vector; use | to fold into a scalar.
2337 this->foldWithMultiOp(BuilderOp::bitwise_or_n_ints, elements);
2338 break;
2339
2340 default:
2341 SkDEBUGFAIL("comparison only allows == and !=");
2342 break;
2343 }
2344}
#define SkDEBUGFAIL(message)
Definition SkAssert.h:118
void foldWithMultiOp(BuilderOp op, int elements)

◆ foldWithMultiOp()

void SkSL::RP::Generator::foldWithMultiOp ( BuilderOp  op,
int  elements 
)

Definition at line 2280 of file SkSLRasterPipelineCodeGenerator.cpp.

2280 {
2281 // Fold the top N elements on the stack using an op that supports multiple slots, e.g.:
2282 // (A + B + C + D) -> add_2_floats $0..1 += $2..3
2283 // add_float $0 += $1
2284 for (; elements >= 8; elements -= 4) {
2285 fBuilder.binary_op(op, /*slots=*/4);
2286 }
2287 for (; elements >= 6; elements -= 3) {
2288 fBuilder.binary_op(op, /*slots=*/3);
2289 }
2290 for (; elements >= 4; elements -= 2) {
2291 fBuilder.binary_op(op, /*slots=*/2);
2292 }
2293 for (; elements >= 2; elements -= 1) {
2294 fBuilder.binary_op(op, /*slots=*/1);
2295 }
2296}

◆ getFunctionDebugInfo()

int SkSL::RP::Generator::getFunctionDebugInfo ( const FunctionDeclaration decl)

Returns the slot index of this function inside the FunctionDebugInfo array in DebugTracePriv. The FunctionDebugInfo slot will be created if it doesn't already exist.

Definition at line 1333 of file SkSLRasterPipelineCodeGenerator.cpp.

1333 {
1334 SkASSERT(fDebugTrace);
1335
1336 std::string name = decl.description();
1337
1338 // When generating the debug trace, we typically mark every function as `noinline`. This makes
1339 // the trace more confusing, since this isn't in the source program, so remove it.
1340 static constexpr std::string_view kNoInline = "noinline ";
1342 name = name.substr(kNoInline.size());
1343 }
1344
1345 // Look for a matching FunctionDebugInfo slot.
1346 for (size_t index = 0; index < fDebugTrace->fFuncInfo.size(); ++index) {
1347 if (fDebugTrace->fFuncInfo[index].name == name) {
1348 return index;
1349 }
1350 }
1351
1352 // We've never called this function before; create a new slot to hold its information.
1353 int slot = (int)fDebugTrace->fFuncInfo.size();
1354 fDebugTrace->fFuncInfo.push_back(FunctionDebugInfo{std::move(name)});
1355 return slot;
1356}
Type::kYUV Type::kRGBA() int(0.7 *637)
std::vector< FunctionDebugInfo > fFuncInfo
const char * name
Definition fuchsia.cc:50
constexpr bool starts_with(std::string_view str, std::string_view prefix)

◆ getFunctionSlots()

SlotRange SkSL::RP::Generator::getFunctionSlots ( const IRNode callSite,
const FunctionDeclaration f 
)
inline

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.

Definition at line 253 of file SkSLRasterPipelineCodeGenerator.cpp.

253 {
254 return fProgramSlots.getFunctionSlots(callSite, f);
255 }
SlotRange getFunctionSlots(const IRNode &callSite, const FunctionDeclaration &f)

◆ getImmutableBitsForSlot()

std::optional< Generator::ImmutableBits > SkSL::RP::Generator::getImmutableBitsForSlot ( const Expression expr,
size_t  slot 
)

Definition at line 2657 of file SkSLRasterPipelineCodeGenerator.cpp.

2658 {
2659 // Determine the constant-value of the slot; bail if it isn't constant.
2660 std::optional<double> v = expr.getConstantValue(slot);
2661 if (!v.has_value()) {
2662 return std::nullopt;
2663 }
2664 // Determine the number-kind of the slot, and convert the value to its bit-representation.
2665 Type::NumberKind kind = expr.type().slotType(slot).numberKind();
2666 double value = *v;
2667 switch (kind) {
2669 return sk_bit_cast<ImmutableBits>((float)value);
2670
2672 return sk_bit_cast<ImmutableBits>((int32_t)value);
2673
2675 return sk_bit_cast<ImmutableBits>((uint32_t)value);
2676
2678 return value ? ~0 : 0;
2679
2680 default:
2681 return std::nullopt;
2682 }
2683}
uint8_t value

◆ getImmutableSlots()

SlotRange SkSL::RP::Generator::getImmutableSlots ( const Variable v)
inline

Looks up the slots associated with an immutable variable; creates the slots if necessary.

Definition at line 235 of file SkSLRasterPipelineCodeGenerator.cpp.

235 {
236 SkASSERT(!IsUniform(v));
237 SkASSERT(fImmutableVariables.contains(&v));
238 return fImmutableSlots.getVariableSlots(v);
239 }
static bool IsUniform(const Variable &var)
SlotRange getVariableSlots(const Variable &v)

◆ getImmutableValueForExpression()

bool SkSL::RP::Generator::getImmutableValueForExpression ( const Expression expr,
TArray< ImmutableBits > *  immutableValues 
)

Definition at line 2685 of file SkSLRasterPipelineCodeGenerator.cpp.

2686 {
2687 if (!expr.supportsConstantValues()) {
2688 return false;
2689 }
2690 size_t numSlots = expr.type().slotCount();
2691 immutableValues->reserve_exact(numSlots);
2692 for (size_t index = 0; index < numSlots; ++index) {
2693 std::optional<ImmutableBits> bits = this->getImmutableBitsForSlot(expr, index);
2694 if (!bits.has_value()) {
2695 return false;
2696 }
2697 immutableValues->push_back(*bits);
2698 }
2699 return true;
2700}
std::optional< ImmutableBits > getImmutableBitsForSlot(const Expression &expr, size_t slot)

◆ GetTypedOp()

BuilderOp SkSL::RP::Generator::GetTypedOp ( const SkSL::Type type,
const TypedOps ops 
)
static

Definition at line 2243 of file SkSLRasterPipelineCodeGenerator.cpp.

2243 {
2244 switch (type.componentType().numberKind()) {
2245 case Type::NumberKind::kFloat: return ops.fFloatOp;
2246 case Type::NumberKind::kSigned: return ops.fSignedOp;
2247 case Type::NumberKind::kUnsigned: return ops.fUnsignedOp;
2248 case Type::NumberKind::kBoolean: return ops.fBooleanOp;
2249 default: return BuilderOp::unsupported;
2250 }
2251}

◆ getTypedOp()

BuilderOp SkSL::RP::Generator::getTypedOp ( const SkSL::Type type,
const TypedOps ops 
) const

◆ getUniformSlots()

SlotRange SkSL::RP::Generator::getUniformSlots ( const Variable v)
inline

Looks up the slots associated with an SkSL uniform; creates the slots if necessary.

Definition at line 242 of file SkSLRasterPipelineCodeGenerator.cpp.

242 {
244 SkASSERT(!fImmutableVariables.contains(&v));
245 return fUniformSlots.getVariableSlots(v);
246 }

◆ getVariableSlots()

SlotRange SkSL::RP::Generator::getVariableSlots ( const Variable v)
inline

Looks up the slots associated with an SkSL variable; creates the slots if necessary.

Definition at line 227 of file SkSLRasterPipelineCodeGenerator.cpp.

227 {
228 SkASSERT(this->hasVariableSlots(v));
229 return fProgramSlots.getVariableSlots(v);
230 }
bool hasVariableSlots(const Variable &v)

◆ hasVariableSlots()

bool SkSL::RP::Generator::hasVariableSlots ( const Variable v)
inline

Returns true for variables with slots in fProgramSlots; immutables or uniforms are false.

Definition at line 222 of file SkSLRasterPipelineCodeGenerator.cpp.

222 {
223 return !IsUniform(v) && !fImmutableVariables.contains(&v);
224 }

◆ IsInoutParameter()

static bool SkSL::RP::Generator::IsInoutParameter ( const Variable var)
inlinestatic

Definition at line 470 of file SkSLRasterPipelineCodeGenerator.cpp.

470 {
471 return (var.modifierFlags() & (ModifierFlag::kIn | ModifierFlag::kOut)) ==
473 }

◆ IsOutParameter()

static bool SkSL::RP::Generator::IsOutParameter ( const Variable var)
inlinestatic

Definition at line 465 of file SkSLRasterPipelineCodeGenerator.cpp.

465 {
466 return (var.modifierFlags() & (ModifierFlag::kIn | ModifierFlag::kOut)) ==
468 }

◆ IsUniform()

static bool SkSL::RP::Generator::IsUniform ( const Variable var)
inlinestatic

Definition at line 461 of file SkSLRasterPipelineCodeGenerator.cpp.

461 {
462 return var.modifierFlags().isUniform();
463 }

◆ makeLValue()

std::unique_ptr< LValue > SkSL::RP::Generator::makeLValue ( const Expression e,
bool  allowScratch = false 
)

Returns an LValue for the passed-in expression; if the expression isn't supported as an LValue, returns nullptr.

Definition at line 1245 of file SkSLRasterPipelineCodeGenerator.cpp.

1245 {
1246 if (e.is<VariableReference>()) {
1247 const Variable* variable = e.as<VariableReference>().variable();
1248 if (fImmutableVariables.contains(variable)) {
1249 return std::make_unique<ImmutableLValue>(variable);
1250 }
1251 return std::make_unique<VariableLValue>(variable);
1252 }
1253 if (e.is<Swizzle>()) {
1254 const Swizzle& swizzleExpr = e.as<Swizzle>();
1255 if (std::unique_ptr<LValue> base = this->makeLValue(*swizzleExpr.base(),
1256 allowScratch)) {
1257 const ComponentArray& components = swizzleExpr.components();
1258 if (is_sliceable_swizzle(components)) {
1259 // If the swizzle is a contiguous subset, we can represent it with a fixed slice.
1260 return std::make_unique<LValueSlice>(std::move(base), components[0],
1261 components.size());
1262 }
1263 return std::make_unique<SwizzleLValue>(std::move(base), components);
1264 }
1265 return nullptr;
1266 }
1267 if (e.is<FieldAccess>()) {
1268 const FieldAccess& fieldExpr = e.as<FieldAccess>();
1269 if (std::unique_ptr<LValue> base = this->makeLValue(*fieldExpr.base(),
1270 allowScratch)) {
1271 // Represent field access with a slice.
1272 return std::make_unique<LValueSlice>(std::move(base), fieldExpr.initialSlot(),
1273 fieldExpr.type().slotCount());
1274 }
1275 return nullptr;
1276 }
1277 if (e.is<IndexExpression>()) {
1278 const IndexExpression& indexExpr = e.as<IndexExpression>();
1279
1280 // If the index base is swizzled (`vec.zyx[idx]`), rewrite it into an equivalent
1281 // non-swizzled form (`vec[uint3(2,1,0)[idx]]`).
1282 if (std::unique_ptr<Expression> rewritten = Transform::RewriteIndexedSwizzle(fContext,
1283 indexExpr)) {
1284 // Convert the rewritten expression into an lvalue.
1285 std::unique_ptr<LValue> lvalue = this->makeLValue(*rewritten, allowScratch);
1286 if (!lvalue) {
1287 return nullptr;
1288 }
1289 // We need to hold onto the rewritten expression for the lifetime of the lvalue.
1290 lvalue->fScratchExpression = std::move(rewritten);
1291 return lvalue;
1292 }
1293 if (std::unique_ptr<LValue> base = this->makeLValue(*indexExpr.base(),
1294 allowScratch)) {
1295 // If the index is a compile-time constant, we can represent it with a fixed slice.
1296 SKSL_INT indexValue;
1297 if (ConstantFolder::GetConstantInt(*indexExpr.index(), &indexValue)) {
1298 int numSlots = indexExpr.type().slotCount();
1299 return std::make_unique<LValueSlice>(std::move(base), numSlots * indexValue,
1300 numSlots);
1301 }
1302
1303 // Represent non-constant indexing via a dynamic index.
1304 auto dynLValue = std::make_unique<DynamicIndexLValue>(std::move(base), indexExpr);
1305 return dynLValue->evaluateDynamicIndices(this) ? std::move(dynLValue)
1306 : nullptr;
1307 }
1308 return nullptr;
1309 }
1310 if (allowScratch) {
1311 // This path allows us to perform field- and index-accesses on an expression as if it were
1312 // an lvalue, but is a temporary and shouldn't be written back to.
1313 return std::make_unique<ScratchLValue>(e);
1314 }
1315 return nullptr;
1316}
int64_t SKSL_INT
Definition SkSLDefines.h:16
static bool GetConstantInt(const Expression &value, SKSL_INT *out)
std::unique_ptr< LValue > makeLValue(const Expression &e, bool allowScratch=false)
static bool is_sliceable_swizzle(SkSpan< const int8_t > components)
std::unique_ptr< Expression > RewriteIndexedSwizzle(const Context &context, const IndexExpression &swizzle)
skia_private::STArray< 4, int8_t > ComponentArray
Definition SkSLDefines.h:24

◆ needsFunctionResultSlots()

bool SkSL::RP::Generator::needsFunctionResultSlots ( const FunctionDefinition func)
inline

Definition at line 456 of file SkSLRasterPipelineCodeGenerator.cpp.

456 {
457 return this->shouldWriteTraceOps() || (this->returnComplexity(func) >
459 }
Analysis::ReturnComplexity returnComplexity(const FunctionDefinition *func)

◆ needsReturnMask()

bool SkSL::RP::Generator::needsReturnMask ( const FunctionDefinition func)
inline

◆ popToSlotRange()

void SkSL::RP::Generator::popToSlotRange ( SlotRange  r)
inline

Pops an expression from the value stack and copies it into slots.

Definition at line 357 of file SkSLRasterPipelineCodeGenerator.cpp.

357 {
358 fBuilder.pop_slots(r);
359 if (this->shouldWriteTraceOps()) {
360 fBuilder.trace_var(fTraceMask->stackID(), r);
361 }
362 }
void trace_var(int traceMaskStackID, SlotRange r)

◆ popToSlotRangeUnmasked()

void SkSL::RP::Generator::popToSlotRangeUnmasked ( SlotRange  r)
inline

Definition at line 363 of file SkSLRasterPipelineCodeGenerator.cpp.

363 {
364 fBuilder.pop_slots_unmasked(r);
365 if (this->shouldWriteTraceOps()) {
366 fBuilder.trace_var(fTraceMask->stackID(), r);
367 }
368 }
void pop_slots_unmasked(SlotRange dst)

◆ push()

bool SkSL::RP::Generator::push ( LValue lvalue)

Pushes the lvalue onto the top-of-stack.

Definition at line 1318 of file SkSLRasterPipelineCodeGenerator.cpp.

1318 {
1319 return lvalue.push(this,
1320 lvalue.fixedSlotRange(this),
1321 lvalue.dynamicSlotRange(),
1322 /*swizzle=*/{});
1323}

◆ pushAbsFloatIntrinsic()

bool SkSL::RP::Generator::pushAbsFloatIntrinsic ( int  slots)

Definition at line 3048 of file SkSLRasterPipelineCodeGenerator.cpp.

3048 {
3049 // Perform abs(float) by masking off the sign bit.
3050 fBuilder.push_constant_u(0x7FFFFFFF, slots);
3051 fBuilder.binary_op(BuilderOp::bitwise_and_n_ints, slots);
3052 return true;
3053}
void push_constant_u(uint32_t val, int count=1)

◆ pushBinaryExpression() [1/2]

bool SkSL::RP::Generator::pushBinaryExpression ( const BinaryExpression e)

Pushes an expression to the value stack.

Definition at line 2416 of file SkSLRasterPipelineCodeGenerator.cpp.

2416 {
2417 return this->pushBinaryExpression(*e.left(), e.getOperator(), *e.right());
2418}
bool pushBinaryExpression(const BinaryExpression &e)

◆ pushBinaryExpression() [2/2]

bool SkSL::RP::Generator::pushBinaryExpression ( const Expression left,
Operator  op,
const Expression right 
)

Definition at line 2420 of file SkSLRasterPipelineCodeGenerator.cpp.

2420 {
2421 switch (op.kind()) {
2422 // Rewrite greater-than ops as their less-than equivalents.
2423 case OperatorKind::GT:
2425
2426 case OperatorKind::GTEQ:
2428
2429 // Handle struct and array comparisons.
2430 case OperatorKind::EQEQ:
2431 case OperatorKind::NEQ:
2432 if (left.type().isStruct() || left.type().isArray()) {
2433 SkASSERT(left.type().matches(right.type()));
2434 std::unique_ptr<LValue> lvLeft = this->makeLValue(left, /*allowScratch=*/true);
2435 std::unique_ptr<LValue> lvRight = this->makeLValue(right, /*allowScratch=*/true);
2436 return this->pushStructuredComparison(lvLeft.get(), op, lvRight.get(), left.type());
2437 }
2438 [[fallthrough]];
2439
2440 // Rewrite commutative ops so that the literal is on the right-hand side. This gives the
2441 // Builder more opportunities to use immediate-mode ops.
2442 case OperatorKind::PLUS:
2443 case OperatorKind::STAR:
2447 double unused;
2450 return this->pushBinaryExpression(right, op, left);
2451 }
2452 break;
2453 }
2454 // Emit comma expressions.
2457 if (!this->pushExpression(left, /*usesResult=*/false)) {
2458 return unsupported();
2459 }
2460 this->discardExpression(left.type().slotCount());
2461 }
2462 return this->pushExpression(right);
2463
2464 default:
2465 break;
2466 }
2467
2468 // Handle binary expressions with mismatched types.
2469 bool vectorizeLeft = false, vectorizeRight = false;
2470 if (!left.type().matches(right.type())) {
2471 if (left.type().componentType().numberKind() != right.type().componentType().numberKind()) {
2472 return unsupported();
2473 }
2474 if (left.type().isScalar() && (right.type().isVector() || right.type().isMatrix())) {
2475 vectorizeLeft = true;
2476 } else if ((left.type().isVector() || left.type().isMatrix()) && right.type().isScalar()) {
2477 vectorizeRight = true;
2478 }
2479 }
2480
2481 const Type& type = vectorizeLeft ? right.type() : left.type();
2482
2483 // If this is an assignment...
2484 std::unique_ptr<LValue> lvalue;
2485 if (op.isAssignment()) {
2486 // ... turn the left side into an lvalue.
2487 lvalue = this->makeLValue(left);
2488 if (!lvalue) {
2489 return unsupported();
2490 }
2491
2492 // Handle simple assignment (`var = expr`).
2493 if (op.kind() == OperatorKind::EQ) {
2494 return this->pushExpression(right) &&
2495 this->store(*lvalue);
2496 }
2497
2498 // Strip off the assignment from the op (turning += into +).
2499 op = op.removeAssignment();
2500 }
2501
2502 // Handle matrix multiplication (MxM/MxV/VxM).
2503 if (op.kind() == OperatorKind::STAR) {
2504 // Matrix * matrix:
2505 if (left.type().isMatrix() && right.type().isMatrix()) {
2506 return this->pushMatrixMultiply(lvalue.get(), left, right,
2507 left.type().columns(), left.type().rows(),
2508 right.type().columns(), right.type().rows());
2509 }
2510
2511 // Vector * matrix:
2512 if (left.type().isVector() && right.type().isMatrix()) {
2513 return this->pushMatrixMultiply(lvalue.get(), left, right,
2514 left.type().columns(), 1,
2515 right.type().columns(), right.type().rows());
2516 }
2517
2518 // Matrix * vector:
2519 if (left.type().isMatrix() && right.type().isVector()) {
2520 return this->pushMatrixMultiply(lvalue.get(), left, right,
2521 left.type().columns(), left.type().rows(),
2522 1, right.type().columns());
2523 }
2524 }
2525
2526 if (!vectorizeLeft && !vectorizeRight && !type.matches(right.type())) {
2527 // We have mismatched types but don't know how to handle them.
2528 return unsupported();
2529 }
2530
2531 // Handle binary ops which require short-circuiting.
2532 switch (op.kind()) {
2535 // If the RHS has side effects, we rewrite `a && b` as `a ? b : false`. This
2536 // generates pretty solid code and gives us the required short-circuit behavior.
2537 SkASSERT(!op.isAssignment());
2538 SkASSERT(type.componentType().isBoolean());
2539 SkASSERT(type.slotCount() == 1); // operator&& only works with scalar types
2540 Literal falseLiteral{Position{}, 0.0, &right.type()};
2541 return this->pushTernaryExpression(left, right, falseLiteral);
2542 }
2543 break;
2544
2547 // If the RHS has side effects, we rewrite `a || b` as `a ? true : b`.
2548 SkASSERT(!op.isAssignment());
2549 SkASSERT(type.componentType().isBoolean());
2550 SkASSERT(type.slotCount() == 1); // operator|| only works with scalar types
2551 Literal trueLiteral{Position{}, 1.0, &right.type()};
2552 return this->pushTernaryExpression(left, trueLiteral, right);
2553 }
2554 break;
2555
2556 default:
2557 break;
2558 }
2559
2560 // Push the left- and right-expressions onto the stack.
2561 if (!this->pushLValueOrExpression(lvalue.get(), left)) {
2562 return unsupported();
2563 }
2564 if (vectorizeLeft) {
2565 fBuilder.push_duplicates(right.type().slotCount() - 1);
2566 }
2567 if (!this->pushExpression(right)) {
2568 return unsupported();
2569 }
2570 if (vectorizeRight) {
2571 fBuilder.push_duplicates(left.type().slotCount() - 1);
2572 }
2573
2574 switch (op.kind()) {
2575 case OperatorKind::PLUS:
2576 if (!this->binaryOp(type, kAddOps)) {
2577 return unsupported();
2578 }
2579 break;
2580
2582 if (!this->binaryOp(type, kSubtractOps)) {
2583 return unsupported();
2584 }
2585 break;
2586
2587 case OperatorKind::STAR:
2588 if (!this->binaryOp(type, kMultiplyOps)) {
2589 return unsupported();
2590 }
2591 break;
2592
2594 if (!this->binaryOp(type, kDivideOps)) {
2595 return unsupported();
2596 }
2597 break;
2598
2599 case OperatorKind::LT:
2600 case OperatorKind::GT:
2601 if (!this->binaryOp(type, kLessThanOps)) {
2602 return unsupported();
2603 }
2604 SkASSERT(type.slotCount() == 1); // operator< only works with scalar types
2605 break;
2606
2607 case OperatorKind::LTEQ:
2608 case OperatorKind::GTEQ:
2609 if (!this->binaryOp(type, kLessThanEqualOps)) {
2610 return unsupported();
2611 }
2612 SkASSERT(type.slotCount() == 1); // operator<= only works with scalar types
2613 break;
2614
2615 case OperatorKind::EQEQ:
2616 if (!this->binaryOp(type, kEqualOps)) {
2617 return unsupported();
2618 }
2619 this->foldComparisonOp(op, type.slotCount());
2620 break;
2621
2622 case OperatorKind::NEQ:
2623 if (!this->binaryOp(type, kNotEqualOps)) {
2624 return unsupported();
2625 }
2626 this->foldComparisonOp(op, type.slotCount());
2627 break;
2628
2631 // For logical-and, we verified above that the RHS does not have side effects, so we
2632 // don't need to worry about short-circuiting side effects.
2633 fBuilder.binary_op(BuilderOp::bitwise_and_n_ints, type.slotCount());
2634 break;
2635
2638 // For logical-or, we verified above that the RHS does not have side effects.
2639 fBuilder.binary_op(BuilderOp::bitwise_or_n_ints, type.slotCount());
2640 break;
2641
2644 // Logical-xor does not short circuit.
2645 fBuilder.binary_op(BuilderOp::bitwise_xor_n_ints, type.slotCount());
2646 break;
2647
2648 default:
2649 return unsupported();
2650 }
2651
2652 // If we have an lvalue, we need to write the result back into it.
2653 return lvalue ? this->store(*lvalue)
2654 : true;
2655}
static bool unused
static bool left(const SkPoint &p0, const SkPoint &p1)
static bool right(const SkPoint &p0, const SkPoint &p1)
static bool GetConstantValue(const Expression &value, double *out)
bool pushTernaryExpression(const TernaryExpression &t)
void foldComparisonOp(Operator op, int elements)
bool binaryOp(const SkSL::Type &type, const TypedOps &ops)
bool pushLValueOrExpression(LValue *lvalue, const Expression &expr)
bool pushMatrixMultiply(LValue *lvalue, const Expression &left, const Expression &right, int leftColumns, int leftRows, int rightColumns, int rightRows)
bool pushStructuredComparison(LValue *left, Operator op, LValue *right, const Type &type)
bool pushExpression(const Expression &e, bool usesResult=true)
bool HasSideEffects(const Expression &expr)

◆ pushChildCall()

bool SkSL::RP::Generator::pushChildCall ( const ChildCall c)

Definition at line 2792 of file SkSLRasterPipelineCodeGenerator.cpp.

2792 {
2793 int* childIdx = fChildEffectMap.find(&c.child());
2794 SkASSERT(childIdx != nullptr);
2795 SkASSERT(!c.arguments().empty());
2796
2797 // All child calls have at least one argument.
2798 const Expression* arg = c.arguments()[0].get();
2799 if (!this->pushExpression(*arg)) {
2800 return unsupported();
2801 }
2802
2803 // Copy arguments from the stack into src/dst as required by this particular child-call.
2804 switch (c.child().type().typeKind()) {
2806 // The argument must be a float2.
2807 SkASSERT(c.arguments().size() == 1);
2808 SkASSERT(arg->type().matches(*fContext.fTypes.fFloat2));
2809
2810 // `exchange_src` will use the top four values on the stack, but we don't care what goes
2811 // into the blue/alpha components. We inject padding here to balance the stack.
2812 fBuilder.pad_stack(2);
2813
2814 // Move the argument into src.rgba while also preserving the execution mask.
2815 fBuilder.exchange_src();
2816 fBuilder.invoke_shader(*childIdx);
2817 break;
2818 }
2820 // The argument must be a half4/float4.
2821 SkASSERT(c.arguments().size() == 1);
2822 SkASSERT(arg->type().matches(*fContext.fTypes.fHalf4) ||
2823 arg->type().matches(*fContext.fTypes.fFloat4));
2824
2825 // Move the argument into src.rgba while also preserving the execution mask.
2826 fBuilder.exchange_src();
2827 fBuilder.invoke_color_filter(*childIdx);
2828 break;
2829 }
2831 // Both arguments must be half4/float4.
2832 SkASSERT(c.arguments().size() == 2);
2833 SkASSERT(c.arguments()[0]->type().matches(*fContext.fTypes.fHalf4) ||
2834 c.arguments()[0]->type().matches(*fContext.fTypes.fFloat4));
2835 SkASSERT(c.arguments()[1]->type().matches(*fContext.fTypes.fHalf4) ||
2836 c.arguments()[1]->type().matches(*fContext.fTypes.fFloat4));
2837
2838 // Move the second argument into dst.rgba, and the first argument into src.rgba, while
2839 // simultaneously preserving the execution mask.
2840 if (!this->pushExpression(*c.arguments()[1])) {
2841 return unsupported();
2842 }
2843 fBuilder.pop_dst_rgba();
2844 fBuilder.exchange_src();
2845 fBuilder.invoke_blender(*childIdx);
2846 break;
2847 }
2848 default: {
2849 SkDEBUGFAILF("cannot sample from type '%s'", c.child().type().description().c_str());
2850 }
2851 }
2852
2853 // The child call has returned the result color via src.rgba, and the SkRP execution mask is
2854 // on top of the stack. Swapping the two puts the result color on top of the stack, and also
2855 // restores our execution masks.
2856 fBuilder.exchange_src();
2857 return true;
2858}
#define SkDEBUGFAILF(fmt,...)
Definition SkAssert.h:119
const std::unique_ptr< Type > fFloat2
const std::unique_ptr< Type > fHalf4
const std::unique_ptr< Type > fFloat4
const BuiltinTypes & fTypes
Definition SkSLContext.h:30
void invoke_color_filter(int childIdx)
void invoke_shader(int childIdx)
void invoke_blender(int childIdx)

◆ pushConstructorCast()

bool SkSL::RP::Generator::pushConstructorCast ( const AnyConstructor c)

Definition at line 2860 of file SkSLRasterPipelineCodeGenerator.cpp.

2860 {
2861 SkASSERT(c.argumentSpan().size() == 1);
2862 const Expression& inner = *c.argumentSpan().front();
2863 SkASSERT(inner.type().slotCount() == c.type().slotCount());
2864
2865 if (!this->pushExpression(inner)) {
2866 return unsupported();
2867 }
2868 const Type::NumberKind innerKind = inner.type().componentType().numberKind();
2869 const Type::NumberKind outerKind = c.type().componentType().numberKind();
2870
2871 if (innerKind == outerKind) {
2872 // Since we ignore type precision, this cast is effectively a no-op.
2873 return true;
2874 }
2875
2876 switch (innerKind) {
2878 if (outerKind == Type::NumberKind::kUnsigned) {
2879 // Treat uint(int) as a no-op.
2880 return true;
2881 }
2882 if (outerKind == Type::NumberKind::kFloat) {
2883 fBuilder.unary_op(BuilderOp::cast_to_float_from_int, c.type().slotCount());
2884 return true;
2885 }
2886 break;
2887
2889 if (outerKind == Type::NumberKind::kSigned) {
2890 // Treat int(uint) as a no-op.
2891 return true;
2892 }
2893 if (outerKind == Type::NumberKind::kFloat) {
2894 fBuilder.unary_op(BuilderOp::cast_to_float_from_uint, c.type().slotCount());
2895 return true;
2896 }
2897 break;
2898
2900 // Converting boolean to int or float can be accomplished via bitwise-and.
2901 if (outerKind == Type::NumberKind::kFloat) {
2902 fBuilder.push_constant_f(1.0f);
2903 } else if (outerKind == Type::NumberKind::kSigned ||
2904 outerKind == Type::NumberKind::kUnsigned) {
2905 fBuilder.push_constant_i(1);
2906 } else {
2907 SkDEBUGFAILF("unexpected cast from bool to %s", c.type().description().c_str());
2908 return unsupported();
2909 }
2910 fBuilder.push_duplicates(c.type().slotCount() - 1);
2911 fBuilder.binary_op(BuilderOp::bitwise_and_n_ints, c.type().slotCount());
2912 return true;
2913
2915 if (outerKind == Type::NumberKind::kSigned) {
2916 fBuilder.unary_op(BuilderOp::cast_to_int_from_float, c.type().slotCount());
2917 return true;
2918 }
2919 if (outerKind == Type::NumberKind::kUnsigned) {
2920 fBuilder.unary_op(BuilderOp::cast_to_uint_from_float, c.type().slotCount());
2921 return true;
2922 }
2923 break;
2924
2926 break;
2927 }
2928
2929 if (outerKind == Type::NumberKind::kBoolean) {
2930 // Converting int or float to boolean can be accomplished via `notEqual(x, 0)`.
2931 fBuilder.push_zeros(c.type().slotCount());
2932 return this->binaryOp(inner.type(), kNotEqualOps);
2933 }
2934
2935 SkDEBUGFAILF("unexpected cast from %s to %s",
2936 c.type().description().c_str(), inner.type().description().c_str());
2937 return unsupported();
2938}
void unary_op(BuilderOp op, int32_t slots)
void push_constant_i(int32_t val, int count=1)

◆ pushConstructorCompound()

bool SkSL::RP::Generator::pushConstructorCompound ( const AnyConstructor c)

Definition at line 2780 of file SkSLRasterPipelineCodeGenerator.cpp.

2780 {
2781 if (c.type().slotCount() > 1 && this->pushImmutableData(c)) {
2782 return true;
2783 }
2784 for (const std::unique_ptr<Expression> &arg : c.argumentSpan()) {
2785 if (!this->pushExpression(*arg)) {
2786 return unsupported();
2787 }
2788 }
2789 return true;
2790}

◆ pushConstructorDiagonalMatrix()

bool SkSL::RP::Generator::pushConstructorDiagonalMatrix ( const ConstructorDiagonalMatrix c)

Definition at line 2940 of file SkSLRasterPipelineCodeGenerator.cpp.

2940 {
2941 if (this->pushImmutableData(c)) {
2942 return true;
2943 }
2944 fBuilder.push_zeros(1);
2945 if (!this->pushExpression(*c.argument())) {
2946 return unsupported();
2947 }
2948 fBuilder.diagonal_matrix(c.type().columns(), c.type().rows());
2949
2950 return true;
2951}
void diagonal_matrix(int columns, int rows)
bool pushImmutableData(const Expression &e)

◆ pushConstructorMatrixResize()

bool SkSL::RP::Generator::pushConstructorMatrixResize ( const ConstructorMatrixResize c)

Definition at line 2953 of file SkSLRasterPipelineCodeGenerator.cpp.

2953 {
2954 if (!this->pushExpression(*c.argument())) {
2955 return unsupported();
2956 }
2957 fBuilder.matrix_resize(c.argument()->type().columns(),
2958 c.argument()->type().rows(),
2959 c.type().columns(),
2960 c.type().rows());
2961 return true;
2962}
void matrix_resize(int origColumns, int origRows, int newColumns, int newRows)

◆ pushConstructorSplat()

bool SkSL::RP::Generator::pushConstructorSplat ( const ConstructorSplat c)

Definition at line 2964 of file SkSLRasterPipelineCodeGenerator.cpp.

2964 {
2965 if (!this->pushExpression(*c.argument())) {
2966 return unsupported();
2967 }
2968 fBuilder.push_duplicates(c.type().slotCount() - 1);
2969 return true;
2970}

◆ pushDynamicallyUniformTernaryExpression()

bool SkSL::RP::Generator::pushDynamicallyUniformTernaryExpression ( const Expression test,
const Expression ifTrue,
const Expression ifFalse 
)

Definition at line 3768 of file SkSLRasterPipelineCodeGenerator.cpp.

3770 {
3772
3773 int falseLabelID = fBuilder.nextLabelID();
3774 int exitLabelID = fBuilder.nextLabelID();
3775
3776 // First, push the test-expression into a separate stack.
3777 AutoStack testStack(this);
3778 testStack.enter();
3779 if (!this->pushExpression(test)) {
3780 return unsupported();
3781 }
3782
3783 // Branch to the true- or false-expression based on the test-expression. We can skip the
3784 // non-true path entirely since the test is known to be uniform.
3785 fBuilder.branch_if_no_active_lanes_on_stack_top_equal(~0, falseLabelID);
3786 testStack.exit();
3787
3788 if (!this->pushExpression(ifTrue)) {
3789 return unsupported();
3790 }
3791
3792 fBuilder.jump(exitLabelID);
3793
3794 // The builder doesn't understand control flow, and assumes that every push moves the stack-top
3795 // forwards. We need to manually balance out the `pushExpression` from the if-true path by
3796 // moving the stack position backwards, so that the if-false path pushes its expression into the
3797 // same as the if-true result.
3798 this->discardExpression(/*slots=*/ifTrue.type().slotCount());
3799
3800 fBuilder.label(falseLabelID);
3801
3802 if (!this->pushExpression(ifFalse)) {
3803 return unsupported();
3804 }
3805
3806 fBuilder.label(exitLabelID);
3807
3808 // Jettison the text-expression from the separate stack.
3809 testStack.enter();
3810 this->discardExpression(/*slots=*/1);
3811 testStack.exit();
3812 return true;
3813}
void branch_if_no_active_lanes_on_stack_top_equal(int value, int labelID)
bool IsDynamicallyUniformExpression(const Expression &expr)

◆ pushExpression()

bool SkSL::RP::Generator::pushExpression ( const Expression e,
bool  usesResult = true 
)

Definition at line 2181 of file SkSLRasterPipelineCodeGenerator.cpp.

2181 {
2182 switch (e.kind()) {
2183 case Expression::Kind::kBinary:
2184 return this->pushBinaryExpression(e.as<BinaryExpression>());
2185
2186 case Expression::Kind::kChildCall:
2187 return this->pushChildCall(e.as<ChildCall>());
2188
2189 case Expression::Kind::kConstructorArray:
2190 case Expression::Kind::kConstructorArrayCast:
2191 case Expression::Kind::kConstructorCompound:
2192 case Expression::Kind::kConstructorStruct:
2193 return this->pushConstructorCompound(e.asAnyConstructor());
2194
2195 case Expression::Kind::kConstructorCompoundCast:
2196 case Expression::Kind::kConstructorScalarCast:
2197 return this->pushConstructorCast(e.asAnyConstructor());
2198
2199 case Expression::Kind::kConstructorDiagonalMatrix:
2200 return this->pushConstructorDiagonalMatrix(e.as<ConstructorDiagonalMatrix>());
2201
2202 case Expression::Kind::kConstructorMatrixResize:
2203 return this->pushConstructorMatrixResize(e.as<ConstructorMatrixResize>());
2204
2205 case Expression::Kind::kConstructorSplat:
2206 return this->pushConstructorSplat(e.as<ConstructorSplat>());
2207
2208 case Expression::Kind::kEmpty:
2209 return true;
2210
2211 case Expression::Kind::kFieldAccess:
2212 return this->pushFieldAccess(e.as<FieldAccess>());
2213
2214 case Expression::Kind::kFunctionCall:
2215 return this->pushFunctionCall(e.as<FunctionCall>());
2216
2217 case Expression::Kind::kIndex:
2218 return this->pushIndexExpression(e.as<IndexExpression>());
2219
2220 case Expression::Kind::kLiteral:
2221 return this->pushLiteral(e.as<Literal>());
2222
2223 case Expression::Kind::kPrefix:
2224 return this->pushPrefixExpression(e.as<PrefixExpression>());
2225
2226 case Expression::Kind::kPostfix:
2227 return this->pushPostfixExpression(e.as<PostfixExpression>(), usesResult);
2228
2229 case Expression::Kind::kSwizzle:
2230 return this->pushSwizzle(e.as<Swizzle>());
2231
2232 case Expression::Kind::kTernary:
2233 return this->pushTernaryExpression(e.as<TernaryExpression>());
2234
2235 case Expression::Kind::kVariableReference:
2236 return this->pushVariableReference(e.as<VariableReference>());
2237
2238 default:
2239 return unsupported();
2240 }
2241}
bool pushConstructorCast(const AnyConstructor &c)
bool pushConstructorDiagonalMatrix(const ConstructorDiagonalMatrix &c)
bool pushFunctionCall(const FunctionCall &c)
bool pushConstructorSplat(const ConstructorSplat &c)
bool pushVariableReference(const VariableReference &v)
bool pushConstructorCompound(const AnyConstructor &c)
bool pushFieldAccess(const FieldAccess &f)
bool pushPostfixExpression(const PostfixExpression &p, bool usesResult)
bool pushIndexExpression(const IndexExpression &i)
bool pushPrefixExpression(const PrefixExpression &p)
bool pushConstructorMatrixResize(const ConstructorMatrixResize &c)

◆ pushFieldAccess()

bool SkSL::RP::Generator::pushFieldAccess ( const FieldAccess f)

Definition at line 2972 of file SkSLRasterPipelineCodeGenerator.cpp.

2972 {
2973 // If possible, get direct field access via the lvalue.
2974 std::unique_ptr<LValue> lvalue = this->makeLValue(f, /*allowScratch=*/true);
2975 return lvalue && this->push(*lvalue);
2976}

◆ pushFunctionCall()

bool SkSL::RP::Generator::pushFunctionCall ( const FunctionCall c)

Definition at line 2978 of file SkSLRasterPipelineCodeGenerator.cpp.

2978 {
2979 if (c.function().isIntrinsic()) {
2980 return this->pushIntrinsic(c);
2981 }
2982
2983 // Keep track of the current function.
2984 const FunctionDefinition* lastFunction = fCurrentFunction;
2985 fCurrentFunction = c.function().definition();
2986
2987 // Skip over the function body entirely if there are no active lanes.
2988 // (If the function call was trivial, it would likely have been inlined in the frontend, so we
2989 // assume here that function calls generally represent a significant amount of work.)
2990 int skipLabelID = fBuilder.nextLabelID();
2991 fBuilder.branch_if_no_lanes_active(skipLabelID);
2992
2993 // Emit the function body.
2994 std::optional<SlotRange> r = this->writeFunction(c, *fCurrentFunction, c.arguments());
2995 if (!r.has_value()) {
2996 return unsupported();
2997 }
2998
2999 // If the function uses result slots, move its result from slots onto the stack.
3000 if (this->needsFunctionResultSlots(fCurrentFunction)) {
3001 fBuilder.push_slots(*r);
3002 }
3003
3004 // We've returned back to the last function.
3005 fCurrentFunction = lastFunction;
3006
3007 // Copy the function result from its slots onto the stack.
3008 fBuilder.label(skipLabelID);
3009 return true;
3010}
void push_slots(SlotRange src)
void branch_if_no_lanes_active(int labelID)
bool pushIntrinsic(const FunctionCall &c)
bool needsFunctionResultSlots(const FunctionDefinition *func)
std::optional< SlotRange > writeFunction(const IRNode &callSite, const FunctionDefinition &function, SkSpan< std::unique_ptr< Expression > const > arguments)

◆ pushImmutableData()

bool SkSL::RP::Generator::pushImmutableData ( const Expression e)

Definition at line 2761 of file SkSLRasterPipelineCodeGenerator.cpp.

2761 {
2762 STArray<16, ImmutableBits> immutableValues;
2763 if (!this->getImmutableValueForExpression(e, &immutableValues)) {
2764 return false;
2765 }
2766 std::optional<SlotRange> preexistingData = this->findPreexistingImmutableData(immutableValues);
2767 if (preexistingData.has_value()) {
2768 fBuilder.push_immutable(*preexistingData);
2769 return true;
2770 }
2771 SlotRange range = fImmutableSlots.createSlots(e.description(),
2772 e.type(),
2773 e.fPosition,
2774 /*isFunctionReturnValue=*/false);
2775 this->storeImmutableValueToSlots(immutableValues, range);
2776 fBuilder.push_immutable(range);
2777 return true;
2778}
void push_immutable(SlotRange src)
bool getImmutableValueForExpression(const Expression &expr, TArray< ImmutableBits > *immutableValues)
std::optional< SlotRange > findPreexistingImmutableData(const TArray< ImmutableBits > &immutableValues)
void storeImmutableValueToSlots(const TArray< ImmutableBits > &immutableValues, SlotRange slots)
SlotRange createSlots(std::string name, const Type &type, Position pos, bool isFunctionReturnValue)

◆ pushIndexExpression()

bool SkSL::RP::Generator::pushIndexExpression ( const IndexExpression i)

Definition at line 3012 of file SkSLRasterPipelineCodeGenerator.cpp.

3012 {
3013 std::unique_ptr<LValue> lvalue = this->makeLValue(i, /*allowScratch=*/true);
3014 return lvalue && this->push(*lvalue);
3015}

◆ pushIntrinsic() [1/8]

bool SkSL::RP::Generator::pushIntrinsic ( BuilderOp  builderOp,
const Expression arg0 
)

Definition at line 3073 of file SkSLRasterPipelineCodeGenerator.cpp.

3073 {
3074 if (!this->pushExpression(arg0)) {
3075 return unsupported();
3076 }
3077 fBuilder.unary_op(builderOp, arg0.type().slotCount());
3078 return true;
3079}

◆ pushIntrinsic() [2/8]

bool SkSL::RP::Generator::pushIntrinsic ( BuilderOp  builderOp,
const Expression arg0,
const Expression arg1 
)

Definition at line 3321 of file SkSLRasterPipelineCodeGenerator.cpp.

3321 {
3322 if (!this->pushExpression(arg0) || !this->pushVectorizedExpression(arg1, arg0.type())) {
3323 return unsupported();
3324 }
3325 fBuilder.binary_op(builderOp, arg0.type().slotCount());
3326 return true;
3327}
bool pushVectorizedExpression(const Expression &expr, const Type &vectorType)

◆ pushIntrinsic() [3/8]

bool SkSL::RP::Generator::pushIntrinsic ( const FunctionCall c)

Definition at line 3017 of file SkSLRasterPipelineCodeGenerator.cpp.

3017 {
3018 const ExpressionArray& args = c.arguments();
3019 switch (args.size()) {
3020 case 1:
3021 return this->pushIntrinsic(c.function().intrinsicKind(), *args[0]);
3022
3023 case 2:
3024 return this->pushIntrinsic(c.function().intrinsicKind(), *args[0], *args[1]);
3025
3026 case 3:
3027 return this->pushIntrinsic(c.function().intrinsicKind(), *args[0], *args[1], *args[2]);
3028
3029 default:
3030 break;
3031 }
3032
3033 return unsupported();
3034}
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args

◆ pushIntrinsic() [4/8]

bool SkSL::RP::Generator::pushIntrinsic ( const TypedOps ops,
const Expression arg0 
)

Definition at line 3066 of file SkSLRasterPipelineCodeGenerator.cpp.

3066 {
3067 if (!this->pushExpression(arg0)) {
3068 return unsupported();
3069 }
3070 return this->unaryOp(arg0.type(), ops);
3071}
bool unaryOp(const SkSL::Type &type, const TypedOps &ops)

◆ pushIntrinsic() [5/8]

bool SkSL::RP::Generator::pushIntrinsic ( const TypedOps ops,
const Expression arg0,
const Expression arg1 
)

Definition at line 3314 of file SkSLRasterPipelineCodeGenerator.cpp.

3314 {
3315 if (!this->pushExpression(arg0) || !this->pushVectorizedExpression(arg1, arg0.type())) {
3316 return unsupported();
3317 }
3318 return this->binaryOp(arg0.type(), ops);
3319}

◆ pushIntrinsic() [6/8]

bool SkSL::RP::Generator::pushIntrinsic ( IntrinsicKind  intrinsic,
const Expression arg0 
)

Definition at line 3081 of file SkSLRasterPipelineCodeGenerator.cpp.

3081 {
3082 switch (intrinsic) {
3083 case IntrinsicKind::k_abs_IntrinsicKind:
3084 if (arg0.type().componentType().isFloat()) {
3085 // Perform abs(float) by masking off the sign bit.
3086 if (!this->pushExpression(arg0)) {
3087 return unsupported();
3088 }
3089 return this->pushAbsFloatIntrinsic(arg0.type().slotCount());
3090 }
3091 // We have a dedicated op for abs(int).
3092 return this->pushIntrinsic(BuilderOp::abs_int, arg0);
3093
3094 case IntrinsicKind::k_any_IntrinsicKind:
3095 if (!this->pushExpression(arg0)) {
3096 return unsupported();
3097 }
3098 this->foldWithMultiOp(BuilderOp::bitwise_or_n_ints, arg0.type().slotCount());
3099 return true;
3100
3101 case IntrinsicKind::k_all_IntrinsicKind:
3102 if (!this->pushExpression(arg0)) {
3103 return unsupported();
3104 }
3105 this->foldWithMultiOp(BuilderOp::bitwise_and_n_ints, arg0.type().slotCount());
3106 return true;
3107
3108 case IntrinsicKind::k_acos_IntrinsicKind:
3109 return this->pushIntrinsic(BuilderOp::acos_float, arg0);
3110
3111 case IntrinsicKind::k_asin_IntrinsicKind:
3112 return this->pushIntrinsic(BuilderOp::asin_float, arg0);
3113
3114 case IntrinsicKind::k_atan_IntrinsicKind:
3115 return this->pushIntrinsic(BuilderOp::atan_float, arg0);
3116
3117 case IntrinsicKind::k_ceil_IntrinsicKind:
3118 return this->pushIntrinsic(BuilderOp::ceil_float, arg0);
3119
3120 case IntrinsicKind::k_cos_IntrinsicKind:
3121 return this->pushIntrinsic(BuilderOp::cos_float, arg0);
3122
3123 case IntrinsicKind::k_degrees_IntrinsicKind: {
3124 Literal lit180OverPi{Position{}, 57.2957795131f, &arg0.type().componentType()};
3125 return this->pushBinaryExpression(arg0, OperatorKind::STAR, lit180OverPi);
3126 }
3127 case IntrinsicKind::k_floatBitsToInt_IntrinsicKind:
3128 case IntrinsicKind::k_floatBitsToUint_IntrinsicKind:
3129 case IntrinsicKind::k_intBitsToFloat_IntrinsicKind:
3130 case IntrinsicKind::k_uintBitsToFloat_IntrinsicKind:
3131 return this->pushExpression(arg0);
3132
3133 case IntrinsicKind::k_exp_IntrinsicKind:
3134 return this->pushIntrinsic(BuilderOp::exp_float, arg0);
3135
3136 case IntrinsicKind::k_exp2_IntrinsicKind:
3137 return this->pushIntrinsic(BuilderOp::exp2_float, arg0);
3138
3139 case IntrinsicKind::k_floor_IntrinsicKind:
3140 return this->pushIntrinsic(BuilderOp::floor_float, arg0);
3141
3142 case IntrinsicKind::k_fract_IntrinsicKind:
3143 // Implement fract as `x - floor(x)`.
3144 if (!this->pushExpression(arg0)) {
3145 return unsupported();
3146 }
3147 fBuilder.push_clone(arg0.type().slotCount());
3148 fBuilder.unary_op(BuilderOp::floor_float, arg0.type().slotCount());
3149 return this->binaryOp(arg0.type(), kSubtractOps);
3150
3151 case IntrinsicKind::k_inverse_IntrinsicKind:
3152 SkASSERT(arg0.type().isMatrix());
3153 SkASSERT(arg0.type().rows() == arg0.type().columns());
3154 if (!this->pushExpression(arg0)) {
3155 return unsupported();
3156 }
3157 fBuilder.inverse_matrix(arg0.type().rows());
3158 return true;
3159
3160 case IntrinsicKind::k_inversesqrt_IntrinsicKind:
3161 return this->pushIntrinsic(kInverseSqrtOps, arg0);
3162
3163 case IntrinsicKind::k_length_IntrinsicKind:
3164 return this->pushExpression(arg0) &&
3165 this->pushLengthIntrinsic(arg0.type().slotCount());
3166
3167 case IntrinsicKind::k_log_IntrinsicKind:
3168 if (!this->pushExpression(arg0)) {
3169 return unsupported();
3170 }
3171 fBuilder.unary_op(BuilderOp::log_float, arg0.type().slotCount());
3172 return true;
3173
3174 case IntrinsicKind::k_log2_IntrinsicKind:
3175 if (!this->pushExpression(arg0)) {
3176 return unsupported();
3177 }
3178 fBuilder.unary_op(BuilderOp::log2_float, arg0.type().slotCount());
3179 return true;
3180
3181 case IntrinsicKind::k_normalize_IntrinsicKind: {
3182 // Implement normalize as `x / length(x)`. First, push the expression.
3183 if (!this->pushExpression(arg0)) {
3184 return unsupported();
3185 }
3186 int slotCount = arg0.type().slotCount();
3187 if (slotCount > 1) {
3188#if defined(SK_USE_RSQRT_IN_RP_NORMALIZE)
3189 // Instead of `x / sqrt(dot(x, x))`, we can get roughly the same result in less time
3190 // by computing `x * invsqrt(dot(x, x))`.
3191 fBuilder.push_clone(slotCount);
3192 fBuilder.push_clone(slotCount);
3193 fBuilder.dot_floats(slotCount);
3194
3195 // Compute `vec(inversesqrt(dot(x, x)))`.
3196 fBuilder.unary_op(BuilderOp::invsqrt_float, 1);
3197 fBuilder.push_duplicates(slotCount - 1);
3198
3199 // Return `x * vec(inversesqrt(dot(x, x)))`.
3200 return this->binaryOp(arg0.type(), kMultiplyOps);
3201#else
3202 // TODO: We can get roughly the same result in less time by using `invsqrt`, but
3203 // that leads to more variance across architectures, which Chromium layout tests do
3204 // not handle nicely.
3205 fBuilder.push_clone(slotCount);
3206 fBuilder.push_clone(slotCount);
3207 fBuilder.dot_floats(slotCount);
3208
3209 // Compute `vec(sqrt(dot(x, x)))`.
3210 fBuilder.unary_op(BuilderOp::sqrt_float, 1);
3211 fBuilder.push_duplicates(slotCount - 1);
3212
3213 // Return `x / vec(sqrt(dot(x, x)))`.
3214 return this->binaryOp(arg0.type(), kDivideOps);
3215#endif
3216 } else {
3217 // For single-slot normalization, we can simplify `sqrt(x * x)` into `abs(x)`.
3218 fBuilder.push_clone(slotCount);
3219 return this->pushAbsFloatIntrinsic(/*slots=*/1) &&
3220 this->binaryOp(arg0.type(), kDivideOps);
3221 }
3222 }
3223 case IntrinsicKind::k_not_IntrinsicKind:
3225
3226 case IntrinsicKind::k_radians_IntrinsicKind: {
3227 Literal litPiOver180{Position{}, 0.01745329251f, &arg0.type().componentType()};
3228 return this->pushBinaryExpression(arg0, OperatorKind::STAR, litPiOver180);
3229 }
3230 case IntrinsicKind::k_saturate_IntrinsicKind: {
3231 // Implement saturate as clamp(arg, 0, 1).
3232 Literal zeroLiteral{Position{}, 0.0, &arg0.type().componentType()};
3233 Literal oneLiteral{Position{}, 1.0, &arg0.type().componentType()};
3234 return this->pushIntrinsic(k_clamp_IntrinsicKind, arg0, zeroLiteral, oneLiteral);
3235 }
3236 case IntrinsicKind::k_sign_IntrinsicKind: {
3237 // Implement floating-point sign() as `clamp(arg * FLT_MAX, -1, 1)`.
3238 // FLT_MIN * FLT_MAX evaluates to 4, so multiplying any float value against FLT_MAX is
3239 // sufficient to ensure that |value| is always 1 or greater (excluding zero and nan).
3240 // Integer sign() doesn't need to worry about fractional values or nans, and can simply
3241 // be `clamp(arg, -1, 1)`.
3242 if (!this->pushExpression(arg0)) {
3243 return unsupported();
3244 }
3245 if (arg0.type().componentType().isFloat()) {
3246 Literal fltMaxLiteral{Position{}, FLT_MAX, &arg0.type().componentType()};
3247 if (!this->pushVectorizedExpression(fltMaxLiteral, arg0.type())) {
3248 return unsupported();
3249 }
3250 if (!this->binaryOp(arg0.type(), kMultiplyOps)) {
3251 return unsupported();
3252 }
3253 }
3254 Literal neg1Literal{Position{}, -1.0, &arg0.type().componentType()};
3255 if (!this->pushVectorizedExpression(neg1Literal, arg0.type())) {
3256 return unsupported();
3257 }
3258 if (!this->binaryOp(arg0.type(), kMaxOps)) {
3259 return unsupported();
3260 }
3261 Literal pos1Literal{Position{}, 1.0, &arg0.type().componentType()};
3262 if (!this->pushVectorizedExpression(pos1Literal, arg0.type())) {
3263 return unsupported();
3264 }
3265 return this->binaryOp(arg0.type(), kMinOps);
3266 }
3267 case IntrinsicKind::k_sin_IntrinsicKind:
3268 return this->pushIntrinsic(BuilderOp::sin_float, arg0);
3269
3270 case IntrinsicKind::k_sqrt_IntrinsicKind:
3271 return this->pushIntrinsic(BuilderOp::sqrt_float, arg0);
3272
3273 case IntrinsicKind::k_tan_IntrinsicKind:
3274 return this->pushIntrinsic(BuilderOp::tan_float, arg0);
3275
3276 case IntrinsicKind::k_transpose_IntrinsicKind:
3277 SkASSERT(arg0.type().isMatrix());
3278 if (!this->pushExpression(arg0)) {
3279 return unsupported();
3280 }
3281 fBuilder.transpose(arg0.type().columns(), arg0.type().rows());
3282 return true;
3283
3284 case IntrinsicKind::k_trunc_IntrinsicKind:
3285 // Implement trunc as `float(int(x))`, since float-to-int rounds toward zero.
3286 if (!this->pushExpression(arg0)) {
3287 return unsupported();
3288 }
3289 fBuilder.unary_op(BuilderOp::cast_to_int_from_float, arg0.type().slotCount());
3290 fBuilder.unary_op(BuilderOp::cast_to_float_from_int, arg0.type().slotCount());
3291 return true;
3292
3293 case IntrinsicKind::k_fromLinearSrgb_IntrinsicKind:
3294 case IntrinsicKind::k_toLinearSrgb_IntrinsicKind:
3295 // The argument must be a half3.
3296 SkASSERT(arg0.type().matches(*fContext.fTypes.fHalf3));
3297 if (!this->pushExpression(arg0)) {
3298 return unsupported();
3299 }
3300
3301 if (intrinsic == IntrinsicKind::k_fromLinearSrgb_IntrinsicKind) {
3302 fBuilder.invoke_from_linear_srgb();
3303 } else {
3304 fBuilder.invoke_to_linear_srgb();
3305 }
3306 return true;
3307
3308 default:
3309 break;
3310 }
3311 return unsupported();
3312}
const std::unique_ptr< Type > fHalf3
void push_clone(int numSlots, int offsetFromStackTop=0)
void transpose(int columns, int rows)

◆ pushIntrinsic() [7/8]

bool SkSL::RP::Generator::pushIntrinsic ( IntrinsicKind  intrinsic,
const Expression arg0,
const Expression arg1 
)

Definition at line 3329 of file SkSLRasterPipelineCodeGenerator.cpp.

3331 {
3332 switch (intrinsic) {
3333 case IntrinsicKind::k_atan_IntrinsicKind:
3334 return this->pushIntrinsic(BuilderOp::atan2_n_floats, arg0, arg1);
3335
3336 case IntrinsicKind::k_cross_IntrinsicKind: {
3337 // Implement cross as `arg0.yzx * arg1.zxy - arg0.zxy * arg1.yzx`. We use two stacks so
3338 // that each subexpression can be multiplied separately.
3339 SkASSERT(arg0.type().matches(arg1.type()));
3340 SkASSERT(arg0.type().slotCount() == 3);
3341 SkASSERT(arg1.type().slotCount() == 3);
3342
3343 // Push `arg0.yzx` onto this stack and `arg0.zxy` onto a separate subexpression stack.
3344 AutoStack subexpressionStack(this);
3345 subexpressionStack.enter();
3346 if (!this->pushExpression(arg0)) {
3347 return unsupported();
3348 }
3349 subexpressionStack.exit();
3350 subexpressionStack.pushClone(/*slots=*/3);
3351
3352 fBuilder.swizzle(/*consumedSlots=*/3, {1, 2, 0});
3353 subexpressionStack.enter();
3354 fBuilder.swizzle(/*consumedSlots=*/3, {2, 0, 1});
3355 subexpressionStack.exit();
3356
3357 // Push `arg1.zxy` onto this stack and `arg1.yzx` onto the next stack. Perform the
3358 // multiply on each subexpression (`arg0.yzx * arg1.zxy` on the first stack, and
3359 // `arg0.zxy * arg1.yzx` on the next).
3360 subexpressionStack.enter();
3361 if (!this->pushExpression(arg1)) {
3362 return unsupported();
3363 }
3364 subexpressionStack.exit();
3365 subexpressionStack.pushClone(/*slots=*/3);
3366
3367 fBuilder.swizzle(/*consumedSlots=*/3, {2, 0, 1});
3368 fBuilder.binary_op(BuilderOp::mul_n_floats, 3);
3369
3370 subexpressionStack.enter();
3371 fBuilder.swizzle(/*consumedSlots=*/3, {1, 2, 0});
3372 fBuilder.binary_op(BuilderOp::mul_n_floats, 3);
3373 subexpressionStack.exit();
3374
3375 // Migrate the result of the second subexpression (`arg0.zxy * arg1.yzx`) back onto the
3376 // main stack and subtract it from the first subexpression (`arg0.yzx * arg1.zxy`).
3377 subexpressionStack.pushClone(/*slots=*/3);
3378 fBuilder.binary_op(BuilderOp::sub_n_floats, 3);
3379
3380 // Now that the calculation is complete, discard the subexpression on the next stack.
3381 subexpressionStack.enter();
3382 this->discardExpression(/*slots=*/3);
3383 subexpressionStack.exit();
3384 return true;
3385 }
3386 case IntrinsicKind::k_distance_IntrinsicKind:
3387 // Implement distance as `length(a - b)`.
3388 SkASSERT(arg0.type().slotCount() == arg1.type().slotCount());
3389 return this->pushBinaryExpression(arg0, OperatorKind::MINUS, arg1) &&
3390 this->pushLengthIntrinsic(arg0.type().slotCount());
3391
3392 case IntrinsicKind::k_dot_IntrinsicKind:
3393 SkASSERT(arg0.type().matches(arg1.type()));
3394 if (!this->pushExpression(arg0) || !this->pushExpression(arg1)) {
3395 return unsupported();
3396 }
3397 fBuilder.dot_floats(arg0.type().slotCount());
3398 return true;
3399
3400 case IntrinsicKind::k_equal_IntrinsicKind:
3401 SkASSERT(arg0.type().matches(arg1.type()));
3402 return this->pushIntrinsic(kEqualOps, arg0, arg1);
3403
3404 case IntrinsicKind::k_notEqual_IntrinsicKind:
3405 SkASSERT(arg0.type().matches(arg1.type()));
3406 return this->pushIntrinsic(kNotEqualOps, arg0, arg1);
3407
3408 case IntrinsicKind::k_lessThan_IntrinsicKind:
3409 SkASSERT(arg0.type().matches(arg1.type()));
3410 return this->pushIntrinsic(kLessThanOps, arg0, arg1);
3411
3412 case IntrinsicKind::k_greaterThan_IntrinsicKind:
3413 SkASSERT(arg0.type().matches(arg1.type()));
3414 return this->pushIntrinsic(kLessThanOps, arg1, arg0);
3415
3416 case IntrinsicKind::k_lessThanEqual_IntrinsicKind:
3417 SkASSERT(arg0.type().matches(arg1.type()));
3418 return this->pushIntrinsic(kLessThanEqualOps, arg0, arg1);
3419
3420 case IntrinsicKind::k_greaterThanEqual_IntrinsicKind:
3421 SkASSERT(arg0.type().matches(arg1.type()));
3422 return this->pushIntrinsic(kLessThanEqualOps, arg1, arg0);
3423
3424 case IntrinsicKind::k_min_IntrinsicKind:
3425 SkASSERT(arg0.type().componentType().matches(arg1.type().componentType()));
3426 return this->pushIntrinsic(kMinOps, arg0, arg1);
3427
3428 case IntrinsicKind::k_matrixCompMult_IntrinsicKind:
3429 SkASSERT(arg0.type().matches(arg1.type()));
3430 return this->pushIntrinsic(kMultiplyOps, arg0, arg1);
3431
3432 case IntrinsicKind::k_max_IntrinsicKind:
3433 SkASSERT(arg0.type().componentType().matches(arg1.type().componentType()));
3434 return this->pushIntrinsic(kMaxOps, arg0, arg1);
3435
3436 case IntrinsicKind::k_mod_IntrinsicKind:
3437 SkASSERT(arg0.type().componentType().matches(arg1.type().componentType()));
3438 return this->pushIntrinsic(kModOps, arg0, arg1);
3439
3440 case IntrinsicKind::k_pow_IntrinsicKind:
3441 SkASSERT(arg0.type().matches(arg1.type()));
3442 return this->pushIntrinsic(BuilderOp::pow_n_floats, arg0, arg1);
3443
3444 case IntrinsicKind::k_reflect_IntrinsicKind: {
3445 // Implement reflect as `I - (N * dot(I,N) * 2)`.
3446 SkASSERT(arg0.type().matches(arg1.type()));
3447 SkASSERT(arg0.type().slotCount() == arg1.type().slotCount());
3448 SkASSERT(arg0.type().componentType().isFloat());
3449 int slotCount = arg0.type().slotCount();
3450
3451 // Stack: I, N.
3452 if (!this->pushExpression(arg0) || !this->pushExpression(arg1)) {
3453 return unsupported();
3454 }
3455 // Stack: I, N, I, N.
3456 fBuilder.push_clone(2 * slotCount);
3457 // Stack: I, N, dot(I,N)
3458 fBuilder.dot_floats(slotCount);
3459 // Stack: I, N, dot(I,N), 2
3460 fBuilder.push_constant_f(2.0);
3461 // Stack: I, N, dot(I,N) * 2
3462 fBuilder.binary_op(BuilderOp::mul_n_floats, 1);
3463 // Stack: I, N * dot(I,N) * 2
3464 fBuilder.push_duplicates(slotCount - 1);
3465 fBuilder.binary_op(BuilderOp::mul_n_floats, slotCount);
3466 // Stack: I - (N * dot(I,N) * 2)
3467 fBuilder.binary_op(BuilderOp::sub_n_floats, slotCount);
3468 return true;
3469 }
3470 case IntrinsicKind::k_step_IntrinsicKind: {
3471 // Compute step as `float(lessThanEqual(edge, x))`. We convert from boolean 0/~0 to
3472 // floating point zero/one by using a bitwise-and against the bit-pattern of 1.0.
3473 SkASSERT(arg0.type().componentType().matches(arg1.type().componentType()));
3474 if (!this->pushVectorizedExpression(arg0, arg1.type()) || !this->pushExpression(arg1)) {
3475 return unsupported();
3476 }
3477 if (!this->binaryOp(arg1.type(), kLessThanEqualOps)) {
3478 return unsupported();
3479 }
3480 Literal pos1Literal{Position{}, 1.0, &arg1.type().componentType()};
3481 if (!this->pushVectorizedExpression(pos1Literal, arg1.type())) {
3482 return unsupported();
3483 }
3484 fBuilder.binary_op(BuilderOp::bitwise_and_n_ints, arg1.type().slotCount());
3485 return true;
3486 }
3487
3488 default:
3489 break;
3490 }
3491 return unsupported();
3492}
void swizzle(int consumedSlots, SkSpan< const int8_t > components)

◆ pushIntrinsic() [8/8]

bool SkSL::RP::Generator::pushIntrinsic ( IntrinsicKind  intrinsic,
const Expression arg0,
const Expression arg1,
const Expression arg2 
)

Definition at line 3494 of file SkSLRasterPipelineCodeGenerator.cpp.

3497 {
3498 switch (intrinsic) {
3499 case IntrinsicKind::k_clamp_IntrinsicKind:
3500 // Implement clamp as min(max(arg, low), high).
3501 SkASSERT(arg0.type().componentType().matches(arg1.type().componentType()));
3502 SkASSERT(arg0.type().componentType().matches(arg2.type().componentType()));
3503 if (!this->pushExpression(arg0) || !this->pushVectorizedExpression(arg1, arg0.type())) {
3504 return unsupported();
3505 }
3506 if (!this->binaryOp(arg0.type(), kMaxOps)) {
3507 return unsupported();
3508 }
3509 if (!this->pushVectorizedExpression(arg2, arg0.type())) {
3510 return unsupported();
3511 }
3512 if (!this->binaryOp(arg0.type(), kMinOps)) {
3513 return unsupported();
3514 }
3515 return true;
3516
3517 case IntrinsicKind::k_faceforward_IntrinsicKind: {
3518 // Implement faceforward as `N ^ ((0 <= dot(I, NRef)) & 0x80000000)`.
3519 // In other words, flip the sign bit of N if `0 <= dot(I, NRef)`.
3520 SkASSERT(arg0.type().matches(arg1.type()));
3521 SkASSERT(arg0.type().matches(arg2.type()));
3522 int slotCount = arg0.type().slotCount();
3523
3524 // Stack: N, 0, I, Nref
3525 if (!this->pushExpression(arg0)) {
3526 return unsupported();
3527 }
3528 fBuilder.push_constant_f(0.0);
3529 if (!this->pushExpression(arg1) || !this->pushExpression(arg2)) {
3530 return unsupported();
3531 }
3532 // Stack: N, 0, dot(I,NRef)
3533 fBuilder.dot_floats(slotCount);
3534 // Stack: N, (0 <= dot(I,NRef))
3535 fBuilder.binary_op(BuilderOp::cmple_n_floats, 1);
3536 // Stack: N, (0 <= dot(I,NRef)), 0x80000000
3537 fBuilder.push_constant_u(0x80000000);
3538 // Stack: N, (0 <= dot(I,NRef)) & 0x80000000)
3539 fBuilder.binary_op(BuilderOp::bitwise_and_n_ints, 1);
3540 // Stack: N, vec(0 <= dot(I,NRef)) & 0x80000000)
3541 fBuilder.push_duplicates(slotCount - 1);
3542 // Stack: N ^ vec((0 <= dot(I,NRef)) & 0x80000000)
3543 fBuilder.binary_op(BuilderOp::bitwise_xor_n_ints, slotCount);
3544 return true;
3545 }
3546 case IntrinsicKind::k_mix_IntrinsicKind:
3547 // Note: our SkRP mix op takes the interpolation point first, not the interpolants.
3548 SkASSERT(arg0.type().matches(arg1.type()));
3549 if (arg2.type().componentType().isFloat()) {
3550 SkASSERT(arg0.type().componentType().matches(arg2.type().componentType()));
3551 if (!this->pushVectorizedExpression(arg2, arg0.type())) {
3552 return unsupported();
3553 }
3554 if (!this->pushExpression(arg0) || !this->pushExpression(arg1)) {
3555 return unsupported();
3556 }
3557 return this->ternaryOp(arg0.type(), kMixOps);
3558 }
3559 if (arg2.type().componentType().isBoolean()) {
3560 if (!this->pushExpression(arg2)) {
3561 return unsupported();
3562 }
3563 if (!this->pushExpression(arg0) || !this->pushExpression(arg1)) {
3564 return unsupported();
3565 }
3566 // The `mix_int` op isn't doing a lerp; it uses the third argument to select values
3567 // from the first and second arguments. It's safe for use with any type in arguments
3568 // 0 and 1.
3569 fBuilder.ternary_op(BuilderOp::mix_n_ints, arg0.type().slotCount());
3570 return true;
3571 }
3572 return unsupported();
3573
3574 case IntrinsicKind::k_refract_IntrinsicKind: {
3575 // We always calculate refraction using vec4s, so we pad out unused N/I slots with zero.
3576 int padding = 4 - arg0.type().slotCount();
3577 if (!this->pushExpression(arg0)) {
3578 return unsupported();
3579 }
3580 fBuilder.push_zeros(padding);
3581
3582 if (!this->pushExpression(arg1)) {
3583 return unsupported();
3584 }
3585 fBuilder.push_zeros(padding);
3586
3587 // eta is always a scalar and doesn't need padding.
3588 if (!this->pushExpression(arg2)) {
3589 return unsupported();
3590 }
3591 fBuilder.refract_floats();
3592
3593 // The result vector was returned as a vec4, so discard the extra columns.
3594 fBuilder.discard_stack(padding);
3595 return true;
3596 }
3597 case IntrinsicKind::k_smoothstep_IntrinsicKind:
3598 SkASSERT(arg0.type().componentType().isFloat());
3599 SkASSERT(arg1.type().matches(arg0.type()));
3600 SkASSERT(arg2.type().componentType().isFloat());
3601
3602 if (!this->pushVectorizedExpression(arg0, arg2.type()) ||
3603 !this->pushVectorizedExpression(arg1, arg2.type()) ||
3604 !this->pushExpression(arg2)) {
3605 return unsupported();
3606 }
3607 fBuilder.ternary_op(BuilderOp::smoothstep_n_floats, arg2.type().slotCount());
3608 return true;
3609
3610 default:
3611 break;
3612 }
3613 return unsupported();
3614}
void ternary_op(BuilderOp op, int32_t slots)
bool ternaryOp(const SkSL::Type &type, const TypedOps &ops)

◆ pushLengthIntrinsic()

bool SkSL::RP::Generator::pushLengthIntrinsic ( int  slotCount)

Definition at line 3036 of file SkSLRasterPipelineCodeGenerator.cpp.

3036 {
3037 if (slotCount == 1) {
3038 // `length(scalar)` is `sqrt(x^2)`, which is equivalent to `abs(x)`.
3039 return this->pushAbsFloatIntrinsic(/*slots=*/1);
3040 }
3041 // Implement `length(vec)` as `sqrt(dot(x, x))`.
3042 fBuilder.push_clone(slotCount);
3043 fBuilder.dot_floats(slotCount);
3044 fBuilder.unary_op(BuilderOp::sqrt_float, 1);
3045 return true;
3046}

◆ pushLiteral()

bool SkSL::RP::Generator::pushLiteral ( const Literal l)

Definition at line 3616 of file SkSLRasterPipelineCodeGenerator.cpp.

3616 {
3617 switch (l.type().numberKind()) {
3619 fBuilder.push_constant_f(l.floatValue());
3620 return true;
3621
3623 fBuilder.push_constant_i(l.intValue());
3624 return true;
3625
3627 fBuilder.push_constant_u(l.intValue());
3628 return true;
3629
3631 fBuilder.push_constant_i(l.boolValue() ? ~0 : 0);
3632 return true;
3633
3634 default:
3636 }
3637}
#define SkUNREACHABLE
Definition SkAssert.h:135

◆ pushLValueOrExpression()

bool SkSL::RP::Generator::pushLValueOrExpression ( LValue lvalue,
const Expression expr 
)

Definition at line 2298 of file SkSLRasterPipelineCodeGenerator.cpp.

2298 {
2299 return lvalue ? this->push(*lvalue)
2300 : this->pushExpression(expr);
2301}

◆ pushMatrixMultiply()

bool SkSL::RP::Generator::pushMatrixMultiply ( LValue lvalue,
const Expression left,
const Expression right,
int  leftColumns,
int  leftRows,
int  rightColumns,
int  rightRows 
)

Definition at line 2303 of file SkSLRasterPipelineCodeGenerator.cpp.

2309 {
2310 SkASSERT(left.type().isMatrix() || left.type().isVector());
2311 SkASSERT(right.type().isMatrix() || right.type().isVector());
2312
2313 // Insert padding space on the stack to hold the result.
2314 fBuilder.pad_stack(rightColumns * leftRows);
2315
2316 // Push the left and right matrices onto the stack.
2317 if (!this->pushLValueOrExpression(lvalue, left) || !this->pushExpression(right)) {
2318 return unsupported();
2319 }
2320
2321 fBuilder.matrix_multiply(leftColumns, leftRows, rightColumns, rightRows);
2322
2323 // If this multiply was actually an assignment (via *=), write the result back to the lvalue.
2324 return lvalue ? this->store(*lvalue)
2325 : true;
2326}
void matrix_multiply(int leftColumns, int leftRows, int rightColumns, int rightRows)

◆ pushPostfixExpression()

bool SkSL::RP::Generator::pushPostfixExpression ( const PostfixExpression p,
bool  usesResult 
)

Definition at line 3639 of file SkSLRasterPipelineCodeGenerator.cpp.

3639 {
3640 // If the result is ignored...
3641 if (!usesResult) {
3642 // ... just emit a prefix expression instead.
3643 return this->pushPrefixExpression(p.getOperator(), *p.operand());
3644 }
3645 // Get the operand as an lvalue, and push it onto the stack as-is.
3646 std::unique_ptr<LValue> lvalue = this->makeLValue(*p.operand());
3647 if (!lvalue || !this->push(*lvalue)) {
3648 return unsupported();
3649 }
3650
3651 // Push a scratch copy of the operand.
3652 fBuilder.push_clone(p.type().slotCount());
3653
3654 // Increment or decrement the scratch copy by one.
3655 Literal oneLiteral{Position{}, 1.0, &p.type().componentType()};
3656 if (!this->pushVectorizedExpression(oneLiteral, p.type())) {
3657 return unsupported();
3658 }
3659
3660 switch (p.getOperator().kind()) {
3662 if (!this->binaryOp(p.type(), kAddOps)) {
3663 return unsupported();
3664 }
3665 break;
3666
3668 if (!this->binaryOp(p.type(), kSubtractOps)) {
3669 return unsupported();
3670 }
3671 break;
3672
3673 default:
3675 }
3676
3677 // Write the new value back to the operand.
3678 if (!this->store(*lvalue)) {
3679 return unsupported();
3680 }
3681
3682 // Discard the scratch copy, leaving only the original value as-is.
3683 this->discardExpression(p.type().slotCount());
3684 return true;
3685}

◆ pushPrefixExpression() [1/2]

bool SkSL::RP::Generator::pushPrefixExpression ( const PrefixExpression p)

Definition at line 3687 of file SkSLRasterPipelineCodeGenerator.cpp.

3687 {
3688 return this->pushPrefixExpression(p.getOperator(), *p.operand());
3689}

◆ pushPrefixExpression() [2/2]

bool SkSL::RP::Generator::pushPrefixExpression ( Operator  op,
const Expression expr 
)

Definition at line 3691 of file SkSLRasterPipelineCodeGenerator.cpp.

3691 {
3692 switch (op.kind()) {
3695 // Handle operators ! and ~.
3696 if (!this->pushExpression(expr)) {
3697 return unsupported();
3698 }
3699 fBuilder.push_constant_u(~0, expr.type().slotCount());
3700 fBuilder.binary_op(BuilderOp::bitwise_xor_n_ints, expr.type().slotCount());
3701 return true;
3702
3703 case OperatorKind::MINUS: {
3704 if (!this->pushExpression(expr)) {
3705 return unsupported();
3706 }
3707 if (expr.type().componentType().isFloat()) {
3708 // Handle float negation as an integer `x ^ 0x80000000`. This toggles the sign bit.
3709 fBuilder.push_constant_u(0x80000000, expr.type().slotCount());
3710 fBuilder.binary_op(BuilderOp::bitwise_xor_n_ints, expr.type().slotCount());
3711 } else {
3712 // Handle integer negation as a componentwise `expr * -1`.
3713 fBuilder.push_constant_i(-1, expr.type().slotCount());
3714 fBuilder.binary_op(BuilderOp::mul_n_ints, expr.type().slotCount());
3715 }
3716 return true;
3717 }
3719 // Rewrite as `expr += 1`.
3720 Literal oneLiteral{Position{}, 1.0, &expr.type().componentType()};
3721 return this->pushBinaryExpression(expr, OperatorKind::PLUSEQ, oneLiteral);
3722 }
3724 // Rewrite as `expr += -1`.
3725 Literal minusOneLiteral{expr.fPosition, -1.0, &expr.type().componentType()};
3726 return this->pushBinaryExpression(expr, OperatorKind::PLUSEQ, minusOneLiteral);
3727 }
3728 default:
3729 break;
3730 }
3731
3732 return unsupported();
3733}

◆ pushStructuredComparison()

bool SkSL::RP::Generator::pushStructuredComparison ( LValue left,
Operator  op,
LValue right,
const Type type 
)

Definition at line 2346 of file SkSLRasterPipelineCodeGenerator.cpp.

2349 {
2350 if (type.isStruct()) {
2351 // Compare every field in the struct.
2352 SkSpan<const Field> fields = type.fields();
2353 int currentSlot = 0;
2354 for (size_t index = 0; index < fields.size(); ++index) {
2355 const Type& fieldType = *fields[index].fType;
2356 const int fieldSlotCount = fieldType.slotCount();
2357 UnownedLValueSlice fieldLeft {left, currentSlot, fieldSlotCount};
2358 UnownedLValueSlice fieldRight{right, currentSlot, fieldSlotCount};
2359 if (!this->pushStructuredComparison(&fieldLeft, op, &fieldRight, fieldType)) {
2360 return unsupported();
2361 }
2362 currentSlot += fieldSlotCount;
2363 }
2364
2365 this->foldComparisonOp(op, fields.size());
2366 return true;
2367 }
2368
2369 if (type.isArray()) {
2370 const Type& indexedType = type.componentType();
2371 if (indexedType.numberKind() == Type::NumberKind::kNonnumeric) {
2372 // Compare every element in the array.
2373 const int indexedSlotCount = indexedType.slotCount();
2374 int currentSlot = 0;
2375 for (int index = 0; index < type.columns(); ++index) {
2376 UnownedLValueSlice indexedLeft {left, currentSlot, indexedSlotCount};
2377 UnownedLValueSlice indexedRight{right, currentSlot, indexedSlotCount};
2378 if (!this->pushStructuredComparison(&indexedLeft, op, &indexedRight, indexedType)) {
2379 return unsupported();
2380 }
2381 currentSlot += indexedSlotCount;
2382 }
2383
2384 this->foldComparisonOp(op, type.columns());
2385 return true;
2386 }
2387 }
2388
2389 // We've winnowed down to a single element, or an array of homogeneous numeric elements.
2390 // Push the elements onto the stack, then compare them.
2391 if (!this->push(*left) || !this->push(*right)) {
2392 return unsupported();
2393 }
2394 switch (op.kind()) {
2395 case OperatorKind::EQEQ:
2396 if (!this->binaryOp(type, kEqualOps)) {
2397 return unsupported();
2398 }
2399 break;
2400
2401 case OperatorKind::NEQ:
2402 if (!this->binaryOp(type, kNotEqualOps)) {
2403 return unsupported();
2404 }
2405 break;
2406
2407 default:
2408 SkDEBUGFAIL("comparison only allows == and !=");
2409 break;
2410 }
2411
2412 this->foldComparisonOp(op, type.slotCount());
2413 return true;
2414}
constexpr size_t size() const
Definition SkSpan_impl.h:95

◆ pushSwizzle()

bool SkSL::RP::Generator::pushSwizzle ( const Swizzle s)

Definition at line 3735 of file SkSLRasterPipelineCodeGenerator.cpp.

3735 {
3736 SkASSERT(!s.components().empty() && s.components().size() <= 4);
3737
3738 // If this is a simple subset of a variable's slots...
3739 bool isSimpleSubset = is_sliceable_swizzle(s.components());
3740 if (isSimpleSubset && s.base()->is<VariableReference>()) {
3741 // ... we can just push part of the variable directly onto the stack, rather than pushing
3742 // the whole expression and then immediately cutting it down. (Either way works, but this
3743 // saves a step.)
3744 return this->pushVariableReferencePartial(
3745 s.base()->as<VariableReference>(),
3746 SlotRange{/*index=*/s.components()[0], /*count=*/s.components().size()});
3747 }
3748 // Push the base expression.
3749 if (!this->pushExpression(*s.base())) {
3750 return false;
3751 }
3752 // An identity swizzle doesn't rearrange the data; it just (potentially) discards tail elements.
3753 if (isSimpleSubset && s.components()[0] == 0) {
3754 int discardedElements = s.base()->type().slotCount() - s.components().size();
3755 SkASSERT(discardedElements >= 0);
3756 fBuilder.discard_stack(discardedElements);
3757 return true;
3758 }
3759 // Perform the swizzle.
3760 fBuilder.swizzle(s.base()->type().slotCount(), s.components());
3761 return true;
3762}
bool pushVariableReferencePartial(const VariableReference &v, SlotRange subset)
struct MyStruct s

◆ pushTernaryExpression() [1/2]

bool SkSL::RP::Generator::pushTernaryExpression ( const Expression test,
const Expression ifTrue,
const Expression ifFalse 
)

Definition at line 3815 of file SkSLRasterPipelineCodeGenerator.cpp.

3817 {
3818 // If the test-expression is dynamically-uniform, we can skip over the non-true expressions
3819 // entirely, and not need to involve the condition mask.
3821 return this->pushDynamicallyUniformTernaryExpression(test, ifTrue, ifFalse);
3822 }
3823
3824 // Analyze the ternary to see which corners we can safely cut.
3825 bool ifFalseHasSideEffects = Analysis::HasSideEffects(ifFalse);
3826 bool ifTrueHasSideEffects = Analysis::HasSideEffects(ifTrue);
3827 bool ifTrueIsTrivial = Analysis::IsTrivialExpression(ifTrue);
3828 int cleanupLabelID = fBuilder.nextLabelID();
3829
3830 // If the true- and false-expressions both lack side effects, we evaluate both of them safely
3831 // without masking off their effects. In that case, we can emit both sides and use boolean mix
3832 // to select the correct result without using the condition mask at all.
3833 if (!ifFalseHasSideEffects && !ifTrueHasSideEffects && ifTrueIsTrivial) {
3834 // Push all of the arguments to mix.
3835 if (!this->pushVectorizedExpression(test, ifTrue.type())) {
3836 return unsupported();
3837 }
3838 if (!this->pushExpression(ifFalse)) {
3839 return unsupported();
3840 }
3841 if (!this->pushExpression(ifTrue)) {
3842 return unsupported();
3843 }
3844 // Use boolean mix to select the true- or false-expression via the test-expression.
3845 fBuilder.ternary_op(BuilderOp::mix_n_ints, ifTrue.type().slotCount());
3846 return true;
3847 }
3848
3849 // First, push the current condition-mask and the test-expression into a separate stack.
3850 fBuilder.enableExecutionMaskWrites();
3851 AutoStack testStack(this);
3852 testStack.enter();
3853 fBuilder.push_condition_mask();
3854 if (!this->pushExpression(test)) {
3855 return unsupported();
3856 }
3857 testStack.exit();
3858
3859 // We can take some shortcuts with condition-mask handling if the false-expression is entirely
3860 // side-effect free. (We can evaluate it without masking off its effects.) We always handle the
3861 // condition mask properly for the test-expression and true-expression properly.
3862 if (!ifFalseHasSideEffects) {
3863 // Push the false-expression onto the primary stack.
3864 if (!this->pushExpression(ifFalse)) {
3865 return unsupported();
3866 }
3867
3868 // Next, merge the condition mask (on the separate stack) with the test expression.
3869 testStack.enter();
3870 fBuilder.merge_condition_mask();
3871 testStack.exit();
3872
3873 // If no lanes are active, we can skip the true-expression entirely. This isn't super likely
3874 // to happen, so it's probably only a win for non-trivial true-expressions.
3875 if (!ifTrueIsTrivial) {
3876 fBuilder.branch_if_no_lanes_active(cleanupLabelID);
3877 }
3878
3879 // Push the true-expression onto the primary stack, immediately after the false-expression.
3880 if (!this->pushExpression(ifTrue)) {
3881 return unsupported();
3882 }
3883
3884 // Use a select to conditionally mask-merge the true-expression and false-expression lanes.
3885 fBuilder.select(/*slots=*/ifTrue.type().slotCount());
3886 fBuilder.label(cleanupLabelID);
3887 } else {
3888 // Merge the condition mask (on the separate stack) with the test expression.
3889 testStack.enter();
3890 fBuilder.merge_condition_mask();
3891 testStack.exit();
3892
3893 // Push the true-expression onto the primary stack.
3894 if (!this->pushExpression(ifTrue)) {
3895 return unsupported();
3896 }
3897
3898 // Switch back to the test-expression stack and apply the inverted test condition.
3899 testStack.enter();
3900 fBuilder.merge_inv_condition_mask();
3901 testStack.exit();
3902
3903 // Push the false-expression onto the primary stack, immediately after the true-expression.
3904 if (!this->pushExpression(ifFalse)) {
3905 return unsupported();
3906 }
3907
3908 // Use a select to conditionally mask-merge the true-expression and false-expression lanes;
3909 // the mask is already set up for this.
3910 fBuilder.select(/*slots=*/ifTrue.type().slotCount());
3911 }
3912
3913 // Restore the condition-mask to its original state and jettison the test-expression.
3914 testStack.enter();
3915 this->discardExpression(/*slots=*/1);
3916 fBuilder.pop_condition_mask();
3917 testStack.exit();
3918
3919 fBuilder.disableExecutionMaskWrites();
3920 return true;
3921}
bool pushDynamicallyUniformTernaryExpression(const Expression &test, const Expression &ifTrue, const Expression &ifFalse)
bool IsTrivialExpression(const Expression &expr)

◆ pushTernaryExpression() [2/2]

bool SkSL::RP::Generator::pushTernaryExpression ( const TernaryExpression t)

Definition at line 3764 of file SkSLRasterPipelineCodeGenerator.cpp.

3764 {
3765 return this->pushTernaryExpression(*t.test(), *t.ifTrue(), *t.ifFalse());
3766}

◆ pushTraceScopeMask()

void SkSL::RP::Generator::pushTraceScopeMask ( )

Emits a trace_scope opcode, which alters the SkSL variable-scope depth. Unlike the other trace ops, trace_scope takes a dedicated mask instead of the trace-scope mask. Call pushTraceScopeMask to synthesize this mask; discard it when you're done.

Definition at line 1549 of file SkSLRasterPipelineCodeGenerator.cpp.

1549 {
1550 if (this->shouldWriteTraceOps()) {
1551 // Take the intersection of the trace mask and the execution mask. To do this, start with an
1552 // all-zero mask, then use select to overwrite those zeros with the trace mask across all
1553 // executing lanes. We'll get the trace mask in executing lanes, and zero in dead lanes.
1554 fBuilder.push_constant_i(0);
1555 fTraceMask->pushClone(/*slots=*/1);
1556 fBuilder.select(/*slots=*/1);
1557 }
1558}

◆ pushVariableReference()

bool SkSL::RP::Generator::pushVariableReference ( const VariableReference v)

Definition at line 3923 of file SkSLRasterPipelineCodeGenerator.cpp.

3923 {
3924 // If we are pushing a constant-value variable, push the value directly; literal values are more
3925 // amenable to optimization.
3926 if (var.type().isScalar() || var.type().isVector()) {
3927 if (const Expression* expr = ConstantFolder::GetConstantValueOrNull(var)) {
3928 return this->pushExpression(*expr);
3929 }
3930 if (fImmutableVariables.contains(var.variable())) {
3931 return this->pushExpression(*var.variable()->initialValue());
3932 }
3933 }
3934 return this->pushVariableReferencePartial(var, SlotRange{0, (int)var.type().slotCount()});
3935}
static const Expression * GetConstantValueOrNull(const Expression &value)

◆ pushVariableReferencePartial()

bool SkSL::RP::Generator::pushVariableReferencePartial ( const VariableReference v,
SlotRange  subset 
)

Definition at line 3937 of file SkSLRasterPipelineCodeGenerator.cpp.

3937 {
3938 const Variable& var = *v.variable();
3939 SlotRange r;
3940 if (IsUniform(var)) {
3941 // Push a uniform.
3942 r = this->getUniformSlots(var);
3943 SkASSERT(r.count == (int)var.type().slotCount());
3944 r.index += subset.index;
3945 r.count = subset.count;
3946 fBuilder.push_uniform(r);
3947 } else if (fImmutableVariables.contains(&var)) {
3948 // If we only need a single slot, we can push a constant. This saves a lookup, and can
3949 // occasionally permit the use of an immediate-mode op.
3950 if (subset.count == 1) {
3951 const Expression& expr = *v.variable()->initialValue();
3952 std::optional<ImmutableBits> bits = this->getImmutableBitsForSlot(expr, subset.index);
3953 if (bits.has_value()) {
3954 fBuilder.push_constant_i(*bits);
3955 return true;
3956 }
3957 }
3958 // Push the immutable slot range.
3959 r = this->getImmutableSlots(var);
3960 SkASSERT(r.count == (int)var.type().slotCount());
3961 r.index += subset.index;
3962 r.count = subset.count;
3963 fBuilder.push_immutable(r);
3964 } else {
3965 // Push the variable.
3966 r = this->getVariableSlots(var);
3967 SkASSERT(r.count == (int)var.type().slotCount());
3968 r.index += subset.index;
3969 r.count = subset.count;
3970 fBuilder.push_slots(r);
3971 }
3972 return true;
3973}
void push_uniform(SlotRange src)
SlotRange getUniformSlots(const Variable &v)
SlotRange getVariableSlots(const Variable &v)
SlotRange getImmutableSlots(const Variable &v)

◆ pushVectorizedExpression()

bool SkSL::RP::Generator::pushVectorizedExpression ( const Expression expr,
const Type vectorType 
)

Definition at line 3055 of file SkSLRasterPipelineCodeGenerator.cpp.

3055 {
3056 if (!this->pushExpression(expr)) {
3057 return unsupported();
3058 }
3059 if (vectorType.slotCount() > expr.type().slotCount()) {
3060 SkASSERT(expr.type().slotCount() == 1);
3061 fBuilder.push_duplicates(vectorType.slotCount() - expr.type().slotCount());
3062 }
3063 return true;
3064}

◆ recycleStack()

void SkSL::RP::Generator::recycleStack ( int  stackID)

Frees a stack generated by createStack. The freed stack must be completely empty.

Definition at line 1367 of file SkSLRasterPipelineCodeGenerator.cpp.

1367 {
1368 fRecycledStacks.push_back(stackID);
1369}

◆ returnComplexity()

Analysis::ReturnComplexity SkSL::RP::Generator::returnComplexity ( const FunctionDefinition func)
inline

Definition at line 443 of file SkSLRasterPipelineCodeGenerator.cpp.

443 {
444 Analysis::ReturnComplexity* complexity = fReturnComplexityMap.find(func);
445 if (!complexity) {
446 complexity = fReturnComplexityMap.set(fCurrentFunction,
448 }
449 return *complexity;
450 }
ReturnComplexity GetReturnComplexity(const FunctionDefinition &funcDef)

◆ setCurrentStack()

void SkSL::RP::Generator::setCurrentStack ( int  stackID)

Redirects builder ops to point to a different stack (created by createStack).

Definition at line 1371 of file SkSLRasterPipelineCodeGenerator.cpp.

1371 {
1372 if (fCurrentStack != stackID) {
1373 fCurrentStack = stackID;
1374 fBuilder.set_current_stack(stackID);
1375 }
1376}
void set_current_stack(int stackID)

◆ shouldWriteTraceOps()

bool SkSL::RP::Generator::shouldWriteTraceOps ( )
inline

Definition at line 399 of file SkSLRasterPipelineCodeGenerator.cpp.

399{ return fDebugTrace && fWriteTraceOps; }

◆ store()

bool SkSL::RP::Generator::store ( LValue lvalue)

Copies the top-of-stack value into this lvalue, without discarding it from the stack.

Definition at line 1325 of file SkSLRasterPipelineCodeGenerator.cpp.

1325 {
1326 SkASSERT(lvalue.isWritable());
1327 return lvalue.store(this,
1328 lvalue.fixedSlotRange(this),
1329 lvalue.dynamicSlotRange(),
1330 /*swizzle=*/{});
1331}

◆ storeImmutableValueToSlots()

void SkSL::RP::Generator::storeImmutableValueToSlots ( const TArray< ImmutableBits > &  immutableValues,
SlotRange  slots 
)

Definition at line 2702 of file SkSLRasterPipelineCodeGenerator.cpp.

2703 {
2704 for (int index = 0; index < slots.count; ++index) {
2705 // Store the immutable value in its slot.
2706 const Slot slot = slots.index++;
2707 const ImmutableBits bits = immutableValues[index];
2708 fBuilder.store_immutable_value_i(slot, bits);
2709
2710 // Keep track of every stored immutable value for potential later reuse.
2711 fImmutableSlotMap[bits].add(slot);
2712 }
2713}
void store_immutable_value_i(Slot slot, int32_t val)

◆ ternaryOp()

bool SkSL::RP::Generator::ternaryOp ( const SkSL::Type type,
const TypedOps ops 
)

Definition at line 2271 of file SkSLRasterPipelineCodeGenerator.cpp.

2271 {
2273 if (op == BuilderOp::unsupported) {
2274 return unsupported();
2275 }
2276 fBuilder.ternary_op(op, type.slotCount());
2277 return true;
2278}

◆ traceMaskStackID()

int SkSL::RP::Generator::traceMaskStackID ( )
inline

Definition at line 400 of file SkSLRasterPipelineCodeGenerator.cpp.

400{ return fTraceMask->stackID(); }

◆ unaryOp()

bool SkSL::RP::Generator::unaryOp ( const SkSL::Type type,
const TypedOps ops 
)

Definition at line 2253 of file SkSLRasterPipelineCodeGenerator.cpp.

2253 {
2255 if (op == BuilderOp::unsupported) {
2256 return unsupported();
2257 }
2258 fBuilder.unary_op(op, type.slotCount());
2259 return true;
2260}

◆ writeBlock()

bool SkSL::RP::Generator::writeBlock ( const Block b)

Definition at line 1696 of file SkSLRasterPipelineCodeGenerator.cpp.

1696 {
1697 if (b.blockKind() == Block::Kind::kCompoundStatement) {
1698 this->emitTraceLine(b.fPosition);
1699 ++fInsideCompoundStatement;
1700 } else {
1701 this->pushTraceScopeMask();
1702 this->emitTraceScope(+1);
1703 }
1704
1705 for (const std::unique_ptr<Statement>& stmt : b.children()) {
1706 if (!this->writeStatement(*stmt)) {
1707 return unsupported();
1708 }
1709 }
1710
1711 if (b.blockKind() == Block::Kind::kCompoundStatement) {
1712 --fInsideCompoundStatement;
1713 } else {
1714 this->emitTraceScope(-1);
1715 this->discardTraceScopeMask();
1716 }
1717
1718 return true;
1719}
bool writeStatement(const Statement &s)
static bool b

◆ writeBreakStatement()

bool SkSL::RP::Generator::writeBreakStatement ( const BreakStatement b)

Definition at line 1721 of file SkSLRasterPipelineCodeGenerator.cpp.

1721 {
1722 // If all lanes have reached this break, we can just branch straight to the break target instead
1723 // of updating masks.
1724 fBuilder.branch_if_all_lanes_active(fCurrentBreakTarget);
1725 fBuilder.mask_off_loop_mask();
1726 return true;
1727}
void branch_if_all_lanes_active(int labelID)

◆ writeContinueStatement()

bool SkSL::RP::Generator::writeContinueStatement ( const ContinueStatement b)

Definition at line 1729 of file SkSLRasterPipelineCodeGenerator.cpp.

1729 {
1730 fBuilder.continue_op(fCurrentContinueMask->stackID());
1731 return true;
1732}
void continue_op(int continueMaskStackID)

◆ writeDoStatement()

bool SkSL::RP::Generator::writeDoStatement ( const DoStatement d)

Definition at line 1734 of file SkSLRasterPipelineCodeGenerator.cpp.

1734 {
1735 // Set up a break target.
1736 AutoLoopTarget breakTarget(this, &fCurrentBreakTarget);
1737
1738 // Save off the original loop mask.
1739 fBuilder.enableExecutionMaskWrites();
1740 fBuilder.push_loop_mask();
1741
1742 // If `continue` is used in the loop...
1743 Analysis::LoopControlFlowInfo loopInfo = Analysis::GetLoopControlFlowInfo(*d.statement());
1744 AutoContinueMask autoContinueMask(this);
1745 if (loopInfo.fHasContinue) {
1746 // ... create a temporary slot for continue-mask storage.
1747 autoContinueMask.enable();
1748 }
1749
1750 // Write the do-loop body.
1751 int labelID = fBuilder.nextLabelID();
1752 fBuilder.label(labelID);
1753
1754 autoContinueMask.enterLoopBody();
1755
1756 if (!this->writeStatement(*d.statement())) {
1757 return false;
1758 }
1759
1760 autoContinueMask.exitLoopBody();
1761
1762 // Point the debugger at the do-statement's test-expression before we run it.
1763 this->emitTraceLine(d.test()->fPosition);
1764
1765 // Emit the test-expression, in order to combine it with the loop mask.
1766 if (!this->pushExpression(*d.test())) {
1767 return false;
1768 }
1769
1770 // Mask off any lanes in the loop mask where the test-expression is false; this breaks the loop.
1771 // We don't use the test expression for anything else, so jettison it.
1772 fBuilder.merge_loop_mask();
1773 this->discardExpression(/*slots=*/1);
1774
1775 // If any lanes are still running, go back to the top and run the loop body again.
1776 fBuilder.branch_if_any_lanes_active(labelID);
1777
1778 // If we hit a break statement on all lanes, we will branch here to escape from the loop.
1779 fBuilder.label(breakTarget.labelID());
1780
1781 // Restore the loop mask.
1782 fBuilder.pop_loop_mask();
1783 fBuilder.disableExecutionMaskWrites();
1784
1785 return true;
1786}
void branch_if_any_lanes_active(int labelID)
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE auto & d
Definition main.cc:19
LoopControlFlowInfo GetLoopControlFlowInfo(const Statement &stmt)

◆ writeDynamicallyUniformIfStatement()

bool SkSL::RP::Generator::writeDynamicallyUniformIfStatement ( const IfStatement i)

Definition at line 1967 of file SkSLRasterPipelineCodeGenerator.cpp.

1967 {
1969
1970 int falseLabelID = fBuilder.nextLabelID();
1971 int exitLabelID = fBuilder.nextLabelID();
1972
1973 if (!this->pushExpression(*i.test())) {
1974 return unsupported();
1975 }
1976
1977 fBuilder.branch_if_no_active_lanes_on_stack_top_equal(~0, falseLabelID);
1978
1979 if (!this->writeStatement(*i.ifTrue())) {
1980 return unsupported();
1981 }
1982
1983 if (!i.ifFalse()) {
1984 // We don't have an if-false condition at all.
1985 fBuilder.label(falseLabelID);
1986 } else {
1987 // We do have an if-false condition. We've just completed the if-true block, so we need to
1988 // jump past the if-false block to avoid executing it.
1989 fBuilder.jump(exitLabelID);
1990
1991 // The if-false block starts here.
1992 fBuilder.label(falseLabelID);
1993
1994 if (!this->writeStatement(*i.ifFalse())) {
1995 return unsupported();
1996 }
1997
1998 fBuilder.label(exitLabelID);
1999 }
2000
2001 // Jettison the test-expression.
2002 this->discardExpression(/*slots=*/1);
2003 return true;
2004}

◆ writeExpressionStatement()

bool SkSL::RP::Generator::writeExpressionStatement ( const ExpressionStatement e)

Definition at line 1959 of file SkSLRasterPipelineCodeGenerator.cpp.

1959 {
1960 if (!this->pushExpression(*e.expression(), /*usesResult=*/false)) {
1961 return unsupported();
1962 }
1963 this->discardExpression(e.expression()->type().slotCount());
1964 return true;
1965}

◆ writeForStatement()

bool SkSL::RP::Generator::writeForStatement ( const ForStatement f)

Definition at line 1856 of file SkSLRasterPipelineCodeGenerator.cpp.

1856 {
1857 // If we've determined that the loop does not run, omit its code entirely.
1858 if (f.unrollInfo() && f.unrollInfo()->fCount == 0) {
1859 return true;
1860 }
1861
1862 // If the loop doesn't escape early due to a `continue`, `break` or `return`, and the loop
1863 // conforms to ES2 structure, we know that we will run the full number of iterations across all
1864 // lanes and don't need to use a loop mask.
1865 Analysis::LoopControlFlowInfo loopInfo = Analysis::GetLoopControlFlowInfo(*f.statement());
1866 if (!loopInfo.fHasContinue && !loopInfo.fHasBreak && !loopInfo.fHasReturn && f.unrollInfo()) {
1867 return this->writeMasklessForStatement(f);
1868 }
1869
1870 // We want the loop index to disappear at the end of the loop, so wrap the for statement in a
1871 // trace scope.
1872 this->pushTraceScopeMask();
1873 this->emitTraceScope(+1);
1874
1875 // Set up a break target.
1876 AutoLoopTarget breakTarget(this, &fCurrentBreakTarget);
1877
1878 // Run the loop initializer.
1879 if (f.initializer()) {
1880 if (!this->writeStatement(*f.initializer())) {
1881 return unsupported();
1882 }
1883 } else {
1884 this->emitTraceLine(f.fPosition);
1885 }
1886
1887 AutoContinueMask autoContinueMask(this);
1888 if (loopInfo.fHasContinue) {
1889 // Acquire a temporary slot for continue-mask storage.
1890 autoContinueMask.enable();
1891 }
1892
1893 // Save off the original loop mask.
1894 fBuilder.enableExecutionMaskWrites();
1895 fBuilder.push_loop_mask();
1896
1897 int loopTestID = fBuilder.nextLabelID();
1898 int loopBodyID = fBuilder.nextLabelID();
1899
1900 // Jump down to the loop test so we can fall out of the loop immediately if it's zero-iteration.
1901 fBuilder.jump(loopTestID);
1902
1903 // Write the for-loop body.
1904 fBuilder.label(loopBodyID);
1905
1906 autoContinueMask.enterLoopBody();
1907
1908 if (!this->writeStatement(*f.statement())) {
1909 return unsupported();
1910 }
1911
1912 autoContinueMask.exitLoopBody();
1913
1914 // Point the debugger at the for-statement's next-expression before we run it, or as close as we
1915 // can reasonably get.
1916 if (f.next()) {
1917 this->emitTraceLine(f.next()->fPosition);
1918 } else if (f.test()) {
1919 this->emitTraceLine(f.test()->fPosition);
1920 } else {
1921 this->emitTraceLine(f.fPosition);
1922 }
1923
1924 // Run the next-expression. Immediately discard its result.
1925 if (f.next()) {
1926 if (!this->pushExpression(*f.next(), /*usesResult=*/false)) {
1927 return unsupported();
1928 }
1929 this->discardExpression(f.next()->type().slotCount());
1930 }
1931
1932 fBuilder.label(loopTestID);
1933 if (f.test()) {
1934 // Emit the test-expression, in order to combine it with the loop mask.
1935 if (!this->pushExpression(*f.test())) {
1936 return unsupported();
1937 }
1938 // Mask off any lanes in the loop mask where the test-expression is false; this breaks the
1939 // loop. We don't use the test expression for anything else, so jettison it.
1940 fBuilder.merge_loop_mask();
1941 this->discardExpression(/*slots=*/1);
1942 }
1943
1944 // If any lanes are still running, go back to the top and run the loop body again.
1945 fBuilder.branch_if_any_lanes_active(loopBodyID);
1946
1947 // If we hit a break statement on all lanes, we will branch here to escape from the loop.
1948 fBuilder.label(breakTarget.labelID());
1949
1950 // Restore the loop mask.
1951 fBuilder.pop_loop_mask();
1952 fBuilder.disableExecutionMaskWrites();
1953
1954 this->emitTraceScope(-1);
1955 this->discardTraceScopeMask();
1956 return true;
1957}
bool writeMasklessForStatement(const ForStatement &f)

◆ writeFunction()

std::optional< SlotRange > SkSL::RP::Generator::writeFunction ( const IRNode callSite,
const FunctionDefinition function,
SkSpan< std::unique_ptr< Expression > const >  arguments 
)

Converts an SkSL function into a set of Instructions. Returns nullopt if the function contained unsupported statements or expressions.

Definition at line 1378 of file SkSLRasterPipelineCodeGenerator.cpp.

1381 {
1382 // Generate debug information and emit a trace-enter op.
1383 int funcIndex = -1;
1384 if (fDebugTrace) {
1385 funcIndex = this->getFunctionDebugInfo(function.declaration());
1386 SkASSERT(funcIndex >= 0);
1387 if (this->shouldWriteTraceOps()) {
1388 fBuilder.trace_enter(fTraceMask->stackID(), funcIndex);
1389 }
1390 }
1391
1392 // Handle parameter lvalues.
1393 struct RemappedSlotRange {
1394 const Variable* fVariable;
1395 std::optional<SlotRange> fSlotRange;
1396 };
1397 SkSpan<Variable* const> parameters = function.declaration().parameters();
1399 TArray<RemappedSlotRange> remappedSlotRanges;
1400
1401 if (function.declaration().isMain()) {
1402 // For main(), the parameter slots have already been populated by `writeProgram`, but we
1403 // still need to explicitly emit trace ops for the variables in main(), since they are
1404 // initialized before it is safe to use trace-var. (We can't invoke init-lane-masks until
1405 // after we've copied the inputs from main into slots, because dst.rgba is used to pass in a
1406 // blend-destination color, but we clobber it and put in the execution mask instead.)
1407 if (this->shouldWriteTraceOps()) {
1408 for (const Variable* var : parameters) {
1409 fBuilder.trace_var(fTraceMask->stackID(), this->getVariableSlots(*var));
1410 }
1411 }
1412 } else {
1413 // Write all the arguments into their parameter's variable slots. Because we never allow
1414 // recursion, we don't need to worry about overwriting any existing values in those slots.
1415 // (In fact, we don't even need to apply the write mask.)
1416 lvalues.resize(arguments.size());
1417
1418 for (size_t index = 0; index < arguments.size(); ++index) {
1419 const Expression& arg = *arguments[index];
1420 const Variable& param = *parameters[index];
1421
1422 // Use LValues for out-parameters and inout-parameters, so we can store back to them
1423 // later.
1424 if (IsInoutParameter(param) || IsOutParameter(param)) {
1425 lvalues[index] = this->makeLValue(arg);
1426 if (!lvalues[index]) {
1427 return std::nullopt;
1428 }
1429 // There are no guarantees on the starting value of an out-parameter, so we only
1430 // need to store the lvalues associated with an inout parameter.
1431 if (IsInoutParameter(param)) {
1432 if (!this->push(*lvalues[index])) {
1433 return std::nullopt;
1434 }
1435 this->popToSlotRangeUnmasked(this->getVariableSlots(param));
1436 }
1437 continue;
1438 }
1439
1440 // If a parameter is never read by the function, we don't need to populate its slots.
1441 ProgramUsage::VariableCounts paramCounts = fProgram.fUsage->get(param);
1442 if (paramCounts.fRead == 0) {
1443 // Honor the expression's side effects, if any.
1444 if (Analysis::HasSideEffects(arg)) {
1445 if (!this->pushExpression(arg, /*usesResult=*/false)) {
1446 return std::nullopt;
1447 }
1448 this->discardExpression(arg.type().slotCount());
1449 }
1450 continue;
1451 }
1452
1453 // If the expression is a plain variable and the parameter is never written to, we don't
1454 // need to copy it; we can just share the slots from the existing variable.
1455 if (paramCounts.fWrite == 0 && arg.is<VariableReference>()) {
1456 const Variable& var = *arg.as<VariableReference>().variable();
1457 if (this->hasVariableSlots(var)) {
1458 std::optional<SlotRange> originalRange =
1459 fProgramSlots.mapVariableToSlots(param, this->getVariableSlots(var));
1460 remappedSlotRanges.push_back({&param, originalRange});
1461 continue;
1462 }
1463 }
1464
1465 // Copy input arguments into their respective parameter slots.
1466 if (!this->pushExpression(arg)) {
1467 return std::nullopt;
1468 }
1469 this->popToSlotRangeUnmasked(this->getVariableSlots(param));
1470 }
1471 }
1472
1473 // Set up a slot range dedicated to this function's return value.
1474 SlotRange lastFunctionResult = fCurrentFunctionResult;
1475 fCurrentFunctionResult = this->getFunctionSlots(callSite, function.declaration());
1476
1477 // Save off the return mask.
1478 if (this->needsReturnMask(&function)) {
1479 fBuilder.enableExecutionMaskWrites();
1480 if (!function.declaration().isMain()) {
1481 fBuilder.push_return_mask();
1482 }
1483 }
1484
1485 // Emit the function body.
1486 if (!this->writeStatement(*function.body())) {
1487 return std::nullopt;
1488 }
1489
1490 // Restore the original return mask.
1491 if (this->needsReturnMask(&function)) {
1492 if (!function.declaration().isMain()) {
1493 fBuilder.pop_return_mask();
1494 }
1495 fBuilder.disableExecutionMaskWrites();
1496 }
1497
1498 // Restore the function-result slot range.
1499 SlotRange functionResult = fCurrentFunctionResult;
1500 fCurrentFunctionResult = lastFunctionResult;
1501
1502 // Emit a trace-exit op.
1503 if (fDebugTrace && fWriteTraceOps) {
1504 fBuilder.trace_exit(fTraceMask->stackID(), funcIndex);
1505 }
1506
1507 // Copy out-parameters and inout-parameters back to their homes.
1508 for (int index = 0; index < lvalues.size(); ++index) {
1509 if (lvalues[index]) {
1510 // Only out- and inout-parameters should have an associated lvalue.
1511 const Variable& param = *parameters[index];
1512 SkASSERT(IsInoutParameter(param) || IsOutParameter(param));
1513
1514 // Copy the parameter's slots directly into the lvalue.
1515 fBuilder.push_slots(this->getVariableSlots(param));
1516 if (!this->store(*lvalues[index])) {
1517 return std::nullopt;
1518 }
1519 this->discardExpression(param.type().slotCount());
1520 }
1521 }
1522
1523 // Restore any remapped parameter slot ranges to their original values.
1524 for (const RemappedSlotRange& remapped : remappedSlotRanges) {
1525 if (remapped.fSlotRange.has_value()) {
1526 fProgramSlots.mapVariableToSlots(*remapped.fVariable, *remapped.fSlotRange);
1527 } else {
1528 fProgramSlots.unmapVariableSlots(*remapped.fVariable);
1529 }
1530 }
1531
1532 return functionResult;
1533}
void trace_enter(int traceMaskStackID, int funcID)
void trace_exit(int traceMaskStackID, int funcID)
bool needsReturnMask(const FunctionDefinition *func)
int getFunctionDebugInfo(const FunctionDeclaration &decl)
SlotRange getFunctionSlots(const IRNode &callSite, const FunctionDeclaration &f)
static bool IsInoutParameter(const Variable &var)
static bool IsOutParameter(const Variable &var)
std::optional< SlotRange > mapVariableToSlots(const Variable &v, SlotRange range)
void resize(size_t count)
Definition SkTArray.h:418
Dart_NativeFunction function
Definition fuchsia.cc:51
std::unique_ptr< ProgramUsage > fUsage

◆ writeGlobals()

bool SkSL::RP::Generator::writeGlobals ( )

Definition at line 1583 of file SkSLRasterPipelineCodeGenerator.cpp.

1583 {
1584 for (const ProgramElement* e : fProgram.elements()) {
1585 if (e->is<GlobalVarDeclaration>()) {
1586 const GlobalVarDeclaration& gvd = e->as<GlobalVarDeclaration>();
1587 const VarDeclaration& decl = gvd.varDeclaration();
1588 const Variable* var = decl.var();
1589
1590 if (var->type().isEffectChild()) {
1591 // Associate each child effect variable with its numeric index.
1592 SkASSERT(!fChildEffectMap.find(var));
1593 int childEffectIndex = fChildEffectMap.count();
1594 fChildEffectMap[var] = childEffectIndex;
1595 continue;
1596 }
1597
1598 // Opaque types include child processors and GL objects (samplers, textures, etc).
1599 // Of those, only child processors are legal variables.
1600 SkASSERT(!var->type().isVoid());
1601 SkASSERT(!var->type().isOpaque());
1602
1603 // Builtin variables are system-defined, with special semantics.
1604 if (int builtin = var->layout().fBuiltin; builtin >= 0) {
1605 if (builtin == SK_FRAGCOORD_BUILTIN) {
1606 fBuilder.store_device_xy01(this->getVariableSlots(*var));
1607 continue;
1608 }
1609 // The only builtin variable exposed to runtime effects is sk_FragCoord.
1610 return unsupported();
1611 }
1612
1613 if (IsUniform(*var)) {
1614 // Create the uniform slot map in first-to-last order.
1615 SlotRange uniformSlotRange = this->getUniformSlots(*var);
1616
1617 if (this->shouldWriteTraceOps()) {
1618 // We expect uniform values to show up in the debug trace. To make this happen
1619 // without updating the file format, we synthesize a value-slot range for the
1620 // uniform here, and copy the uniform data into the value slots. This allows
1621 // trace_var to work naturally. This wastes a bit of memory, but debug traces
1622 // don't need to be hyper-efficient.
1623 SlotRange copyRange = fProgramSlots.getVariableSlots(*var);
1624 fBuilder.push_uniform(uniformSlotRange);
1625 this->popToSlotRangeUnmasked(copyRange);
1626 }
1627
1628 continue;
1629 }
1630
1631 // Other globals are treated as normal variable declarations.
1632 if (!this->writeVarDeclaration(decl)) {
1633 return unsupported();
1634 }
1635 }
1636 }
1637
1638 return true;
1639}
constexpr int SK_FRAGCOORD_BUILTIN
void store_device_xy01(SlotRange slots)
bool writeVarDeclaration(const VarDeclaration &v)

◆ writeIfStatement()

bool SkSL::RP::Generator::writeIfStatement ( const IfStatement i)

Definition at line 2006 of file SkSLRasterPipelineCodeGenerator.cpp.

2006 {
2007 // If the test condition is known to be uniform, we can skip over the untrue portion entirely.
2009 return this->writeDynamicallyUniformIfStatement(i);
2010 }
2011
2012 // Save the current condition-mask.
2013 fBuilder.enableExecutionMaskWrites();
2014 fBuilder.push_condition_mask();
2015
2016 // Push the test condition mask.
2017 if (!this->pushExpression(*i.test())) {
2018 return unsupported();
2019 }
2020
2021 // Merge the current condition-mask with the test condition, then run the if-true branch.
2022 fBuilder.merge_condition_mask();
2023 if (!this->writeStatement(*i.ifTrue())) {
2024 return unsupported();
2025 }
2026
2027 if (i.ifFalse()) {
2028 // Apply the inverse condition-mask. Then run the if-false branch.
2029 fBuilder.merge_inv_condition_mask();
2030 if (!this->writeStatement(*i.ifFalse())) {
2031 return unsupported();
2032 }
2033 }
2034
2035 // Jettison the test-expression, and restore the the condition-mask.
2036 this->discardExpression(/*slots=*/1);
2037 fBuilder.pop_condition_mask();
2038 fBuilder.disableExecutionMaskWrites();
2039
2040 return true;
2041}
bool writeDynamicallyUniformIfStatement(const IfStatement &i)

◆ writeImmutableVarDeclaration()

bool SkSL::RP::Generator::writeImmutableVarDeclaration ( const VarDeclaration d)

Definition at line 2126 of file SkSLRasterPipelineCodeGenerator.cpp.

2126 {
2127 // In a debugging session, we expect debug traces for a variable declaration to appear, even if
2128 // it's constant, so we don't use immutable slots for variables when tracing is on.
2129 if (this->shouldWriteTraceOps()) {
2130 return false;
2131 }
2132
2133 // Find the constant value for this variable.
2134 const Expression* initialValue = ConstantFolder::GetConstantValueForVariable(*d.value());
2135 SkASSERT(initialValue);
2136
2137 // For a variable to be immutable, it cannot be written-to besides its initial declaration.
2138 ProgramUsage::VariableCounts counts = fProgram.fUsage->get(*d.var());
2139 if (counts.fWrite != 1) {
2140 return false;
2141 }
2142
2143 STArray<16, ImmutableBits> immutableValues;
2144 if (!this->getImmutableValueForExpression(*initialValue, &immutableValues)) {
2145 return false;
2146 }
2147
2148 fImmutableVariables.add(d.var());
2149
2150 std::optional<SlotRange> preexistingSlots = this->findPreexistingImmutableData(immutableValues);
2151 if (preexistingSlots.has_value()) {
2152 // Associate this variable with a preexisting range of immutable data (no new data or code).
2153 fImmutableSlots.mapVariableToSlots(*d.var(), *preexistingSlots);
2154 } else {
2155 // Write out the constant value back to immutable slots. (This generates data, but no
2156 // runtime code.)
2157 SlotRange slots = this->getImmutableSlots(*d.var());
2158 this->storeImmutableValueToSlots(immutableValues, slots);
2159 }
2160
2161 return true;
2162}
static const Expression * GetConstantValueForVariable(const Expression &value)
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core counts
Definition switches.h:239

◆ writeMasklessForStatement()

bool SkSL::RP::Generator::writeMasklessForStatement ( const ForStatement f)

Definition at line 1788 of file SkSLRasterPipelineCodeGenerator.cpp.

1788 {
1789 SkASSERT(f.unrollInfo());
1790 SkASSERT(f.unrollInfo()->fCount > 0);
1791 SkASSERT(f.initializer());
1792 SkASSERT(f.test());
1793 SkASSERT(f.next());
1794
1795 // We want the loop index to disappear at the end of the loop, so wrap the for statement in a
1796 // trace scope.
1797 this->pushTraceScopeMask();
1798 this->emitTraceScope(+1);
1799
1800 // If no lanes are active, skip over the loop entirely. This guards against looping forever;
1801 // with no lanes active, we wouldn't be able to write the loop variable back to its slot, so
1802 // we'd never make forward progress.
1803 int loopExitID = fBuilder.nextLabelID();
1804 int loopBodyID = fBuilder.nextLabelID();
1805 fBuilder.branch_if_no_lanes_active(loopExitID);
1806
1807 // Run the loop initializer.
1808 if (!this->writeStatement(*f.initializer())) {
1809 return unsupported();
1810 }
1811
1812 // Write the for-loop body. We know the for-loop has a standard ES2 unrollable structure, and
1813 // that it runs for at least one iteration, so we can plow straight ahead into the loop body
1814 // instead of running the loop-test first.
1815 fBuilder.label(loopBodyID);
1816
1817 if (!this->writeStatement(*f.statement())) {
1818 return unsupported();
1819 }
1820
1821 // Point the debugger at the for-statement's next-expression before we run it, or as close as we
1822 // can reasonably get.
1823 if (f.next()) {
1824 this->emitTraceLine(f.next()->fPosition);
1825 } else if (f.test()) {
1826 this->emitTraceLine(f.test()->fPosition);
1827 } else {
1828 this->emitTraceLine(f.fPosition);
1829 }
1830
1831 // If the loop only runs for a single iteration, we are already done. If not...
1832 if (f.unrollInfo()->fCount > 1) {
1833 // ... run the next-expression, and immediately discard its result.
1834 if (!this->pushExpression(*f.next(), /*usesResult=*/false)) {
1835 return unsupported();
1836 }
1837 this->discardExpression(f.next()->type().slotCount());
1838
1839 // Run the test-expression, and repeat the loop until the test-expression evaluates false.
1840 if (!this->pushExpression(*f.test())) {
1841 return unsupported();
1842 }
1843 fBuilder.branch_if_no_active_lanes_on_stack_top_equal(0, loopBodyID);
1844
1845 // Jettison the test-expression.
1846 this->discardExpression(/*slots=*/1);
1847 }
1848
1849 fBuilder.label(loopExitID);
1850
1851 this->emitTraceScope(-1);
1852 this->discardTraceScopeMask();
1853 return true;
1854}

◆ writeProgram()

bool SkSL::RP::Generator::writeProgram ( const FunctionDefinition function)

Converts the SkSL main() function into a set of Instructions.

Definition at line 3975 of file SkSLRasterPipelineCodeGenerator.cpp.

3975 {
3976 fCurrentFunction = &function;
3977
3978 if (fDebugTrace) {
3979 // Copy the program source into the debug info so that it will be written in the trace file.
3980 fDebugTrace->setSource(*fProgram.fSource);
3981
3982 if (fWriteTraceOps) {
3983 // The Raster Pipeline blitter generates centered pixel coordinates. (0.5, 1.5, 2.5,
3984 // etc.) Add 0.5 to the requested trace coordinate to match this, then compare against
3985 // src.rg, which contains the shader's coordinates. We keep this result in a dedicated
3986 // trace-mask stack.
3987 fTraceMask.emplace(this);
3988 fTraceMask->enter();
3989 fBuilder.push_device_xy01();
3990 fBuilder.discard_stack(2);
3991 fBuilder.push_constant_f(fDebugTrace->fTraceCoord.fX + 0.5f);
3992 fBuilder.push_constant_f(fDebugTrace->fTraceCoord.fY + 0.5f);
3993 fBuilder.binary_op(BuilderOp::cmpeq_n_floats, 2);
3994 fBuilder.binary_op(BuilderOp::bitwise_and_n_ints, 1);
3995 fTraceMask->exit();
3996
3997 // Assemble a position-to-line-number mapping for the debugger.
3998 this->calculateLineOffsets();
3999 }
4000 }
4001
4002 // Assign slots to the parameters of main; copy src and dst into those slots as appropriate.
4003 const SkSL::Variable* mainCoordsParam = function.declaration().getMainCoordsParameter();
4004 const SkSL::Variable* mainInputColorParam = function.declaration().getMainInputColorParameter();
4005 const SkSL::Variable* mainDestColorParam = function.declaration().getMainDestColorParameter();
4006
4007 for (const SkSL::Variable* param : function.declaration().parameters()) {
4008 if (param == mainCoordsParam) {
4009 // Coordinates are passed via RG.
4010 SlotRange fragCoord = this->getVariableSlots(*param);
4011 SkASSERT(fragCoord.count == 2);
4012 fBuilder.store_src_rg(fragCoord);
4013 } else if (param == mainInputColorParam) {
4014 // Input colors are passed via RGBA.
4015 SlotRange srcColor = this->getVariableSlots(*param);
4016 SkASSERT(srcColor.count == 4);
4017 fBuilder.store_src(srcColor);
4018 } else if (param == mainDestColorParam) {
4019 // Dest colors are passed via dRGBA.
4020 SlotRange destColor = this->getVariableSlots(*param);
4021 SkASSERT(destColor.count == 4);
4022 fBuilder.store_dst(destColor);
4023 } else {
4024 SkDEBUGFAIL("Invalid parameter to main()");
4025 return unsupported();
4026 }
4027 }
4028
4029 // Initialize the program.
4030 fBuilder.init_lane_masks();
4031
4032 // Emit global variables.
4033 if (!this->writeGlobals()) {
4034 return unsupported();
4035 }
4036
4037 // Invoke main().
4038 std::optional<SlotRange> mainResult = this->writeFunction(function, function, /*arguments=*/{});
4039 if (!mainResult.has_value()) {
4040 return unsupported();
4041 }
4042
4043 // Move the result of main() from slots into RGBA.
4044 SkASSERT(mainResult->count == 4);
4045 if (this->needsFunctionResultSlots(fCurrentFunction)) {
4046 fBuilder.load_src(*mainResult);
4047 } else {
4048 fBuilder.pop_src_rgba();
4049 }
4050
4051 // Discard the trace mask.
4052 if (fTraceMask.has_value()) {
4053 fTraceMask->enter();
4054 fBuilder.discard_stack(1);
4055 fTraceMask->exit();
4056 }
4057
4058 return true;
4059}
void setSource(const std::string &source)
void store_dst(SlotRange slots)
void load_src(SlotRange slots)
void store_src(SlotRange slots)
void store_src_rg(SlotRange slots)
int32_t fX
x-axis value
int32_t fY
y-axis value

◆ writeReturnStatement()

bool SkSL::RP::Generator::writeReturnStatement ( const ReturnStatement r)

Definition at line 2043 of file SkSLRasterPipelineCodeGenerator.cpp.

2043 {
2044 if (r.expression()) {
2045 if (!this->pushExpression(*r.expression())) {
2046 return unsupported();
2047 }
2048 if (this->needsFunctionResultSlots(fCurrentFunction)) {
2049 this->popToSlotRange(fCurrentFunctionResult);
2050 }
2051 }
2052 if (fBuilder.executionMaskWritesAreEnabled() && this->needsReturnMask(fCurrentFunction)) {
2053 fBuilder.mask_off_return_mask();
2054 }
2055 return true;
2056}

◆ writeStatement()

bool SkSL::RP::Generator::writeStatement ( const Statement s)

Appends a statement to the program.

Definition at line 1641 of file SkSLRasterPipelineCodeGenerator.cpp.

1641 {
1642 switch (s.kind()) {
1643 case Statement::Kind::kBlock:
1644 // The debugger will stop on statements inside Blocks; there's no need for an additional
1645 // stop on the block's initial open-brace.
1646 case Statement::Kind::kFor:
1647 // The debugger will stop on the init-statement of a for statement, so we don't need to
1648 // stop on the outer for-statement itself as well.
1649 break;
1650
1651 default:
1652 // The debugger should stop on other statements.
1653 this->emitTraceLine(s.fPosition);
1654 break;
1655 }
1656
1657 switch (s.kind()) {
1658 case Statement::Kind::kBlock:
1659 return this->writeBlock(s.as<Block>());
1660
1661 case Statement::Kind::kBreak:
1662 return this->writeBreakStatement(s.as<BreakStatement>());
1663
1664 case Statement::Kind::kContinue:
1665 return this->writeContinueStatement(s.as<ContinueStatement>());
1666
1667 case Statement::Kind::kDo:
1668 return this->writeDoStatement(s.as<DoStatement>());
1669
1670 case Statement::Kind::kExpression:
1671 return this->writeExpressionStatement(s.as<ExpressionStatement>());
1672
1673 case Statement::Kind::kFor:
1674 return this->writeForStatement(s.as<ForStatement>());
1675
1676 case Statement::Kind::kIf:
1677 return this->writeIfStatement(s.as<IfStatement>());
1678
1679 case Statement::Kind::kNop:
1680 return true;
1681
1682 case Statement::Kind::kReturn:
1683 return this->writeReturnStatement(s.as<ReturnStatement>());
1684
1685 case Statement::Kind::kSwitch:
1686 return this->writeSwitchStatement(s.as<SwitchStatement>());
1687
1688 case Statement::Kind::kVarDeclaration:
1689 return this->writeVarDeclaration(s.as<VarDeclaration>());
1690
1691 default:
1692 return unsupported();
1693 }
1694}
bool writeReturnStatement(const ReturnStatement &r)
bool writeDoStatement(const DoStatement &d)
bool writeExpressionStatement(const ExpressionStatement &e)
bool writeBreakStatement(const BreakStatement &b)
bool writeIfStatement(const IfStatement &i)
bool writeContinueStatement(const ContinueStatement &b)
bool writeForStatement(const ForStatement &f)
bool writeSwitchStatement(const SwitchStatement &s)

◆ writeSwitchStatement()

bool SkSL::RP::Generator::writeSwitchStatement ( const SwitchStatement s)

Definition at line 2058 of file SkSLRasterPipelineCodeGenerator.cpp.

2058 {
2059 const StatementArray& cases = s.cases();
2060 SkASSERT(std::all_of(cases.begin(), cases.end(), [](const std::unique_ptr<Statement>& stmt) {
2061 return stmt->is<SwitchCase>();
2062 }));
2063
2064 // Set up a break target.
2065 AutoLoopTarget breakTarget(this, &fCurrentBreakTarget);
2066
2067 // Save off the original loop mask.
2068 fBuilder.enableExecutionMaskWrites();
2069 fBuilder.push_loop_mask();
2070
2071 // Push the switch-case value, and write a default-mask that enables every lane which already
2072 // has an active loop mask. As we match cases, the default mask will get pared down.
2073 if (!this->pushExpression(*s.value())) {
2074 return unsupported();
2075 }
2076 fBuilder.push_loop_mask();
2077
2078 // Zero out the loop mask; each case op will re-enable it as we go.
2079 fBuilder.mask_off_loop_mask();
2080
2081 // Write each switch-case.
2082 bool foundDefaultCase = false;
2083 for (const std::unique_ptr<Statement>& stmt : cases) {
2084 int skipLabelID = fBuilder.nextLabelID();
2085
2086 const SwitchCase& sc = stmt->as<SwitchCase>();
2087 if (sc.isDefault()) {
2088 foundDefaultCase = true;
2089 if (stmt.get() != cases.back().get()) {
2090 // We only support a default case when it is the very last case. If that changes,
2091 // this logic will need to be updated.
2092 return unsupported();
2093 }
2094 // Keep whatever lanes are executing now, and also enable any lanes in the default mask.
2095 fBuilder.pop_and_reenable_loop_mask();
2096 // Execute the switch-case block, if any lanes are alive to see it.
2097 fBuilder.branch_if_no_lanes_active(skipLabelID);
2098 if (!this->writeStatement(*sc.statement())) {
2099 return unsupported();
2100 }
2101 } else {
2102 // The case-op will enable the loop mask if the switch-value matches, and mask off lanes
2103 // from the default-mask.
2104 fBuilder.case_op(sc.value());
2105 // Execute the switch-case block, if any lanes are alive to see it.
2106 fBuilder.branch_if_no_lanes_active(skipLabelID);
2107 if (!this->writeStatement(*sc.statement())) {
2108 return unsupported();
2109 }
2110 }
2111 fBuilder.label(skipLabelID);
2112 }
2113
2114 // Jettison the switch value, and the default case mask if it was never consumed above.
2115 this->discardExpression(/*slots=*/foundDefaultCase ? 1 : 2);
2116
2117 // If we hit a break statement on all lanes, we will branch here to escape from the switch.
2118 fBuilder.label(breakTarget.labelID());
2119
2120 // Restore the loop mask.
2121 fBuilder.pop_loop_mask();
2122 fBuilder.disableExecutionMaskWrites();
2123 return true;
2124}
skia_private::STArray< 2, std::unique_ptr< Statement > > StatementArray
Definition SkSLDefines.h:34

◆ writeVarDeclaration()

bool SkSL::RP::Generator::writeVarDeclaration ( const VarDeclaration v)

Definition at line 2164 of file SkSLRasterPipelineCodeGenerator.cpp.

2164 {
2165 if (v.value()) {
2166 // If a variable never actually changes, we can make it immutable.
2167 if (this->writeImmutableVarDeclaration(v)) {
2168 return true;
2169 }
2170 // This is a real variable which can change over the course of execution.
2171 if (!this->pushExpression(*v.value())) {
2172 return unsupported();
2173 }
2174 this->popToSlotRangeUnmasked(this->getVariableSlots(*v.var()));
2175 } else {
2176 this->zeroSlotRangeUnmasked(this->getVariableSlots(*v.var()));
2177 }
2178 return true;
2179}
bool writeImmutableVarDeclaration(const VarDeclaration &d)

◆ zeroSlotRangeUnmasked()

void SkSL::RP::Generator::zeroSlotRangeUnmasked ( SlotRange  r)
inline

Zeroes out a range of slots.

Definition at line 374 of file SkSLRasterPipelineCodeGenerator.cpp.

374 {
375 fBuilder.zero_slots_unmasked(r);
376 if (this->shouldWriteTraceOps()) {
377 fBuilder.trace_var(fTraceMask->stackID(), r);
378 }
379 }
void zero_slots_unmasked(SlotRange dst)

Friends And Related Symbol Documentation

◆ AutoContinueMask

friend class AutoContinueMask
friend

Definition at line 561 of file SkSLRasterPipelineCodeGenerator.cpp.


The documentation for this class was generated from the following file: