diff options
Diffstat (limited to 'src/sksl/SkSLJIT.h')
-rw-r--r-- | src/sksl/SkSLJIT.h | 344 |
1 files changed, 344 insertions, 0 deletions
diff --git a/src/sksl/SkSLJIT.h b/src/sksl/SkSLJIT.h new file mode 100644 index 0000000000..b23e31237f --- /dev/null +++ b/src/sksl/SkSLJIT.h @@ -0,0 +1,344 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_JIT +#define SKSL_JIT + +#ifdef SK_LLVM_AVAILABLE + +#include "ir/SkSLAppendStage.h" +#include "ir/SkSLBinaryExpression.h" +#include "ir/SkSLBreakStatement.h" +#include "ir/SkSLContinueStatement.h" +#include "ir/SkSLExpression.h" +#include "ir/SkSLDoStatement.h" +#include "ir/SkSLForStatement.h" +#include "ir/SkSLFunctionCall.h" +#include "ir/SkSLFunctionDefinition.h" +#include "ir/SkSLIfStatement.h" +#include "ir/SkSLIndexExpression.h" +#include "ir/SkSLPrefixExpression.h" +#include "ir/SkSLPostfixExpression.h" +#include "ir/SkSLProgram.h" +#include "ir/SkSLReturnStatement.h" +#include "ir/SkSLStatement.h" +#include "ir/SkSLSwizzle.h" +#include "ir/SkSLTernaryExpression.h" +#include "ir/SkSLVarDeclarationsStatement.h" +#include "ir/SkSLVariableReference.h" +#include "ir/SkSLWhileStatement.h" + +#include "llvm-c/Analysis.h" +#include "llvm-c/Core.h" +#include "llvm-c/OrcBindings.h" +#include "llvm-c/Support.h" +#include "llvm-c/Target.h" +#include "llvm-c/Transforms/PassManagerBuilder.h" +#include "llvm-c/Types.h" +#include <stack> + +class SkRasterPipeline; + +namespace SkSL { + +/** + * A just-in-time compiler for SkSL code which uses an LLVM backend. Only available when the + * skia_llvm_path gn arg is set. + * + * Example of using SkSLJIT to set up an SkJumper pipeline stage: + * + * #ifdef SK_LLVM_AVAILABLE + * SkSL::Compiler compiler; + * SkSL::Program::Settings settings; + * std::unique_ptr<SkSL::Program> program = compiler.convertProgram(SkSL::Program::kCPU_Kind, + * "void swap(int x, int y, inout float4 color) {" + * " color.rb = color.br;" + * "}", + * settings); + * if (!program) { + * printf("%s\n", compiler.errorText().c_str()); + * abort(); + * } + * SkSL::JIT& jit = *scratch->make<SkSL::JIT>(&compiler); + * std::unique_ptr<SkSL::JIT::Module> module = jit.compile(std::move(program)); + * void* func = module->getJumperStage("swap"); + * p->append(func, nullptr); + * #endif + */ +class JIT { + typedef int StackIndex; + +public: + class Module { + public: + /** + * Returns the address of a symbol in the module. + */ + void* getSymbol(const char* name); + + /** + * Returns the address of a function as an SkJumper pipeline stage. The function must have + * the signature void <name>(int x, int y, inout float4 color). The returned function will + * have the correct signature to function as an SkJumper stage (meaning it will actually + * have a different signature at runtime, accepting vector parameters and operating on + * multiple pixels simultaneously as is normal for SkJumper stages). + */ + void* getJumperStage(const char* name); + + ~Module() { + LLVMOrcDisposeSharedModuleRef(fSharedModule); + } + + private: + Module(std::unique_ptr<Program> program, + LLVMSharedModuleRef sharedModule, + LLVMOrcJITStackRef jitStack) + : fProgram(std::move(program)) + , fSharedModule(sharedModule) + , fJITStack(jitStack) {} + + std::unique_ptr<Program> fProgram; + LLVMSharedModuleRef fSharedModule; + LLVMOrcJITStackRef fJITStack; + + friend class JIT; + }; + + JIT(Compiler* compiler); + + ~JIT(); + + /** + * Just-in-time compiles an SkSL program and returns the resulting Module. The JIT must not be + * destroyed before all of its Modules are destroyed. + */ + std::unique_ptr<Module> compile(std::unique_ptr<Program> program); + +private: + static constexpr int CHANNELS = 4; + + enum TypeKind { + kFloat_TypeKind, + kInt_TypeKind, + kUInt_TypeKind, + kBool_TypeKind + }; + + class LValue { + public: + virtual ~LValue() {} + + virtual LLVMValueRef load(LLVMBuilderRef builder) = 0; + + virtual void store(LLVMBuilderRef builder, LLVMValueRef value) = 0; + }; + + void addBuiltinFunction(const char* ourName, const char* realName, LLVMTypeRef returnType, + std::vector<LLVMTypeRef> parameters); + + void loadBuiltinFunctions(); + + void setBlock(LLVMBuilderRef builder, LLVMBasicBlockRef block); + + LLVMTypeRef getType(const Type& type); + + TypeKind typeKind(const Type& type); + + std::unique_ptr<LValue> getLValue(LLVMBuilderRef builder, const Expression& expr); + + void vectorize(LLVMBuilderRef builder, LLVMValueRef* value, int columns); + + void vectorize(LLVMBuilderRef builder, const BinaryExpression& b, LLVMValueRef* left, + LLVMValueRef* right); + + LLVMValueRef compileBinary(LLVMBuilderRef builder, const BinaryExpression& b); + + LLVMValueRef compileConstructor(LLVMBuilderRef builder, const Constructor& c); + + LLVMValueRef compileFunctionCall(LLVMBuilderRef builder, const FunctionCall& fc); + + LLVMValueRef compileIndex(LLVMBuilderRef builder, const IndexExpression& v); + + LLVMValueRef compilePostfix(LLVMBuilderRef builder, const PostfixExpression& p); + + LLVMValueRef compilePrefix(LLVMBuilderRef builder, const PrefixExpression& p); + + LLVMValueRef compileSwizzle(LLVMBuilderRef builder, const Swizzle& s); + + LLVMValueRef compileVariableReference(LLVMBuilderRef builder, const VariableReference& v); + + LLVMValueRef compileTernary(LLVMBuilderRef builder, const TernaryExpression& t); + + LLVMValueRef compileExpression(LLVMBuilderRef builder, const Expression& expr); + + void appendStage(LLVMBuilderRef builder, const AppendStage& a); + + void compileBlock(LLVMBuilderRef builder, const Block& block); + + void compileBreak(LLVMBuilderRef builder, const BreakStatement& b); + + void compileContinue(LLVMBuilderRef builder, const ContinueStatement& c); + + void compileDo(LLVMBuilderRef builder, const DoStatement& d); + + void compileFor(LLVMBuilderRef builder, const ForStatement& f); + + void compileIf(LLVMBuilderRef builder, const IfStatement& i); + + void compileReturn(LLVMBuilderRef builder, const ReturnStatement& r); + + void compileVarDeclarations(LLVMBuilderRef builder, const VarDeclarationsStatement& decls); + + void compileWhile(LLVMBuilderRef builder, const WhileStatement& w); + + void compileStatement(LLVMBuilderRef builder, const Statement& stmt); + + // The "Vector" variants of functions attempt to compile a given expression or statement as part + // of a vectorized SkJumper stage function - that is, with r, g, b, and a each being vectors of + // fVectorCount floats. So a statement like "color.r = 0;" looks like it modifies a single + // channel of a single pixel, but the compiled code will actually modify the red channel of + // fVectorCount pixels at once. + // + // As not everything can be vectorized, these calls return a bool to indicate whether they were + // successful. If anything anywhere in the function cannot be vectorized, the JIT will fall back + // to looping over the pixels instead. + // + // Since we process multiple pixels at once, and each pixel consists of multiple color channels, + // expressions may effectively result in a vector-of-vectors. We produce zero to four outputs + // when compiling expression, each of which is a vector, so that e.g. float2(1, 0) actually + // produces two vectors, one containing all 1s, the other all 0s. The out parameter always + // allows for 4 channels, but the functions produce 0 to 4 channels depending on the type they + // are operating on. Thus evaluating "color.rgb" actually fills in out[0] through out[2], + // leaving out[3] uninitialized. + // As the number of outputs can be inferred from the type of the expression, it is not + // explicitly signalled anywhere. + bool compileVectorBinary(LLVMBuilderRef builder, const BinaryExpression& b, + LLVMValueRef out[CHANNELS]); + + bool compileVectorConstructor(LLVMBuilderRef builder, const Constructor& c, + LLVMValueRef out[CHANNELS]); + + bool compileVectorFloatLiteral(LLVMBuilderRef builder, const FloatLiteral& f, + LLVMValueRef out[CHANNELS]); + + bool compileVectorSwizzle(LLVMBuilderRef builder, const Swizzle& s, + LLVMValueRef out[CHANNELS]); + + bool compileVectorVariableReference(LLVMBuilderRef builder, const VariableReference& v, + LLVMValueRef out[CHANNELS]); + + bool compileVectorExpression(LLVMBuilderRef builder, const Expression& expr, + LLVMValueRef out[CHANNELS]); + + bool getVectorLValue(LLVMBuilderRef builder, const Expression& e, LLVMValueRef out[CHANNELS]); + + /** + * Evaluates the left and right operands of a binary operation, promoting one of them to a + * vector if necessary to make the types match. + */ + bool getVectorBinaryOperands(LLVMBuilderRef builder, const Expression& left, + LLVMValueRef outLeft[CHANNELS], const Expression& right, + LLVMValueRef outRight[CHANNELS]); + + bool compileVectorStatement(LLVMBuilderRef builder, const Statement& stmt); + + /** + * Returns true if this function has the signature void(int, int, inout float4) and thus can be + * used as an SkJumper stage. + */ + bool hasStageSignature(const FunctionDeclaration& f); + + /** + * Attempts to compile a vectorized stage function, returning true on success. A stage function + * of e.g. "color.r = 0;" will produce code which sets the entire red vector to zeros in a + * single instruction, thus calculating several pixels at once. + */ + bool compileStageFunctionVector(const FunctionDefinition& f, LLVMValueRef newFunc); + + /** + * Fallback function which loops over the pixels, for when vectorization fails. A stage function + * of e.g. "color.r = 0;" will produce a loop which iterates over the entries in the red vector, + * setting each one to zero individually. + */ + void compileStageFunctionLoop(const FunctionDefinition& f, LLVMValueRef newFunc); + + /** + * Called when compiling a function which has the signature of an SkJumper stage. Produces a + * version of the function which can be plugged into SkJumper (thus having a signature which + * accepts four vectors, one for each color channel, containing the color data of multiple + * pixels at once). To go from SkSL code which operates on a single pixel at a time to CPU code + * which operates on multiple pixels at once, the code is either vectorized using + * compileStageFunctionVector or wrapped in a loop using compileStageFunctionLoop. + */ + LLVMValueRef compileStageFunction(const FunctionDefinition& f); + + /** + * Compiles an SkSL function to an LLVM function. If the function has the signature of an + * SkJumper stage, it will *also* be compiled by compileStageFunction, resulting in both a stage + * and non-stage version of the function. + */ + LLVMValueRef compileFunction(const FunctionDefinition& f); + + void createModule(); + + void optimize(); + + bool isColorRef(const Expression& expr); + + static uint64_t resolveSymbol(const char* name, JIT* jit); + + const char* fCPU; + int fVectorCount; + Compiler& fCompiler; + std::unique_ptr<Program> fProgram; + LLVMContextRef fContext; + LLVMModuleRef fModule; + LLVMSharedModuleRef fSharedModule; + LLVMOrcJITStackRef fJITStack; + LLVMValueRef fCurrentFunction; + LLVMBasicBlockRef fAllocaBlock; + LLVMBasicBlockRef fCurrentBlock; + LLVMTypeRef fVoidType; + LLVMTypeRef fInt1Type; + LLVMTypeRef fInt8Type; + LLVMTypeRef fInt8PtrType; + LLVMTypeRef fInt32Type; + LLVMTypeRef fInt32VectorType; + LLVMTypeRef fInt32Vector2Type; + LLVMTypeRef fInt32Vector3Type; + LLVMTypeRef fInt32Vector4Type; + LLVMTypeRef fInt64Type; + LLVMTypeRef fSizeTType; + LLVMTypeRef fFloat32Type; + LLVMTypeRef fFloat32VectorType; + LLVMTypeRef fFloat32Vector2Type; + LLVMTypeRef fFloat32Vector3Type; + LLVMTypeRef fFloat32Vector4Type; + // Our SkSL stage functions have a single float4 for color, but the actual SkJumper stage + // function has four separate vectors, one for each channel. These four values are references to + // the red, green, blue, and alpha vectors respectively. + LLVMValueRef fChannels[CHANNELS]; + // when processing a stage function, this points to the SkSL color parameter (an inout float4) + const Variable* fColorParam; + std::map<const FunctionDeclaration*, LLVMValueRef> fFunctions; + std::map<const Variable*, LLVMValueRef> fVariables; + // LLVM function parameters are read-only, so when modifying function parameters we need to + // first promote them to variables. This keeps track of which parameters have been promoted. + std::set<const Variable*> fPromotedParameters; + std::vector<LLVMBasicBlockRef> fBreakTarget; + std::vector<LLVMBasicBlockRef> fContinueTarget; + + LLVMValueRef fAppendFunc; + LLVMValueRef fAppendCallbackFunc; + LLVMValueRef fDebugFunc; +}; + +} // namespace + +#endif // SK_LLVM_AVAILABLE + +#endif // SKSL_JIT |