| /* |
| * 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/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 { |
| |
| struct AppendStage; |
| |
| /** |
| * 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::kPipelineStage_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 fInt1VectorType; |
| LLVMTypeRef fInt1Vector2Type; |
| LLVMTypeRef fInt1Vector3Type; |
| LLVMTypeRef fInt1Vector4Type; |
| 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::unordered_map<const FunctionDeclaration*, LLVMValueRef> fFunctions; |
| std::unordered_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 fFoldAnd2Func; |
| LLVMValueRef fFoldOr2Func; |
| LLVMValueRef fFoldAnd3Func; |
| LLVMValueRef fFoldOr3Func; |
| LLVMValueRef fFoldAnd4Func; |
| LLVMValueRef fFoldOr4Func; |
| LLVMValueRef fAppendFunc; |
| LLVMValueRef fAppendCallbackFunc; |
| LLVMValueRef fDebugFunc; |
| }; |
| |
| } // namespace |
| |
| #endif // SK_LLVM_AVAILABLE |
| |
| #endif // SKSL_JIT |