blob: 43ccbec8a7f2deb033015e93266ccd6028e7a13a [file] [log] [blame]
/*
* Copyright 2019 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SKSL_BYTECODEGENERATOR
#define SKSL_BYTECODEGENERATOR
#include <algorithm>
#include <stack>
#include <unordered_map>
#include "src/sksl/SkSLByteCode.h"
#include "src/sksl/SkSLCodeGenerator.h"
#include "src/sksl/ir/SkSLBinaryExpression.h"
#include "src/sksl/ir/SkSLBlock.h"
#include "src/sksl/ir/SkSLBoolLiteral.h"
#include "src/sksl/ir/SkSLBreakStatement.h"
#include "src/sksl/ir/SkSLConstructor.h"
#include "src/sksl/ir/SkSLContinueStatement.h"
#include "src/sksl/ir/SkSLDoStatement.h"
#include "src/sksl/ir/SkSLExpressionStatement.h"
#include "src/sksl/ir/SkSLExternalFunctionCall.h"
#include "src/sksl/ir/SkSLExternalValueReference.h"
#include "src/sksl/ir/SkSLFieldAccess.h"
#include "src/sksl/ir/SkSLFloatLiteral.h"
#include "src/sksl/ir/SkSLForStatement.h"
#include "src/sksl/ir/SkSLFunctionCall.h"
#include "src/sksl/ir/SkSLFunctionDeclaration.h"
#include "src/sksl/ir/SkSLFunctionDefinition.h"
#include "src/sksl/ir/SkSLIfStatement.h"
#include "src/sksl/ir/SkSLIndexExpression.h"
#include "src/sksl/ir/SkSLIntLiteral.h"
#include "src/sksl/ir/SkSLInterfaceBlock.h"
#include "src/sksl/ir/SkSLNullLiteral.h"
#include "src/sksl/ir/SkSLPostfixExpression.h"
#include "src/sksl/ir/SkSLPrefixExpression.h"
#include "src/sksl/ir/SkSLProgramElement.h"
#include "src/sksl/ir/SkSLReturnStatement.h"
#include "src/sksl/ir/SkSLStatement.h"
#include "src/sksl/ir/SkSLSwitchStatement.h"
#include "src/sksl/ir/SkSLSwizzle.h"
#include "src/sksl/ir/SkSLTernaryExpression.h"
#include "src/sksl/ir/SkSLVarDeclarations.h"
#include "src/sksl/ir/SkSLVariableReference.h"
#include "src/sksl/ir/SkSLWhileStatement.h"
#include "src/sksl/spirv.h"
namespace SkSL {
class ByteCodeGenerator : public CodeGenerator {
public:
class LValue {
public:
LValue(ByteCodeGenerator& generator)
: fGenerator(generator) {}
virtual ~LValue() {}
/**
* Stack before call: ... lvalue
* Stack after call: ... lvalue load
*/
virtual void load() = 0;
/**
* Stack before call: ... lvalue value
* Stack after call: ...
*/
virtual void store(bool discard) = 0;
protected:
ByteCodeGenerator& fGenerator;
};
ByteCodeGenerator(const Context* context, const Program* program, ErrorReporter* errors,
ByteCode* output);
bool generateCode() override;
void write8(uint8_t b);
void write16(uint16_t b);
void write32(uint32_t b);
void write(ByteCodeInstruction inst, int count = kUnusedStackCount);
/**
* Based on 'type', writes the s (signed), u (unsigned), or f (float) instruction.
*/
void writeTypedInstruction(const Type& type, ByteCodeInstruction s, ByteCodeInstruction u,
ByteCodeInstruction f, int count);
static int SlotCount(const Type& type);
private:
static constexpr int kUnusedStackCount = INT32_MAX;
static int StackUsage(ByteCodeInstruction, int count);
// reserves 16 bits in the output code, to be filled in later with an address once we determine
// it
class DeferredLocation {
public:
DeferredLocation(ByteCodeGenerator* generator)
: fGenerator(*generator)
, fOffset(generator->fCode->size()) {
generator->write16(0);
}
#ifdef SK_DEBUG
~DeferredLocation() {
SkASSERT(fSet);
}
#endif
void set() {
int target = fGenerator.fCode->size();
SkASSERT(target <= 65535);
(*fGenerator.fCode)[fOffset] = target;
(*fGenerator.fCode)[fOffset + 1] = target >> 8;
#ifdef SK_DEBUG
fSet = true;
#endif
}
private:
ByteCodeGenerator& fGenerator;
size_t fOffset;
#ifdef SK_DEBUG
bool fSet = false;
#endif
};
// Intrinsics which do not simply map to a single opcode
enum class SpecialIntrinsic {
kAll,
kAny,
kATan,
kClamp,
kDot,
kLength,
kMax,
kMin,
kMix,
kMod,
kNormalize,
kSample,
kSaturate,
kSmoothstep,
kStep,
};
struct Intrinsic {
Intrinsic(SpecialIntrinsic s) : is_special(true), special(s) {}
Intrinsic(ByteCodeInstruction i) : Intrinsic(i, i, i) {}
// Workaround: We should be able to leave special uninitialized here, and were for a long
// time. Unrelated changes have made valgrind suddenly start complaining about us accessing
// uninitialized memory in the code:
// if (intrin.is_special && intrin.special == SpecialIntrinsic::kSample)
// despite intrin.is_special being false at the time and therefore, one would think, not
// actually accessing intrin.special. I'm not sure whether this is a buggy optimization on
// clang's part or a false positive on valgrind's part, but either way initializing the
// field works around it.
Intrinsic(ByteCodeInstruction f,
ByteCodeInstruction s,
ByteCodeInstruction u) : is_special(false), special((SpecialIntrinsic) -1),
inst_f(f), inst_s(s), inst_u(u) {}
bool is_special;
SpecialIntrinsic special;
ByteCodeInstruction inst_f;
ByteCodeInstruction inst_s;
ByteCodeInstruction inst_u;
};
// Similar to Variable::Storage, but locals and parameters are grouped together, and globals
// are further subidivided into uniforms and other (writable) globals.
enum class Storage {
kLocal, // include parameters
kGlobal, // non-uniform globals
kUniform, // uniform globals
kChildFP, // child fragment processors
};
struct Location {
int fSlot;
Storage fStorage;
// Not really invalid, but a "safe" placeholder to be more explicit at call-sites
static Location MakeInvalid() { return { 0, Storage::kLocal }; }
Location makeOnStack() {
SkASSERT(fStorage != Storage::kChildFP);
return { -1, fStorage };
}
bool isOnStack() const { return fSlot < 0; }
Location operator+(int offset) {
SkASSERT(fStorage != Storage::kChildFP);
SkASSERT(fSlot >= 0);
return { fSlot + offset, fStorage };
}
ByteCodeInstruction selectLoad(ByteCodeInstruction local,
ByteCodeInstruction global,
ByteCodeInstruction uniform) const {
switch (fStorage) {
case Storage::kLocal: return local;
case Storage::kGlobal: return global;
case Storage::kUniform: return uniform;
case Storage::kChildFP: ABORT("Trying to load an FP"); break;
}
return local;
}
ByteCodeInstruction selectStore(ByteCodeInstruction local,
ByteCodeInstruction global) const {
switch (fStorage) {
case Storage::kLocal: return local;
case Storage::kGlobal: return global;
case Storage::kUniform: ABORT("Trying to store to a uniform"); break;
case Storage::kChildFP: ABORT("Trying to store an FP"); break;
}
return local;
}
};
/**
* Returns the local slot into which var should be stored, allocating a new slot if it has not
* already been assigned one. Compound variables (e.g. vectors) will consume more than one local
* slot, with the getLocation return value indicating where the first element should be stored.
*/
Location getLocation(const Variable& var);
/**
* As above, but computes the (possibly dynamic) address of an expression involving indexing &
* field access. If the address is known, it's returned. If not, -1 is returned, and the
* location will be left on the top of the stack.
*/
Location getLocation(const Expression& expr);
void gatherUniforms(const Type& type, const String& name);
std::unique_ptr<ByteCodeFunction> writeFunction(const FunctionDefinition& f);
void writeVarDeclaration(const VarDeclaration& decl);
void writeVariableExpression(const Expression& expr);
void writeExpression(const Expression& expr, bool discard = false);
/**
* Pushes whatever values are required by the lvalue onto the stack, and returns an LValue
* permitting loads and stores to it.
*/
std::unique_ptr<LValue> getLValue(const Expression& expr);
void writeIntrinsicCall(const FunctionCall& c);
void writeFunctionCall(const FunctionCall& c);
void writeConstructor(const Constructor& c);
void writeExternalFunctionCall(const ExternalFunctionCall& c);
void writeExternalValue(const ExternalValueReference& r);
void writeSwizzle(const Swizzle& swizzle);
bool writeBinaryExpression(const BinaryExpression& b, bool discard);
void writeTernaryExpression(const TernaryExpression& t);
bool writePrefixExpression(const PrefixExpression& p, bool discard);
bool writePostfixExpression(const PostfixExpression& p, bool discard);
void writeNullLiteral(const NullLiteral& n);
void writeBoolLiteral(const BoolLiteral& b);
void writeIntLiteral(const IntLiteral& i);
void writeFloatLiteral(const FloatLiteral& f);
void writeStatement(const Statement& s);
void writeBlock(const Block& b);
void writeBreakStatement(const BreakStatement& b);
void writeContinueStatement(const ContinueStatement& c);
void writeIfStatement(const IfStatement& stmt);
void writeForStatement(const ForStatement& f);
void writeWhileStatement(const WhileStatement& w);
void writeDoStatement(const DoStatement& d);
void writeSwitchStatement(const SwitchStatement& s);
void writeReturnStatement(const ReturnStatement& r);
// Some intrinsics are complex enough to warrant their own functions:
void writeSmoothstep(const ExpressionArray& args);
// updates the current set of breaks to branch to the current location
void setBreakTargets();
// updates the current set of continues to branch to the current location
void setContinueTargets();
void enterLoop() {
fLoopCount++;
fMaxLoopCount = std::max(fMaxLoopCount, fLoopCount);
}
void exitLoop() {
SkASSERT(fLoopCount > 0);
fLoopCount--;
}
void enterCondition() {
fConditionCount++;
fMaxConditionCount = std::max(fMaxConditionCount, fConditionCount);
}
void exitCondition() {
SkASSERT(fConditionCount > 0);
fConditionCount--;
}
const Context& fContext;
ByteCode* fOutput;
const FunctionDefinition* fFunction;
std::vector<uint8_t>* fCode;
std::vector<const Variable*> fLocals;
std::stack<std::vector<DeferredLocation>> fContinueTargets;
std::stack<std::vector<DeferredLocation>> fBreakTargets;
std::vector<const FunctionDefinition*> fFunctions;
int fParameterCount;
int fStackCount;
int fMaxStackCount;
int fLoopCount;
int fMaxLoopCount;
int fConditionCount;
int fMaxConditionCount;
// Holds variables synthesized during output, for lifetime purposes
SymbolTable fSynthetics;
const std::unordered_map<String, Intrinsic> fIntrinsics;
friend class DeferredLocation;
friend class ByteCodeExpressionLValue;
friend class ByteCodeSwizzleLValue;
using INHERITED = CodeGenerator;
};
} // namespace SkSL
#endif