/* * 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 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 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(&compiler); * std::unique_ptr 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 (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, LLVMSharedModuleRef sharedModule, LLVMOrcJITStackRef jitStack) : fProgram(std::move(program)) , fSharedModule(sharedModule) , fJITStack(jitStack) {} std::unique_ptr 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 compile(std::unique_ptr 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 parameters); void loadBuiltinFunctions(); void setBlock(LLVMBuilderRef builder, LLVMBasicBlockRef block); LLVMTypeRef getType(const Type& type); TypeKind typeKind(const Type& type); std::unique_ptr 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 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::map fFunctions; std::map 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 fPromotedParameters; std::vector fBreakTarget; std::vector 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