blob: 84cccaa2aff489d2de01beb2e1a5eea6eb2b78e4 [file] [log] [blame]
/*
* Copyright 2022 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkSpan.h"
#include "include/private/SkSLDefines.h"
#include "include/private/SkSLIRNode.h"
#include "include/private/SkSLLayout.h"
#include "include/private/SkSLModifiers.h"
#include "include/private/SkSLStatement.h"
#include "include/private/SkStringView.h"
#include "include/private/SkTArray.h"
#include "include/private/SkTHash.h"
#include "include/sksl/SkSLOperator.h"
#include "include/sksl/SkSLPosition.h"
#include "src/sksl/SkSLCompiler.h"
#include "src/sksl/codegen/SkSLRasterPipelineBuilder.h"
#include "src/sksl/codegen/SkSLRasterPipelineCodeGenerator.h"
#include "src/sksl/ir/SkSLBinaryExpression.h"
#include "src/sksl/ir/SkSLBlock.h"
#include "src/sksl/ir/SkSLConstructorCompound.h"
#include "src/sksl/ir/SkSLConstructorSplat.h"
#include "src/sksl/ir/SkSLExpression.h"
#include "src/sksl/ir/SkSLExpressionStatement.h"
#include "src/sksl/ir/SkSLFunctionDeclaration.h"
#include "src/sksl/ir/SkSLFunctionDefinition.h"
#include "src/sksl/ir/SkSLIfStatement.h"
#include "src/sksl/ir/SkSLLiteral.h"
#include "src/sksl/ir/SkSLProgram.h"
#include "src/sksl/ir/SkSLReturnStatement.h"
#include "src/sksl/ir/SkSLType.h"
#include "src/sksl/ir/SkSLVarDeclarations.h"
#include "src/sksl/ir/SkSLVariable.h"
#include "src/sksl/ir/SkSLVariableReference.h"
#include "src/sksl/tracing/SkRPDebugTrace.h"
#include "src/sksl/tracing/SkSLDebugInfo.h"
#include <cstddef>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace SkSL {
namespace RP {
class Generator {
public:
Generator(const SkSL::Program& program, SkRPDebugTrace* debugTrace)
: fProgram(program)
, fDebugTrace(debugTrace) {}
/** Converts the SkSL main() function into a set of Instructions. */
bool writeProgram(const FunctionDefinition& function);
/**
* Converts an SkSL function into a set of Instructions. Returns nullopt if the function
* contained unsupported statements or expressions.
*/
std::optional<SlotRange> writeFunction(const IRNode& callSite,
const FunctionDefinition& function,
SkSpan<const SlotRange> args);
/** Used by `createSlots` to add this variable to SlotDebugInfo inside SkRPDebugTrace. */
void addDebugSlotInfoForGroup(const std::string& varName,
const Type& type,
Position pos,
int* groupIndex,
bool isFunctionReturnValue);
void addDebugSlotInfo(const std::string& varName,
const Type& type,
Position pos,
bool isFunctionReturnValue);
/**
* Returns the slot index of this function inside the FunctionDebugInfo array in SkRPDebugTrace.
* The FunctionDebugInfo slot will be created if it doesn't already exist.
*/
int getDebugFunctionInfo(const FunctionDeclaration& decl);
/** Implements low-level slot creation; slots will not be known to the debugger. */
SlotRange createSlots(int numSlots);
/** Creates slots associated with an SkSL variable or return value. */
SlotRange createSlots(std::string name,
const Type& type,
Position pos,
bool isFunctionReturnValue);
/** Looks up the slots associated with an SkSL variable; creates the slot if necessary. */
SlotRange getSlots(const Variable& v);
/** Returns the number of slots needed by the program. */
int slotCount() const { return fSlotCount; }
/**
* Looks up the slots associated with an SkSL function's return value; creates the range if
* necessary. Note that recursion is never supported, so we don't need to maintain return values
* in a stack; we can just statically allocate one slot per function call-site.
*/
SlotRange getFunctionSlots(const IRNode& callSite, const FunctionDeclaration& f);
/** The Builder stitches our instructions together into Raster Pipeline code. */
Builder* builder() { return &fBuilder; }
/** Appends a statement to the program. */
bool writeStatement(const Statement& s);
bool writeBlock(const Block& b);
bool writeExpressionStatement(const ExpressionStatement& e);
bool writeIfStatement(const IfStatement& i);
bool writeReturnStatement(const ReturnStatement& r);
bool writeVarDeclaration(const VarDeclaration& v);
/** Pushes an expression to the value stack. */
bool pushAssignmentExpression(const BinaryExpression& e);
bool pushExpression(const Expression& e);
bool pushBinaryExpression(const BinaryExpression& e);
bool pushConstructorCompound(const ConstructorCompound& c);
bool pushConstructorSplat(const ConstructorSplat& c);
bool pushLiteral(const Literal& l);
bool pushVariableReference(const VariableReference& v);
/** Copies an expression from the value stack and copies it into slots. */
void copyToSlotRange(SlotRange r) { fBuilder.copy_stack_to_slots(r); }
/** Pops an expression from the value stack and copies it into slots. */
void popToSlotRange(SlotRange r) { fBuilder.pop_slots(r); }
void popToSlotRangeUnmasked(SlotRange r) { fBuilder.pop_slots_unmasked(r); }
/** Pops an expression from the value stack and discards it. */
void discardExpression(int slots) { fBuilder.discard_stack(slots); }
/** Zeroes out a range of slots. */
void zeroSlotRangeUnmasked(SlotRange r) { fBuilder.zero_slots_unmasked(r); }
/** Expression utilities. */
struct BinaryOps {
BuilderOp fFloatOp;
BuilderOp fSignedOp;
BuilderOp fUnsignedOp;
BuilderOp fBooleanOp;
};
bool assign(const Expression& e);
bool binaryOp(SkSL::Type::NumberKind numberKind, int slots, const BinaryOps& ops);
void foldWithOp(BuilderOp op, int elements);
private:
const SkSL::Program& fProgram;
Builder fBuilder;
SkRPDebugTrace* fDebugTrace = nullptr;
SkTHashMap<const IRNode*, SlotRange> fSlotMap;
int fSlotCount = 0;
SkTArray<SlotRange> fFunctionStack;
};
struct LValue {
virtual ~LValue() = default;
/**
* Returns an LValue for the passed-in expression; if the expression isn't supported as an
* LValue, returns nullptr.
*/
static std::unique_ptr<LValue> Make(const Expression& e);
/** Copies the top-of-stack value into this lvalue, without discarding it from the stack. */
virtual bool store(Generator* gen) = 0;
};
struct VariableLValue : public LValue {
VariableLValue(const Variable* v) : fVariable(v) {}
bool store(Generator* gen) override {
gen->copyToSlotRange(gen->getSlots(*fVariable));
return true;
}
const Variable* fVariable;
};
std::unique_ptr<LValue> LValue::Make(const Expression& e) {
if (e.is<VariableReference>()) {
return std::make_unique<VariableLValue>(e.as<VariableReference>().variable());
}
// TODO(skia:13676): add support for other kinds of lvalues
return nullptr;
}
void Generator::addDebugSlotInfoForGroup(const std::string& varName,
const Type& type,
Position pos,
int* groupIndex,
bool isFunctionReturnValue) {
SkASSERT(fDebugTrace);
switch (type.typeKind()) {
case Type::TypeKind::kArray: {
int nslots = type.columns();
const Type& elemType = type.componentType();
for (int slot = 0; slot < nslots; ++slot) {
this->addDebugSlotInfoForGroup(varName + "[" + std::to_string(slot) + "]", elemType,
pos, groupIndex, isFunctionReturnValue);
}
break;
}
case Type::TypeKind::kStruct: {
for (const Type::Field& field : type.fields()) {
this->addDebugSlotInfoForGroup(varName + "." + std::string(field.fName),
*field.fType, pos, groupIndex,
isFunctionReturnValue);
}
break;
}
default:
SkASSERTF(0, "unsupported slot type %d", (int)type.typeKind());
[[fallthrough]];
case Type::TypeKind::kScalar:
case Type::TypeKind::kVector:
case Type::TypeKind::kMatrix: {
Type::NumberKind numberKind = type.componentType().numberKind();
int nslots = type.slotCount();
for (int slot = 0; slot < nslots; ++slot) {
SlotDebugInfo slotInfo;
slotInfo.name = varName;
slotInfo.columns = type.columns();
slotInfo.rows = type.rows();
slotInfo.componentIndex = slot;
slotInfo.groupIndex = (*groupIndex)++;
slotInfo.numberKind = numberKind;
slotInfo.pos = pos;
slotInfo.fnReturnValue = isFunctionReturnValue ? 1 : -1;
fDebugTrace->fSlotInfo.push_back(std::move(slotInfo));
}
break;
}
}
}
void Generator::addDebugSlotInfo(const std::string& varName,
const Type& type,
Position pos,
bool isFunctionReturnValue) {
int groupIndex = 0;
this->addDebugSlotInfoForGroup(varName, type, pos, &groupIndex, isFunctionReturnValue);
SkASSERT((size_t)groupIndex == type.slotCount());
}
SlotRange Generator::createSlots(int numSlots) {
SlotRange range = {fSlotCount, numSlots};
fSlotCount += numSlots;
return range;
}
SlotRange Generator::createSlots(std::string name,
const Type& type,
Position pos,
bool isFunctionReturnValue) {
size_t nslots = type.slotCount();
if (nslots == 0) {
return {};
}
if (fDebugTrace) {
// Our debug slot-info table should have the same length as the actual slot table.
SkASSERT(fDebugTrace->fSlotInfo.size() == (size_t)fSlotCount);
// Append slot names and types to our debug slot-info table.
fDebugTrace->fSlotInfo.reserve(fSlotCount + nslots);
this->addDebugSlotInfo(name, type, pos, isFunctionReturnValue);
// Confirm that we added the expected number of slots.
SkASSERT(fDebugTrace->fSlotInfo.size() == (size_t)(fSlotCount + nslots));
}
return this->createSlots(nslots);
}
SlotRange Generator::getSlots(const Variable& v) {
SlotRange* entry = fSlotMap.find(&v);
if (entry != nullptr) {
return *entry;
}
SlotRange range = this->createSlots(std::string(v.name()),
v.type(),
v.fPosition,
/*isFunctionReturnValue=*/false);
fSlotMap.set(&v, range);
return range;
}
SlotRange Generator::getFunctionSlots(const IRNode& callSite, const FunctionDeclaration& f) {
SlotRange* entry = fSlotMap.find(&callSite);
if (entry != nullptr) {
return *entry;
}
SlotRange range = this->createSlots("[" + std::string(f.name()) + "].result",
f.returnType(),
f.fPosition,
/*isFunctionReturnValue=*/true);
fSlotMap.set(&callSite, range);
return range;
}
int Generator::getDebugFunctionInfo(const FunctionDeclaration& decl) {
SkASSERT(fDebugTrace);
std::string name = decl.description();
// When generating the debug trace, we typically mark every function as `noinline`. This makes
// the trace more confusing, since this isn't in the source program, so remove it.
static constexpr std::string_view kNoInline = "noinline ";
if (skstd::starts_with(name, kNoInline)) {
name = name.substr(kNoInline.size());
}
// Look for a matching FunctionDebugInfo slot.
for (size_t index = 0; index < fDebugTrace->fFuncInfo.size(); ++index) {
if (fDebugTrace->fFuncInfo[index].name == name) {
return index;
}
}
// We've never called this function before; create a new slot to hold its information.
int slot = (int)fDebugTrace->fFuncInfo.size();
fDebugTrace->fFuncInfo.push_back(FunctionDebugInfo{std::move(name)});
return slot;
}
std::optional<SlotRange> Generator::writeFunction(const IRNode& callSite,
const FunctionDefinition& function,
SkSpan<const SlotRange> args) {
[[maybe_unused]] int funcIndex = -1;
if (fDebugTrace) {
funcIndex = this->getDebugFunctionInfo(function.declaration());
SkASSERT(funcIndex >= 0);
// TODO(debugger): add trace for function-enter
}
fFunctionStack.push_back(this->getFunctionSlots(callSite, function.declaration()));
if (!this->writeStatement(*function.body())) {
return std::nullopt;
}
SlotRange functionResult = fFunctionStack.back();
fFunctionStack.pop_back();
if (fDebugTrace) {
// TODO(debugger): add trace for function-exit
}
return functionResult;
}
bool Generator::writeStatement(const Statement& s) {
switch (s.kind()) {
case Statement::Kind::kBlock:
return this->writeBlock(s.as<Block>());
case Statement::Kind::kExpression:
return this->writeExpressionStatement(s.as<ExpressionStatement>());
case Statement::Kind::kIf:
return this->writeIfStatement(s.as<IfStatement>());
case Statement::Kind::kNop:
return true;
case Statement::Kind::kReturn:
return this->writeReturnStatement(s.as<ReturnStatement>());
case Statement::Kind::kVarDeclaration:
return this->writeVarDeclaration(s.as<VarDeclaration>());
default:
// Unsupported statement
return false;
}
}
bool Generator::writeBlock(const Block& b) {
for (const std::unique_ptr<Statement>& stmt : b.children()) {
if (!this->writeStatement(*stmt)) {
return false;
}
}
return true;
}
bool Generator::writeExpressionStatement(const ExpressionStatement& e) {
if (!this->pushExpression(*e.expression())) {
return false;
}
this->discardExpression(e.expression()->type().slotCount());
return true;
}
bool Generator::writeIfStatement(const IfStatement& i) {
if (!this->pushExpression(*i.test())) {
return false;
}
// Apply the test-expression as a condition, then run the if-true branch.
fBuilder.push_condition_mask();
if (!this->writeStatement(*i.ifTrue())) {
return false;
}
fBuilder.pop_condition_mask();
if (i.ifFalse()) {
// The test condition is still at the top of the stack. Negate it, apply it as a condition
// mask again, and run the if-false branch.
fBuilder.unary_op(BuilderOp::bitwise_not, /*slots=*/1);
fBuilder.push_condition_mask();
if (!this->writeStatement(*i.ifFalse())) {
return false;
}
fBuilder.pop_condition_mask();
}
// Jettison the test condition.
this->discardExpression(/*slots=*/1);
return true;
}
bool Generator::writeReturnStatement(const ReturnStatement& r) {
if (r.expression()) {
if (!this->pushExpression(*r.expression())) {
return false;
}
this->popToSlotRange(fFunctionStack.back());
}
fBuilder.update_return_mask();
return true;
}
bool Generator::writeVarDeclaration(const VarDeclaration& v) {
if (v.value()) {
if (!this->pushExpression(*v.value())) {
return false;
}
this->popToSlotRangeUnmasked(this->getSlots(*v.var()));
} else {
this->zeroSlotRangeUnmasked(this->getSlots(*v.var()));
}
return true;
}
bool Generator::pushExpression(const Expression& e) {
switch (e.kind()) {
case Expression::Kind::kBinary:
return this->pushBinaryExpression(e.as<BinaryExpression>());
case Expression::Kind::kConstructorCompound:
return this->pushConstructorCompound(e.as<ConstructorCompound>());
case Expression::Kind::kConstructorSplat:
return this->pushConstructorSplat(e.as<ConstructorSplat>());
case Expression::Kind::kLiteral:
return this->pushLiteral(e.as<Literal>());
case Expression::Kind::kVariableReference:
return this->pushVariableReference(e.as<VariableReference>());
default:
// Unsupported expression
return false;
}
}
bool Generator::binaryOp(SkSL::Type::NumberKind numberKind, int slots, const BinaryOps& ops) {
BuilderOp op = BuilderOp::unsupported;
switch (numberKind) {
case Type::NumberKind::kFloat: op = ops.fFloatOp; break;
case Type::NumberKind::kSigned: op = ops.fSignedOp; break;
case Type::NumberKind::kUnsigned: op = ops.fUnsignedOp; break;
case Type::NumberKind::kBoolean: op = ops.fBooleanOp; break;
default: SkUNREACHABLE;
}
if (op == BuilderOp::unsupported) {
return false;
}
fBuilder.binary_op(op, slots);
return true;
}
bool Generator::assign(const Expression& e) {
std::unique_ptr<LValue> lvalue = LValue::Make(e);
return lvalue && lvalue->store(this);
}
void Generator::foldWithOp(BuilderOp op, int elements) {
// Fold the top N elements on the stack using an op, e.g. (A && (B && C)) -> D.
for (; elements > 1; elements--) {
fBuilder.binary_op(op, /*slots=*/1);
}
}
bool Generator::pushBinaryExpression(const BinaryExpression& e) {
// TODO: add support for non-matching types (e.g. matrix-vector ops)
if (!e.left()->type().matches(e.right()->type())) {
return false;
}
// Handle simple assignment (`var = expr`).
if (e.getOperator().kind() == OperatorKind::EQ) {
return this->pushExpression(*e.right()) &&
this->assign(*e.left());
}
const Type& type = e.left()->type();
Type::NumberKind numberKind = type.componentType().numberKind();
Operator basicOp = e.getOperator().removeAssignment();
switch (basicOp.kind()) {
case OperatorKind::GT:
case OperatorKind::GTEQ:
// We replace `x > y` with `y < x`, and `x >= y` with `y <= x`.
this->pushExpression(*e.right());
this->pushExpression(*e.left());
break;
default:
this->pushExpression(*e.left());
this->pushExpression(*e.right());
break;
}
switch (basicOp.kind()) {
case OperatorKind::PLUS: {
static constexpr auto kPlus = BinaryOps{BuilderOp::add_n_floats,
BuilderOp::add_n_ints,
BuilderOp::add_n_ints,
BuilderOp::unsupported};
if (!this->binaryOp(numberKind, type.slotCount(), kPlus)) {
return false;
}
break;
}
case OperatorKind::LT:
case OperatorKind::GT: {
// TODO(skia:13676): add support for unsigned <
static constexpr auto kLessThan = BinaryOps{BuilderOp::cmplt_n_floats,
BuilderOp::cmplt_n_ints,
BuilderOp::unsupported,
BuilderOp::unsupported};
if (!this->binaryOp(numberKind, type.slotCount(), kLessThan)) {
return false;
}
SkASSERT(type.slotCount() == 1); // operator< only works with scalar types
break;
}
case OperatorKind::LTEQ:
case OperatorKind::GTEQ: {
// TODO(skia:13676): add support for unsigned <=
static constexpr auto kLessThanEquals = BinaryOps{BuilderOp::cmple_n_floats,
BuilderOp::cmple_n_ints,
BuilderOp::unsupported,
BuilderOp::unsupported};
if (!this->binaryOp(numberKind, type.slotCount(), kLessThanEquals)) {
return false;
}
SkASSERT(type.slotCount() == 1); // operator<= only works with scalar types
break;
}
case OperatorKind::EQEQ: {
static constexpr auto kEquals = BinaryOps{BuilderOp::cmpeq_n_floats,
BuilderOp::cmpeq_n_ints,
BuilderOp::cmpeq_n_ints,
BuilderOp::cmpeq_n_ints};
if (!this->binaryOp(numberKind, type.slotCount(), kEquals)) {
return false;
}
this->foldWithOp(BuilderOp::bitwise_and, type.slotCount()); // fold vector result
break;
}
case OperatorKind::NEQ: {
static constexpr auto kNotEquals = BinaryOps{BuilderOp::cmpne_n_floats,
BuilderOp::cmpne_n_ints,
BuilderOp::cmpne_n_ints,
BuilderOp::cmpne_n_ints};
if (!this->binaryOp(numberKind, type.slotCount(), kNotEquals)) {
return false;
}
this->foldWithOp(BuilderOp::bitwise_or, type.slotCount()); // fold vector result
break;
}
default:
return false;
}
// Handle compound assignment (`var *= expr`).
if (e.getOperator().isAssignment()) {
return this->assign(*e.left());
}
return true;
}
bool Generator::pushConstructorCompound(const ConstructorCompound& c) {
for (const std::unique_ptr<Expression> &arg : c.arguments()) {
if (!this->pushExpression(*arg)) {
return false;
}
}
return true;
}
bool Generator::pushConstructorSplat(const ConstructorSplat& c) {
if (!this->pushExpression(*c.argument())) {
return false;
}
fBuilder.duplicate(c.type().slotCount() - 1);
return true;
}
bool Generator::pushLiteral(const Literal& l) {
switch (l.type().numberKind()) {
case Type::NumberKind::kFloat:
fBuilder.push_literal_f(l.floatValue());
return true;
case Type::NumberKind::kSigned:
fBuilder.push_literal_i(l.intValue());
return true;
case Type::NumberKind::kUnsigned:
fBuilder.push_literal_u(l.intValue());
return true;
case Type::NumberKind::kBoolean:
fBuilder.push_literal_i(l.boolValue() ? ~0 : 0);
return true;
default:
SkUNREACHABLE;
}
}
bool Generator::pushVariableReference(const VariableReference& v) {
fBuilder.push_slots(this->getSlots(*v.variable()));
return true;
}
bool Generator::writeProgram(const FunctionDefinition& function) {
if (fDebugTrace) {
// Copy the program source into the debug info so that it will be written in the trace file.
fDebugTrace->setSource(*fProgram.fSource);
}
// Assign slots to the parameters of main; copy src and dst into those slots as appropriate.
SkSTArray<2, SlotRange> args;
for (const SkSL::Variable* param : function.declaration().parameters()) {
switch (param->modifiers().fLayout.fBuiltin) {
case SK_MAIN_COORDS_BUILTIN: {
// Coordinates are passed via RG.
SlotRange fragCoord = this->getSlots(*param);
SkASSERT(fragCoord.count == 2);
fBuilder.store_src_rg(fragCoord);
args.push_back(fragCoord);
break;
}
case SK_INPUT_COLOR_BUILTIN: {
// Input colors are passed via RGBA.
SlotRange srcColor = this->getSlots(*param);
SkASSERT(srcColor.count == 4);
fBuilder.store_src(srcColor);
args.push_back(srcColor);
break;
}
case SK_DEST_COLOR_BUILTIN: {
// Dest colors are passed via dRGBA.
SlotRange destColor = this->getSlots(*param);
SkASSERT(destColor.count == 4);
fBuilder.store_dst(destColor);
args.push_back(destColor);
break;
}
default: {
SkDEBUGFAIL("Invalid parameter to main()");
return false;
}
}
}
// Initialize the program.
fBuilder.init_lane_masks();
// Invoke main().
std::optional<SlotRange> mainResult = this->writeFunction(function, function, args);
if (!mainResult.has_value()) {
return false;
}
// Move the result of main() from slots into RGBA. Allow dRGBA to remain in a trashed state.
SkASSERT(mainResult->count == 4);
fBuilder.load_src(*mainResult);
return true;
}
} // namespace RP
std::unique_ptr<RP::Program> MakeRasterPipelineProgram(const SkSL::Program& program,
const FunctionDefinition& function,
SkRPDebugTrace* debugTrace) {
// TODO(skia:13676): add mechanism for uniform passing
RP::Generator generator(program, debugTrace);
if (!generator.writeProgram(function)) {
return nullptr;
}
return generator.builder()->finish(generator.slotCount(), debugTrace);
}
} // namespace SkSL