summaryrefslogtreecommitdiffstats
path: root/gfx/skia/skia/src/sksl
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/skia/skia/src/sksl')
-rw-r--r--gfx/skia/skia/src/sksl/GLSL.std.450.h131
-rw-r--r--gfx/skia/skia/src/sksl/README.md158
-rw-r--r--gfx/skia/skia/src/sksl/SkSLAnalysis.cpp705
-rw-r--r--gfx/skia/skia/src/sksl/SkSLAnalysis.h261
-rw-r--r--gfx/skia/skia/src/sksl/SkSLBuiltinTypes.cpp205
-rw-r--r--gfx/skia/skia/src/sksl/SkSLBuiltinTypes.h167
-rw-r--r--gfx/skia/skia/src/sksl/SkSLCompiler.cpp726
-rw-r--r--gfx/skia/skia/src/sksl/SkSLCompiler.h242
-rw-r--r--gfx/skia/skia/src/sksl/SkSLConstantFolder.cpp884
-rw-r--r--gfx/skia/skia/src/sksl/SkSLConstantFolder.h71
-rw-r--r--gfx/skia/skia/src/sksl/SkSLContext.cpp29
-rw-r--r--gfx/skia/skia/src/sksl/SkSLContext.h49
-rw-r--r--gfx/skia/skia/src/sksl/SkSLErrorReporter.cpp29
-rw-r--r--gfx/skia/skia/src/sksl/SkSLFileOutputStream.h78
-rw-r--r--gfx/skia/skia/src/sksl/SkSLGLSL.h58
-rw-r--r--gfx/skia/skia/src/sksl/SkSLInliner.cpp1062
-rw-r--r--gfx/skia/skia/src/sksl/SkSLInliner.h119
-rw-r--r--gfx/skia/skia/src/sksl/SkSLIntrinsicList.cpp33
-rw-r--r--gfx/skia/skia/src/sksl/SkSLIntrinsicList.h145
-rw-r--r--gfx/skia/skia/src/sksl/SkSLLexer.cpp808
-rw-r--r--gfx/skia/skia/src/sksl/SkSLLexer.h145
-rw-r--r--gfx/skia/skia/src/sksl/SkSLMangler.cpp76
-rw-r--r--gfx/skia/skia/src/sksl/SkSLMangler.h35
-rw-r--r--gfx/skia/skia/src/sksl/SkSLMemoryLayout.h211
-rw-r--r--gfx/skia/skia/src/sksl/SkSLMemoryPool.h44
-rw-r--r--gfx/skia/skia/src/sksl/SkSLModifiersPool.h38
-rw-r--r--gfx/skia/skia/src/sksl/SkSLModuleLoader.cpp444
-rw-r--r--gfx/skia/skia/src/sksl/SkSLModuleLoader.h67
-rw-r--r--gfx/skia/skia/src/sksl/SkSLOperator.cpp384
-rw-r--r--gfx/skia/skia/src/sksl/SkSLOutputStream.cpp41
-rw-r--r--gfx/skia/skia/src/sksl/SkSLOutputStream.h58
-rw-r--r--gfx/skia/skia/src/sksl/SkSLParser.cpp2248
-rw-r--r--gfx/skia/skia/src/sksl/SkSLParser.h369
-rw-r--r--gfx/skia/skia/src/sksl/SkSLPool.cpp97
-rw-r--r--gfx/skia/skia/src/sksl/SkSLPool.h96
-rw-r--r--gfx/skia/skia/src/sksl/SkSLPosition.cpp34
-rw-r--r--gfx/skia/skia/src/sksl/SkSLProgramSettings.h160
-rw-r--r--gfx/skia/skia/src/sksl/SkSLSampleUsage.cpp26
-rw-r--r--gfx/skia/skia/src/sksl/SkSLString.cpp115
-rw-r--r--gfx/skia/skia/src/sksl/SkSLStringStream.h58
-rw-r--r--gfx/skia/skia/src/sksl/SkSLThreadContext.cpp126
-rw-r--r--gfx/skia/skia/src/sksl/SkSLThreadContext.h177
-rw-r--r--gfx/skia/skia/src/sksl/SkSLUtil.cpp89
-rw-r--r--gfx/skia/skia/src/sksl/SkSLUtil.h187
-rw-r--r--gfx/skia/skia/src/sksl/analysis/SkSLCanExitWithoutReturningValue.cpp176
-rw-r--r--gfx/skia/skia/src/sksl/analysis/SkSLCheckProgramStructure.cpp223
-rw-r--r--gfx/skia/skia/src/sksl/analysis/SkSLFinalizationChecks.cpp172
-rw-r--r--gfx/skia/skia/src/sksl/analysis/SkSLGetLoopControlFlowInfo.cpp79
-rw-r--r--gfx/skia/skia/src/sksl/analysis/SkSLGetLoopUnrollInfo.cpp280
-rw-r--r--gfx/skia/skia/src/sksl/analysis/SkSLGetReturnComplexity.cpp131
-rw-r--r--gfx/skia/skia/src/sksl/analysis/SkSLHasSideEffects.cpp65
-rw-r--r--gfx/skia/skia/src/sksl/analysis/SkSLIsConstantExpression.cpp113
-rw-r--r--gfx/skia/skia/src/sksl/analysis/SkSLIsDynamicallyUniformExpression.cpp86
-rw-r--r--gfx/skia/skia/src/sksl/analysis/SkSLIsSameExpressionTree.cpp99
-rw-r--r--gfx/skia/skia/src/sksl/analysis/SkSLIsTrivialExpression.cpp70
-rw-r--r--gfx/skia/skia/src/sksl/analysis/SkSLNoOpErrorReporter.h23
-rw-r--r--gfx/skia/skia/src/sksl/analysis/SkSLProgramUsage.cpp242
-rw-r--r--gfx/skia/skia/src/sksl/analysis/SkSLProgramUsage.h53
-rw-r--r--gfx/skia/skia/src/sksl/analysis/SkSLProgramVisitor.h77
-rw-r--r--gfx/skia/skia/src/sksl/analysis/SkSLSwitchCaseContainsExit.cpp98
-rw-r--r--gfx/skia/skia/src/sksl/analysis/SkSLSymbolTableStackBuilder.cpp63
-rw-r--r--gfx/skia/skia/src/sksl/codegen/SkSLCodeGenerator.h89
-rw-r--r--gfx/skia/skia/src/sksl/codegen/SkSLGLSLCodeGenerator.cpp1774
-rw-r--r--gfx/skia/skia/src/sksl/codegen/SkSLGLSLCodeGenerator.h210
-rw-r--r--gfx/skia/skia/src/sksl/codegen/SkSLMetalCodeGenerator.cpp3226
-rw-r--r--gfx/skia/skia/src/sksl/codegen/SkSLMetalCodeGenerator.h330
-rw-r--r--gfx/skia/skia/src/sksl/codegen/SkSLPipelineStageCodeGenerator.cpp814
-rw-r--r--gfx/skia/skia/src/sksl/codegen/SkSLPipelineStageCodeGenerator.h70
-rw-r--r--gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineBuilder.cpp2861
-rw-r--r--gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineBuilder.h655
-rw-r--r--gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineCodeGenerator.cpp3444
-rw-r--r--gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineCodeGenerator.h32
-rw-r--r--gfx/skia/skia/src/sksl/codegen/SkSLSPIRVCodeGenerator.cpp4365
-rw-r--r--gfx/skia/skia/src/sksl/codegen/SkSLSPIRVCodeGenerator.h601
-rw-r--r--gfx/skia/skia/src/sksl/codegen/SkSLSPIRVtoHLSL.cpp49
-rw-r--r--gfx/skia/skia/src/sksl/codegen/SkSLSPIRVtoHLSL.h19
-rw-r--r--gfx/skia/skia/src/sksl/codegen/SkSLVMCodeGenerator.cpp2302
-rw-r--r--gfx/skia/skia/src/sksl/codegen/SkSLVMCodeGenerator.h79
-rw-r--r--gfx/skia/skia/src/sksl/codegen/SkSLWGSLCodeGenerator.cpp1939
-rw-r--r--gfx/skia/skia/src/sksl/codegen/SkSLWGSLCodeGenerator.h289
-rw-r--r--gfx/skia/skia/src/sksl/dsl/DSLBlock.cpp49
-rw-r--r--gfx/skia/skia/src/sksl/dsl/DSLCase.cpp46
-rw-r--r--gfx/skia/skia/src/sksl/dsl/DSLCore.cpp615
-rw-r--r--gfx/skia/skia/src/sksl/dsl/DSLExpression.cpp295
-rw-r--r--gfx/skia/skia/src/sksl/dsl/DSLFunction.cpp146
-rw-r--r--gfx/skia/skia/src/sksl/dsl/DSLLayout.cpp36
-rw-r--r--gfx/skia/skia/src/sksl/dsl/DSLStatement.cpp67
-rw-r--r--gfx/skia/skia/src/sksl/dsl/DSLType.cpp316
-rw-r--r--gfx/skia/skia/src/sksl/dsl/DSLVar.cpp177
-rw-r--r--gfx/skia/skia/src/sksl/dsl/priv/DSLWriter.cpp132
-rw-r--r--gfx/skia/skia/src/sksl/dsl/priv/DSLWriter.h64
-rw-r--r--gfx/skia/skia/src/sksl/dsl/priv/DSL_priv.h32
-rw-r--r--gfx/skia/skia/src/sksl/generated/sksl_compute.minified.sksl7
-rw-r--r--gfx/skia/skia/src/sksl/generated/sksl_compute.unoptimized.sksl7
-rw-r--r--gfx/skia/skia/src/sksl/generated/sksl_frag.minified.sksl5
-rw-r--r--gfx/skia/skia/src/sksl/generated/sksl_frag.unoptimized.sksl5
-rw-r--r--gfx/skia/skia/src/sksl/generated/sksl_gpu.minified.sksl85
-rw-r--r--gfx/skia/skia/src/sksl/generated/sksl_gpu.unoptimized.sksl107
-rw-r--r--gfx/skia/skia/src/sksl/generated/sksl_graphite_frag.dehydrated.sksl3119
-rw-r--r--gfx/skia/skia/src/sksl/generated/sksl_graphite_frag.minified.sksl179
-rw-r--r--gfx/skia/skia/src/sksl/generated/sksl_graphite_frag.unoptimized.sksl314
-rw-r--r--gfx/skia/skia/src/sksl/generated/sksl_graphite_vert.minified.sksl64
-rw-r--r--gfx/skia/skia/src/sksl/generated/sksl_graphite_vert.unoptimized.sksl121
-rw-r--r--gfx/skia/skia/src/sksl/generated/sksl_public.minified.sksl4
-rw-r--r--gfx/skia/skia/src/sksl/generated/sksl_public.unoptimized.sksl4
-rw-r--r--gfx/skia/skia/src/sksl/generated/sksl_rt_shader.minified.sksl2
-rw-r--r--gfx/skia/skia/src/sksl/generated/sksl_rt_shader.unoptimized.sksl2
-rw-r--r--gfx/skia/skia/src/sksl/generated/sksl_shared.minified.sksl143
-rw-r--r--gfx/skia/skia/src/sksl/generated/sksl_shared.unoptimized.sksl163
-rw-r--r--gfx/skia/skia/src/sksl/generated/sksl_vert.minified.sksl4
-rw-r--r--gfx/skia/skia/src/sksl/generated/sksl_vert.unoptimized.sksl4
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLBinaryExpression.cpp284
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLBinaryExpression.h112
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLBlock.cpp100
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLBlock.h110
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLBreakStatement.h44
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLChildCall.cpp73
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLChildCall.h72
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLConstructor.cpp241
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLConstructor.h143
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLConstructorArray.cpp94
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLConstructorArray.h58
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLConstructorArrayCast.cpp73
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLConstructorArrayCast.h54
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLConstructorCompound.cpp158
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLConstructorCompound.h55
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLConstructorCompoundCast.cpp100
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLConstructorCompoundCast.h52
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLConstructorDiagonalMatrix.cpp45
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLConstructorDiagonalMatrix.h55
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLConstructorMatrixResize.cpp58
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLConstructorMatrixResize.h56
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLConstructorScalarCast.cpp93
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLConstructorScalarCast.h61
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLConstructorSplat.cpp38
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLConstructorSplat.h63
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLConstructorStruct.cpp87
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLConstructorStruct.h58
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLContinueStatement.h44
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLDiscardStatement.cpp33
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLDiscardStatement.h49
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLDoStatement.cpp59
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLDoStatement.h78
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLExpression.cpp50
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLExpression.h143
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLExpressionStatement.cpp57
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLExpressionStatement.h66
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLExtension.h46
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLField.h56
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLFieldAccess.cpp125
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLFieldAccess.h104
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLForStatement.cpp197
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLForStatement.h150
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLFunctionCall.cpp1056
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLFunctionCall.h89
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLFunctionDeclaration.cpp598
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLFunctionDeclaration.h153
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLFunctionDefinition.cpp246
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLFunctionDefinition.h91
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLFunctionPrototype.h55
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLFunctionReference.h55
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLIfStatement.cpp108
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLIfStatement.h91
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLIndexExpression.cpp178
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLIndexExpression.h97
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLInterfaceBlock.cpp132
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLInterfaceBlock.h114
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLLayout.cpp75
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLLiteral.cpp23
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLLiteral.h145
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLMethodReference.h73
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLModifiers.cpp115
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLModifiersDeclaration.h49
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLNop.h48
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLPoison.h36
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLPostfixExpression.cpp50
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLPostfixExpression.h76
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLPrefixExpression.cpp282
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLPrefixExpression.h73
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLProgram.cpp116
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLProgram.h171
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLReturnStatement.h65
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLSetting.cpp94
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLSetting.h75
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLStructDefinition.h66
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLSwitchCase.h86
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLSwitchStatement.cpp275
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLSwitchStatement.h102
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLSwizzle.cpp548
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLSwizzle.h103
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLSymbolTable.cpp122
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLSymbolTable.h214
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLTernaryExpression.cpp136
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLTernaryExpression.h100
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLType.cpp1208
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLType.h600
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLTypeReference.cpp32
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLTypeReference.h70
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLVarDeclarations.cpp468
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLVarDeclarations.h164
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLVariable.cpp212
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLVariable.h179
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLVariableReference.cpp33
-rw-r--r--gfx/skia/skia/src/sksl/ir/SkSLVariableReference.h87
-rw-r--r--gfx/skia/skia/src/sksl/lex/DFA.h37
-rw-r--r--gfx/skia/skia/src/sksl/lex/DFAState.h75
-rw-r--r--gfx/skia/skia/src/sksl/lex/LexUtil.h20
-rw-r--r--gfx/skia/skia/src/sksl/lex/Main.cpp238
-rw-r--r--gfx/skia/skia/src/sksl/lex/NFA.cpp44
-rw-r--r--gfx/skia/skia/src/sksl/lex/NFA.h58
-rw-r--r--gfx/skia/skia/src/sksl/lex/NFAState.h152
-rw-r--r--gfx/skia/skia/src/sksl/lex/NFAtoDFA.h168
-rw-r--r--gfx/skia/skia/src/sksl/lex/RegexNode.cpp123
-rw-r--r--gfx/skia/skia/src/sksl/lex/RegexNode.h79
-rw-r--r--gfx/skia/skia/src/sksl/lex/RegexParser.cpp183
-rw-r--r--gfx/skia/skia/src/sksl/lex/RegexParser.h89
-rw-r--r--gfx/skia/skia/src/sksl/lex/TransitionTable.cpp241
-rw-r--r--gfx/skia/skia/src/sksl/lex/TransitionTable.h18
-rw-r--r--gfx/skia/skia/src/sksl/lex/sksl.lex102
-rw-r--r--gfx/skia/skia/src/sksl/sksl_compute.sksl21
-rw-r--r--gfx/skia/skia/src/sksl/sksl_frag.sksl9
-rw-r--r--gfx/skia/skia/src/sksl/sksl_gpu.sksl324
-rw-r--r--gfx/skia/skia/src/sksl/sksl_graphite_frag.sksl1135
-rw-r--r--gfx/skia/skia/src/sksl/sksl_graphite_vert.sksl535
-rw-r--r--gfx/skia/skia/src/sksl/sksl_public.sksl10
-rw-r--r--gfx/skia/skia/src/sksl/sksl_rt_shader.sksl1
-rw-r--r--gfx/skia/skia/src/sksl/sksl_shared.sksl449
-rw-r--r--gfx/skia/skia/src/sksl/sksl_vert.sksl9
-rw-r--r--gfx/skia/skia/src/sksl/spirv.h870
-rw-r--r--gfx/skia/skia/src/sksl/tracing/SkRPDebugTrace.cpp32
-rw-r--r--gfx/skia/skia/src/sksl/tracing/SkRPDebugTrace.h48
-rw-r--r--gfx/skia/skia/src/sksl/tracing/SkSLDebugInfo.h55
-rw-r--r--gfx/skia/skia/src/sksl/tracing/SkSLTraceHook.cpp35
-rw-r--r--gfx/skia/skia/src/sksl/tracing/SkSLTraceHook.h45
-rw-r--r--gfx/skia/skia/src/sksl/tracing/SkVMDebugTrace.cpp417
-rw-r--r--gfx/skia/skia/src/sksl/tracing/SkVMDebugTrace.h78
-rw-r--r--gfx/skia/skia/src/sksl/tracing/SkVMDebugTracePlayer.cpp284
-rw-r--r--gfx/skia/skia/src/sksl/tracing/SkVMDebugTracePlayer.h136
-rw-r--r--gfx/skia/skia/src/sksl/transform/SkSLAddConstToVarModifiers.cpp44
-rw-r--r--gfx/skia/skia/src/sksl/transform/SkSLEliminateDeadFunctions.cpp79
-rw-r--r--gfx/skia/skia/src/sksl/transform/SkSLEliminateDeadGlobalVariables.cpp90
-rw-r--r--gfx/skia/skia/src/sksl/transform/SkSLEliminateDeadLocalVariables.cpp169
-rw-r--r--gfx/skia/skia/src/sksl/transform/SkSLEliminateEmptyStatements.cpp67
-rw-r--r--gfx/skia/skia/src/sksl/transform/SkSLEliminateUnreachableCode.cpp214
-rw-r--r--gfx/skia/skia/src/sksl/transform/SkSLFindAndDeclareBuiltinFunctions.cpp95
-rw-r--r--gfx/skia/skia/src/sksl/transform/SkSLFindAndDeclareBuiltinVariables.cpp180
-rw-r--r--gfx/skia/skia/src/sksl/transform/SkSLProgramWriter.h40
-rw-r--r--gfx/skia/skia/src/sksl/transform/SkSLRenamePrivateSymbols.cpp243
-rw-r--r--gfx/skia/skia/src/sksl/transform/SkSLReplaceConstVarsWithLiterals.cpp104
-rw-r--r--gfx/skia/skia/src/sksl/transform/SkSLRewriteIndexedSwizzle.cpp54
-rw-r--r--gfx/skia/skia/src/sksl/transform/SkSLTransform.h103
251 files changed, 63527 insertions, 0 deletions
diff --git a/gfx/skia/skia/src/sksl/GLSL.std.450.h b/gfx/skia/skia/src/sksl/GLSL.std.450.h
new file mode 100644
index 0000000000..943fd8650f
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/GLSL.std.450.h
@@ -0,0 +1,131 @@
+/*
+** Copyright (c) 2014-2016 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a copy
+** of this software and/or associated documentation files (the "Materials"),
+** to deal in the Materials without restriction, including without limitation
+** the rights to use, copy, modify, merge, publish, distribute, sublicense,
+** and/or sell copies of the Materials, and to permit persons to whom the
+** Materials are furnished to do so, subject to the following conditions:
+**
+** The above copyright notice and this permission notice shall be included in
+** all copies or substantial portions of the Materials.
+**
+** MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS
+** STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND
+** HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+** OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+** THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+** FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS
+** IN THE MATERIALS.
+*/
+
+#ifndef GLSLstd450_H
+#define GLSLstd450_H
+
+static const int GLSLstd450Version = 100;
+static const int GLSLstd450Revision = 3;
+
+enum GLSLstd450 {
+ GLSLstd450Bad = 0, // Don't use
+
+ GLSLstd450Round = 1,
+ GLSLstd450RoundEven = 2,
+ GLSLstd450Trunc = 3,
+ GLSLstd450FAbs = 4,
+ GLSLstd450SAbs = 5,
+ GLSLstd450FSign = 6,
+ GLSLstd450SSign = 7,
+ GLSLstd450Floor = 8,
+ GLSLstd450Ceil = 9,
+ GLSLstd450Fract = 10,
+
+ GLSLstd450Radians = 11,
+ GLSLstd450Degrees = 12,
+ GLSLstd450Sin = 13,
+ GLSLstd450Cos = 14,
+ GLSLstd450Tan = 15,
+ GLSLstd450Asin = 16,
+ GLSLstd450Acos = 17,
+ GLSLstd450Atan = 18,
+ GLSLstd450Sinh = 19,
+ GLSLstd450Cosh = 20,
+ GLSLstd450Tanh = 21,
+ GLSLstd450Asinh = 22,
+ GLSLstd450Acosh = 23,
+ GLSLstd450Atanh = 24,
+ GLSLstd450Atan2 = 25,
+
+ GLSLstd450Pow = 26,
+ GLSLstd450Exp = 27,
+ GLSLstd450Log = 28,
+ GLSLstd450Exp2 = 29,
+ GLSLstd450Log2 = 30,
+ GLSLstd450Sqrt = 31,
+ GLSLstd450InverseSqrt = 32,
+
+ GLSLstd450Determinant = 33,
+ GLSLstd450MatrixInverse = 34,
+
+ GLSLstd450Modf = 35, // second operand needs an OpVariable to write to
+ GLSLstd450ModfStruct = 36, // no OpVariable operand
+ GLSLstd450FMin = 37,
+ GLSLstd450UMin = 38,
+ GLSLstd450SMin = 39,
+ GLSLstd450FMax = 40,
+ GLSLstd450UMax = 41,
+ GLSLstd450SMax = 42,
+ GLSLstd450FClamp = 43,
+ GLSLstd450UClamp = 44,
+ GLSLstd450SClamp = 45,
+ GLSLstd450FMix = 46,
+ GLSLstd450IMix = 47, // Reserved
+ GLSLstd450Step = 48,
+ GLSLstd450SmoothStep = 49,
+
+ GLSLstd450Fma = 50,
+ GLSLstd450Frexp = 51, // second operand needs an OpVariable to write to
+ GLSLstd450FrexpStruct = 52, // no OpVariable operand
+ GLSLstd450Ldexp = 53,
+
+ GLSLstd450PackSnorm4x8 = 54,
+ GLSLstd450PackUnorm4x8 = 55,
+ GLSLstd450PackSnorm2x16 = 56,
+ GLSLstd450PackUnorm2x16 = 57,
+ GLSLstd450PackHalf2x16 = 58,
+ GLSLstd450PackDouble2x32 = 59,
+ GLSLstd450UnpackSnorm2x16 = 60,
+ GLSLstd450UnpackUnorm2x16 = 61,
+ GLSLstd450UnpackHalf2x16 = 62,
+ GLSLstd450UnpackSnorm4x8 = 63,
+ GLSLstd450UnpackUnorm4x8 = 64,
+ GLSLstd450UnpackDouble2x32 = 65,
+
+ GLSLstd450Length = 66,
+ GLSLstd450Distance = 67,
+ GLSLstd450Cross = 68,
+ GLSLstd450Normalize = 69,
+ GLSLstd450FaceForward = 70,
+ GLSLstd450Reflect = 71,
+ GLSLstd450Refract = 72,
+
+ GLSLstd450FindILsb = 73,
+ GLSLstd450FindSMsb = 74,
+ GLSLstd450FindUMsb = 75,
+
+ GLSLstd450InterpolateAtCentroid = 76,
+ GLSLstd450InterpolateAtSample = 77,
+ GLSLstd450InterpolateAtOffset = 78,
+
+ GLSLstd450NMin = 79,
+ GLSLstd450NMax = 80,
+ GLSLstd450NClamp = 81,
+
+ GLSLstd450Count
+};
+
+#endif // #ifndef GLSLstd450_H
diff --git a/gfx/skia/skia/src/sksl/README.md b/gfx/skia/skia/src/sksl/README.md
new file mode 100644
index 0000000000..862f5c6965
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/README.md
@@ -0,0 +1,158 @@
+# Overview
+
+SkSL ("Skia Shading Language") is a variant of GLSL which is used as Skia's
+internal shading language. SkSL is, at its heart, a single standardized version
+of GLSL which avoids all of the various version and dialect differences found
+in GLSL "in the wild", but it does bring a few of its own changes to the table.
+
+Skia uses the SkSL compiler to convert SkSL code to GLSL, GLSL ES, SPIR-V, or
+MSL before handing it over to the graphics driver.
+
+
+# Differences from GLSL
+
+* Precision modifiers are not used. 'float', 'int', and 'uint' are always high
+ precision. New types 'half', 'short', and 'ushort' are medium precision (we
+ do not use low precision).
+* Vector types are named <base type><columns>, so float2 instead of vec2 and
+ bool4 instead of bvec4
+* Matrix types are named <base type><columns>x<rows>, so float2x3 instead of
+ mat2x3 and double4x4 instead of dmat4
+* GLSL caps can be referenced via the syntax 'sk_Caps.<name>', e.g.
+ sk_Caps.integerSupport. The value will be a constant boolean or int,
+ as appropriate. As SkSL supports constant folding and branch elimination, this
+ means that an 'if' statement which statically queries a cap will collapse down
+ to the chosen branch, meaning that:
+
+ if (sk_Caps.integerSupport)
+ do_something();
+ else
+ do_something_else();
+
+ will compile as if you had written either 'do_something();' or
+ 'do_something_else();', depending on whether that cap is enabled or not.
+* no #version statement is required, and it will be ignored if present
+* the output color is sk_FragColor (do not declare it)
+* use sk_Position instead of gl_Position. sk_Position is in device coordinates
+ rather than normalized coordinates.
+* use sk_PointSize instead of gl_PointSize
+* use sk_VertexID instead of gl_VertexID
+* use sk_InstanceID instead of gl_InstanceID
+* the fragment coordinate is sk_FragCoord, and is always relative to the upper
+ left.
+* use sk_Clockwise instead of gl_FrontFacing. This is always relative to an
+ upper left origin.
+* you do not need to include ".0" to make a number a float (meaning that
+ "float2(x, y) * 4" is perfectly legal in SkSL, unlike GLSL where it would
+ often have to be expressed "float2(x, y) * 4.0". There is no performance
+ penalty for this, as the number is converted to a float at compile time)
+* type suffixes on numbers (1.0f, 0xFFu) are both unnecessary and unsupported
+* creating a smaller vector from a larger vector (e.g. float2(float3(1))) is
+ intentionally disallowed, as it is just a wordier way of performing a swizzle.
+ Use swizzles instead.
+* Swizzle components, in addition to the normal rgba / xyzw components, can also
+ be LTRB (meaning "left/top/right/bottom", for when we store rectangles in
+ vectors), and may also be the constants '0' or '1' to produce a constant 0 or
+ 1 in that channel instead of selecting anything from the source vector.
+ foo.rgb1 is equivalent to float4(foo.rgb, 1).
+* All texture functions are named "sample", e.g. sample(sampler2D, float3) is
+ equivalent to GLSL's textureProj(sampler2D, float3).
+* Functions support the 'inline' modifier, which causes the compiler to ignore
+ its normal inlining heuristics and inline the function if at all possible
+* some built-in functions and one or two rarely-used language features are not
+ yet supported (sorry!)
+
+
+# Synchronization Primitives
+
+SkSL offers atomic operations and synchronization primitives geared towards GPU compute
+programs. These primitives are designed to abstract over the capabilities provided by
+MSL, SPIR-V, and WGSL, and differ from the corresponding primitives in GLSL.
+
+## Atomics
+
+SkSL provides the `atomicUint` type. This is an opaque type that requires the use of an
+atomic intrinsic (such as `atomicLoad`, `atomicStore`, and `atomicAdd`) to act on its value (which
+is of type `uint`).
+
+A variable with the `atomicUint` type must be declared inside a writable storage buffer block or as
+a workgroup-shared variable. When declared inside a buffer block, it is guaranteed to conform to the
+same size and stride as a `uint`.
+
+```
+workgroup atomicUint myLocalAtomicUint;
+
+layout(set = 0, binding = 0) buffer mySSBO {
+ atomicUint myGlobalAtomicUint;
+};
+
+```
+
+An `atomicUint` can be declared as a struct member or the element type of an array, provided that
+the struct/array type is only instantiated in a workgroup-shared or storage buffer block variable.
+
+### Backend considerations and differences from GLSL
+
+`atomicUint` should not be confused with the GLSL [`atomic_uint` (aka Atomic
+Counter)](https://www.khronos.org/opengl/wiki/Atomic_Counter) type. The semantics provided by
+`atomicUint` are more similar to GLSL ["Atomic Memory
+Functions"](https://www.khronos.org/opengl/wiki/Atomic_Variable_Operations)
+(see GLSL Spec v4.3, 8.11 "Atomic Memory Functions"). The key difference is that SkSL atomic
+operations only operate on a variable of type `atomicUint` while GLSL Atomic Memory Functions can
+operate over arbitrary memory locations (such as a component of a vector).
+
+* The semantics of `atomicUint` are similar to Metal's `atomic<uint>` and WGSL's `atomic<u32>`.
+ These are the types that an `atomicUint` is translated to when targeting Metal and WGSL.
+* When translated to Metal, the atomic intrinsics use relaxed memory order semantics.
+* When translated to SPIR-V, the atomic intrinsics use relaxed [memory
+ semantics](https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#Memory_Semantics_-id-)
+ (i.e. `0x0 None`). The [memory
+ scope](https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#Scope_-id-) is either `1
+ Device` or `2 Workgroup` depending on whether the `atomicUint` is declared in a buffer block or
+ workgroup variable.
+
+## Barriers
+
+SkSL provides two barrier intrinsics: `workgroupBarrier()` and `storageBarrier()`. These functions
+are only available in compute programs and synchronize access to workgroup-shared and storage buffer
+memory between invocations in the same workgroup. They provide the same semantics as the equivalent
+[WGSL Synchronization Built-in Functions](https://www.w3.org/TR/WGSL/#sync-builtin-functions). More
+specifically:
+
+* Both functions execute a control barrier with Acquire/Release memory ordering.
+* Both functions use a `Workgroup` execution and memory scope. This means that a coherent memory
+ view is only guaranteed between invocations in the same workgroup and NOT across workgroups in a
+ given compute pipeline dispatch. If multiple workgroups require a _synchronized_ coherent view
+ over the same shared mutable state, their access must be synchronized via other means (such as a
+ pipeline barrier between multiple dispatches).
+
+### Backend considerations
+
+* The closest GLSL equivalent for `workgroupBarrier()` is the
+[`barrier()`](https://registry.khronos.org/OpenGL-Refpages/gl4/html/barrier.xhtml) intrinsic. Both
+`workgroupBarrier()` and `storageBarrier()` can be defined as the following invocations of the
+`controlBarrier` intrinsic defined in
+[GL_KHR_memory_scope_semantics](https://github.com/KhronosGroup/GLSL/blob/master/extensions/khr/GL_KHR_memory_scope_semantics.txt):
+
+```
+// workgroupBarrier():
+controlBarrier(gl_ScopeWorkgroup,
+ gl_ScopeWorkgroup,
+ gl_StorageSemanticsShared,
+ gl_SemanticsAcquireRelease);
+
+// storageBarrier():
+controlBarrier(gl_ScopeWorkgroup,
+ gl_ScopeWorkgroup,
+ gl_StorageSemanticsBuffer,
+ gl_SemanticsAcquireRelease);
+```
+
+* In Metal, `workgroupBarrier()` is equivalent to `threadgroup_barrier(mem_flags::mem_threadgroup)`.
+ `storageBarrier()` is equivalent to `threadgroup_barrier(mem_flags::mem_device)`.
+
+* In Vulkan SPIR-V, `workgroupBarrier()` is equivalent to `OpControlBarrier` with `Workgroup`
+ execution and memory scope, and `AcquireRelease | WorkgroupMemory` memory semantics.
+
+ `storageBarrier()` is equivalent to `OpControlBarrier` with `Workgroup` execution and memory
+ scope, and `AcquireRelease | UniformMemory` memory semantics.
diff --git a/gfx/skia/skia/src/sksl/SkSLAnalysis.cpp b/gfx/skia/skia/src/sksl/SkSLAnalysis.cpp
new file mode 100644
index 0000000000..a7aba3d02c
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLAnalysis.cpp
@@ -0,0 +1,705 @@
+/*
+ * Copyright 2020 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/SkSLAnalysis.h"
+
+#include "include/core/SkSpan.h"
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLLayout.h"
+#include "include/private/SkSLModifiers.h"
+#include "include/private/SkSLProgramElement.h"
+#include "include/private/SkSLSampleUsage.h"
+#include "include/private/SkSLStatement.h"
+#include "include/private/base/SkTArray.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "include/sksl/SkSLOperator.h"
+#include "src/core/SkTHash.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLConstantFolder.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLIntrinsicList.h"
+#include "src/sksl/analysis/SkSLNoOpErrorReporter.h"
+#include "src/sksl/analysis/SkSLProgramUsage.h"
+#include "src/sksl/analysis/SkSLProgramVisitor.h"
+#include "src/sksl/ir/SkSLBinaryExpression.h"
+#include "src/sksl/ir/SkSLBlock.h"
+#include "src/sksl/ir/SkSLChildCall.h"
+#include "src/sksl/ir/SkSLConstructor.h"
+#include "src/sksl/ir/SkSLDoStatement.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLExpressionStatement.h"
+#include "src/sksl/ir/SkSLFieldAccess.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/SkSLPostfixExpression.h"
+#include "src/sksl/ir/SkSLPrefixExpression.h"
+#include "src/sksl/ir/SkSLProgram.h"
+#include "src/sksl/ir/SkSLReturnStatement.h"
+#include "src/sksl/ir/SkSLSwitchCase.h"
+#include "src/sksl/ir/SkSLSwitchStatement.h"
+#include "src/sksl/ir/SkSLSwizzle.h"
+#include "src/sksl/ir/SkSLTernaryExpression.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/transform/SkSLProgramWriter.h"
+
+#include <optional>
+#include <string>
+#include <string_view>
+
+namespace SkSL {
+
+namespace {
+
+// Visitor that determines the merged SampleUsage for a given child in the program.
+class MergeSampleUsageVisitor : public ProgramVisitor {
+public:
+ MergeSampleUsageVisitor(const Context& context,
+ const Variable& child,
+ bool writesToSampleCoords)
+ : fContext(context), fChild(child), fWritesToSampleCoords(writesToSampleCoords) {}
+
+ SampleUsage visit(const Program& program) {
+ fUsage = SampleUsage(); // reset to none
+ INHERITED::visit(program);
+ return fUsage;
+ }
+
+ int elidedSampleCoordCount() const { return fElidedSampleCoordCount; }
+
+protected:
+ const Context& fContext;
+ const Variable& fChild;
+ const bool fWritesToSampleCoords;
+ SampleUsage fUsage;
+ int fElidedSampleCoordCount = 0;
+
+ bool visitExpression(const Expression& e) override {
+ // Looking for child(...)
+ if (e.is<ChildCall>() && &e.as<ChildCall>().child() == &fChild) {
+ // Determine the type of call at this site, and merge it with the accumulated state
+ const ExpressionArray& arguments = e.as<ChildCall>().arguments();
+ SkASSERT(arguments.size() >= 1);
+
+ const Expression* maybeCoords = arguments[0].get();
+ if (maybeCoords->type().matches(*fContext.fTypes.fFloat2)) {
+ // If the coords are a direct reference to the program's sample-coords, and those
+ // coords are never modified, we can conservatively turn this into PassThrough
+ // sampling. In all other cases, we consider it Explicit.
+ if (!fWritesToSampleCoords && maybeCoords->is<VariableReference>() &&
+ maybeCoords->as<VariableReference>().variable()->modifiers().fLayout.fBuiltin ==
+ SK_MAIN_COORDS_BUILTIN) {
+ fUsage.merge(SampleUsage::PassThrough());
+ ++fElidedSampleCoordCount;
+ } else {
+ fUsage.merge(SampleUsage::Explicit());
+ }
+ } else {
+ // child(inputColor) or child(srcColor, dstColor) -> PassThrough
+ fUsage.merge(SampleUsage::PassThrough());
+ }
+ }
+
+ return INHERITED::visitExpression(e);
+ }
+
+ using INHERITED = ProgramVisitor;
+};
+
+// Visitor that searches for child calls from a function other than main()
+class SampleOutsideMainVisitor : public ProgramVisitor {
+public:
+ SampleOutsideMainVisitor() {}
+
+ bool visitExpression(const Expression& e) override {
+ if (e.is<ChildCall>()) {
+ return true;
+ }
+ return INHERITED::visitExpression(e);
+ }
+
+ bool visitProgramElement(const ProgramElement& p) override {
+ return p.is<FunctionDefinition>() &&
+ !p.as<FunctionDefinition>().declaration().isMain() &&
+ INHERITED::visitProgramElement(p);
+ }
+
+ using INHERITED = ProgramVisitor;
+};
+
+class ReturnsNonOpaqueColorVisitor : public ProgramVisitor {
+public:
+ ReturnsNonOpaqueColorVisitor() {}
+
+ bool visitStatement(const Statement& s) override {
+ if (s.is<ReturnStatement>()) {
+ const Expression* e = s.as<ReturnStatement>().expression().get();
+ bool knownOpaque = e && e->type().slotCount() == 4 &&
+ ConstantFolder::GetConstantValueForVariable(*e)
+ ->getConstantValue(/*n=*/3)
+ .value_or(0) == 1;
+ return !knownOpaque;
+ }
+ return INHERITED::visitStatement(s);
+ }
+
+ bool visitExpression(const Expression& e) override {
+ // No need to recurse into expressions, these can never contain return statements
+ return false;
+ }
+
+ using INHERITED = ProgramVisitor;
+ using INHERITED::visitProgramElement;
+};
+
+// Visitor that counts the number of nodes visited
+class NodeCountVisitor : public ProgramVisitor {
+public:
+ NodeCountVisitor(int limit) : fLimit(limit) {}
+
+ int visit(const Statement& s) {
+ this->visitStatement(s);
+ return fCount;
+ }
+
+ bool visitExpression(const Expression& e) override {
+ ++fCount;
+ return (fCount >= fLimit) || INHERITED::visitExpression(e);
+ }
+
+ bool visitProgramElement(const ProgramElement& p) override {
+ ++fCount;
+ return (fCount >= fLimit) || INHERITED::visitProgramElement(p);
+ }
+
+ bool visitStatement(const Statement& s) override {
+ ++fCount;
+ return (fCount >= fLimit) || INHERITED::visitStatement(s);
+ }
+
+private:
+ int fCount = 0;
+ int fLimit;
+
+ using INHERITED = ProgramVisitor;
+};
+
+class VariableWriteVisitor : public ProgramVisitor {
+public:
+ VariableWriteVisitor(const Variable* var)
+ : fVar(var) {}
+
+ bool visit(const Statement& s) {
+ return this->visitStatement(s);
+ }
+
+ bool visitExpression(const Expression& e) override {
+ if (e.is<VariableReference>()) {
+ const VariableReference& ref = e.as<VariableReference>();
+ if (ref.variable() == fVar &&
+ (ref.refKind() == VariableReference::RefKind::kWrite ||
+ ref.refKind() == VariableReference::RefKind::kReadWrite ||
+ ref.refKind() == VariableReference::RefKind::kPointer)) {
+ return true;
+ }
+ }
+ return INHERITED::visitExpression(e);
+ }
+
+private:
+ const Variable* fVar;
+
+ using INHERITED = ProgramVisitor;
+};
+
+// This isn't actually using ProgramVisitor, because it only considers a subset of the fields for
+// any given expression kind. For instance, when indexing an array (e.g. `x[1]`), we only want to
+// know if the base (`x`) is assignable; the index expression (`1`) doesn't need to be.
+class IsAssignableVisitor {
+public:
+ IsAssignableVisitor(ErrorReporter* errors) : fErrors(errors) {}
+
+ bool visit(Expression& expr, Analysis::AssignmentInfo* info) {
+ int oldErrorCount = fErrors->errorCount();
+ this->visitExpression(expr);
+ if (info) {
+ info->fAssignedVar = fAssignedVar;
+ }
+ return fErrors->errorCount() == oldErrorCount;
+ }
+
+ void visitExpression(Expression& expr, const FieldAccess* fieldAccess = nullptr) {
+ switch (expr.kind()) {
+ case Expression::Kind::kVariableReference: {
+ VariableReference& varRef = expr.as<VariableReference>();
+ const Variable* var = varRef.variable();
+ auto fieldName = [&] {
+ return fieldAccess ? fieldAccess->description(OperatorPrecedence::kTopLevel)
+ : std::string(var->name());
+ };
+ if (var->modifiers().fFlags & (Modifiers::kConst_Flag | Modifiers::kUniform_Flag)) {
+ fErrors->error(expr.fPosition,
+ "cannot modify immutable variable '" + fieldName() + "'");
+ } else if (var->storage() == Variable::Storage::kGlobal &&
+ (var->modifiers().fFlags & Modifiers::kIn_Flag)) {
+ fErrors->error(expr.fPosition,
+ "cannot modify pipeline input variable '" + fieldName() + "'");
+ } else {
+ SkASSERT(fAssignedVar == nullptr);
+ fAssignedVar = &varRef;
+ }
+ break;
+ }
+ case Expression::Kind::kFieldAccess: {
+ const FieldAccess& f = expr.as<FieldAccess>();
+ this->visitExpression(*f.base(), &f);
+ break;
+ }
+ case Expression::Kind::kSwizzle: {
+ const Swizzle& swizzle = expr.as<Swizzle>();
+ this->checkSwizzleWrite(swizzle);
+ this->visitExpression(*swizzle.base(), fieldAccess);
+ break;
+ }
+ case Expression::Kind::kIndex:
+ this->visitExpression(*expr.as<IndexExpression>().base(), fieldAccess);
+ break;
+
+ case Expression::Kind::kPoison:
+ break;
+
+ default:
+ fErrors->error(expr.fPosition, "cannot assign to this expression");
+ break;
+ }
+ }
+
+private:
+ void checkSwizzleWrite(const Swizzle& swizzle) {
+ int bits = 0;
+ for (int8_t idx : swizzle.components()) {
+ SkASSERT(idx >= SwizzleComponent::X && idx <= SwizzleComponent::W);
+ int bit = 1 << idx;
+ if (bits & bit) {
+ fErrors->error(swizzle.fPosition,
+ "cannot write to the same swizzle field more than once");
+ break;
+ }
+ bits |= bit;
+ }
+ }
+
+ ErrorReporter* fErrors;
+ VariableReference* fAssignedVar = nullptr;
+
+ using INHERITED = ProgramVisitor;
+};
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// Analysis
+
+SampleUsage Analysis::GetSampleUsage(const Program& program,
+ const Variable& child,
+ bool writesToSampleCoords,
+ int* elidedSampleCoordCount) {
+ MergeSampleUsageVisitor visitor(*program.fContext, child, writesToSampleCoords);
+ SampleUsage result = visitor.visit(program);
+ if (elidedSampleCoordCount) {
+ *elidedSampleCoordCount += visitor.elidedSampleCoordCount();
+ }
+ return result;
+}
+
+bool Analysis::ReferencesBuiltin(const Program& program, int builtin) {
+ SkASSERT(program.fUsage);
+ for (const auto& [variable, counts] : program.fUsage->fVariableCounts) {
+ if (counts.fRead > 0 && variable->modifiers().fLayout.fBuiltin == builtin) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Analysis::ReferencesSampleCoords(const Program& program) {
+ return Analysis::ReferencesBuiltin(program, SK_MAIN_COORDS_BUILTIN);
+}
+
+bool Analysis::ReferencesFragCoords(const Program& program) {
+ return Analysis::ReferencesBuiltin(program, SK_FRAGCOORD_BUILTIN);
+}
+
+bool Analysis::CallsSampleOutsideMain(const Program& program) {
+ SampleOutsideMainVisitor visitor;
+ return visitor.visit(program);
+}
+
+bool Analysis::CallsColorTransformIntrinsics(const Program& program) {
+ for (auto [fn, count] : program.usage()->fCallCounts) {
+ if (count != 0 && (fn->intrinsicKind() == k_toLinearSrgb_IntrinsicKind ||
+ fn->intrinsicKind() == k_fromLinearSrgb_IntrinsicKind)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Analysis::ReturnsOpaqueColor(const FunctionDefinition& function) {
+ ReturnsNonOpaqueColorVisitor visitor;
+ return !visitor.visitProgramElement(function);
+}
+
+bool Analysis::ContainsRTAdjust(const Expression& expr) {
+ class ContainsRTAdjustVisitor : public ProgramVisitor {
+ public:
+ bool visitExpression(const Expression& expr) override {
+ if (expr.is<VariableReference>() &&
+ expr.as<VariableReference>().variable()->name() == Compiler::RTADJUST_NAME) {
+ return true;
+ }
+ return INHERITED::visitExpression(expr);
+ }
+
+ using INHERITED = ProgramVisitor;
+ };
+
+ ContainsRTAdjustVisitor visitor;
+ return visitor.visitExpression(expr);
+}
+
+bool Analysis::IsCompileTimeConstant(const Expression& expr) {
+ class IsCompileTimeConstantVisitor : public ProgramVisitor {
+ public:
+ bool visitExpression(const Expression& expr) override {
+ switch (expr.kind()) {
+ case Expression::Kind::kLiteral:
+ // Literals are compile-time constants.
+ return false;
+
+ case Expression::Kind::kConstructorArray:
+ case Expression::Kind::kConstructorCompound:
+ case Expression::Kind::kConstructorDiagonalMatrix:
+ case Expression::Kind::kConstructorMatrixResize:
+ case Expression::Kind::kConstructorSplat:
+ case Expression::Kind::kConstructorStruct:
+ // Constructors might be compile-time constants, if they are composed entirely
+ // of literals and constructors. (Casting constructors are intentionally omitted
+ // here. If the value inside was a compile-time constant, we would have not have
+ // generated a cast at all.)
+ return INHERITED::visitExpression(expr);
+
+ default:
+ // This expression isn't a compile-time constant.
+ fIsConstant = false;
+ return true;
+ }
+ }
+
+ bool fIsConstant = true;
+ using INHERITED = ProgramVisitor;
+ };
+
+ IsCompileTimeConstantVisitor visitor;
+ visitor.visitExpression(expr);
+ return visitor.fIsConstant;
+}
+
+bool Analysis::DetectVarDeclarationWithoutScope(const Statement& stmt, ErrorReporter* errors) {
+ // A variable declaration can create either a lone VarDeclaration or an unscoped Block
+ // containing multiple VarDeclaration statements. We need to detect either case.
+ const Variable* var;
+ if (stmt.is<VarDeclaration>()) {
+ // The single-variable case. No blocks at all.
+ var = stmt.as<VarDeclaration>().var();
+ } else if (stmt.is<Block>()) {
+ // The multiple-variable case: an unscoped, non-empty block...
+ const Block& block = stmt.as<Block>();
+ if (block.isScope() || block.children().empty()) {
+ return false;
+ }
+ // ... holding a variable declaration.
+ const Statement& innerStmt = *block.children().front();
+ if (!innerStmt.is<VarDeclaration>()) {
+ return false;
+ }
+ var = innerStmt.as<VarDeclaration>().var();
+ } else {
+ // This statement wasn't a variable declaration. No problem.
+ return false;
+ }
+
+ // Report an error.
+ SkASSERT(var);
+ if (errors) {
+ errors->error(var->fPosition,
+ "variable '" + std::string(var->name()) + "' must be created in a scope");
+ }
+ return true;
+}
+
+int Analysis::NodeCountUpToLimit(const FunctionDefinition& function, int limit) {
+ return NodeCountVisitor{limit}.visit(*function.body());
+}
+
+bool Analysis::StatementWritesToVariable(const Statement& stmt, const Variable& var) {
+ return VariableWriteVisitor(&var).visit(stmt);
+}
+
+bool Analysis::IsAssignable(Expression& expr, AssignmentInfo* info, ErrorReporter* errors) {
+ NoOpErrorReporter unusedErrors;
+ return IsAssignableVisitor{errors ? errors : &unusedErrors}.visit(expr, info);
+}
+
+bool Analysis::UpdateVariableRefKind(Expression* expr,
+ VariableReference::RefKind kind,
+ ErrorReporter* errors) {
+ Analysis::AssignmentInfo info;
+ if (!Analysis::IsAssignable(*expr, &info, errors)) {
+ return false;
+ }
+ if (!info.fAssignedVar) {
+ if (errors) {
+ errors->error(expr->fPosition, "can't assign to expression '" + expr->description() +
+ "'");
+ }
+ return false;
+ }
+ info.fAssignedVar->setRefKind(kind);
+ return true;
+}
+
+class ES2IndexingVisitor : public ProgramVisitor {
+public:
+ ES2IndexingVisitor(ErrorReporter& errors) : fErrors(errors) {}
+
+ bool visitStatement(const Statement& s) override {
+ if (s.is<ForStatement>()) {
+ const ForStatement& f = s.as<ForStatement>();
+ SkASSERT(f.initializer() && f.initializer()->is<VarDeclaration>());
+ const Variable* var = f.initializer()->as<VarDeclaration>().var();
+ auto [iter, inserted] = fLoopIndices.insert(var);
+ SkASSERT(inserted);
+ bool result = this->visitStatement(*f.statement());
+ fLoopIndices.erase(iter);
+ return result;
+ }
+ return INHERITED::visitStatement(s);
+ }
+
+ bool visitExpression(const Expression& e) override {
+ if (e.is<IndexExpression>()) {
+ const IndexExpression& i = e.as<IndexExpression>();
+ if (!Analysis::IsConstantIndexExpression(*i.index(), &fLoopIndices)) {
+ fErrors.error(i.fPosition, "index expression must be constant");
+ return true;
+ }
+ }
+ return INHERITED::visitExpression(e);
+ }
+
+ using ProgramVisitor::visitProgramElement;
+
+private:
+ ErrorReporter& fErrors;
+ std::set<const Variable*> fLoopIndices;
+ using INHERITED = ProgramVisitor;
+};
+
+void Analysis::ValidateIndexingForES2(const ProgramElement& pe, ErrorReporter& errors) {
+ ES2IndexingVisitor visitor(errors);
+ visitor.visitProgramElement(pe);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ProgramVisitor
+
+bool ProgramVisitor::visit(const Program& program) {
+ for (const ProgramElement* pe : program.elements()) {
+ if (this->visitProgramElement(*pe)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+template <typename T> bool TProgramVisitor<T>::visitExpression(typename T::Expression& e) {
+ switch (e.kind()) {
+ case Expression::Kind::kFunctionReference:
+ case Expression::Kind::kLiteral:
+ case Expression::Kind::kMethodReference:
+ case Expression::Kind::kPoison:
+ case Expression::Kind::kSetting:
+ case Expression::Kind::kTypeReference:
+ case Expression::Kind::kVariableReference:
+ // Leaf expressions return false
+ return false;
+
+ case Expression::Kind::kBinary: {
+ auto& b = e.template as<BinaryExpression>();
+ return (b.left() && this->visitExpressionPtr(b.left())) ||
+ (b.right() && this->visitExpressionPtr(b.right()));
+ }
+ case Expression::Kind::kChildCall: {
+ // We don't visit the child variable itself, just the arguments
+ auto& c = e.template as<ChildCall>();
+ for (auto& arg : c.arguments()) {
+ if (arg && this->visitExpressionPtr(arg)) { return true; }
+ }
+ return false;
+ }
+ case Expression::Kind::kConstructorArray:
+ case Expression::Kind::kConstructorArrayCast:
+ case Expression::Kind::kConstructorCompound:
+ case Expression::Kind::kConstructorCompoundCast:
+ case Expression::Kind::kConstructorDiagonalMatrix:
+ case Expression::Kind::kConstructorMatrixResize:
+ case Expression::Kind::kConstructorScalarCast:
+ case Expression::Kind::kConstructorSplat:
+ case Expression::Kind::kConstructorStruct: {
+ auto& c = e.asAnyConstructor();
+ for (auto& arg : c.argumentSpan()) {
+ if (this->visitExpressionPtr(arg)) { return true; }
+ }
+ return false;
+ }
+ case Expression::Kind::kFieldAccess:
+ return this->visitExpressionPtr(e.template as<FieldAccess>().base());
+
+ case Expression::Kind::kFunctionCall: {
+ auto& c = e.template as<FunctionCall>();
+ for (auto& arg : c.arguments()) {
+ if (arg && this->visitExpressionPtr(arg)) { return true; }
+ }
+ return false;
+ }
+ case Expression::Kind::kIndex: {
+ auto& i = e.template as<IndexExpression>();
+ return this->visitExpressionPtr(i.base()) || this->visitExpressionPtr(i.index());
+ }
+ case Expression::Kind::kPostfix:
+ return this->visitExpressionPtr(e.template as<PostfixExpression>().operand());
+
+ case Expression::Kind::kPrefix:
+ return this->visitExpressionPtr(e.template as<PrefixExpression>().operand());
+
+ case Expression::Kind::kSwizzle: {
+ auto& s = e.template as<Swizzle>();
+ return s.base() && this->visitExpressionPtr(s.base());
+ }
+
+ case Expression::Kind::kTernary: {
+ auto& t = e.template as<TernaryExpression>();
+ return this->visitExpressionPtr(t.test()) ||
+ (t.ifTrue() && this->visitExpressionPtr(t.ifTrue())) ||
+ (t.ifFalse() && this->visitExpressionPtr(t.ifFalse()));
+ }
+ default:
+ SkUNREACHABLE;
+ }
+}
+
+template <typename T> bool TProgramVisitor<T>::visitStatement(typename T::Statement& s) {
+ switch (s.kind()) {
+ case Statement::Kind::kBreak:
+ case Statement::Kind::kContinue:
+ case Statement::Kind::kDiscard:
+ case Statement::Kind::kNop:
+ // Leaf statements just return false
+ return false;
+
+ case Statement::Kind::kBlock:
+ for (auto& stmt : s.template as<Block>().children()) {
+ if (stmt && this->visitStatementPtr(stmt)) {
+ return true;
+ }
+ }
+ return false;
+
+ case Statement::Kind::kSwitchCase: {
+ auto& sc = s.template as<SwitchCase>();
+ return this->visitStatementPtr(sc.statement());
+ }
+ case Statement::Kind::kDo: {
+ auto& d = s.template as<DoStatement>();
+ return this->visitExpressionPtr(d.test()) || this->visitStatementPtr(d.statement());
+ }
+ case Statement::Kind::kExpression:
+ return this->visitExpressionPtr(s.template as<ExpressionStatement>().expression());
+
+ case Statement::Kind::kFor: {
+ auto& f = s.template as<ForStatement>();
+ return (f.initializer() && this->visitStatementPtr(f.initializer())) ||
+ (f.test() && this->visitExpressionPtr(f.test())) ||
+ (f.next() && this->visitExpressionPtr(f.next())) ||
+ this->visitStatementPtr(f.statement());
+ }
+ case Statement::Kind::kIf: {
+ auto& i = s.template as<IfStatement>();
+ return (i.test() && this->visitExpressionPtr(i.test())) ||
+ (i.ifTrue() && this->visitStatementPtr(i.ifTrue())) ||
+ (i.ifFalse() && this->visitStatementPtr(i.ifFalse()));
+ }
+ case Statement::Kind::kReturn: {
+ auto& r = s.template as<ReturnStatement>();
+ return r.expression() && this->visitExpressionPtr(r.expression());
+ }
+ case Statement::Kind::kSwitch: {
+ auto& sw = s.template as<SwitchStatement>();
+ if (this->visitExpressionPtr(sw.value())) {
+ return true;
+ }
+ for (auto& c : sw.cases()) {
+ if (this->visitStatementPtr(c)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ case Statement::Kind::kVarDeclaration: {
+ auto& v = s.template as<VarDeclaration>();
+ return v.value() && this->visitExpressionPtr(v.value());
+ }
+ default:
+ SkUNREACHABLE;
+ }
+}
+
+template <typename T> bool TProgramVisitor<T>::visitProgramElement(typename T::ProgramElement& pe) {
+ switch (pe.kind()) {
+ case ProgramElement::Kind::kExtension:
+ case ProgramElement::Kind::kFunctionPrototype:
+ case ProgramElement::Kind::kInterfaceBlock:
+ case ProgramElement::Kind::kModifiers:
+ case ProgramElement::Kind::kStructDefinition:
+ // Leaf program elements just return false by default
+ return false;
+
+ case ProgramElement::Kind::kFunction:
+ return this->visitStatementPtr(pe.template as<FunctionDefinition>().body());
+
+ case ProgramElement::Kind::kGlobalVar:
+ return this->visitStatementPtr(pe.template as<GlobalVarDeclaration>().declaration());
+
+ default:
+ SkUNREACHABLE;
+ }
+}
+
+template class TProgramVisitor<ProgramVisitorTypes>;
+template class TProgramVisitor<ProgramWriterTypes>;
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/SkSLAnalysis.h b/gfx/skia/skia/src/sksl/SkSLAnalysis.h
new file mode 100644
index 0000000000..b875098fda
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLAnalysis.h
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkSLAnalysis_DEFINED
+#define SkSLAnalysis_DEFINED
+
+#include "include/private/SkSLSampleUsage.h"
+#include "include/private/base/SkTArray.h"
+
+#include <cstdint>
+#include <memory>
+#include <set>
+#include <vector>
+
+namespace SkSL {
+
+class Context;
+class ErrorReporter;
+class Expression;
+class FunctionDeclaration;
+class FunctionDefinition;
+class Position;
+class ProgramElement;
+class ProgramUsage;
+class Statement;
+class SymbolTable;
+class Variable;
+class VariableReference;
+enum class VariableRefKind : int8_t;
+struct ForLoopPositions;
+struct LoopUnrollInfo;
+struct Module;
+struct Program;
+
+/**
+ * Provides utilities for analyzing SkSL statically before it's composed into a full program.
+ */
+namespace Analysis {
+
+/**
+ * Determines how `program` samples `child`. By default, assumes that the sample coords
+ * (SK_MAIN_COORDS_BUILTIN) might be modified, so `child.eval(sampleCoords)` is treated as
+ * Explicit. If writesToSampleCoords is false, treats that as PassThrough, instead.
+ * If elidedSampleCoordCount is provided, the pointed to value will be incremented by the
+ * number of sample calls where the above rewrite was performed.
+ */
+SampleUsage GetSampleUsage(const Program& program,
+ const Variable& child,
+ bool writesToSampleCoords = true,
+ int* elidedSampleCoordCount = nullptr);
+
+bool ReferencesBuiltin(const Program& program, int builtin);
+
+bool ReferencesSampleCoords(const Program& program);
+bool ReferencesFragCoords(const Program& program);
+
+bool CallsSampleOutsideMain(const Program& program);
+
+bool CallsColorTransformIntrinsics(const Program& program);
+
+/**
+ * Determines if `function` always returns an opaque color (a vec4 where the last component is known
+ * to be 1). This is conservative, and based on constant expression analysis.
+ */
+bool ReturnsOpaqueColor(const FunctionDefinition& function);
+
+/**
+ * Checks for recursion or overly-deep function-call chains, and rejects programs which have them.
+ * Also, computes the size of the program in a completely flattened state--loops fully unrolled,
+ * function calls inlined--and rejects programs that exceed an arbitrary upper bound. This is
+ * intended to prevent absurdly large programs from overwhemling SkVM. Only strict-ES2 mode is
+ * supported; complex control flow is not SkVM-compatible (and this becomes the halting problem)
+ */
+bool CheckProgramStructure(const Program& program, bool enforceSizeLimit);
+
+/** Determines if `expr` contains a reference to the variable sk_RTAdjust. */
+bool ContainsRTAdjust(const Expression& expr);
+
+/** Determines if `expr` has any side effects. (Is the expression state-altering or pure?) */
+bool HasSideEffects(const Expression& expr);
+
+/** Determines if `expr` is a compile-time constant (composed of just constructors and literals). */
+bool IsCompileTimeConstant(const Expression& expr);
+
+/**
+ * Determines if `expr` is a dynamically-uniform expression; this returns true if the expression
+ * could be evaluated at compile time if uniform values were known.
+ */
+bool IsDynamicallyUniformExpression(const Expression& expr);
+
+/**
+ * Detect an orphaned variable declaration outside of a scope, e.g. if (true) int a;. Returns
+ * true if an error was reported.
+ */
+bool DetectVarDeclarationWithoutScope(const Statement& stmt, ErrorReporter* errors = nullptr);
+
+int NodeCountUpToLimit(const FunctionDefinition& function, int limit);
+
+/**
+ * Finds unconditional exits from a switch-case. Returns true if this statement unconditionally
+ * causes an exit from this switch (via continue, break or return).
+ */
+bool SwitchCaseContainsUnconditionalExit(Statement& stmt);
+
+/**
+ * Finds conditional exits from a switch-case. Returns true if this statement contains a
+ * conditional that wraps a potential exit from the switch (via continue, break or return).
+ */
+bool SwitchCaseContainsConditionalExit(Statement& stmt);
+
+std::unique_ptr<ProgramUsage> GetUsage(const Program& program);
+std::unique_ptr<ProgramUsage> GetUsage(const Module& module);
+
+/** Returns true if the passed-in statement might alter `var`. */
+bool StatementWritesToVariable(const Statement& stmt, const Variable& var);
+
+/**
+ * Detects if the passed-in block contains a `continue`, `break` or `return` that could directly
+ * affect its control flow. (A `continue` or `break` nested inside an inner loop/switch will not
+ * affect the loop, but a `return` will.)
+ */
+struct LoopControlFlowInfo {
+ bool fHasContinue = false;
+ bool fHasBreak = false;
+ bool fHasReturn = false;
+};
+LoopControlFlowInfo GetLoopControlFlowInfo(const Statement& stmt);
+
+/**
+ * Returns true if the expression can be assigned-into. Pass `info` if you want to know the
+ * VariableReference that will be written to. Pass `errors` to report an error for expressions that
+ * are not actually writable.
+ */
+struct AssignmentInfo {
+ VariableReference* fAssignedVar = nullptr;
+};
+bool IsAssignable(Expression& expr, AssignmentInfo* info = nullptr,
+ ErrorReporter* errors = nullptr);
+
+/**
+ * Updates the `refKind` field of the VariableReference at the top level of `expr`.
+ * If `expr` can be assigned to (`IsAssignable`), true is returned and no errors are reported.
+ * If not, false is returned. and an error is reported if `errors` is non-null.
+ */
+bool UpdateVariableRefKind(Expression* expr, VariableRefKind kind, ErrorReporter* errors = nullptr);
+
+/**
+ * A "trivial" expression is one where we'd feel comfortable cloning it multiple times in
+ * the code, without worrying about incurring a performance penalty. Examples:
+ * - true
+ * - 3.14159265
+ * - myIntVariable
+ * - myColor.rgb
+ * - myArray[123]
+ * - myStruct.myField
+ * - half4(0)
+ *
+ * Trivial-ness is stackable. Somewhat large expressions can occasionally make the cut:
+ * - half4(myColor.a)
+ * - myStruct.myArrayField[7].xzy
+ */
+bool IsTrivialExpression(const Expression& expr);
+
+/**
+ * Returns true if both expression trees are the same. Used by the optimizer to look for self-
+ * assignment or self-comparison; won't necessarily catch complex cases. Rejects expressions
+ * that may cause side effects.
+ */
+bool IsSameExpressionTree(const Expression& left, const Expression& right);
+
+/**
+ * Returns true if expr is a constant-expression, as defined by GLSL 1.0, section 5.10.
+ * A constant expression is one of:
+ * - A literal value
+ * - A global or local variable qualified as 'const', excluding function parameters
+ * - An expression formed by an operator on operands that are constant expressions, including
+ * getting an element of a constant vector or a constant matrix, or a field of a constant
+ * structure
+ * - A constructor whose arguments are all constant expressions
+ * - A built-in function call whose arguments are all constant expressions, with the exception
+ * of the texture lookup functions
+ */
+bool IsConstantExpression(const Expression& expr);
+
+/**
+ * Returns true if expr is a valid constant-index-expression, as defined by GLSL 1.0, Appendix A,
+ * Section 5. A constant-index-expression is:
+ * - A constant-expression
+ * - Loop indices (as defined in Appendix A, Section 4)
+ * - Expressions composed of both of the above
+ */
+bool IsConstantIndexExpression(const Expression& expr,
+ const std::set<const Variable*>* loopIndices);
+
+/**
+ * Ensures that a for-loop meets the strict requirements of The OpenGL ES Shading Language 1.00,
+ * Appendix A, Section 4.
+ * If the requirements are met, information about the loop's structure is returned.
+ * If the requirements are not met, the problem is reported via `errors` (if not nullptr), and
+ * null is returned.
+ */
+std::unique_ptr<LoopUnrollInfo> GetLoopUnrollInfo(Position pos,
+ const ForLoopPositions& positions,
+ const Statement* loopInitializer,
+ const Expression* loopTest,
+ const Expression* loopNext,
+ const Statement* loopStatement,
+ ErrorReporter* errors);
+
+void ValidateIndexingForES2(const ProgramElement& pe, ErrorReporter& errors);
+
+/** Detects functions that fail to return a value on at least one path. */
+bool CanExitWithoutReturningValue(const FunctionDeclaration& funcDecl, const Statement& body);
+
+/** Determines if a given function has multiple and/or early returns. */
+enum class ReturnComplexity {
+ kSingleSafeReturn,
+ kScopedReturns,
+ kEarlyReturns,
+};
+ReturnComplexity GetReturnComplexity(const FunctionDefinition& funcDef);
+
+/**
+ * Runs at finalization time to perform any last-minute correctness checks:
+ * - Reports dangling FunctionReference or TypeReference expressions
+ * - Reports function `out` params which are never written to (structs are currently exempt)
+ */
+void DoFinalizationChecks(const Program& program);
+
+/**
+ * Error checks compute shader in/outs and returns a vector containing them ordered by location.
+ */
+SkTArray<const SkSL::Variable*> GetComputeShaderMainParams(const Context& context,
+ const Program& program);
+
+/**
+ * Tracks the symbol table stack, in conjunction with a ProgramVisitor. Inside `visitStatement`,
+ * pass the current statement and a symbol-table vector to a SymbolTableStackBuilder and the symbol
+ * table stack will be maintained automatically.
+ */
+class SymbolTableStackBuilder {
+public:
+ // If the passed-in statement holds a symbol table, adds it to the stack.
+ SymbolTableStackBuilder(const Statement* stmt,
+ std::vector<std::shared_ptr<SymbolTable>>* stack);
+
+ // If a symbol table was added to the stack earlier, removes it from the stack.
+ ~SymbolTableStackBuilder();
+
+private:
+ std::vector<std::shared_ptr<SymbolTable>>* fStackToPop = nullptr;
+};
+
+} // namespace Analysis
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/SkSLBuiltinTypes.cpp b/gfx/skia/skia/src/sksl/SkSLBuiltinTypes.cpp
new file mode 100644
index 0000000000..460358e54a
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLBuiltinTypes.cpp
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/SkSLBuiltinTypes.h"
+
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/spirv.h"
+
+namespace SkSL {
+
+/**
+ * Initializes the core SkSL types.
+ */
+BuiltinTypes::BuiltinTypes()
+ : fFloat(Type::MakeScalarType(
+ "float", "f", Type::NumberKind::kFloat, /*priority=*/10, /*bitWidth=*/32))
+ , fFloat2(Type::MakeVectorType("float2", "f2", *fFloat, /*columns=*/2))
+ , fFloat3(Type::MakeVectorType("float3", "f3", *fFloat, /*columns=*/3))
+ , fFloat4(Type::MakeVectorType("float4", "f4", *fFloat, /*columns=*/4))
+ , fHalf(Type::MakeScalarType(
+ "half", "h", Type::NumberKind::kFloat, /*priority=*/9, /*bitWidth=*/16))
+ , fHalf2(Type::MakeVectorType("half2", "h2", *fHalf, /*columns=*/2))
+ , fHalf3(Type::MakeVectorType("half3", "h3", *fHalf, /*columns=*/3))
+ , fHalf4(Type::MakeVectorType("half4", "h4", *fHalf, /*columns=*/4))
+ , fInt(Type::MakeScalarType(
+ "int", "i", Type::NumberKind::kSigned, /*priority=*/7, /*bitWidth=*/32))
+ , fInt2(Type::MakeVectorType("int2", "i2", *fInt, /*columns=*/2))
+ , fInt3(Type::MakeVectorType("int3", "i3", *fInt, /*columns=*/3))
+ , fInt4(Type::MakeVectorType("int4", "i4", *fInt, /*columns=*/4))
+ , fUInt(Type::MakeScalarType(
+ "uint", "I", Type::NumberKind::kUnsigned, /*priority=*/6, /*bitWidth=*/32))
+ , fUInt2(Type::MakeVectorType("uint2", "I2", *fUInt, /*columns=*/2))
+ , fUInt3(Type::MakeVectorType("uint3", "I3", *fUInt, /*columns=*/3))
+ , fUInt4(Type::MakeVectorType("uint4", "I4", *fUInt, /*columns=*/4))
+ , fShort(Type::MakeScalarType(
+ "short", "s", Type::NumberKind::kSigned, /*priority=*/4, /*bitWidth=*/16))
+ , fShort2(Type::MakeVectorType("short2", "s2", *fShort, /*columns=*/2))
+ , fShort3(Type::MakeVectorType("short3", "s3", *fShort, /*columns=*/3))
+ , fShort4(Type::MakeVectorType("short4", "s4", *fShort, /*columns=*/4))
+ , fUShort(Type::MakeScalarType(
+ "ushort", "S", Type::NumberKind::kUnsigned, /*priority=*/3, /*bitWidth=*/16))
+ , fUShort2(Type::MakeVectorType("ushort2", "S2", *fUShort, /*columns=*/2))
+ , fUShort3(Type::MakeVectorType("ushort3", "S3", *fUShort, /*columns=*/3))
+ , fUShort4(Type::MakeVectorType("ushort4", "S4", *fUShort, /*columns=*/4))
+ , fBool(Type::MakeScalarType(
+ "bool", "b", Type::NumberKind::kBoolean, /*priority=*/0, /*bitWidth=*/1))
+ , fBool2(Type::MakeVectorType("bool2", "b2", *fBool, /*columns=*/2))
+ , fBool3(Type::MakeVectorType("bool3", "b3", *fBool, /*columns=*/3))
+ , fBool4(Type::MakeVectorType("bool4", "b4", *fBool, /*columns=*/4))
+ , fInvalid(Type::MakeSpecialType("<INVALID>", "O", Type::TypeKind::kOther))
+ , fPoison(Type::MakeSpecialType(Compiler::POISON_TAG, "P", Type::TypeKind::kOther))
+ , fVoid(Type::MakeSpecialType("void", "v", Type::TypeKind::kVoid))
+ , fFloatLiteral(Type::MakeLiteralType("$floatLiteral", *fFloat, /*priority=*/8))
+ , fIntLiteral(Type::MakeLiteralType("$intLiteral", *fInt, /*priority=*/5))
+ , fFloat2x2(Type::MakeMatrixType("float2x2", "f22", *fFloat, /*columns=*/2, /*rows=*/2))
+ , fFloat2x3(Type::MakeMatrixType("float2x3", "f23", *fFloat, /*columns=*/2, /*rows=*/3))
+ , fFloat2x4(Type::MakeMatrixType("float2x4", "f24", *fFloat, /*columns=*/2, /*rows=*/4))
+ , fFloat3x2(Type::MakeMatrixType("float3x2", "f32", *fFloat, /*columns=*/3, /*rows=*/2))
+ , fFloat3x3(Type::MakeMatrixType("float3x3", "f33", *fFloat, /*columns=*/3, /*rows=*/3))
+ , fFloat3x4(Type::MakeMatrixType("float3x4", "f34", *fFloat, /*columns=*/3, /*rows=*/4))
+ , fFloat4x2(Type::MakeMatrixType("float4x2", "f42", *fFloat, /*columns=*/4, /*rows=*/2))
+ , fFloat4x3(Type::MakeMatrixType("float4x3", "f43", *fFloat, /*columns=*/4, /*rows=*/3))
+ , fFloat4x4(Type::MakeMatrixType("float4x4", "f44", *fFloat, /*columns=*/4, /*rows=*/4))
+ , fHalf2x2(Type::MakeMatrixType("half2x2", "h22", *fHalf, /*columns=*/2, /*rows=*/2))
+ , fHalf2x3(Type::MakeMatrixType("half2x3", "h23", *fHalf, /*columns=*/2, /*rows=*/3))
+ , fHalf2x4(Type::MakeMatrixType("half2x4", "h24", *fHalf, /*columns=*/2, /*rows=*/4))
+ , fHalf3x2(Type::MakeMatrixType("half3x2", "h32", *fHalf, /*columns=*/3, /*rows=*/2))
+ , fHalf3x3(Type::MakeMatrixType("half3x3", "h33", *fHalf, /*columns=*/3, /*rows=*/3))
+ , fHalf3x4(Type::MakeMatrixType("half3x4", "h34", *fHalf, /*columns=*/3, /*rows=*/4))
+ , fHalf4x2(Type::MakeMatrixType("half4x2", "h42", *fHalf, /*columns=*/4, /*rows=*/2))
+ , fHalf4x3(Type::MakeMatrixType("half4x3", "h43", *fHalf, /*columns=*/4, /*rows=*/3))
+ , fHalf4x4(Type::MakeMatrixType("half4x4", "h44", *fHalf, /*columns=*/4, /*rows=*/4))
+ , fVec2(Type::MakeAliasType("vec2", *fFloat2))
+ , fVec3(Type::MakeAliasType("vec3", *fFloat3))
+ , fVec4(Type::MakeAliasType("vec4", *fFloat4))
+ , fIVec2(Type::MakeAliasType("ivec2", *fInt2))
+ , fIVec3(Type::MakeAliasType("ivec3", *fInt3))
+ , fIVec4(Type::MakeAliasType("ivec4", *fInt4))
+ , fBVec2(Type::MakeAliasType("bvec2", *fBool2))
+ , fBVec3(Type::MakeAliasType("bvec3", *fBool3))
+ , fBVec4(Type::MakeAliasType("bvec4", *fBool4))
+ , fMat2(Type::MakeAliasType("mat2", *fFloat2x2))
+ , fMat3(Type::MakeAliasType("mat3", *fFloat3x3))
+ , fMat4(Type::MakeAliasType("mat4", *fFloat4x4))
+ , fMat2x2(Type::MakeAliasType("mat2x2", *fFloat2x2))
+ , fMat2x3(Type::MakeAliasType("mat2x3", *fFloat2x3))
+ , fMat2x4(Type::MakeAliasType("mat2x4", *fFloat2x4))
+ , fMat3x2(Type::MakeAliasType("mat3x2", *fFloat3x2))
+ , fMat3x3(Type::MakeAliasType("mat3x3", *fFloat3x3))
+ , fMat3x4(Type::MakeAliasType("mat3x4", *fFloat3x4))
+ , fMat4x2(Type::MakeAliasType("mat4x2", *fFloat4x2))
+ , fMat4x3(Type::MakeAliasType("mat4x3", *fFloat4x3))
+ , fMat4x4(Type::MakeAliasType("mat4x4", *fFloat4x4))
+ , fTexture2D(Type::MakeTextureType("texture2D",
+ SpvDim2D,
+ /*isDepth=*/false,
+ /*isArrayedTexture=*/false,
+ /*isMultisampled=*/false,
+ Type::TextureAccess::kSample))
+ , fTextureExternalOES(Type::MakeTextureType("textureExternalOES",
+ SpvDim2D,
+ /*isDepth=*/false,
+ /*isArrayedTexture=*/false,
+ /*isMultisampled=*/false,
+ Type::TextureAccess::kSample))
+ , fTexture2DRect(Type::MakeTextureType("texture2DRect",
+ SpvDimRect,
+ /*isDepth=*/false,
+ /*isArrayedTexture=*/false,
+ /*isMultisampled=*/false,
+ Type::TextureAccess::kSample))
+ , fReadWriteTexture2D(Type::MakeTextureType("readWriteTexture2D",
+ SpvDim2D,
+ /*isDepth=*/false,
+ /*isArrayedTexture=*/false,
+ /*isMultisampled=*/false,
+ Type::TextureAccess::kReadWrite))
+ , fReadOnlyTexture2D(Type::MakeTextureType("readonlyTexture2D",
+ SpvDim2D,
+ /*isDepth=*/false,
+ /*isArrayedTexture=*/false,
+ /*isMultisampled=*/false,
+ Type::TextureAccess::kRead))
+ , fWriteOnlyTexture2D(Type::MakeTextureType("writeonlyTexture2D",
+ SpvDim2D,
+ /*isDepth=*/false,
+ /*isArrayedTexture=*/false,
+ /*isMultisampled=*/false,
+ Type::TextureAccess::kWrite))
+ , fGenTexture2D(Type::MakeGenericType("$genTexture2D",
+ {fReadOnlyTexture2D.get(),
+ fWriteOnlyTexture2D.get(),
+ fReadWriteTexture2D.get()}))
+ , fReadableTexture2D(Type::MakeGenericType("$readableTexture2D",
+ {fReadOnlyTexture2D.get(),
+ fInvalid.get(),
+ fReadWriteTexture2D.get()}))
+ , fWritableTexture2D(Type::MakeGenericType("$writableTexture2D",
+ {fInvalid.get(),
+ fWriteOnlyTexture2D.get(),
+ fReadWriteTexture2D.get()}))
+ , fSampler2D(Type::MakeSamplerType("sampler2D", *fTexture2D))
+ , fSamplerExternalOES(Type::MakeSamplerType("samplerExternalOES", *fTextureExternalOES))
+ , fSampler2DRect(Type::MakeSamplerType("sampler2DRect", *fTexture2DRect))
+
+ , fSampler(Type::MakeSpecialType("sampler", "ss", Type::TypeKind::kSeparateSampler))
+
+ , fSubpassInput(Type::MakeTextureType("subpassInput",
+ SpvDimSubpassData,
+ /*isDepth=*/false,
+ /*isArrayedTexture=*/false,
+ /*isMultisampled=*/false,
+ Type::TextureAccess::kRead))
+ , fSubpassInputMS(Type::MakeTextureType("subpassInputMS",
+ SpvDimSubpassData,
+ /*isDepth=*/false,
+ /*isArrayedTexture=*/false,
+ /*isMultisampled=*/true,
+ Type::TextureAccess::kRead))
+ , fGenType(Type::MakeGenericType("$genType", {fFloat.get(), fFloat2.get(), fFloat3.get(),
+ fFloat4.get()}))
+ , fGenHType(Type::MakeGenericType("$genHType", {fHalf.get(), fHalf2.get(), fHalf3.get(),
+ fHalf4.get()}))
+ , fGenIType(Type::MakeGenericType("$genIType", {fInt.get(), fInt2.get(), fInt3.get(),
+ fInt4.get()}))
+ , fGenUType(Type::MakeGenericType("$genUType", {fUInt.get(), fUInt2.get(), fUInt3.get(),
+ fUInt4.get()}))
+ , fGenBType(Type::MakeGenericType("$genBType", {fBool.get(), fBool2.get(), fBool3.get(),
+ fBool4.get()}))
+ , fMat(Type::MakeGenericType("$mat", {fFloat2x2.get(), fFloat2x3.get(), fFloat2x4.get(),
+ fFloat3x2.get(), fFloat3x3.get(), fFloat3x4.get(),
+ fFloat4x2.get(), fFloat4x3.get(), fFloat4x4.get()}))
+ , fHMat(Type::MakeGenericType(
+ "$hmat",
+ {fHalf2x2.get(), fHalf2x3.get(), fHalf2x4.get(), fHalf3x2.get(), fHalf3x3.get(),
+ fHalf3x4.get(), fHalf4x2.get(), fHalf4x3.get(), fHalf4x4.get()}))
+ , fSquareMat(Type::MakeGenericType("$squareMat", {fInvalid.get(), fFloat2x2.get(),
+ fFloat3x3.get(), fFloat4x4.get()}))
+ , fSquareHMat(Type::MakeGenericType("$squareHMat", {fInvalid.get(), fHalf2x2.get(),
+ fHalf3x3.get(), fHalf4x4.get()}))
+ , fVec(Type::MakeGenericType("$vec", {fInvalid.get(), fFloat2.get(), fFloat3.get(),
+ fFloat4.get()}))
+ , fHVec(Type::MakeGenericType("$hvec", {fInvalid.get(), fHalf2.get(), fHalf3.get(),
+ fHalf4.get()}))
+ , fIVec(Type::MakeGenericType("$ivec", {fInvalid.get(), fInt2.get(), fInt3.get(),
+ fInt4.get()}))
+ , fUVec(Type::MakeGenericType("$uvec", {fInvalid.get(), fUInt2.get(), fUInt3.get(),
+ fUInt4.get()}))
+ , fSVec(Type::MakeGenericType("$svec", {fInvalid.get(), fShort2.get(), fShort3.get(),
+ fShort4.get()}))
+ , fUSVec(Type::MakeGenericType("$usvec", {fInvalid.get(), fUShort2.get(), fUShort3.get(),
+ fUShort4.get()}))
+ , fBVec(Type::MakeGenericType("$bvec", {fInvalid.get(), fBool2.get(), fBool3.get(),
+ fBool4.get()}))
+ , fSkCaps(Type::MakeSpecialType("$sk_Caps", "O", Type::TypeKind::kOther))
+ , fColorFilter(Type::MakeSpecialType("colorFilter", "CF", Type::TypeKind::kColorFilter))
+ , fShader(Type::MakeSpecialType("shader", "SH", Type::TypeKind::kShader))
+ , fBlender(Type::MakeSpecialType("blender", "B", Type::TypeKind::kBlender))
+ , fAtomicUInt(Type::MakeAtomicType("atomicUint", "au")) {}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/SkSLBuiltinTypes.h b/gfx/skia/skia/src/sksl/SkSLBuiltinTypes.h
new file mode 100644
index 0000000000..75da759659
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLBuiltinTypes.h
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_BUILTIN_TYPES
+#define SKSL_BUILTIN_TYPES
+
+#include <memory>
+
+#include "src/sksl/ir/SkSLType.h"
+
+namespace SkSL {
+
+/**
+ * Contains the built-in, core types for SkSL.
+ */
+class BuiltinTypes {
+public:
+ BuiltinTypes();
+
+ const std::unique_ptr<Type> fFloat;
+ const std::unique_ptr<Type> fFloat2;
+ const std::unique_ptr<Type> fFloat3;
+ const std::unique_ptr<Type> fFloat4;
+
+ const std::unique_ptr<Type> fHalf;
+ const std::unique_ptr<Type> fHalf2;
+ const std::unique_ptr<Type> fHalf3;
+ const std::unique_ptr<Type> fHalf4;
+
+ const std::unique_ptr<Type> fInt;
+ const std::unique_ptr<Type> fInt2;
+ const std::unique_ptr<Type> fInt3;
+ const std::unique_ptr<Type> fInt4;
+
+ const std::unique_ptr<Type> fUInt;
+ const std::unique_ptr<Type> fUInt2;
+ const std::unique_ptr<Type> fUInt3;
+ const std::unique_ptr<Type> fUInt4;
+
+ const std::unique_ptr<Type> fShort;
+ const std::unique_ptr<Type> fShort2;
+ const std::unique_ptr<Type> fShort3;
+ const std::unique_ptr<Type> fShort4;
+
+ const std::unique_ptr<Type> fUShort;
+ const std::unique_ptr<Type> fUShort2;
+ const std::unique_ptr<Type> fUShort3;
+ const std::unique_ptr<Type> fUShort4;
+
+ const std::unique_ptr<Type> fBool;
+ const std::unique_ptr<Type> fBool2;
+ const std::unique_ptr<Type> fBool3;
+ const std::unique_ptr<Type> fBool4;
+
+ const std::unique_ptr<Type> fInvalid;
+ const std::unique_ptr<Type> fPoison;
+ const std::unique_ptr<Type> fVoid;
+ const std::unique_ptr<Type> fFloatLiteral;
+ const std::unique_ptr<Type> fIntLiteral;
+
+ const std::unique_ptr<Type> fFloat2x2;
+ const std::unique_ptr<Type> fFloat2x3;
+ const std::unique_ptr<Type> fFloat2x4;
+ const std::unique_ptr<Type> fFloat3x2;
+ const std::unique_ptr<Type> fFloat3x3;
+ const std::unique_ptr<Type> fFloat3x4;
+ const std::unique_ptr<Type> fFloat4x2;
+ const std::unique_ptr<Type> fFloat4x3;
+ const std::unique_ptr<Type> fFloat4x4;
+
+ const std::unique_ptr<Type> fHalf2x2;
+ const std::unique_ptr<Type> fHalf2x3;
+ const std::unique_ptr<Type> fHalf2x4;
+ const std::unique_ptr<Type> fHalf3x2;
+ const std::unique_ptr<Type> fHalf3x3;
+ const std::unique_ptr<Type> fHalf3x4;
+ const std::unique_ptr<Type> fHalf4x2;
+ const std::unique_ptr<Type> fHalf4x3;
+ const std::unique_ptr<Type> fHalf4x4;
+
+ const std::unique_ptr<Type> fVec2;
+ const std::unique_ptr<Type> fVec3;
+ const std::unique_ptr<Type> fVec4;
+
+ const std::unique_ptr<Type> fIVec2;
+ const std::unique_ptr<Type> fIVec3;
+ const std::unique_ptr<Type> fIVec4;
+
+ const std::unique_ptr<Type> fBVec2;
+ const std::unique_ptr<Type> fBVec3;
+ const std::unique_ptr<Type> fBVec4;
+
+ const std::unique_ptr<Type> fMat2;
+ const std::unique_ptr<Type> fMat3;
+ const std::unique_ptr<Type> fMat4;
+
+ const std::unique_ptr<Type> fMat2x2;
+ const std::unique_ptr<Type> fMat2x3;
+ const std::unique_ptr<Type> fMat2x4;
+ const std::unique_ptr<Type> fMat3x2;
+ const std::unique_ptr<Type> fMat3x3;
+ const std::unique_ptr<Type> fMat3x4;
+ const std::unique_ptr<Type> fMat4x2;
+ const std::unique_ptr<Type> fMat4x3;
+ const std::unique_ptr<Type> fMat4x4;
+
+ const std::unique_ptr<Type> fTexture2D;
+ const std::unique_ptr<Type> fTextureExternalOES;
+ const std::unique_ptr<Type> fTexture2DRect;
+
+ const std::unique_ptr<Type> fReadWriteTexture2D;
+ const std::unique_ptr<Type> fReadOnlyTexture2D;
+ const std::unique_ptr<Type> fWriteOnlyTexture2D;
+
+ const std::unique_ptr<Type> fGenTexture2D;
+ const std::unique_ptr<Type> fReadableTexture2D;
+ const std::unique_ptr<Type> fWritableTexture2D;
+
+ const std::unique_ptr<Type> fSampler2D;
+ const std::unique_ptr<Type> fSamplerExternalOES;
+ const std::unique_ptr<Type> fSampler2DRect;
+
+ const std::unique_ptr<Type> fSampler;
+
+ const std::unique_ptr<Type> fSubpassInput;
+ const std::unique_ptr<Type> fSubpassInputMS;
+
+ const std::unique_ptr<Type> fGenType;
+ const std::unique_ptr<Type> fGenHType;
+ const std::unique_ptr<Type> fGenIType;
+ const std::unique_ptr<Type> fGenUType;
+ const std::unique_ptr<Type> fGenBType;
+
+ const std::unique_ptr<Type> fMat;
+ const std::unique_ptr<Type> fHMat;
+ const std::unique_ptr<Type> fSquareMat;
+ const std::unique_ptr<Type> fSquareHMat;
+
+ const std::unique_ptr<Type> fVec;
+
+ const std::unique_ptr<Type> fHVec;
+ const std::unique_ptr<Type> fDVec;
+ const std::unique_ptr<Type> fIVec;
+ const std::unique_ptr<Type> fUVec;
+ const std::unique_ptr<Type> fSVec;
+ const std::unique_ptr<Type> fUSVec;
+ const std::unique_ptr<Type> fByteVec;
+ const std::unique_ptr<Type> fUByteVec;
+
+ const std::unique_ptr<Type> fBVec;
+
+ const std::unique_ptr<Type> fSkCaps;
+
+ const std::unique_ptr<Type> fColorFilter;
+ const std::unique_ptr<Type> fShader;
+ const std::unique_ptr<Type> fBlender;
+
+ const std::unique_ptr<Type> fAtomicUInt;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/SkSLCompiler.cpp b/gfx/skia/skia/src/sksl/SkSLCompiler.cpp
new file mode 100644
index 0000000000..78498b58af
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLCompiler.cpp
@@ -0,0 +1,726 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/SkSLCompiler.h"
+
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLProgramKind.h"
+#include "include/private/SkSLSymbol.h"
+#include "include/private/base/SkDebug.h"
+#include "include/sksl/DSLCore.h"
+#include "include/sksl/DSLModifiers.h"
+#include "include/sksl/DSLType.h"
+#include "src/core/SkTraceEvent.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLInliner.h"
+#include "src/sksl/SkSLModuleLoader.h"
+#include "src/sksl/SkSLOutputStream.h"
+#include "src/sksl/SkSLParser.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/SkSLStringStream.h"
+#include "src/sksl/analysis/SkSLProgramUsage.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLField.h"
+#include "src/sksl/ir/SkSLFieldAccess.h"
+#include "src/sksl/ir/SkSLFunctionDeclaration.h"
+#include "src/sksl/ir/SkSLFunctionReference.h"
+#include "src/sksl/ir/SkSLProgram.h"
+#include "src/sksl/ir/SkSLSymbolTable.h" // IWYU pragma: keep
+#include "src/sksl/ir/SkSLTypeReference.h"
+#include "src/sksl/ir/SkSLVariable.h"
+#include "src/sksl/ir/SkSLVariableReference.h"
+#include "src/sksl/transform/SkSLTransform.h"
+
+#include <atomic>
+#include <cstdint>
+#include <memory>
+#include <utility>
+
+#if defined(SKSL_STANDALONE)
+#include <fstream>
+#endif
+
+#if defined(SKSL_STANDALONE) || defined(SK_GANESH) || defined(SK_GRAPHITE)
+#include "src/sksl/codegen/SkSLGLSLCodeGenerator.h"
+#include "src/sksl/codegen/SkSLMetalCodeGenerator.h"
+#include "src/sksl/codegen/SkSLSPIRVCodeGenerator.h"
+#include "src/sksl/codegen/SkSLSPIRVtoHLSL.h"
+#include "src/sksl/codegen/SkSLWGSLCodeGenerator.h"
+#endif
+
+#ifdef SK_ENABLE_SPIRV_VALIDATION
+#include "spirv-tools/libspirv.hpp"
+#endif
+
+#ifdef SK_ENABLE_WGSL_VALIDATION
+#include "tint/tint.h"
+#endif
+
+namespace SkSL {
+
+class ModifiersPool;
+
+// These flags allow tools like Viewer or Nanobench to override the compiler's ProgramSettings.
+Compiler::OverrideFlag Compiler::sOptimizer = OverrideFlag::kDefault;
+Compiler::OverrideFlag Compiler::sInliner = OverrideFlag::kDefault;
+
+using RefKind = VariableReference::RefKind;
+
+class AutoSource {
+public:
+ AutoSource(Compiler* compiler, std::string_view source)
+ : fCompiler(compiler) {
+ SkASSERT(!fCompiler->errorReporter().source().data());
+ fCompiler->errorReporter().setSource(source);
+ }
+
+ ~AutoSource() {
+ fCompiler->errorReporter().setSource(std::string_view());
+ }
+
+ Compiler* fCompiler;
+};
+
+class AutoProgramConfig {
+public:
+ AutoProgramConfig(Context& context, ProgramConfig* config)
+ : fContext(context)
+ , fOldConfig(context.fConfig) {
+ fContext.fConfig = config;
+ }
+
+ ~AutoProgramConfig() {
+ fContext.fConfig = fOldConfig;
+ }
+
+ Context& fContext;
+ ProgramConfig* fOldConfig;
+};
+
+class AutoShaderCaps {
+public:
+ AutoShaderCaps(std::shared_ptr<Context>& context, const ShaderCaps* caps)
+ : fContext(context.get())
+ , fOldCaps(fContext->fCaps) {
+ fContext->fCaps = caps;
+ }
+
+ ~AutoShaderCaps() {
+ fContext->fCaps = fOldCaps;
+ }
+
+ Context* fContext;
+ const ShaderCaps* fOldCaps;
+};
+
+class AutoModifiersPool {
+public:
+ AutoModifiersPool(std::shared_ptr<Context>& context, ModifiersPool* modifiersPool)
+ : fContext(context.get()) {
+ SkASSERT(!fContext->fModifiersPool);
+ fContext->fModifiersPool = modifiersPool;
+ }
+
+ ~AutoModifiersPool() {
+ fContext->fModifiersPool = nullptr;
+ }
+
+ Context* fContext;
+};
+
+Compiler::Compiler(const ShaderCaps* caps) : fErrorReporter(this), fCaps(caps) {
+ SkASSERT(caps);
+
+ auto moduleLoader = ModuleLoader::Get();
+ fContext = std::make_shared<Context>(moduleLoader.builtinTypes(), /*caps=*/nullptr,
+ fErrorReporter);
+}
+
+Compiler::~Compiler() {}
+
+const Module* Compiler::moduleForProgramKind(ProgramKind kind) {
+ auto m = ModuleLoader::Get();
+ switch (kind) {
+ case ProgramKind::kVertex: return m.loadVertexModule(this);
+ case ProgramKind::kFragment: return m.loadFragmentModule(this);
+ case ProgramKind::kCompute: return m.loadComputeModule(this);
+ case ProgramKind::kGraphiteVertex: return m.loadGraphiteVertexModule(this);
+ case ProgramKind::kGraphiteFragment: return m.loadGraphiteFragmentModule(this);
+ case ProgramKind::kPrivateRuntimeShader: return m.loadPrivateRTShaderModule(this);
+ case ProgramKind::kRuntimeColorFilter:
+ case ProgramKind::kRuntimeShader:
+ case ProgramKind::kRuntimeBlender:
+ case ProgramKind::kPrivateRuntimeColorFilter:
+ case ProgramKind::kPrivateRuntimeBlender:
+ case ProgramKind::kMeshVertex:
+ case ProgramKind::kMeshFragment: return m.loadPublicModule(this);
+ }
+ SkUNREACHABLE;
+}
+
+void Compiler::FinalizeSettings(ProgramSettings* settings, ProgramKind kind) {
+ // Honor our optimization-override flags.
+ switch (sOptimizer) {
+ case OverrideFlag::kDefault:
+ break;
+ case OverrideFlag::kOff:
+ settings->fOptimize = false;
+ break;
+ case OverrideFlag::kOn:
+ settings->fOptimize = true;
+ break;
+ }
+
+ switch (sInliner) {
+ case OverrideFlag::kDefault:
+ break;
+ case OverrideFlag::kOff:
+ settings->fInlineThreshold = 0;
+ break;
+ case OverrideFlag::kOn:
+ if (settings->fInlineThreshold == 0) {
+ settings->fInlineThreshold = kDefaultInlineThreshold;
+ }
+ break;
+ }
+
+ // Disable optimization settings that depend on a parent setting which has been disabled.
+ settings->fInlineThreshold *= (int)settings->fOptimize;
+ settings->fRemoveDeadFunctions &= settings->fOptimize;
+ settings->fRemoveDeadVariables &= settings->fOptimize;
+
+ // Runtime effects always allow narrowing conversions.
+ if (ProgramConfig::IsRuntimeEffect(kind)) {
+ settings->fAllowNarrowingConversions = true;
+ }
+}
+
+std::unique_ptr<Module> Compiler::compileModule(ProgramKind kind,
+ const char* moduleName,
+ std::string moduleSource,
+ const Module* parent,
+ ModifiersPool& modifiersPool,
+ bool shouldInline) {
+ SkASSERT(parent);
+ SkASSERT(!moduleSource.empty());
+ SkASSERT(this->errorCount() == 0);
+
+ // Modules are shared and cannot rely on shader caps.
+ AutoShaderCaps autoCaps(fContext, nullptr);
+ AutoModifiersPool autoPool(fContext, &modifiersPool);
+
+ // Compile the module from source, using default program settings.
+ ProgramSettings settings;
+ FinalizeSettings(&settings, kind);
+ SkSL::Parser parser{this, settings, kind, std::move(moduleSource)};
+ std::unique_ptr<Module> module = parser.moduleInheritingFrom(parent);
+ if (this->errorCount() != 0) {
+ SkDebugf("Unexpected errors compiling %s:\n\n%s\n", moduleName, this->errorText().c_str());
+ return nullptr;
+ }
+ if (shouldInline) {
+ this->optimizeModuleAfterLoading(kind, *module);
+ }
+ return module;
+}
+
+std::unique_ptr<Program> Compiler::convertProgram(ProgramKind kind,
+ std::string text,
+ ProgramSettings settings) {
+ TRACE_EVENT0("skia.shaders", "SkSL::Compiler::convertProgram");
+
+ // Make sure the passed-in settings are valid.
+ FinalizeSettings(&settings, kind);
+
+ // Put the ShaderCaps into the context while compiling a program.
+ AutoShaderCaps autoCaps(fContext, fCaps);
+
+ this->resetErrors();
+
+ return Parser(this, settings, kind, std::move(text)).program();
+}
+
+std::unique_ptr<Expression> Compiler::convertIdentifier(Position pos, std::string_view name) {
+ const Symbol* result = fSymbolTable->find(name);
+ if (!result) {
+ this->errorReporter().error(pos, "unknown identifier '" + std::string(name) + "'");
+ return nullptr;
+ }
+ switch (result->kind()) {
+ case Symbol::Kind::kFunctionDeclaration: {
+ return std::make_unique<FunctionReference>(*fContext, pos,
+ &result->as<FunctionDeclaration>());
+ }
+ case Symbol::Kind::kVariable: {
+ const Variable* var = &result->as<Variable>();
+ // default to kRead_RefKind; this will be corrected later if the variable is written to
+ return VariableReference::Make(pos, var, VariableReference::RefKind::kRead);
+ }
+ case Symbol::Kind::kField: {
+ const Field* field = &result->as<Field>();
+ auto base = VariableReference::Make(pos, &field->owner(),
+ VariableReference::RefKind::kRead);
+ return FieldAccess::Make(*fContext, pos, std::move(base), field->fieldIndex(),
+ FieldAccess::OwnerKind::kAnonymousInterfaceBlock);
+ }
+ case Symbol::Kind::kType: {
+ // go through DSLType so we report errors on private types
+ dsl::DSLModifiers modifiers;
+ dsl::DSLType dslType(result->name(), &modifiers, pos);
+ return TypeReference::Convert(*fContext, pos, &dslType.skslType());
+ }
+ default:
+ SK_ABORT("unsupported symbol type %d\n", (int) result->kind());
+ }
+}
+
+bool Compiler::optimizeModuleBeforeMinifying(ProgramKind kind, Module& module) {
+ SkASSERT(this->errorCount() == 0);
+
+ auto m = SkSL::ModuleLoader::Get();
+
+ // Create a temporary program configuration with default settings.
+ ProgramConfig config;
+ config.fIsBuiltinCode = true;
+ config.fKind = kind;
+ AutoProgramConfig autoConfig(this->context(), &config);
+ AutoModifiersPool autoPool(fContext, &m.coreModifiers());
+
+ std::unique_ptr<ProgramUsage> usage = Analysis::GetUsage(module);
+
+ // Assign shorter names to symbols as long as it won't change the external meaning of the code.
+ Transform::RenamePrivateSymbols(this->context(), module, usage.get(), kind);
+
+ // Replace constant variables with their literal values to save space.
+ Transform::ReplaceConstVarsWithLiterals(module, usage.get());
+
+ // Remove any unreachable code.
+ Transform::EliminateUnreachableCode(module, usage.get());
+
+ // We can only remove dead functions from runtime shaders, since runtime-effect helper functions
+ // are isolated from other parts of the program. In a module, an unreferenced function is
+ // intended to be called by the code that includes the module.
+ if (kind == ProgramKind::kRuntimeShader) {
+ while (Transform::EliminateDeadFunctions(this->context(), module, usage.get())) {
+ // Removing dead functions may cause more functions to become unreferenced. Try again.
+ }
+ }
+
+ while (Transform::EliminateDeadLocalVariables(this->context(), module, usage.get())) {
+ // Removing dead variables may cause more variables to become unreferenced. Try again.
+ }
+
+ // Runtime shaders are isolated from other parts of the program via name mangling, so we can
+ // eliminate public globals if they aren't referenced. Otherwise, we only eliminate private
+ // globals (prefixed with `$`) to avoid changing the meaning of the module code.
+ bool onlyPrivateGlobals = !ProgramConfig::IsRuntimeEffect(kind);
+ while (Transform::EliminateDeadGlobalVariables(this->context(), module, usage.get(),
+ onlyPrivateGlobals)) {
+ // Repeat until no changes occur.
+ }
+
+ // We eliminate empty statements to avoid runs of `;;;;;;` caused by the previous passes.
+ SkSL::Transform::EliminateEmptyStatements(module);
+
+ // Make sure that program usage is still correct after the optimization pass is complete.
+ SkASSERT(*usage == *Analysis::GetUsage(module));
+
+ return this->errorCount() == 0;
+}
+
+bool Compiler::optimizeModuleAfterLoading(ProgramKind kind, Module& module) {
+ SkASSERT(this->errorCount() == 0);
+
+#ifndef SK_ENABLE_OPTIMIZE_SIZE
+ // Create a temporary program configuration with default settings.
+ ProgramConfig config;
+ config.fIsBuiltinCode = true;
+ config.fKind = kind;
+ AutoProgramConfig autoConfig(this->context(), &config);
+
+ std::unique_ptr<ProgramUsage> usage = Analysis::GetUsage(module);
+
+ // Perform inline-candidate analysis and inline any functions deemed suitable.
+ Inliner inliner(fContext.get());
+ while (this->errorCount() == 0) {
+ if (!this->runInliner(&inliner, module.fElements, module.fSymbols, usage.get())) {
+ break;
+ }
+ }
+ // Make sure that program usage is still correct after the optimization pass is complete.
+ SkASSERT(*usage == *Analysis::GetUsage(module));
+#endif
+
+ return this->errorCount() == 0;
+}
+
+bool Compiler::optimize(Program& program) {
+ // The optimizer only needs to run when it is enabled.
+ if (!program.fConfig->fSettings.fOptimize) {
+ return true;
+ }
+
+ AutoShaderCaps autoCaps(fContext, fCaps);
+
+ SkASSERT(!this->errorCount());
+ if (this->errorCount() == 0) {
+#ifndef SK_ENABLE_OPTIMIZE_SIZE
+ // Run the inliner only once; it is expensive! Multiple passes can occasionally shake out
+ // more wins, but it's diminishing returns.
+ Inliner inliner(fContext.get());
+ this->runInliner(&inliner, program.fOwnedElements, program.fSymbols, program.fUsage.get());
+#endif
+
+ // Unreachable code can confuse some drivers, so it's worth removing. (skia:12012)
+ Transform::EliminateUnreachableCode(program);
+
+ while (Transform::EliminateDeadFunctions(program)) {
+ // Removing dead functions may cause more functions to become unreferenced. Try again.
+ }
+ while (Transform::EliminateDeadLocalVariables(program)) {
+ // Removing dead variables may cause more variables to become unreferenced. Try again.
+ }
+ while (Transform::EliminateDeadGlobalVariables(program)) {
+ // Repeat until no changes occur.
+ }
+ // Make sure that program usage is still correct after the optimization pass is complete.
+ SkASSERT(*program.usage() == *Analysis::GetUsage(program));
+ }
+
+ return this->errorCount() == 0;
+}
+
+bool Compiler::runInliner(Inliner* inliner,
+ const std::vector<std::unique_ptr<ProgramElement>>& elements,
+ std::shared_ptr<SymbolTable> symbols,
+ ProgramUsage* usage) {
+#ifdef SK_ENABLE_OPTIMIZE_SIZE
+ return true;
+#else
+ // The program's SymbolTable was taken out of fSymbolTable when the program was bundled, but
+ // the inliner relies (indirectly) on having a valid SymbolTable.
+ // In particular, inlining can turn a non-optimizable expression like `normalize(myVec)` into
+ // `normalize(vec2(7))`, which is now optimizable. The optimizer can use DSL to simplify this
+ // expression--e.g., in the case of normalize, using DSL's Length(). The DSL relies on
+ // convertIdentifier() to look up `length`. convertIdentifier() needs a valid symbol table to
+ // find the declaration of `length`. To allow this chain of events to succeed, we re-insert the
+ // program's symbol table temporarily.
+ SkASSERT(!fSymbolTable);
+ fSymbolTable = symbols;
+
+ bool result = inliner->analyze(elements, symbols, usage);
+
+ fSymbolTable = nullptr;
+ return result;
+#endif
+}
+
+bool Compiler::finalize(Program& program) {
+ AutoShaderCaps autoCaps(fContext, fCaps);
+
+ // Copy all referenced built-in functions into the Program.
+ Transform::FindAndDeclareBuiltinFunctions(program);
+
+ // Variables defined in the pre-includes need their declaring elements added to the program.
+ Transform::FindAndDeclareBuiltinVariables(program);
+
+ // Do one last correctness-check pass. This looks for dangling FunctionReference/TypeReference
+ // expressions, and reports them as errors.
+ Analysis::DoFinalizationChecks(program);
+
+ if (fContext->fConfig->strictES2Mode() && this->errorCount() == 0) {
+ // Enforce Appendix A, Section 5 of the GLSL ES 1.00 spec -- Indexing. This logic assumes
+ // that all loops meet the criteria of Section 4, and if they don't, could crash.
+ for (const auto& pe : program.fOwnedElements) {
+ Analysis::ValidateIndexingForES2(*pe, this->errorReporter());
+ }
+ }
+ if (this->errorCount() == 0) {
+ bool enforceSizeLimit = ProgramConfig::IsRuntimeEffect(program.fConfig->fKind);
+ Analysis::CheckProgramStructure(program, enforceSizeLimit);
+ }
+
+ // Make sure that program usage is still correct after finalization is complete.
+ SkASSERT(*program.usage() == *Analysis::GetUsage(program));
+
+ return this->errorCount() == 0;
+}
+
+#if defined(SKSL_STANDALONE) || defined(SK_GANESH) || defined(SK_GRAPHITE)
+
+#if defined(SK_ENABLE_SPIRV_VALIDATION)
+static bool validate_spirv(ErrorReporter& reporter, std::string_view program) {
+ SkASSERT(0 == program.size() % 4);
+ const uint32_t* programData = reinterpret_cast<const uint32_t*>(program.data());
+ size_t programSize = program.size() / 4;
+
+ spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_0);
+ std::string errors;
+ auto msgFn = [&errors](spv_message_level_t, const char*, const spv_position_t&, const char* m) {
+ errors += "SPIR-V validation error: ";
+ errors += m;
+ errors += '\n';
+ };
+ tools.SetMessageConsumer(msgFn);
+
+ // Verify that the SPIR-V we produced is valid. At runtime, we will abort() with a message
+ // explaining the error. In standalone mode (skslc), we will send the message, plus the
+ // entire disassembled SPIR-V (for easier context & debugging) as *our* error message.
+ bool result = tools.Validate(programData, programSize);
+ if (!result) {
+#if defined(SKSL_STANDALONE)
+ // Convert the string-stream to a SPIR-V disassembly.
+ std::string disassembly;
+ if (tools.Disassemble(programData, programSize, &disassembly)) {
+ errors.append(disassembly);
+ }
+ reporter.error(Position(), errors);
+#else
+ SkDEBUGFAILF("%s", errors.c_str());
+#endif
+ }
+ return result;
+}
+#endif
+
+bool Compiler::toSPIRV(Program& program, OutputStream& out) {
+ TRACE_EVENT0("skia.shaders", "SkSL::Compiler::toSPIRV");
+ AutoSource as(this, *program.fSource);
+ AutoShaderCaps autoCaps(fContext, fCaps);
+ ProgramSettings settings;
+ settings.fUseMemoryPool = false;
+ dsl::Start(this, program.fConfig->fKind, settings);
+ dsl::SetErrorReporter(&fErrorReporter);
+ fSymbolTable = program.fSymbols;
+#ifdef SK_ENABLE_SPIRV_VALIDATION
+ StringStream buffer;
+ SPIRVCodeGenerator cg(fContext.get(), &program, &buffer);
+ bool result = cg.generateCode();
+
+ if (result && program.fConfig->fSettings.fValidateSPIRV) {
+ std::string_view binary = buffer.str();
+ result = validate_spirv(this->errorReporter(), binary);
+ out.write(binary.data(), binary.size());
+ }
+#else
+ SPIRVCodeGenerator cg(fContext.get(), &program, &out);
+ bool result = cg.generateCode();
+#endif
+ dsl::End();
+ return result;
+}
+
+bool Compiler::toSPIRV(Program& program, std::string* out) {
+ StringStream buffer;
+ bool result = this->toSPIRV(program, buffer);
+ if (result) {
+ *out = buffer.str();
+ }
+ return result;
+}
+
+bool Compiler::toGLSL(Program& program, OutputStream& out) {
+ TRACE_EVENT0("skia.shaders", "SkSL::Compiler::toGLSL");
+ AutoSource as(this, *program.fSource);
+ AutoShaderCaps autoCaps(fContext, fCaps);
+ GLSLCodeGenerator cg(fContext.get(), &program, &out);
+ bool result = cg.generateCode();
+ return result;
+}
+
+bool Compiler::toGLSL(Program& program, std::string* out) {
+ StringStream buffer;
+ bool result = this->toGLSL(program, buffer);
+ if (result) {
+ *out = buffer.str();
+ }
+ return result;
+}
+
+bool Compiler::toHLSL(Program& program, OutputStream& out) {
+ TRACE_EVENT0("skia.shaders", "SkSL::Compiler::toHLSL");
+ std::string hlsl;
+ if (!this->toHLSL(program, &hlsl)) {
+ return false;
+ }
+ out.writeString(hlsl);
+ return true;
+}
+
+bool Compiler::toHLSL(Program& program, std::string* out) {
+ std::string spirv;
+ if (!this->toSPIRV(program, &spirv)) {
+ return false;
+ }
+
+ if (!SPIRVtoHLSL(spirv, out)) {
+ fErrorText += "HLSL cross-compilation not enabled";
+ return false;
+ }
+
+ return true;
+}
+
+bool Compiler::toMetal(Program& program, OutputStream& out) {
+ TRACE_EVENT0("skia.shaders", "SkSL::Compiler::toMetal");
+ AutoSource as(this, *program.fSource);
+ AutoShaderCaps autoCaps(fContext, fCaps);
+ MetalCodeGenerator cg(fContext.get(), &program, &out);
+ bool result = cg.generateCode();
+ return result;
+}
+
+bool Compiler::toMetal(Program& program, std::string* out) {
+ StringStream buffer;
+ bool result = this->toMetal(program, buffer);
+ if (result) {
+ *out = buffer.str();
+ }
+ return result;
+}
+
+#if defined(SK_ENABLE_WGSL_VALIDATION)
+static bool validate_wgsl(ErrorReporter& reporter, const std::string& wgsl) {
+ tint::Source::File srcFile("", wgsl);
+ tint::Program program(tint::reader::wgsl::Parse(&srcFile));
+ if (program.Diagnostics().count() > 0) {
+ tint::diag::Formatter diagFormatter;
+ std::string diagOutput = diagFormatter.format(program.Diagnostics());
+#if defined(SKSL_STANDALONE)
+ reporter.error(Position(), diagOutput);
+#else
+ SkDEBUGFAILF("%s", diagOutput.c_str());
+#endif
+ return false;
+ }
+ return true;
+}
+#endif // defined(SK_ENABLE_WGSL_VALIDATION)
+
+bool Compiler::toWGSL(Program& program, OutputStream& out) {
+ TRACE_EVENT0("skia.shaders", "SkSL::Compiler::toWGSL");
+ AutoSource as(this, *program.fSource);
+#ifdef SK_ENABLE_WGSL_VALIDATION
+ StringStream wgsl;
+ WGSLCodeGenerator cg(fContext.get(), &program, &wgsl);
+ bool result = cg.generateCode();
+ if (result) {
+ std::string wgslString = wgsl.str();
+ result = validate_wgsl(this->errorReporter(), wgslString);
+ out.writeString(wgslString);
+ }
+#else
+ WGSLCodeGenerator cg(fContext.get(), &program, &out);
+ bool result = cg.generateCode();
+#endif
+ return result;
+}
+
+#endif // defined(SKSL_STANDALONE) || defined(SK_GANESH) || defined(SK_GRAPHITE)
+
+void Compiler::handleError(std::string_view msg, Position pos) {
+ fErrorText += "error: ";
+ bool printLocation = false;
+ std::string_view src = this->errorReporter().source();
+ int line = -1;
+ if (pos.valid()) {
+ line = pos.line(src);
+ printLocation = pos.startOffset() < (int)src.length();
+ fErrorText += std::to_string(line) + ": ";
+ }
+ fErrorText += std::string(msg) + "\n";
+ if (printLocation) {
+ const int kMaxSurroundingChars = 100;
+
+ // Find the beginning of the line.
+ int lineStart = pos.startOffset();
+ while (lineStart > 0) {
+ if (src[lineStart - 1] == '\n') {
+ break;
+ }
+ --lineStart;
+ }
+
+ // We don't want to show more than 100 characters surrounding the error, so push the line
+ // start forward and add a leading ellipsis if there would be more than this.
+ std::string lineText;
+ std::string caretText;
+ if ((pos.startOffset() - lineStart) > kMaxSurroundingChars) {
+ lineStart = pos.startOffset() - kMaxSurroundingChars;
+ lineText = "...";
+ caretText = " ";
+ }
+
+ // Echo the line. Again, we don't want to show more than 100 characters after the end of the
+ // error, so truncate with a trailing ellipsis if needed.
+ const char* lineSuffix = "...\n";
+ int lineStop = pos.endOffset() + kMaxSurroundingChars;
+ if (lineStop >= (int)src.length()) {
+ lineStop = src.length() - 1;
+ lineSuffix = "\n"; // no ellipsis if we reach end-of-file
+ }
+ for (int i = lineStart; i < lineStop; ++i) {
+ char c = src[i];
+ if (c == '\n') {
+ lineSuffix = "\n"; // no ellipsis if we reach end-of-line
+ break;
+ }
+ switch (c) {
+ case '\t': lineText += " "; break;
+ case '\0': lineText += " "; break;
+ default: lineText += src[i]; break;
+ }
+ }
+ fErrorText += lineText + lineSuffix;
+
+ // print the carets underneath it, pointing to the range in question
+ for (int i = lineStart; i < (int)src.length(); i++) {
+ if (i >= pos.endOffset()) {
+ break;
+ }
+ switch (src[i]) {
+ case '\t':
+ caretText += (i >= pos.startOffset()) ? "^^^^" : " ";
+ break;
+ case '\n':
+ SkASSERT(i >= pos.startOffset());
+ // use an ellipsis if the error continues past the end of the line
+ caretText += (pos.endOffset() > i + 1) ? "..." : "^";
+ i = src.length();
+ break;
+ default:
+ caretText += (i >= pos.startOffset()) ? '^' : ' ';
+ break;
+ }
+ }
+ fErrorText += caretText + '\n';
+ }
+}
+
+std::string Compiler::errorText(bool showCount) {
+ if (showCount) {
+ this->writeErrorCount();
+ }
+ std::string result = fErrorText;
+ this->resetErrors();
+ return result;
+}
+
+void Compiler::writeErrorCount() {
+ int count = this->errorCount();
+ if (count) {
+ fErrorText += std::to_string(count) + " error";
+ if (count > 1) {
+ fErrorText += "s";
+ }
+ fErrorText += "\n";
+ }
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/SkSLCompiler.h b/gfx/skia/skia/src/sksl/SkSLCompiler.h
new file mode 100644
index 0000000000..382c69609b
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLCompiler.h
@@ -0,0 +1,242 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_COMPILER
+#define SKSL_COMPILER
+
+#include "include/core/SkSize.h"
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLProgramElement.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/SkSLContext.h" // IWYU pragma: keep
+
+#include <array>
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <vector>
+
+#define SK_FRAGCOLOR_BUILTIN 10001
+#define SK_LASTFRAGCOLOR_BUILTIN 10008
+#define SK_MAIN_COORDS_BUILTIN 10009
+#define SK_INPUT_COLOR_BUILTIN 10010
+#define SK_DEST_COLOR_BUILTIN 10011
+#define SK_SECONDARYFRAGCOLOR_BUILTIN 10012
+#define SK_FRAGCOORD_BUILTIN 15
+#define SK_CLOCKWISE_BUILTIN 17
+
+#define SK_VERTEXID_BUILTIN 42
+#define SK_INSTANCEID_BUILTIN 43
+#define SK_POSITION_BUILTIN 0
+#define SK_POINTSIZE_BUILTIN 1
+
+#define SK_NUMWORKGROUPS_BUILTIN 24
+#define SK_WORKGROUPID_BUILTIN 26
+#define SK_LOCALINVOCATIONID_BUILTIN 27
+#define SK_GLOBALINVOCATIONID_BUILTIN 28
+#define SK_LOCALINVOCATIONINDEX_BUILTIN 29
+
+namespace SkSL {
+
+namespace dsl {
+ class DSLCore;
+}
+
+class Expression;
+class Inliner;
+class ModifiersPool;
+class OutputStream;
+class ProgramUsage;
+class SymbolTable;
+enum class ProgramKind : int8_t;
+struct Program;
+struct ProgramSettings;
+struct ShaderCaps;
+
+struct Module {
+ const Module* fParent = nullptr;
+ std::shared_ptr<SymbolTable> fSymbols;
+ std::vector<std::unique_ptr<ProgramElement>> fElements;
+};
+
+/**
+ * Main compiler entry point. The compiler parses the SkSL text directly into a tree of IRNodes,
+ * while performing basic optimizations such as constant-folding and dead-code elimination. Then the
+ * Program is passed into a CodeGenerator to produce compiled output.
+ *
+ * See the README for information about SkSL.
+ */
+class SK_API Compiler {
+public:
+ inline static constexpr const char FRAGCOLOR_NAME[] = "sk_FragColor";
+ inline static constexpr const char RTADJUST_NAME[] = "sk_RTAdjust";
+ inline static constexpr const char POSITION_NAME[] = "sk_Position";
+ inline static constexpr const char POISON_TAG[] = "<POISON>";
+
+ /**
+ * Gets a float4 that adjusts the position from Skia device coords to normalized device coords,
+ * used to populate sk_RTAdjust. Assuming the transformed position, pos, is a homogeneous
+ * float4, the vec, v, is applied as such:
+ * float4((pos.xy * v.xz) + sk_Position.ww * v.yw, 0, pos.w);
+ */
+ static std::array<float, 4> GetRTAdjustVector(SkISize rtDims, bool flipY) {
+ std::array<float, 4> result;
+ result[0] = 2.f/rtDims.width();
+ result[2] = 2.f/rtDims.height();
+ result[1] = -1.f;
+ result[3] = -1.f;
+ if (flipY) {
+ result[2] = -result[2];
+ result[3] = -result[3];
+ }
+ return result;
+ }
+
+ /**
+ * Uniform values used by the compiler to implement origin-neutral dFdy, sk_Clockwise, and
+ * sk_FragCoord.
+ */
+ static std::array<float, 2> GetRTFlipVector(int rtHeight, bool flipY) {
+ std::array<float, 2> result;
+ result[0] = flipY ? rtHeight : 0.f;
+ result[1] = flipY ? -1.f : 1.f;
+ return result;
+ }
+
+ Compiler(const ShaderCaps* caps);
+
+ ~Compiler();
+
+ Compiler(const Compiler&) = delete;
+ Compiler& operator=(const Compiler&) = delete;
+
+ /**
+ * Allows optimization settings to be unilaterally overridden. This is meant to allow tools like
+ * Viewer or Nanobench to override the compiler's ProgramSettings and ShaderCaps for debugging.
+ */
+ enum class OverrideFlag {
+ kDefault,
+ kOff,
+ kOn,
+ };
+ static void EnableOptimizer(OverrideFlag flag) { sOptimizer = flag; }
+ static void EnableInliner(OverrideFlag flag) { sInliner = flag; }
+
+ std::unique_ptr<Program> convertProgram(ProgramKind kind,
+ std::string text,
+ ProgramSettings settings);
+
+ std::unique_ptr<Expression> convertIdentifier(Position pos, std::string_view name);
+
+ bool toSPIRV(Program& program, OutputStream& out);
+
+ bool toSPIRV(Program& program, std::string* out);
+
+ bool toGLSL(Program& program, OutputStream& out);
+
+ bool toGLSL(Program& program, std::string* out);
+
+ bool toHLSL(Program& program, OutputStream& out);
+
+ bool toHLSL(Program& program, std::string* out);
+
+ bool toMetal(Program& program, OutputStream& out);
+
+ bool toMetal(Program& program, std::string* out);
+
+ bool toWGSL(Program& program, OutputStream& out);
+
+ void handleError(std::string_view msg, Position pos);
+
+ std::string errorText(bool showCount = true);
+
+ ErrorReporter& errorReporter() { return *fContext->fErrors; }
+
+ int errorCount() const { return fContext->fErrors->errorCount(); }
+
+ void writeErrorCount();
+
+ void resetErrors() {
+ fErrorText.clear();
+ this->errorReporter().resetErrorCount();
+ }
+
+ Context& context() const {
+ return *fContext;
+ }
+
+ std::shared_ptr<SymbolTable>& symbolTable() {
+ return fSymbolTable;
+ }
+
+ std::unique_ptr<Module> compileModule(ProgramKind kind,
+ const char* moduleName,
+ std::string moduleSource,
+ const Module* parent,
+ ModifiersPool& modifiersPool,
+ bool shouldInline);
+
+ /** Optimize a module at minification time, before writing it out. */
+ bool optimizeModuleBeforeMinifying(ProgramKind kind, Module& module);
+
+ const Module* moduleForProgramKind(ProgramKind kind);
+
+private:
+ class CompilerErrorReporter : public ErrorReporter {
+ public:
+ CompilerErrorReporter(Compiler* compiler)
+ : fCompiler(*compiler) {}
+
+ void handleError(std::string_view msg, Position pos) override {
+ fCompiler.handleError(msg, pos);
+ }
+
+ private:
+ Compiler& fCompiler;
+ };
+
+ /** Updates ProgramSettings to eliminate contradictions and to honor the ProgramKind. */
+ static void FinalizeSettings(ProgramSettings* settings, ProgramKind kind);
+
+ /** Optimize every function in the program. */
+ bool optimize(Program& program);
+
+ /** Performs final checks to confirm that a fully-assembled/optimized is valid. */
+ bool finalize(Program& program);
+
+ /** Optimize a module at Skia runtime, after loading it. */
+ bool optimizeModuleAfterLoading(ProgramKind kind, Module& module);
+
+ /** Flattens out function calls when it is safe to do so. */
+ bool runInliner(Inliner* inliner,
+ const std::vector<std::unique_ptr<ProgramElement>>& elements,
+ std::shared_ptr<SymbolTable> symbols,
+ ProgramUsage* usage);
+
+ CompilerErrorReporter fErrorReporter;
+ std::shared_ptr<Context> fContext;
+ const ShaderCaps* fCaps;
+
+ // This is the current symbol table of the code we are processing, and therefore changes during
+ // compilation
+ std::shared_ptr<SymbolTable> fSymbolTable;
+
+ std::string fErrorText;
+
+ static OverrideFlag sOptimizer;
+ static OverrideFlag sInliner;
+
+ friend class ThreadContext;
+ friend class dsl::DSLCore;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/SkSLConstantFolder.cpp b/gfx/skia/skia/src/sksl/SkSLConstantFolder.cpp
new file mode 100644
index 0000000000..76cf7f820a
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLConstantFolder.cpp
@@ -0,0 +1,884 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/SkSLConstantFolder.h"
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLModifiers.h"
+#include "include/private/base/SkFloatingPoint.h"
+#include "include/private/base/SkTArray.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/ir/SkSLBinaryExpression.h"
+#include "src/sksl/ir/SkSLConstructorCompound.h"
+#include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h"
+#include "src/sksl/ir/SkSLConstructorSplat.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLLiteral.h"
+#include "src/sksl/ir/SkSLPrefixExpression.h"
+#include "src/sksl/ir/SkSLType.h"
+#include "src/sksl/ir/SkSLVariable.h"
+#include "src/sksl/ir/SkSLVariableReference.h"
+
+#include <cstdint>
+#include <float.h>
+#include <limits>
+#include <optional>
+#include <string>
+#include <utility>
+
+namespace SkSL {
+
+static bool is_vec_or_mat(const Type& type) {
+ switch (type.typeKind()) {
+ case Type::TypeKind::kMatrix:
+ case Type::TypeKind::kVector:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+static std::unique_ptr<Expression> eliminate_no_op_boolean(Position pos,
+ const Expression& left,
+ Operator op,
+ const Expression& right) {
+ bool rightVal = right.as<Literal>().boolValue();
+
+ // Detect no-op Boolean expressions and optimize them away.
+ if ((op.kind() == Operator::Kind::LOGICALAND && rightVal) || // (expr && true) -> (expr)
+ (op.kind() == Operator::Kind::LOGICALOR && !rightVal) || // (expr || false) -> (expr)
+ (op.kind() == Operator::Kind::LOGICALXOR && !rightVal) || // (expr ^^ false) -> (expr)
+ (op.kind() == Operator::Kind::EQEQ && rightVal) || // (expr == true) -> (expr)
+ (op.kind() == Operator::Kind::NEQ && !rightVal)) { // (expr != false) -> (expr)
+
+ return left.clone(pos);
+ }
+
+ return nullptr;
+}
+
+static std::unique_ptr<Expression> short_circuit_boolean(Position pos,
+ const Expression& left,
+ Operator op,
+ const Expression& right) {
+ bool leftVal = left.as<Literal>().boolValue();
+
+ // When the literal is on the left, we can sometimes eliminate the other expression entirely.
+ if ((op.kind() == Operator::Kind::LOGICALAND && !leftVal) || // (false && expr) -> (false)
+ (op.kind() == Operator::Kind::LOGICALOR && leftVal)) { // (true || expr) -> (true)
+
+ return left.clone(pos);
+ }
+
+ // We can't eliminate the right-side expression via short-circuit, but we might still be able to
+ // simplify away a no-op expression.
+ return eliminate_no_op_boolean(pos, right, op, left);
+}
+
+static std::unique_ptr<Expression> simplify_constant_equality(const Context& context,
+ Position pos,
+ const Expression& left,
+ Operator op,
+ const Expression& right) {
+ if (op.kind() == Operator::Kind::EQEQ || op.kind() == Operator::Kind::NEQ) {
+ bool equality = (op.kind() == Operator::Kind::EQEQ);
+
+ switch (left.compareConstant(right)) {
+ case Expression::ComparisonResult::kNotEqual:
+ equality = !equality;
+ [[fallthrough]];
+
+ case Expression::ComparisonResult::kEqual:
+ return Literal::MakeBool(context, pos, equality);
+
+ case Expression::ComparisonResult::kUnknown:
+ break;
+ }
+ }
+ return nullptr;
+}
+
+static std::unique_ptr<Expression> simplify_matrix_multiplication(const Context& context,
+ Position pos,
+ const Expression& left,
+ const Expression& right,
+ int leftColumns,
+ int leftRows,
+ int rightColumns,
+ int rightRows) {
+ const Type& componentType = left.type().componentType();
+ SkASSERT(componentType.matches(right.type().componentType()));
+
+ // Fetch the left matrix.
+ double leftVals[4][4];
+ for (int c = 0; c < leftColumns; ++c) {
+ for (int r = 0; r < leftRows; ++r) {
+ leftVals[c][r] = *left.getConstantValue((c * leftRows) + r);
+ }
+ }
+ // Fetch the right matrix.
+ double rightVals[4][4];
+ for (int c = 0; c < rightColumns; ++c) {
+ for (int r = 0; r < rightRows; ++r) {
+ rightVals[c][r] = *right.getConstantValue((c * rightRows) + r);
+ }
+ }
+
+ SkASSERT(leftColumns == rightRows);
+ int outColumns = rightColumns,
+ outRows = leftRows;
+
+ ExpressionArray args;
+ args.reserve_back(outColumns * outRows);
+ for (int c = 0; c < outColumns; ++c) {
+ for (int r = 0; r < outRows; ++r) {
+ // Compute a dot product for this position.
+ double val = 0;
+ for (int dotIdx = 0; dotIdx < leftColumns; ++dotIdx) {
+ val += leftVals[dotIdx][r] * rightVals[c][dotIdx];
+ }
+ args.push_back(Literal::Make(pos, val, &componentType));
+ }
+ }
+
+ if (outColumns == 1) {
+ // Matrix-times-vector conceptually makes a 1-column N-row matrix, but we return vecN.
+ std::swap(outColumns, outRows);
+ }
+
+ const Type& resultType = componentType.toCompound(context, outColumns, outRows);
+ return ConstructorCompound::Make(context, pos, resultType, std::move(args));
+}
+
+static std::unique_ptr<Expression> simplify_matrix_times_matrix(const Context& context,
+ Position pos,
+ const Expression& left,
+ const Expression& right) {
+ const Type& leftType = left.type();
+ const Type& rightType = right.type();
+
+ SkASSERT(leftType.isMatrix());
+ SkASSERT(rightType.isMatrix());
+
+ return simplify_matrix_multiplication(context, pos, left, right,
+ leftType.columns(), leftType.rows(),
+ rightType.columns(), rightType.rows());
+}
+
+static std::unique_ptr<Expression> simplify_vector_times_matrix(const Context& context,
+ Position pos,
+ const Expression& left,
+ const Expression& right) {
+ const Type& leftType = left.type();
+ const Type& rightType = right.type();
+
+ SkASSERT(leftType.isVector());
+ SkASSERT(rightType.isMatrix());
+
+ return simplify_matrix_multiplication(context, pos, left, right,
+ /*leftColumns=*/leftType.columns(), /*leftRows=*/1,
+ rightType.columns(), rightType.rows());
+}
+
+static std::unique_ptr<Expression> simplify_matrix_times_vector(const Context& context,
+ Position pos,
+ const Expression& left,
+ const Expression& right) {
+ const Type& leftType = left.type();
+ const Type& rightType = right.type();
+
+ SkASSERT(leftType.isMatrix());
+ SkASSERT(rightType.isVector());
+
+ return simplify_matrix_multiplication(context, pos, left, right,
+ leftType.columns(), leftType.rows(),
+ /*rightColumns=*/1, /*rightRows=*/rightType.columns());
+}
+
+static std::unique_ptr<Expression> simplify_componentwise(const Context& context,
+ Position pos,
+ const Expression& left,
+ Operator op,
+ const Expression& right) {
+ SkASSERT(is_vec_or_mat(left.type()));
+ SkASSERT(left.type().matches(right.type()));
+ const Type& type = left.type();
+
+ // Handle equality operations: == !=
+ if (std::unique_ptr<Expression> result = simplify_constant_equality(context, pos, left, op,
+ right)) {
+ return result;
+ }
+
+ // Handle floating-point arithmetic: + - * /
+ using FoldFn = double (*)(double, double);
+ FoldFn foldFn;
+ switch (op.kind()) {
+ case Operator::Kind::PLUS: foldFn = +[](double a, double b) { return a + b; }; break;
+ case Operator::Kind::MINUS: foldFn = +[](double a, double b) { return a - b; }; break;
+ case Operator::Kind::STAR: foldFn = +[](double a, double b) { return a * b; }; break;
+ case Operator::Kind::SLASH: foldFn = +[](double a, double b) { return a / b; }; break;
+ default:
+ return nullptr;
+ }
+
+ const Type& componentType = type.componentType();
+ SkASSERT(componentType.isNumber());
+
+ double minimumValue = componentType.minimumValue();
+ double maximumValue = componentType.maximumValue();
+
+ ExpressionArray args;
+ int numSlots = type.slotCount();
+ args.reserve_back(numSlots);
+ for (int i = 0; i < numSlots; i++) {
+ double value = foldFn(*left.getConstantValue(i), *right.getConstantValue(i));
+ if (value < minimumValue || value > maximumValue) {
+ return nullptr;
+ }
+
+ args.push_back(Literal::Make(pos, value, &componentType));
+ }
+ return ConstructorCompound::Make(context, pos, type, std::move(args));
+}
+
+static std::unique_ptr<Expression> splat_scalar(const Context& context,
+ const Expression& scalar,
+ const Type& type) {
+ if (type.isVector()) {
+ return ConstructorSplat::Make(context, scalar.fPosition, type, scalar.clone());
+ }
+ if (type.isMatrix()) {
+ int numSlots = type.slotCount();
+ ExpressionArray splatMatrix;
+ splatMatrix.reserve_back(numSlots);
+ for (int index = 0; index < numSlots; ++index) {
+ splatMatrix.push_back(scalar.clone());
+ }
+ return ConstructorCompound::Make(context, scalar.fPosition, type, std::move(splatMatrix));
+ }
+ SkDEBUGFAILF("unsupported type %s", type.description().c_str());
+ return nullptr;
+}
+
+static std::unique_ptr<Expression> cast_expression(const Context& context,
+ Position pos,
+ const Expression& expr,
+ const Type& type) {
+ SkASSERT(type.componentType().matches(expr.type().componentType()));
+ if (expr.type().isScalar()) {
+ if (type.isMatrix()) {
+ return ConstructorDiagonalMatrix::Make(context, pos, type, expr.clone());
+ }
+ if (type.isVector()) {
+ return ConstructorSplat::Make(context, pos, type, expr.clone());
+ }
+ }
+ if (type.matches(expr.type())) {
+ return expr.clone(pos);
+ }
+ // We can't cast matrices into vectors or vice-versa.
+ return nullptr;
+}
+
+static std::unique_ptr<Expression> zero_expression(const Context& context,
+ Position pos,
+ const Type& type) {
+ std::unique_ptr<Expression> zero = Literal::Make(pos, 0.0, &type.componentType());
+ if (type.isScalar()) {
+ return zero;
+ }
+ if (type.isVector()) {
+ return ConstructorSplat::Make(context, pos, type, std::move(zero));
+ }
+ if (type.isMatrix()) {
+ return ConstructorDiagonalMatrix::Make(context, pos, type, std::move(zero));
+ }
+ SkDEBUGFAILF("unsupported type %s", type.description().c_str());
+ return nullptr;
+}
+
+static std::unique_ptr<Expression> negate_expression(const Context& context,
+ Position pos,
+ const Expression& expr,
+ const Type& type) {
+ std::unique_ptr<Expression> ctor = cast_expression(context, pos, expr, type);
+ return ctor ? PrefixExpression::Make(context, pos, Operator::Kind::MINUS, std::move(ctor))
+ : nullptr;
+}
+
+bool ConstantFolder::GetConstantInt(const Expression& value, SKSL_INT* out) {
+ const Expression* expr = GetConstantValueForVariable(value);
+ if (!expr->isIntLiteral()) {
+ return false;
+ }
+ *out = expr->as<Literal>().intValue();
+ return true;
+}
+
+bool ConstantFolder::GetConstantValue(const Expression& value, double* out) {
+ const Expression* expr = GetConstantValueForVariable(value);
+ if (!expr->is<Literal>()) {
+ return false;
+ }
+ *out = expr->as<Literal>().value();
+ return true;
+}
+
+static bool contains_constant_zero(const Expression& expr) {
+ int numSlots = expr.type().slotCount();
+ for (int index = 0; index < numSlots; ++index) {
+ std::optional<double> slotVal = expr.getConstantValue(index);
+ if (slotVal.has_value() && *slotVal == 0.0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Returns true if the expression contains `value` in every slot.
+static bool is_constant_splat(const Expression& expr, double value) {
+ int numSlots = expr.type().slotCount();
+ for (int index = 0; index < numSlots; ++index) {
+ std::optional<double> slotVal = expr.getConstantValue(index);
+ if (!slotVal.has_value() || *slotVal != value) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// Returns true if the expression is a square diagonal matrix containing `value`.
+static bool is_constant_diagonal(const Expression& expr, double value) {
+ SkASSERT(expr.type().isMatrix());
+ int columns = expr.type().columns();
+ int rows = expr.type().rows();
+ if (columns != rows) {
+ return false;
+ }
+ int slotIdx = 0;
+ for (int c = 0; c < columns; ++c) {
+ for (int r = 0; r < rows; ++r) {
+ double expectation = (c == r) ? value : 0;
+ std::optional<double> slotVal = expr.getConstantValue(slotIdx++);
+ if (!slotVal.has_value() || *slotVal != expectation) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+// Returns true if the expression is a scalar, vector, or diagonal matrix containing `value`.
+static bool is_constant_value(const Expression& expr, double value) {
+ return expr.type().isMatrix() ? is_constant_diagonal(expr, value)
+ : is_constant_splat(expr, value);
+}
+
+// The expression represents the right-hand side of a division op. If the division can be
+// strength-reduced into multiplication by a reciprocal, returns that reciprocal as an expression.
+// Note that this only supports literal values with safe-to-use reciprocals, and returns null if
+// Expression contains anything else.
+static std::unique_ptr<Expression> make_reciprocal_expression(const Context& context,
+ const Expression& right) {
+ if (right.type().isMatrix() || !right.type().componentType().isFloat()) {
+ return nullptr;
+ }
+ // Verify that each slot contains a finite, non-zero literal, take its reciprocal.
+ int nslots = right.type().slotCount();
+ SkSTArray<4, double> values;
+ for (int index = 0; index < nslots; ++index) {
+ std::optional<double> value = right.getConstantValue(index);
+ if (!value) {
+ return nullptr;
+ }
+ *value = sk_ieee_double_divide(1.0, *value);
+ if (*value >= -FLT_MAX && *value <= FLT_MAX && *value != 0.0) {
+ // The reciprocal can be represented safely as a finite 32-bit float.
+ values.push_back(*value);
+ } else {
+ // The value is outside the 32-bit float range, or is NaN; do not optimize.
+ return nullptr;
+ }
+ }
+ // Convert our reciprocal values to Literals.
+ ExpressionArray exprs;
+ exprs.reserve_back(nslots);
+ for (double value : values) {
+ exprs.push_back(Literal::Make(right.fPosition, value, &right.type().componentType()));
+ }
+ // Turn the expression array into a compound constructor. (If this is a single-slot expression,
+ // this will return the literal as-is.)
+ return ConstructorCompound::Make(context, right.fPosition, right.type(), std::move(exprs));
+}
+
+static bool error_on_divide_by_zero(const Context& context, Position pos, Operator op,
+ const Expression& right) {
+ switch (op.kind()) {
+ case Operator::Kind::SLASH:
+ case Operator::Kind::SLASHEQ:
+ case Operator::Kind::PERCENT:
+ case Operator::Kind::PERCENTEQ:
+ if (contains_constant_zero(right)) {
+ context.fErrors->error(pos, "division by zero");
+ return true;
+ }
+ return false;
+ default:
+ return false;
+ }
+}
+
+const Expression* ConstantFolder::GetConstantValueOrNullForVariable(const Expression& inExpr) {
+ for (const Expression* expr = &inExpr;;) {
+ if (!expr->is<VariableReference>()) {
+ break;
+ }
+ const VariableReference& varRef = expr->as<VariableReference>();
+ if (varRef.refKind() != VariableRefKind::kRead) {
+ break;
+ }
+ const Variable& var = *varRef.variable();
+ if (!(var.modifiers().fFlags & Modifiers::kConst_Flag)) {
+ break;
+ }
+ expr = var.initialValue();
+ if (!expr) {
+ // Function parameters can be const but won't have an initial value.
+ break;
+ }
+ if (Analysis::IsCompileTimeConstant(*expr)) {
+ return expr;
+ }
+ }
+ // We didn't find a compile-time constant at the end.
+ return nullptr;
+}
+
+const Expression* ConstantFolder::GetConstantValueForVariable(const Expression& inExpr) {
+ const Expression* expr = GetConstantValueOrNullForVariable(inExpr);
+ return expr ? expr : &inExpr;
+}
+
+std::unique_ptr<Expression> ConstantFolder::MakeConstantValueForVariable(
+ Position pos, std::unique_ptr<Expression> inExpr) {
+ const Expression* expr = GetConstantValueOrNullForVariable(*inExpr);
+ return expr ? expr->clone(pos) : std::move(inExpr);
+}
+
+static bool is_scalar_op_matrix(const Expression& left, const Expression& right) {
+ return left.type().isScalar() && right.type().isMatrix();
+}
+
+static bool is_matrix_op_scalar(const Expression& left, const Expression& right) {
+ return is_scalar_op_matrix(right, left);
+}
+
+static std::unique_ptr<Expression> simplify_arithmetic(const Context& context,
+ Position pos,
+ const Expression& left,
+ Operator op,
+ const Expression& right,
+ const Type& resultType) {
+ switch (op.kind()) {
+ case Operator::Kind::PLUS:
+ if (!is_scalar_op_matrix(left, right) && is_constant_splat(right, 0.0)) { // x + 0
+ if (std::unique_ptr<Expression> expr = cast_expression(context, pos, left,
+ resultType)) {
+ return expr;
+ }
+ }
+ if (!is_matrix_op_scalar(left, right) && is_constant_splat(left, 0.0)) { // 0 + x
+ if (std::unique_ptr<Expression> expr = cast_expression(context, pos, right,
+ resultType)) {
+ return expr;
+ }
+ }
+ break;
+
+ case Operator::Kind::STAR:
+ if (is_constant_value(right, 1.0)) { // x * 1
+ if (std::unique_ptr<Expression> expr = cast_expression(context, pos, left,
+ resultType)) {
+ return expr;
+ }
+ }
+ if (is_constant_value(left, 1.0)) { // 1 * x
+ if (std::unique_ptr<Expression> expr = cast_expression(context, pos, right,
+ resultType)) {
+ return expr;
+ }
+ }
+ if (is_constant_value(right, 0.0) && !Analysis::HasSideEffects(left)) { // x * 0
+ return zero_expression(context, pos, resultType);
+ }
+ if (is_constant_value(left, 0.0) && !Analysis::HasSideEffects(right)) { // 0 * x
+ return zero_expression(context, pos, resultType);
+ }
+ if (is_constant_value(right, -1.0)) { // x * -1 (to `-x`)
+ if (std::unique_ptr<Expression> expr = negate_expression(context, pos, left,
+ resultType)) {
+ return expr;
+ }
+ }
+ if (is_constant_value(left, -1.0)) { // -1 * x (to `-x`)
+ if (std::unique_ptr<Expression> expr = negate_expression(context, pos, right,
+ resultType)) {
+ return expr;
+ }
+ }
+ break;
+
+ case Operator::Kind::MINUS:
+ if (!is_scalar_op_matrix(left, right) && is_constant_splat(right, 0.0)) { // x - 0
+ if (std::unique_ptr<Expression> expr = cast_expression(context, pos, left,
+ resultType)) {
+ return expr;
+ }
+ }
+ if (!is_matrix_op_scalar(left, right) && is_constant_splat(left, 0.0)) { // 0 - x
+ if (std::unique_ptr<Expression> expr = negate_expression(context, pos, right,
+ resultType)) {
+ return expr;
+ }
+ }
+ break;
+
+ case Operator::Kind::SLASH:
+ if (!is_scalar_op_matrix(left, right) && is_constant_splat(right, 1.0)) { // x / 1
+ if (std::unique_ptr<Expression> expr = cast_expression(context, pos, left,
+ resultType)) {
+ return expr;
+ }
+ }
+ if (!left.type().isMatrix()) { // convert `x / 2` into `x * 0.5`
+ if (std::unique_ptr<Expression> expr = make_reciprocal_expression(context, right)) {
+ return BinaryExpression::Make(context, pos, left.clone(), Operator::Kind::STAR,
+ std::move(expr));
+ }
+ }
+ break;
+
+ case Operator::Kind::PLUSEQ:
+ case Operator::Kind::MINUSEQ:
+ if (is_constant_splat(right, 0.0)) { // x += 0, x -= 0
+ if (std::unique_ptr<Expression> var = cast_expression(context, pos, left,
+ resultType)) {
+ Analysis::UpdateVariableRefKind(var.get(), VariableRefKind::kRead);
+ return var;
+ }
+ }
+ break;
+
+ case Operator::Kind::STAREQ:
+ if (is_constant_value(right, 1.0)) { // x *= 1
+ if (std::unique_ptr<Expression> var = cast_expression(context, pos, left,
+ resultType)) {
+ Analysis::UpdateVariableRefKind(var.get(), VariableRefKind::kRead);
+ return var;
+ }
+ }
+ break;
+
+ case Operator::Kind::SLASHEQ:
+ if (is_constant_splat(right, 1.0)) { // x /= 1
+ if (std::unique_ptr<Expression> var = cast_expression(context, pos, left,
+ resultType)) {
+ Analysis::UpdateVariableRefKind(var.get(), VariableRefKind::kRead);
+ return var;
+ }
+ }
+ if (std::unique_ptr<Expression> expr = make_reciprocal_expression(context, right)) {
+ return BinaryExpression::Make(context, pos, left.clone(), Operator::Kind::STAREQ,
+ std::move(expr));
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return nullptr;
+}
+
+// The expression must be scalar, and represents the right-hand side of a division op. It can
+// contain anything, not just literal values. This returns the binary expression `1.0 / expr`. The
+// expression might be further simplified by the constant folding, if possible.
+static std::unique_ptr<Expression> one_over_scalar(const Context& context,
+ const Expression& right) {
+ SkASSERT(right.type().isScalar());
+ Position pos = right.fPosition;
+ return BinaryExpression::Make(context, pos,
+ Literal::Make(pos, 1.0, &right.type()),
+ Operator::Kind::SLASH,
+ right.clone());
+}
+
+static std::unique_ptr<Expression> simplify_matrix_division(const Context& context,
+ Position pos,
+ const Expression& left,
+ Operator op,
+ const Expression& right,
+ const Type& resultType) {
+ // Convert matrix-over-scalar `x /= y` into `x *= (1.0 / y)`. This generates better
+ // code in SPIR-V and Metal, and should be roughly equivalent elsewhere.
+ switch (op.kind()) {
+ case OperatorKind::SLASH:
+ case OperatorKind::SLASHEQ:
+ if (left.type().isMatrix() && right.type().isScalar()) {
+ Operator multiplyOp = op.isAssignment() ? OperatorKind::STAREQ
+ : OperatorKind::STAR;
+ return BinaryExpression::Make(context, pos,
+ left.clone(),
+ multiplyOp,
+ one_over_scalar(context, right));
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return nullptr;
+}
+
+static std::unique_ptr<Expression> fold_expression(Position pos,
+ double result,
+ const Type* resultType) {
+ if (resultType->isNumber()) {
+ if (result >= resultType->minimumValue() && result <= resultType->maximumValue()) {
+ // This result will fit inside its type.
+ } else {
+ // The value is outside the range or is NaN (all if-checks fail); do not optimize.
+ return nullptr;
+ }
+ }
+
+ return Literal::Make(pos, result, resultType);
+}
+
+std::unique_ptr<Expression> ConstantFolder::Simplify(const Context& context,
+ Position pos,
+ const Expression& leftExpr,
+ Operator op,
+ const Expression& rightExpr,
+ const Type& resultType) {
+ // Replace constant variables with their literal values.
+ const Expression* left = GetConstantValueForVariable(leftExpr);
+ const Expression* right = GetConstantValueForVariable(rightExpr);
+
+ // If this is the assignment operator, and both sides are the same trivial expression, this is
+ // self-assignment (i.e., `var = var`) and can be reduced to just a variable reference (`var`).
+ // This can happen when other parts of the assignment are optimized away.
+ if (op.kind() == Operator::Kind::EQ && Analysis::IsSameExpressionTree(*left, *right)) {
+ return right->clone(pos);
+ }
+
+ // Simplify the expression when both sides are constant Boolean literals.
+ if (left->isBoolLiteral() && right->isBoolLiteral()) {
+ bool leftVal = left->as<Literal>().boolValue();
+ bool rightVal = right->as<Literal>().boolValue();
+ bool result;
+ switch (op.kind()) {
+ case Operator::Kind::LOGICALAND: result = leftVal && rightVal; break;
+ case Operator::Kind::LOGICALOR: result = leftVal || rightVal; break;
+ case Operator::Kind::LOGICALXOR: result = leftVal ^ rightVal; break;
+ case Operator::Kind::EQEQ: result = leftVal == rightVal; break;
+ case Operator::Kind::NEQ: result = leftVal != rightVal; break;
+ default: return nullptr;
+ }
+ return Literal::MakeBool(context, pos, result);
+ }
+
+ // If the left side is a Boolean literal, apply short-circuit optimizations.
+ if (left->isBoolLiteral()) {
+ return short_circuit_boolean(pos, *left, op, *right);
+ }
+
+ // If the right side is a Boolean literal...
+ if (right->isBoolLiteral()) {
+ // ... and the left side has no side effects...
+ if (!Analysis::HasSideEffects(*left)) {
+ // We can reverse the expressions and short-circuit optimizations are still valid.
+ return short_circuit_boolean(pos, *right, op, *left);
+ }
+
+ // We can't use short-circuiting, but we can still optimize away no-op Boolean expressions.
+ return eliminate_no_op_boolean(pos, *left, op, *right);
+ }
+
+ if (op.kind() == Operator::Kind::EQEQ && Analysis::IsSameExpressionTree(*left, *right)) {
+ // With == comparison, if both sides are the same trivial expression, this is self-
+ // comparison and is always true. (We are not concerned with NaN.)
+ return Literal::MakeBool(context, pos, /*value=*/true);
+ }
+
+ if (op.kind() == Operator::Kind::NEQ && Analysis::IsSameExpressionTree(*left, *right)) {
+ // With != comparison, if both sides are the same trivial expression, this is self-
+ // comparison and is always false. (We are not concerned with NaN.)
+ return Literal::MakeBool(context, pos, /*value=*/false);
+ }
+
+ if (error_on_divide_by_zero(context, pos, op, *right)) {
+ return nullptr;
+ }
+
+ // Perform full constant folding when both sides are compile-time constants.
+ const Type& leftType = left->type();
+ const Type& rightType = right->type();
+ bool leftSideIsConstant = Analysis::IsCompileTimeConstant(*left);
+ bool rightSideIsConstant = Analysis::IsCompileTimeConstant(*right);
+
+ if (leftSideIsConstant && rightSideIsConstant) {
+ // Handle pairs of integer literals.
+ if (left->isIntLiteral() && right->isIntLiteral()) {
+ using SKSL_UINT = uint64_t;
+ SKSL_INT leftVal = left->as<Literal>().intValue();
+ SKSL_INT rightVal = right->as<Literal>().intValue();
+
+ // Note that fold_expression returns null if the result would overflow its type.
+ #define RESULT(Op) fold_expression(pos, (SKSL_INT)(leftVal) Op \
+ (SKSL_INT)(rightVal), &resultType)
+ #define URESULT(Op) fold_expression(pos, (SKSL_INT)((SKSL_UINT)(leftVal) Op \
+ (SKSL_UINT)(rightVal)), &resultType)
+ switch (op.kind()) {
+ case Operator::Kind::PLUS: return URESULT(+);
+ case Operator::Kind::MINUS: return URESULT(-);
+ case Operator::Kind::STAR: return URESULT(*);
+ case Operator::Kind::SLASH:
+ if (leftVal == std::numeric_limits<SKSL_INT>::min() && rightVal == -1) {
+ context.fErrors->error(pos, "arithmetic overflow");
+ return nullptr;
+ }
+ return RESULT(/);
+ case Operator::Kind::PERCENT:
+ if (leftVal == std::numeric_limits<SKSL_INT>::min() && rightVal == -1) {
+ context.fErrors->error(pos, "arithmetic overflow");
+ return nullptr;
+ }
+ return RESULT(%);
+ case Operator::Kind::BITWISEAND: return RESULT(&);
+ case Operator::Kind::BITWISEOR: return RESULT(|);
+ case Operator::Kind::BITWISEXOR: return RESULT(^);
+ case Operator::Kind::EQEQ: return RESULT(==);
+ case Operator::Kind::NEQ: return RESULT(!=);
+ case Operator::Kind::GT: return RESULT(>);
+ case Operator::Kind::GTEQ: return RESULT(>=);
+ case Operator::Kind::LT: return RESULT(<);
+ case Operator::Kind::LTEQ: return RESULT(<=);
+ case Operator::Kind::SHL:
+ if (rightVal >= 0 && rightVal <= 31) {
+ // Left-shifting a negative (or really, any signed) value is undefined
+ // behavior in C++, but not in GLSL. Do the shift on unsigned values to avoid
+ // triggering an UBSAN error.
+ return URESULT(<<);
+ }
+ context.fErrors->error(pos, "shift value out of range");
+ return nullptr;
+ case Operator::Kind::SHR:
+ if (rightVal >= 0 && rightVal <= 31) {
+ return RESULT(>>);
+ }
+ context.fErrors->error(pos, "shift value out of range");
+ return nullptr;
+
+ default:
+ return nullptr;
+ }
+ #undef RESULT
+ #undef URESULT
+ }
+
+ // Handle pairs of floating-point literals.
+ if (left->isFloatLiteral() && right->isFloatLiteral()) {
+ SKSL_FLOAT leftVal = left->as<Literal>().floatValue();
+ SKSL_FLOAT rightVal = right->as<Literal>().floatValue();
+
+ #define RESULT(Op) fold_expression(pos, leftVal Op rightVal, &resultType)
+ switch (op.kind()) {
+ case Operator::Kind::PLUS: return RESULT(+);
+ case Operator::Kind::MINUS: return RESULT(-);
+ case Operator::Kind::STAR: return RESULT(*);
+ case Operator::Kind::SLASH: return RESULT(/);
+ case Operator::Kind::EQEQ: return RESULT(==);
+ case Operator::Kind::NEQ: return RESULT(!=);
+ case Operator::Kind::GT: return RESULT(>);
+ case Operator::Kind::GTEQ: return RESULT(>=);
+ case Operator::Kind::LT: return RESULT(<);
+ case Operator::Kind::LTEQ: return RESULT(<=);
+ default: return nullptr;
+ }
+ #undef RESULT
+ }
+
+ // Perform matrix multiplication.
+ if (op.kind() == Operator::Kind::STAR) {
+ if (leftType.isMatrix() && rightType.isMatrix()) {
+ return simplify_matrix_times_matrix(context, pos, *left, *right);
+ }
+ if (leftType.isVector() && rightType.isMatrix()) {
+ return simplify_vector_times_matrix(context, pos, *left, *right);
+ }
+ if (leftType.isMatrix() && rightType.isVector()) {
+ return simplify_matrix_times_vector(context, pos, *left, *right);
+ }
+ }
+
+ // Perform constant folding on pairs of vectors/matrices.
+ if (is_vec_or_mat(leftType) && leftType.matches(rightType)) {
+ return simplify_componentwise(context, pos, *left, op, *right);
+ }
+
+ // Perform constant folding on vectors/matrices against scalars, e.g.: half4(2) + 2
+ if (rightType.isScalar() && is_vec_or_mat(leftType) &&
+ leftType.componentType().matches(rightType)) {
+ return simplify_componentwise(context, pos,
+ *left, op, *splat_scalar(context, *right, left->type()));
+ }
+
+ // Perform constant folding on scalars against vectors/matrices, e.g.: 2 + half4(2)
+ if (leftType.isScalar() && is_vec_or_mat(rightType) &&
+ rightType.componentType().matches(leftType)) {
+ return simplify_componentwise(context, pos,
+ *splat_scalar(context, *left, right->type()), op, *right);
+ }
+
+ // Perform constant folding on pairs of matrices, arrays or structs.
+ if ((leftType.isMatrix() && rightType.isMatrix()) ||
+ (leftType.isArray() && rightType.isArray()) ||
+ (leftType.isStruct() && rightType.isStruct())) {
+ return simplify_constant_equality(context, pos, *left, op, *right);
+ }
+ }
+
+ if (context.fConfig->fSettings.fOptimize) {
+ // If just one side is constant, we might still be able to simplify arithmetic expressions
+ // like `x * 1`, `x *= 1`, `x + 0`, `x * 0`, `0 / x`, etc.
+ if (leftSideIsConstant || rightSideIsConstant) {
+ if (std::unique_ptr<Expression> expr = simplify_arithmetic(context, pos, *left, op,
+ *right, resultType)) {
+ return expr;
+ }
+ }
+
+ // We can simplify some forms of matrix division even when neither side is constant.
+ if (std::unique_ptr<Expression> expr = simplify_matrix_division(context, pos, *left, op,
+ *right, resultType)) {
+ return expr;
+ }
+ }
+
+ // We aren't able to constant-fold.
+ return nullptr;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/SkSLConstantFolder.h b/gfx/skia/skia/src/sksl/SkSLConstantFolder.h
new file mode 100644
index 0000000000..25dd2e7b86
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLConstantFolder.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_CONSTANT_FOLDER
+#define SKSL_CONSTANT_FOLDER
+
+#include <memory>
+
+#include "include/private/SkSLDefines.h"
+#include "include/sksl/SkSLOperator.h"
+
+namespace SkSL {
+
+class Context;
+class Expression;
+class Position;
+class Type;
+
+/**
+ * Performs constant folding on IR expressions. This simplifies expressions containing
+ * compile-time constants, such as replacing `Literal(2) + Literal(2)` with `Literal(4)`.
+ */
+class ConstantFolder {
+public:
+ /**
+ * If value is an int literal or const int variable with a known value, returns true and stores
+ * the value in out. Otherwise returns false.
+ */
+ static bool GetConstantInt(const Expression& value, SKSL_INT* out);
+
+ /**
+ * If value is a literal or const scalar variable with a known value, returns true and stores
+ * the value in out. Otherwise returns false.
+ */
+ static bool GetConstantValue(const Expression& value, double* out);
+
+ /**
+ * If the expression is a const variable with a known compile-time-constant value, returns that
+ * value. If not, returns the original expression as-is.
+ */
+ static const Expression* GetConstantValueForVariable(const Expression& value);
+
+ /**
+ * If the expression is a const variable with a known compile-time-constant value, returns that
+ * value. If not, returns null.
+ */
+ static const Expression* GetConstantValueOrNullForVariable(const Expression& value);
+
+ /**
+ * If the expression is a const variable with a known compile-time-constant value, returns a
+ * clone of that value. If not, returns the original expression as-is.
+ */
+ static std::unique_ptr<Expression> MakeConstantValueForVariable(Position pos,
+ std::unique_ptr<Expression> expr);
+
+ /** Simplifies the binary expression `left OP right`. Returns null if it can't be simplified. */
+ static std::unique_ptr<Expression> Simplify(const Context& context,
+ Position pos,
+ const Expression& left,
+ Operator op,
+ const Expression& right,
+ const Type& resultType);
+};
+
+} // namespace SkSL
+
+#endif // SKSL_CONSTANT_FOLDER
diff --git a/gfx/skia/skia/src/sksl/SkSLContext.cpp b/gfx/skia/skia/src/sksl/SkSLContext.cpp
new file mode 100644
index 0000000000..d28fc9d727
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLContext.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/SkSLContext.h"
+
+#include "include/core/SkTypes.h"
+#ifdef SK_DEBUG
+#include "src/sksl/SkSLPool.h"
+#endif
+
+namespace SkSL {
+
+Context::Context(const BuiltinTypes& types, const ShaderCaps* caps, ErrorReporter& errors)
+ : fTypes(types)
+ , fCaps(caps)
+ , fErrors(&errors) {
+ SkASSERT(!Pool::IsAttached());
+}
+
+Context::~Context() {
+ SkASSERT(!Pool::IsAttached());
+}
+
+} // namespace SkSL
+
diff --git a/gfx/skia/skia/src/sksl/SkSLContext.h b/gfx/skia/skia/src/sksl/SkSLContext.h
new file mode 100644
index 0000000000..e83f2f36ec
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLContext.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_CONTEXT
+#define SKSL_CONTEXT
+
+namespace SkSL {
+
+class BuiltinTypes;
+class ErrorReporter;
+class ModifiersPool;
+struct Module;
+struct ProgramConfig;
+struct ShaderCaps;
+
+/**
+ * Contains compiler-wide objects, which currently means the core types.
+ */
+class Context {
+public:
+ Context(const BuiltinTypes& types, const ShaderCaps* caps, ErrorReporter& errors);
+ ~Context();
+
+ // The Context holds a reference to all of the built-in types.
+ const BuiltinTypes& fTypes;
+
+ // The Context holds a reference to our shader caps bits.
+ const ShaderCaps* fCaps;
+
+ // The Context holds a pointer to our pool of modifiers.
+ ModifiersPool* fModifiersPool = nullptr;
+
+ // The Context holds a pointer to the configuration of the program being compiled.
+ ProgramConfig* fConfig = nullptr;
+
+ // The Context holds a pointer to our error reporter.
+ ErrorReporter* fErrors;
+
+ // The Context holds a pointer to our module with built-in declarations.
+ const Module* fModule = nullptr;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/SkSLErrorReporter.cpp b/gfx/skia/skia/src/sksl/SkSLErrorReporter.cpp
new file mode 100644
index 0000000000..a11234ff5e
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLErrorReporter.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2021 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/sksl/SkSLErrorReporter.h"
+
+#include "include/sksl/SkSLPosition.h"
+#include "src/base/SkStringView.h"
+#include "src/sksl/SkSLCompiler.h"
+
+namespace SkSL {
+
+void ErrorReporter::error(Position position, std::string_view msg) {
+ if (skstd::contains(msg, Compiler::POISON_TAG)) {
+ // Don't report errors on poison values.
+ return;
+ }
+ ++fErrorCount;
+ this->handleError(msg, position);
+}
+
+void TestingOnly_AbortErrorReporter::handleError(std::string_view msg, Position pos) {
+ SK_ABORT("%.*s", (int)msg.length(), msg.data());
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/SkSLFileOutputStream.h b/gfx/skia/skia/src/sksl/SkSLFileOutputStream.h
new file mode 100644
index 0000000000..26f59edefc
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLFileOutputStream.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_FILEOUTPUTSTREAM
+#define SKSL_FILEOUTPUTSTREAM
+
+#include "src/sksl/SkSLOutputStream.h"
+#include "src/sksl/SkSLUtil.h"
+#include <stdio.h>
+
+namespace SkSL {
+
+class FileOutputStream : public OutputStream {
+public:
+ FileOutputStream(const char* name) {
+ fFile = fopen(name, "wb");
+ }
+
+ ~FileOutputStream() override {
+ if (fOpen) {
+ close();
+ }
+ }
+
+ bool isValid() const override {
+ return nullptr != fFile;
+ }
+
+ void write8(uint8_t b) override {
+ SkASSERT(fOpen);
+ if (isValid()) {
+ if (EOF == fputc(b, fFile)) {
+ fFile = nullptr;
+ }
+ }
+ }
+
+ void writeText(const char* s) override {
+ SkASSERT(fOpen);
+ if (isValid()) {
+ if (EOF == fputs(s, fFile)) {
+ fFile = nullptr;
+ }
+ }
+ }
+
+ void write(const void* s, size_t size) override {
+ if (isValid()) {
+ size_t written = fwrite(s, 1, size, fFile);
+ if (written != size) {
+ fFile = nullptr;
+ }
+ }
+ }
+
+ bool close() {
+ fOpen = false;
+ if (isValid() && fclose(fFile)) {
+ fFile = nullptr;
+ return false;
+ }
+ return true;
+ }
+
+private:
+ bool fOpen = true;
+ FILE *fFile;
+
+ using INHERITED = OutputStream;
+};
+
+} // namespace
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/SkSLGLSL.h b/gfx/skia/skia/src/sksl/SkSLGLSL.h
new file mode 100644
index 0000000000..55c8bc87e5
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLGLSL.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2021 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkSLGLSL_DEFINED
+#define SkSLGLSL_DEFINED
+
+namespace SkSL {
+
+// Limited set of GLSL versions we build shaders for. Caller should round
+// down the GLSL version to one of these enums.
+enum class GLSLGeneration {
+ /**
+ * Desktop GLSL 1.10 and ES2 shading language (based on desktop GLSL 1.20)
+ */
+ k110,
+ k100es = k110,
+ /**
+ * Desktop GLSL 1.30
+ */
+ k130,
+ /**
+ * Desktop GLSL 1.40
+ */
+ k140,
+ /**
+ * Desktop GLSL 1.50
+ */
+ k150,
+ /**
+ * Desktop GLSL 3.30, and ES GLSL 3.00
+ */
+ k330,
+ k300es = k330,
+ /**
+ * Desktop GLSL 4.00
+ */
+ k400,
+ /**
+ * Desktop GLSL 4.20
+ */
+ k420,
+ /**
+ * ES GLSL 3.10 only TODO Make GLSLCap objects to make this more granular
+ */
+ k310es,
+ /**
+ * ES GLSL 3.20
+ */
+ k320es,
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/SkSLInliner.cpp b/gfx/skia/skia/src/sksl/SkSLInliner.cpp
new file mode 100644
index 0000000000..d90227e3bb
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLInliner.cpp
@@ -0,0 +1,1062 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/SkSLInliner.h"
+
+#ifndef SK_ENABLE_OPTIMIZE_SIZE
+
+#include "include/core/SkSpan.h"
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLModifiers.h"
+#include "include/private/SkSLProgramElement.h"
+#include "include/private/SkSLStatement.h"
+#include "include/private/base/SkTArray.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "include/sksl/SkSLOperator.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/analysis/SkSLProgramUsage.h"
+#include "src/sksl/ir/SkSLBinaryExpression.h"
+#include "src/sksl/ir/SkSLChildCall.h"
+#include "src/sksl/ir/SkSLConstructor.h"
+#include "src/sksl/ir/SkSLConstructorArray.h"
+#include "src/sksl/ir/SkSLConstructorArrayCast.h"
+#include "src/sksl/ir/SkSLConstructorCompound.h"
+#include "src/sksl/ir/SkSLConstructorCompoundCast.h"
+#include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h"
+#include "src/sksl/ir/SkSLConstructorMatrixResize.h"
+#include "src/sksl/ir/SkSLConstructorScalarCast.h"
+#include "src/sksl/ir/SkSLConstructorSplat.h"
+#include "src/sksl/ir/SkSLConstructorStruct.h"
+#include "src/sksl/ir/SkSLDoStatement.h"
+#include "src/sksl/ir/SkSLExpressionStatement.h"
+#include "src/sksl/ir/SkSLFieldAccess.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/SkSLLiteral.h"
+#include "src/sksl/ir/SkSLNop.h"
+#include "src/sksl/ir/SkSLPostfixExpression.h"
+#include "src/sksl/ir/SkSLPrefixExpression.h"
+#include "src/sksl/ir/SkSLReturnStatement.h"
+#include "src/sksl/ir/SkSLSetting.h"
+#include "src/sksl/ir/SkSLSwitchCase.h"
+#include "src/sksl/ir/SkSLSwitchStatement.h"
+#include "src/sksl/ir/SkSLSwizzle.h"
+#include "src/sksl/ir/SkSLSymbolTable.h"
+#include "src/sksl/ir/SkSLTernaryExpression.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/transform/SkSLTransform.h"
+
+#include <algorithm>
+#include <climits>
+#include <cstddef>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+
+namespace SkSL {
+namespace {
+
+static constexpr int kInlinedStatementLimit = 2500;
+
+static std::unique_ptr<Statement>* find_parent_statement(
+ const std::vector<std::unique_ptr<Statement>*>& stmtStack) {
+ SkASSERT(!stmtStack.empty());
+
+ // Walk the statement stack from back to front, ignoring the last element (which is the
+ // enclosing statement).
+ auto iter = stmtStack.rbegin();
+ ++iter;
+
+ // Anything counts as a parent statement other than a scopeless Block.
+ for (; iter != stmtStack.rend(); ++iter) {
+ std::unique_ptr<Statement>* stmt = *iter;
+ if (!(*stmt)->is<Block>() || (*stmt)->as<Block>().isScope()) {
+ return stmt;
+ }
+ }
+
+ // There wasn't any parent statement to be found.
+ return nullptr;
+}
+
+std::unique_ptr<Expression> clone_with_ref_kind(const Expression& expr,
+ VariableReference::RefKind refKind) {
+ std::unique_ptr<Expression> clone = expr.clone();
+ Analysis::UpdateVariableRefKind(clone.get(), refKind);
+ return clone;
+}
+
+} // namespace
+
+const Variable* Inliner::RemapVariable(const Variable* variable,
+ const VariableRewriteMap* varMap) {
+ std::unique_ptr<Expression>* remap = varMap->find(variable);
+ if (!remap) {
+ SkDEBUGFAILF("rewrite map does not contain variable '%.*s'",
+ (int)variable->name().size(), variable->name().data());
+ return variable;
+ }
+ Expression* expr = remap->get();
+ SkASSERT(expr);
+ if (!expr->is<VariableReference>()) {
+ SkDEBUGFAILF("rewrite map contains non-variable replacement for '%.*s'",
+ (int)variable->name().size(), variable->name().data());
+ return variable;
+ }
+ return expr->as<VariableReference>().variable();
+}
+
+void Inliner::ensureScopedBlocks(Statement* inlinedBody, Statement* parentStmt) {
+ // No changes necessary if this statement isn't actually a block.
+ if (!inlinedBody || !inlinedBody->is<Block>()) {
+ return;
+ }
+
+ // No changes necessary if the parent statement doesn't require a scope.
+ if (!parentStmt || !(parentStmt->is<IfStatement>() || parentStmt->is<ForStatement>() ||
+ parentStmt->is<DoStatement>())) {
+ return;
+ }
+
+ Block& block = inlinedBody->as<Block>();
+
+ // The inliner will create inlined function bodies as a Block containing multiple statements,
+ // but no scope. Normally, this is fine, but if this block is used as the statement for a
+ // do/for/if/while, the block needs to be scoped for the generated code to match the intent.
+ // In the case of Blocks nested inside other Blocks, we add the scope to the outermost block if
+ // needed.
+ for (Block* nestedBlock = &block;; ) {
+ if (nestedBlock->isScope()) {
+ // We found an explicit scope; all is well.
+ return;
+ }
+ if (nestedBlock->children().size() == 1 && nestedBlock->children()[0]->is<Block>()) {
+ // This block wraps another unscoped block; we need to go deeper.
+ nestedBlock = &nestedBlock->children()[0]->as<Block>();
+ continue;
+ }
+ // We found a block containing real statements (not just more blocks), but no scope.
+ // Let's add a scope to the outermost block.
+ block.setBlockKind(Block::Kind::kBracedScope);
+ return;
+ }
+}
+
+std::unique_ptr<Expression> Inliner::inlineExpression(Position pos,
+ VariableRewriteMap* varMap,
+ SymbolTable* symbolTableForExpression,
+ const Expression& expression) {
+ auto expr = [&](const std::unique_ptr<Expression>& e) -> std::unique_ptr<Expression> {
+ if (e) {
+ return this->inlineExpression(pos, varMap, symbolTableForExpression, *e);
+ }
+ return nullptr;
+ };
+ auto argList = [&](const ExpressionArray& originalArgs) -> ExpressionArray {
+ ExpressionArray args;
+ args.reserve_back(originalArgs.size());
+ for (const std::unique_ptr<Expression>& arg : originalArgs) {
+ args.push_back(expr(arg));
+ }
+ return args;
+ };
+
+ switch (expression.kind()) {
+ case Expression::Kind::kBinary: {
+ const BinaryExpression& binaryExpr = expression.as<BinaryExpression>();
+ return BinaryExpression::Make(*fContext,
+ pos,
+ expr(binaryExpr.left()),
+ binaryExpr.getOperator(),
+ expr(binaryExpr.right()));
+ }
+ case Expression::Kind::kLiteral:
+ return expression.clone();
+ case Expression::Kind::kChildCall: {
+ const ChildCall& childCall = expression.as<ChildCall>();
+ return ChildCall::Make(*fContext,
+ pos,
+ childCall.type().clone(symbolTableForExpression),
+ childCall.child(),
+ argList(childCall.arguments()));
+ }
+ case Expression::Kind::kConstructorArray: {
+ const ConstructorArray& ctor = expression.as<ConstructorArray>();
+ return ConstructorArray::Make(*fContext, pos,
+ *ctor.type().clone(symbolTableForExpression),
+ argList(ctor.arguments()));
+ }
+ case Expression::Kind::kConstructorArrayCast: {
+ const ConstructorArrayCast& ctor = expression.as<ConstructorArrayCast>();
+ return ConstructorArrayCast::Make(*fContext, pos,
+ *ctor.type().clone(symbolTableForExpression),
+ expr(ctor.argument()));
+ }
+ case Expression::Kind::kConstructorCompound: {
+ const ConstructorCompound& ctor = expression.as<ConstructorCompound>();
+ return ConstructorCompound::Make(*fContext, pos,
+ *ctor.type().clone(symbolTableForExpression),
+ argList(ctor.arguments()));
+ }
+ case Expression::Kind::kConstructorCompoundCast: {
+ const ConstructorCompoundCast& ctor = expression.as<ConstructorCompoundCast>();
+ return ConstructorCompoundCast::Make(*fContext, pos,
+ *ctor.type().clone(symbolTableForExpression),
+ expr(ctor.argument()));
+ }
+ case Expression::Kind::kConstructorDiagonalMatrix: {
+ const ConstructorDiagonalMatrix& ctor = expression.as<ConstructorDiagonalMatrix>();
+ return ConstructorDiagonalMatrix::Make(*fContext, pos,
+ *ctor.type().clone(symbolTableForExpression),
+ expr(ctor.argument()));
+ }
+ case Expression::Kind::kConstructorMatrixResize: {
+ const ConstructorMatrixResize& ctor = expression.as<ConstructorMatrixResize>();
+ return ConstructorMatrixResize::Make(*fContext, pos,
+ *ctor.type().clone(symbolTableForExpression),
+ expr(ctor.argument()));
+ }
+ case Expression::Kind::kConstructorScalarCast: {
+ const ConstructorScalarCast& ctor = expression.as<ConstructorScalarCast>();
+ return ConstructorScalarCast::Make(*fContext, pos,
+ *ctor.type().clone(symbolTableForExpression),
+ expr(ctor.argument()));
+ }
+ case Expression::Kind::kConstructorSplat: {
+ const ConstructorSplat& ctor = expression.as<ConstructorSplat>();
+ return ConstructorSplat::Make(*fContext, pos,
+ *ctor.type().clone(symbolTableForExpression),
+ expr(ctor.argument()));
+ }
+ case Expression::Kind::kConstructorStruct: {
+ const ConstructorStruct& ctor = expression.as<ConstructorStruct>();
+ return ConstructorStruct::Make(*fContext, pos,
+ *ctor.type().clone(symbolTableForExpression),
+ argList(ctor.arguments()));
+ }
+ case Expression::Kind::kFieldAccess: {
+ const FieldAccess& f = expression.as<FieldAccess>();
+ return FieldAccess::Make(*fContext, pos, expr(f.base()), f.fieldIndex(), f.ownerKind());
+ }
+ case Expression::Kind::kFunctionCall: {
+ const FunctionCall& funcCall = expression.as<FunctionCall>();
+ return FunctionCall::Make(*fContext,
+ pos,
+ funcCall.type().clone(symbolTableForExpression),
+ funcCall.function(),
+ argList(funcCall.arguments()));
+ }
+ case Expression::Kind::kFunctionReference:
+ return expression.clone();
+ case Expression::Kind::kIndex: {
+ const IndexExpression& idx = expression.as<IndexExpression>();
+ return IndexExpression::Make(*fContext, pos, expr(idx.base()), expr(idx.index()));
+ }
+ case Expression::Kind::kMethodReference:
+ return expression.clone();
+ case Expression::Kind::kPrefix: {
+ const PrefixExpression& p = expression.as<PrefixExpression>();
+ return PrefixExpression::Make(*fContext, pos, p.getOperator(), expr(p.operand()));
+ }
+ case Expression::Kind::kPostfix: {
+ const PostfixExpression& p = expression.as<PostfixExpression>();
+ return PostfixExpression::Make(*fContext, pos, expr(p.operand()), p.getOperator());
+ }
+ case Expression::Kind::kSetting: {
+ const Setting& s = expression.as<Setting>();
+ return Setting::Convert(*fContext, pos, s.name());
+ }
+ case Expression::Kind::kSwizzle: {
+ const Swizzle& s = expression.as<Swizzle>();
+ return Swizzle::Make(*fContext, pos, expr(s.base()), s.components());
+ }
+ case Expression::Kind::kTernary: {
+ const TernaryExpression& t = expression.as<TernaryExpression>();
+ return TernaryExpression::Make(*fContext, pos, expr(t.test()),
+ expr(t.ifTrue()), expr(t.ifFalse()));
+ }
+ case Expression::Kind::kTypeReference:
+ return expression.clone();
+ case Expression::Kind::kVariableReference: {
+ const VariableReference& v = expression.as<VariableReference>();
+ std::unique_ptr<Expression>* remap = varMap->find(v.variable());
+ if (remap) {
+ return clone_with_ref_kind(**remap, v.refKind());
+ }
+ return expression.clone();
+ }
+ default:
+ SkASSERT(false);
+ return nullptr;
+ }
+}
+
+std::unique_ptr<Statement> Inliner::inlineStatement(Position pos,
+ VariableRewriteMap* varMap,
+ SymbolTable* symbolTableForStatement,
+ std::unique_ptr<Expression>* resultExpr,
+ Analysis::ReturnComplexity returnComplexity,
+ const Statement& statement,
+ const ProgramUsage& usage,
+ bool isBuiltinCode) {
+ auto stmt = [&](const std::unique_ptr<Statement>& s) -> std::unique_ptr<Statement> {
+ if (s) {
+ return this->inlineStatement(pos, varMap, symbolTableForStatement, resultExpr,
+ returnComplexity, *s, usage, isBuiltinCode);
+ }
+ return nullptr;
+ };
+ auto blockStmts = [&](const Block& block) {
+ StatementArray result;
+ result.reserve_back(block.children().size());
+ for (const std::unique_ptr<Statement>& child : block.children()) {
+ result.push_back(stmt(child));
+ }
+ return result;
+ };
+ auto expr = [&](const std::unique_ptr<Expression>& e) -> std::unique_ptr<Expression> {
+ if (e) {
+ return this->inlineExpression(pos, varMap, symbolTableForStatement, *e);
+ }
+ return nullptr;
+ };
+ auto variableModifiers = [&](const Variable& variable,
+ const Expression* initialValue) -> const Modifiers* {
+ return Transform::AddConstToVarModifiers(*fContext, variable, initialValue, &usage);
+ };
+
+ ++fInlinedStatementCounter;
+
+ switch (statement.kind()) {
+ case Statement::Kind::kBlock: {
+ const Block& b = statement.as<Block>();
+ return Block::Make(pos, blockStmts(b), b.blockKind(),
+ SymbolTable::WrapIfBuiltin(b.symbolTable()));
+ }
+
+ case Statement::Kind::kBreak:
+ case Statement::Kind::kContinue:
+ case Statement::Kind::kDiscard:
+ return statement.clone();
+
+ case Statement::Kind::kDo: {
+ const DoStatement& d = statement.as<DoStatement>();
+ return DoStatement::Make(*fContext, pos, stmt(d.statement()), expr(d.test()));
+ }
+ case Statement::Kind::kExpression: {
+ const ExpressionStatement& e = statement.as<ExpressionStatement>();
+ return ExpressionStatement::Make(*fContext, expr(e.expression()));
+ }
+ case Statement::Kind::kFor: {
+ const ForStatement& f = statement.as<ForStatement>();
+ // need to ensure initializer is evaluated first so that we've already remapped its
+ // declarations by the time we evaluate test & next
+ std::unique_ptr<Statement> initializer = stmt(f.initializer());
+
+ std::unique_ptr<LoopUnrollInfo> unrollInfo;
+ if (f.unrollInfo()) {
+ // The for loop's unroll-info points to the Variable in the initializer as the
+ // index. This variable has been rewritten into a clone by the inliner, so we need
+ // to update the loop-unroll info to point to the clone.
+ unrollInfo = std::make_unique<LoopUnrollInfo>(*f.unrollInfo());
+ unrollInfo->fIndex = RemapVariable(unrollInfo->fIndex, varMap);
+ }
+ return ForStatement::Make(*fContext, pos, ForLoopPositions{}, std::move(initializer),
+ expr(f.test()), expr(f.next()), stmt(f.statement()),
+ std::move(unrollInfo),
+ SymbolTable::WrapIfBuiltin(f.symbols()));
+ }
+ case Statement::Kind::kIf: {
+ const IfStatement& i = statement.as<IfStatement>();
+ return IfStatement::Make(*fContext, pos, expr(i.test()),
+ stmt(i.ifTrue()), stmt(i.ifFalse()));
+ }
+ case Statement::Kind::kNop:
+ return statement.clone();
+
+ case Statement::Kind::kReturn: {
+ const ReturnStatement& r = statement.as<ReturnStatement>();
+ if (!r.expression()) {
+ // This function doesn't return a value. We won't inline functions with early
+ // returns, so a return statement is a no-op and can be treated as such.
+ return Nop::Make();
+ }
+
+ // If a function only contains a single return, and it doesn't reference variables from
+ // inside an Block's scope, we don't need to store the result in a variable at all. Just
+ // replace the function-call expression with the function's return expression.
+ SkASSERT(resultExpr);
+ if (returnComplexity <= Analysis::ReturnComplexity::kSingleSafeReturn) {
+ *resultExpr = expr(r.expression());
+ return Nop::Make();
+ }
+
+ // For more complex functions, we assign their result into a variable. We refuse to
+ // inline anything with early returns, so this should be safe to do; that is, on this
+ // control path, this is the last statement that will occur.
+ SkASSERT(*resultExpr);
+ return ExpressionStatement::Make(
+ *fContext,
+ BinaryExpression::Make(
+ *fContext,
+ pos,
+ clone_with_ref_kind(**resultExpr, VariableRefKind::kWrite),
+ Operator::Kind::EQ,
+ expr(r.expression())));
+ }
+ case Statement::Kind::kSwitch: {
+ const SwitchStatement& ss = statement.as<SwitchStatement>();
+ StatementArray cases;
+ cases.reserve_back(ss.cases().size());
+ for (const std::unique_ptr<Statement>& switchCaseStmt : ss.cases()) {
+ const SwitchCase& sc = switchCaseStmt->as<SwitchCase>();
+ if (sc.isDefault()) {
+ cases.push_back(SwitchCase::MakeDefault(pos, stmt(sc.statement())));
+ } else {
+ cases.push_back(SwitchCase::Make(pos, sc.value(), stmt(sc.statement())));
+ }
+ }
+ return SwitchStatement::Make(*fContext, pos, expr(ss.value()),
+ std::move(cases), SymbolTable::WrapIfBuiltin(ss.symbols()));
+ }
+ case Statement::Kind::kVarDeclaration: {
+ const VarDeclaration& decl = statement.as<VarDeclaration>();
+ std::unique_ptr<Expression> initialValue = expr(decl.value());
+ const Variable* variable = decl.var();
+
+ // We assign unique names to inlined variables--scopes hide most of the problems in this
+ // regard, but see `InlinerAvoidsVariableNameOverlap` for a counterexample where unique
+ // names are important.
+ const std::string* name = symbolTableForStatement->takeOwnershipOfString(
+ fMangler.uniqueName(variable->name(), symbolTableForStatement));
+ auto clonedVar = std::make_unique<Variable>(
+ pos,
+ variable->modifiersPosition(),
+ variableModifiers(*variable, initialValue.get()),
+ name->c_str(),
+ variable->type().clone(symbolTableForStatement),
+ isBuiltinCode,
+ variable->storage());
+ varMap->set(variable, VariableReference::Make(pos, clonedVar.get()));
+ auto result = VarDeclaration::Make(*fContext,
+ clonedVar.get(),
+ decl.baseType().clone(symbolTableForStatement),
+ decl.arraySize(),
+ std::move(initialValue));
+ symbolTableForStatement->takeOwnershipOfSymbol(std::move(clonedVar));
+ return result;
+ }
+ default:
+ SkASSERT(false);
+ return nullptr;
+ }
+}
+
+Inliner::InlinedCall Inliner::inlineCall(const FunctionCall& call,
+ std::shared_ptr<SymbolTable> symbolTable,
+ const ProgramUsage& usage,
+ const FunctionDeclaration* caller) {
+ using ScratchVariable = Variable::ScratchVariable;
+
+ // Inlining is more complicated here than in a typical compiler, because we have to have a
+ // high-level IR and can't just drop statements into the middle of an expression or even use
+ // gotos.
+ //
+ // Since we can't insert statements into an expression, we run the inline function as extra
+ // statements before the statement we're currently processing, relying on a lack of execution
+ // order guarantees. Since we can't use gotos (which are normally used to replace return
+ // statements), we wrap the whole function in a loop and use break statements to jump to the
+ // end.
+ SkASSERT(fContext);
+ SkASSERT(this->isSafeToInline(call.function().definition(), usage));
+
+ const ExpressionArray& arguments = call.arguments();
+ const Position pos = call.fPosition;
+ const FunctionDefinition& function = *call.function().definition();
+ const Block& body = function.body()->as<Block>();
+ const Analysis::ReturnComplexity returnComplexity = Analysis::GetReturnComplexity(function);
+
+ StatementArray inlineStatements;
+ int expectedStmtCount = 1 + // Result variable
+ arguments.size() + // Function argument temp-vars
+ body.children().size(); // Inlined code
+
+ inlineStatements.reserve_back(expectedStmtCount);
+
+ std::unique_ptr<Expression> resultExpr;
+ if (returnComplexity > Analysis::ReturnComplexity::kSingleSafeReturn &&
+ !function.declaration().returnType().isVoid()) {
+ // Create a variable to hold the result in the extra statements. We don't need to do this
+ // for void-return functions, or in cases that are simple enough that we can just replace
+ // the function-call node with the result expression.
+ ScratchVariable var = Variable::MakeScratchVariable(*fContext,
+ fMangler,
+ function.declaration().name(),
+ &function.declaration().returnType(),
+ Modifiers{},
+ symbolTable.get(),
+ /*initialValue=*/nullptr);
+ inlineStatements.push_back(std::move(var.fVarDecl));
+ resultExpr = VariableReference::Make(Position(), var.fVarSymbol);
+ }
+
+ // Create variables in the extra statements to hold the arguments, and assign the arguments to
+ // them.
+ VariableRewriteMap varMap;
+ for (int i = 0; i < arguments.size(); ++i) {
+ // If the parameter isn't written to within the inline function ...
+ const Expression* arg = arguments[i].get();
+ const Variable* param = function.declaration().parameters()[i];
+ const ProgramUsage::VariableCounts& paramUsage = usage.get(*param);
+ if (!paramUsage.fWrite) {
+ // ... and can be inlined trivially (e.g. a swizzle, or a constant array index),
+ // or any expression without side effects that is only accessed at most once...
+ if ((paramUsage.fRead > 1) ? Analysis::IsTrivialExpression(*arg)
+ : !Analysis::HasSideEffects(*arg)) {
+ // ... we don't need to copy it at all! We can just use the existing expression.
+ varMap.set(param, arg->clone());
+ continue;
+ }
+ }
+ ScratchVariable var = Variable::MakeScratchVariable(*fContext,
+ fMangler,
+ param->name(),
+ &arg->type(),
+ param->modifiers(),
+ symbolTable.get(),
+ arg->clone());
+ inlineStatements.push_back(std::move(var.fVarDecl));
+ varMap.set(param, VariableReference::Make(Position(), var.fVarSymbol));
+ }
+
+ for (const std::unique_ptr<Statement>& stmt : body.children()) {
+ inlineStatements.push_back(this->inlineStatement(pos, &varMap, symbolTable.get(),
+ &resultExpr, returnComplexity, *stmt,
+ usage, caller->isBuiltin()));
+ }
+
+ SkASSERT(inlineStatements.size() <= expectedStmtCount);
+
+ // Wrap all of the generated statements in a block. We need a real Block here, because we need
+ // to add another child statement to the Block later.
+ InlinedCall inlinedCall;
+ inlinedCall.fInlinedBody = Block::MakeBlock(pos, std::move(inlineStatements),
+ Block::Kind::kUnbracedBlock);
+ if (resultExpr) {
+ // Return our result expression as-is.
+ inlinedCall.fReplacementExpr = std::move(resultExpr);
+ } else if (function.declaration().returnType().isVoid()) {
+ // It's a void function, so it doesn't actually result in anything, but we have to return
+ // something non-null as a standin.
+ inlinedCall.fReplacementExpr = Literal::MakeBool(*fContext, pos, /*value=*/false);
+ } else {
+ // It's a non-void function, but it never created a result expression--that is, it never
+ // returned anything on any path! This should have been detected in the function finalizer.
+ // Still, discard our output and generate an error.
+ SkDEBUGFAIL("inliner found non-void function that fails to return a value on any path");
+ fContext->fErrors->error(function.fPosition, "inliner found non-void function '" +
+ std::string(function.declaration().name()) +
+ "' that fails to return a value on any path");
+ inlinedCall = {};
+ }
+
+ return inlinedCall;
+}
+
+bool Inliner::isSafeToInline(const FunctionDefinition* functionDef, const ProgramUsage& usage) {
+ // A threshold of zero indicates that the inliner is completely disabled, so we can just return.
+ if (this->settings().fInlineThreshold <= 0) {
+ return false;
+ }
+
+ // Enforce a limit on inlining to avoid pathological cases. (inliner/ExponentialGrowth.sksl)
+ if (fInlinedStatementCounter >= kInlinedStatementLimit) {
+ return false;
+ }
+
+ if (functionDef == nullptr) {
+ // Can't inline something if we don't actually have its definition.
+ return false;
+ }
+
+ if (functionDef->declaration().modifiers().fFlags & Modifiers::kNoInline_Flag) {
+ // Refuse to inline functions decorated with `noinline`.
+ return false;
+ }
+
+ // We don't allow inlining a function with out parameters that are written to.
+ // (See skia:11326 for rationale.)
+ for (const Variable* param : functionDef->declaration().parameters()) {
+ if (param->modifiers().fFlags & Modifiers::Flag::kOut_Flag) {
+ ProgramUsage::VariableCounts counts = usage.get(*param);
+ if (counts.fWrite > 0) {
+ return false;
+ }
+ }
+ }
+
+ // We don't have a mechanism to simulate early returns, so we can't inline if there is one.
+ return Analysis::GetReturnComplexity(*functionDef) < Analysis::ReturnComplexity::kEarlyReturns;
+}
+
+// A candidate function for inlining, containing everything that `inlineCall` needs.
+struct InlineCandidate {
+ std::shared_ptr<SymbolTable> fSymbols; // the SymbolTable of the candidate
+ std::unique_ptr<Statement>* fParentStmt; // the parent Statement of the enclosing stmt
+ std::unique_ptr<Statement>* fEnclosingStmt; // the Statement containing the candidate
+ std::unique_ptr<Expression>* fCandidateExpr; // the candidate FunctionCall to be inlined
+ FunctionDefinition* fEnclosingFunction; // the Function containing the candidate
+};
+
+struct InlineCandidateList {
+ std::vector<InlineCandidate> fCandidates;
+};
+
+class InlineCandidateAnalyzer {
+public:
+ // A list of all the inlining candidates we found during analysis.
+ InlineCandidateList* fCandidateList;
+
+ // A stack of the symbol tables; since most nodes don't have one, expected to be shallower than
+ // the enclosing-statement stack.
+ std::vector<std::shared_ptr<SymbolTable>> fSymbolTableStack;
+ // A stack of "enclosing" statements--these would be suitable for the inliner to use for adding
+ // new instructions. Not all statements are suitable (e.g. a for-loop's initializer). The
+ // inliner might replace a statement with a block containing the statement.
+ std::vector<std::unique_ptr<Statement>*> fEnclosingStmtStack;
+ // The function that we're currently processing (i.e. inlining into).
+ FunctionDefinition* fEnclosingFunction = nullptr;
+
+ void visit(const std::vector<std::unique_ptr<ProgramElement>>& elements,
+ std::shared_ptr<SymbolTable> symbols,
+ InlineCandidateList* candidateList) {
+ fCandidateList = candidateList;
+ fSymbolTableStack.push_back(symbols);
+
+ for (const std::unique_ptr<ProgramElement>& pe : elements) {
+ this->visitProgramElement(pe.get());
+ }
+
+ fSymbolTableStack.pop_back();
+ fCandidateList = nullptr;
+ }
+
+ void visitProgramElement(ProgramElement* pe) {
+ switch (pe->kind()) {
+ case ProgramElement::Kind::kFunction: {
+ FunctionDefinition& funcDef = pe->as<FunctionDefinition>();
+ fEnclosingFunction = &funcDef;
+ this->visitStatement(&funcDef.body());
+ break;
+ }
+ default:
+ // The inliner can't operate outside of a function's scope.
+ break;
+ }
+ }
+
+ void visitStatement(std::unique_ptr<Statement>* stmt,
+ bool isViableAsEnclosingStatement = true) {
+ if (!*stmt) {
+ return;
+ }
+
+ Analysis::SymbolTableStackBuilder scopedStackBuilder(stmt->get(), &fSymbolTableStack);
+ size_t oldEnclosingStmtStackSize = fEnclosingStmtStack.size();
+
+ if (isViableAsEnclosingStatement) {
+ fEnclosingStmtStack.push_back(stmt);
+ }
+
+ switch ((*stmt)->kind()) {
+ case Statement::Kind::kBreak:
+ case Statement::Kind::kContinue:
+ case Statement::Kind::kDiscard:
+ case Statement::Kind::kNop:
+ break;
+
+ case Statement::Kind::kBlock: {
+ Block& block = (*stmt)->as<Block>();
+ for (std::unique_ptr<Statement>& blockStmt : block.children()) {
+ this->visitStatement(&blockStmt);
+ }
+ break;
+ }
+ case Statement::Kind::kDo: {
+ DoStatement& doStmt = (*stmt)->as<DoStatement>();
+ // The loop body is a candidate for inlining.
+ this->visitStatement(&doStmt.statement());
+ // The inliner isn't smart enough to inline the test-expression for a do-while
+ // loop at this time. There are two limitations:
+ // - We would need to insert the inlined-body block at the very end of the do-
+ // statement's inner fStatement. We don't support that today, but it's doable.
+ // - We cannot inline the test expression if the loop uses `continue` anywhere; that
+ // would skip over the inlined block that evaluates the test expression. There
+ // isn't a good fix for this--any workaround would be more complex than the cost
+ // of a function call. However, loops that don't use `continue` would still be
+ // viable candidates for inlining.
+ break;
+ }
+ case Statement::Kind::kExpression: {
+ ExpressionStatement& expr = (*stmt)->as<ExpressionStatement>();
+ this->visitExpression(&expr.expression());
+ break;
+ }
+ case Statement::Kind::kFor: {
+ ForStatement& forStmt = (*stmt)->as<ForStatement>();
+ // The initializer and loop body are candidates for inlining.
+ this->visitStatement(&forStmt.initializer(),
+ /*isViableAsEnclosingStatement=*/false);
+ this->visitStatement(&forStmt.statement());
+
+ // The inliner isn't smart enough to inline the test- or increment-expressions
+ // of a for loop loop at this time. There are a handful of limitations:
+ // - We would need to insert the test-expression block at the very beginning of the
+ // for-loop's inner fStatement, and the increment-expression block at the very
+ // end. We don't support that today, but it's doable.
+ // - The for-loop's built-in test-expression would need to be dropped entirely,
+ // and the loop would be halted via a break statement at the end of the inlined
+ // test-expression. This is again something we don't support today, but it could
+ // be implemented.
+ // - We cannot inline the increment-expression if the loop uses `continue` anywhere;
+ // that would skip over the inlined block that evaluates the increment expression.
+ // There isn't a good fix for this--any workaround would be more complex than the
+ // cost of a function call. However, loops that don't use `continue` would still
+ // be viable candidates for increment-expression inlining.
+ break;
+ }
+ case Statement::Kind::kIf: {
+ IfStatement& ifStmt = (*stmt)->as<IfStatement>();
+ this->visitExpression(&ifStmt.test());
+ this->visitStatement(&ifStmt.ifTrue());
+ this->visitStatement(&ifStmt.ifFalse());
+ break;
+ }
+ case Statement::Kind::kReturn: {
+ ReturnStatement& returnStmt = (*stmt)->as<ReturnStatement>();
+ this->visitExpression(&returnStmt.expression());
+ break;
+ }
+ case Statement::Kind::kSwitch: {
+ SwitchStatement& switchStmt = (*stmt)->as<SwitchStatement>();
+ this->visitExpression(&switchStmt.value());
+ for (const std::unique_ptr<Statement>& switchCase : switchStmt.cases()) {
+ // The switch-case's fValue cannot be a FunctionCall; skip it.
+ this->visitStatement(&switchCase->as<SwitchCase>().statement());
+ }
+ break;
+ }
+ case Statement::Kind::kVarDeclaration: {
+ VarDeclaration& varDeclStmt = (*stmt)->as<VarDeclaration>();
+ // Don't need to scan the declaration's sizes; those are always IntLiterals.
+ this->visitExpression(&varDeclStmt.value());
+ break;
+ }
+ default:
+ SkUNREACHABLE;
+ }
+
+ // Pop our symbol and enclosing-statement stacks.
+ fEnclosingStmtStack.resize(oldEnclosingStmtStackSize);
+ }
+
+ void visitExpression(std::unique_ptr<Expression>* expr) {
+ if (!*expr) {
+ return;
+ }
+
+ switch ((*expr)->kind()) {
+ case Expression::Kind::kFieldAccess:
+ case Expression::Kind::kFunctionReference:
+ case Expression::Kind::kLiteral:
+ case Expression::Kind::kMethodReference:
+ case Expression::Kind::kSetting:
+ case Expression::Kind::kTypeReference:
+ case Expression::Kind::kVariableReference:
+ // Nothing to scan here.
+ break;
+
+ case Expression::Kind::kBinary: {
+ BinaryExpression& binaryExpr = (*expr)->as<BinaryExpression>();
+ this->visitExpression(&binaryExpr.left());
+
+ // Logical-and and logical-or binary expressions do not inline the right side,
+ // because that would invalidate short-circuiting. That is, when evaluating
+ // expressions like these:
+ // (false && x()) // always false
+ // (true || y()) // always true
+ // It is illegal for side-effects from x() or y() to occur. The simplest way to
+ // enforce that rule is to avoid inlining the right side entirely. However, it is
+ // safe for other types of binary expression to inline both sides.
+ Operator op = binaryExpr.getOperator();
+ bool shortCircuitable = (op.kind() == Operator::Kind::LOGICALAND ||
+ op.kind() == Operator::Kind::LOGICALOR);
+ if (!shortCircuitable) {
+ this->visitExpression(&binaryExpr.right());
+ }
+ break;
+ }
+ case Expression::Kind::kChildCall: {
+ ChildCall& childCallExpr = (*expr)->as<ChildCall>();
+ for (std::unique_ptr<Expression>& arg : childCallExpr.arguments()) {
+ this->visitExpression(&arg);
+ }
+ break;
+ }
+ case Expression::Kind::kConstructorArray:
+ case Expression::Kind::kConstructorArrayCast:
+ case Expression::Kind::kConstructorCompound:
+ case Expression::Kind::kConstructorCompoundCast:
+ case Expression::Kind::kConstructorDiagonalMatrix:
+ case Expression::Kind::kConstructorMatrixResize:
+ case Expression::Kind::kConstructorScalarCast:
+ case Expression::Kind::kConstructorSplat:
+ case Expression::Kind::kConstructorStruct: {
+ AnyConstructor& constructorExpr = (*expr)->asAnyConstructor();
+ for (std::unique_ptr<Expression>& arg : constructorExpr.argumentSpan()) {
+ this->visitExpression(&arg);
+ }
+ break;
+ }
+ case Expression::Kind::kFunctionCall: {
+ FunctionCall& funcCallExpr = (*expr)->as<FunctionCall>();
+ for (std::unique_ptr<Expression>& arg : funcCallExpr.arguments()) {
+ this->visitExpression(&arg);
+ }
+ this->addInlineCandidate(expr);
+ break;
+ }
+ case Expression::Kind::kIndex: {
+ IndexExpression& indexExpr = (*expr)->as<IndexExpression>();
+ this->visitExpression(&indexExpr.base());
+ this->visitExpression(&indexExpr.index());
+ break;
+ }
+ case Expression::Kind::kPostfix: {
+ PostfixExpression& postfixExpr = (*expr)->as<PostfixExpression>();
+ this->visitExpression(&postfixExpr.operand());
+ break;
+ }
+ case Expression::Kind::kPrefix: {
+ PrefixExpression& prefixExpr = (*expr)->as<PrefixExpression>();
+ this->visitExpression(&prefixExpr.operand());
+ break;
+ }
+ case Expression::Kind::kSwizzle: {
+ Swizzle& swizzleExpr = (*expr)->as<Swizzle>();
+ this->visitExpression(&swizzleExpr.base());
+ break;
+ }
+ case Expression::Kind::kTernary: {
+ TernaryExpression& ternaryExpr = (*expr)->as<TernaryExpression>();
+ // The test expression is a candidate for inlining.
+ this->visitExpression(&ternaryExpr.test());
+ // The true- and false-expressions cannot be inlined, because we are only allowed to
+ // evaluate one side.
+ break;
+ }
+ default:
+ SkUNREACHABLE;
+ }
+ }
+
+ void addInlineCandidate(std::unique_ptr<Expression>* candidate) {
+ fCandidateList->fCandidates.push_back(
+ InlineCandidate{fSymbolTableStack.back(),
+ find_parent_statement(fEnclosingStmtStack),
+ fEnclosingStmtStack.back(),
+ candidate,
+ fEnclosingFunction});
+ }
+};
+
+static const FunctionDeclaration& candidate_func(const InlineCandidate& candidate) {
+ return (*candidate.fCandidateExpr)->as<FunctionCall>().function();
+}
+
+bool Inliner::candidateCanBeInlined(const InlineCandidate& candidate,
+ const ProgramUsage& usage,
+ InlinabilityCache* cache) {
+ const FunctionDeclaration& funcDecl = candidate_func(candidate);
+ if (const bool* cachedInlinability = cache->find(&funcDecl)) {
+ return *cachedInlinability;
+ }
+ bool inlinability = this->isSafeToInline(funcDecl.definition(), usage);
+ cache->set(&funcDecl, inlinability);
+ return inlinability;
+}
+
+int Inliner::getFunctionSize(const FunctionDeclaration& funcDecl, FunctionSizeCache* cache) {
+ if (const int* cachedSize = cache->find(&funcDecl)) {
+ return *cachedSize;
+ }
+ int size = Analysis::NodeCountUpToLimit(*funcDecl.definition(),
+ this->settings().fInlineThreshold);
+ cache->set(&funcDecl, size);
+ return size;
+}
+
+void Inliner::buildCandidateList(const std::vector<std::unique_ptr<ProgramElement>>& elements,
+ std::shared_ptr<SymbolTable> symbols, ProgramUsage* usage,
+ InlineCandidateList* candidateList) {
+ // This is structured much like a ProgramVisitor, but does not actually use ProgramVisitor.
+ // The analyzer needs to keep track of the `unique_ptr<T>*` of statements and expressions so
+ // that they can later be replaced, and ProgramVisitor does not provide this; it only provides a
+ // `const T&`.
+ InlineCandidateAnalyzer analyzer;
+ analyzer.visit(elements, symbols, candidateList);
+
+ // Early out if there are no inlining candidates.
+ std::vector<InlineCandidate>& candidates = candidateList->fCandidates;
+ if (candidates.empty()) {
+ return;
+ }
+
+ // Remove candidates that are not safe to inline.
+ InlinabilityCache cache;
+ candidates.erase(std::remove_if(candidates.begin(),
+ candidates.end(),
+ [&](const InlineCandidate& candidate) {
+ return !this->candidateCanBeInlined(
+ candidate, *usage, &cache);
+ }),
+ candidates.end());
+
+ // If the inline threshold is unlimited, or if we have no candidates left, our candidate list is
+ // complete.
+ if (this->settings().fInlineThreshold == INT_MAX || candidates.empty()) {
+ return;
+ }
+
+ // Remove candidates on a per-function basis if the effect of inlining would be to make more
+ // than `inlineThreshold` nodes. (i.e. if Func() would be inlined six times and its size is
+ // 10 nodes, it should be inlined if the inlineThreshold is 60 or higher.)
+ FunctionSizeCache functionSizeCache;
+ FunctionSizeCache candidateTotalCost;
+ for (InlineCandidate& candidate : candidates) {
+ const FunctionDeclaration& fnDecl = candidate_func(candidate);
+ candidateTotalCost[&fnDecl] += this->getFunctionSize(fnDecl, &functionSizeCache);
+ }
+
+ candidates.erase(std::remove_if(candidates.begin(), candidates.end(),
+ [&](const InlineCandidate& candidate) {
+ const FunctionDeclaration& fnDecl = candidate_func(candidate);
+ if (fnDecl.modifiers().fFlags & Modifiers::kInline_Flag) {
+ // Functions marked `inline` ignore size limitations.
+ return false;
+ }
+ if (usage->get(fnDecl) == 1) {
+ // If a function is only used once, it's cost-free to inline.
+ return false;
+ }
+ if (candidateTotalCost[&fnDecl] <= this->settings().fInlineThreshold) {
+ // We won't exceed the inline threshold by inlining this.
+ return false;
+ }
+ // Inlining this function will add too many IRNodes.
+ return true;
+ }),
+ candidates.end());
+}
+
+bool Inliner::analyze(const std::vector<std::unique_ptr<ProgramElement>>& elements,
+ std::shared_ptr<SymbolTable> symbols,
+ ProgramUsage* usage) {
+ // A threshold of zero indicates that the inliner is completely disabled, so we can just return.
+ if (this->settings().fInlineThreshold <= 0) {
+ return false;
+ }
+
+ // Enforce a limit on inlining to avoid pathological cases. (inliner/ExponentialGrowth.sksl)
+ if (fInlinedStatementCounter >= kInlinedStatementLimit) {
+ return false;
+ }
+
+ InlineCandidateList candidateList;
+ this->buildCandidateList(elements, symbols, usage, &candidateList);
+
+ // Inline the candidates where we've determined that it's safe to do so.
+ using StatementRemappingTable = SkTHashMap<std::unique_ptr<Statement>*,
+ std::unique_ptr<Statement>*>;
+ StatementRemappingTable statementRemappingTable;
+
+ bool madeChanges = false;
+ for (const InlineCandidate& candidate : candidateList.fCandidates) {
+ const FunctionCall& funcCall = (*candidate.fCandidateExpr)->as<FunctionCall>();
+
+ // Convert the function call to its inlined equivalent.
+ InlinedCall inlinedCall = this->inlineCall(funcCall, candidate.fSymbols, *usage,
+ &candidate.fEnclosingFunction->declaration());
+
+ // Stop if an error was detected during the inlining process.
+ if (!inlinedCall.fInlinedBody && !inlinedCall.fReplacementExpr) {
+ break;
+ }
+
+ // Ensure that the inlined body has a scope if it needs one.
+ this->ensureScopedBlocks(inlinedCall.fInlinedBody.get(), candidate.fParentStmt->get());
+
+ // Add references within the inlined body
+ usage->add(inlinedCall.fInlinedBody.get());
+
+ // Look up the enclosing statement; remap it if necessary.
+ std::unique_ptr<Statement>* enclosingStmt = candidate.fEnclosingStmt;
+ for (;;) {
+ std::unique_ptr<Statement>** remappedStmt = statementRemappingTable.find(enclosingStmt);
+ if (!remappedStmt) {
+ break;
+ }
+ enclosingStmt = *remappedStmt;
+ }
+
+ // Move the enclosing statement to the end of the unscoped Block containing the inlined
+ // function, then replace the enclosing statement with that Block.
+ // Before:
+ // fInlinedBody = Block{ stmt1, stmt2, stmt3 }
+ // fEnclosingStmt = stmt4
+ // After:
+ // fInlinedBody = null
+ // fEnclosingStmt = Block{ stmt1, stmt2, stmt3, stmt4 }
+ inlinedCall.fInlinedBody->children().push_back(std::move(*enclosingStmt));
+ *enclosingStmt = std::move(inlinedCall.fInlinedBody);
+
+ // Replace the candidate function call with our replacement expression.
+ usage->remove(candidate.fCandidateExpr->get());
+ usage->add(inlinedCall.fReplacementExpr.get());
+ *candidate.fCandidateExpr = std::move(inlinedCall.fReplacementExpr);
+ madeChanges = true;
+
+ // If anything else pointed at our enclosing statement, it's now pointing at a Block
+ // containing many other statements as well. Maintain a fix-up table to account for this.
+ statementRemappingTable.set(enclosingStmt,&(*enclosingStmt)->as<Block>().children().back());
+
+ // Stop inlining if we've reached our hard cap on new statements.
+ if (fInlinedStatementCounter >= kInlinedStatementLimit) {
+ break;
+ }
+
+ // Note that nothing was destroyed except for the FunctionCall. All other nodes should
+ // remain valid.
+ }
+
+ return madeChanges;
+}
+
+} // namespace SkSL
+
+#endif // SK_ENABLE_OPTIMIZE_SIZE
diff --git a/gfx/skia/skia/src/sksl/SkSLInliner.h b/gfx/skia/skia/src/sksl/SkSLInliner.h
new file mode 100644
index 0000000000..618365baf0
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLInliner.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_INLINER
+#define SKSL_INLINER
+
+#ifndef SK_ENABLE_OPTIMIZE_SIZE
+
+#include "src/core/SkTHash.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLMangler.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/ir/SkSLBlock.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+#include <memory>
+#include <vector>
+
+namespace SkSL {
+
+class FunctionCall;
+class FunctionDeclaration;
+class FunctionDefinition;
+class Position;
+class ProgramElement;
+class ProgramUsage;
+class Statement;
+class SymbolTable;
+class Variable;
+struct InlineCandidate;
+struct InlineCandidateList;
+namespace Analysis { enum class ReturnComplexity; }
+
+/**
+ * Converts a FunctionCall in the IR to a set of statements to be injected ahead of the function
+ * call, and a replacement expression. Can also detect cases where inlining isn't cleanly possible
+ * (e.g. return statements nested inside of a loop construct). The inliner isn't able to guarantee
+ * identical-to-GLSL execution order if the inlined function has visible side effects.
+ */
+class Inliner {
+public:
+ Inliner(const Context* context) : fContext(context) {}
+
+ /** Inlines any eligible functions that are found. Returns true if any changes are made. */
+ bool analyze(const std::vector<std::unique_ptr<ProgramElement>>& elements,
+ std::shared_ptr<SymbolTable> symbols,
+ ProgramUsage* usage);
+
+private:
+ using VariableRewriteMap = SkTHashMap<const Variable*, std::unique_ptr<Expression>>;
+
+ const ProgramSettings& settings() const { return fContext->fConfig->fSettings; }
+
+ void buildCandidateList(const std::vector<std::unique_ptr<ProgramElement>>& elements,
+ std::shared_ptr<SymbolTable> symbols, ProgramUsage* usage,
+ InlineCandidateList* candidateList);
+
+ std::unique_ptr<Expression> inlineExpression(Position pos,
+ VariableRewriteMap* varMap,
+ SymbolTable* symbolTableForExpression,
+ const Expression& expression);
+ std::unique_ptr<Statement> inlineStatement(Position pos,
+ VariableRewriteMap* varMap,
+ SymbolTable* symbolTableForStatement,
+ std::unique_ptr<Expression>* resultExpr,
+ Analysis::ReturnComplexity returnComplexity,
+ const Statement& statement,
+ const ProgramUsage& usage,
+ bool isBuiltinCode);
+
+ /**
+ * Searches the rewrite map for an rewritten Variable* for the passed-in one. Asserts if the
+ * rewrite map doesn't contain the variable, or contains a different type of expression.
+ */
+ static const Variable* RemapVariable(const Variable* variable,
+ const VariableRewriteMap* varMap);
+
+ using InlinabilityCache = SkTHashMap<const FunctionDeclaration*, bool>;
+ bool candidateCanBeInlined(const InlineCandidate& candidate,
+ const ProgramUsage& usage,
+ InlinabilityCache* cache);
+
+ using FunctionSizeCache = SkTHashMap<const FunctionDeclaration*, int>;
+ int getFunctionSize(const FunctionDeclaration& fnDecl, FunctionSizeCache* cache);
+
+ /**
+ * Processes the passed-in FunctionCall expression. The FunctionCall expression should be
+ * replaced with `fReplacementExpr`. If non-null, `fInlinedBody` should be inserted immediately
+ * above the statement containing the inlined expression.
+ */
+ struct InlinedCall {
+ std::unique_ptr<Block> fInlinedBody;
+ std::unique_ptr<Expression> fReplacementExpr;
+ };
+ InlinedCall inlineCall(const FunctionCall&,
+ std::shared_ptr<SymbolTable>,
+ const ProgramUsage&,
+ const FunctionDeclaration* caller);
+
+ /** Adds a scope to inlined bodies returned by `inlineCall`, if one is required. */
+ void ensureScopedBlocks(Statement* inlinedBody, Statement* parentStmt);
+
+ /** Checks whether inlining is viable for a FunctionCall, modulo recursion and function size. */
+ bool isSafeToInline(const FunctionDefinition* functionDef, const ProgramUsage& usage);
+
+ const Context* fContext = nullptr;
+ Mangler fMangler;
+ int fInlinedStatementCounter = 0;
+};
+
+} // namespace SkSL
+
+#endif // SK_ENABLE_OPTIMIZE_SIZE
+
+#endif // SKSL_INLINER
diff --git a/gfx/skia/skia/src/sksl/SkSLIntrinsicList.cpp b/gfx/skia/skia/src/sksl/SkSLIntrinsicList.cpp
new file mode 100644
index 0000000000..a582bdff60
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLIntrinsicList.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2021 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/base/SkStringView.h"
+#include "src/sksl/SkSLIntrinsicList.h"
+
+namespace SkSL {
+
+const IntrinsicMap& GetIntrinsicMap() {
+ #define SKSL_INTRINSIC(name) {#name, k_##name##_IntrinsicKind},
+ static const auto* kAllIntrinsics = new SkTHashMap<std::string_view, IntrinsicKind>{
+ SKSL_INTRINSIC_LIST
+ };
+ #undef SKSL_INTRINSIC
+
+ return *kAllIntrinsics;
+}
+
+IntrinsicKind FindIntrinsicKind(std::string_view functionName) {
+ if (skstd::starts_with(functionName, '$')) {
+ functionName.remove_prefix(1);
+ }
+
+ const IntrinsicMap& intrinsicMap = GetIntrinsicMap();
+ IntrinsicKind* kind = intrinsicMap.find(functionName);
+ return kind ? *kind : kNotIntrinsic;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/SkSLIntrinsicList.h b/gfx/skia/skia/src/sksl/SkSLIntrinsicList.h
new file mode 100644
index 0000000000..9e41f3b1aa
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLIntrinsicList.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_INTRINSIC_LIST_DEFINED
+#define SKSL_INTRINSIC_LIST_DEFINED
+
+#include "src/core/SkTHash.h"
+
+#include <cstdint>
+#include <initializer_list>
+#include <string_view>
+
+// A list of every intrinsic supported by SkSL.
+// Using an X-Macro (https://en.wikipedia.org/wiki/X_Macro) to manage the list.
+#define SKSL_INTRINSIC_LIST \
+ SKSL_INTRINSIC(abs) \
+ SKSL_INTRINSIC(acosh) \
+ SKSL_INTRINSIC(acos) \
+ SKSL_INTRINSIC(all) \
+ SKSL_INTRINSIC(any) \
+ SKSL_INTRINSIC(asinh) \
+ SKSL_INTRINSIC(asin) \
+ SKSL_INTRINSIC(atanh) \
+ SKSL_INTRINSIC(atan) \
+ SKSL_INTRINSIC(atomicAdd) \
+ SKSL_INTRINSIC(atomicLoad) \
+ SKSL_INTRINSIC(atomicStore) \
+ SKSL_INTRINSIC(bitCount) \
+ SKSL_INTRINSIC(ceil) \
+ SKSL_INTRINSIC(clamp) \
+ SKSL_INTRINSIC(cosh) \
+ SKSL_INTRINSIC(cos) \
+ SKSL_INTRINSIC(cross) \
+ SKSL_INTRINSIC(degrees) \
+ SKSL_INTRINSIC(determinant) \
+ SKSL_INTRINSIC(dFdx) \
+ SKSL_INTRINSIC(dFdy) \
+ SKSL_INTRINSIC(distance) \
+ SKSL_INTRINSIC(dot) \
+ SKSL_INTRINSIC(equal) \
+ SKSL_INTRINSIC(eval) \
+ SKSL_INTRINSIC(exp2) \
+ SKSL_INTRINSIC(exp) \
+ SKSL_INTRINSIC(faceforward) \
+ SKSL_INTRINSIC(findLSB) \
+ SKSL_INTRINSIC(findMSB) \
+ SKSL_INTRINSIC(floatBitsToInt) \
+ SKSL_INTRINSIC(floatBitsToUint) \
+ SKSL_INTRINSIC(floor) \
+ SKSL_INTRINSIC(fma) \
+ SKSL_INTRINSIC(fract) \
+ SKSL_INTRINSIC(frexp) \
+ SKSL_INTRINSIC(fromLinearSrgb) \
+ SKSL_INTRINSIC(fwidth) \
+ SKSL_INTRINSIC(greaterThanEqual) \
+ SKSL_INTRINSIC(greaterThan) \
+ SKSL_INTRINSIC(height) \
+ SKSL_INTRINSIC(intBitsToFloat) \
+ SKSL_INTRINSIC(inversesqrt) \
+ SKSL_INTRINSIC(inverse) \
+ SKSL_INTRINSIC(isinf) \
+ SKSL_INTRINSIC(isnan) \
+ SKSL_INTRINSIC(ldexp) \
+ SKSL_INTRINSIC(length) \
+ SKSL_INTRINSIC(lessThanEqual) \
+ SKSL_INTRINSIC(lessThan) \
+ SKSL_INTRINSIC(log2) \
+ SKSL_INTRINSIC(log) \
+ SKSL_INTRINSIC(makeSampler2D) \
+ SKSL_INTRINSIC(matrixCompMult) \
+ SKSL_INTRINSIC(matrixInverse) \
+ SKSL_INTRINSIC(max) \
+ SKSL_INTRINSIC(min) \
+ SKSL_INTRINSIC(mix) \
+ SKSL_INTRINSIC(modf) \
+ SKSL_INTRINSIC(mod) \
+ SKSL_INTRINSIC(normalize) \
+ SKSL_INTRINSIC(notEqual) \
+ SKSL_INTRINSIC(not ) \
+ SKSL_INTRINSIC(outerProduct) \
+ SKSL_INTRINSIC(packDouble2x32) \
+ SKSL_INTRINSIC(packHalf2x16) \
+ SKSL_INTRINSIC(packSnorm2x16) \
+ SKSL_INTRINSIC(packSnorm4x8) \
+ SKSL_INTRINSIC(packUnorm2x16) \
+ SKSL_INTRINSIC(packUnorm4x8) \
+ SKSL_INTRINSIC(pow) \
+ SKSL_INTRINSIC(radians) \
+ SKSL_INTRINSIC(read) \
+ SKSL_INTRINSIC(reflect) \
+ SKSL_INTRINSIC(refract) \
+ SKSL_INTRINSIC(roundEven) \
+ SKSL_INTRINSIC(round) \
+ SKSL_INTRINSIC(sample) \
+ SKSL_INTRINSIC(sampleGrad) \
+ SKSL_INTRINSIC(sampleLod) \
+ SKSL_INTRINSIC(saturate) \
+ SKSL_INTRINSIC(sign) \
+ SKSL_INTRINSIC(sinh) \
+ SKSL_INTRINSIC(sin) \
+ SKSL_INTRINSIC(smoothstep) \
+ SKSL_INTRINSIC(sqrt) \
+ SKSL_INTRINSIC(step) \
+ SKSL_INTRINSIC(storageBarrier) \
+ SKSL_INTRINSIC(subpassLoad) \
+ SKSL_INTRINSIC(tanh) \
+ SKSL_INTRINSIC(tan) \
+ SKSL_INTRINSIC(toLinearSrgb) \
+ SKSL_INTRINSIC(transpose) \
+ SKSL_INTRINSIC(trunc) \
+ SKSL_INTRINSIC(uintBitsToFloat) \
+ SKSL_INTRINSIC(unpackDouble2x32) \
+ SKSL_INTRINSIC(unpackHalf2x16) \
+ SKSL_INTRINSIC(unpackSnorm2x16) \
+ SKSL_INTRINSIC(unpackSnorm4x8) \
+ SKSL_INTRINSIC(unpackUnorm2x16) \
+ SKSL_INTRINSIC(unpackUnorm4x8) \
+ SKSL_INTRINSIC(width) \
+ SKSL_INTRINSIC(workgroupBarrier) \
+ SKSL_INTRINSIC(write)
+
+namespace SkSL {
+
+// The `IntrinsicKind` enum holds every intrinsic supported by SkSL.
+#define SKSL_INTRINSIC(name) k_##name##_IntrinsicKind,
+enum IntrinsicKind : int8_t {
+ kNotIntrinsic = -1,
+ SKSL_INTRINSIC_LIST
+};
+#undef SKSL_INTRINSIC
+
+// Returns a map which allows IntrinsicKind values to be looked up by name.
+using IntrinsicMap = SkTHashMap<std::string_view, IntrinsicKind>;
+const IntrinsicMap& GetIntrinsicMap();
+
+// Looks up intrinsic functions by name.
+IntrinsicKind FindIntrinsicKind(std::string_view functionName);
+
+}
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/SkSLLexer.cpp b/gfx/skia/skia/src/sksl/SkSLLexer.cpp
new file mode 100644
index 0000000000..10c1108c09
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLLexer.cpp
@@ -0,0 +1,808 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+/*****************************************************************************************
+ ******************** This file was generated by sksllex. Do not edit. *******************
+ *****************************************************************************************/
+#include "src/sksl/SkSLLexer.h"
+
+namespace SkSL {
+
+using State = uint16_t;
+static constexpr uint8_t kInvalidChar = 18;
+static constexpr int8_t kMappings[118] = {
+ 1, 2, 3, 3, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 1, 4, 3, 5, 6, 7, 8, 3, 9, 10, 11, 12, 13, 14, 15, 16, 17,
+ 18, 19, 20, 21, 22, 22, 22, 23, 23, 24, 25, 26, 27, 28, 29, 3, 30, 30, 31, 32,
+ 33, 30, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 35, 36, 34, 37, 34, 34, 38,
+ 34, 34, 39, 3, 40, 41, 42, 3, 43, 44, 45, 46, 47, 48, 49, 50, 51, 34, 52, 53,
+ 54, 55, 56, 57, 34, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70};
+using IndexEntry = int16_t;
+struct FullEntry {
+ State data[71];
+};
+struct CompactEntry {
+ uint32_t values;
+ uint8_t data[18];
+};
+static constexpr FullEntry kFull[] = {
+ {
+ 0, 2, 3, 4, 5, 7, 9, 23, 25, 28, 29, 30, 32, 35, 36,
+ 39, 44, 50, 69, 69, 69, 69, 69, 69, 71, 72, 73, 77, 79, 83,
+ 84, 84, 84, 84, 84, 84, 84, 84, 84, 86, 87, 88, 84, 91, 104,
+ 114, 130, 150, 162, 178, 183, 191, 84, 215, 225, 232, 258, 263, 279, 291,
+ 345, 362, 378, 390, 84, 84, 84, 411, 412, 415, 416,
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 59, 59, 59, 59, 59, 59, 60,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 61, 0, 0, 0, 66, 67, 0, 0, 0, 0, 0, 0, 0, 0, 61,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 0, 0, 67, 0, 0, 0, 0, 0, 0,
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 59, 59, 59, 59, 59, 59, 60,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 61, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 61,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 60, 60, 60, 60, 60, 60, 60,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 61, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 61,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 70, 70, 70, 70, 70, 70, 70,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 61, 0, 0, 0, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 61,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85,
+ 85, 85, 85, 85, 85, 85, 0, 0, 0, 0, 0, 0, 85, 85, 85, 85, 85, 85,
+ 85, 85, 85, 0, 0, 0, 85, 115, 85, 85, 85, 85, 85, 85, 85, 85, 85, 118,
+ 85, 85, 121, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 0, 0, 0, 0,
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85,
+ 85, 85, 85, 85, 85, 85, 0, 0, 0, 0, 0, 0, 85, 85, 85, 85, 85, 85,
+ 85, 85, 85, 0, 0, 0, 85, 85, 85, 85, 85, 131, 85, 85, 85, 137, 85, 85,
+ 85, 85, 143, 85, 85, 85, 85, 85, 147, 85, 85, 85, 85, 0, 0, 0, 0,
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85,
+ 85, 85, 85, 85, 85, 85, 0, 0, 0, 0, 0, 0, 85, 85, 85, 85, 85, 85,
+ 85, 85, 85, 0, 0, 0, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 151,
+ 85, 154, 85, 85, 85, 85, 85, 85, 85, 85, 156, 85, 85, 0, 0, 0, 0,
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85,
+ 85, 85, 85, 85, 85, 85, 0, 0, 0, 0, 0, 0, 85, 85, 85, 85, 85, 85,
+ 85, 85, 85, 0, 0, 0, 85, 163, 85, 85, 85, 85, 85, 85, 85, 167, 85, 170,
+ 85, 85, 173, 85, 85, 85, 85, 85, 175, 85, 85, 85, 85, 0, 0, 0, 0,
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85,
+ 85, 85, 85, 85, 85, 85, 0, 0, 0, 0, 0, 0, 85, 85, 85, 85, 85, 85,
+ 85, 85, 85, 0, 0, 0, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 194,
+ 85, 85, 198, 201, 85, 85, 203, 85, 209, 85, 85, 85, 85, 0, 0, 0, 0,
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85,
+ 85, 85, 85, 85, 85, 85, 0, 0, 0, 0, 0, 0, 85, 85, 85, 85, 85, 85,
+ 85, 85, 85, 0, 0, 0, 85, 264, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85,
+ 85, 85, 85, 85, 268, 85, 85, 275, 85, 85, 85, 85, 85, 0, 0, 0, 0,
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85,
+ 85, 85, 85, 85, 85, 85, 0, 0, 0, 0, 0, 0, 85, 85, 85, 85, 85, 85,
+ 85, 85, 85, 0, 0, 0, 85, 292, 85, 85, 85, 85, 85, 85, 85, 324, 85, 85,
+ 85, 85, 85, 85, 85, 85, 328, 336, 85, 340, 85, 85, 85, 0, 0, 0, 0,
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85,
+ 298, 305, 316, 85, 85, 85, 0, 0, 0, 0, 0, 0, 85, 321, 85, 85, 85, 85,
+ 85, 85, 85, 0, 0, 0, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85,
+ 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 0, 0, 0, 0,
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85,
+ 85, 85, 85, 85, 85, 85, 0, 0, 0, 0, 0, 0, 85, 85, 85, 85, 85, 85,
+ 85, 85, 85, 0, 0, 0, 85, 85, 85, 85, 85, 346, 85, 85, 352, 85, 85, 85,
+ 85, 85, 85, 85, 354, 85, 85, 85, 85, 85, 85, 357, 85, 0, 0, 0, 0,
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85,
+ 85, 85, 85, 85, 85, 85, 0, 0, 0, 0, 0, 0, 85, 85, 85, 85, 85, 85,
+ 85, 85, 85, 0, 0, 0, 85, 85, 85, 85, 85, 85, 85, 85, 391, 85, 85, 85,
+ 85, 85, 395, 85, 403, 85, 85, 85, 85, 85, 85, 85, 85, 0, 0, 0, 0,
+ },
+};
+static constexpr CompactEntry kCompact[] = {
+ {0,
+ {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}},
+ {3,
+ {195, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}},
+ {6,
+ {255, 255, 255, 255, 255, 255, 63, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}},
+ {8, {255, 255, 255, 255, 255, 255, 255, 15, 0, 192, 15, 0, 0, 0, 0, 0, 192, 63}},
+ {8, {255, 255, 255, 255, 3, 0, 255, 15, 0, 192, 15, 0, 0, 0, 0, 0, 192, 63}},
+ {19 | (11 << 9) | (10 << 18),
+ {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 106, 170, 170, 162, 170, 234, 63}},
+ {10, {255, 255, 255, 255, 3, 0, 255, 15, 0, 192, 15, 0, 0, 0, 0, 0, 192, 63}},
+ {14 | (12 << 9) | (10 << 18),
+ {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 170, 170, 170, 106, 170, 232, 63}},
+ {13 | (10 << 9),
+ {255, 255, 255, 255, 87, 84, 255, 95, 85, 213, 95, 85, 85, 85, 85, 85, 213, 63}},
+ {15 | (10 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 81, 85, 213, 63}},
+ {16 | (10 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 84, 85, 213, 63}},
+ {17 | (10 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}},
+ {18 | (10 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}},
+ {20 | (10 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}},
+ {21 | (10 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}},
+ {22 | (10 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {24,
+ {255, 255, 255, 255, 255, 255, 63, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}},
+ {27 | (26 << 9),
+ {255, 255, 253, 255, 255, 255, 63, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}},
+ {31,
+ {255, 255, 255, 255, 255, 255, 63, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}},
+ {34 | (33 << 9),
+ {255, 255, 255, 253, 255, 255, 63, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}},
+ {38 | (37 << 9),
+ {255, 255, 255, 223, 255, 255, 63, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}},
+ {40, {255, 255, 255, 255, 3, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}},
+ {41 | (40 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 255, 243, 255, 255, 63, 255, 255, 255, 255, 255, 63}},
+ {43 | (42 << 9),
+ {255, 255, 255, 221, 3, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}},
+ {43, {255, 255, 255, 255, 3, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}},
+ {49 | (48 << 9) | (45 << 18),
+ {255, 255, 191, 255, 253, 255, 63, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}},
+ {46 | (45 << 9), {87, 85, 21, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 21}},
+ {47 | (45 << 9), {87, 85, 85, 85, 84, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 21}},
+ {48, {51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
+ {56 | (52 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 255, 243, 255, 255, 63, 255, 255, 255, 255, 255, 63}},
+ {53 | (52 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 255, 243, 255, 255, 63, 255, 255, 255, 255, 255, 63}},
+ {55 | (54 << 9),
+ {255, 255, 255, 221, 3, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}},
+ {55, {255, 255, 255, 255, 3, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}},
+ {58 | (57 << 9),
+ {255, 255, 255, 221, 3, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}},
+ {58, {255, 255, 255, 255, 3, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}},
+ {63 | (62 << 9),
+ {255, 255, 255, 221, 3, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}},
+ {63, {255, 255, 255, 255, 3, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}},
+ {68, {255, 255, 255, 255, 3, 0, 255, 15, 240, 255, 63, 0, 252, 255, 255, 255, 255, 63}},
+ {68 | (66 << 9),
+ {255, 255, 255, 255, 3, 0, 255, 15, 240, 247, 63, 0, 252, 255, 255, 247, 255, 63}},
+ {76 | (74 << 9),
+ {255, 255, 255, 255, 255, 255, 31, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}},
+ {75,
+ {255, 255, 255, 255, 255, 255, 63, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}},
+ {78,
+ {255, 255, 255, 255, 255, 255, 63, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}},
+ {81 | (80 << 9),
+ {255, 255, 255, 255, 255, 255, 127, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}},
+ {82,
+ {255, 255, 255, 255, 255, 255, 63, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}},
+ {85, {255, 255, 255, 255, 3, 0, 255, 15, 0, 192, 15, 0, 0, 0, 0, 0, 192, 63}},
+ {90 | (89 << 9),
+ {255, 255, 255, 255, 255, 255, 127, 255, 255, 255, 243, 255, 255, 255, 255, 255, 255, 63}},
+ {94 | (92 << 9) | (85 << 18),
+ {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 170, 170, 170, 106, 168, 234, 63}},
+ {93 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 69, 85, 85, 213, 63}},
+ {98 | (95 << 9) | (85 << 18),
+ {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 170, 170, 170, 169, 168, 234, 63}},
+ {96 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 69, 85, 85, 213, 63}},
+ {97 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}},
+ {93 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}},
+ {99 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}},
+ {100 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}},
+ {101 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 84, 85, 85, 85, 85, 213, 63}},
+ {102 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}},
+ {103 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}},
+ {93 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {109 | (105 << 9) | (85 << 18),
+ {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 170, 170, 170, 154, 162, 234, 63}},
+ {106 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {107 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}},
+ {108 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 84, 85, 85, 213, 63}},
+ {110 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 84, 85, 85, 85, 213, 63}},
+ {111 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 84, 85, 85, 85, 213, 63}},
+ {112 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {113 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}},
+ {116 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 21, 85, 213, 63}},
+ {117 | (93 << 9) | (85 << 18),
+ {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 42, 170, 170, 170, 169, 234, 63}},
+ {119 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}},
+ {120 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 21, 85, 213, 63}},
+ {93 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 21, 85, 213, 63}},
+ {122 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}},
+ {125 | (123 << 9) | (85 << 18),
+ {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 170, 170, 170, 106, 168, 234, 63}},
+ {124 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}},
+ {126 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}},
+ {127 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}},
+ {128 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}},
+ {129 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {132 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 84, 85, 85, 85, 213, 63}},
+ {133 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}},
+ {134 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}},
+ {135 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 81, 85, 85, 213, 63}},
+ {136 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}},
+ {138 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 21, 85, 213, 63}},
+ {139 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}},
+ {140 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}},
+ {141 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}},
+ {142 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 69, 85, 85, 85, 85, 213, 63}},
+ {144 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}},
+ {145 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 84, 85, 85, 85, 85, 213, 63}},
+ {146 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 81, 85, 85, 213, 63}},
+ {148 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {149 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}},
+ {93 | (85 << 9),
+ {255, 255, 255, 255, 23, 80, 255, 95, 85, 213, 95, 85, 85, 85, 85, 85, 213, 63}},
+ {152 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 21, 85, 213, 63}},
+ {153 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {155 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}},
+ {157 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}},
+ {158 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {159 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}},
+ {160 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}},
+ {161 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}},
+ {93 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 81, 85, 85, 213, 63}},
+ {164 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 81, 85, 85, 213, 63}},
+ {165 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 21, 85, 213, 63}},
+ {166 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {168 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 85, 212, 63}},
+ {169 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {93 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 69, 85, 85, 85, 85, 213, 63}},
+ {171 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}},
+ {172 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}},
+ {174 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}},
+ {176 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {177 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}},
+ {181 | (179 << 9) | (85 << 18),
+ {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 170, 170, 166, 168, 170, 234, 63}},
+ {180 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 79, 85, 85, 85, 85, 85, 213, 63}},
+ {180, {255, 255, 255, 255, 3, 0, 255, 15, 0, 192, 15, 0, 0, 0, 0, 0, 192, 63}},
+ {182 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}},
+ {93 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 84, 85, 213, 63}},
+ {188 | (184 << 9) | (85 << 18),
+ {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 170, 106, 170, 170, 138, 234, 63}},
+ {185 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 81, 85, 85, 85, 213, 63}},
+ {186 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 69, 85, 85, 85, 213, 63}},
+ {187 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 81, 85, 213, 63}},
+ {189 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {190 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}},
+ {193 | (192 << 9) | (85 << 18),
+ {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 170, 169, 42, 170, 170, 234, 63}},
+ {195 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}},
+ {196 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}},
+ {197 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {199 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}},
+ {200 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}},
+ {202 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}},
+ {93 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}},
+ {204 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {205 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}},
+ {206 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 84, 85, 85, 85, 213, 63}},
+ {207 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}},
+ {208 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}},
+ {210 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}},
+ {211 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}},
+ {212 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}},
+ {213 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}},
+ {214 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}},
+ {221 | (216 << 9) | (85 << 18),
+ {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 111, 170, 170, 170, 168, 170, 234, 63}},
+ {217 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 85, 209, 63}},
+ {218 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 84, 85, 213, 63}},
+ {219 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}},
+ {220 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}},
+ {223 | (222 << 9) | (85 << 18),
+ {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 170, 170, 106, 170, 42, 234, 63}},
+ {93 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 81, 85, 85, 85, 213, 63}},
+ {224 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 81, 85, 213, 63}},
+ {226 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {227 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 69, 85, 85, 85, 85, 213, 63}},
+ {228 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}},
+ {229 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}},
+ {230 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 69, 85, 85, 213, 63}},
+ {231 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 81, 85, 213, 63}},
+ {240 | (233 << 9) | (85 << 18),
+ {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 111, 170, 170, 170, 168, 170, 234, 63}},
+ {234 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 69, 85, 85, 213, 63}},
+ {235 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {236 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 21, 85, 213, 63}},
+ {237 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 81, 85, 213, 63}},
+ {238 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}},
+ {239 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}},
+ {247 | (241 << 9) | (85 << 18),
+ {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 170, 106, 170, 162, 170, 234, 63}},
+ {242 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}},
+ {243 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 81, 85, 85, 213, 63}},
+ {244 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}},
+ {245 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}},
+ {246 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {248 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {249 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}},
+ {250 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 21, 85, 213, 63}},
+ {251 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 81, 85, 213, 63}},
+ {252 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {253 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}},
+ {254 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}},
+ {255 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}},
+ {256 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 69, 213, 63}},
+ {257 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {259 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}},
+ {260 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}},
+ {261 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 81, 85, 213, 63}},
+ {262 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}},
+ {265 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}},
+ {266 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 84, 85, 85, 213, 63}},
+ {267 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {269 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {270 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}},
+ {271 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}},
+ {272 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 21, 85, 213, 63}},
+ {273 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}},
+ {274 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 84, 85, 213, 63}},
+ {93 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}},
+ {276 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 84, 85, 85, 85, 85, 213, 63}},
+ {277 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 81, 85, 85, 213, 63}},
+ {278 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}},
+ {280 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {287 | (281 << 9) | (85 << 18),
+ {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 111, 170, 170, 170, 170, 168, 234, 63}},
+ {282 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 69, 85, 85, 85, 85, 213, 63}},
+ {283 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 84, 85, 213, 63}},
+ {284 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}},
+ {285 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 81, 85, 85, 213, 63}},
+ {286 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 85, 209, 63}},
+ {288 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}},
+ {289 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}},
+ {290 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}},
+ {293 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 69, 85, 85, 213, 63}},
+ {294 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 81, 85, 213, 63}},
+ {295 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 81, 85, 85, 213, 63}},
+ {296 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {297 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}},
+ {299 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 84, 213, 95, 85, 85, 85, 85, 85, 213, 63}},
+ {300 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 212, 95, 85, 85, 85, 85, 85, 213, 63}},
+ {301 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 69, 85, 85, 85, 213, 63}},
+ {302 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}},
+ {303 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 69, 85, 85, 85, 85, 213, 63}},
+ {304 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 84, 85, 213, 63}},
+ {93 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 21, 213, 63}},
+ {306 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 84, 213, 95, 85, 85, 85, 85, 85, 213, 63}},
+ {307 | (300 << 9) | (85 << 18),
+ {255, 255, 255, 255, 171, 170, 255, 175, 42, 233, 175, 170, 170, 170, 170, 170, 234, 63}},
+ {308 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {309 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}},
+ {310 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}},
+ {311 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 212, 95, 85, 85, 85, 85, 85, 213, 63}},
+ {312 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 69, 85, 85, 85, 213, 63}},
+ {313 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}},
+ {314 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 69, 85, 85, 85, 85, 213, 63}},
+ {315 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 84, 85, 213, 63}},
+ {317 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 84, 213, 95, 85, 85, 85, 85, 85, 213, 63}},
+ {318 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 21, 213, 95, 85, 85, 85, 85, 85, 213, 63}},
+ {319 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {320 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}},
+ {322 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}},
+ {323 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 84, 85, 85, 85, 85, 213, 63}},
+ {325 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 85, 197, 63}},
+ {326 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {327 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 84, 85, 213, 63}},
+ {93 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 84, 85, 85, 85, 213, 63}},
+ {332 | (329 << 9) | (85 << 18),
+ {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 111, 170, 170, 170, 138, 170, 234, 63}},
+ {330 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}},
+ {331 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}},
+ {333 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}},
+ {334 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}},
+ {335 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}},
+ {337 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 81, 85, 213, 63}},
+ {338 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {339 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}},
+ {93 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 81, 85, 213, 63}},
+ {341 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}},
+ {342 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}},
+ {343 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}},
+ {344 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 69, 85, 85, 85, 213, 63}},
+ {347 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 69, 85, 85, 213, 63}},
+ {348 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 81, 85, 213, 63}},
+ {349 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 81, 85, 85, 213, 63}},
+ {350 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}},
+ {351 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}},
+ {353 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}},
+ {355 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}},
+ {356 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {358 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 81, 85, 213, 63}},
+ {359 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {360 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 69, 85, 85, 85, 85, 213, 63}},
+ {361 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {375 | (363 << 9) | (85 << 18),
+ {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 170, 170, 106, 42, 170, 234, 63}},
+ {370 | (364 << 9) | (85 << 18),
+ {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 170, 106, 170, 42, 170, 234, 63}},
+ {369 | (365 << 9) | (85 << 18),
+ {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 170, 169, 170, 168, 170, 234, 63}},
+ {366 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 84, 85, 213, 63}},
+ {367 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}},
+ {368 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 69, 85, 85, 213, 63}},
+ {371 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}},
+ {372 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 81, 85, 85, 85, 213, 63}},
+ {373 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}},
+ {374 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {376 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}},
+ {377 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}},
+ {384 | (379 << 9) | (85 << 18),
+ {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 111, 170, 170, 170, 168, 170, 234, 63}},
+ {380 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}},
+ {381 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 85, 209, 63}},
+ {382 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}},
+ {383 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}},
+ {385 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 81, 85, 85, 213, 63}},
+ {386 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}},
+ {387 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}},
+ {388 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}},
+ {389 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 81, 85, 85, 213, 63}},
+ {392 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}},
+ {393 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 81, 85, 85, 213, 63}},
+ {394 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {396 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}},
+ {397 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 84, 85, 85, 213, 63}},
+ {398 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 81, 85, 85, 85, 213, 63}},
+ {399 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}},
+ {400 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 84, 85, 213, 63}},
+ {401 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}},
+ {402 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 81, 85, 213, 63}},
+ {404 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}},
+ {405 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}},
+ {406 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}},
+ {407 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 84, 85, 213, 63}},
+ {408 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}},
+ {409 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 81, 85, 85, 213, 63}},
+ {410 | (85 << 9),
+ {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 85, 209, 63}},
+ {414 | (413 << 9),
+ {255, 255, 255, 255, 255, 255, 127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 60}},
+};
+static constexpr IndexEntry kIndices[] = {
+ 0, -1, 1, 1, 0, 2, 0, 3, 4, 5, 6, 7, 8, 6, 9, 10, 11, 12,
+ 6, 13, 14, 15, 6, 16, 0, 17, 0, 0, 0, 0, 18, 0, 19, 0, 0, 0,
+ 20, 0, 0, 21, 22, 23, 24, 24, 25, 26, 27, 0, 28, 0, -2, 29, 30, 31,
+ 32, 32, 33, 34, 34, -3, -4, 35, 36, 36, 0, 0, 0, 37, 38, -5, -5, 0,
+ 0, 39, 40, 0, 0, 41, 0, 42, 0, 43, 0, 0, 44, 44, 0, 0, 45, 0,
+ 0, 46, 47, 44, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
+ 44, 62, 63, 64, 65, 44, -6, 66, 67, 44, 68, 69, 70, 71, 72, 73, 44, 74,
+ 75, 76, 77, 44, -7, 78, 79, 80, 81, 82, 44, 83, 84, 85, 86, 87, 44, 88,
+ 89, 90, 57, 91, 92, 93, -8, 94, 95, 44, 96, 47, 97, 98, 99, 100, 101, 102,
+ -9, 103, 104, 105, 44, 106, 107, 108, 109, 110, 44, 111, 44, 112, 113, 93, 114, 115,
+ 116, 117, 118, 119, 120, 121, 122, 44, 123, 124, 93, 125, 44, -10, 126, 127, 128, 44,
+ 129, 130, 44, 131, 132, 133, 134, 135, 136, 137, 57, 138, 139, 140, 141, 142, 132, 143,
+ 144, 145, 146, 147, 44, 148, 149, 150, 44, 151, 152, 153, 154, 155, 156, 44, 157, 158,
+ 159, 160, 161, 162, 163, 57, 164, 165, 166, 167, 168, 169, 44, 170, 171, 172, 173, 174,
+ 175, 176, 177, 178, 179, 44, 180, 181, 182, 183, 132, -11, 184, 185, 186, 108, 187, 188,
+ 189, 190, 191, 192, 193, 194, 195, 196, 51, 197, 198, 199, 200, 201, 202, 203, 44, 204,
+ 205, 206, 44, -12, 207, 208, 209, 210, 211, -13, 212, 213, 214, 215, 216, 217, 218, 219,
+ 220, 221, 222, 223, 224, 225, 226, 227, 228, 218, 229, 230, 231, 232, 132, 233, 234, 57,
+ 235, 236, 237, 238, 239, 240, 241, 51, 242, 243, 244, 44, 245, 246, 247, 248, 249, 250,
+ 251, 252, 44, -14, 253, 254, 255, 256, 257, 57, 258, 70, 259, 260, 44, 261, 262, 263,
+ 264, 238, 265, 266, 267, 268, 269, 270, 44, 193, 271, 272, 273, 274, 108, 275, 276, 149,
+ 277, 278, 279, 280, 281, 149, 282, 283, 284, 285, 286, 57, -15, 287, 288, 289, 44, 290,
+ 291, 292, 293, 294, 295, 296, 44, 297, 298, 299, 300, 301, 302, 303, 44, 0, 304, 0,
+ 0, 0, 0,
+};
+State get_transition(int transition, int state) {
+ IndexEntry index = kIndices[state];
+ if (index < 0) {
+ return kFull[~index].data[transition];
+ }
+ const CompactEntry& entry = kCompact[index];
+ int v = entry.data[transition >> 2];
+ v >>= 2 * (transition & 3);
+ v &= 3;
+ v *= 9;
+ return (entry.values >> v) & 511;
+}
+static const int8_t kAccepts[417] = {
+ -1, -1, 88, 88, 91, 67, 72, 91, 42, 40, 40, 40, 40, 36, 40, 40, 40, 40, 37, 40, 40, 40,
+ 27, 57, 81, 62, 66, 86, 43, 44, 55, 79, 53, 51, 77, 50, 54, 52, 78, 49, 1, -1, -1, 1,
+ 56, -1, -1, 90, 89, 80, 2, 1, 1, -1, -1, 1, -1, -1, 1, 2, 3, -1, -1, 1, 3, 2,
+ 2, -1, 2, 2, 2, 69, 87, 74, 58, 82, 76, 70, 71, 73, 75, 59, 83, 68, 41, 41, 47, 48,
+ 61, 85, 65, 41, 41, 39, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 14, 41,
+ 41, 41, 41, 30, 41, 41, 41, 12, 41, 41, 41, 41, 41, 41, 22, 41, 41, 41, 41, 15, 41, 41,
+ 41, 41, 41, 41, 13, 41, 41, 41, 41, 41, 16, 10, 41, 41, 41, 41, 41, 41, 41, 41, 41, 7,
+ 41, 41, 41, 41, 41, 41, 39, 41, 41, 41, 41, 41, 5, 41, 41, 41, 41, 41, 23, 41, 8, 41,
+ 41, 41, 41, 41, 39, 41, 41, 41, 41, 41, 41, 33, 41, 41, 41, 41, 6, 18, 41, 41, 41, 25,
+ 41, 41, 20, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
+ 32, 41, 41, 41, 35, 41, 41, 41, 41, 41, 41, 34, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
+ 41, 41, 41, 41, 26, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 24, 41, 41, 19, 41, 41, 41,
+ 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
+ 28, 41, 41, 41, 17, 41, 41, 41, 41, 41, 41, 41, 41, 39, 41, 41, 41, 41, 41, 41, 41, 41,
+ 41, 41, 41, 41, 41, 41, 41, 41, 41, 39, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
+ 41, 41, 41, 41, 41, 31, 41, 41, 41, 41, 41, 41, 41, 41, 11, 41, 41, 41, 41, 41, 41, 41,
+ 41, 41, 41, 41, 4, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 21, 41, 41, 41, 41, 41,
+ 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 9, 41,
+ 41, 41, 41, 41, 41, 41, 38, 41, 41, 41, 41, 41, 41, 41, 29, 45, 60, 84, 64, 46, 63,
+};
+
+Token Lexer::next() {
+ // note that we cheat here: normally a lexer needs to worry about the case
+ // where a token has a prefix which is not itself a valid token - for instance,
+ // maybe we have a valid token 'while', but 'w', 'wh', etc. are not valid
+ // tokens. Our grammar doesn't have this property, so we can simplify the logic
+ // a bit.
+ int32_t startOffset = fOffset;
+ State state = 1;
+ for (;;) {
+ if (fOffset >= (int32_t)fText.length()) {
+ if (startOffset == (int32_t)fText.length() || kAccepts[state] == -1) {
+ return Token(Token::Kind::TK_END_OF_FILE, startOffset, 0);
+ }
+ break;
+ }
+ uint8_t c = (uint8_t)(fText[fOffset] - 9);
+ if (c >= 118) {
+ c = kInvalidChar;
+ }
+ State newState = get_transition(kMappings[c], state);
+ if (!newState) {
+ break;
+ }
+ state = newState;
+ ++fOffset;
+ }
+ Token::Kind kind = (Token::Kind)kAccepts[state];
+ return Token(kind, startOffset, fOffset - startOffset);
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/SkSLLexer.h b/gfx/skia/skia/src/sksl/SkSLLexer.h
new file mode 100644
index 0000000000..1cd81a66b7
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLLexer.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+/*****************************************************************************************
+ ******************** This file was generated by sksllex. Do not edit. *******************
+ *****************************************************************************************/
+#ifndef SKSL_Lexer
+#define SKSL_Lexer
+#include <cstdint>
+#include <string_view>
+namespace SkSL {
+
+struct Token {
+ enum class Kind {
+ TK_END_OF_FILE,
+ TK_FLOAT_LITERAL,
+ TK_INT_LITERAL,
+ TK_BAD_OCTAL,
+ TK_TRUE_LITERAL,
+ TK_FALSE_LITERAL,
+ TK_IF,
+ TK_ELSE,
+ TK_FOR,
+ TK_WHILE,
+ TK_DO,
+ TK_SWITCH,
+ TK_CASE,
+ TK_DEFAULT,
+ TK_BREAK,
+ TK_CONTINUE,
+ TK_DISCARD,
+ TK_RETURN,
+ TK_IN,
+ TK_OUT,
+ TK_INOUT,
+ TK_UNIFORM,
+ TK_CONST,
+ TK_FLAT,
+ TK_NOPERSPECTIVE,
+ TK_INLINE,
+ TK_NOINLINE,
+ TK_PURE,
+ TK_READONLY,
+ TK_WRITEONLY,
+ TK_BUFFER,
+ TK_STRUCT,
+ TK_LAYOUT,
+ TK_HIGHP,
+ TK_MEDIUMP,
+ TK_LOWP,
+ TK_ES3,
+ TK_EXPORT,
+ TK_WORKGROUP,
+ TK_RESERVED,
+ TK_PRIVATE_IDENTIFIER,
+ TK_IDENTIFIER,
+ TK_DIRECTIVE,
+ TK_LPAREN,
+ TK_RPAREN,
+ TK_LBRACE,
+ TK_RBRACE,
+ TK_LBRACKET,
+ TK_RBRACKET,
+ TK_DOT,
+ TK_COMMA,
+ TK_PLUSPLUS,
+ TK_MINUSMINUS,
+ TK_PLUS,
+ TK_MINUS,
+ TK_STAR,
+ TK_SLASH,
+ TK_PERCENT,
+ TK_SHL,
+ TK_SHR,
+ TK_BITWISEOR,
+ TK_BITWISEXOR,
+ TK_BITWISEAND,
+ TK_BITWISENOT,
+ TK_LOGICALOR,
+ TK_LOGICALXOR,
+ TK_LOGICALAND,
+ TK_LOGICALNOT,
+ TK_QUESTION,
+ TK_COLON,
+ TK_EQ,
+ TK_EQEQ,
+ TK_NEQ,
+ TK_GT,
+ TK_LT,
+ TK_GTEQ,
+ TK_LTEQ,
+ TK_PLUSEQ,
+ TK_MINUSEQ,
+ TK_STAREQ,
+ TK_SLASHEQ,
+ TK_PERCENTEQ,
+ TK_SHLEQ,
+ TK_SHREQ,
+ TK_BITWISEOREQ,
+ TK_BITWISEXOREQ,
+ TK_BITWISEANDEQ,
+ TK_SEMICOLON,
+ TK_WHITESPACE,
+ TK_LINE_COMMENT,
+ TK_BLOCK_COMMENT,
+ TK_INVALID,
+ TK_NONE,
+ };
+
+ Token() {}
+ Token(Kind kind, int32_t offset, int32_t length)
+ : fKind(kind), fOffset(offset), fLength(length) {}
+
+ Kind fKind = Kind::TK_NONE;
+ int32_t fOffset = -1;
+ int32_t fLength = -1;
+};
+
+class Lexer {
+public:
+ void start(std::string_view text) {
+ fText = text;
+ fOffset = 0;
+ }
+
+ Token next();
+
+ struct Checkpoint {
+ int32_t fOffset;
+ };
+
+ Checkpoint getCheckpoint() const { return {fOffset}; }
+
+ void rewindToCheckpoint(Checkpoint checkpoint) { fOffset = checkpoint.fOffset; }
+
+private:
+ std::string_view fText;
+ int32_t fOffset;
+};
+
+} // namespace SkSL
+#endif
diff --git a/gfx/skia/skia/src/sksl/SkSLMangler.cpp b/gfx/skia/skia/src/sksl/SkSLMangler.cpp
new file mode 100644
index 0000000000..650837d7aa
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLMangler.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2021 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/SkSLMangler.h"
+
+#include "include/core/SkString.h"
+#include "include/core/SkTypes.h"
+#include "src/base/SkStringView.h"
+#include "src/sksl/ir/SkSLSymbolTable.h"
+
+#include <algorithm>
+#include <cstring>
+#include <ctype.h>
+
+namespace SkSL {
+
+std::string Mangler::uniqueName(std::string_view baseName, SymbolTable* symbolTable) {
+ SkASSERT(symbolTable);
+
+ // Private names might begin with a $. Strip that off.
+ if (skstd::starts_with(baseName, '$')) {
+ baseName.remove_prefix(1);
+ }
+
+ // The inliner runs more than once, so the base name might already have been mangled and have a
+ // prefix like "_123_x". Let's strip that prefix off to make the generated code easier to read.
+ if (skstd::starts_with(baseName, '_')) {
+ // Determine if we have a string of digits.
+ int offset = 1;
+ while (isdigit(baseName[offset])) {
+ ++offset;
+ }
+ // If we found digits, another underscore, and anything else, that's the mangler prefix.
+ // Strip it off.
+ if (offset > 1 && baseName[offset] == '_' && baseName[offset + 1] != '\0') {
+ baseName.remove_prefix(offset + 1);
+ } else {
+ // This name doesn't contain a mangler prefix, but it does start with an underscore.
+ // OpenGL disallows two consecutive underscores anywhere in the string, and we'll be
+ // adding one as part of the mangler prefix, so strip the leading underscore.
+ baseName.remove_prefix(1);
+ }
+ }
+
+ // Append a unique numeric prefix to avoid name overlap. Check the symbol table to make sure
+ // we're not reusing an existing name. (Note that within a single compilation pass, this check
+ // isn't fully comprehensive, as code isn't always generated in top-to-bottom order.)
+
+ // This code is a performance hotspot. Assemble the string manually to save a few cycles.
+ char uniqueName[256];
+ uniqueName[0] = '_';
+ char* uniqueNameEnd = uniqueName + std::size(uniqueName);
+ for (;;) {
+ // _123
+ char* endPtr = SkStrAppendS32(uniqueName + 1, fCounter++);
+
+ // _123_
+ *endPtr++ = '_';
+
+ // _123_baseNameTruncatedToFit (no null terminator, because string_view doesn't require one)
+ int baseNameCopyLength = std::min<int>(baseName.size(), uniqueNameEnd - endPtr);
+ memcpy(endPtr, baseName.data(), baseNameCopyLength);
+ endPtr += baseNameCopyLength;
+
+ std::string_view uniqueNameView(uniqueName, endPtr - uniqueName);
+ if (symbolTable->find(uniqueNameView) == nullptr) {
+ return std::string(uniqueNameView);
+ }
+ }
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/SkSLMangler.h b/gfx/skia/skia/src/sksl/SkSLMangler.h
new file mode 100644
index 0000000000..8c0dd5e6e0
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLMangler.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_MANGLER
+#define SKSL_MANGLER
+
+#include <string>
+#include <string_view>
+
+namespace SkSL {
+
+class SymbolTable;
+
+class Mangler {
+public:
+ /**
+ * Mangles baseName to create a name that is unique within symbolTable.
+ */
+ std::string uniqueName(std::string_view baseName, SymbolTable* symbolTable);
+
+ void reset() {
+ fCounter = 0;
+ }
+
+private:
+ int fCounter = 0;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/SkSLMemoryLayout.h b/gfx/skia/skia/src/sksl/SkSLMemoryLayout.h
new file mode 100644
index 0000000000..8389c00346
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLMemoryLayout.h
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKIASL_MEMORYLAYOUT
+#define SKIASL_MEMORYLAYOUT
+
+#include <algorithm>
+
+#include "src/sksl/ir/SkSLType.h"
+
+namespace SkSL {
+
+class MemoryLayout {
+public:
+ enum class Standard {
+ // GLSL std140 layout as described in OpenGL Spec v4.5, 7.6.2.2.
+ k140,
+
+ // GLSL std430 layout. This layout is like std140 but with optimizations. This layout can
+ // ONLY be used with shader storage blocks.
+ k430,
+
+ // MSL memory layout.
+ kMetal,
+
+ // WebGPU Shading Language buffer layout constraints for the uniform address space.
+ kWGSLUniform,
+
+ // WebGPU Shading Language buffer layout constraints for the storage address space.
+ kWGSLStorage,
+ };
+
+ MemoryLayout(Standard std)
+ : fStd(std) {}
+
+ bool isWGSL() const { return fStd == Standard::kWGSLUniform || fStd == Standard::kWGSLStorage; }
+
+ bool isMetal() const { return fStd == Standard::kMetal; }
+
+ /**
+ * WGSL and std140 require various types of variables (structs, arrays, and matrices) in the
+ * uniform address space to be rounded up to the nearest multiple of 16. This function performs
+ * the rounding depending on the given `type` and the current memory layout standard.
+ *
+ * (For WGSL, see https://www.w3.org/TR/WGSL/#address-space-layout-constraints).
+ */
+ size_t roundUpIfNeeded(size_t raw, Type::TypeKind type) const {
+ if (fStd == Standard::k140) {
+ return roundUp16(raw);
+ }
+ // WGSL uniform matrix layout is simply the alignment of the matrix columns and
+ // doesn't have a 16-byte multiple alignment constraint.
+ if (fStd == Standard::kWGSLUniform && type != Type::TypeKind::kMatrix) {
+ return roundUp16(raw);
+ }
+ return raw;
+ }
+
+ /**
+ * Rounds up the integer `n` to the smallest multiple of 16 greater than `n`.
+ */
+ size_t roundUp16(size_t n) const { return (n + 15) & ~15; }
+
+ /**
+ * Returns a type's required alignment when used as a standalone variable.
+ */
+ size_t alignment(const Type& type) const {
+ // See OpenGL Spec 7.6.2.2 Standard Uniform Block Layout
+ switch (type.typeKind()) {
+ case Type::TypeKind::kScalar:
+ case Type::TypeKind::kAtomic:
+ return this->size(type);
+ case Type::TypeKind::kVector:
+ return GetVectorAlignment(this->size(type.componentType()), type.columns());
+ case Type::TypeKind::kMatrix:
+ return this->roundUpIfNeeded(
+ GetVectorAlignment(this->size(type.componentType()), type.rows()),
+ type.typeKind());
+ case Type::TypeKind::kArray:
+ return this->roundUpIfNeeded(this->alignment(type.componentType()),
+ type.typeKind());
+ case Type::TypeKind::kStruct: {
+ size_t result = 0;
+ for (const auto& f : type.fields()) {
+ size_t alignment = this->alignment(*f.fType);
+ if (alignment > result) {
+ result = alignment;
+ }
+ }
+ return this->roundUpIfNeeded(result, type.typeKind());
+ }
+ default:
+ SK_ABORT("cannot determine alignment of type %s", type.displayName().c_str());
+ }
+ }
+
+ /**
+ * For matrices and arrays, returns the number of bytes from the start of one entry (row, in
+ * the case of matrices) to the start of the next.
+ */
+ size_t stride(const Type& type) const {
+ switch (type.typeKind()) {
+ case Type::TypeKind::kMatrix:
+ return this->alignment(type);
+ case Type::TypeKind::kArray: {
+ int stride = this->size(type.componentType());
+ if (stride > 0) {
+ int align = this->alignment(type.componentType());
+ stride += align - 1;
+ stride -= stride % align;
+ stride = this->roundUpIfNeeded(stride, type.typeKind());
+ }
+ return stride;
+ }
+ default:
+ SK_ABORT("type does not have a stride");
+ }
+ }
+
+ /**
+ * Returns the size of a type in bytes. Returns 0 if the given type is not supported.
+ */
+ size_t size(const Type& type) const {
+ switch (type.typeKind()) {
+ case Type::TypeKind::kScalar:
+ if (type.isBoolean()) {
+ if (this->isWGSL()) {
+ return 0;
+ }
+ return 1;
+ }
+ if ((this->isMetal() || this->isWGSL()) && !type.highPrecision() &&
+ type.isNumber()) {
+ return 2;
+ }
+ return 4;
+ case Type::TypeKind::kAtomic:
+ // Our atomic types (currently atomicUint) always occupy 4 bytes.
+ return 4;
+ case Type::TypeKind::kVector:
+ if (this->isMetal() && type.columns() == 3) {
+ return 4 * this->size(type.componentType());
+ }
+ return type.columns() * this->size(type.componentType());
+ case Type::TypeKind::kMatrix: // fall through
+ case Type::TypeKind::kArray:
+ return type.isUnsizedArray() ? 0 : (type.columns() * this->stride(type));
+ case Type::TypeKind::kStruct: {
+ size_t total = 0;
+ for (const auto& f : type.fields()) {
+ size_t alignment = this->alignment(*f.fType);
+ if (total % alignment != 0) {
+ total += alignment - total % alignment;
+ }
+ SkASSERT(total % alignment == 0);
+ total += this->size(*f.fType);
+ }
+ size_t alignment = this->alignment(type);
+ SkASSERT(!type.fields().size() ||
+ (0 == alignment % this->alignment(*type.fields()[0].fType)));
+ return (total + alignment - 1) & ~(alignment - 1);
+ }
+ default:
+ SK_ABORT("cannot determine size of type %s", type.displayName().c_str());
+ }
+ }
+
+ /**
+ * Not all types are compatible with memory layout.
+ */
+ size_t isSupported(const Type& type) const {
+ switch (type.typeKind()) {
+ case Type::TypeKind::kAtomic:
+ return true;
+
+ case Type::TypeKind::kScalar:
+ // bool and short are not host-shareable in WGSL.
+ return !this->isWGSL() ||
+ (!type.isBoolean() && (type.isFloat() || type.highPrecision()));
+
+ case Type::TypeKind::kVector:
+ case Type::TypeKind::kMatrix:
+ case Type::TypeKind::kArray:
+ return this->isSupported(type.componentType());
+
+ case Type::TypeKind::kStruct:
+ return std::all_of(
+ type.fields().begin(), type.fields().end(), [this](const Type::Field& f) {
+ return this->isSupported(*f.fType);
+ });
+
+ default:
+ return false;
+ }
+ }
+
+private:
+ static size_t GetVectorAlignment(size_t componentSize, int columns) {
+ return componentSize * (columns + columns % 2);
+ }
+
+ const Standard fStd;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/SkSLMemoryPool.h b/gfx/skia/skia/src/sksl/SkSLMemoryPool.h
new file mode 100644
index 0000000000..0d16d84ac0
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLMemoryPool.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_MEMORYPOOL
+#define SKSL_MEMORYPOOL
+
+#include <memory>
+
+#include "include/core/SkTypes.h"
+
+#if defined(SK_GANESH)
+
+#include "src/gpu/ganesh/GrMemoryPool.h"
+
+namespace SkSL {
+using MemoryPool = ::GrMemoryPool;
+}
+
+#else
+
+// When Ganesh is disabled, GrMemoryPool is not linked in. We include a minimal class which mimics
+// the GrMemoryPool interface but simply redirects to the system allocator.
+namespace SkSL {
+
+class MemoryPool {
+public:
+ static std::unique_ptr<MemoryPool> Make(size_t, size_t) {
+ return std::make_unique<MemoryPool>();
+ }
+ void resetScratchSpace() {}
+ void reportLeaks() const {}
+ bool isEmpty() const { return true; }
+ void* allocate(size_t size) { return ::operator new(size); }
+ void release(void* p) { ::operator delete(p); }
+};
+
+} // namespace SkSL
+
+#endif // defined(SK_GANESH)
+#endif // SKSL_MEMORYPOOL
diff --git a/gfx/skia/skia/src/sksl/SkSLModifiersPool.h b/gfx/skia/skia/src/sksl/SkSLModifiersPool.h
new file mode 100644
index 0000000000..e9b863c871
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLModifiersPool.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2020 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_MODIFIERSPOOL
+#define SKSL_MODIFIERSPOOL
+
+#include "include/private/SkSLModifiers.h"
+
+#include <unordered_set>
+
+namespace SkSL {
+
+/**
+ * Deduplicates Modifiers objects and stores them in a shared pool. Modifiers are fairly heavy, and
+ * tend to be reused a lot, so deduplication can be a significant win.
+ */
+class ModifiersPool {
+public:
+ const Modifiers* add(const Modifiers& modifiers) {
+ auto [iter, wasInserted] = fModifiersSet.insert(modifiers);
+ return &*iter;
+ }
+
+ void clear() {
+ fModifiersSet.clear();
+ }
+
+private:
+ std::unordered_set<Modifiers> fModifiersSet;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/SkSLModuleLoader.cpp b/gfx/skia/skia/src/sksl/SkSLModuleLoader.cpp
new file mode 100644
index 0000000000..c164fc1fe6
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLModuleLoader.cpp
@@ -0,0 +1,444 @@
+/*
+ * 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 "src/sksl/SkSLModuleLoader.h"
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLModifiers.h"
+#include "include/private/SkSLProgramElement.h"
+#include "include/private/SkSLProgramKind.h"
+#include "include/private/base/SkMutex.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLModifiersPool.h"
+#include "src/sksl/ir/SkSLSymbolTable.h"
+#include "src/sksl/ir/SkSLType.h"
+#include "src/sksl/ir/SkSLVariable.h"
+
+#include <algorithm>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#if SKSL_STANDALONE
+
+#include "include/core/SkString.h"
+#include "src/utils/SkOSPath.h"
+#include "tools/SkGetExecutablePath.h"
+
+ // In standalone mode, we load the original SkSL source files. GN is responsible for copying
+ // these files from src/sksl/ to the directory where the executable is located.
+ #include <fstream>
+
+ static std::string load_module_file(const char* moduleFilename) {
+ std::string exePath = SkGetExecutablePath();
+ SkString exeDir = SkOSPath::Dirname(exePath.c_str());
+ SkString modulePath = SkOSPath::Join(exeDir.c_str(), moduleFilename);
+ std::ifstream in(std::string{modulePath.c_str()});
+ std::string moduleSource{std::istreambuf_iterator<char>(in),
+ std::istreambuf_iterator<char>()};
+ if (in.rdstate()) {
+ SK_ABORT("Error reading %s\n", modulePath.c_str());
+ }
+ return moduleSource;
+ }
+
+ #define MODULE_DATA(name) #name, load_module_file(#name ".sksl")
+
+#else
+
+ // We include minified SkSL module code and pass it directly to the compiler.
+ #if defined(SK_ENABLE_OPTIMIZE_SIZE) || !defined(SK_DEBUG)
+ #include "src/sksl/generated/sksl_shared.minified.sksl"
+ #include "src/sksl/generated/sksl_compute.minified.sksl"
+ #include "src/sksl/generated/sksl_frag.minified.sksl"
+ #include "src/sksl/generated/sksl_gpu.minified.sksl"
+ #include "src/sksl/generated/sksl_public.minified.sksl"
+ #include "src/sksl/generated/sksl_rt_shader.minified.sksl"
+ #include "src/sksl/generated/sksl_vert.minified.sksl"
+ #if defined(SK_GRAPHITE)
+ #include "src/sksl/generated/sksl_graphite_frag.minified.sksl"
+ #include "src/sksl/generated/sksl_graphite_vert.minified.sksl"
+ #endif
+ #else
+ #include "src/sksl/generated/sksl_shared.unoptimized.sksl"
+ #include "src/sksl/generated/sksl_compute.unoptimized.sksl"
+ #include "src/sksl/generated/sksl_frag.unoptimized.sksl"
+ #include "src/sksl/generated/sksl_gpu.unoptimized.sksl"
+ #include "src/sksl/generated/sksl_public.unoptimized.sksl"
+ #include "src/sksl/generated/sksl_rt_shader.unoptimized.sksl"
+ #include "src/sksl/generated/sksl_vert.unoptimized.sksl"
+ #if defined(SK_GRAPHITE)
+ #include "src/sksl/generated/sksl_graphite_frag.unoptimized.sksl"
+ #include "src/sksl/generated/sksl_graphite_vert.unoptimized.sksl"
+ #endif
+ #endif
+
+ #define MODULE_DATA(name) #name, std::string(SKSL_MINIFIED_##name)
+
+#endif
+
+namespace SkSL {
+
+#define TYPE(t) &BuiltinTypes::f ## t
+
+static constexpr BuiltinTypePtr kRootTypes[] = {
+ TYPE(Void),
+
+ TYPE( Float), TYPE( Float2), TYPE( Float3), TYPE( Float4),
+ TYPE( Half), TYPE( Half2), TYPE( Half3), TYPE( Half4),
+ TYPE( Int), TYPE( Int2), TYPE( Int3), TYPE( Int4),
+ TYPE( UInt), TYPE( UInt2), TYPE( UInt3), TYPE( UInt4),
+ TYPE( Short), TYPE( Short2), TYPE( Short3), TYPE( Short4),
+ TYPE(UShort), TYPE(UShort2), TYPE(UShort3), TYPE(UShort4),
+ TYPE( Bool), TYPE( Bool2), TYPE( Bool3), TYPE( Bool4),
+
+ TYPE(Float2x2), TYPE(Float2x3), TYPE(Float2x4),
+ TYPE(Float3x2), TYPE(Float3x3), TYPE(Float3x4),
+ TYPE(Float4x2), TYPE(Float4x3), TYPE(Float4x4),
+
+ TYPE(Half2x2), TYPE(Half2x3), TYPE(Half2x4),
+ TYPE(Half3x2), TYPE(Half3x3), TYPE(Half3x4),
+ TYPE(Half4x2), TYPE(Half4x3), TYPE(Half4x4),
+
+ TYPE(SquareMat), TYPE(SquareHMat),
+ TYPE(Mat), TYPE(HMat),
+
+ // TODO(skia:12349): generic short/ushort
+ TYPE(GenType), TYPE(GenIType), TYPE(GenUType),
+ TYPE(GenHType), /* (GenSType) (GenUSType) */
+ TYPE(GenBType),
+ TYPE(IntLiteral),
+ TYPE(FloatLiteral),
+
+ TYPE(Vec), TYPE(IVec), TYPE(UVec),
+ TYPE(HVec), TYPE(SVec), TYPE(USVec),
+ TYPE(BVec),
+
+ TYPE(ColorFilter),
+ TYPE(Shader),
+ TYPE(Blender),
+};
+
+static constexpr BuiltinTypePtr kPrivateTypes[] = {
+ TYPE(Sampler2D), TYPE(SamplerExternalOES), TYPE(Sampler2DRect),
+
+ TYPE(SubpassInput), TYPE(SubpassInputMS),
+
+ TYPE(Sampler),
+ TYPE(Texture2D),
+ TYPE(ReadWriteTexture2D), TYPE(ReadOnlyTexture2D), TYPE(WriteOnlyTexture2D),
+ TYPE(GenTexture2D), TYPE(ReadableTexture2D), TYPE(WritableTexture2D),
+
+ TYPE(AtomicUInt),
+};
+
+#undef TYPE
+
+struct ModuleLoader::Impl {
+ Impl();
+
+ void makeRootSymbolTable();
+
+ // This mutex is taken when ModuleLoader::Get is called, and released when the returned
+ // ModuleLoader object falls out of scope.
+ SkMutex fMutex;
+ const BuiltinTypes fBuiltinTypes;
+ ModifiersPool fCoreModifiers;
+
+ std::unique_ptr<const Module> fRootModule;
+
+ std::unique_ptr<const Module> fSharedModule; // [Root] + Public intrinsics
+ std::unique_ptr<const Module> fGPUModule; // [Shared] + Non-public intrinsics/
+ // helper functions
+ std::unique_ptr<const Module> fVertexModule; // [GPU] + Vertex stage decls
+ std::unique_ptr<const Module> fFragmentModule; // [GPU] + Fragment stage decls
+ std::unique_ptr<const Module> fComputeModule; // [GPU] + Compute stage decls
+ std::unique_ptr<const Module> fGraphiteVertexModule; // [Vert] + Graphite vertex helpers
+ std::unique_ptr<const Module> fGraphiteFragmentModule; // [Frag] + Graphite fragment helpers
+
+ std::unique_ptr<const Module> fPublicModule; // [Shared] minus Private types +
+ // Runtime effect intrinsics
+ std::unique_ptr<const Module> fRuntimeShaderModule; // [Public] + Runtime shader decls
+};
+
+ModuleLoader ModuleLoader::Get() {
+ static ModuleLoader::Impl* sModuleLoaderImpl = new ModuleLoader::Impl;
+ return ModuleLoader(*sModuleLoaderImpl);
+}
+
+ModuleLoader::ModuleLoader(ModuleLoader::Impl& m) : fModuleLoader(m) {
+ fModuleLoader.fMutex.acquire();
+}
+
+ModuleLoader::~ModuleLoader() {
+ fModuleLoader.fMutex.release();
+}
+
+void ModuleLoader::unloadModules() {
+ fModuleLoader.fSharedModule = nullptr;
+ fModuleLoader.fGPUModule = nullptr;
+ fModuleLoader.fVertexModule = nullptr;
+ fModuleLoader.fFragmentModule = nullptr;
+ fModuleLoader.fComputeModule = nullptr;
+ fModuleLoader.fGraphiteVertexModule = nullptr;
+ fModuleLoader.fGraphiteFragmentModule = nullptr;
+ fModuleLoader.fPublicModule = nullptr;
+ fModuleLoader.fRuntimeShaderModule = nullptr;
+}
+
+ModuleLoader::Impl::Impl() {
+ this->makeRootSymbolTable();
+}
+
+static void add_compute_type_aliases(SkSL::SymbolTable* symbols, const SkSL::BuiltinTypes& types) {
+ // A `texture2D` in a compute shader should generally mean "read-write" texture access, not
+ // "sample" texture access. Remap the name `texture2D` to point to `readWriteTexture2D`.
+ symbols->inject(Type::MakeAliasType("texture2D", *types.fReadWriteTexture2D));
+}
+
+static std::unique_ptr<Module> compile_and_shrink(SkSL::Compiler* compiler,
+ ProgramKind kind,
+ const char* moduleName,
+ std::string moduleSource,
+ const Module* parent,
+ ModifiersPool& modifiersPool) {
+ std::unique_ptr<Module> m = compiler->compileModule(kind,
+ moduleName,
+ std::move(moduleSource),
+ parent,
+ modifiersPool,
+ /*shouldInline=*/true);
+ if (!m) {
+ SK_ABORT("Unable to load module %s", moduleName);
+ }
+
+ // We can eliminate FunctionPrototypes without changing the meaning of the module; the function
+ // declaration is still safely in the symbol table. This only impacts our ability to recreate
+ // the input verbatim, which we don't care about at runtime.
+ m->fElements.erase(std::remove_if(m->fElements.begin(), m->fElements.end(),
+ [](const std::unique_ptr<ProgramElement>& element) {
+ switch (element->kind()) {
+ case ProgramElement::Kind::kFunction:
+ case ProgramElement::Kind::kGlobalVar:
+ case ProgramElement::Kind::kInterfaceBlock:
+ // We need to preserve these.
+ return false;
+
+ case ProgramElement::Kind::kFunctionPrototype:
+ // These are already in the symbol table; the
+ // ProgramElement isn't needed anymore.
+ return true;
+
+ default:
+ SkDEBUGFAILF("Unsupported element: %s\n",
+ element->description().c_str());
+ return false;
+ }
+ }),
+ m->fElements.end());
+
+ m->fElements.shrink_to_fit();
+ return m;
+}
+
+const BuiltinTypes& ModuleLoader::builtinTypes() {
+ return fModuleLoader.fBuiltinTypes;
+}
+
+ModifiersPool& ModuleLoader::coreModifiers() {
+ return fModuleLoader.fCoreModifiers;
+}
+
+const Module* ModuleLoader::rootModule() {
+ return fModuleLoader.fRootModule.get();
+}
+
+void ModuleLoader::addPublicTypeAliases(const SkSL::Module* module) {
+ const SkSL::BuiltinTypes& types = this->builtinTypes();
+ SymbolTable* symbols = module->fSymbols.get();
+
+ // Add some aliases to the runtime effect modules so that it's friendlier, and more like GLSL.
+ symbols->addWithoutOwnership(types.fVec2.get());
+ symbols->addWithoutOwnership(types.fVec3.get());
+ symbols->addWithoutOwnership(types.fVec4.get());
+
+ symbols->addWithoutOwnership(types.fIVec2.get());
+ symbols->addWithoutOwnership(types.fIVec3.get());
+ symbols->addWithoutOwnership(types.fIVec4.get());
+
+ symbols->addWithoutOwnership(types.fBVec2.get());
+ symbols->addWithoutOwnership(types.fBVec3.get());
+ symbols->addWithoutOwnership(types.fBVec4.get());
+
+ symbols->addWithoutOwnership(types.fMat2.get());
+ symbols->addWithoutOwnership(types.fMat3.get());
+ symbols->addWithoutOwnership(types.fMat4.get());
+
+ symbols->addWithoutOwnership(types.fMat2x2.get());
+ symbols->addWithoutOwnership(types.fMat2x3.get());
+ symbols->addWithoutOwnership(types.fMat2x4.get());
+ symbols->addWithoutOwnership(types.fMat3x2.get());
+ symbols->addWithoutOwnership(types.fMat3x3.get());
+ symbols->addWithoutOwnership(types.fMat3x4.get());
+ symbols->addWithoutOwnership(types.fMat4x2.get());
+ symbols->addWithoutOwnership(types.fMat4x3.get());
+ symbols->addWithoutOwnership(types.fMat4x4.get());
+
+ // Hide all the private symbols by aliasing them all to "invalid". This will prevent code from
+ // using built-in names like `sampler2D` as variable names.
+ for (BuiltinTypePtr privateType : kPrivateTypes) {
+ symbols->inject(Type::MakeAliasType((types.*privateType)->name(), *types.fInvalid));
+ }
+}
+
+const Module* ModuleLoader::loadPublicModule(SkSL::Compiler* compiler) {
+ if (!fModuleLoader.fPublicModule) {
+ const Module* sharedModule = this->loadSharedModule(compiler);
+ fModuleLoader.fPublicModule = compile_and_shrink(compiler,
+ ProgramKind::kFragment,
+ MODULE_DATA(sksl_public),
+ sharedModule,
+ this->coreModifiers());
+ this->addPublicTypeAliases(fModuleLoader.fPublicModule.get());
+ }
+ return fModuleLoader.fPublicModule.get();
+}
+
+const Module* ModuleLoader::loadPrivateRTShaderModule(SkSL::Compiler* compiler) {
+ if (!fModuleLoader.fRuntimeShaderModule) {
+ const Module* publicModule = this->loadPublicModule(compiler);
+ fModuleLoader.fRuntimeShaderModule = compile_and_shrink(compiler,
+ ProgramKind::kFragment,
+ MODULE_DATA(sksl_rt_shader),
+ publicModule,
+ this->coreModifiers());
+ }
+ return fModuleLoader.fRuntimeShaderModule.get();
+}
+
+const Module* ModuleLoader::loadSharedModule(SkSL::Compiler* compiler) {
+ if (!fModuleLoader.fSharedModule) {
+ const Module* rootModule = this->rootModule();
+ fModuleLoader.fSharedModule = compile_and_shrink(compiler,
+ ProgramKind::kFragment,
+ MODULE_DATA(sksl_shared),
+ rootModule,
+ this->coreModifiers());
+ }
+ return fModuleLoader.fSharedModule.get();
+}
+
+const Module* ModuleLoader::loadGPUModule(SkSL::Compiler* compiler) {
+ if (!fModuleLoader.fGPUModule) {
+ const Module* sharedModule = this->loadSharedModule(compiler);
+ fModuleLoader.fGPUModule = compile_and_shrink(compiler,
+ ProgramKind::kFragment,
+ MODULE_DATA(sksl_gpu),
+ sharedModule,
+ this->coreModifiers());
+ }
+ return fModuleLoader.fGPUModule.get();
+}
+
+const Module* ModuleLoader::loadFragmentModule(SkSL::Compiler* compiler) {
+ if (!fModuleLoader.fFragmentModule) {
+ const Module* gpuModule = this->loadGPUModule(compiler);
+ fModuleLoader.fFragmentModule = compile_and_shrink(compiler,
+ ProgramKind::kFragment,
+ MODULE_DATA(sksl_frag),
+ gpuModule,
+ this->coreModifiers());
+ }
+ return fModuleLoader.fFragmentModule.get();
+}
+
+const Module* ModuleLoader::loadVertexModule(SkSL::Compiler* compiler) {
+ if (!fModuleLoader.fVertexModule) {
+ const Module* gpuModule = this->loadGPUModule(compiler);
+ fModuleLoader.fVertexModule = compile_and_shrink(compiler,
+ ProgramKind::kVertex,
+ MODULE_DATA(sksl_vert),
+ gpuModule,
+ this->coreModifiers());
+ }
+ return fModuleLoader.fVertexModule.get();
+}
+
+const Module* ModuleLoader::loadComputeModule(SkSL::Compiler* compiler) {
+ if (!fModuleLoader.fComputeModule) {
+ const Module* gpuModule = this->loadGPUModule(compiler);
+ fModuleLoader.fComputeModule = compile_and_shrink(compiler,
+ ProgramKind::kCompute,
+ MODULE_DATA(sksl_compute),
+ gpuModule,
+ this->coreModifiers());
+ add_compute_type_aliases(fModuleLoader.fComputeModule->fSymbols.get(),
+ this->builtinTypes());
+ }
+ return fModuleLoader.fComputeModule.get();
+}
+
+const Module* ModuleLoader::loadGraphiteFragmentModule(SkSL::Compiler* compiler) {
+#if defined(SK_GRAPHITE)
+ if (!fModuleLoader.fGraphiteFragmentModule) {
+ const Module* fragmentModule = this->loadFragmentModule(compiler);
+ fModuleLoader.fGraphiteFragmentModule = compile_and_shrink(compiler,
+ ProgramKind::kGraphiteFragment,
+ MODULE_DATA(sksl_graphite_frag),
+ fragmentModule,
+ this->coreModifiers());
+ }
+ return fModuleLoader.fGraphiteFragmentModule.get();
+#else
+ return this->loadFragmentModule(compiler);
+#endif
+}
+
+const Module* ModuleLoader::loadGraphiteVertexModule(SkSL::Compiler* compiler) {
+#if defined(SK_GRAPHITE)
+ if (!fModuleLoader.fGraphiteVertexModule) {
+ const Module* vertexModule = this->loadVertexModule(compiler);
+ fModuleLoader.fGraphiteVertexModule = compile_and_shrink(compiler,
+ ProgramKind::kGraphiteVertex,
+ MODULE_DATA(sksl_graphite_vert),
+ vertexModule,
+ this->coreModifiers());
+ }
+ return fModuleLoader.fGraphiteVertexModule.get();
+#else
+ return this->loadVertexModule(compiler);
+#endif
+}
+
+void ModuleLoader::Impl::makeRootSymbolTable() {
+ auto rootModule = std::make_unique<Module>();
+ rootModule->fSymbols = std::make_shared<SymbolTable>(/*builtin=*/true);
+
+ for (BuiltinTypePtr rootType : kRootTypes) {
+ rootModule->fSymbols->addWithoutOwnership((fBuiltinTypes.*rootType).get());
+ }
+
+ for (BuiltinTypePtr privateType : kPrivateTypes) {
+ rootModule->fSymbols->addWithoutOwnership((fBuiltinTypes.*privateType).get());
+ }
+
+ // sk_Caps is "builtin", but all references to it are resolved to Settings, so we don't need to
+ // treat it as builtin (ie, no need to clone it into the Program).
+ rootModule->fSymbols->add(std::make_unique<Variable>(/*pos=*/Position(),
+ /*modifiersPosition=*/Position(),
+ fCoreModifiers.add(Modifiers{}),
+ "sk_Caps",
+ fBuiltinTypes.fSkCaps.get(),
+ /*builtin=*/false,
+ Variable::Storage::kGlobal));
+ fRootModule = std::move(rootModule);
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/SkSLModuleLoader.h b/gfx/skia/skia/src/sksl/SkSLModuleLoader.h
new file mode 100644
index 0000000000..bb300e2f7a
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLModuleLoader.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_MODULELOADER
+#define SKSL_MODULELOADER
+
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include <memory>
+
+namespace SkSL {
+
+class Compiler;
+class ModifiersPool;
+struct Module;
+class Type;
+
+using BuiltinTypePtr = const std::unique_ptr<Type> BuiltinTypes::*;
+
+class ModuleLoader {
+private:
+ struct Impl;
+ Impl& fModuleLoader;
+
+public:
+ ModuleLoader(ModuleLoader::Impl&);
+ ~ModuleLoader();
+
+ // Acquires a mutex-locked reference to the singleton ModuleLoader. When the ModuleLoader is
+ // allowed to fall out of scope, the mutex will be released.
+ static ModuleLoader Get();
+
+ // The built-in types and root module are universal, immutable, and shared by every Compiler.
+ // They are created when the ModuleLoader is instantiated and never change.
+ const BuiltinTypes& builtinTypes();
+ const Module* rootModule();
+
+ // This ModifiersPool is shared by every built-in module.
+ ModifiersPool& coreModifiers();
+
+ // These modules are loaded on demand; once loaded, they are kept for the lifetime of the
+ // process.
+ const Module* loadSharedModule(SkSL::Compiler* compiler);
+ const Module* loadGPUModule(SkSL::Compiler* compiler);
+ const Module* loadVertexModule(SkSL::Compiler* compiler);
+ const Module* loadFragmentModule(SkSL::Compiler* compiler);
+ const Module* loadComputeModule(SkSL::Compiler* compiler);
+ const Module* loadGraphiteVertexModule(SkSL::Compiler* compiler);
+ const Module* loadGraphiteFragmentModule(SkSL::Compiler* compiler);
+
+ const Module* loadPublicModule(SkSL::Compiler* compiler);
+ const Module* loadPrivateRTShaderModule(SkSL::Compiler* compiler);
+
+ // This updates an existing Module's symbol table to match Runtime Effect rules. GLSL types like
+ // `vec4` are added; SkSL private types like `sampler2D` are replaced with an invalid type.
+ void addPublicTypeAliases(const SkSL::Module* module);
+
+ // This unloads every module. It's useful primarily for benchmarking purposes.
+ void unloadModules();
+};
+
+} // namespace SkSL
+
+#endif // SKSL_MODULELOADER
diff --git a/gfx/skia/skia/src/sksl/SkSLOperator.cpp b/gfx/skia/skia/src/sksl/SkSLOperator.cpp
new file mode 100644
index 0000000000..6c9ddc92b4
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLOperator.cpp
@@ -0,0 +1,384 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/sksl/SkSLOperator.h"
+
+#include "include/core/SkTypes.h"
+#include "src/base/SkStringView.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/ir/SkSLType.h"
+
+#include <memory>
+
+namespace SkSL {
+
+OperatorPrecedence Operator::getBinaryPrecedence() const {
+ switch (this->kind()) {
+ case Kind::STAR: // fall through
+ case Kind::SLASH: // fall through
+ case Kind::PERCENT: return OperatorPrecedence::kMultiplicative;
+ case Kind::PLUS: // fall through
+ case Kind::MINUS: return OperatorPrecedence::kAdditive;
+ case Kind::SHL: // fall through
+ case Kind::SHR: return OperatorPrecedence::kShift;
+ case Kind::LT: // fall through
+ case Kind::GT: // fall through
+ case Kind::LTEQ: // fall through
+ case Kind::GTEQ: return OperatorPrecedence::kRelational;
+ case Kind::EQEQ: // fall through
+ case Kind::NEQ: return OperatorPrecedence::kEquality;
+ case Kind::BITWISEAND: return OperatorPrecedence::kBitwiseAnd;
+ case Kind::BITWISEXOR: return OperatorPrecedence::kBitwiseXor;
+ case Kind::BITWISEOR: return OperatorPrecedence::kBitwiseOr;
+ case Kind::LOGICALAND: return OperatorPrecedence::kLogicalAnd;
+ case Kind::LOGICALXOR: return OperatorPrecedence::kLogicalXor;
+ case Kind::LOGICALOR: return OperatorPrecedence::kLogicalOr;
+ case Kind::EQ: // fall through
+ case Kind::PLUSEQ: // fall through
+ case Kind::MINUSEQ: // fall through
+ case Kind::STAREQ: // fall through
+ case Kind::SLASHEQ: // fall through
+ case Kind::PERCENTEQ: // fall through
+ case Kind::SHLEQ: // fall through
+ case Kind::SHREQ: // fall through
+ case Kind::BITWISEANDEQ: // fall through
+ case Kind::BITWISEXOREQ: // fall through
+ case Kind::BITWISEOREQ: return OperatorPrecedence::kAssignment;
+ case Kind::COMMA: return OperatorPrecedence::kSequence;
+ default: SK_ABORT("unsupported binary operator");
+ }
+}
+
+const char* Operator::operatorName() const {
+ switch (this->kind()) {
+ case Kind::PLUS: return " + ";
+ case Kind::MINUS: return " - ";
+ case Kind::STAR: return " * ";
+ case Kind::SLASH: return " / ";
+ case Kind::PERCENT: return " % ";
+ case Kind::SHL: return " << ";
+ case Kind::SHR: return " >> ";
+ case Kind::LOGICALNOT: return "!";
+ case Kind::LOGICALAND: return " && ";
+ case Kind::LOGICALOR: return " || ";
+ case Kind::LOGICALXOR: return " ^^ ";
+ case Kind::BITWISENOT: return "~";
+ case Kind::BITWISEAND: return " & ";
+ case Kind::BITWISEOR: return " | ";
+ case Kind::BITWISEXOR: return " ^ ";
+ case Kind::EQ: return " = ";
+ case Kind::EQEQ: return " == ";
+ case Kind::NEQ: return " != ";
+ case Kind::LT: return " < ";
+ case Kind::GT: return " > ";
+ case Kind::LTEQ: return " <= ";
+ case Kind::GTEQ: return " >= ";
+ case Kind::PLUSEQ: return " += ";
+ case Kind::MINUSEQ: return " -= ";
+ case Kind::STAREQ: return " *= ";
+ case Kind::SLASHEQ: return " /= ";
+ case Kind::PERCENTEQ: return " %= ";
+ case Kind::SHLEQ: return " <<= ";
+ case Kind::SHREQ: return " >>= ";
+ case Kind::BITWISEANDEQ: return " &= ";
+ case Kind::BITWISEOREQ: return " |= ";
+ case Kind::BITWISEXOREQ: return " ^= ";
+ case Kind::PLUSPLUS: return "++";
+ case Kind::MINUSMINUS: return "--";
+ case Kind::COMMA: return ", ";
+ default: SkUNREACHABLE;
+ }
+}
+
+std::string_view Operator::tightOperatorName() const {
+ std::string_view name = this->operatorName();
+ if (skstd::starts_with(name, ' ')) {
+ name.remove_prefix(1);
+ }
+ if (skstd::ends_with(name, ' ')) {
+ name.remove_suffix(1);
+ }
+ return name;
+}
+
+bool Operator::isAssignment() const {
+ switch (this->kind()) {
+ case Kind::EQ: // fall through
+ case Kind::PLUSEQ: // fall through
+ case Kind::MINUSEQ: // fall through
+ case Kind::STAREQ: // fall through
+ case Kind::SLASHEQ: // fall through
+ case Kind::PERCENTEQ: // fall through
+ case Kind::SHLEQ: // fall through
+ case Kind::SHREQ: // fall through
+ case Kind::BITWISEOREQ: // fall through
+ case Kind::BITWISEXOREQ: // fall through
+ case Kind::BITWISEANDEQ:
+ return true;
+ default:
+ return false;
+ }
+}
+
+Operator Operator::removeAssignment() const {
+ switch (this->kind()) {
+ case Kind::PLUSEQ: return Kind::PLUS;
+ case Kind::MINUSEQ: return Kind::MINUS;
+ case Kind::STAREQ: return Kind::STAR;
+ case Kind::SLASHEQ: return Kind::SLASH;
+ case Kind::PERCENTEQ: return Kind::PERCENT;
+ case Kind::SHLEQ: return Kind::SHL;
+ case Kind::SHREQ: return Kind::SHR;
+ case Kind::BITWISEOREQ: return Kind::BITWISEOR;
+ case Kind::BITWISEXOREQ: return Kind::BITWISEXOR;
+ case Kind::BITWISEANDEQ: return Kind::BITWISEAND;
+ default: return *this;
+ }
+}
+
+bool Operator::isRelational() const {
+ switch (this->kind()) {
+ case Kind::LT:
+ case Kind::GT:
+ case Kind::LTEQ:
+ case Kind::GTEQ:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool Operator::isOnlyValidForIntegralTypes() const {
+ switch (this->kind()) {
+ case Kind::SHL:
+ case Kind::SHR:
+ case Kind::BITWISEAND:
+ case Kind::BITWISEOR:
+ case Kind::BITWISEXOR:
+ case Kind::PERCENT:
+ case Kind::SHLEQ:
+ case Kind::SHREQ:
+ case Kind::BITWISEANDEQ:
+ case Kind::BITWISEOREQ:
+ case Kind::BITWISEXOREQ:
+ case Kind::PERCENTEQ:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool Operator::isValidForMatrixOrVector() const {
+ switch (this->kind()) {
+ case Kind::PLUS:
+ case Kind::MINUS:
+ case Kind::STAR:
+ case Kind::SLASH:
+ case Kind::PERCENT:
+ case Kind::SHL:
+ case Kind::SHR:
+ case Kind::BITWISEAND:
+ case Kind::BITWISEOR:
+ case Kind::BITWISEXOR:
+ case Kind::PLUSEQ:
+ case Kind::MINUSEQ:
+ case Kind::STAREQ:
+ case Kind::SLASHEQ:
+ case Kind::PERCENTEQ:
+ case Kind::SHLEQ:
+ case Kind::SHREQ:
+ case Kind::BITWISEANDEQ:
+ case Kind::BITWISEOREQ:
+ case Kind::BITWISEXOREQ:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool Operator::isMatrixMultiply(const Type& left, const Type& right) const {
+ if (this->kind() != Kind::STAR && this->kind() != Kind::STAREQ) {
+ return false;
+ }
+ if (left.isMatrix()) {
+ return right.isMatrix() || right.isVector();
+ }
+ return left.isVector() && right.isMatrix();
+}
+
+/**
+ * Determines the operand and result types of a binary expression. Returns true if the expression is
+ * legal, false otherwise. If false, the values of the out parameters are undefined.
+ */
+bool Operator::determineBinaryType(const Context& context,
+ const Type& left,
+ const Type& right,
+ const Type** outLeftType,
+ const Type** outRightType,
+ const Type** outResultType) const {
+ const bool allowNarrowing = context.fConfig->fSettings.fAllowNarrowingConversions;
+ switch (this->kind()) {
+ case Kind::EQ: // left = right
+ if (left.isVoid()) {
+ return false;
+ }
+ *outLeftType = &left;
+ *outRightType = &left;
+ *outResultType = &left;
+ return right.canCoerceTo(left, allowNarrowing);
+
+ case Kind::EQEQ: // left == right
+ case Kind::NEQ: { // left != right
+ if (left.isVoid() || left.isOpaque()) {
+ return false;
+ }
+ CoercionCost rightToLeft = right.coercionCost(left),
+ leftToRight = left.coercionCost(right);
+ if (rightToLeft < leftToRight) {
+ if (rightToLeft.isPossible(allowNarrowing)) {
+ *outLeftType = &left;
+ *outRightType = &left;
+ *outResultType = context.fTypes.fBool.get();
+ return true;
+ }
+ } else {
+ if (leftToRight.isPossible(allowNarrowing)) {
+ *outLeftType = &right;
+ *outRightType = &right;
+ *outResultType = context.fTypes.fBool.get();
+ return true;
+ }
+ }
+ return false;
+ }
+ case Kind::LOGICALOR: // left || right
+ case Kind::LOGICALAND: // left && right
+ case Kind::LOGICALXOR: // left ^^ right
+ *outLeftType = context.fTypes.fBool.get();
+ *outRightType = context.fTypes.fBool.get();
+ *outResultType = context.fTypes.fBool.get();
+ return left.canCoerceTo(*context.fTypes.fBool, allowNarrowing) &&
+ right.canCoerceTo(*context.fTypes.fBool, allowNarrowing);
+
+ case Operator::Kind::COMMA: // left, right
+ if (left.isOpaque() || right.isOpaque()) {
+ return false;
+ }
+ *outLeftType = &left;
+ *outRightType = &right;
+ *outResultType = &right;
+ return true;
+
+ default:
+ break;
+ }
+
+ // Boolean types only support the operators listed above (, = == != || && ^^).
+ // If we've gotten this far with a boolean, we have an unsupported operator.
+ const Type& leftComponentType = left.componentType();
+ const Type& rightComponentType = right.componentType();
+ if (leftComponentType.isBoolean() || rightComponentType.isBoolean()) {
+ return false;
+ }
+
+ bool isAssignment = this->isAssignment();
+ if (this->isMatrixMultiply(left, right)) { // left * right
+ // Determine final component type.
+ if (!this->determineBinaryType(context, left.componentType(), right.componentType(),
+ outLeftType, outRightType, outResultType)) {
+ return false;
+ }
+ // Convert component type to compound.
+ *outLeftType = &(*outResultType)->toCompound(context, left.columns(), left.rows());
+ *outRightType = &(*outResultType)->toCompound(context, right.columns(), right.rows());
+ int leftColumns = left.columns(), leftRows = left.rows();
+ int rightColumns = right.columns(), rightRows = right.rows();
+ if (right.isVector()) {
+ // `matrix * vector` treats the vector as a column vector; we need to transpose it.
+ std::swap(rightColumns, rightRows);
+ SkASSERT(rightColumns == 1);
+ }
+ if (rightColumns > 1) {
+ *outResultType = &(*outResultType)->toCompound(context, rightColumns, leftRows);
+ } else {
+ // The result was a column vector. Transpose it back to a row.
+ *outResultType = &(*outResultType)->toCompound(context, leftRows, rightColumns);
+ }
+ if (isAssignment && ((*outResultType)->columns() != leftColumns ||
+ (*outResultType)->rows() != leftRows)) {
+ return false;
+ }
+ return leftColumns == rightRows;
+ }
+
+ bool leftIsVectorOrMatrix = left.isVector() || left.isMatrix();
+ bool validMatrixOrVectorOp = this->isValidForMatrixOrVector();
+
+ if (leftIsVectorOrMatrix && validMatrixOrVectorOp && right.isScalar()) {
+ // Determine final component type.
+ if (!this->determineBinaryType(context, left.componentType(), right,
+ outLeftType, outRightType, outResultType)) {
+ return false;
+ }
+ // Convert component type to compound.
+ *outLeftType = &(*outLeftType)->toCompound(context, left.columns(), left.rows());
+ if (!this->isRelational()) {
+ *outResultType = &(*outResultType)->toCompound(context, left.columns(), left.rows());
+ }
+ return true;
+ }
+
+ bool rightIsVectorOrMatrix = right.isVector() || right.isMatrix();
+
+ if (!isAssignment && rightIsVectorOrMatrix && validMatrixOrVectorOp && left.isScalar()) {
+ // Determine final component type.
+ if (!this->determineBinaryType(context, left, right.componentType(),
+ outLeftType, outRightType, outResultType)) {
+ return false;
+ }
+ // Convert component type to compound.
+ *outRightType = &(*outRightType)->toCompound(context, right.columns(), right.rows());
+ if (!this->isRelational()) {
+ *outResultType = &(*outResultType)->toCompound(context, right.columns(), right.rows());
+ }
+ return true;
+ }
+
+ CoercionCost rightToLeftCost = right.coercionCost(left);
+ CoercionCost leftToRightCost = isAssignment ? CoercionCost::Impossible()
+ : left.coercionCost(right);
+
+ if ((left.isScalar() && right.isScalar()) || (leftIsVectorOrMatrix && validMatrixOrVectorOp)) {
+ if (this->isOnlyValidForIntegralTypes()) {
+ if (!leftComponentType.isInteger() || !rightComponentType.isInteger()) {
+ return false;
+ }
+ }
+ if (rightToLeftCost.isPossible(allowNarrowing) && rightToLeftCost < leftToRightCost) {
+ // Right-to-Left conversion is possible and cheaper
+ *outLeftType = &left;
+ *outRightType = &left;
+ *outResultType = &left;
+ } else if (leftToRightCost.isPossible(allowNarrowing)) {
+ // Left-to-Right conversion is possible (and at least as cheap as Right-to-Left)
+ *outLeftType = &right;
+ *outRightType = &right;
+ *outResultType = &right;
+ } else {
+ return false;
+ }
+ if (this->isRelational()) {
+ *outResultType = context.fTypes.fBool.get();
+ }
+ return true;
+ }
+ return false;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/SkSLOutputStream.cpp b/gfx/skia/skia/src/sksl/SkSLOutputStream.cpp
new file mode 100644
index 0000000000..7972c9fd19
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLOutputStream.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/SkSLOutputStream.h"
+
+#include <stdio.h>
+#include <memory>
+
+namespace SkSL {
+
+void OutputStream::writeString(const std::string& s) {
+ this->write(s.c_str(), s.size());
+}
+
+void OutputStream::printf(const char format[], ...) {
+ va_list args;
+ va_start(args, format);
+ this->appendVAList(format, args);
+ va_end(args);
+}
+
+void OutputStream::appendVAList(const char format[], va_list args) {
+ char buffer[kBufferSize];
+ va_list copy;
+ va_copy(copy, args);
+ int length = vsnprintf(buffer, kBufferSize, format, args);
+ if (length > (int) kBufferSize) {
+ std::unique_ptr<char[]> bigBuffer(new char[length + 1]);
+ vsnprintf(bigBuffer.get(), length + 1, format, copy);
+ this->write(bigBuffer.get(), length);
+ } else {
+ this->write(buffer, length);
+ }
+ va_end(copy);
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/SkSLOutputStream.h b/gfx/skia/skia/src/sksl/SkSLOutputStream.h
new file mode 100644
index 0000000000..542ffd6f90
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLOutputStream.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_OUTPUTSTREAM
+#define SKSL_OUTPUTSTREAM
+
+#include "include/core/SkTypes.h"
+
+#include <cstdarg>
+#include <cstddef>
+#include <cstdint>
+#include <string>
+
+namespace SkSL {
+
+class OutputStream {
+public:
+ virtual bool isValid() const {
+ return true;
+ }
+
+ virtual void write8(uint8_t b) = 0;
+
+ void write16(uint16_t i) {
+ this->write8((uint8_t) i);
+ this->write8((uint8_t) (i >> 8));
+ }
+
+ void write32(uint32_t i) {
+ this->write8((uint8_t) i);
+ this->write8((uint8_t) (i >> 8));
+ this->write8((uint8_t) (i >> 16));
+ this->write8((uint8_t) (i >> 24));
+ }
+
+ virtual void writeText(const char* s) = 0;
+
+ virtual void write(const void* s, size_t size) = 0;
+
+ void writeString(const std::string& s);
+
+ void printf(const char format[], ...) SK_PRINTF_LIKE(2, 3);
+
+ void appendVAList(const char format[], va_list args) SK_PRINTF_LIKE(2, 0);
+
+ virtual ~OutputStream() {}
+
+private:
+ static const int kBufferSize = 1024;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/SkSLParser.cpp b/gfx/skia/skia/src/sksl/SkSLParser.cpp
new file mode 100644
index 0000000000..d63f930a63
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLParser.cpp
@@ -0,0 +1,2248 @@
+/*
+ * Copyright 2021 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/SkSLParser.h"
+
+#include "include/core/SkSpan.h"
+#include "include/private/SkSLModifiers.h"
+#include "include/private/SkSLProgramElement.h"
+#include "include/private/SkSLString.h"
+#include "include/sksl/DSLBlock.h"
+#include "include/sksl/DSLCase.h"
+#include "include/sksl/DSLFunction.h"
+#include "include/sksl/DSLVar.h"
+#include "include/sksl/SkSLOperator.h"
+#include "include/sksl/SkSLVersion.h"
+#include "src/core/SkTHash.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLConstantFolder.h"
+#include "src/sksl/SkSLThreadContext.h"
+#include "src/sksl/dsl/priv/DSLWriter.h"
+#include "src/sksl/dsl/priv/DSL_priv.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLProgram.h"
+#include "src/sksl/ir/SkSLSymbolTable.h"
+#include "src/sksl/ir/SkSLVariable.h"
+
+#include <algorithm>
+#include <climits>
+#include <initializer_list>
+#include <memory>
+#include <utility>
+#include <vector>
+
+using namespace SkSL::dsl;
+
+namespace SkSL {
+
+static constexpr int kMaxParseDepth = 50;
+
+static int parse_modifier_token(Token::Kind token) {
+ switch (token) {
+ case Token::Kind::TK_UNIFORM: return Modifiers::kUniform_Flag;
+ case Token::Kind::TK_CONST: return Modifiers::kConst_Flag;
+ case Token::Kind::TK_IN: return Modifiers::kIn_Flag;
+ case Token::Kind::TK_OUT: return Modifiers::kOut_Flag;
+ case Token::Kind::TK_INOUT: return Modifiers::kIn_Flag | Modifiers::kOut_Flag;
+ case Token::Kind::TK_FLAT: return Modifiers::kFlat_Flag;
+ case Token::Kind::TK_NOPERSPECTIVE: return Modifiers::kNoPerspective_Flag;
+ case Token::Kind::TK_PURE: return Modifiers::kPure_Flag;
+ case Token::Kind::TK_INLINE: return Modifiers::kInline_Flag;
+ case Token::Kind::TK_NOINLINE: return Modifiers::kNoInline_Flag;
+ case Token::Kind::TK_HIGHP: return Modifiers::kHighp_Flag;
+ case Token::Kind::TK_MEDIUMP: return Modifiers::kMediump_Flag;
+ case Token::Kind::TK_LOWP: return Modifiers::kLowp_Flag;
+ case Token::Kind::TK_EXPORT: return Modifiers::kExport_Flag;
+ case Token::Kind::TK_ES3: return Modifiers::kES3_Flag;
+ case Token::Kind::TK_WORKGROUP: return Modifiers::kWorkgroup_Flag;
+ case Token::Kind::TK_READONLY: return Modifiers::kReadOnly_Flag;
+ case Token::Kind::TK_WRITEONLY: return Modifiers::kWriteOnly_Flag;
+ case Token::Kind::TK_BUFFER: return Modifiers::kBuffer_Flag;
+ default: return 0;
+ }
+}
+
+class Parser::AutoDepth {
+public:
+ AutoDepth(Parser* p)
+ : fParser(p)
+ , fDepth(0) {}
+
+ ~AutoDepth() {
+ fParser->fDepth -= fDepth;
+ }
+
+ bool increase() {
+ ++fDepth;
+ ++fParser->fDepth;
+ if (fParser->fDepth > kMaxParseDepth) {
+ fParser->error(fParser->peek(), "exceeded max parse depth");
+ fParser->fEncounteredFatalError = true;
+ return false;
+ }
+ return true;
+ }
+
+private:
+ Parser* fParser;
+ int fDepth;
+};
+
+class Parser::AutoSymbolTable {
+public:
+ AutoSymbolTable(Parser* p) : fParser(p) {
+ SymbolTable::Push(&fParser->symbolTable());
+ }
+
+ ~AutoSymbolTable() {
+ SymbolTable::Pop(&fParser->symbolTable());
+ }
+
+private:
+ Parser* fParser;
+};
+
+Parser::Parser(Compiler* compiler,
+ const ProgramSettings& settings,
+ ProgramKind kind,
+ std::string text)
+ : fCompiler(*compiler)
+ , fSettings(settings)
+ , fKind(kind)
+ , fText(std::make_unique<std::string>(std::move(text)))
+ , fPushback(Token::Kind::TK_NONE, /*offset=*/-1, /*length=*/-1) {
+ fLexer.start(*fText);
+}
+
+std::shared_ptr<SymbolTable>& Parser::symbolTable() {
+ return fCompiler.symbolTable();
+}
+
+void Parser::addToSymbolTable(DSLVarBase& var, Position pos) {
+ if (SkSL::Variable* skslVar = DSLWriter::Var(var)) {
+ this->symbolTable()->addWithoutOwnership(skslVar);
+ }
+}
+
+Token Parser::nextRawToken() {
+ Token token;
+ if (fPushback.fKind != Token::Kind::TK_NONE) {
+ // Retrieve the token from the pushback buffer.
+ token = fPushback;
+ fPushback.fKind = Token::Kind::TK_NONE;
+ } else {
+ // Fetch a token from the lexer.
+ token = fLexer.next();
+
+ // Some tokens are always invalid, so we detect and report them here.
+ switch (token.fKind) {
+ case Token::Kind::TK_PRIVATE_IDENTIFIER:
+ if (ProgramConfig::AllowsPrivateIdentifiers(fKind)) {
+ token.fKind = Token::Kind::TK_IDENTIFIER;
+ break;
+ }
+ [[fallthrough]];
+
+ case Token::Kind::TK_RESERVED:
+ this->error(token, "name '" + std::string(this->text(token)) + "' is reserved");
+ token.fKind = Token::Kind::TK_IDENTIFIER; // reduces additional follow-up errors
+ break;
+
+ case Token::Kind::TK_BAD_OCTAL:
+ this->error(token, "'" + std::string(this->text(token)) +
+ "' is not a valid octal number");
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return token;
+}
+
+static bool is_whitespace(Token::Kind kind) {
+ switch (kind) {
+ case Token::Kind::TK_WHITESPACE:
+ case Token::Kind::TK_LINE_COMMENT:
+ case Token::Kind::TK_BLOCK_COMMENT:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+bool Parser::expectNewline() {
+ Token token = this->nextRawToken();
+ if (token.fKind == Token::Kind::TK_WHITESPACE) {
+ // The lexer doesn't distinguish newlines from other forms of whitespace, so we check
+ // for newlines by searching through the token text.
+ std::string_view tokenText = this->text(token);
+ if (tokenText.find_first_of('\r') != std::string_view::npos ||
+ tokenText.find_first_of('\n') != std::string_view::npos) {
+ return true;
+ }
+ }
+ // We didn't find a newline.
+ this->pushback(token);
+ return false;
+}
+
+Token Parser::nextToken() {
+ for (;;) {
+ Token token = this->nextRawToken();
+ if (!is_whitespace(token.fKind)) {
+ return token;
+ }
+ }
+}
+
+void Parser::pushback(Token t) {
+ SkASSERT(fPushback.fKind == Token::Kind::TK_NONE);
+ fPushback = std::move(t);
+}
+
+Token Parser::peek() {
+ if (fPushback.fKind == Token::Kind::TK_NONE) {
+ fPushback = this->nextToken();
+ }
+ return fPushback;
+}
+
+bool Parser::checkNext(Token::Kind kind, Token* result) {
+ if (fPushback.fKind != Token::Kind::TK_NONE && fPushback.fKind != kind) {
+ return false;
+ }
+ Token next = this->nextToken();
+ if (next.fKind == kind) {
+ if (result) {
+ *result = next;
+ }
+ return true;
+ }
+ this->pushback(std::move(next));
+ return false;
+}
+
+bool Parser::expect(Token::Kind kind, const char* expected, Token* result) {
+ Token next = this->nextToken();
+ if (next.fKind == kind) {
+ if (result) {
+ *result = std::move(next);
+ }
+ return true;
+ } else {
+ this->error(next, "expected " + std::string(expected) + ", but found '" +
+ std::string(this->text(next)) + "'");
+ this->fEncounteredFatalError = true;
+ return false;
+ }
+}
+
+bool Parser::expectIdentifier(Token* result) {
+ if (!this->expect(Token::Kind::TK_IDENTIFIER, "an identifier", result)) {
+ return false;
+ }
+ if (this->symbolTable()->isBuiltinType(this->text(*result))) {
+ this->error(*result, "expected an identifier, but found type '" +
+ std::string(this->text(*result)) + "'");
+ this->fEncounteredFatalError = true;
+ return false;
+ }
+ return true;
+}
+
+bool Parser::checkIdentifier(Token* result) {
+ if (!this->checkNext(Token::Kind::TK_IDENTIFIER, result)) {
+ return false;
+ }
+ if (this->symbolTable()->isBuiltinType(this->text(*result))) {
+ this->pushback(std::move(*result));
+ return false;
+ }
+ return true;
+}
+
+std::string_view Parser::text(Token token) {
+ return std::string_view(fText->data() + token.fOffset, token.fLength);
+}
+
+Position Parser::position(Token t) {
+ if (t.fOffset >= 0) {
+ return Position::Range(t.fOffset, t.fOffset + t.fLength);
+ } else {
+ return Position();
+ }
+}
+
+void Parser::error(Token token, std::string_view msg) {
+ this->error(this->position(token), msg);
+}
+
+void Parser::error(Position position, std::string_view msg) {
+ GetErrorReporter().error(position, msg);
+}
+
+Position Parser::rangeFrom(Position start) {
+ int offset = fPushback.fKind != Token::Kind::TK_NONE ? fPushback.fOffset
+ : fLexer.getCheckpoint().fOffset;
+ return Position::Range(start.startOffset(), offset);
+}
+
+Position Parser::rangeFrom(Token start) {
+ return this->rangeFrom(this->position(start));
+}
+
+/* declaration* END_OF_FILE */
+std::unique_ptr<Program> Parser::program() {
+ ErrorReporter* errorReporter = &fCompiler.errorReporter();
+ Start(&fCompiler, fKind, fSettings);
+ SetErrorReporter(errorReporter);
+ errorReporter->setSource(*fText);
+ this->declarations();
+ std::unique_ptr<Program> result;
+ if (!GetErrorReporter().errorCount()) {
+ result = dsl::ReleaseProgram(std::move(fText));
+ }
+ errorReporter->setSource(std::string_view());
+ End();
+ return result;
+}
+
+std::unique_ptr<SkSL::Module> Parser::moduleInheritingFrom(const SkSL::Module* parent) {
+ ErrorReporter* errorReporter = &fCompiler.errorReporter();
+ StartModule(&fCompiler, fKind, fSettings, parent);
+ SetErrorReporter(errorReporter);
+ errorReporter->setSource(*fText);
+ this->declarations();
+ this->symbolTable()->takeOwnershipOfString(std::move(*fText));
+ auto result = std::make_unique<SkSL::Module>();
+ result->fParent = parent;
+ result->fSymbols = this->symbolTable();
+ result->fElements = std::move(ThreadContext::ProgramElements());
+ errorReporter->setSource(std::string_view());
+ End();
+ return result;
+}
+
+void Parser::declarations() {
+ fEncounteredFatalError = false;
+ // Any #version directive must appear as the first thing in a file
+ if (this->peek().fKind == Token::Kind::TK_DIRECTIVE) {
+ this->directive(/*allowVersion=*/true);
+ }
+ bool done = false;
+ while (!done) {
+ switch (this->peek().fKind) {
+ case Token::Kind::TK_END_OF_FILE:
+ done = true;
+ break;
+ case Token::Kind::TK_DIRECTIVE:
+ this->directive(/*allowVersion=*/false);
+ break;
+ case Token::Kind::TK_INVALID:
+ this->error(this->peek(), "invalid token");
+ this->nextToken();
+ done = true;
+ break;
+ default:
+ this->declaration();
+ done = fEncounteredFatalError;
+ break;
+ }
+ }
+}
+
+/* DIRECTIVE(#extension) IDENTIFIER COLON IDENTIFIER NEWLINE |
+ DIRECTIVE(#version) INTLITERAL NEWLINE */
+void Parser::directive(bool allowVersion) {
+ Token start;
+ if (!this->expect(Token::Kind::TK_DIRECTIVE, "a directive", &start)) {
+ return;
+ }
+ std::string_view text = this->text(start);
+ const bool allowExtensions = !ProgramConfig::IsRuntimeEffect(fKind);
+ if (text == "#extension" && allowExtensions) {
+ Token name;
+ if (!this->expectIdentifier(&name)) {
+ return;
+ }
+ if (!this->expect(Token::Kind::TK_COLON, "':'")) {
+ return;
+ }
+ Token behavior;
+ if (!this->expect(Token::Kind::TK_IDENTIFIER, "an identifier", &behavior)) {
+ return;
+ }
+ std::string_view behaviorText = this->text(behavior);
+ if (behaviorText != "disable") {
+ if (behaviorText == "require" || behaviorText == "enable" || behaviorText == "warn") {
+ // We don't currently do anything different between require, enable, and warn
+ dsl::AddExtension(this->text(name));
+ } else {
+ this->error(behavior, "expected 'require', 'enable', 'warn', or 'disable'");
+ }
+ }
+
+ // We expect a newline after an #extension directive.
+ if (!this->expectNewline()) {
+ this->error(start, "invalid #extension directive");
+ }
+ } else if (text == "#version") {
+ if (!allowVersion) {
+ this->error(start, "#version directive must appear before anything else");
+ return;
+ }
+ SKSL_INT version;
+ if (!this->intLiteral(&version)) {
+ return;
+ }
+ switch (version) {
+ case 100:
+ ThreadContext::GetProgramConfig()->fRequiredSkSLVersion = Version::k100;
+ break;
+ case 300:
+ ThreadContext::GetProgramConfig()->fRequiredSkSLVersion = Version::k300;
+ break;
+ default:
+ this->error(start, "unsupported version number");
+ return;
+ }
+ // We expect a newline after a #version directive.
+ if (!this->expectNewline()) {
+ this->error(start, "invalid #version directive");
+ }
+ } else {
+ this->error(start, "unsupported directive '" + std::string(this->text(start)) + "'");
+ }
+}
+
+/* modifiers (structVarDeclaration | type IDENTIFIER ((LPAREN parameter (COMMA parameter)* RPAREN
+ (block | SEMICOLON)) | SEMICOLON) | interfaceBlock) */
+bool Parser::declaration() {
+ Token start = this->peek();
+ if (start.fKind == Token::Kind::TK_SEMICOLON) {
+ this->nextToken();
+ this->error(start, "expected a declaration, but found ';'");
+ return false;
+ }
+ DSLModifiers modifiers = this->modifiers();
+ Token lookahead = this->peek();
+ if (lookahead.fKind == Token::Kind::TK_IDENTIFIER &&
+ !this->symbolTable()->isType(this->text(lookahead))) {
+ // we have an identifier that's not a type, could be the start of an interface block
+ return this->interfaceBlock(modifiers);
+ }
+ if (lookahead.fKind == Token::Kind::TK_SEMICOLON) {
+ this->nextToken();
+ Declare(modifiers, this->position(start));
+ return true;
+ }
+ if (lookahead.fKind == Token::Kind::TK_STRUCT) {
+ this->structVarDeclaration(this->position(start), modifiers);
+ return true;
+ }
+ DSLType type = this->type(&modifiers);
+ if (!type.hasValue()) {
+ return false;
+ }
+ Token name;
+ if (!this->expectIdentifier(&name)) {
+ return false;
+ }
+ if (this->checkNext(Token::Kind::TK_LPAREN)) {
+ return this->functionDeclarationEnd(this->position(start), modifiers, type, name);
+ } else {
+ this->globalVarDeclarationEnd(this->position(start), modifiers, type, name);
+ return true;
+ }
+}
+
+/* (RPAREN | VOID RPAREN | parameter (COMMA parameter)* RPAREN) (block | SEMICOLON) */
+bool Parser::functionDeclarationEnd(Position start,
+ DSLModifiers& modifiers,
+ DSLType type,
+ const Token& name) {
+ SkSTArray<8, DSLParameter> parameters;
+ Token lookahead = this->peek();
+ if (lookahead.fKind == Token::Kind::TK_RPAREN) {
+ // `()` means no parameters at all.
+ } else if (lookahead.fKind == Token::Kind::TK_IDENTIFIER && this->text(lookahead) == "void") {
+ // `(void)` also means no parameters at all.
+ this->nextToken();
+ } else {
+ for (;;) {
+ size_t paramIndex = parameters.size();
+ std::optional<DSLParameter> parameter = this->parameter(paramIndex);
+ if (!parameter) {
+ return false;
+ }
+ parameters.push_back(std::move(*parameter));
+ if (!this->checkNext(Token::Kind::TK_COMMA)) {
+ break;
+ }
+ }
+ }
+ if (!this->expect(Token::Kind::TK_RPAREN, "')'")) {
+ return false;
+ }
+ SkSTArray<8, DSLParameter*> parameterPointers;
+ parameterPointers.reserve_back(parameters.size());
+ for (DSLParameter& param : parameters) {
+ parameterPointers.push_back(&param);
+ }
+
+ DSLFunction result(this->text(name), modifiers, type, parameterPointers,
+ this->rangeFrom(start));
+
+ const bool hasFunctionBody = !this->checkNext(Token::Kind::TK_SEMICOLON);
+ if (hasFunctionBody) {
+ AutoSymbolTable symbols(this);
+ for (DSLParameter* var : parameterPointers) {
+ if (!var->name().empty()) {
+ this->addToSymbolTable(*var);
+ }
+ }
+ Token bodyStart = this->peek();
+ std::optional<DSLBlock> body = this->block();
+ if (!body) {
+ return false;
+ }
+ result.define(std::move(*body), this->rangeFrom(bodyStart));
+ } else {
+ result.prototype();
+ }
+ return true;
+}
+
+bool Parser::arraySize(SKSL_INT* outResult) {
+ // Start out with a safe value that won't generate any errors downstream
+ *outResult = 1;
+ Token next = this->peek();
+ if (next.fKind == Token::Kind::TK_RBRACKET) {
+ this->error(this->position(next), "unsized arrays are not permitted here");
+ return true;
+ }
+ DSLExpression sizeExpr = this->expression();
+ if (!sizeExpr.hasValue()) {
+ return false;
+ }
+ if (sizeExpr.isValid()) {
+ std::unique_ptr<SkSL::Expression> sizeLiteral = sizeExpr.release();
+ SKSL_INT size;
+ if (!ConstantFolder::GetConstantInt(*sizeLiteral, &size)) {
+ this->error(sizeLiteral->fPosition, "array size must be an integer");
+ return true;
+ }
+ if (size > INT32_MAX) {
+ this->error(sizeLiteral->fPosition, "array size out of bounds");
+ return true;
+ }
+ if (size <= 0) {
+ this->error(sizeLiteral->fPosition, "array size must be positive");
+ return true;
+ }
+ // Now that we've validated it, output the real value
+ *outResult = size;
+ }
+ return true;
+}
+
+bool Parser::parseArrayDimensions(Position pos, DSLType* type) {
+ Token next;
+ while (this->checkNext(Token::Kind::TK_LBRACKET, &next)) {
+ if (this->checkNext(Token::Kind::TK_RBRACKET)) {
+ if (this->allowUnsizedArrays()) {
+ *type = UnsizedArray(*type, this->rangeFrom(pos));
+ } else {
+ this->error(this->rangeFrom(pos), "unsized arrays are not permitted here");
+ }
+ } else {
+ SKSL_INT size;
+ if (!this->arraySize(&size)) {
+ return false;
+ }
+ if (!this->expect(Token::Kind::TK_RBRACKET, "']'")) {
+ return false;
+ }
+ *type = Array(*type, size, this->rangeFrom(pos));
+ }
+ }
+ return true;
+}
+
+bool Parser::parseInitializer(Position pos, DSLExpression* initializer) {
+ if (this->checkNext(Token::Kind::TK_EQ)) {
+ DSLExpression value = this->assignmentExpression();
+ if (!value.hasValue()) {
+ return false;
+ }
+ initializer->swap(value);
+ }
+ return true;
+}
+
+/* (LBRACKET expression? RBRACKET)* (EQ assignmentExpression)? (COMMA IDENTIFER
+ (LBRACKET expression? RBRACKET)* (EQ assignmentExpression)?)* SEMICOLON */
+void Parser::globalVarDeclarationEnd(Position pos,
+ const dsl::DSLModifiers& mods,
+ dsl::DSLType baseType,
+ Token name) {
+ using namespace dsl;
+ DSLType type = baseType;
+ DSLExpression initializer;
+ if (!this->parseArrayDimensions(pos, &type)) {
+ return;
+ }
+ if (!this->parseInitializer(pos, &initializer)) {
+ return;
+ }
+ DSLGlobalVar first(mods, type, this->text(name), std::move(initializer), this->rangeFrom(pos),
+ this->position(name));
+ Declare(first);
+ this->addToSymbolTable(first);
+
+ while (this->checkNext(Token::Kind::TK_COMMA)) {
+ type = baseType;
+ Token identifierName;
+ if (!this->expectIdentifier(&identifierName)) {
+ return;
+ }
+ if (!this->parseArrayDimensions(pos, &type)) {
+ return;
+ }
+ DSLExpression anotherInitializer;
+ if (!this->parseInitializer(pos, &anotherInitializer)) {
+ return;
+ }
+ DSLGlobalVar next(mods, type, this->text(identifierName), std::move(anotherInitializer),
+ this->rangeFrom(identifierName));
+ Declare(next);
+ this->addToSymbolTable(next, this->position(identifierName));
+ }
+ this->expect(Token::Kind::TK_SEMICOLON, "';'");
+}
+
+/* (LBRACKET expression? RBRACKET)* (EQ assignmentExpression)? (COMMA IDENTIFER
+ (LBRACKET expression? RBRACKET)* (EQ assignmentExpression)?)* SEMICOLON */
+DSLStatement Parser::localVarDeclarationEnd(Position pos,
+ const dsl::DSLModifiers& mods,
+ dsl::DSLType baseType,
+ Token name) {
+ using namespace dsl;
+ DSLType type = baseType;
+ DSLExpression initializer;
+ if (!this->parseArrayDimensions(pos, &type)) {
+ return {};
+ }
+ if (!this->parseInitializer(pos, &initializer)) {
+ return {};
+ }
+ DSLVar first(mods, type, this->text(name), std::move(initializer), this->rangeFrom(pos),
+ this->position(name));
+ DSLStatement result = Declare(first);
+ this->addToSymbolTable(first);
+
+ while (this->checkNext(Token::Kind::TK_COMMA)) {
+ type = baseType;
+ Token identifierName;
+ if (!this->expectIdentifier(&identifierName)) {
+ return result;
+ }
+ if (!this->parseArrayDimensions(pos, &type)) {
+ return result;
+ }
+ DSLExpression anotherInitializer;
+ if (!this->parseInitializer(pos, &anotherInitializer)) {
+ return result;
+ }
+ DSLVar next(mods, type, this->text(identifierName), std::move(anotherInitializer),
+ this->rangeFrom(identifierName), this->position(identifierName));
+ DSLWriter::AddVarDeclaration(result, next);
+ this->addToSymbolTable(next, this->position(identifierName));
+ }
+ this->expect(Token::Kind::TK_SEMICOLON, "';'");
+ result.setPosition(this->rangeFrom(pos));
+ return result;
+}
+
+/* (varDeclarations | expressionStatement) */
+DSLStatement Parser::varDeclarationsOrExpressionStatement() {
+ Token nextToken = this->peek();
+ if (nextToken.fKind == Token::Kind::TK_CONST) {
+ // Statements that begin with `const` might be variable declarations, but can't be legal
+ // SkSL expression-statements. (SkSL constructors don't take a `const` modifier.)
+ return this->varDeclarations();
+ }
+
+ if (nextToken.fKind == Token::Kind::TK_HIGHP ||
+ nextToken.fKind == Token::Kind::TK_MEDIUMP ||
+ nextToken.fKind == Token::Kind::TK_LOWP ||
+ this->symbolTable()->isType(this->text(nextToken))) {
+ // Statements that begin with a typename are most often variable declarations, but
+ // occasionally the type is part of a constructor, and these are actually expression-
+ // statements in disguise. First, attempt the common case: parse it as a vardecl.
+ Checkpoint checkpoint(this);
+ VarDeclarationsPrefix prefix;
+ if (this->varDeclarationsPrefix(&prefix)) {
+ checkpoint.accept();
+ return this->localVarDeclarationEnd(prefix.fPosition, prefix.fModifiers, prefix.fType,
+ prefix.fName);
+ }
+
+ // If this statement wasn't actually a vardecl after all, rewind and try parsing it as an
+ // expression-statement instead.
+ checkpoint.rewind();
+ }
+ return this->expressionStatement();
+}
+
+// Helper function for varDeclarations(). If this function succeeds, we assume that the rest of the
+// statement is a variable-declaration statement, not an expression-statement.
+bool Parser::varDeclarationsPrefix(VarDeclarationsPrefix* prefixData) {
+ prefixData->fPosition = this->position(this->peek());
+ prefixData->fModifiers = this->modifiers();
+ prefixData->fType = this->type(&prefixData->fModifiers);
+ if (!prefixData->fType.hasValue()) {
+ return false;
+ }
+ return this->expectIdentifier(&prefixData->fName);
+}
+
+/* modifiers type IDENTIFIER varDeclarationEnd */
+DSLStatement Parser::varDeclarations() {
+ VarDeclarationsPrefix prefix;
+ if (!this->varDeclarationsPrefix(&prefix)) {
+ return {};
+ }
+ return this->localVarDeclarationEnd(prefix.fPosition, prefix.fModifiers, prefix.fType,
+ prefix.fName);
+}
+
+/* STRUCT IDENTIFIER LBRACE varDeclaration* RBRACE */
+DSLType Parser::structDeclaration() {
+ Position start = this->position(this->peek());
+ if (!this->expect(Token::Kind::TK_STRUCT, "'struct'")) {
+ return DSLType(nullptr);
+ }
+ Token name;
+ if (!this->expectIdentifier(&name)) {
+ return DSLType(nullptr);
+ }
+ if (!this->expect(Token::Kind::TK_LBRACE, "'{'")) {
+ return DSLType(nullptr);
+ }
+ AutoDepth depth(this);
+ if (!depth.increase()) {
+ return DSLType(nullptr);
+ }
+ SkTArray<DSLField> fields;
+ SkTHashSet<std::string_view> fieldNames;
+ while (!this->checkNext(Token::Kind::TK_RBRACE)) {
+ Token fieldStart = this->peek();
+ DSLModifiers modifiers = this->modifiers();
+ DSLType type = this->type(&modifiers);
+ if (!type.hasValue()) {
+ return DSLType(nullptr);
+ }
+
+ do {
+ DSLType actualType = type;
+ Token memberName;
+ if (!this->expectIdentifier(&memberName)) {
+ return DSLType(nullptr);
+ }
+
+ while (this->checkNext(Token::Kind::TK_LBRACKET)) {
+ SKSL_INT size;
+ if (!this->arraySize(&size)) {
+ return DSLType(nullptr);
+ }
+ if (!this->expect(Token::Kind::TK_RBRACKET, "']'")) {
+ return DSLType(nullptr);
+ }
+ actualType = dsl::Array(actualType, size,
+ this->rangeFrom(this->position(fieldStart)));
+ }
+
+ std::string_view nameText = this->text(memberName);
+ if (!fieldNames.contains(nameText)) {
+ fields.push_back(DSLField(modifiers,
+ std::move(actualType),
+ nameText,
+ this->rangeFrom(fieldStart)));
+ fieldNames.add(nameText);
+ } else {
+ this->error(memberName, "field '" + std::string(nameText) +
+ "' was already defined in the same struct ('" +
+ std::string(this->text(name)) + "')");
+ }
+ } while (this->checkNext(Token::Kind::TK_COMMA));
+ if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) {
+ return DSLType(nullptr);
+ }
+ }
+ if (fields.empty()) {
+ this->error(this->rangeFrom(start), "struct '" + std::string(this->text(name)) +
+ "' must contain at least one field");
+ }
+ return dsl::Struct(this->text(name), SkSpan(fields), this->rangeFrom(start));
+}
+
+/* structDeclaration ((IDENTIFIER varDeclarationEnd) | SEMICOLON) */
+SkTArray<dsl::DSLGlobalVar> Parser::structVarDeclaration(Position start,
+ const DSLModifiers& modifiers) {
+ DSLType type = this->structDeclaration();
+ if (!type.hasValue()) {
+ return {};
+ }
+ Token name;
+ if (this->checkIdentifier(&name)) {
+ this->globalVarDeclarationEnd(this->rangeFrom(name), modifiers, type, name);
+ } else {
+ this->expect(Token::Kind::TK_SEMICOLON, "';'");
+ }
+ return {};
+}
+
+/* modifiers type IDENTIFIER (LBRACKET INT_LITERAL RBRACKET)? */
+std::optional<DSLParameter> Parser::parameter(size_t paramIndex) {
+ Position pos = this->position(this->peek());
+ DSLModifiers modifiers = this->modifiers();
+ DSLType type = this->type(&modifiers);
+ if (!type.hasValue()) {
+ return std::nullopt;
+ }
+ Token name;
+ std::string_view paramText;
+ Position paramPos;
+ if (this->checkIdentifier(&name)) {
+ paramText = this->text(name);
+ paramPos = this->position(name);
+ } else {
+ paramPos = this->rangeFrom(pos);
+ }
+ if (!this->parseArrayDimensions(pos, &type)) {
+ return std::nullopt;
+ }
+ return DSLParameter(modifiers, type, paramText, this->rangeFrom(pos), paramPos);
+}
+
+/** EQ INT_LITERAL */
+int Parser::layoutInt() {
+ if (!this->expect(Token::Kind::TK_EQ, "'='")) {
+ return -1;
+ }
+ Token resultToken;
+ if (!this->expect(Token::Kind::TK_INT_LITERAL, "a non-negative integer", &resultToken)) {
+ return -1;
+ }
+ std::string_view resultFrag = this->text(resultToken);
+ SKSL_INT resultValue;
+ if (!SkSL::stoi(resultFrag, &resultValue)) {
+ this->error(resultToken, "value in layout is too large: " + std::string(resultFrag));
+ return -1;
+ }
+ return resultValue;
+}
+
+/** EQ IDENTIFIER */
+std::string_view Parser::layoutIdentifier() {
+ if (!this->expect(Token::Kind::TK_EQ, "'='")) {
+ return {};
+ }
+ Token resultToken;
+ if (!this->expectIdentifier(&resultToken)) {
+ return {};
+ }
+ return this->text(resultToken);
+}
+
+/* LAYOUT LPAREN IDENTIFIER (EQ INT_LITERAL)? (COMMA IDENTIFIER (EQ INT_LITERAL)?)* RPAREN */
+DSLLayout Parser::layout() {
+ enum class LayoutToken {
+ LOCATION,
+ OFFSET,
+ BINDING,
+ TEXTURE,
+ SAMPLER,
+ INDEX,
+ SET,
+ BUILTIN,
+ INPUT_ATTACHMENT_INDEX,
+ ORIGIN_UPPER_LEFT,
+ BLEND_SUPPORT_ALL_EQUATIONS,
+ PUSH_CONSTANT,
+ COLOR,
+ SPIRV,
+ METAL,
+ GL,
+ WGSL
+ };
+
+ using LayoutMap = SkTHashMap<std::string_view, LayoutToken>;
+ static LayoutMap* sLayoutTokens = new LayoutMap{
+ {"location", LayoutToken::LOCATION},
+ {"offset", LayoutToken::OFFSET},
+ {"binding", LayoutToken::BINDING},
+ {"texture", LayoutToken::TEXTURE},
+ {"sampler", LayoutToken::SAMPLER},
+ {"index", LayoutToken::INDEX},
+ {"set", LayoutToken::SET},
+ {"builtin", LayoutToken::BUILTIN},
+ {"input_attachment_index", LayoutToken::INPUT_ATTACHMENT_INDEX},
+ {"origin_upper_left", LayoutToken::ORIGIN_UPPER_LEFT},
+ {"blend_support_all_equations", LayoutToken::BLEND_SUPPORT_ALL_EQUATIONS},
+ {"push_constant", LayoutToken::PUSH_CONSTANT},
+ {"color", LayoutToken::COLOR},
+ {"spirv", LayoutToken::SPIRV},
+ {"metal", LayoutToken::METAL},
+ {"gl", LayoutToken::GL},
+ {"wgsl", LayoutToken::WGSL},
+ };
+
+ DSLLayout result;
+ if (this->checkNext(Token::Kind::TK_LAYOUT)) {
+ if (!this->expect(Token::Kind::TK_LPAREN, "'('")) {
+ return result;
+ }
+ for (;;) {
+ Token t = this->nextToken();
+ std::string text(this->text(t));
+ LayoutToken* found = sLayoutTokens->find(text);
+ if (found != nullptr) {
+ switch (*found) {
+ case LayoutToken::SPIRV:
+ result.spirv(this->position(t));
+ break;
+ case LayoutToken::METAL:
+ result.metal(this->position(t));
+ break;
+ case LayoutToken::GL:
+ result.gl(this->position(t));
+ break;
+ case LayoutToken::WGSL:
+ result.wgsl(this->position(t));
+ break;
+ case LayoutToken::ORIGIN_UPPER_LEFT:
+ result.originUpperLeft(this->position(t));
+ break;
+ case LayoutToken::PUSH_CONSTANT:
+ result.pushConstant(this->position(t));
+ break;
+ case LayoutToken::BLEND_SUPPORT_ALL_EQUATIONS:
+ result.blendSupportAllEquations(this->position(t));
+ break;
+ case LayoutToken::COLOR:
+ result.color(this->position(t));
+ break;
+ case LayoutToken::LOCATION:
+ result.location(this->layoutInt(), this->position(t));
+ break;
+ case LayoutToken::OFFSET:
+ result.offset(this->layoutInt(), this->position(t));
+ break;
+ case LayoutToken::BINDING:
+ result.binding(this->layoutInt(), this->position(t));
+ break;
+ case LayoutToken::INDEX:
+ result.index(this->layoutInt(), this->position(t));
+ break;
+ case LayoutToken::SET:
+ result.set(this->layoutInt(), this->position(t));
+ break;
+ case LayoutToken::TEXTURE:
+ result.texture(this->layoutInt(), this->position(t));
+ break;
+ case LayoutToken::SAMPLER:
+ result.sampler(this->layoutInt(), this->position(t));
+ break;
+ case LayoutToken::BUILTIN:
+ result.builtin(this->layoutInt(), this->position(t));
+ break;
+ case LayoutToken::INPUT_ATTACHMENT_INDEX:
+ result.inputAttachmentIndex(this->layoutInt(), this->position(t));
+ break;
+ }
+ } else {
+ this->error(t, "'" + text + "' is not a valid layout qualifier");
+ }
+ if (this->checkNext(Token::Kind::TK_RPAREN)) {
+ break;
+ }
+ if (!this->expect(Token::Kind::TK_COMMA, "','")) {
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+/* layout? (UNIFORM | CONST | IN | OUT | INOUT | LOWP | MEDIUMP | HIGHP | FLAT | NOPERSPECTIVE |
+ VARYING | INLINE | WORKGROUP | READONLY | WRITEONLY | BUFFER)* */
+DSLModifiers Parser::modifiers() {
+ int start = this->peek().fOffset;
+ DSLLayout layout = this->layout();
+ Token raw = this->nextRawToken();
+ int end = raw.fOffset;
+ if (!is_whitespace(raw.fKind)) {
+ this->pushback(raw);
+ }
+ int flags = 0;
+ for (;;) {
+ int tokenFlag = parse_modifier_token(peek().fKind);
+ if (!tokenFlag) {
+ break;
+ }
+ Token modifier = this->nextToken();
+ if (int duplicateFlags = (tokenFlag & flags)) {
+ this->error(modifier, "'" + Modifiers::DescribeFlags(duplicateFlags) +
+ "' appears more than once");
+ }
+ flags |= tokenFlag;
+ end = this->position(modifier).endOffset();
+ }
+ return DSLModifiers(std::move(layout), flags, Position::Range(start, end));
+}
+
+/* ifStatement | forStatement | doStatement | whileStatement | block | expression */
+DSLStatement Parser::statement() {
+ Token start = this->nextToken();
+ AutoDepth depth(this);
+ if (!depth.increase()) {
+ return {};
+ }
+ this->pushback(start);
+ switch (start.fKind) {
+ case Token::Kind::TK_IF:
+ return this->ifStatement();
+ case Token::Kind::TK_FOR:
+ return this->forStatement();
+ case Token::Kind::TK_DO:
+ return this->doStatement();
+ case Token::Kind::TK_WHILE:
+ return this->whileStatement();
+ case Token::Kind::TK_SWITCH:
+ return this->switchStatement();
+ case Token::Kind::TK_RETURN:
+ return this->returnStatement();
+ case Token::Kind::TK_BREAK:
+ return this->breakStatement();
+ case Token::Kind::TK_CONTINUE:
+ return this->continueStatement();
+ case Token::Kind::TK_DISCARD:
+ return this->discardStatement();
+ case Token::Kind::TK_LBRACE: {
+ std::optional<DSLBlock> result = this->block();
+ return result ? DSLStatement(std::move(*result)) : DSLStatement();
+ }
+ case Token::Kind::TK_SEMICOLON:
+ this->nextToken();
+ return DSLBlock();
+ case Token::Kind::TK_HIGHP:
+ case Token::Kind::TK_MEDIUMP:
+ case Token::Kind::TK_LOWP:
+ case Token::Kind::TK_CONST:
+ case Token::Kind::TK_IDENTIFIER:
+ return this->varDeclarationsOrExpressionStatement();
+ default:
+ return this->expressionStatement();
+ }
+}
+
+/* IDENTIFIER(type) (LBRACKET intLiteral? RBRACKET)* QUESTION? */
+DSLType Parser::type(DSLModifiers* modifiers) {
+ Token type;
+ if (!this->expect(Token::Kind::TK_IDENTIFIER, "a type", &type)) {
+ return DSLType(nullptr);
+ }
+ if (!this->symbolTable()->isType(this->text(type))) {
+ this->error(type, "no type named '" + std::string(this->text(type)) + "'");
+ return DSLType::Invalid();
+ }
+ DSLType result(this->text(type), modifiers, this->position(type));
+ if (result.isInterfaceBlock()) {
+ // SkSL puts interface blocks into the symbol table, but they aren't general-purpose types;
+ // you can't use them to declare a variable type or a function return type.
+ this->error(type, "expected a type, found '" + std::string(this->text(type)) + "'");
+ return DSLType::Invalid();
+ }
+ Token bracket;
+ while (this->checkNext(Token::Kind::TK_LBRACKET, &bracket)) {
+ if (this->checkNext(Token::Kind::TK_RBRACKET)) {
+ if (this->allowUnsizedArrays()) {
+ result = UnsizedArray(result, this->rangeFrom(type));
+ } else {
+ this->error(this->rangeFrom(bracket), "unsized arrays are not permitted here");
+ }
+ } else {
+ SKSL_INT size;
+ if (!this->arraySize(&size)) {
+ return DSLType(nullptr);
+ }
+ this->expect(Token::Kind::TK_RBRACKET, "']'");
+ result = Array(result, size, this->rangeFrom(type));
+ }
+ }
+ return result;
+}
+
+/* IDENTIFIER LBRACE
+ varDeclaration+
+ RBRACE (IDENTIFIER (LBRACKET expression RBRACKET)*)? SEMICOLON */
+bool Parser::interfaceBlock(const dsl::DSLModifiers& modifiers) {
+ Token typeName;
+ if (!this->expectIdentifier(&typeName)) {
+ return false;
+ }
+ if (this->peek().fKind != Token::Kind::TK_LBRACE) {
+ // we only get into interfaceBlock if we found a top-level identifier which was not a type.
+ // 99% of the time, the user was not actually intending to create an interface block, so
+ // it's better to report it as an unknown type
+ this->error(typeName, "no type named '" + std::string(this->text(typeName)) + "'");
+ return false;
+ }
+ this->nextToken();
+ SkTArray<DSLField> fields;
+ SkTHashSet<std::string_view> fieldNames;
+ while (!this->checkNext(Token::Kind::TK_RBRACE)) {
+ Position fieldPos = this->position(this->peek());
+ DSLModifiers fieldModifiers = this->modifiers();
+ DSLType type = this->type(&fieldModifiers);
+ if (!type.hasValue()) {
+ return false;
+ }
+ do {
+ Token fieldName;
+ if (!this->expectIdentifier(&fieldName)) {
+ return false;
+ }
+ DSLType actualType = type;
+ if (this->checkNext(Token::Kind::TK_LBRACKET)) {
+ Token sizeToken = this->peek();
+ if (sizeToken.fKind != Token::Kind::TK_RBRACKET) {
+ SKSL_INT size;
+ if (!this->arraySize(&size)) {
+ return false;
+ }
+ actualType = Array(std::move(actualType), size, this->position(typeName));
+ } else if (this->allowUnsizedArrays()) {
+ actualType = UnsizedArray(std::move(actualType), this->position(typeName));
+ } else {
+ this->error(sizeToken, "unsized arrays are not permitted here");
+ }
+ this->expect(Token::Kind::TK_RBRACKET, "']'");
+ }
+ if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) {
+ return false;
+ }
+
+ std::string_view nameText = this->text(fieldName);
+ if (!fieldNames.contains(nameText)) {
+ fields.push_back(DSLField(fieldModifiers,
+ std::move(actualType),
+ nameText,
+ this->rangeFrom(fieldPos)));
+ fieldNames.add(nameText);
+ } else {
+ this->error(fieldName, "field '" + std::string(nameText) +
+ "' was already defined in the same interface block ('" +
+ std::string(this->text(typeName)) + "')");
+ }
+ } while (this->checkNext(Token::Kind::TK_COMMA));
+ }
+ if (fields.empty()) {
+ this->error(this->rangeFrom(typeName), "interface block '" +
+ std::string(this->text(typeName)) + "' must contain at least one member");
+ }
+ std::string_view instanceName;
+ Token instanceNameToken;
+ SKSL_INT size = 0;
+ if (this->checkIdentifier(&instanceNameToken)) {
+ instanceName = this->text(instanceNameToken);
+ if (this->checkNext(Token::Kind::TK_LBRACKET)) {
+ if (!this->arraySize(&size)) {
+ return false;
+ }
+ this->expect(Token::Kind::TK_RBRACKET, "']'");
+ }
+ }
+ if (!fields.empty()) {
+ dsl::InterfaceBlock(modifiers, this->text(typeName), std::move(fields), instanceName,
+ size, this->position(typeName));
+ }
+ this->expect(Token::Kind::TK_SEMICOLON, "';'");
+ return true;
+}
+
+/* IF LPAREN expression RPAREN statement (ELSE statement)? */
+DSLStatement Parser::ifStatement() {
+ Token start;
+ if (!this->expect(Token::Kind::TK_IF, "'if'", &start)) {
+ return {};
+ }
+ if (!this->expect(Token::Kind::TK_LPAREN, "'('")) {
+ return {};
+ }
+ DSLExpression test = this->expression();
+ if (!test.hasValue()) {
+ return {};
+ }
+ if (!this->expect(Token::Kind::TK_RPAREN, "')'")) {
+ return {};
+ }
+ DSLStatement ifTrue = this->statement();
+ if (!ifTrue.hasValue()) {
+ return {};
+ }
+ DSLStatement ifFalse;
+ if (this->checkNext(Token::Kind::TK_ELSE)) {
+ ifFalse = this->statement();
+ if (!ifFalse.hasValue()) {
+ return {};
+ }
+ }
+ Position pos = this->rangeFrom(start);
+ return If(std::move(test), std::move(ifTrue),
+ ifFalse.hasValue() ? std::move(ifFalse) : DSLStatement(), pos);
+}
+
+/* DO statement WHILE LPAREN expression RPAREN SEMICOLON */
+DSLStatement Parser::doStatement() {
+ Token start;
+ if (!this->expect(Token::Kind::TK_DO, "'do'", &start)) {
+ return {};
+ }
+ DSLStatement statement = this->statement();
+ if (!statement.hasValue()) {
+ return {};
+ }
+ if (!this->expect(Token::Kind::TK_WHILE, "'while'")) {
+ return {};
+ }
+ if (!this->expect(Token::Kind::TK_LPAREN, "'('")) {
+ return {};
+ }
+ DSLExpression test = this->expression();
+ if (!test.hasValue()) {
+ return {};
+ }
+ if (!this->expect(Token::Kind::TK_RPAREN, "')'")) {
+ return {};
+ }
+ if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) {
+ return {};
+ }
+ return Do(std::move(statement), std::move(test), this->rangeFrom(start));
+}
+
+/* WHILE LPAREN expression RPAREN STATEMENT */
+DSLStatement Parser::whileStatement() {
+ Token start;
+ if (!this->expect(Token::Kind::TK_WHILE, "'while'", &start)) {
+ return {};
+ }
+ if (!this->expect(Token::Kind::TK_LPAREN, "'('")) {
+ return {};
+ }
+ DSLExpression test = this->expression();
+ if (!test.hasValue()) {
+ return {};
+ }
+ if (!this->expect(Token::Kind::TK_RPAREN, "')'")) {
+ return {};
+ }
+ DSLStatement statement = this->statement();
+ if (!statement.hasValue()) {
+ return {};
+ }
+ return While(std::move(test), std::move(statement), this->rangeFrom(start));
+}
+
+/* CASE expression COLON statement* */
+std::optional<DSLCase> Parser::switchCase() {
+ Token start;
+ if (!this->expect(Token::Kind::TK_CASE, "'case'", &start)) {
+ return {};
+ }
+ DSLExpression value = this->expression();
+ if (!value.hasValue()) {
+ return {};
+ }
+ if (!this->expect(Token::Kind::TK_COLON, "':'")) {
+ return {};
+ }
+ SkTArray<DSLStatement> statements;
+ while (this->peek().fKind != Token::Kind::TK_RBRACE &&
+ this->peek().fKind != Token::Kind::TK_CASE &&
+ this->peek().fKind != Token::Kind::TK_DEFAULT) {
+ DSLStatement s = this->statement();
+ if (!s.hasValue()) {
+ return {};
+ }
+ statements.push_back(std::move(s));
+ }
+ return DSLCase(std::move(value), std::move(statements));
+}
+
+/* SWITCH LPAREN expression RPAREN LBRACE switchCase* (DEFAULT COLON statement*)? RBRACE */
+DSLStatement Parser::switchStatement() {
+ Token start;
+ if (!this->expect(Token::Kind::TK_SWITCH, "'switch'", &start)) {
+ return {};
+ }
+ if (!this->expect(Token::Kind::TK_LPAREN, "'('")) {
+ return {};
+ }
+ DSLExpression value = this->expression();
+ if (!value.hasValue()) {
+ return {};
+ }
+ if (!this->expect(Token::Kind::TK_RPAREN, "')'")) {
+ return {};
+ }
+ if (!this->expect(Token::Kind::TK_LBRACE, "'{'")) {
+ return {};
+ }
+ SkTArray<DSLCase> cases;
+ while (this->peek().fKind == Token::Kind::TK_CASE) {
+ std::optional<DSLCase> c = this->switchCase();
+ if (!c) {
+ return {};
+ }
+ cases.push_back(std::move(*c));
+ }
+ // Requiring default: to be last (in defiance of C and GLSL) was a deliberate decision. Other
+ // parts of the compiler may rely upon this assumption.
+ if (this->peek().fKind == Token::Kind::TK_DEFAULT) {
+ SkTArray<DSLStatement> statements;
+ Token defaultStart;
+ SkAssertResult(this->expect(Token::Kind::TK_DEFAULT, "'default'", &defaultStart));
+ if (!this->expect(Token::Kind::TK_COLON, "':'")) {
+ return {};
+ }
+ while (this->peek().fKind != Token::Kind::TK_RBRACE) {
+ DSLStatement s = this->statement();
+ if (!s.hasValue()) {
+ return {};
+ }
+ statements.push_back(std::move(s));
+ }
+ cases.push_back(DSLCase(DSLExpression(), std::move(statements), this->position(start)));
+ }
+ if (!this->expect(Token::Kind::TK_RBRACE, "'}'")) {
+ return {};
+ }
+ Position pos = this->rangeFrom(start);
+ return Switch(std::move(value), std::move(cases), pos);
+}
+
+static Position range_of_at_least_one_char(int start, int end) {
+ return Position::Range(start, std::max(end, start + 1));
+}
+
+/* FOR LPAREN (declaration | expression)? SEMICOLON expression? SEMICOLON expression? RPAREN
+ STATEMENT */
+dsl::DSLStatement Parser::forStatement() {
+ Token start;
+ if (!this->expect(Token::Kind::TK_FOR, "'for'", &start)) {
+ return {};
+ }
+ Token lparen;
+ if (!this->expect(Token::Kind::TK_LPAREN, "'('", &lparen)) {
+ return {};
+ }
+ AutoSymbolTable symbols(this);
+ dsl::DSLStatement initializer;
+ Token nextToken = this->peek();
+ int firstSemicolonOffset;
+ if (nextToken.fKind == Token::Kind::TK_SEMICOLON) {
+ // An empty init-statement.
+ firstSemicolonOffset = this->nextToken().fOffset;
+ } else {
+ // The init-statement must be an expression or variable declaration.
+ initializer = this->varDeclarationsOrExpressionStatement();
+ if (!initializer.hasValue()) {
+ return {};
+ }
+ firstSemicolonOffset = fLexer.getCheckpoint().fOffset - 1;
+ }
+ dsl::DSLExpression test;
+ if (this->peek().fKind != Token::Kind::TK_SEMICOLON) {
+ dsl::DSLExpression testValue = this->expression();
+ if (!testValue.hasValue()) {
+ return {};
+ }
+ test.swap(testValue);
+ }
+ Token secondSemicolon;
+ if (!this->expect(Token::Kind::TK_SEMICOLON, "';'", &secondSemicolon)) {
+ return {};
+ }
+ dsl::DSLExpression next;
+ if (this->peek().fKind != Token::Kind::TK_RPAREN) {
+ dsl::DSLExpression nextValue = this->expression();
+ if (!nextValue.hasValue()) {
+ return {};
+ }
+ next.swap(nextValue);
+ }
+ Token rparen;
+ if (!this->expect(Token::Kind::TK_RPAREN, "')'", &rparen)) {
+ return {};
+ }
+ dsl::DSLStatement statement = this->statement();
+ if (!statement.hasValue()) {
+ return {};
+ }
+ return For(initializer.hasValue() ? std::move(initializer) : DSLStatement(),
+ test.hasValue() ? std::move(test) : DSLExpression(),
+ next.hasValue() ? std::move(next) : DSLExpression(),
+ std::move(statement),
+ this->rangeFrom(start),
+ ForLoopPositions{
+ range_of_at_least_one_char(lparen.fOffset + 1, firstSemicolonOffset),
+ range_of_at_least_one_char(firstSemicolonOffset + 1, secondSemicolon.fOffset),
+ range_of_at_least_one_char(secondSemicolon.fOffset + 1, rparen.fOffset)
+ });
+}
+
+/* RETURN expression? SEMICOLON */
+DSLStatement Parser::returnStatement() {
+ Token start;
+ if (!this->expect(Token::Kind::TK_RETURN, "'return'", &start)) {
+ return {};
+ }
+ DSLExpression expression;
+ if (this->peek().fKind != Token::Kind::TK_SEMICOLON) {
+ DSLExpression next = this->expression();
+ if (!next.hasValue()) {
+ return {};
+ }
+ expression.swap(next);
+ }
+ if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) {
+ return {};
+ }
+ return Return(expression.hasValue() ? std::move(expression) : DSLExpression(),
+ this->rangeFrom(start));
+}
+
+/* BREAK SEMICOLON */
+DSLStatement Parser::breakStatement() {
+ Token start;
+ if (!this->expect(Token::Kind::TK_BREAK, "'break'", &start)) {
+ return {};
+ }
+ if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) {
+ return {};
+ }
+ return Break(this->position(start));
+}
+
+/* CONTINUE SEMICOLON */
+DSLStatement Parser::continueStatement() {
+ Token start;
+ if (!this->expect(Token::Kind::TK_CONTINUE, "'continue'", &start)) {
+ return {};
+ }
+ if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) {
+ return {};
+ }
+ return Continue(this->position(start));
+}
+
+/* DISCARD SEMICOLON */
+DSLStatement Parser::discardStatement() {
+ Token start;
+ if (!this->expect(Token::Kind::TK_DISCARD, "'continue'", &start)) {
+ return {};
+ }
+ if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) {
+ return {};
+ }
+ return Discard(this->position(start));
+}
+
+/* LBRACE statement* RBRACE */
+std::optional<DSLBlock> Parser::block() {
+ Token start;
+ if (!this->expect(Token::Kind::TK_LBRACE, "'{'", &start)) {
+ return std::nullopt;
+ }
+ AutoDepth depth(this);
+ if (!depth.increase()) {
+ return std::nullopt;
+ }
+ AutoSymbolTable symbols(this);
+ StatementArray statements;
+ for (;;) {
+ switch (this->peek().fKind) {
+ case Token::Kind::TK_RBRACE:
+ this->nextToken();
+ return DSLBlock(std::move(statements), this->symbolTable(), this->rangeFrom(start));
+ case Token::Kind::TK_END_OF_FILE:
+ this->error(this->peek(), "expected '}', but found end of file");
+ return std::nullopt;
+ default: {
+ DSLStatement statement = this->statement();
+ if (fEncounteredFatalError) {
+ return std::nullopt;
+ }
+ if (statement.hasValue()) {
+ statements.push_back(statement.release());
+ }
+ break;
+ }
+ }
+ }
+}
+
+/* expression SEMICOLON */
+DSLStatement Parser::expressionStatement() {
+ DSLExpression expr = this->expression();
+ if (expr.hasValue()) {
+ if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) {
+ return {};
+ }
+ return DSLStatement(std::move(expr));
+ }
+ return {};
+}
+
+bool Parser::operatorRight(Parser::AutoDepth& depth,
+ Operator::Kind op,
+ BinaryParseFn rightFn,
+ DSLExpression& result) {
+ this->nextToken();
+ if (!depth.increase()) {
+ return false;
+ }
+ DSLExpression right = (this->*rightFn)();
+ if (!right.hasValue()) {
+ return false;
+ }
+ Position pos = result.position().rangeThrough(right.position());
+ DSLExpression next = result.binary(op, std::move(right), pos);
+ result.swap(next);
+ return true;
+}
+
+/* assignmentExpression (COMMA assignmentExpression)* */
+DSLExpression Parser::expression() {
+ [[maybe_unused]] Token start = this->peek();
+ DSLExpression result = this->assignmentExpression();
+ if (!result.hasValue()) {
+ return {};
+ }
+ Token t;
+ AutoDepth depth(this);
+ while (this->peek().fKind == Token::Kind::TK_COMMA) {
+ if (!operatorRight(depth, Operator::Kind::COMMA, &Parser::assignmentExpression,
+ result)) {
+ return {};
+ }
+ }
+ SkASSERTF(result.position().valid(), "Expression %s has invalid position",
+ result.description().c_str());
+ SkASSERTF(result.position().startOffset() == this->position(start).startOffset(),
+ "Expected %s to start at %d (first token: '%.*s'), but it has range %d-%d\n",
+ result.description().c_str(), this->position(start).startOffset(),
+ (int)this->text(start).length(), this->text(start).data(),
+ result.position().startOffset(), result.position().endOffset());
+ return result;
+}
+
+/* ternaryExpression ((EQEQ | STAREQ | SLASHEQ | PERCENTEQ | PLUSEQ | MINUSEQ | SHLEQ | SHREQ |
+ BITWISEANDEQ | BITWISEXOREQ | BITWISEOREQ | LOGICALANDEQ | LOGICALXOREQ | LOGICALOREQ)
+ assignmentExpression)*
+ */
+DSLExpression Parser::assignmentExpression() {
+ AutoDepth depth(this);
+ DSLExpression result = this->ternaryExpression();
+ if (!result.hasValue()) {
+ return {};
+ }
+ for (;;) {
+ switch (this->peek().fKind) {
+ case Token::Kind::TK_EQ:
+ if (!operatorRight(depth, Operator::Kind::EQ, &Parser::assignmentExpression,
+ result)) {
+ return {};
+ }
+ break;
+ case Token::Kind::TK_STAREQ:
+ if (!operatorRight(depth, Operator::Kind::STAREQ, &Parser::assignmentExpression,
+ result)) {
+ return {};
+ }
+ break;
+ case Token::Kind::TK_SLASHEQ:
+ if (!operatorRight(depth, Operator::Kind::SLASHEQ, &Parser::assignmentExpression,
+ result)) {
+ return {};
+ }
+ break;
+ case Token::Kind::TK_PERCENTEQ:
+ if (!operatorRight(depth, Operator::Kind::PERCENTEQ,
+ &Parser::assignmentExpression, result)) {
+ return {};
+ }
+ break;
+ case Token::Kind::TK_PLUSEQ:
+ if (!operatorRight(depth, Operator::Kind::PLUSEQ, &Parser::assignmentExpression,
+ result)) {
+ return {};
+ }
+ break;
+ case Token::Kind::TK_MINUSEQ:
+ if (!operatorRight(depth, Operator::Kind::MINUSEQ, &Parser::assignmentExpression,
+ result)) {
+ return {};
+ }
+ break;
+ case Token::Kind::TK_SHLEQ:
+ if (!operatorRight(depth, Operator::Kind::SHLEQ, &Parser::assignmentExpression,
+ result)) {
+ return {};
+ }
+ break;
+ case Token::Kind::TK_SHREQ:
+ if (!operatorRight(depth, Operator::Kind::SHREQ, &Parser::assignmentExpression,
+ result)) {
+ return {};
+ }
+ break;
+ case Token::Kind::TK_BITWISEANDEQ:
+ if (!operatorRight(depth, Operator::Kind::BITWISEANDEQ,
+ &Parser::assignmentExpression, result)) {
+ return {};
+ }
+ break;
+ case Token::Kind::TK_BITWISEXOREQ:
+ if (!operatorRight(depth, Operator::Kind::BITWISEXOREQ,
+ &Parser::assignmentExpression, result)) {
+ return {};
+ }
+ break;
+ case Token::Kind::TK_BITWISEOREQ:
+ if (!operatorRight(depth, Operator::Kind::BITWISEOREQ,
+ &Parser::assignmentExpression, result)) {
+ return {};
+ }
+ break;
+ default:
+ return result;
+ }
+ }
+}
+
+/* logicalOrExpression ('?' expression ':' assignmentExpression)? */
+DSLExpression Parser::ternaryExpression() {
+ DSLExpression base = this->logicalOrExpression();
+ if (!base.hasValue()) {
+ return {};
+ }
+ if (!this->checkNext(Token::Kind::TK_QUESTION)) {
+ return base;
+ }
+ AutoDepth depth(this);
+ if (!depth.increase()) {
+ return {};
+ }
+ DSLExpression trueExpr = this->expression();
+ if (!trueExpr.hasValue()) {
+ return {};
+ }
+ if (!this->expect(Token::Kind::TK_COLON, "':'")) {
+ return {};
+ }
+ DSLExpression falseExpr = this->assignmentExpression();
+ if (!falseExpr.hasValue()) {
+ return {};
+ }
+ Position pos = base.position().rangeThrough(falseExpr.position());
+ return Select(std::move(base), std::move(trueExpr), std::move(falseExpr), pos);
+}
+
+/* logicalXorExpression (LOGICALOR logicalXorExpression)* */
+DSLExpression Parser::logicalOrExpression() {
+ AutoDepth depth(this);
+ DSLExpression result = this->logicalXorExpression();
+ if (!result.hasValue()) {
+ return {};
+ }
+ while (this->peek().fKind == Token::Kind::TK_LOGICALOR) {
+ if (!operatorRight(depth, Operator::Kind::LOGICALOR, &Parser::logicalXorExpression,
+ result)) {
+ return {};
+ }
+ }
+ return result;
+}
+
+/* logicalAndExpression (LOGICALXOR logicalAndExpression)* */
+DSLExpression Parser::logicalXorExpression() {
+ AutoDepth depth(this);
+ DSLExpression result = this->logicalAndExpression();
+ if (!result.hasValue()) {
+ return {};
+ }
+ while (this->peek().fKind == Token::Kind::TK_LOGICALXOR) {
+ if (!operatorRight(depth, Operator::Kind::LOGICALXOR, &Parser::logicalAndExpression,
+ result)) {
+ return {};
+ }
+ }
+ return result;
+}
+
+/* bitwiseOrExpression (LOGICALAND bitwiseOrExpression)* */
+DSLExpression Parser::logicalAndExpression() {
+ AutoDepth depth(this);
+ DSLExpression result = this->bitwiseOrExpression();
+ if (!result.hasValue()) {
+ return {};
+ }
+ while (this->peek().fKind == Token::Kind::TK_LOGICALAND) {
+ if (!operatorRight(depth, Operator::Kind::LOGICALAND, &Parser::bitwiseOrExpression,
+ result)) {
+ return {};
+ }
+ }
+ return result;
+}
+
+/* bitwiseXorExpression (BITWISEOR bitwiseXorExpression)* */
+DSLExpression Parser::bitwiseOrExpression() {
+ AutoDepth depth(this);
+ DSLExpression result = this->bitwiseXorExpression();
+ if (!result.hasValue()) {
+ return {};
+ }
+ while (this->peek().fKind == Token::Kind::TK_BITWISEOR) {
+ if (!operatorRight(depth, Operator::Kind::BITWISEOR, &Parser::bitwiseXorExpression,
+ result)) {
+ return {};
+ }
+ }
+ return result;
+}
+
+/* bitwiseAndExpression (BITWISEXOR bitwiseAndExpression)* */
+DSLExpression Parser::bitwiseXorExpression() {
+ AutoDepth depth(this);
+ DSLExpression result = this->bitwiseAndExpression();
+ if (!result.hasValue()) {
+ return {};
+ }
+ while (this->peek().fKind == Token::Kind::TK_BITWISEXOR) {
+ if (!operatorRight(depth, Operator::Kind::BITWISEXOR, &Parser::bitwiseAndExpression,
+ result)) {
+ return {};
+ }
+ }
+ return result;
+}
+
+/* equalityExpression (BITWISEAND equalityExpression)* */
+DSLExpression Parser::bitwiseAndExpression() {
+ AutoDepth depth(this);
+ DSLExpression result = this->equalityExpression();
+ if (!result.hasValue()) {
+ return {};
+ }
+ while (this->peek().fKind == Token::Kind::TK_BITWISEAND) {
+ if (!operatorRight(depth, Operator::Kind::BITWISEAND, &Parser::equalityExpression,
+ result)) {
+ return {};
+ }
+ }
+ return result;
+}
+
+/* relationalExpression ((EQEQ | NEQ) relationalExpression)* */
+DSLExpression Parser::equalityExpression() {
+ AutoDepth depth(this);
+ DSLExpression result = this->relationalExpression();
+ if (!result.hasValue()) {
+ return {};
+ }
+ for (;;) {
+ switch (this->peek().fKind) {
+ case Token::Kind::TK_EQEQ:
+ if (!operatorRight(depth, Operator::Kind::EQEQ, &Parser::relationalExpression,
+ result)) {
+ return {};
+ }
+ break;
+ case Token::Kind::TK_NEQ:
+ if (!operatorRight(depth, Operator::Kind::NEQ, &Parser::relationalExpression,
+ result)) {
+ return {};
+ }
+ break;
+ default: return result;
+ }
+ }
+}
+
+/* shiftExpression ((LT | GT | LTEQ | GTEQ) shiftExpression)* */
+DSLExpression Parser::relationalExpression() {
+ AutoDepth depth(this);
+ DSLExpression result = this->shiftExpression();
+ if (!result.hasValue()) {
+ return {};
+ }
+ for (;;) {
+ switch (this->peek().fKind) {
+ case Token::Kind::TK_LT:
+ if (!operatorRight(depth, Operator::Kind::LT, &Parser::shiftExpression,
+ result)) {
+ return {};
+ }
+ break;
+ case Token::Kind::TK_GT:
+ if (!operatorRight(depth, Operator::Kind::GT, &Parser::shiftExpression,
+ result)) {
+ return {};
+ }
+ break;
+ case Token::Kind::TK_LTEQ:
+ if (!operatorRight(depth, Operator::Kind::LTEQ, &Parser::shiftExpression,
+ result)) {
+ return {};
+ }
+ break;
+ case Token::Kind::TK_GTEQ:
+ if (!operatorRight(depth, Operator::Kind::GTEQ, &Parser::shiftExpression,
+ result)) {
+ return {};
+ }
+ break;
+ default:
+ return result;
+ }
+ }
+}
+
+/* additiveExpression ((SHL | SHR) additiveExpression)* */
+DSLExpression Parser::shiftExpression() {
+ AutoDepth depth(this);
+ DSLExpression result = this->additiveExpression();
+ if (!result.hasValue()) {
+ return {};
+ }
+ for (;;) {
+ switch (this->peek().fKind) {
+ case Token::Kind::TK_SHL:
+ if (!operatorRight(depth, Operator::Kind::SHL, &Parser::additiveExpression,
+ result)) {
+ return {};
+ }
+ break;
+ case Token::Kind::TK_SHR:
+ if (!operatorRight(depth, Operator::Kind::SHR, &Parser::additiveExpression,
+ result)) {
+ return {};
+ }
+ break;
+ default:
+ return result;
+ }
+ }
+}
+
+/* multiplicativeExpression ((PLUS | MINUS) multiplicativeExpression)* */
+DSLExpression Parser::additiveExpression() {
+ AutoDepth depth(this);
+ DSLExpression result = this->multiplicativeExpression();
+ if (!result.hasValue()) {
+ return {};
+ }
+ for (;;) {
+ switch (this->peek().fKind) {
+ case Token::Kind::TK_PLUS:
+ if (!operatorRight(depth, Operator::Kind::PLUS,
+ &Parser::multiplicativeExpression, result)) {
+ return {};
+ }
+ break;
+ case Token::Kind::TK_MINUS:
+ if (!operatorRight(depth, Operator::Kind::MINUS,
+ &Parser::multiplicativeExpression, result)) {
+ return {};
+ }
+ break;
+ default:
+ return result;
+ }
+ }
+}
+
+/* unaryExpression ((STAR | SLASH | PERCENT) unaryExpression)* */
+DSLExpression Parser::multiplicativeExpression() {
+ AutoDepth depth(this);
+ DSLExpression result = this->unaryExpression();
+ if (!result.hasValue()) {
+ return {};
+ }
+ for (;;) {
+ switch (this->peek().fKind) {
+ case Token::Kind::TK_STAR:
+ if (!operatorRight(depth, Operator::Kind::STAR, &Parser::unaryExpression,
+ result)) {
+ return {};
+ }
+ break;
+ case Token::Kind::TK_SLASH:
+ if (!operatorRight(depth, Operator::Kind::SLASH, &Parser::unaryExpression,
+ result)) {
+ return {};
+ }
+ break;
+ case Token::Kind::TK_PERCENT:
+ if (!operatorRight(depth, Operator::Kind::PERCENT, &Parser::unaryExpression,
+ result)) {
+ return {};
+ }
+ break;
+ default: return result;
+ }
+ }
+}
+
+/* postfixExpression | (PLUS | MINUS | NOT | PLUSPLUS | MINUSMINUS) unaryExpression */
+DSLExpression Parser::unaryExpression() {
+ AutoDepth depth(this);
+ Token start = this->peek();
+ switch (start.fKind) {
+ case Token::Kind::TK_PLUS:
+ case Token::Kind::TK_MINUS:
+ case Token::Kind::TK_LOGICALNOT:
+ case Token::Kind::TK_BITWISENOT:
+ case Token::Kind::TK_PLUSPLUS:
+ case Token::Kind::TK_MINUSMINUS: {
+ this->nextToken();
+ if (!depth.increase()) {
+ return {};
+ }
+ DSLExpression expr = this->unaryExpression();
+ if (!expr.hasValue()) {
+ return {};
+ }
+ Position p = Position::Range(start.fOffset, expr.position().endOffset());
+ switch (start.fKind) {
+ case Token::Kind::TK_PLUS: return expr.prefix(Operator::Kind::PLUS, p);
+ case Token::Kind::TK_MINUS: return expr.prefix(Operator::Kind::MINUS, p);
+ case Token::Kind::TK_LOGICALNOT: return expr.prefix(Operator::Kind::LOGICALNOT, p);
+ case Token::Kind::TK_BITWISENOT: return expr.prefix(Operator::Kind::BITWISENOT, p);
+ case Token::Kind::TK_PLUSPLUS: return expr.prefix(Operator::Kind::PLUSPLUS, p);
+ case Token::Kind::TK_MINUSMINUS: return expr.prefix(Operator::Kind::MINUSMINUS, p);
+ default: SkUNREACHABLE;
+ }
+ }
+ default:
+ return this->postfixExpression();
+ }
+}
+
+/* term suffix* */
+DSLExpression Parser::postfixExpression() {
+ AutoDepth depth(this);
+ DSLExpression result = this->term();
+ if (!result.hasValue()) {
+ return {};
+ }
+ for (;;) {
+ Token t = this->peek();
+ switch (t.fKind) {
+ case Token::Kind::TK_FLOAT_LITERAL:
+ if (this->text(t)[0] != '.') {
+ return result;
+ }
+ [[fallthrough]];
+ case Token::Kind::TK_LBRACKET:
+ case Token::Kind::TK_DOT:
+ case Token::Kind::TK_LPAREN:
+ case Token::Kind::TK_PLUSPLUS:
+ case Token::Kind::TK_MINUSMINUS: {
+ if (!depth.increase()) {
+ return {};
+ }
+ DSLExpression next = this->suffix(std::move(result));
+ if (!next.hasValue()) {
+ return {};
+ }
+ result.swap(next);
+ break;
+ }
+ default:
+ return result;
+ }
+ }
+}
+
+DSLExpression Parser::swizzle(Position pos,
+ DSLExpression base,
+ std::string_view swizzleMask,
+ Position maskPos) {
+ SkASSERT(swizzleMask.length() > 0);
+ if (!base.type().isVector() && !base.type().isScalar()) {
+ return base.field(swizzleMask, pos);
+ }
+ int length = swizzleMask.length();
+ SkSL::SwizzleComponent::Type components[4];
+ for (int i = 0; i < length; ++i) {
+ if (i >= 4) {
+ Position errorPos = maskPos.valid() ? Position::Range(maskPos.startOffset() + 4,
+ maskPos.endOffset())
+ : pos;
+ this->error(errorPos, "too many components in swizzle mask");
+ return DSLExpression::Poison(pos);
+ }
+ switch (swizzleMask[i]) {
+ case '0': components[i] = SwizzleComponent::ZERO; break;
+ case '1': components[i] = SwizzleComponent::ONE; break;
+ case 'r': components[i] = SwizzleComponent::R; break;
+ case 'x': components[i] = SwizzleComponent::X; break;
+ case 's': components[i] = SwizzleComponent::S; break;
+ case 'L': components[i] = SwizzleComponent::UL; break;
+ case 'g': components[i] = SwizzleComponent::G; break;
+ case 'y': components[i] = SwizzleComponent::Y; break;
+ case 't': components[i] = SwizzleComponent::T; break;
+ case 'T': components[i] = SwizzleComponent::UT; break;
+ case 'b': components[i] = SwizzleComponent::B; break;
+ case 'z': components[i] = SwizzleComponent::Z; break;
+ case 'p': components[i] = SwizzleComponent::P; break;
+ case 'R': components[i] = SwizzleComponent::UR; break;
+ case 'a': components[i] = SwizzleComponent::A; break;
+ case 'w': components[i] = SwizzleComponent::W; break;
+ case 'q': components[i] = SwizzleComponent::Q; break;
+ case 'B': components[i] = SwizzleComponent::UB; break;
+ default: {
+ Position componentPos = Position::Range(maskPos.startOffset() + i,
+ maskPos.startOffset() + i + 1);
+ this->error(componentPos, String::printf("invalid swizzle component '%c'",
+ swizzleMask[i]).c_str());
+ return DSLExpression::Poison(pos);
+ }
+ }
+ }
+ switch (length) {
+ case 1: return dsl::Swizzle(std::move(base), components[0], pos, maskPos);
+ case 2: return dsl::Swizzle(std::move(base), components[0], components[1], pos, maskPos);
+ case 3: return dsl::Swizzle(std::move(base), components[0], components[1], components[2],
+ pos, maskPos);
+ case 4: return dsl::Swizzle(std::move(base), components[0], components[1], components[2],
+ components[3], pos, maskPos);
+ default: SkUNREACHABLE;
+ }
+}
+
+dsl::DSLExpression Parser::call(Position pos, dsl::DSLExpression base, ExpressionArray args) {
+ return base(std::move(args), pos);
+}
+
+/* LBRACKET expression? RBRACKET | DOT IDENTIFIER | LPAREN arguments RPAREN |
+ PLUSPLUS | MINUSMINUS | COLONCOLON IDENTIFIER | FLOAT_LITERAL [IDENTIFIER] */
+DSLExpression Parser::suffix(DSLExpression base) {
+ Token next = this->nextToken();
+ AutoDepth depth(this);
+ if (!depth.increase()) {
+ return {};
+ }
+ switch (next.fKind) {
+ case Token::Kind::TK_LBRACKET: {
+ if (this->checkNext(Token::Kind::TK_RBRACKET)) {
+ this->error(this->rangeFrom(next), "missing index in '[]'");
+ return DSLExpression::Poison(this->rangeFrom(base.position()));
+ }
+ DSLExpression index = this->expression();
+ if (!index.hasValue()) {
+ return {};
+ }
+ this->expect(Token::Kind::TK_RBRACKET, "']' to complete array access expression");
+ return base.index(std::move(index), this->rangeFrom(base.position()));
+ }
+ case Token::Kind::TK_DOT: {
+ std::string_view text;
+ if (this->identifier(&text)) {
+ Position pos = this->rangeFrom(base.position());
+ return this->swizzle(pos, std::move(base), text,
+ this->rangeFrom(this->position(next).after()));
+ }
+ [[fallthrough]];
+ }
+ case Token::Kind::TK_FLOAT_LITERAL: {
+ // Swizzles that start with a constant number, e.g. '.000r', will be tokenized as
+ // floating point literals, possibly followed by an identifier. Handle that here.
+ std::string_view field = this->text(next);
+ SkASSERT(field[0] == '.');
+ field.remove_prefix(1);
+ // use the next *raw* token so we don't ignore whitespace - we only care about
+ // identifiers that directly follow the float
+ Position pos = this->rangeFrom(base.position());
+ Position start = this->position(next);
+ // skip past the "."
+ start = Position::Range(start.startOffset() + 1, start.endOffset());
+ Position maskPos = this->rangeFrom(start);
+ Token id = this->nextRawToken();
+ if (id.fKind == Token::Kind::TK_IDENTIFIER) {
+ pos = this->rangeFrom(base.position());
+ maskPos = this->rangeFrom(start);
+ return this->swizzle(pos, std::move(base), std::string(field) +
+ std::string(this->text(id)), maskPos);
+ } else if (field.empty()) {
+ this->error(pos, "expected field name or swizzle mask after '.'");
+ return {{DSLExpression::Poison(pos)}};
+ }
+ this->pushback(id);
+ return this->swizzle(pos, std::move(base), field, maskPos);
+ }
+ case Token::Kind::TK_LPAREN: {
+ ExpressionArray args;
+ if (this->peek().fKind != Token::Kind::TK_RPAREN) {
+ for (;;) {
+ DSLExpression expr = this->assignmentExpression();
+ if (!expr.hasValue()) {
+ return {};
+ }
+ args.push_back(expr.release());
+ if (!this->checkNext(Token::Kind::TK_COMMA)) {
+ break;
+ }
+ }
+ }
+ this->expect(Token::Kind::TK_RPAREN, "')' to complete function arguments");
+ Position pos = this->rangeFrom(base.position());
+ return this->call(pos, std::move(base), std::move(args));
+ }
+ case Token::Kind::TK_PLUSPLUS:
+ return base.postfix(Operator::Kind::PLUSPLUS, this->rangeFrom(base.position()));
+ case Token::Kind::TK_MINUSMINUS:
+ return base.postfix(Operator::Kind::MINUSMINUS, this->rangeFrom(base.position()));
+ default: {
+ this->error(next, "expected expression suffix, but found '" +
+ std::string(this->text(next)) + "'");
+ return {};
+ }
+ }
+}
+
+/* IDENTIFIER | intLiteral | floatLiteral | boolLiteral | '(' expression ')' */
+DSLExpression Parser::term() {
+ Token t = this->peek();
+ switch (t.fKind) {
+ case Token::Kind::TK_IDENTIFIER: {
+ std::string_view text;
+ if (this->identifier(&text)) {
+ Position pos = this->position(t);
+ return DSLExpression(fCompiler.convertIdentifier(pos, text), pos);
+ }
+ break;
+ }
+ case Token::Kind::TK_INT_LITERAL: {
+ SKSL_INT i;
+ if (!this->intLiteral(&i)) {
+ i = 0;
+ }
+ return DSLExpression(i, this->position(t));
+ }
+ case Token::Kind::TK_FLOAT_LITERAL: {
+ SKSL_FLOAT f;
+ if (!this->floatLiteral(&f)) {
+ f = 0.0f;
+ }
+ return DSLExpression(f, this->position(t));
+ }
+ case Token::Kind::TK_TRUE_LITERAL: // fall through
+ case Token::Kind::TK_FALSE_LITERAL: {
+ bool b;
+ SkAssertResult(this->boolLiteral(&b));
+ return DSLExpression(b, this->position(t));
+ }
+ case Token::Kind::TK_LPAREN: {
+ this->nextToken();
+ AutoDepth depth(this);
+ if (!depth.increase()) {
+ return {};
+ }
+ DSLExpression result = this->expression();
+ if (result.hasValue()) {
+ this->expect(Token::Kind::TK_RPAREN, "')' to complete expression");
+ result.setPosition(this->rangeFrom(this->position(t)));
+ return result;
+ }
+ break;
+ }
+ default:
+ this->nextToken();
+ this->error(t, "expected expression, but found '" + std::string(this->text(t)) + "'");
+ fEncounteredFatalError = true;
+ break;
+ }
+ return {};
+}
+
+/* INT_LITERAL */
+bool Parser::intLiteral(SKSL_INT* dest) {
+ Token t;
+ if (!this->expect(Token::Kind::TK_INT_LITERAL, "integer literal", &t)) {
+ return false;
+ }
+ std::string_view s = this->text(t);
+ if (!SkSL::stoi(s, dest)) {
+ this->error(t, "integer is too large: " + std::string(s));
+ return false;
+ }
+ return true;
+}
+
+/* FLOAT_LITERAL */
+bool Parser::floatLiteral(SKSL_FLOAT* dest) {
+ Token t;
+ if (!this->expect(Token::Kind::TK_FLOAT_LITERAL, "float literal", &t)) {
+ return false;
+ }
+ std::string_view s = this->text(t);
+ if (!SkSL::stod(s, dest)) {
+ this->error(t, "floating-point value is too large: " + std::string(s));
+ return false;
+ }
+ return true;
+}
+
+/* TRUE_LITERAL | FALSE_LITERAL */
+bool Parser::boolLiteral(bool* dest) {
+ Token t = this->nextToken();
+ switch (t.fKind) {
+ case Token::Kind::TK_TRUE_LITERAL:
+ *dest = true;
+ return true;
+ case Token::Kind::TK_FALSE_LITERAL:
+ *dest = false;
+ return true;
+ default:
+ this->error(t, "expected 'true' or 'false', but found '" +
+ std::string(this->text(t)) + "'");
+ return false;
+ }
+}
+
+/* IDENTIFIER */
+bool Parser::identifier(std::string_view* dest) {
+ Token t;
+ if (this->expect(Token::Kind::TK_IDENTIFIER, "identifier", &t)) {
+ *dest = this->text(t);
+ return true;
+ }
+ return false;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/SkSLParser.h b/gfx/skia/skia/src/sksl/SkSLParser.h
new file mode 100644
index 0000000000..74909f5e94
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLParser.h
@@ -0,0 +1,369 @@
+/*
+ * Copyright 2021 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_PARSER
+#define SKSL_PARSER
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLDefines.h"
+#include "include/private/base/SkTArray.h"
+#include "include/sksl/DSLCore.h"
+#include "include/sksl/DSLExpression.h"
+#include "include/sksl/DSLLayout.h"
+#include "include/sksl/DSLModifiers.h"
+#include "include/sksl/DSLStatement.h"
+#include "include/sksl/DSLType.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "include/sksl/SkSLOperator.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/SkSLLexer.h"
+#include "src/sksl/SkSLProgramSettings.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+
+namespace SkSL {
+
+class Compiler;
+class SymbolTable;
+enum class ProgramKind : int8_t;
+struct Module;
+struct Program;
+
+namespace dsl {
+class DSLBlock;
+class DSLCase;
+class DSLGlobalVar;
+class DSLParameter;
+class DSLVarBase;
+}
+
+/**
+ * Consumes .sksl text and invokes DSL functions to instantiate the program.
+ */
+class Parser {
+public:
+ Parser(Compiler* compiler, const ProgramSettings& settings, ProgramKind kind, std::string text);
+
+ std::unique_ptr<Program> program();
+
+ std::unique_ptr<Module> moduleInheritingFrom(const Module* parent);
+
+ std::string_view text(Token token);
+
+ Position position(Token token);
+
+private:
+ class AutoDepth;
+ class AutoSymbolTable;
+
+ /**
+ * Return the next token, including whitespace tokens, from the parse stream.
+ */
+ Token nextRawToken();
+
+ /**
+ * Return the next non-whitespace token from the parse stream.
+ */
+ Token nextToken();
+
+ /**
+ * Push a token back onto the parse stream, so that it is the next one read. Only a single level
+ * of pushback is supported (that is, it is an error to call pushback() twice in a row without
+ * an intervening nextToken()).
+ */
+ void pushback(Token t);
+
+ /**
+ * Returns the next non-whitespace token without consuming it from the stream.
+ */
+ Token peek();
+
+ /**
+ * Checks to see if the next token is of the specified type. If so, stores it in result (if
+ * result is non-null) and returns true. Otherwise, pushes it back and returns false.
+ */
+ bool checkNext(Token::Kind kind, Token* result = nullptr);
+
+ /**
+ * Behaves like checkNext(TK_IDENTIFIER), but also verifies that identifier is not a builtin
+ * type. If the token was actually a builtin type, false is returned (the next token is not
+ * considered to be an identifier).
+ */
+ bool checkIdentifier(Token* result = nullptr);
+
+ /**
+ * Reads the next non-whitespace token and generates an error if it is not the expected type.
+ * The 'expected' string is part of the error message, which reads:
+ *
+ * "expected <expected>, but found '<actual text>'"
+ *
+ * If 'result' is non-null, it is set to point to the token that was read.
+ * Returns true if the read token was as expected, false otherwise.
+ */
+ bool expect(Token::Kind kind, const char* expected, Token* result = nullptr);
+ bool expect(Token::Kind kind, std::string expected, Token* result = nullptr);
+
+ /**
+ * Behaves like expect(TK_IDENTIFIER), but also verifies that identifier is not a type.
+ * If the token was actually a type, generates an error message of the form:
+ *
+ * "expected an identifier, but found type 'float2'"
+ */
+ bool expectIdentifier(Token* result);
+
+ /** If the next token is a newline, consumes it and returns true. If not, returns false. */
+ bool expectNewline();
+
+ void error(Token token, std::string_view msg);
+ void error(Position position, std::string_view msg);
+
+ // Returns the range from `start` to the current parse position.
+ Position rangeFrom(Position start);
+ Position rangeFrom(Token start);
+
+ // these functions parse individual grammar rules from the current parse position; you probably
+ // don't need to call any of these outside of the parser. The function declarations in the .cpp
+ // file have comments describing the grammar rules.
+
+ void declarations();
+
+ /**
+ * Parses an expression representing an array size. Reports errors if the array size is not
+ * valid (out of bounds, not a literal integer). Returns true if an expression was
+ * successfully parsed, even if that array size is not actually valid. In the event of a true
+ * return, outResult always contains a valid array size (even if the parsed array size was not
+ * actually valid; invalid array sizes result in a 1 to avoid additional errors downstream).
+ */
+ bool arraySize(SKSL_INT* outResult);
+
+ void directive(bool allowVersion);
+
+ bool declaration();
+
+ bool functionDeclarationEnd(Position start,
+ dsl::DSLModifiers& modifiers,
+ dsl::DSLType type,
+ const Token& name);
+
+ struct VarDeclarationsPrefix {
+ Position fPosition;
+ dsl::DSLModifiers fModifiers;
+ dsl::DSLType fType = dsl::DSLType(dsl::kVoid_Type);
+ Token fName;
+ };
+
+ bool varDeclarationsPrefix(VarDeclarationsPrefix* prefixData);
+
+ dsl::DSLStatement varDeclarationsOrExpressionStatement();
+
+ dsl::DSLStatement varDeclarations();
+
+ dsl::DSLType structDeclaration();
+
+ SkTArray<dsl::DSLGlobalVar> structVarDeclaration(Position start,
+ const dsl::DSLModifiers& modifiers);
+
+ bool allowUnsizedArrays() {
+ return ProgramConfig::IsCompute(fKind) || ProgramConfig::IsFragment(fKind) ||
+ ProgramConfig::IsVertex(fKind);
+ }
+
+ bool parseArrayDimensions(Position pos, dsl::DSLType* type);
+
+ bool parseInitializer(Position pos, dsl::DSLExpression* initializer);
+
+ void globalVarDeclarationEnd(Position position, const dsl::DSLModifiers& mods,
+ dsl::DSLType baseType, Token name);
+
+ dsl::DSLStatement localVarDeclarationEnd(Position position, const dsl::DSLModifiers& mods,
+ dsl::DSLType baseType, Token name);
+
+ std::optional<dsl::DSLParameter> parameter(size_t paramIndex);
+
+ int layoutInt();
+
+ std::string_view layoutIdentifier();
+
+ dsl::DSLLayout layout();
+
+ dsl::DSLModifiers modifiers();
+
+ dsl::DSLStatement statement();
+
+ dsl::DSLType type(dsl::DSLModifiers* modifiers);
+
+ bool interfaceBlock(const dsl::DSLModifiers& mods);
+
+ dsl::DSLStatement ifStatement();
+
+ dsl::DSLStatement doStatement();
+
+ dsl::DSLStatement whileStatement();
+
+ dsl::DSLStatement forStatement();
+
+ std::optional<dsl::DSLCase> switchCase();
+
+ dsl::DSLStatement switchStatement();
+
+ dsl::DSLStatement returnStatement();
+
+ dsl::DSLStatement breakStatement();
+
+ dsl::DSLStatement continueStatement();
+
+ dsl::DSLStatement discardStatement();
+
+ std::optional<dsl::DSLBlock> block();
+
+ dsl::DSLStatement expressionStatement();
+
+ using BinaryParseFn = dsl::DSLExpression (Parser::*)();
+ bool SK_WARN_UNUSED_RESULT operatorRight(AutoDepth& depth, Operator::Kind op,
+ BinaryParseFn rightFn, dsl::DSLExpression& result);
+
+ dsl::DSLExpression expression();
+
+ dsl::DSLExpression assignmentExpression();
+
+ dsl::DSLExpression ternaryExpression();
+
+ dsl::DSLExpression logicalOrExpression();
+
+ dsl::DSLExpression logicalXorExpression();
+
+ dsl::DSLExpression logicalAndExpression();
+
+ dsl::DSLExpression bitwiseOrExpression();
+
+ dsl::DSLExpression bitwiseXorExpression();
+
+ dsl::DSLExpression bitwiseAndExpression();
+
+ dsl::DSLExpression equalityExpression();
+
+ dsl::DSLExpression relationalExpression();
+
+ dsl::DSLExpression shiftExpression();
+
+ dsl::DSLExpression additiveExpression();
+
+ dsl::DSLExpression multiplicativeExpression();
+
+ dsl::DSLExpression unaryExpression();
+
+ dsl::DSLExpression postfixExpression();
+
+ dsl::DSLExpression swizzle(Position pos, dsl::DSLExpression base,
+ std::string_view swizzleMask, Position maskPos);
+
+ dsl::DSLExpression call(Position pos, dsl::DSLExpression base, ExpressionArray args);
+
+ dsl::DSLExpression suffix(dsl::DSLExpression base);
+
+ dsl::DSLExpression term();
+
+ bool intLiteral(SKSL_INT* dest);
+
+ bool floatLiteral(SKSL_FLOAT* dest);
+
+ bool boolLiteral(bool* dest);
+
+ bool identifier(std::string_view* dest);
+
+ std::shared_ptr<SymbolTable>& symbolTable();
+
+ void addToSymbolTable(dsl::DSLVarBase& var, Position pos = {});
+
+ class Checkpoint {
+ public:
+ Checkpoint(Parser* p) : fParser(p) {
+ fPushbackCheckpoint = fParser->fPushback;
+ fLexerCheckpoint = fParser->fLexer.getCheckpoint();
+ fOldErrorReporter = &dsl::GetErrorReporter();
+ fOldEncounteredFatalError = fParser->fEncounteredFatalError;
+ SkASSERT(fOldErrorReporter);
+ dsl::SetErrorReporter(&fErrorReporter);
+ }
+
+ ~Checkpoint() {
+ SkASSERTF(!fOldErrorReporter,
+ "Checkpoint was not accepted or rewound before destruction");
+ }
+
+ void accept() {
+ this->restoreErrorReporter();
+ // Parser errors should have been fatal, but we can encounter other errors like type
+ // mismatches despite accepting the parse. Forward those messages to the actual error
+ // handler now.
+ fErrorReporter.forwardErrors();
+ }
+
+ void rewind() {
+ this->restoreErrorReporter();
+ fParser->fPushback = fPushbackCheckpoint;
+ fParser->fLexer.rewindToCheckpoint(fLexerCheckpoint);
+ fParser->fEncounteredFatalError = fOldEncounteredFatalError;
+ }
+
+ private:
+ class ForwardingErrorReporter : public ErrorReporter {
+ public:
+ void handleError(std::string_view msg, Position pos) override {
+ fErrors.push_back({std::string(msg), pos});
+ }
+
+ void forwardErrors() {
+ for (Error& error : fErrors) {
+ dsl::GetErrorReporter().error(error.fPos, error.fMsg);
+ }
+ }
+
+ private:
+ struct Error {
+ std::string fMsg;
+ Position fPos;
+ };
+
+ SkTArray<Error> fErrors;
+ };
+
+ void restoreErrorReporter() {
+ SkASSERT(fOldErrorReporter);
+ dsl::SetErrorReporter(fOldErrorReporter);
+ fOldErrorReporter = nullptr;
+ }
+
+ Parser* fParser;
+ Token fPushbackCheckpoint;
+ SkSL::Lexer::Checkpoint fLexerCheckpoint;
+ ForwardingErrorReporter fErrorReporter;
+ ErrorReporter* fOldErrorReporter;
+ bool fOldEncounteredFatalError;
+ };
+
+ Compiler& fCompiler;
+ ProgramSettings fSettings;
+ ErrorReporter* fErrorReporter;
+ bool fEncounteredFatalError;
+ ProgramKind fKind;
+ std::unique_ptr<std::string> fText;
+ Lexer fLexer;
+ // current parse depth, used to enforce a recursion limit to try to keep us from overflowing the
+ // stack on pathological inputs
+ int fDepth = 0;
+ Token fPushback;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/SkSLPool.cpp b/gfx/skia/skia/src/sksl/SkSLPool.cpp
new file mode 100644
index 0000000000..5ef5d0065e
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLPool.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/SkSLPool.h"
+
+#include "include/core/SkTypes.h"
+
+#if defined(SK_GANESH)
+// With GPU support, SkSL::MemoryPool is really GrMemoryPool
+#include "src/gpu/ganesh/GrMemoryPool.h"
+#endif
+
+#define VLOG(...) // printf(__VA_ARGS__)
+
+namespace SkSL {
+
+static thread_local MemoryPool* sMemPool = nullptr;
+
+static MemoryPool* get_thread_local_memory_pool() {
+ return sMemPool;
+}
+
+static void set_thread_local_memory_pool(MemoryPool* memPool) {
+ sMemPool = memPool;
+}
+
+Pool::~Pool() {
+ if (get_thread_local_memory_pool() == fMemPool.get()) {
+ SkDEBUGFAIL("SkSL pool is being destroyed while it is still attached to the thread");
+ set_thread_local_memory_pool(nullptr);
+ }
+
+ fMemPool->reportLeaks();
+ SkASSERT(fMemPool->isEmpty());
+
+ VLOG("DELETE Pool:0x%016llX\n", (uint64_t)fMemPool.get());
+}
+
+std::unique_ptr<Pool> Pool::Create() {
+ auto pool = std::unique_ptr<Pool>(new Pool);
+ pool->fMemPool = MemoryPool::Make(/*preallocSize=*/65536, /*minAllocSize=*/32768);
+ VLOG("CREATE Pool:0x%016llX\n", (uint64_t)pool->fMemPool.get());
+ return pool;
+}
+
+bool Pool::IsAttached() {
+ return get_thread_local_memory_pool();
+}
+
+void Pool::attachToThread() {
+ VLOG("ATTACH Pool:0x%016llX\n", (uint64_t)fMemPool.get());
+ SkASSERT(get_thread_local_memory_pool() == nullptr);
+ set_thread_local_memory_pool(fMemPool.get());
+}
+
+void Pool::detachFromThread() {
+ MemoryPool* memPool = get_thread_local_memory_pool();
+ VLOG("DETACH Pool:0x%016llX\n", (uint64_t)memPool);
+ SkASSERT(memPool == fMemPool.get());
+ memPool->resetScratchSpace();
+ set_thread_local_memory_pool(nullptr);
+}
+
+void* Pool::AllocMemory(size_t size) {
+ // Is a pool attached?
+ MemoryPool* memPool = get_thread_local_memory_pool();
+ if (memPool) {
+ void* ptr = memPool->allocate(size);
+ VLOG("ALLOC Pool:0x%016llX 0x%016llX\n", (uint64_t)memPool, (uint64_t)ptr);
+ return ptr;
+ }
+
+ // There's no pool attached. Allocate memory using the system allocator.
+ void* ptr = ::operator new(size);
+ VLOG("ALLOC Pool:__________________ 0x%016llX\n", (uint64_t)ptr);
+ return ptr;
+}
+
+void Pool::FreeMemory(void* ptr) {
+ // Is a pool attached?
+ MemoryPool* memPool = get_thread_local_memory_pool();
+ if (memPool) {
+ VLOG("FREE Pool:0x%016llX 0x%016llX\n", (uint64_t)memPool, (uint64_t)ptr);
+ memPool->release(ptr);
+ return;
+ }
+
+ // There's no pool attached. Free it using the system allocator.
+ VLOG("FREE Pool:__________________ 0x%016llX\n", (uint64_t)ptr);
+ ::operator delete(ptr);
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/SkSLPool.h b/gfx/skia/skia/src/sksl/SkSLPool.h
new file mode 100644
index 0000000000..9e64d44b9a
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLPool.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_POOL
+#define SKSL_POOL
+
+#include "src/sksl/SkSLMemoryPool.h"
+
+#include <cstddef>
+#include <memory>
+
+namespace SkSL {
+
+/**
+ * Efficiently allocates memory in an SkSL program. Optimized for allocate/release performance over
+ * memory efficiency.
+ *
+ * All allocated memory must be released back to the pool before it can be destroyed or recycled.
+ */
+
+class Pool {
+public:
+ ~Pool();
+
+ // Creates a pool to store objects during program creation. Call attachToThread() to start using
+ // the pool for its allocations. When your program is complete, call pool->detachFromThread() to
+ // take ownership of the pool and its allocations. Before freeing any of the program's
+ // allocations, make sure to reattach the pool by calling pool->attachToThread() again.
+ static std::unique_ptr<Pool> Create();
+
+ // Attaches a pool to the current thread.
+ // It is an error to call this while a pool is already attached.
+ void attachToThread();
+
+ // Once you are done creating or destroying objects in the pool, detach it from the thread.
+ // It is an error to call this while no pool is attached.
+ void detachFromThread();
+
+ // Allocates memory from the thread pool. If the pool is exhausted, an additional block of pool
+ // storage will be created to hold the data.
+ static void* AllocMemory(size_t size);
+
+ // Releases memory that was created by AllocMemory. All objects in the pool must be freed before
+ // the pool can be destroyed.
+ static void FreeMemory(void* ptr);
+
+ static bool IsAttached();
+
+private:
+ Pool() = default; // use Create to make a pool
+ std::unique_ptr<SkSL::MemoryPool> fMemPool;
+};
+
+/**
+ * If your class inherits from Poolable, its objects will be allocated from the pool.
+ */
+class Poolable {
+public:
+ // Override operator new and delete to allow us to use a memory pool.
+ static void* operator new(const size_t size) {
+ return Pool::AllocMemory(size);
+ }
+
+ static void operator delete(void* ptr) {
+ Pool::FreeMemory(ptr);
+ }
+};
+
+/**
+ * Temporarily attaches a pool to the current thread within a scope.
+ */
+class AutoAttachPoolToThread {
+public:
+ AutoAttachPoolToThread(Pool* p) : fPool(p) {
+ if (fPool) {
+ fPool->attachToThread();
+ }
+ }
+ ~AutoAttachPoolToThread() {
+ if (fPool) {
+ fPool->detachFromThread();
+ }
+ }
+
+private:
+ Pool* fPool = nullptr;
+};
+
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/SkSLPosition.cpp b/gfx/skia/skia/src/sksl/SkSLPosition.cpp
new file mode 100644
index 0000000000..494accaf7b
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLPosition.cpp
@@ -0,0 +1,34 @@
+/*
+ * 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/sksl/SkSLPosition.h"
+
+#include <algorithm>
+
+namespace SkSL {
+
+int Position::line(std::string_view source) const {
+ SkASSERT(this->valid());
+ if (fStartOffset == -1) {
+ return -1;
+ }
+ if (!source.data()) {
+ return -1;
+ }
+ // we allow the offset to equal the length, because that's where TK_END_OF_FILE is reported
+ SkASSERT(fStartOffset <= (int)source.length());
+ int offset = std::min(fStartOffset, (int)source.length());
+ int line = 1;
+ for (int i = 0; i < offset; i++) {
+ if (source[i] == '\n') {
+ ++line;
+ }
+ }
+ return line;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/SkSLProgramSettings.h b/gfx/skia/skia/src/sksl/SkSLProgramSettings.h
new file mode 100644
index 0000000000..48622bb0c3
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLProgramSettings.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_PROGRAMSETTINGS
+#define SKSL_PROGRAMSETTINGS
+
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLProgramKind.h"
+#include "include/sksl/SkSLVersion.h"
+
+#include <vector>
+
+namespace SkSL {
+
+/**
+ * Holds the compiler settings for a program.
+ */
+struct ProgramSettings {
+ // If true, the destination fragment color can be read from sk_FragColor. It must be declared
+ // inout. This is only supported in GLSL, when framebuffer-fetch is used.
+ bool fFragColorIsInOut = false;
+ // if true, all halfs are forced to be floats
+ bool fForceHighPrecision = false;
+ // if true, add -0.5 bias to LOD of all texture lookups
+ bool fSharpenTextures = false;
+ // If true, sk_FragCoord, the dFdy gradient, and sk_Clockwise won't be modified by the
+ // rtFlip. Additionally, the 'fUseFlipRTUniform' boolean will be forced to false so no rtFlip
+ // uniform will be emitted.
+ bool fForceNoRTFlip = false;
+ // if the program needs to create an RTFlip uniform, this is its offset in the uniform buffer
+ int fRTFlipOffset = -1;
+ // if the program needs to create an RTFlip uniform and is creating SPIR-V, this is the binding
+ // and set number of the uniform buffer.
+ int fRTFlipBinding = -1;
+ int fRTFlipSet = -1;
+ // If layout(set=S, binding=B) is not specified for a uniform, these values will be used.
+ // At present, zero is always used by our backends.
+ int fDefaultUniformSet = 0;
+ int fDefaultUniformBinding = 0;
+ // Enables the SkSL optimizer. Note that we never disable optimizations which are needed to
+ // fully evaluate constant-expressions, like constant folding or constant-intrinsic evaluation.
+ bool fOptimize = true;
+ // (Requires fOptimize = true) Removes any uncalled functions other than main(). Note that a
+ // function which starts out being used may end up being uncalled after optimization.
+ bool fRemoveDeadFunctions = true;
+ // (Requires fOptimize = true) Removes variables which are never used.
+ bool fRemoveDeadVariables = true;
+ // (Requires fOptimize = true) When greater than zero, enables the inliner. The threshold value
+ // sets an upper limit on the acceptable amount of code growth from inlining.
+ int fInlineThreshold = SkSL::kDefaultInlineThreshold;
+ // If true, every function in the generated program will be given the `noinline` modifier.
+ bool fForceNoInline = false;
+ // If true, implicit conversions to lower precision numeric types are allowed (e.g., float to
+ // half). These are always allowed when compiling Runtime Effects.
+ bool fAllowNarrowingConversions = false;
+ // If true, then Debug code will run SPIR-V output through the validator to ensure its
+ // correctness
+ bool fValidateSPIRV = true;
+ // If true, any synthetic uniforms must use push constant syntax
+ bool fUsePushConstants = false;
+ // TODO(skia:11209) - Replace this with a "promised" capabilities?
+ // Sets a maximum SkSL version. Compilation will fail if the program uses features that aren't
+ // allowed at the requested version. For instance, a valid program must have fully-unrollable
+ // `for` loops at version 100, but any loop structure is allowed at version 300.
+ SkSL::Version fMaxVersionAllowed = SkSL::Version::k100;
+ // If true, SkVM debug traces will contain the `trace_var` opcode. This opcode can cause the
+ // generated code to contain a lot of extra computations, because we need to explicitly compute
+ // every temporary value, even ones that would otherwise be optimized away entirely. The other
+ // debug opcodes are much less invasive on the generated code.
+ bool fAllowTraceVarInSkVMDebugTrace = true;
+ // If true, SkSL will use a memory pool for all IR nodes when compiling a program. This is
+ // usually a significant speed increase, but uses more memory, so it is a good idea for programs
+ // that will be freed shortly after compilation. It can also be useful to disable this flag when
+ // investigating memory corruption. (This controls behavior of the SkSL compiler, not the code
+ // we generate.)
+ bool fUseMemoryPool = true;
+ // If true, VarDeclaration can be cloned for testing purposes. See VarDeclaration::clone for
+ // more information.
+ bool fAllowVarDeclarationCloneForTesting = false;
+ // If true, SPIR-V codegen restricted to a subset supported by Dawn.
+ // TODO(skia:13840, skia:14023): Remove this setting when Skia can use WGSL on Dawn.
+ bool fSPIRVDawnCompatMode = false;
+};
+
+/**
+ * All the configuration data for a given program.
+ */
+struct ProgramConfig {
+ /** True if we are currently processing one of the built-in SkSL include modules. */
+ bool fIsBuiltinCode;
+ ProgramKind fKind;
+ ProgramSettings fSettings;
+
+ // When enforcesSkSLVersion() is true, this determines the available feature set that will be
+ // enforced. This is set automatically when the `#version` directive is parsed.
+ SkSL::Version fRequiredSkSLVersion = SkSL::Version::k100;
+
+ bool enforcesSkSLVersion() const {
+ return IsRuntimeEffect(fKind);
+ }
+
+ bool strictES2Mode() const {
+ // TODO(skia:11209): Remove the first condition - so this is just based on #version.
+ // Make it more generic (eg, isVersionLT) checking.
+ return fSettings.fMaxVersionAllowed == Version::k100 &&
+ fRequiredSkSLVersion == Version::k100 &&
+ this->enforcesSkSLVersion();
+ }
+
+ const char* versionDescription() const {
+ if (this->enforcesSkSLVersion()) {
+ switch (fRequiredSkSLVersion) {
+ case Version::k100: return "#version 100\n";
+ case Version::k300: return "#version 300\n";
+ }
+ }
+ return "";
+ }
+
+ static bool IsFragment(ProgramKind kind) {
+ return kind == ProgramKind::kFragment ||
+ kind == ProgramKind::kGraphiteFragment;
+ }
+
+ static bool IsVertex(ProgramKind kind) {
+ return kind == ProgramKind::kVertex ||
+ kind == ProgramKind::kGraphiteVertex;
+ }
+
+ static bool IsCompute(ProgramKind kind) {
+ return kind == ProgramKind::kCompute;
+ }
+
+ static bool IsRuntimeEffect(ProgramKind kind) {
+ return (kind == ProgramKind::kRuntimeColorFilter ||
+ kind == ProgramKind::kRuntimeShader ||
+ kind == ProgramKind::kRuntimeBlender ||
+ kind == ProgramKind::kPrivateRuntimeColorFilter ||
+ kind == ProgramKind::kPrivateRuntimeShader ||
+ kind == ProgramKind::kPrivateRuntimeBlender ||
+ kind == ProgramKind::kMeshVertex ||
+ kind == ProgramKind::kMeshFragment);
+ }
+
+ static bool AllowsPrivateIdentifiers(ProgramKind kind) {
+ return (kind != ProgramKind::kRuntimeColorFilter &&
+ kind != ProgramKind::kRuntimeShader &&
+ kind != ProgramKind::kRuntimeBlender &&
+ kind != ProgramKind::kMeshVertex &&
+ kind != ProgramKind::kMeshFragment);
+ }
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/SkSLSampleUsage.cpp b/gfx/skia/skia/src/sksl/SkSLSampleUsage.cpp
new file mode 100644
index 0000000000..9b908b35e8
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLSampleUsage.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/private/SkSLSampleUsage.h"
+
+#include <algorithm>
+
+namespace SkSL {
+
+SampleUsage SampleUsage::merge(const SampleUsage& other) {
+ // This function is only used in Analysis::MergeSampleUsageVisitor to determine the combined
+ // SampleUsage for a child fp/shader/etc. We should never see matrix sampling here.
+ SkASSERT(fKind != Kind::kUniformMatrix && other.fKind != Kind::kUniformMatrix);
+
+ static_assert(Kind::kExplicit > Kind::kPassThrough);
+ static_assert(Kind::kPassThrough > Kind::kNone);
+ fKind = std::max(fKind, other.fKind);
+
+ return *this;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/SkSLString.cpp b/gfx/skia/skia/src/sksl/SkSLString.cpp
new file mode 100644
index 0000000000..c746ff2823
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLString.cpp
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLString.h"
+#include "include/private/base/SkAssert.h"
+#include "src/base/SkStringView.h"
+
+#include <cerrno>
+#include <cmath>
+#include <cstdarg>
+#include <cstdio>
+#include <cstdlib>
+#include <locale>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <string_view>
+
+template <typename RoundtripType, int kFullPrecision>
+static std::string to_string_impl(RoundtripType value) {
+ std::stringstream buffer;
+ buffer.imbue(std::locale::classic());
+ buffer.precision(7);
+ buffer << value;
+ std::string text = buffer.str();
+
+ double roundtripped;
+ buffer >> roundtripped;
+ if (value != (RoundtripType)roundtripped && std::isfinite(value)) {
+ buffer.str({});
+ buffer.clear();
+ buffer.precision(kFullPrecision);
+ buffer << value;
+ text = buffer.str();
+ SkASSERTF((buffer >> roundtripped, value == (RoundtripType)roundtripped),
+ "%.17g -> %s -> %.17g", value, text.c_str(), roundtripped);
+ }
+
+ // We need to emit a decimal point to distinguish floats from ints.
+ if (!skstd::contains(text, '.') && !skstd::contains(text, 'e')) {
+ text += ".0";
+ }
+
+ return text;
+}
+
+std::string skstd::to_string(float value) {
+ return to_string_impl<float, 9>(value);
+}
+
+std::string skstd::to_string(double value) {
+ return to_string_impl<double, 17>(value);
+}
+
+bool SkSL::stod(std::string_view s, SKSL_FLOAT* value) {
+ std::string str(s.data(), s.size());
+ std::stringstream buffer(str);
+ buffer.imbue(std::locale::classic());
+ buffer >> *value;
+ return !buffer.fail() && std::isfinite(*value);
+}
+
+bool SkSL::stoi(std::string_view s, SKSL_INT* value) {
+ if (s.empty()) {
+ return false;
+ }
+ char suffix = s.back();
+ if (suffix == 'u' || suffix == 'U') {
+ s.remove_suffix(1);
+ }
+ std::string str(s); // s is not null-terminated
+ const char* strEnd = str.data() + str.length();
+ char* p;
+ errno = 0;
+ unsigned long long result = strtoull(str.data(), &p, /*base=*/0);
+ *value = static_cast<SKSL_INT>(result);
+ return p == strEnd && errno == 0 && result <= 0xFFFFFFFF;
+}
+
+std::string SkSL::String::printf(const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ std::string result;
+ vappendf(&result, fmt, args);
+ va_end(args);
+ return result;
+}
+
+void SkSL::String::appendf(std::string *str, const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ vappendf(str, fmt, args);
+ va_end(args);
+}
+
+void SkSL::String::vappendf(std::string *str, const char* fmt, va_list args) {
+ #define BUFFER_SIZE 256
+ char buffer[BUFFER_SIZE];
+ va_list reuse;
+ va_copy(reuse, args);
+ size_t size = vsnprintf(buffer, BUFFER_SIZE, fmt, args);
+ if (BUFFER_SIZE >= size + 1) {
+ str->append(buffer, size);
+ } else {
+ auto newBuffer = std::unique_ptr<char[]>(new char[size + 1]);
+ vsnprintf(newBuffer.get(), size + 1, fmt, reuse);
+ str->append(newBuffer.get(), size);
+ }
+ va_end(reuse);
+}
diff --git a/gfx/skia/skia/src/sksl/SkSLStringStream.h b/gfx/skia/skia/src/sksl/SkSLStringStream.h
new file mode 100644
index 0000000000..aabda3894e
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLStringStream.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_STRINGSTREAM
+#define SKSL_STRINGSTREAM
+
+#include "include/core/SkData.h"
+#include "include/core/SkStream.h"
+#include "src/sksl/SkSLOutputStream.h"
+
+namespace SkSL {
+
+class StringStream : public OutputStream {
+public:
+ void write8(uint8_t b) override {
+ SkASSERT(fString.empty());
+ fStream.write8(b);
+ }
+
+ void writeText(const char* s) override {
+ SkASSERT(fString.empty());
+ fStream.writeText(s);
+ }
+
+ void write(const void* s, size_t size) override {
+ SkASSERT(fString.empty());
+ fStream.write(s, size);
+ }
+
+ size_t bytesWritten() const {
+ return fStream.bytesWritten();
+ }
+
+ const std::string& str() const {
+ if (!fString.size()) {
+ sk_sp<SkData> data = fStream.detachAsData();
+ fString = std::string((const char*) data->data(), data->size());
+ }
+ return fString;
+ }
+
+ void reset() {
+ fStream.reset();
+ fString = "";
+ }
+
+private:
+ mutable SkDynamicMemoryWStream fStream;
+ mutable std::string fString;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/SkSLThreadContext.cpp b/gfx/skia/skia/src/sksl/SkSLThreadContext.cpp
new file mode 100644
index 0000000000..58d9b40d83
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLThreadContext.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/SkSLThreadContext.h"
+
+#include "include/private/SkSLProgramElement.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLModifiersPool.h"
+#include "src/sksl/SkSLPool.h"
+#include "src/sksl/ir/SkSLSymbolTable.h"
+
+#include <type_traits>
+
+namespace SkSL {
+
+ThreadContext::ThreadContext(SkSL::Compiler* compiler,
+ SkSL::ProgramKind kind,
+ const SkSL::ProgramSettings& settings,
+ const SkSL::Module* module,
+ bool isModule)
+ : fCompiler(compiler)
+ , fOldConfig(fCompiler->fContext->fConfig)
+ , fOldModifiersPool(fCompiler->fContext->fModifiersPool)
+ , fOldErrorReporter(*fCompiler->fContext->fErrors)
+ , fSettings(settings) {
+ if (!isModule) {
+ if (settings.fUseMemoryPool) {
+ fPool = Pool::Create();
+ fPool->attachToThread();
+ }
+ fModifiersPool = std::make_unique<SkSL::ModifiersPool>();
+ fCompiler->fContext->fModifiersPool = fModifiersPool.get();
+ }
+
+ fConfig = std::make_unique<SkSL::ProgramConfig>();
+ fConfig->fKind = kind;
+ fConfig->fSettings = settings;
+ fConfig->fIsBuiltinCode = isModule;
+ fCompiler->fContext->fConfig = fConfig.get();
+ fCompiler->fContext->fErrors = &fDefaultErrorReporter;
+ fCompiler->fContext->fModule = module;
+ fCompiler->fSymbolTable = module->fSymbols;
+ this->setupSymbolTable();
+}
+
+ThreadContext::~ThreadContext() {
+ if (SymbolTable()) {
+ fCompiler->fSymbolTable = nullptr;
+ fProgramElements.clear();
+ } else {
+ // We should only be here with a null symbol table if ReleaseProgram was called
+ SkASSERT(fProgramElements.empty());
+ }
+ fCompiler->fContext->fErrors = &fOldErrorReporter;
+ fCompiler->fContext->fConfig = fOldConfig;
+ fCompiler->fContext->fModifiersPool = fOldModifiersPool;
+ if (fPool) {
+ fPool->detachFromThread();
+ }
+}
+
+void ThreadContext::setupSymbolTable() {
+ SkSL::Context& context = *fCompiler->fContext;
+ SymbolTable::Push(&fCompiler->fSymbolTable, context.fConfig->fIsBuiltinCode);
+
+ SkSL::SymbolTable& symbolTable = *fCompiler->fSymbolTable;
+ symbolTable.markModuleBoundary();
+}
+
+SkSL::Context& ThreadContext::Context() {
+ return Compiler().context();
+}
+
+const SkSL::ProgramSettings& ThreadContext::Settings() {
+ return Context().fConfig->fSettings;
+}
+
+std::shared_ptr<SkSL::SymbolTable>& ThreadContext::SymbolTable() {
+ return Compiler().fSymbolTable;
+}
+
+const SkSL::Modifiers* ThreadContext::Modifiers(const SkSL::Modifiers& modifiers) {
+ return Context().fModifiersPool->add(modifiers);
+}
+
+ThreadContext::RTAdjustData& ThreadContext::RTAdjustState() {
+ return Instance().fRTAdjust;
+}
+
+void ThreadContext::SetErrorReporter(ErrorReporter* errorReporter) {
+ SkASSERT(errorReporter);
+ Context().fErrors = errorReporter;
+}
+
+void ThreadContext::ReportError(std::string_view msg, Position pos) {
+ GetErrorReporter().error(pos, msg);
+}
+
+void ThreadContext::DefaultErrorReporter::handleError(std::string_view msg, Position pos) {
+ SK_ABORT("error: %.*s\nNo SkSL error reporter configured, treating this as a fatal error\n",
+ (int)msg.length(), msg.data());
+}
+
+thread_local ThreadContext* instance = nullptr;
+
+bool ThreadContext::IsActive() {
+ return instance != nullptr;
+}
+
+ThreadContext& ThreadContext::Instance() {
+ SkASSERTF(instance, "dsl::Start() has not been called");
+ return *instance;
+}
+
+void ThreadContext::SetInstance(std::unique_ptr<ThreadContext> newInstance) {
+ SkASSERT((instance == nullptr) != (newInstance == nullptr));
+ delete instance;
+ instance = newInstance.release();
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/SkSLThreadContext.h b/gfx/skia/skia/src/sksl/SkSLThreadContext.h
new file mode 100644
index 0000000000..853ec4ed98
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLThreadContext.h
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_THREADCONTEXT
+#define SKSL_THREADCONTEXT
+
+#include "include/core/SkTypes.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/ir/SkSLProgram.h"
+
+#include <cstdint>
+#include <memory>
+#include <string_view>
+#include <vector>
+
+namespace SkSL {
+
+class Compiler;
+class ModifiersPool;
+class Pool;
+class ProgramElement;
+class SymbolTable;
+class Variable;
+enum class ProgramKind : int8_t;
+struct Modifiers;
+struct Module;
+
+namespace dsl {
+
+class DSLCore;
+
+} // namespace dsl
+
+/**
+ * Thread-safe class that tracks per-thread state associated with SkSL output.
+ */
+class ThreadContext {
+public:
+ ThreadContext(SkSL::Compiler* compiler,
+ SkSL::ProgramKind kind,
+ const SkSL::ProgramSettings& settings,
+ const SkSL::Module* module,
+ bool isModule);
+ ~ThreadContext();
+
+ /**
+ * Returns true if the DSL has been started.
+ */
+ static bool IsActive();
+
+ /**
+ * Returns the Compiler used by DSL operations in the current thread.
+ */
+ static SkSL::Compiler& Compiler() { return *Instance().fCompiler; }
+
+ /**
+ * Returns the Context used by DSL operations in the current thread.
+ */
+ static SkSL::Context& Context();
+
+ /**
+ * Returns the Settings used by DSL operations in the current thread.
+ */
+ static const SkSL::ProgramSettings& Settings();
+
+ /**
+ * Returns the Program::Inputs used by the current thread.
+ */
+ static SkSL::Program::Inputs& Inputs() { return Instance().fInputs; }
+
+ /**
+ * Returns the collection to which DSL program elements in this thread should be appended.
+ */
+ static std::vector<std::unique_ptr<SkSL::ProgramElement>>& ProgramElements() {
+ return Instance().fProgramElements;
+ }
+
+ static std::vector<const ProgramElement*>& SharedElements() {
+ return Instance().fSharedElements;
+ }
+
+ /**
+ * Returns the current SymbolTable.
+ */
+ static std::shared_ptr<SkSL::SymbolTable>& SymbolTable();
+
+ /**
+ * Returns the current memory pool.
+ */
+ static std::unique_ptr<Pool>& MemoryPool() { return Instance().fPool; }
+
+ /**
+ * Returns the current modifiers pool.
+ */
+ static std::unique_ptr<ModifiersPool>& GetModifiersPool() { return Instance().fModifiersPool; }
+
+ /**
+ * Returns the current ProgramConfig.
+ */
+ static const std::unique_ptr<ProgramConfig>& GetProgramConfig() { return Instance().fConfig; }
+
+ static bool IsModule() { return GetProgramConfig()->fIsBuiltinCode; }
+
+ /**
+ * Returns the final pointer to a pooled Modifiers object that should be used to represent the
+ * given modifiers.
+ */
+ static const SkSL::Modifiers* Modifiers(const SkSL::Modifiers& modifiers);
+
+ struct RTAdjustData {
+ // Points to a standalone sk_RTAdjust variable, if one exists.
+ const Variable* fVar = nullptr;
+ // Points to the interface block containing an sk_RTAdjust field, if one exists.
+ const Variable* fInterfaceBlock = nullptr;
+ // If fInterfaceBlock is non-null, contains the index of the sk_RTAdjust field within it.
+ int fFieldIndex = -1;
+ };
+
+ /**
+ * Returns a struct containing information about the RTAdjust variable.
+ */
+ static RTAdjustData& RTAdjustState();
+
+ /**
+ * Returns the ErrorReporter associated with the current thread. This object will be notified
+ * when any DSL errors occur.
+ */
+ static ErrorReporter& GetErrorReporter() {
+ return *Context().fErrors;
+ }
+
+ static void SetErrorReporter(ErrorReporter* errorReporter);
+
+ /**
+ * Notifies the current ErrorReporter that an error has occurred. The default error handler
+ * prints the message to stderr and aborts.
+ */
+ static void ReportError(std::string_view msg, Position pos = Position{});
+
+ static ThreadContext& Instance();
+
+ static void SetInstance(std::unique_ptr<ThreadContext> instance);
+
+private:
+ class DefaultErrorReporter : public ErrorReporter {
+ void handleError(std::string_view msg, Position pos) override;
+ };
+
+ void setupSymbolTable();
+
+ std::unique_ptr<SkSL::ProgramConfig> fConfig;
+ std::unique_ptr<SkSL::ModifiersPool> fModifiersPool;
+ SkSL::Compiler* fCompiler;
+ std::unique_ptr<Pool> fPool;
+ SkSL::ProgramConfig* fOldConfig;
+ SkSL::ModifiersPool* fOldModifiersPool;
+ std::vector<std::unique_ptr<SkSL::ProgramElement>> fProgramElements;
+ std::vector<const SkSL::ProgramElement*> fSharedElements;
+ DefaultErrorReporter fDefaultErrorReporter;
+ ErrorReporter& fOldErrorReporter;
+ ProgramSettings fSettings;
+ RTAdjustData fRTAdjust;
+ Program::Inputs fInputs;
+
+ friend class dsl::DSLCore;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/SkSLUtil.cpp b/gfx/skia/skia/src/sksl/SkSLUtil.cpp
new file mode 100644
index 0000000000..8efeb21790
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLUtil.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/SkSLUtil.h"
+
+#include "src/core/SkSLTypeShared.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLOutputStream.h"
+#include "src/sksl/SkSLStringStream.h"
+#include "src/sksl/ir/SkSLType.h"
+
+#include <string>
+
+namespace SkSL {
+
+// TODO: Once Graphite has its own GPU-caps system, SK_GRAPHITE should get its own mode.
+// At the moment, it either mimics what GrShaderCaps reports, or it uses these hard-coded values
+// depending on the build.
+#if defined(SKSL_STANDALONE) || !defined(SK_GANESH)
+std::unique_ptr<ShaderCaps> ShaderCapsFactory::MakeShaderCaps() {
+ std::unique_ptr<ShaderCaps> standalone = std::make_unique<ShaderCaps>();
+ standalone->fShaderDerivativeSupport = true;
+ standalone->fExplicitTextureLodSupport = true;
+ standalone->fFlatInterpolationSupport = true;
+ standalone->fNoPerspectiveInterpolationSupport = true;
+ standalone->fSampleMaskSupport = true;
+ standalone->fExternalTextureSupport = true;
+ return standalone;
+}
+#else
+std::unique_ptr<ShaderCaps> ShaderCapsFactory::MakeShaderCaps() {
+ return std::make_unique<ShaderCaps>();
+}
+#endif // defined(SKSL_STANDALONE) || !defined(SK_GANESH)
+
+void write_stringstream(const StringStream& s, OutputStream& out) {
+ out.write(s.str().c_str(), s.str().size());
+}
+
+#if !defined(SKSL_STANDALONE) && (defined(SK_GANESH) || SK_SUPPORT_GRAPHITE)
+bool type_to_sksltype(const Context& context, const Type& type, SkSLType* outType) {
+ // If a new GrSL type is added, this function will need to be updated.
+ static_assert(kSkSLTypeCount == 41);
+
+ if (type.matches(*context.fTypes.fVoid )) { *outType = SkSLType::kVoid; return true; }
+ if (type.matches(*context.fTypes.fBool )) { *outType = SkSLType::kBool; return true; }
+ if (type.matches(*context.fTypes.fBool2 )) { *outType = SkSLType::kBool2; return true; }
+ if (type.matches(*context.fTypes.fBool3 )) { *outType = SkSLType::kBool3; return true; }
+ if (type.matches(*context.fTypes.fBool4 )) { *outType = SkSLType::kBool4; return true; }
+ if (type.matches(*context.fTypes.fShort )) { *outType = SkSLType::kShort; return true; }
+ if (type.matches(*context.fTypes.fShort2 )) { *outType = SkSLType::kShort2; return true; }
+ if (type.matches(*context.fTypes.fShort3 )) { *outType = SkSLType::kShort3; return true; }
+ if (type.matches(*context.fTypes.fShort4 )) { *outType = SkSLType::kShort4; return true; }
+ if (type.matches(*context.fTypes.fUShort )) { *outType = SkSLType::kUShort; return true; }
+ if (type.matches(*context.fTypes.fUShort2 )) { *outType = SkSLType::kUShort2; return true; }
+ if (type.matches(*context.fTypes.fUShort3 )) { *outType = SkSLType::kUShort3; return true; }
+ if (type.matches(*context.fTypes.fUShort4 )) { *outType = SkSLType::kUShort4; return true; }
+ if (type.matches(*context.fTypes.fFloat )) { *outType = SkSLType::kFloat; return true; }
+ if (type.matches(*context.fTypes.fFloat2 )) { *outType = SkSLType::kFloat2; return true; }
+ if (type.matches(*context.fTypes.fFloat3 )) { *outType = SkSLType::kFloat3; return true; }
+ if (type.matches(*context.fTypes.fFloat4 )) { *outType = SkSLType::kFloat4; return true; }
+ if (type.matches(*context.fTypes.fFloat2x2)) { *outType = SkSLType::kFloat2x2; return true; }
+ if (type.matches(*context.fTypes.fFloat3x3)) { *outType = SkSLType::kFloat3x3; return true; }
+ if (type.matches(*context.fTypes.fFloat4x4)) { *outType = SkSLType::kFloat4x4; return true; }
+ if (type.matches(*context.fTypes.fHalf )) { *outType = SkSLType::kHalf; return true; }
+ if (type.matches(*context.fTypes.fHalf2 )) { *outType = SkSLType::kHalf2; return true; }
+ if (type.matches(*context.fTypes.fHalf3 )) { *outType = SkSLType::kHalf3; return true; }
+ if (type.matches(*context.fTypes.fHalf4 )) { *outType = SkSLType::kHalf4; return true; }
+ if (type.matches(*context.fTypes.fHalf2x2 )) { *outType = SkSLType::kHalf2x2; return true; }
+ if (type.matches(*context.fTypes.fHalf3x3 )) { *outType = SkSLType::kHalf3x3; return true; }
+ if (type.matches(*context.fTypes.fHalf4x4 )) { *outType = SkSLType::kHalf4x4; return true; }
+ if (type.matches(*context.fTypes.fInt )) { *outType = SkSLType::kInt; return true; }
+ if (type.matches(*context.fTypes.fInt2 )) { *outType = SkSLType::kInt2; return true; }
+ if (type.matches(*context.fTypes.fInt3 )) { *outType = SkSLType::kInt3; return true; }
+ if (type.matches(*context.fTypes.fInt4 )) { *outType = SkSLType::kInt4; return true; }
+ if (type.matches(*context.fTypes.fUInt )) { *outType = SkSLType::kUInt; return true; }
+ if (type.matches(*context.fTypes.fUInt2 )) { *outType = SkSLType::kUInt2; return true; }
+ if (type.matches(*context.fTypes.fUInt3 )) { *outType = SkSLType::kUInt3; return true; }
+ if (type.matches(*context.fTypes.fUInt4 )) { *outType = SkSLType::kUInt4; return true; }
+ return false;
+}
+#endif
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/SkSLUtil.h b/gfx/skia/skia/src/sksl/SkSLUtil.h
new file mode 100644
index 0000000000..92dfe537a9
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/SkSLUtil.h
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_UTIL
+#define SKSL_UTIL
+
+#include "include/core/SkTypes.h"
+#include "include/sksl/SkSLVersion.h"
+#include "src/sksl/SkSLGLSL.h"
+
+#include <memory>
+
+enum class SkSLType : char;
+
+namespace SkSL {
+
+class Context;
+class OutputStream;
+class StringStream;
+class Type;
+
+struct ShaderCaps {
+ /**
+ * Indicates how GLSL must interact with advanced blend equations. The KHR extension requires
+ * special layout qualifiers in the fragment shader.
+ */
+ enum AdvBlendEqInteraction {
+ kNotSupported_AdvBlendEqInteraction, //<! No _blend_equation_advanced extension
+ kAutomatic_AdvBlendEqInteraction, //<! No interaction required
+ kGeneralEnable_AdvBlendEqInteraction, //<! layout(blend_support_all_equations) out
+
+ kLast_AdvBlendEqInteraction = kGeneralEnable_AdvBlendEqInteraction
+ };
+
+ bool mustEnableAdvBlendEqs() const {
+ return fAdvBlendEqInteraction >= kGeneralEnable_AdvBlendEqInteraction;
+ }
+
+ bool mustDeclareFragmentShaderOutput() const {
+ return fGLSLGeneration > SkSL::GLSLGeneration::k110;
+ }
+
+ // Returns the string of an extension that must be enabled in the shader to support
+ // derivatives. If nullptr is returned then no extension needs to be enabled. Before calling
+ // this function, the caller should check that shaderDerivativeSupport exists.
+ const char* shaderDerivativeExtensionString() const {
+ SkASSERT(this->fShaderDerivativeSupport);
+ return fShaderDerivativeExtensionString;
+ }
+
+ // This returns the name of an extension that must be enabled in the shader to support external
+ // textures. In some cases, two extensions must be enabled - the second extension is returned
+ // by secondExternalTextureExtensionString(). If that function returns nullptr, then only one
+ // extension is required.
+ const char* externalTextureExtensionString() const {
+ SkASSERT(this->fExternalTextureSupport);
+ return fExternalTextureExtensionString;
+ }
+
+ const char* secondExternalTextureExtensionString() const {
+ SkASSERT(this->fExternalTextureSupport);
+ return fSecondExternalTextureExtensionString;
+ }
+
+ /**
+ * SkSL 300 requires support for derivatives, nonsquare matrices and bitwise integer operations.
+ */
+ SkSL::Version supportedSkSLVerion() const {
+ if (fShaderDerivativeSupport && fNonsquareMatrixSupport && fIntegerSupport &&
+ fGLSLGeneration >= SkSL::GLSLGeneration::k330) {
+ return SkSL::Version::k300;
+ }
+ return SkSL::Version::k100;
+ }
+
+ bool supportsDistanceFieldText() const { return fShaderDerivativeSupport; }
+
+ SkSL::GLSLGeneration fGLSLGeneration = SkSL::GLSLGeneration::k330;
+
+ bool fShaderDerivativeSupport = false;
+ /** Enables sampleGrad and sampleLod functions that don't rely on implicit derivatives */
+ bool fExplicitTextureLodSupport = false;
+ /** Indicates true 32-bit integer support, with unsigned types and bitwise operations */
+ bool fIntegerSupport = false;
+ bool fNonsquareMatrixSupport = false;
+ /** asinh(), acosh(), atanh() */
+ bool fInverseHyperbolicSupport = false;
+ bool fFBFetchSupport = false;
+ bool fFBFetchNeedsCustomOutput = false;
+ bool fUsesPrecisionModifiers = false;
+ bool fFlatInterpolationSupport = false;
+ bool fNoPerspectiveInterpolationSupport = false;
+ bool fSampleMaskSupport = false;
+ bool fExternalTextureSupport = false;
+ bool fFloatIs32Bits = true;
+
+ // isinf() is defined, and floating point infinities are handled according to IEEE standards.
+ bool fInfinitySupport = false;
+
+ // Used by SkSL to know when to generate polyfills.
+ bool fBuiltinFMASupport = true;
+ bool fBuiltinDeterminantSupport = true;
+
+ // Used for specific driver bug work arounds
+ bool fCanUseMinAndAbsTogether = true;
+ bool fCanUseFractForNegativeValues = true;
+ bool fMustForceNegatedAtanParamToFloat = false;
+ bool fMustForceNegatedLdexpParamToMultiply = false; // http://skbug.com/12076
+ // Returns whether a device incorrectly implements atan(y,x) as atan(y/x)
+ bool fAtan2ImplementedAsAtanYOverX = false;
+ // If this returns true some operation (could be a no op) must be called between floor and abs
+ // to make sure the driver compiler doesn't inline them together which can cause a driver bug in
+ // the shader.
+ bool fMustDoOpBetweenFloorAndAbs = false;
+ // The D3D shader compiler, when targeting PS 3.0 (ie within ANGLE) fails to compile certain
+ // constructs. See detailed comments in GrGLCaps.cpp.
+ bool fMustGuardDivisionEvenAfterExplicitZeroCheck = false;
+ // If false, SkSL uses a workaround so that sk_FragCoord doesn't actually query gl_FragCoord
+ bool fCanUseFragCoord = true;
+ // If true, short ints can't represent every integer in the 16-bit two's complement range as
+ // required by the spec. SKSL will always emit full ints.
+ bool fIncompleteShortIntPrecision = false;
+ // If true, then conditions in for loops need "&& true" to work around driver bugs.
+ bool fAddAndTrueToLoopCondition = false;
+ // If true, then expressions such as "x && y" or "x || y" are rewritten as ternary to work
+ // around driver bugs.
+ bool fUnfoldShortCircuitAsTernary = false;
+ bool fEmulateAbsIntFunction = false;
+ bool fRewriteDoWhileLoops = false;
+ bool fRewriteSwitchStatements = false;
+ bool fRemovePowWithConstantExponent = false;
+ // The Android emulator claims samplerExternalOES is an unknown type if a default precision
+ // statement is made for the type.
+ bool fNoDefaultPrecisionForExternalSamplers = false;
+ // ARM GPUs calculate `matrix * vector` in SPIR-V at full precision, even when the inputs are
+ // RelaxedPrecision. Rewriting the multiply as a sum of vector*scalar fixes this. (skia:11769)
+ bool fRewriteMatrixVectorMultiply = false;
+ // Rewrites matrix equality comparisons to avoid an Adreno driver bug. (skia:11308)
+ bool fRewriteMatrixComparisons = false;
+ // Strips const from function parameters in the GLSL code generator. (skia:13858)
+ bool fRemoveConstFromFunctionParameters = false;
+
+ const char* fVersionDeclString = "";
+
+ const char* fShaderDerivativeExtensionString = nullptr;
+ const char* fExternalTextureExtensionString = nullptr;
+ const char* fSecondExternalTextureExtensionString = nullptr;
+ const char* fFBFetchColorName = nullptr;
+
+ AdvBlendEqInteraction fAdvBlendEqInteraction = kNotSupported_AdvBlendEqInteraction;
+};
+
+// Various sets of caps for use in tests
+class ShaderCapsFactory {
+public:
+ static const ShaderCaps* Default() {
+ static const SkSL::ShaderCaps* sCaps = [] {
+ std::unique_ptr<ShaderCaps> caps = MakeShaderCaps();
+ caps->fVersionDeclString = "#version 400";
+ caps->fShaderDerivativeSupport = true;
+ return caps.release();
+ }();
+ return sCaps;
+ }
+
+ static const ShaderCaps* Standalone() {
+ static const SkSL::ShaderCaps* sCaps = MakeShaderCaps().release();
+ return sCaps;
+ }
+
+protected:
+ static std::unique_ptr<ShaderCaps> MakeShaderCaps();
+};
+
+#if !defined(SKSL_STANDALONE) && (defined(SK_GANESH) || defined(SK_GRAPHITE))
+bool type_to_sksltype(const Context& context, const Type& type, SkSLType* outType);
+#endif
+
+void write_stringstream(const StringStream& d, OutputStream& out);
+
+} // namespace SkSL
+
+#endif // SKSL_UTIL
diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLCanExitWithoutReturningValue.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLCanExitWithoutReturningValue.cpp
new file mode 100644
index 0000000000..015f233c4c
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/analysis/SkSLCanExitWithoutReturningValue.cpp
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLStatement.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/analysis/SkSLProgramVisitor.h"
+#include "src/sksl/ir/SkSLDoStatement.h"
+#include "src/sksl/ir/SkSLForStatement.h"
+#include "src/sksl/ir/SkSLFunctionDeclaration.h"
+#include "src/sksl/ir/SkSLIfStatement.h"
+#include "src/sksl/ir/SkSLSwitchCase.h"
+#include "src/sksl/ir/SkSLSwitchStatement.h"
+#include "src/sksl/ir/SkSLType.h"
+
+#include <memory>
+
+namespace SkSL {
+class Expression;
+namespace {
+
+class ReturnsOnAllPathsVisitor : public ProgramVisitor {
+public:
+ bool visitExpression(const Expression& expr) override {
+ // We can avoid processing expressions entirely.
+ return false;
+ }
+
+ bool visitStatement(const Statement& stmt) override {
+ switch (stmt.kind()) {
+ // Returns, breaks, or continues will stop the scan, so only one of these should ever be
+ // true.
+ case Statement::Kind::kReturn:
+ fFoundReturn = true;
+ return true;
+
+ case Statement::Kind::kBreak:
+ fFoundBreak = true;
+ return true;
+
+ case Statement::Kind::kContinue:
+ fFoundContinue = true;
+ return true;
+
+ case Statement::Kind::kIf: {
+ const IfStatement& i = stmt.as<IfStatement>();
+ ReturnsOnAllPathsVisitor trueVisitor;
+ ReturnsOnAllPathsVisitor falseVisitor;
+ trueVisitor.visitStatement(*i.ifTrue());
+ if (i.ifFalse()) {
+ falseVisitor.visitStatement(*i.ifFalse());
+ }
+ // If either branch leads to a break or continue, we report the entire if as
+ // containing a break or continue, since we don't know which side will be reached.
+ fFoundBreak = (trueVisitor.fFoundBreak || falseVisitor.fFoundBreak);
+ fFoundContinue = (trueVisitor.fFoundContinue || falseVisitor.fFoundContinue);
+ // On the other hand, we only want to report returns that definitely happen, so we
+ // require those to be found on both sides.
+ fFoundReturn = (trueVisitor.fFoundReturn && falseVisitor.fFoundReturn);
+ return fFoundBreak || fFoundContinue || fFoundReturn;
+ }
+ case Statement::Kind::kFor: {
+ const ForStatement& f = stmt.as<ForStatement>();
+ // We assume a for/while loop runs for at least one iteration; this isn't strictly
+ // guaranteed, but it's better to be slightly over-permissive here than to fail on
+ // reasonable code.
+ ReturnsOnAllPathsVisitor forVisitor;
+ forVisitor.visitStatement(*f.statement());
+ // A for loop that contains a break or continue is safe; it won't exit the entire
+ // function, just the loop. So we disregard those signals.
+ fFoundReturn = forVisitor.fFoundReturn;
+ return fFoundReturn;
+ }
+ case Statement::Kind::kDo: {
+ const DoStatement& d = stmt.as<DoStatement>();
+ // Do-while blocks are always entered at least once.
+ ReturnsOnAllPathsVisitor doVisitor;
+ doVisitor.visitStatement(*d.statement());
+ // A do-while loop that contains a break or continue is safe; it won't exit the
+ // entire function, just the loop. So we disregard those signals.
+ fFoundReturn = doVisitor.fFoundReturn;
+ return fFoundReturn;
+ }
+ case Statement::Kind::kBlock:
+ // Blocks are definitely entered and don't imply any additional control flow.
+ // If the block contains a break, continue or return, we want to keep that.
+ return INHERITED::visitStatement(stmt);
+
+ case Statement::Kind::kSwitch: {
+ // Switches are the most complex control flow we need to deal with; fortunately we
+ // already have good primitives for dissecting them. We need to verify that:
+ // - a default case exists, so that every possible input value is covered
+ // - every switch-case either (a) returns unconditionally, or
+ // (b) falls through to another case that does
+ const SwitchStatement& s = stmt.as<SwitchStatement>();
+ bool foundDefault = false;
+ bool fellThrough = false;
+ for (const std::unique_ptr<Statement>& switchStmt : s.cases()) {
+ // The default case is indicated by a null value. A switch without a default
+ // case cannot definitively return, as its value might not be in the cases list.
+ const SwitchCase& sc = switchStmt->as<SwitchCase>();
+ if (sc.isDefault()) {
+ foundDefault = true;
+ }
+ // Scan this switch-case for any exit (break, continue or return).
+ ReturnsOnAllPathsVisitor caseVisitor;
+ caseVisitor.visitStatement(sc);
+
+ // If we found a break or continue, whether conditional or not, this switch case
+ // can't be called an unconditional return. Switches absorb breaks but not
+ // continues.
+ if (caseVisitor.fFoundContinue) {
+ fFoundContinue = true;
+ return false;
+ }
+ if (caseVisitor.fFoundBreak) {
+ return false;
+ }
+ // We just confirmed that there weren't any breaks or continues. If we didn't
+ // find an unconditional return either, the switch is considered fallen-through.
+ // (There might be a conditional return, but that doesn't count.)
+ fellThrough = !caseVisitor.fFoundReturn;
+ }
+
+ // If we didn't find a default case, or the very last case fell through, this switch
+ // doesn't meet our criteria.
+ if (fellThrough || !foundDefault) {
+ return false;
+ }
+
+ // We scanned the entire switch, found a default case, and every section either fell
+ // through or contained an unconditional return.
+ fFoundReturn = true;
+ return true;
+ }
+
+ case Statement::Kind::kSwitchCase:
+ // Recurse into the switch-case.
+ return INHERITED::visitStatement(stmt);
+
+ case Statement::Kind::kDiscard:
+ case Statement::Kind::kExpression:
+ case Statement::Kind::kNop:
+ case Statement::Kind::kVarDeclaration:
+ // None of these statements could contain a return.
+ break;
+ }
+
+ return false;
+ }
+
+ bool fFoundReturn = false;
+ bool fFoundBreak = false;
+ bool fFoundContinue = false;
+
+ using INHERITED = ProgramVisitor;
+};
+
+} // namespace
+
+bool Analysis::CanExitWithoutReturningValue(const FunctionDeclaration& funcDecl,
+ const Statement& body) {
+ if (funcDecl.returnType().isVoid()) {
+ return false;
+ }
+ ReturnsOnAllPathsVisitor visitor;
+ visitor.visitStatement(body);
+ return !visitor.fFoundReturn;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLCheckProgramStructure.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLCheckProgramStructure.cpp
new file mode 100644
index 0000000000..5407d820d8
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/analysis/SkSLCheckProgramStructure.cpp
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2021 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/SkTypes.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLProgramElement.h"
+#include "include/private/SkSLStatement.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/base/SkSafeMath.h"
+#include "src/core/SkTHash.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/analysis/SkSLProgramVisitor.h"
+#include "src/sksl/ir/SkSLExpression.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/SkSLProgram.h"
+
+#include <cstddef>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace SkSL {
+
+bool Analysis::CheckProgramStructure(const Program& program, bool enforceSizeLimit) {
+ // We check the size of strict-ES2 programs; since SkVM will completely unroll them, it's
+ // important to know how large the result will be. For non-ES2 code, we compute an approximate
+ // lower bound by assuming all non-unrollable loops will execute one time only.
+ const Context& context = *program.fContext;
+
+ // If we decide that expressions are cheaper than statements, or that certain statements are
+ // more expensive than others, etc., we can always tweak these ratios as needed. A very rough
+ // ballpark estimate is currently good enough for our purposes.
+ static constexpr size_t kExpressionCost = 1;
+ static constexpr size_t kStatementCost = 1;
+ static constexpr size_t kUnknownCost = -1;
+ static constexpr size_t kProgramSizeLimit = 100000;
+ static constexpr size_t kProgramStackDepthLimit = 50;
+
+ class ProgramSizeVisitor : public ProgramVisitor {
+ public:
+ ProgramSizeVisitor(const Context& c) : fContext(c) {}
+
+ using ProgramVisitor::visitProgramElement;
+
+ size_t functionSize() const {
+ return fFunctionSize;
+ }
+
+ bool visitProgramElement(const ProgramElement& pe) override {
+ if (pe.is<FunctionDefinition>()) {
+ // Check the function-size cache map first. We don't need to visit this function if
+ // we already processed it before.
+ const FunctionDeclaration* decl = &pe.as<FunctionDefinition>().declaration();
+ if (size_t *cachedCost = fFunctionCostMap.find(decl)) {
+ // We already have this function in our map. We don't need to check it again.
+ if (*cachedCost == kUnknownCost) {
+ // If the function is present in the map with an unknown cost, we're
+ // recursively processing it--in other words, we found a cycle in the code.
+ // Unwind our stack into a string.
+ std::string msg = "\n\t" + decl->description();
+ for (auto unwind = fStack.rbegin(); unwind != fStack.rend(); ++unwind) {
+ msg = "\n\t" + (*unwind)->description() + msg;
+ if (*unwind == decl) {
+ break;
+ }
+ }
+ msg = "potential recursion (function call cycle) not allowed:" + msg;
+ fContext.fErrors->error(pe.fPosition, std::move(msg));
+ fFunctionSize = 0;
+ *cachedCost = 0;
+ return true;
+ }
+ // Set the size to its known value.
+ fFunctionSize = *cachedCost;
+ return false;
+ }
+
+ // If the function-call stack has gotten too deep, stop the analysis.
+ if (fStack.size() >= kProgramStackDepthLimit) {
+ std::string msg = "exceeded max function call depth:";
+ for (auto unwind = fStack.begin(); unwind != fStack.end(); ++unwind) {
+ msg += "\n\t" + (*unwind)->description();
+ }
+ msg += "\n\t" + decl->description();
+ fContext.fErrors->error(pe.fPosition, std::move(msg));
+ fFunctionSize = 0;
+ fFunctionCostMap.set(decl, 0);
+ return true;
+ }
+
+ // Calculate the function cost and store it in our cache.
+ fFunctionCostMap.set(decl, kUnknownCost);
+ fStack.push_back(decl);
+ fFunctionSize = 0;
+ bool result = INHERITED::visitProgramElement(pe);
+ fFunctionCostMap.set(decl, fFunctionSize);
+ fStack.pop_back();
+
+ return result;
+ }
+
+ return INHERITED::visitProgramElement(pe);
+ }
+
+ bool visitStatement(const Statement& stmt) override {
+ switch (stmt.kind()) {
+ case Statement::Kind::kFor: {
+ // We count a for-loop's unrolled size here. We expect that the init statement
+ // will be emitted once, and the test-expr, next-expr and statement will be
+ // repeated in the output for every iteration of the loop.
+ bool earlyExit = false;
+ const ForStatement& forStmt = stmt.as<ForStatement>();
+ if (forStmt.initializer() && this->visitStatement(*forStmt.initializer())) {
+ earlyExit = true;
+ }
+
+ size_t originalFunctionSize = fFunctionSize;
+ fFunctionSize = 0;
+
+ if (forStmt.next() && this->visitExpression(*forStmt.next())) {
+ earlyExit = true;
+ }
+ if (forStmt.test() && this->visitExpression(*forStmt.test())) {
+ earlyExit = true;
+ }
+ if (this->visitStatement(*forStmt.statement())) {
+ earlyExit = true;
+ }
+
+ // ES2 programs always have a known unroll count. Non-ES2 programs don't enforce
+ // a maximum program size, so it's fine to treat the loop as executing once.
+ if (const LoopUnrollInfo* unrollInfo = forStmt.unrollInfo()) {
+ fFunctionSize = SkSafeMath::Mul(fFunctionSize, unrollInfo->fCount);
+ }
+ fFunctionSize = SkSafeMath::Add(fFunctionSize, originalFunctionSize);
+ return earlyExit;
+ }
+
+ case Statement::Kind::kExpression:
+ // The cost of an expression-statement is counted in visitExpression. It would
+ // be double-dipping to count it here too.
+ break;
+
+ case Statement::Kind::kNop:
+ case Statement::Kind::kVarDeclaration:
+ // These statements don't directly consume any space in a compiled program.
+ break;
+
+ default:
+ // Note that we don't make any attempt to estimate the number of iterations of
+ // do-while loops here. Those aren't an ES2 construct so we aren't enforcing
+ // program size on them.
+ fFunctionSize = SkSafeMath::Add(fFunctionSize, kStatementCost);
+ break;
+ }
+
+ return INHERITED::visitStatement(stmt);
+ }
+
+ bool visitExpression(const Expression& expr) override {
+ // Other than function calls, all expressions are assumed to have a fixed unit cost.
+ bool earlyExit = false;
+ size_t expressionCost = kExpressionCost;
+
+ if (expr.is<FunctionCall>()) {
+ // Visit this function call to calculate its size. If we've already sized it, this
+ // will retrieve the size from our cache.
+ const FunctionCall& call = expr.as<FunctionCall>();
+ const FunctionDeclaration* decl = &call.function();
+ if (decl->definition() && !decl->isIntrinsic()) {
+ size_t originalFunctionSize = fFunctionSize;
+ fFunctionSize = 0;
+
+ earlyExit = this->visitProgramElement(*decl->definition());
+ expressionCost = fFunctionSize;
+
+ fFunctionSize = originalFunctionSize;
+ }
+ }
+
+ fFunctionSize = SkSafeMath::Add(fFunctionSize, expressionCost);
+ return earlyExit || INHERITED::visitExpression(expr);
+ }
+
+ private:
+ using INHERITED = ProgramVisitor;
+
+ const Context& fContext;
+ size_t fFunctionSize = 0;
+ SkTHashMap<const FunctionDeclaration*, size_t> fFunctionCostMap;
+ std::vector<const FunctionDeclaration*> fStack;
+ };
+
+ // Process every function in our program.
+ ProgramSizeVisitor visitor{context};
+ for (const std::unique_ptr<ProgramElement>& element : program.fOwnedElements) {
+ if (element->is<FunctionDefinition>()) {
+ // Visit every function--we want to detect static recursion and report it as an error,
+ // even in unreferenced functions.
+ visitor.visitProgramElement(*element);
+ // Report an error when main()'s flattened size is larger than our program limit.
+ if (enforceSizeLimit &&
+ visitor.functionSize() > kProgramSizeLimit &&
+ element->as<FunctionDefinition>().declaration().isMain()) {
+ context.fErrors->error(Position(), "program is too large");
+ }
+ }
+ }
+
+ return true;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLFinalizationChecks.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLFinalizationChecks.cpp
new file mode 100644
index 0000000000..f1c0b4cdfa
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/analysis/SkSLFinalizationChecks.cpp
@@ -0,0 +1,172 @@
+/*
+ * 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/SkTypes.h"
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLLayout.h"
+#include "include/private/SkSLModifiers.h"
+#include "include/private/SkSLProgramElement.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "src/base/SkSafeMath.h"
+#include "src/core/SkTHash.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/analysis/SkSLProgramUsage.h"
+#include "src/sksl/analysis/SkSLProgramVisitor.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLFunctionCall.h"
+#include "src/sksl/ir/SkSLFunctionDeclaration.h"
+#include "src/sksl/ir/SkSLFunctionDefinition.h"
+#include "src/sksl/ir/SkSLInterfaceBlock.h"
+#include "src/sksl/ir/SkSLProgram.h"
+#include "src/sksl/ir/SkSLType.h"
+#include "src/sksl/ir/SkSLVarDeclarations.h"
+#include "src/sksl/ir/SkSLVariable.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace SkSL {
+namespace {
+
+class FinalizationVisitor : public ProgramVisitor {
+public:
+ FinalizationVisitor(const Context& c, const ProgramUsage& u) : fContext(c), fUsage(u) {}
+
+ bool visitProgramElement(const ProgramElement& pe) override {
+ switch (pe.kind()) {
+ case ProgramElement::Kind::kGlobalVar:
+ this->checkGlobalVariableSizeLimit(pe.as<GlobalVarDeclaration>());
+ break;
+ case ProgramElement::Kind::kInterfaceBlock:
+ // TODO(skia:13664): Enforce duplicate checks universally. This is currently not
+ // possible without changes to the binding index assignment logic in graphite.
+ this->checkBindUniqueness(pe.as<InterfaceBlock>());
+ break;
+ case ProgramElement::Kind::kFunction:
+ this->checkOutParamsAreAssigned(pe.as<FunctionDefinition>());
+ break;
+ default:
+ break;
+ }
+ return INHERITED::visitProgramElement(pe);
+ }
+
+ void checkGlobalVariableSizeLimit(const GlobalVarDeclaration& globalDecl) {
+ if (!ProgramConfig::IsRuntimeEffect(fContext.fConfig->fKind)) {
+ return;
+ }
+ const VarDeclaration& decl = globalDecl.varDeclaration();
+
+ size_t prevSlotsUsed = fGlobalSlotsUsed;
+ fGlobalSlotsUsed = SkSafeMath::Add(fGlobalSlotsUsed, decl.var()->type().slotCount());
+ // To avoid overzealous error reporting, only trigger the error at the first place where the
+ // global limit is exceeded.
+ if (prevSlotsUsed < kVariableSlotLimit && fGlobalSlotsUsed >= kVariableSlotLimit) {
+ fContext.fErrors->error(decl.fPosition,
+ "global variable '" + std::string(decl.var()->name()) +
+ "' exceeds the size limit");
+ }
+ }
+
+ void checkBindUniqueness(const InterfaceBlock& block) {
+ const Variable* var = block.var();
+ int32_t set = var->modifiers().fLayout.fSet;
+ int32_t binding = var->modifiers().fLayout.fBinding;
+ if (binding != -1) {
+ // TODO(skia:13664): This should map a `set` value of -1 to the default settings value
+ // used by codegen backends to prevent duplicates that may arise from the effective
+ // default set value.
+ uint64_t key = ((uint64_t)set << 32) + binding;
+ if (!fBindings.contains(key)) {
+ fBindings.add(key);
+ } else {
+ if (set != -1) {
+ fContext.fErrors->error(block.fPosition,
+ "layout(set=" + std::to_string(set) +
+ ", binding=" + std::to_string(binding) +
+ ") has already been defined");
+ } else {
+ fContext.fErrors->error(block.fPosition,
+ "layout(binding=" + std::to_string(binding) +
+ ") has already been defined");
+ }
+ }
+ }
+ }
+
+ void checkOutParamsAreAssigned(const FunctionDefinition& funcDef) {
+ const FunctionDeclaration& funcDecl = funcDef.declaration();
+
+ // Searches for `out` parameters that are not written to. According to the GLSL spec,
+ // the value of an out-param that's never assigned to is unspecified, so report it.
+ for (const Variable* param : funcDecl.parameters()) {
+ const int paramInout = param->modifiers().fFlags & (Modifiers::Flag::kIn_Flag |
+ Modifiers::Flag::kOut_Flag);
+ if (paramInout == Modifiers::Flag::kOut_Flag) {
+ ProgramUsage::VariableCounts counts = fUsage.get(*param);
+ if (counts.fWrite <= 0) {
+ fContext.fErrors->error(param->fPosition,
+ "function '" + std::string(funcDecl.name()) +
+ "' never assigns a value to out parameter '" +
+ std::string(param->name()) + "'");
+ }
+ }
+ }
+ }
+
+ bool visitExpression(const Expression& expr) override {
+ switch (expr.kind()) {
+ case Expression::Kind::kFunctionCall: {
+ const FunctionDeclaration& decl = expr.as<FunctionCall>().function();
+ if (!decl.isBuiltin() && !decl.definition()) {
+ fContext.fErrors->error(expr.fPosition, "function '" + decl.description() +
+ "' is not defined");
+ }
+ break;
+ }
+ case Expression::Kind::kFunctionReference:
+ case Expression::Kind::kMethodReference:
+ case Expression::Kind::kTypeReference:
+ SkDEBUGFAIL("invalid reference-expr, should have been reported by coerce()");
+ fContext.fErrors->error(expr.fPosition, "invalid expression");
+ break;
+ default:
+ if (expr.type().matches(*fContext.fTypes.fInvalid)) {
+ fContext.fErrors->error(expr.fPosition, "invalid expression");
+ }
+ break;
+ }
+ return INHERITED::visitExpression(expr);
+ }
+
+private:
+ using INHERITED = ProgramVisitor;
+ size_t fGlobalSlotsUsed = 0;
+ const Context& fContext;
+ const ProgramUsage& fUsage;
+ // we pack the set/binding pair into a single 64 bit int
+ SkTHashSet<uint64_t> fBindings;
+};
+
+} // namespace
+
+void Analysis::DoFinalizationChecks(const Program& program) {
+ // Check all of the program's owned elements. (Built-in elements are assumed to be valid.)
+ FinalizationVisitor visitor{*program.fContext, *program.usage()};
+ for (const std::unique_ptr<ProgramElement>& element : program.fOwnedElements) {
+ visitor.visitProgramElement(*element);
+ }
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLGetLoopControlFlowInfo.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLGetLoopControlFlowInfo.cpp
new file mode 100644
index 0000000000..65c9e5e424
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/analysis/SkSLGetLoopControlFlowInfo.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/SkSLAnalysis.h"
+
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLStatement.h"
+#include "src/sksl/analysis/SkSLProgramVisitor.h"
+
+namespace SkSL {
+
+class Expression;
+
+namespace Analysis {
+namespace {
+
+class LoopControlFlowVisitor : public ProgramVisitor {
+public:
+ LoopControlFlowVisitor() {}
+
+ bool visitExpression(const Expression& expr) override {
+ // We can avoid processing expressions entirely.
+ return false;
+ }
+
+ bool visitStatement(const Statement& stmt) override {
+ switch (stmt.kind()) {
+ case Statement::Kind::kContinue:
+ // A continue only affects the control flow of the loop if it's not nested inside
+ // another looping structure. (Inside a switch, SkSL disallows continue entirely.)
+ fResult.fHasContinue |= (fDepth == 0);
+ break;
+
+ case Statement::Kind::kBreak:
+ // A break only affects the control flow of the loop if it's not nested inside
+ // another loop/switch structure.
+ fResult.fHasBreak |= (fDepth == 0);
+ break;
+
+ case Statement::Kind::kReturn:
+ // A return will abort the loop's control flow no matter how deeply it is nested.
+ fResult.fHasReturn = true;
+ break;
+
+ case Statement::Kind::kFor:
+ case Statement::Kind::kDo:
+ case Statement::Kind::kSwitch: {
+ ++fDepth;
+ bool done = ProgramVisitor::visitStatement(stmt);
+ --fDepth;
+ return done;
+ }
+
+ default:
+ return ProgramVisitor::visitStatement(stmt);
+ }
+
+ // If we've already found everything we're hunting for, we can stop searching early.
+ return fResult.fHasContinue && fResult.fHasBreak && fResult.fHasReturn;
+ }
+
+ LoopControlFlowInfo fResult;
+ int fDepth = 0;
+};
+
+} // namespace
+
+LoopControlFlowInfo GetLoopControlFlowInfo(const Statement& stmt) {
+ LoopControlFlowVisitor visitor;
+ visitor.visitStatement(stmt);
+ return visitor.fResult;
+}
+
+} // namespace Analysis
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLGetLoopUnrollInfo.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLGetLoopUnrollInfo.cpp
new file mode 100644
index 0000000000..cf49867392
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/analysis/SkSLGetLoopUnrollInfo.cpp
@@ -0,0 +1,280 @@
+/*
+ * Copyright 2021 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/SkTypes.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLStatement.h"
+#include "include/private/base/SkFloatingPoint.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "include/sksl/SkSLOperator.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLConstantFolder.h"
+#include "src/sksl/analysis/SkSLNoOpErrorReporter.h"
+#include "src/sksl/ir/SkSLBinaryExpression.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLForStatement.h"
+#include "src/sksl/ir/SkSLPostfixExpression.h"
+#include "src/sksl/ir/SkSLPrefixExpression.h"
+#include "src/sksl/ir/SkSLType.h"
+#include "src/sksl/ir/SkSLVarDeclarations.h"
+#include "src/sksl/ir/SkSLVariableReference.h"
+
+#include <cmath>
+#include <memory>
+
+namespace SkSL {
+
+// Loops that run for 100000+ iterations will exceed our program size limit.
+static constexpr int kLoopTerminationLimit = 100000;
+
+static int calculate_count(double start, double end, double delta, bool forwards, bool inclusive) {
+ if (forwards != (start < end)) {
+ // The loop starts in a completed state (the start has already advanced past the end).
+ return 0;
+ }
+ if ((delta == 0.0) || forwards != (delta > 0.0)) {
+ // The loop does not progress toward a completed state, and will never terminate.
+ return kLoopTerminationLimit;
+ }
+ double iterations = sk_ieee_double_divide(end - start, delta);
+ double count = std::ceil(iterations);
+ if (inclusive && (count == iterations)) {
+ count += 1.0;
+ }
+ if (count > kLoopTerminationLimit || !std::isfinite(count)) {
+ // The loop runs for more iterations than we can safely unroll.
+ return kLoopTerminationLimit;
+ }
+ return (int)count;
+}
+
+std::unique_ptr<LoopUnrollInfo> Analysis::GetLoopUnrollInfo(Position loopPos,
+ const ForLoopPositions& positions,
+ const Statement* loopInitializer,
+ const Expression* loopTest,
+ const Expression* loopNext,
+ const Statement* loopStatement,
+ ErrorReporter* errorPtr) {
+ NoOpErrorReporter unused;
+ ErrorReporter& errors = errorPtr ? *errorPtr : unused;
+ auto loopInfo = std::make_unique<LoopUnrollInfo>();
+
+ //
+ // init_declaration has the form: type_specifier identifier = constant_expression
+ //
+ if (!loopInitializer) {
+ Position pos = positions.initPosition.valid() ? positions.initPosition : loopPos;
+ errors.error(pos, "missing init declaration");
+ return nullptr;
+ }
+ if (!loopInitializer->is<VarDeclaration>()) {
+ errors.error(loopInitializer->fPosition, "invalid init declaration");
+ return nullptr;
+ }
+ const VarDeclaration& initDecl = loopInitializer->as<VarDeclaration>();
+ if (!initDecl.baseType().isNumber()) {
+ errors.error(loopInitializer->fPosition, "invalid type for loop index");
+ return nullptr;
+ }
+ if (initDecl.arraySize() != 0) {
+ errors.error(loopInitializer->fPosition, "invalid type for loop index");
+ return nullptr;
+ }
+ if (!initDecl.value()) {
+ errors.error(loopInitializer->fPosition, "missing loop index initializer");
+ return nullptr;
+ }
+ if (!ConstantFolder::GetConstantValue(*initDecl.value(), &loopInfo->fStart)) {
+ errors.error(loopInitializer->fPosition,
+ "loop index initializer must be a constant expression");
+ return nullptr;
+ }
+
+ loopInfo->fIndex = initDecl.var();
+
+ auto is_loop_index = [&](const std::unique_ptr<Expression>& expr) {
+ return expr->is<VariableReference>() &&
+ expr->as<VariableReference>().variable() == loopInfo->fIndex;
+ };
+
+ //
+ // condition has the form: loop_index relational_operator constant_expression
+ //
+ if (!loopTest) {
+ Position pos = positions.conditionPosition.valid() ? positions.conditionPosition : loopPos;
+ errors.error(pos, "missing condition");
+ return nullptr;
+ }
+ if (!loopTest->is<BinaryExpression>()) {
+ errors.error(loopTest->fPosition, "invalid condition");
+ return nullptr;
+ }
+ const BinaryExpression& cond = loopTest->as<BinaryExpression>();
+ if (!is_loop_index(cond.left())) {
+ errors.error(loopTest->fPosition, "expected loop index on left hand side of condition");
+ return nullptr;
+ }
+ // relational_operator is one of: > >= < <= == or !=
+ switch (cond.getOperator().kind()) {
+ case Operator::Kind::GT:
+ case Operator::Kind::GTEQ:
+ case Operator::Kind::LT:
+ case Operator::Kind::LTEQ:
+ case Operator::Kind::EQEQ:
+ case Operator::Kind::NEQ:
+ break;
+ default:
+ errors.error(loopTest->fPosition, "invalid relational operator");
+ return nullptr;
+ }
+ double loopEnd = 0;
+ if (!ConstantFolder::GetConstantValue(*cond.right(), &loopEnd)) {
+ errors.error(loopTest->fPosition, "loop index must be compared with a constant expression");
+ return nullptr;
+ }
+
+ //
+ // expression has one of the following forms:
+ // loop_index++
+ // loop_index--
+ // loop_index += constant_expression
+ // loop_index -= constant_expression
+ // The spec doesn't mention prefix increment and decrement, but there is some consensus that
+ // it's an oversight, so we allow those as well.
+ //
+ if (!loopNext) {
+ Position pos = positions.nextPosition.valid() ? positions.nextPosition : loopPos;
+ errors.error(pos, "missing loop expression");
+ return nullptr;
+ }
+ switch (loopNext->kind()) {
+ case Expression::Kind::kBinary: {
+ const BinaryExpression& next = loopNext->as<BinaryExpression>();
+ if (!is_loop_index(next.left())) {
+ errors.error(loopNext->fPosition, "expected loop index in loop expression");
+ return nullptr;
+ }
+ if (!ConstantFolder::GetConstantValue(*next.right(), &loopInfo->fDelta)) {
+ errors.error(loopNext->fPosition,
+ "loop index must be modified by a constant expression");
+ return nullptr;
+ }
+ switch (next.getOperator().kind()) {
+ case Operator::Kind::PLUSEQ: break;
+ case Operator::Kind::MINUSEQ: loopInfo->fDelta = -loopInfo->fDelta; break;
+ default:
+ errors.error(loopNext->fPosition, "invalid operator in loop expression");
+ return nullptr;
+ }
+ } break;
+ case Expression::Kind::kPrefix: {
+ const PrefixExpression& next = loopNext->as<PrefixExpression>();
+ if (!is_loop_index(next.operand())) {
+ errors.error(loopNext->fPosition, "expected loop index in loop expression");
+ return nullptr;
+ }
+ switch (next.getOperator().kind()) {
+ case Operator::Kind::PLUSPLUS: loopInfo->fDelta = 1; break;
+ case Operator::Kind::MINUSMINUS: loopInfo->fDelta = -1; break;
+ default:
+ errors.error(loopNext->fPosition, "invalid operator in loop expression");
+ return nullptr;
+ }
+ } break;
+ case Expression::Kind::kPostfix: {
+ const PostfixExpression& next = loopNext->as<PostfixExpression>();
+ if (!is_loop_index(next.operand())) {
+ errors.error(loopNext->fPosition, "expected loop index in loop expression");
+ return nullptr;
+ }
+ switch (next.getOperator().kind()) {
+ case Operator::Kind::PLUSPLUS: loopInfo->fDelta = 1; break;
+ case Operator::Kind::MINUSMINUS: loopInfo->fDelta = -1; break;
+ default:
+ errors.error(loopNext->fPosition, "invalid operator in loop expression");
+ return nullptr;
+ }
+ } break;
+ default:
+ errors.error(loopNext->fPosition, "invalid loop expression");
+ return nullptr;
+ }
+
+ //
+ // Within the body of the loop, the loop index is not statically assigned to, nor is it used as
+ // argument to a function 'out' or 'inout' parameter.
+ //
+ if (Analysis::StatementWritesToVariable(*loopStatement, *initDecl.var())) {
+ errors.error(loopStatement->fPosition,
+ "loop index must not be modified within body of the loop");
+ return nullptr;
+ }
+
+ // Finally, compute the iteration count, based on the bounds, and the termination operator.
+ loopInfo->fCount = 0;
+
+ switch (cond.getOperator().kind()) {
+ case Operator::Kind::LT:
+ loopInfo->fCount = calculate_count(loopInfo->fStart, loopEnd, loopInfo->fDelta,
+ /*forwards=*/true, /*inclusive=*/false);
+ break;
+
+ case Operator::Kind::GT:
+ loopInfo->fCount = calculate_count(loopInfo->fStart, loopEnd, loopInfo->fDelta,
+ /*forwards=*/false, /*inclusive=*/false);
+ break;
+
+ case Operator::Kind::LTEQ:
+ loopInfo->fCount = calculate_count(loopInfo->fStart, loopEnd, loopInfo->fDelta,
+ /*forwards=*/true, /*inclusive=*/true);
+ break;
+
+ case Operator::Kind::GTEQ:
+ loopInfo->fCount = calculate_count(loopInfo->fStart, loopEnd, loopInfo->fDelta,
+ /*forwards=*/false, /*inclusive=*/true);
+ break;
+
+ case Operator::Kind::NEQ: {
+ float iterations = sk_ieee_double_divide(loopEnd - loopInfo->fStart, loopInfo->fDelta);
+ loopInfo->fCount = std::ceil(iterations);
+ if (loopInfo->fCount < 0 || loopInfo->fCount != iterations ||
+ !std::isfinite(iterations)) {
+ // The loop doesn't reach the exact endpoint and so will never terminate.
+ loopInfo->fCount = kLoopTerminationLimit;
+ }
+ break;
+ }
+ case Operator::Kind::EQEQ: {
+ if (loopInfo->fStart == loopEnd) {
+ // Start and end begin in the same place, so we can run one iteration...
+ if (loopInfo->fDelta) {
+ // ... and then they diverge, so the loop terminates.
+ loopInfo->fCount = 1;
+ } else {
+ // ... but they never diverge, so the loop runs forever.
+ loopInfo->fCount = kLoopTerminationLimit;
+ }
+ } else {
+ // Start never equals end, so the loop will not run a single iteration.
+ loopInfo->fCount = 0;
+ }
+ break;
+ }
+ default: SkUNREACHABLE;
+ }
+
+ SkASSERT(loopInfo->fCount >= 0);
+ if (loopInfo->fCount >= kLoopTerminationLimit) {
+ errors.error(loopPos, "loop must guarantee termination in fewer iterations");
+ return nullptr;
+ }
+
+ return loopInfo;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLGetReturnComplexity.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLGetReturnComplexity.cpp
new file mode 100644
index 0000000000..a5b6e1132f
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/analysis/SkSLGetReturnComplexity.cpp
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLStatement.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/analysis/SkSLProgramVisitor.h"
+#include "src/sksl/ir/SkSLBlock.h"
+#include "src/sksl/ir/SkSLFunctionDefinition.h"
+
+#include <algorithm>
+#include <memory>
+
+namespace SkSL {
+
+class Expression;
+
+static int count_returns_at_end_of_control_flow(const FunctionDefinition& funcDef) {
+ class CountReturnsAtEndOfControlFlow : public ProgramVisitor {
+ public:
+ CountReturnsAtEndOfControlFlow(const FunctionDefinition& funcDef) {
+ this->visitProgramElement(funcDef);
+ }
+
+ bool visitExpression(const Expression& expr) override {
+ // Do not recurse into expressions.
+ return false;
+ }
+
+ bool visitStatement(const Statement& stmt) override {
+ switch (stmt.kind()) {
+ case Statement::Kind::kBlock: {
+ // Check only the last statement of a block.
+ const auto& block = stmt.as<Block>();
+ return block.children().size() &&
+ this->visitStatement(*block.children().back());
+ }
+ case Statement::Kind::kSwitch:
+ case Statement::Kind::kDo:
+ case Statement::Kind::kFor:
+ // Don't introspect switches or loop structures at all.
+ return false;
+
+ case Statement::Kind::kReturn:
+ ++fNumReturns;
+ [[fallthrough]];
+
+ default:
+ return INHERITED::visitStatement(stmt);
+ }
+ }
+
+ int fNumReturns = 0;
+ using INHERITED = ProgramVisitor;
+ };
+
+ return CountReturnsAtEndOfControlFlow{funcDef}.fNumReturns;
+}
+
+class CountReturnsWithLimit : public ProgramVisitor {
+public:
+ CountReturnsWithLimit(const FunctionDefinition& funcDef, int limit) : fLimit(limit) {
+ this->visitProgramElement(funcDef);
+ }
+
+ bool visitExpression(const Expression& expr) override {
+ // Do not recurse into expressions.
+ return false;
+ }
+
+ bool visitStatement(const Statement& stmt) override {
+ switch (stmt.kind()) {
+ case Statement::Kind::kReturn: {
+ ++fNumReturns;
+ fDeepestReturn = std::max(fDeepestReturn, fScopedBlockDepth);
+ return (fNumReturns >= fLimit) || INHERITED::visitStatement(stmt);
+ }
+ case Statement::Kind::kVarDeclaration: {
+ if (fScopedBlockDepth > 1) {
+ fVariablesInBlocks = true;
+ }
+ return INHERITED::visitStatement(stmt);
+ }
+ case Statement::Kind::kBlock: {
+ int depthIncrement = stmt.as<Block>().isScope() ? 1 : 0;
+ fScopedBlockDepth += depthIncrement;
+ bool result = INHERITED::visitStatement(stmt);
+ fScopedBlockDepth -= depthIncrement;
+ if (fNumReturns == 0 && fScopedBlockDepth <= 1) {
+ // If closing this block puts us back at the top level, and we haven't
+ // encountered any return statements yet, any vardecls we may have encountered
+ // up until this point can be ignored. They are out of scope now, and they were
+ // never used in a return statement.
+ fVariablesInBlocks = false;
+ }
+ return result;
+ }
+ default:
+ return INHERITED::visitStatement(stmt);
+ }
+ }
+
+ int fNumReturns = 0;
+ int fDeepestReturn = 0;
+ int fLimit = 0;
+ int fScopedBlockDepth = 0;
+ bool fVariablesInBlocks = false;
+ using INHERITED = ProgramVisitor;
+};
+
+Analysis::ReturnComplexity Analysis::GetReturnComplexity(const FunctionDefinition& funcDef) {
+ int returnsAtEndOfControlFlow = count_returns_at_end_of_control_flow(funcDef);
+ CountReturnsWithLimit counter{funcDef, returnsAtEndOfControlFlow + 1};
+ if (counter.fNumReturns > returnsAtEndOfControlFlow) {
+ return ReturnComplexity::kEarlyReturns;
+ }
+ if (counter.fNumReturns > 1) {
+ return ReturnComplexity::kScopedReturns;
+ }
+ if (counter.fVariablesInBlocks && counter.fDeepestReturn > 1) {
+ return ReturnComplexity::kScopedReturns;
+ }
+ return ReturnComplexity::kSingleSafeReturn;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLHasSideEffects.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLHasSideEffects.cpp
new file mode 100644
index 0000000000..0d328991e0
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/analysis/SkSLHasSideEffects.cpp
@@ -0,0 +1,65 @@
+/*
+ * 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/SkTypes.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLModifiers.h"
+#include "include/sksl/SkSLOperator.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/analysis/SkSLProgramVisitor.h"
+#include "src/sksl/ir/SkSLBinaryExpression.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLFunctionCall.h"
+#include "src/sksl/ir/SkSLFunctionDeclaration.h"
+#include "src/sksl/ir/SkSLPrefixExpression.h"
+
+namespace SkSL {
+
+bool Analysis::HasSideEffects(const Expression& expr) {
+ class HasSideEffectsVisitor : public ProgramVisitor {
+ public:
+ bool visitExpression(const Expression& expr) override {
+ switch (expr.kind()) {
+ case Expression::Kind::kFunctionCall: {
+ const FunctionCall& call = expr.as<FunctionCall>();
+ if (!(call.function().modifiers().fFlags & Modifiers::kPure_Flag)) {
+ return true;
+ }
+ break;
+ }
+ case Expression::Kind::kPrefix: {
+ const PrefixExpression& prefix = expr.as<PrefixExpression>();
+ if (prefix.getOperator().kind() == Operator::Kind::PLUSPLUS ||
+ prefix.getOperator().kind() == Operator::Kind::MINUSMINUS) {
+ return true;
+ }
+ break;
+ }
+ case Expression::Kind::kBinary: {
+ const BinaryExpression& binary = expr.as<BinaryExpression>();
+ if (binary.getOperator().isAssignment()) {
+ return true;
+ }
+ break;
+ }
+ case Expression::Kind::kPostfix:
+ return true;
+
+ default:
+ break;
+ }
+ return INHERITED::visitExpression(expr);
+ }
+
+ using INHERITED = ProgramVisitor;
+ };
+
+ HasSideEffectsVisitor visitor;
+ return visitor.visitExpression(expr);
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLIsConstantExpression.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLIsConstantExpression.cpp
new file mode 100644
index 0000000000..d0a54d2d4b
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/analysis/SkSLIsConstantExpression.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2021 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/SkTypes.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLModifiers.h"
+#include "include/sksl/SkSLOperator.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/analysis/SkSLProgramVisitor.h"
+#include "src/sksl/ir/SkSLBinaryExpression.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLVariable.h"
+#include "src/sksl/ir/SkSLVariableReference.h"
+
+#include <set>
+
+namespace SkSL {
+
+// Checks for ES2 constant-expression rules, and (optionally) constant-index-expression rules
+// (if loopIndices is non-nullptr)
+class ConstantExpressionVisitor : public ProgramVisitor {
+public:
+ ConstantExpressionVisitor(const std::set<const Variable*>* loopIndices)
+ : fLoopIndices(loopIndices) {}
+
+ bool visitExpression(const Expression& e) override {
+ // A constant-(index)-expression is one of...
+ switch (e.kind()) {
+ // ... a literal value
+ case Expression::Kind::kLiteral:
+ return false;
+
+ // ... settings can appear in fragment processors; they will resolve when compiled
+ case Expression::Kind::kSetting:
+ return false;
+
+ // ... a global or local variable qualified as 'const', excluding function parameters.
+ // ... loop indices as defined in section 4. [constant-index-expression]
+ case Expression::Kind::kVariableReference: {
+ const Variable* v = e.as<VariableReference>().variable();
+ if ((v->storage() == Variable::Storage::kGlobal ||
+ v->storage() == Variable::Storage::kLocal) &&
+ (v->modifiers().fFlags & Modifiers::kConst_Flag)) {
+ return false;
+ }
+ return !fLoopIndices || fLoopIndices->find(v) == fLoopIndices->end();
+ }
+
+ // ... not a sequence expression (skia:13311)...
+ case Expression::Kind::kBinary:
+ if (e.as<BinaryExpression>().getOperator().kind() == Operator::Kind::COMMA) {
+ return true;
+ }
+ [[fallthrough]];
+
+ // ... expressions composed of both of the above
+ case Expression::Kind::kConstructorArray:
+ case Expression::Kind::kConstructorArrayCast:
+ case Expression::Kind::kConstructorCompound:
+ case Expression::Kind::kConstructorCompoundCast:
+ case Expression::Kind::kConstructorDiagonalMatrix:
+ case Expression::Kind::kConstructorMatrixResize:
+ case Expression::Kind::kConstructorScalarCast:
+ case Expression::Kind::kConstructorSplat:
+ case Expression::Kind::kConstructorStruct:
+ case Expression::Kind::kFieldAccess:
+ case Expression::Kind::kIndex:
+ case Expression::Kind::kPrefix:
+ case Expression::Kind::kPostfix:
+ case Expression::Kind::kSwizzle:
+ case Expression::Kind::kTernary:
+ return INHERITED::visitExpression(e);
+
+ // Function calls are completely disallowed in SkSL constant-(index)-expressions.
+ // GLSL does mandate that calling a built-in function where the arguments are all
+ // constant-expressions should result in a constant-expression. SkSL handles this by
+ // optimizing fully-constant function calls into literals in FunctionCall::Make.
+ case Expression::Kind::kFunctionCall:
+ case Expression::Kind::kChildCall:
+
+ // These shouldn't appear in a valid program at all, and definitely aren't
+ // constant-(index)-expressions.
+ case Expression::Kind::kPoison:
+ case Expression::Kind::kFunctionReference:
+ case Expression::Kind::kMethodReference:
+ case Expression::Kind::kTypeReference:
+ return true;
+
+ default:
+ SkDEBUGFAIL("Unexpected expression type");
+ return true;
+ }
+ }
+
+private:
+ const std::set<const Variable*>* fLoopIndices;
+ using INHERITED = ProgramVisitor;
+};
+
+bool Analysis::IsConstantExpression(const Expression& expr) {
+ return !ConstantExpressionVisitor{/*loopIndices=*/nullptr}.visitExpression(expr);
+}
+
+bool Analysis::IsConstantIndexExpression(const Expression& expr,
+ const std::set<const Variable*>* loopIndices) {
+ return !ConstantExpressionVisitor{loopIndices}.visitExpression(expr);
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLIsDynamicallyUniformExpression.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLIsDynamicallyUniformExpression.cpp
new file mode 100644
index 0000000000..0e07fac6f5
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/analysis/SkSLIsDynamicallyUniformExpression.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2023 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/SkTypes.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLModifiers.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/analysis/SkSLProgramVisitor.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLFunctionCall.h"
+#include "src/sksl/ir/SkSLFunctionDeclaration.h"
+#include "src/sksl/ir/SkSLVariable.h"
+#include "src/sksl/ir/SkSLVariableReference.h"
+
+namespace SkSL {
+
+bool Analysis::IsDynamicallyUniformExpression(const Expression& expr) {
+ class IsDynamicallyUniformExpressionVisitor : public ProgramVisitor {
+ public:
+ bool visitExpression(const Expression& expr) override {
+ switch (expr.kind()) {
+ case Expression::Kind::kBinary:
+ case Expression::Kind::kConstructorArray:
+ case Expression::Kind::kConstructorArrayCast:
+ case Expression::Kind::kConstructorCompound:
+ case Expression::Kind::kConstructorCompoundCast:
+ case Expression::Kind::kConstructorDiagonalMatrix:
+ case Expression::Kind::kConstructorMatrixResize:
+ case Expression::Kind::kConstructorScalarCast:
+ case Expression::Kind::kConstructorSplat:
+ case Expression::Kind::kConstructorStruct:
+ case Expression::Kind::kFieldAccess:
+ case Expression::Kind::kIndex:
+ case Expression::Kind::kPostfix:
+ case Expression::Kind::kPrefix:
+ case Expression::Kind::kSwizzle:
+ case Expression::Kind::kTernary:
+ // These expressions might be dynamically uniform, if they are composed entirely
+ // of constants and uniforms.
+ break;
+
+ case Expression::Kind::kVariableReference: {
+ // Verify that variable references are const or uniform.
+ const Variable* var = expr.as<VariableReference>().variable();
+ if (!var || !(var->modifiers().fFlags & (Modifiers::Flag::kConst_Flag |
+ Modifiers::Flag::kUniform_Flag))) {
+ fIsDynamicallyUniform = false;
+ return true;
+ }
+ break;
+ }
+ case Expression::Kind::kFunctionCall: {
+ // Verify that function calls are pure.
+ const FunctionDeclaration& decl = expr.as<FunctionCall>().function();
+ if (!(decl.modifiers().fFlags & Modifiers::Flag::kPure_Flag)) {
+ fIsDynamicallyUniform = false;
+ return true;
+ }
+ break;
+ }
+ case Expression::Kind::kLiteral:
+ // Literals are compile-time constants.
+ return false;
+
+ default:
+ // This expression isn't dynamically uniform.
+ fIsDynamicallyUniform = false;
+ return true;
+ }
+ return INHERITED::visitExpression(expr);
+ }
+
+ bool fIsDynamicallyUniform = true;
+ using INHERITED = ProgramVisitor;
+ };
+
+ IsDynamicallyUniformExpressionVisitor visitor;
+ visitor.visitExpression(expr);
+ return visitor.fIsDynamicallyUniform;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLIsSameExpressionTree.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLIsSameExpressionTree.cpp
new file mode 100644
index 0000000000..2c4506a725
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/analysis/SkSLIsSameExpressionTree.cpp
@@ -0,0 +1,99 @@
+/*
+ * 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/core/SkTypes.h"
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/base/SkTArray.h"
+#include "include/sksl/SkSLOperator.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/ir/SkSLConstructor.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLFieldAccess.h"
+#include "src/sksl/ir/SkSLIndexExpression.h"
+#include "src/sksl/ir/SkSLLiteral.h"
+#include "src/sksl/ir/SkSLPrefixExpression.h"
+#include "src/sksl/ir/SkSLSwizzle.h"
+#include "src/sksl/ir/SkSLType.h"
+#include "src/sksl/ir/SkSLVariableReference.h"
+
+#include <cstddef>
+#include <memory>
+
+namespace SkSL {
+
+bool Analysis::IsSameExpressionTree(const Expression& left, const Expression& right) {
+ if (left.kind() != right.kind() || !left.type().matches(right.type())) {
+ return false;
+ }
+
+ // This isn't a fully exhaustive list of expressions by any stretch of the imagination; for
+ // instance, `x[y+1] = x[y+1]` isn't detected because we don't look at BinaryExpressions.
+ // Since this is intended to be used for optimization purposes, handling the common cases is
+ // sufficient.
+ switch (left.kind()) {
+ case Expression::Kind::kLiteral:
+ return left.as<Literal>().value() == right.as<Literal>().value();
+
+ case Expression::Kind::kConstructorArray:
+ case Expression::Kind::kConstructorArrayCast:
+ case Expression::Kind::kConstructorCompound:
+ case Expression::Kind::kConstructorCompoundCast:
+ case Expression::Kind::kConstructorDiagonalMatrix:
+ case Expression::Kind::kConstructorMatrixResize:
+ case Expression::Kind::kConstructorScalarCast:
+ case Expression::Kind::kConstructorStruct:
+ case Expression::Kind::kConstructorSplat: {
+ if (left.kind() != right.kind()) {
+ return false;
+ }
+ const AnyConstructor& leftCtor = left.asAnyConstructor();
+ const AnyConstructor& rightCtor = right.asAnyConstructor();
+ const auto leftSpan = leftCtor.argumentSpan();
+ const auto rightSpan = rightCtor.argumentSpan();
+ if (leftSpan.size() != rightSpan.size()) {
+ return false;
+ }
+ for (size_t index = 0; index < leftSpan.size(); ++index) {
+ if (!IsSameExpressionTree(*leftSpan[index], *rightSpan[index])) {
+ return false;
+ }
+ }
+ return true;
+ }
+ case Expression::Kind::kFieldAccess:
+ return left.as<FieldAccess>().fieldIndex() == right.as<FieldAccess>().fieldIndex() &&
+ IsSameExpressionTree(*left.as<FieldAccess>().base(),
+ *right.as<FieldAccess>().base());
+
+ case Expression::Kind::kIndex:
+ return IsSameExpressionTree(*left.as<IndexExpression>().index(),
+ *right.as<IndexExpression>().index()) &&
+ IsSameExpressionTree(*left.as<IndexExpression>().base(),
+ *right.as<IndexExpression>().base());
+
+ case Expression::Kind::kPrefix:
+ return (left.as<PrefixExpression>().getOperator().kind() ==
+ right.as<PrefixExpression>().getOperator().kind()) &&
+ IsSameExpressionTree(*left.as<PrefixExpression>().operand(),
+ *right.as<PrefixExpression>().operand());
+
+ case Expression::Kind::kSwizzle:
+ return left.as<Swizzle>().components() == right.as<Swizzle>().components() &&
+ IsSameExpressionTree(*left.as<Swizzle>().base(), *right.as<Swizzle>().base());
+
+ case Expression::Kind::kVariableReference:
+ return left.as<VariableReference>().variable() ==
+ right.as<VariableReference>().variable();
+
+ default:
+ return false;
+ }
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLIsTrivialExpression.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLIsTrivialExpression.cpp
new file mode 100644
index 0000000000..4479d1215d
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/analysis/SkSLIsTrivialExpression.cpp
@@ -0,0 +1,70 @@
+/*
+ * 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/core/SkTypes.h"
+#include "include/private/SkSLIRNode.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/ir/SkSLConstructor.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLFieldAccess.h"
+#include "src/sksl/ir/SkSLIndexExpression.h"
+#include "src/sksl/ir/SkSLSwizzle.h"
+#include "src/sksl/ir/SkSLType.h"
+
+#include <memory>
+
+namespace SkSL {
+
+bool Analysis::IsTrivialExpression(const Expression& expr) {
+ switch (expr.kind()) {
+ case Expression::Kind::kLiteral:
+ case Expression::Kind::kVariableReference:
+ return true;
+
+ case Expression::Kind::kSwizzle:
+ // All swizzles are considered to be trivial.
+ return IsTrivialExpression(*expr.as<Swizzle>().base());
+
+ case Expression::Kind::kFieldAccess:
+ // Accessing a field is trivial.
+ return IsTrivialExpression(*expr.as<FieldAccess>().base());
+
+ case Expression::Kind::kIndex: {
+ // Accessing a constant array index is trivial.
+ const IndexExpression& inner = expr.as<IndexExpression>();
+ return inner.index()->isIntLiteral() && IsTrivialExpression(*inner.base());
+ }
+ case Expression::Kind::kConstructorArray:
+ case Expression::Kind::kConstructorStruct:
+ // Only consider small arrays/structs of compile-time-constants to be trivial.
+ return expr.type().slotCount() <= 4 && IsCompileTimeConstant(expr);
+
+ case Expression::Kind::kConstructorArrayCast:
+ case Expression::Kind::kConstructorMatrixResize:
+ // These operations require function calls in Metal, so they're never trivial.
+ return false;
+
+ case Expression::Kind::kConstructorCompound:
+ // Only compile-time-constant compound constructors are considered to be trivial.
+ return IsCompileTimeConstant(expr);
+
+ case Expression::Kind::kConstructorCompoundCast:
+ case Expression::Kind::kConstructorScalarCast:
+ case Expression::Kind::kConstructorSplat:
+ case Expression::Kind::kConstructorDiagonalMatrix: {
+ // Single-argument constructors are trivial when their inner expression is trivial.
+ SkASSERT(expr.asAnyConstructor().argumentSpan().size() == 1);
+ const Expression& inner = *expr.asAnyConstructor().argumentSpan().front();
+ return IsTrivialExpression(inner);
+ }
+ default:
+ return false;
+ }
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLNoOpErrorReporter.h b/gfx/skia/skia/src/sksl/analysis/SkSLNoOpErrorReporter.h
new file mode 100644
index 0000000000..16040d4d71
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/analysis/SkSLNoOpErrorReporter.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkSLNoOpErrorReporter_DEFINED
+#define SkSLNoOpErrorReporter_DEFINED
+
+#include "include/sksl/SkSLErrorReporter.h"
+
+namespace SkSL {
+
+// We can use a no-op error reporter to silently ignore errors.
+class NoOpErrorReporter : public ErrorReporter {
+public:
+ void handleError(std::string_view, Position) override {}
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLProgramUsage.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLProgramUsage.cpp
new file mode 100644
index 0000000000..46f0a452b6
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/analysis/SkSLProgramUsage.cpp
@@ -0,0 +1,242 @@
+/*
+ * Copyright 2021 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/SkTypes.h"
+#include "include/private/SkSLModifiers.h"
+#include "include/private/SkSLProgramElement.h"
+#include "include/private/SkSLStatement.h"
+#include "include/private/base/SkDebug.h"
+#include "src/core/SkTHash.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/analysis/SkSLProgramUsage.h"
+#include "src/sksl/analysis/SkSLProgramVisitor.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLFunctionCall.h"
+#include "src/sksl/ir/SkSLFunctionDeclaration.h"
+#include "src/sksl/ir/SkSLFunctionDefinition.h"
+#include "src/sksl/ir/SkSLInterfaceBlock.h"
+#include "src/sksl/ir/SkSLVarDeclarations.h"
+#include "src/sksl/ir/SkSLVariable.h"
+#include "src/sksl/ir/SkSLVariableReference.h"
+
+#include <cstring>
+#include <memory>
+#include <string_view>
+#include <vector>
+
+namespace SkSL {
+
+struct Program;
+
+namespace {
+
+class ProgramUsageVisitor : public ProgramVisitor {
+public:
+ ProgramUsageVisitor(ProgramUsage* usage, int delta) : fUsage(usage), fDelta(delta) {}
+
+ bool visitProgramElement(const ProgramElement& pe) override {
+ if (pe.is<FunctionDefinition>()) {
+ for (const Variable* param : pe.as<FunctionDefinition>().declaration().parameters()) {
+ // Ensure function-parameter variables exist in the variable usage map. They aren't
+ // otherwise declared, but ProgramUsage::get() should be able to find them, even if
+ // they are unread and unwritten.
+ fUsage->fVariableCounts[param];
+ }
+ } else if (pe.is<InterfaceBlock>()) {
+ // Ensure interface-block variables exist in the variable usage map.
+ fUsage->fVariableCounts[pe.as<InterfaceBlock>().var()];
+ }
+ return INHERITED::visitProgramElement(pe);
+ }
+
+ bool visitStatement(const Statement& s) override {
+ if (s.is<VarDeclaration>()) {
+ // Add all declared variables to the usage map (even if never otherwise accessed).
+ const VarDeclaration& vd = s.as<VarDeclaration>();
+ ProgramUsage::VariableCounts& counts = fUsage->fVariableCounts[vd.var()];
+ counts.fVarExists += fDelta;
+ SkASSERT(counts.fVarExists >= 0 && counts.fVarExists <= 1);
+ if (vd.value()) {
+ // The initial-value expression, when present, counts as a write.
+ counts.fWrite += fDelta;
+ }
+ }
+ return INHERITED::visitStatement(s);
+ }
+
+ bool visitExpression(const Expression& e) override {
+ if (e.is<FunctionCall>()) {
+ const FunctionDeclaration* f = &e.as<FunctionCall>().function();
+ fUsage->fCallCounts[f] += fDelta;
+ SkASSERT(fUsage->fCallCounts[f] >= 0);
+ } else if (e.is<VariableReference>()) {
+ const VariableReference& ref = e.as<VariableReference>();
+ ProgramUsage::VariableCounts& counts = fUsage->fVariableCounts[ref.variable()];
+ switch (ref.refKind()) {
+ case VariableRefKind::kRead:
+ counts.fRead += fDelta;
+ break;
+ case VariableRefKind::kWrite:
+ counts.fWrite += fDelta;
+ break;
+ case VariableRefKind::kReadWrite:
+ case VariableRefKind::kPointer:
+ counts.fRead += fDelta;
+ counts.fWrite += fDelta;
+ break;
+ }
+ SkASSERT(counts.fRead >= 0 && counts.fWrite >= 0);
+ }
+ return INHERITED::visitExpression(e);
+ }
+
+ using ProgramVisitor::visitProgramElement;
+ using ProgramVisitor::visitStatement;
+
+ ProgramUsage* fUsage;
+ int fDelta;
+ using INHERITED = ProgramVisitor;
+};
+
+} // namespace
+
+std::unique_ptr<ProgramUsage> Analysis::GetUsage(const Program& program) {
+ auto usage = std::make_unique<ProgramUsage>();
+ ProgramUsageVisitor addRefs(usage.get(), /*delta=*/+1);
+ addRefs.visit(program);
+ return usage;
+}
+
+std::unique_ptr<ProgramUsage> Analysis::GetUsage(const Module& module) {
+ auto usage = std::make_unique<ProgramUsage>();
+ ProgramUsageVisitor addRefs(usage.get(), /*delta=*/+1);
+
+ for (const Module* m = &module; m != nullptr; m = m->fParent) {
+ for (const std::unique_ptr<ProgramElement>& element : m->fElements) {
+ addRefs.visitProgramElement(*element);
+ }
+ }
+ return usage;
+}
+
+ProgramUsage::VariableCounts ProgramUsage::get(const Variable& v) const {
+ const VariableCounts* counts = fVariableCounts.find(&v);
+ SkASSERT(counts);
+ return *counts;
+}
+
+bool ProgramUsage::isDead(const Variable& v) const {
+ const Modifiers& modifiers = v.modifiers();
+ VariableCounts counts = this->get(v);
+ if ((v.storage() != Variable::Storage::kLocal && counts.fRead) ||
+ (modifiers.fFlags &
+ (Modifiers::kIn_Flag | Modifiers::kOut_Flag | Modifiers::kUniform_Flag))) {
+ return false;
+ }
+ // Consider the variable dead if it's never read and never written (besides the initial-value).
+ return !counts.fRead && (counts.fWrite <= (v.initialValue() ? 1 : 0));
+}
+
+int ProgramUsage::get(const FunctionDeclaration& f) const {
+ const int* count = fCallCounts.find(&f);
+ return count ? *count : 0;
+}
+
+void ProgramUsage::add(const Expression* expr) {
+ ProgramUsageVisitor addRefs(this, /*delta=*/+1);
+ addRefs.visitExpression(*expr);
+}
+
+void ProgramUsage::add(const Statement* stmt) {
+ ProgramUsageVisitor addRefs(this, /*delta=*/+1);
+ addRefs.visitStatement(*stmt);
+}
+
+void ProgramUsage::add(const ProgramElement& element) {
+ ProgramUsageVisitor addRefs(this, /*delta=*/+1);
+ addRefs.visitProgramElement(element);
+}
+
+void ProgramUsage::remove(const Expression* expr) {
+ ProgramUsageVisitor subRefs(this, /*delta=*/-1);
+ subRefs.visitExpression(*expr);
+}
+
+void ProgramUsage::remove(const Statement* stmt) {
+ ProgramUsageVisitor subRefs(this, /*delta=*/-1);
+ subRefs.visitStatement(*stmt);
+}
+
+void ProgramUsage::remove(const ProgramElement& element) {
+ ProgramUsageVisitor subRefs(this, /*delta=*/-1);
+ subRefs.visitProgramElement(element);
+}
+
+static bool contains_matching_data(const ProgramUsage& a, const ProgramUsage& b) {
+ constexpr bool kReportMismatch = false;
+
+ for (const auto& [varA, varCountA] : a.fVariableCounts) {
+ // Skip variable entries with zero reported usage.
+ if (!varCountA.fVarExists && !varCountA.fRead && !varCountA.fWrite) {
+ continue;
+ }
+ // Find the matching variable in the other map and ensure that its counts match.
+ const ProgramUsage::VariableCounts* varCountB = b.fVariableCounts.find(varA);
+ if (!varCountB || 0 != memcmp(&varCountA, varCountB, sizeof(varCountA))) {
+ if constexpr (kReportMismatch) {
+ SkDebugf("VariableCounts mismatch: '%.*s' (E%d R%d W%d != E%d R%d W%d)\n",
+ (int)varA->name().size(), varA->name().data(),
+ varCountA.fVarExists,
+ varCountA.fRead,
+ varCountA.fWrite,
+ varCountB ? varCountB->fVarExists : 0,
+ varCountB ? varCountB->fRead : 0,
+ varCountB ? varCountB->fWrite : 0);
+ }
+ return false;
+ }
+ }
+
+ for (const auto& [callA, callCountA] : a.fCallCounts) {
+ // Skip function-call entries with zero reported usage.
+ if (!callCountA) {
+ continue;
+ }
+ // Find the matching function in the other map and ensure that its call-count matches.
+ const int* callCountB = b.fCallCounts.find(callA);
+ if (!callCountB || callCountA != *callCountB) {
+ if constexpr (kReportMismatch) {
+ SkDebugf("CallCounts mismatch: '%.*s' (%d != %d)\n",
+ (int)callA->name().size(), callA->name().data(),
+ callCountA,
+ callCountB ? *callCountB : 0);
+ }
+ return false;
+ }
+ }
+
+ // Every non-zero entry in A has a matching non-zero entry in B.
+ return true;
+}
+
+bool ProgramUsage::operator==(const ProgramUsage& that) const {
+ // ProgramUsage can be "equal" while the underlying hash maps look slightly different, because a
+ // dead-stripped variable or function will have a usage count of zero, but will still exist in
+ // the maps. If the program usage is re-analyzed from scratch, the maps will not contain an
+ // entry for these variables or functions at all. This means our maps can be "equal" while
+ // having different element counts.
+ //
+ // In order to check these maps, we compare map entries bi-directionally, skipping zero-usage
+ // entries. If all the non-zero elements in `this` match the elements in `that`, and all the
+ // non-zero elements in `that` match the elements in `this`, all the non-zero elements must be
+ // identical, and all the zero elements must be either zero or non-existent on both sides.
+ return contains_matching_data(*this, that) &&
+ contains_matching_data(that, *this);
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLProgramUsage.h b/gfx/skia/skia/src/sksl/analysis/SkSLProgramUsage.h
new file mode 100644
index 0000000000..991240ce2d
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/analysis/SkSLProgramUsage.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_PROGRAMUSAGE
+#define SKSL_PROGRAMUSAGE
+
+#include "include/core/SkTypes.h"
+#include "src/core/SkTHash.h"
+
+namespace SkSL {
+
+class Expression;
+class FunctionDeclaration;
+class ProgramElement;
+class Statement;
+class Variable;
+
+/**
+ * Side-car class holding mutable information about a Program's IR
+ */
+class ProgramUsage {
+public:
+ struct VariableCounts {
+ int fVarExists = 0; // if this is zero, the Variable might have already been deleted
+ int fRead = 0;
+ int fWrite = 0;
+ };
+ VariableCounts get(const Variable&) const;
+ bool isDead(const Variable&) const;
+
+ int get(const FunctionDeclaration&) const;
+
+ void add(const Expression* expr);
+ void add(const Statement* stmt);
+ void add(const ProgramElement& element);
+ void remove(const Expression* expr);
+ void remove(const Statement* stmt);
+ void remove(const ProgramElement& element);
+
+ bool operator==(const ProgramUsage& that) const;
+ bool operator!=(const ProgramUsage& that) const { return !(*this == that); }
+
+ SkTHashMap<const Variable*, VariableCounts> fVariableCounts;
+ SkTHashMap<const FunctionDeclaration*, int> fCallCounts;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLProgramVisitor.h b/gfx/skia/skia/src/sksl/analysis/SkSLProgramVisitor.h
new file mode 100644
index 0000000000..3a27ef35d1
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/analysis/SkSLProgramVisitor.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkSLProgramVisitor_DEFINED
+#define SkSLProgramVisitor_DEFINED
+
+#include <memory>
+
+namespace SkSL {
+
+struct Program;
+class Expression;
+class Statement;
+class ProgramElement;
+
+/**
+ * Utility class to visit every element, statement, and expression in an SkSL program IR.
+ * This is intended for simple analysis and accumulation, where custom visitation behavior is only
+ * needed for a limited set of expression kinds.
+ *
+ * Subclasses should override visitExpression/visitStatement/visitProgramElement as needed and
+ * intercept elements of interest. They can then invoke the base class's function to visit all
+ * sub expressions. They can also choose not to call the base function to arrest recursion, or
+ * implement custom recursion.
+ *
+ * The visit functions return a bool that determines how the default implementation recurses. Once
+ * any visit call returns true, the default behavior stops recursing and propagates true up the
+ * stack.
+ */
+template <typename T>
+class TProgramVisitor {
+public:
+ virtual ~TProgramVisitor() = default;
+
+protected:
+ virtual bool visitExpression(typename T::Expression& expression);
+ virtual bool visitStatement(typename T::Statement& statement);
+ virtual bool visitProgramElement(typename T::ProgramElement& programElement);
+
+ virtual bool visitExpressionPtr(typename T::UniquePtrExpression& expr) = 0;
+ virtual bool visitStatementPtr(typename T::UniquePtrStatement& stmt) = 0;
+};
+
+// ProgramVisitors take const types; ProgramWriters do not.
+struct ProgramVisitorTypes {
+ using Program = const SkSL::Program;
+ using Expression = const SkSL::Expression;
+ using Statement = const SkSL::Statement;
+ using ProgramElement = const SkSL::ProgramElement;
+ using UniquePtrExpression = const std::unique_ptr<SkSL::Expression>;
+ using UniquePtrStatement = const std::unique_ptr<SkSL::Statement>;
+};
+
+extern template class TProgramVisitor<ProgramVisitorTypes>;
+
+class ProgramVisitor : public TProgramVisitor<ProgramVisitorTypes> {
+public:
+ bool visit(const Program& program);
+
+private:
+ // ProgramVisitors shouldn't need access to unique_ptrs, and marking these as final should help
+ // these accessors inline away. Use ProgramWriter if you need the unique_ptrs.
+ bool visitExpressionPtr(const std::unique_ptr<Expression>& e) final {
+ return this->visitExpression(*e);
+ }
+ bool visitStatementPtr(const std::unique_ptr<Statement>& s) final {
+ return this->visitStatement(*s);
+ }
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLSwitchCaseContainsExit.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLSwitchCaseContainsExit.cpp
new file mode 100644
index 0000000000..992df2adde
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/analysis/SkSLSwitchCaseContainsExit.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/SkSLAnalysis.h"
+
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLStatement.h"
+#include "src/sksl/analysis/SkSLProgramVisitor.h"
+
+namespace SkSL {
+
+class Expression;
+
+namespace {
+
+class SwitchCaseContainsExit : public ProgramVisitor {
+public:
+ SwitchCaseContainsExit(bool conditionalExits) : fConditionalExits(conditionalExits) {}
+
+ bool visitExpression(const Expression& expr) override {
+ // We can avoid processing expressions entirely.
+ return false;
+ }
+
+ bool visitStatement(const Statement& stmt) override {
+ switch (stmt.kind()) {
+ case Statement::Kind::kBlock:
+ case Statement::Kind::kSwitchCase:
+ return INHERITED::visitStatement(stmt);
+
+ case Statement::Kind::kReturn:
+ // Returns are an early exit regardless of the surrounding control structures.
+ return fConditionalExits ? fInConditional : !fInConditional;
+
+ case Statement::Kind::kContinue:
+ // Continues are an early exit from switches, but not loops.
+ return !fInLoop &&
+ (fConditionalExits ? fInConditional : !fInConditional);
+
+ case Statement::Kind::kBreak:
+ // Breaks cannot escape from switches or loops.
+ return !fInLoop && !fInSwitch &&
+ (fConditionalExits ? fInConditional : !fInConditional);
+
+ case Statement::Kind::kIf: {
+ ++fInConditional;
+ bool result = INHERITED::visitStatement(stmt);
+ --fInConditional;
+ return result;
+ }
+
+ case Statement::Kind::kFor:
+ case Statement::Kind::kDo: {
+ // Loops are treated as conditionals because a loop could potentially execute zero
+ // times. We don't have a straightforward way to determine that a loop definitely
+ // executes at least once.
+ ++fInConditional;
+ ++fInLoop;
+ bool result = INHERITED::visitStatement(stmt);
+ --fInLoop;
+ --fInConditional;
+ return result;
+ }
+
+ case Statement::Kind::kSwitch: {
+ ++fInSwitch;
+ bool result = INHERITED::visitStatement(stmt);
+ --fInSwitch;
+ return result;
+ }
+
+ default:
+ return false;
+ }
+ }
+
+ bool fConditionalExits = false;
+ int fInConditional = 0;
+ int fInLoop = 0;
+ int fInSwitch = 0;
+ using INHERITED = ProgramVisitor;
+};
+
+} // namespace
+
+bool Analysis::SwitchCaseContainsUnconditionalExit(Statement& stmt) {
+ return SwitchCaseContainsExit{/*conditionalExits=*/false}.visitStatement(stmt);
+}
+
+bool Analysis::SwitchCaseContainsConditionalExit(Statement& stmt) {
+ return SwitchCaseContainsExit{/*conditionalExits=*/true}.visitStatement(stmt);
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLSymbolTableStackBuilder.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLSymbolTableStackBuilder.cpp
new file mode 100644
index 0000000000..ada31aa000
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/analysis/SkSLSymbolTableStackBuilder.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLStatement.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/ir/SkSLBlock.h"
+#include "src/sksl/ir/SkSLForStatement.h"
+#include "src/sksl/ir/SkSLSwitchStatement.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+namespace SkSL {
+
+class SymbolTable;
+
+namespace Analysis {
+
+SymbolTableStackBuilder::SymbolTableStackBuilder(const Statement* stmt,
+ std::vector<std::shared_ptr<SymbolTable>>* stack) {
+ if (stmt) {
+ switch (stmt->kind()) {
+ case Statement::Kind::kBlock:
+ if (std::shared_ptr<SymbolTable> symbols = stmt->as<Block>().symbolTable()) {
+ stack->push_back(std::move(symbols));
+ fStackToPop = stack;
+ }
+ break;
+
+ case Statement::Kind::kFor:
+ if (std::shared_ptr<SymbolTable> symbols = stmt->as<ForStatement>().symbols()) {
+ stack->push_back(std::move(symbols));
+ fStackToPop = stack;
+ }
+ break;
+
+ case Statement::Kind::kSwitch:
+ if (std::shared_ptr<SymbolTable> symbols = stmt->as<SwitchStatement>().symbols()) {
+ stack->push_back(std::move(symbols));
+ fStackToPop = stack;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+SymbolTableStackBuilder::~SymbolTableStackBuilder() {
+ if (fStackToPop) {
+ fStackToPop->pop_back();
+ }
+}
+
+} // namespace Analysis
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLCodeGenerator.h b/gfx/skia/skia/src/sksl/codegen/SkSLCodeGenerator.h
new file mode 100644
index 0000000000..fd58648cd9
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/codegen/SkSLCodeGenerator.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_CODEGENERATOR
+#define SKSL_CODEGENERATOR
+
+#include "src/sksl/SkSLOutputStream.h"
+#include "src/sksl/ir/SkSLProgram.h"
+
+namespace SkSL {
+
+/**
+ * Abstract superclass of all code generators, which take a Program as input and produce code as
+ * output.
+ */
+class CodeGenerator {
+public:
+ CodeGenerator(const Context* context, const Program* program, OutputStream* out)
+ : fContext(*context)
+ , fProgram(*program)
+ , fOut(out) {}
+
+ virtual ~CodeGenerator() {}
+
+ virtual bool generateCode() = 0;
+
+ // Intended for use by AutoOutputStream.
+ OutputStream* outputStream() { return fOut; }
+ void setOutputStream(OutputStream* output) { fOut = output; }
+
+protected:
+#if defined(SK_USE_LEGACY_MIPMAP_LOD_BIAS)
+ static constexpr float kSharpenTexturesBias = -.5f;
+#else
+ // For SkMipmapMode::kLinear we want a bias such that when the unbiased LOD value is
+ // midway between levels we select just the larger level, i.e. a bias of -.5. However, using
+ // this bias with kNearest mode with a draw that is a perfect power of two downscale puts us
+ // right on the rounding edge where it could go up or down depending on the particular GPU.
+ // Experimentally we found that at -.49 most iOS devices (iPhone 7, 8, and iPad Pro
+ // [PowerVRGT7800 version]) all round to the level twice as big as the device space footprint
+ // for some such draws in our unit tests on GLES. However, the iPhone 11 still fails and so
+ // we are using -.475. They do not at -.48. All other GPUs passed tests with -.499. Though, at
+ // this time the bias is not implemented in the MSL codegen and so iOS/Metal was not tested.
+ static constexpr float kSharpenTexturesBias = -.475f;
+#endif
+
+ const Context& fContext;
+ const Program& fProgram;
+ OutputStream* fOut;
+};
+
+class AutoOutputStream {
+public:
+ // Maintains the current indentation level while writing to the new output stream.
+ AutoOutputStream(CodeGenerator* codeGen, OutputStream* newOutput)
+ : fCodeGen(codeGen)
+ , fOldOutput(codeGen->outputStream()) {
+ fCodeGen->setOutputStream(newOutput);
+ }
+ // Resets the indentation when entering the scope, and restores it when leaving.
+ AutoOutputStream(CodeGenerator* codeGen, OutputStream* newOutput, int *indentationPtr)
+ : fCodeGen(codeGen)
+ , fOldOutput(codeGen->outputStream())
+ , fIndentationPtr(indentationPtr)
+ , fOldIndentation(indentationPtr ? *indentationPtr : 0) {
+ fCodeGen->setOutputStream(newOutput);
+ *fIndentationPtr = 0;
+ }
+ ~AutoOutputStream() {
+ fCodeGen->setOutputStream(fOldOutput);
+ if (fIndentationPtr) {
+ *fIndentationPtr = fOldIndentation;
+ }
+ }
+
+private:
+ CodeGenerator* fCodeGen = nullptr;
+ OutputStream* fOldOutput = nullptr;
+ int *fIndentationPtr = nullptr;
+ int fOldIndentation = 0;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLGLSLCodeGenerator.cpp b/gfx/skia/skia/src/sksl/codegen/SkSLGLSLCodeGenerator.cpp
new file mode 100644
index 0000000000..c953a1a46c
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/codegen/SkSLGLSLCodeGenerator.cpp
@@ -0,0 +1,1774 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/codegen/SkSLGLSLCodeGenerator.h"
+
+#include "include/core/SkSpan.h"
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLLayout.h"
+#include "include/private/SkSLModifiers.h"
+#include "include/private/SkSLProgramElement.h"
+#include "include/private/SkSLStatement.h"
+#include "include/private/SkSLString.h"
+#include "include/private/base/SkTArray.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "include/sksl/SkSLOperator.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/base/SkStringView.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLGLSL.h"
+#include "src/sksl/SkSLIntrinsicList.h"
+#include "src/sksl/SkSLOutputStream.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/SkSLUtil.h"
+#include "src/sksl/ir/SkSLBinaryExpression.h"
+#include "src/sksl/ir/SkSLBlock.h"
+#include "src/sksl/ir/SkSLConstructor.h"
+#include "src/sksl/ir/SkSLConstructorArrayCast.h"
+#include "src/sksl/ir/SkSLConstructorCompound.h"
+#include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h"
+#include "src/sksl/ir/SkSLDoStatement.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLExpressionStatement.h"
+#include "src/sksl/ir/SkSLExtension.h"
+#include "src/sksl/ir/SkSLFieldAccess.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/SkSLFunctionPrototype.h"
+#include "src/sksl/ir/SkSLIfStatement.h"
+#include "src/sksl/ir/SkSLIndexExpression.h"
+#include "src/sksl/ir/SkSLInterfaceBlock.h"
+#include "src/sksl/ir/SkSLLiteral.h"
+#include "src/sksl/ir/SkSLModifiersDeclaration.h"
+#include "src/sksl/ir/SkSLPostfixExpression.h"
+#include "src/sksl/ir/SkSLPrefixExpression.h"
+#include "src/sksl/ir/SkSLProgram.h"
+#include "src/sksl/ir/SkSLReturnStatement.h"
+#include "src/sksl/ir/SkSLSetting.h"
+#include "src/sksl/ir/SkSLStructDefinition.h"
+#include "src/sksl/ir/SkSLSwitchCase.h"
+#include "src/sksl/ir/SkSLSwitchStatement.h"
+#include "src/sksl/ir/SkSLSwizzle.h"
+#include "src/sksl/ir/SkSLTernaryExpression.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/spirv.h"
+
+#include <cstddef>
+#include <memory>
+#include <vector>
+
+namespace SkSL {
+
+void GLSLCodeGenerator::write(std::string_view s) {
+ if (!s.length()) {
+ return;
+ }
+ if (fAtLineStart) {
+ for (int i = 0; i < fIndentation; i++) {
+ fOut->writeText(" ");
+ }
+ }
+ fOut->write(s.data(), s.length());
+ fAtLineStart = false;
+}
+
+void GLSLCodeGenerator::writeLine(std::string_view s) {
+ this->write(s);
+ fOut->writeText("\n");
+ fAtLineStart = true;
+}
+
+void GLSLCodeGenerator::finishLine() {
+ if (!fAtLineStart) {
+ this->writeLine();
+ }
+}
+
+void GLSLCodeGenerator::writeExtension(std::string_view name, bool require) {
+ fExtensions.writeText("#extension ");
+ fExtensions.write(name.data(), name.length());
+ fExtensions.writeText(require ? " : require\n" : " : enable\n");
+}
+
+bool GLSLCodeGenerator::usesPrecisionModifiers() const {
+ return this->caps().fUsesPrecisionModifiers;
+}
+
+void GLSLCodeGenerator::writeIdentifier(std::string_view identifier) {
+ // GLSL forbids two underscores in a row.
+ // If an identifier contains "__" or "_X", replace each "_" in the identifier with "_X".
+ if (skstd::contains(identifier, "__") || skstd::contains(identifier, "_X")) {
+ for (const char c : identifier) {
+ if (c == '_') {
+ this->write("_X");
+ } else {
+ this->write(std::string_view(&c, 1));
+ }
+ }
+ } else {
+ this->write(identifier);
+ }
+}
+
+// Returns the name of the type with array dimensions, e.g. `float[2]`.
+std::string GLSLCodeGenerator::getTypeName(const Type& raw) {
+ const Type& type = raw.resolve();
+ switch (type.typeKind()) {
+ case Type::TypeKind::kVector: {
+ const Type& component = type.componentType();
+ std::string result;
+ if (component.matches(*fContext.fTypes.fFloat) ||
+ component.matches(*fContext.fTypes.fHalf)) {
+ result = "vec";
+ }
+ else if (component.isSigned()) {
+ result = "ivec";
+ }
+ else if (component.isUnsigned()) {
+ result = "uvec";
+ }
+ else if (component.matches(*fContext.fTypes.fBool)) {
+ result = "bvec";
+ }
+ else {
+ SK_ABORT("unsupported vector type");
+ }
+ result += std::to_string(type.columns());
+ return result;
+ }
+ case Type::TypeKind::kMatrix: {
+ std::string result;
+ const Type& component = type.componentType();
+ if (component.matches(*fContext.fTypes.fFloat) ||
+ component.matches(*fContext.fTypes.fHalf)) {
+ result = "mat";
+ }
+ else {
+ SK_ABORT("unsupported matrix type");
+ }
+ result += std::to_string(type.columns());
+ if (type.columns() != type.rows()) {
+ result += "x";
+ result += std::to_string(type.rows());
+ }
+ return result;
+ }
+ case Type::TypeKind::kArray: {
+ std::string baseTypeName = this->getTypeName(type.componentType());
+ if (type.isUnsizedArray()) {
+ return String::printf("%s[]", baseTypeName.c_str());
+ }
+ return String::printf("%s[%d]", baseTypeName.c_str(), type.columns());
+ }
+ case Type::TypeKind::kScalar: {
+ if (type.matches(*fContext.fTypes.fHalf)) {
+ return "float";
+ }
+ else if (type.matches(*fContext.fTypes.fShort)) {
+ return "int";
+ }
+ else if (type.matches(*fContext.fTypes.fUShort)) {
+ return "uint";
+ }
+
+ return std::string(type.name());
+ }
+ default:
+ return std::string(type.name());
+ }
+}
+
+void GLSLCodeGenerator::writeStructDefinition(const StructDefinition& s) {
+ const Type& type = s.type();
+ this->write("struct ");
+ this->writeIdentifier(type.name());
+ this->writeLine(" {");
+ fIndentation++;
+ for (const auto& f : type.fields()) {
+ this->writeModifiers(f.fModifiers, false);
+ this->writeTypePrecision(*f.fType);
+ const Type& baseType = f.fType->isArray() ? f.fType->componentType() : *f.fType;
+ this->writeType(baseType);
+ this->write(" ");
+ this->writeIdentifier(f.fName);
+ if (f.fType->isArray()) {
+ this->write("[" + std::to_string(f.fType->columns()) + "]");
+ }
+ this->writeLine(";");
+ }
+ fIndentation--;
+ this->writeLine("};");
+}
+
+void GLSLCodeGenerator::writeType(const Type& type) {
+ this->writeIdentifier(this->getTypeName(type));
+}
+
+void GLSLCodeGenerator::writeExpression(const Expression& expr, Precedence parentPrecedence) {
+ switch (expr.kind()) {
+ case Expression::Kind::kBinary:
+ this->writeBinaryExpression(expr.as<BinaryExpression>(), parentPrecedence);
+ break;
+ case Expression::Kind::kConstructorDiagonalMatrix:
+ this->writeConstructorDiagonalMatrix(expr.as<ConstructorDiagonalMatrix>(),
+ parentPrecedence);
+ break;
+ case Expression::Kind::kConstructorArrayCast:
+ this->writeExpression(*expr.as<ConstructorArrayCast>().argument(), parentPrecedence);
+ break;
+ case Expression::Kind::kConstructorCompound:
+ this->writeConstructorCompound(expr.as<ConstructorCompound>(), parentPrecedence);
+ break;
+ case Expression::Kind::kConstructorArray:
+ case Expression::Kind::kConstructorMatrixResize:
+ case Expression::Kind::kConstructorSplat:
+ case Expression::Kind::kConstructorStruct:
+ this->writeAnyConstructor(expr.asAnyConstructor(), parentPrecedence);
+ break;
+ case Expression::Kind::kConstructorScalarCast:
+ case Expression::Kind::kConstructorCompoundCast:
+ this->writeCastConstructor(expr.asAnyConstructor(), parentPrecedence);
+ break;
+ case Expression::Kind::kFieldAccess:
+ this->writeFieldAccess(expr.as<FieldAccess>());
+ break;
+ case Expression::Kind::kFunctionCall:
+ this->writeFunctionCall(expr.as<FunctionCall>());
+ break;
+ case Expression::Kind::kLiteral:
+ this->writeLiteral(expr.as<Literal>());
+ break;
+ case Expression::Kind::kPrefix:
+ this->writePrefixExpression(expr.as<PrefixExpression>(), parentPrecedence);
+ break;
+ case Expression::Kind::kPostfix:
+ this->writePostfixExpression(expr.as<PostfixExpression>(), parentPrecedence);
+ break;
+ case Expression::Kind::kSetting:
+ this->writeExpression(*expr.as<Setting>().toLiteral(fContext), parentPrecedence);
+ break;
+ case Expression::Kind::kSwizzle:
+ this->writeSwizzle(expr.as<Swizzle>());
+ break;
+ case Expression::Kind::kVariableReference:
+ this->writeVariableReference(expr.as<VariableReference>());
+ break;
+ case Expression::Kind::kTernary:
+ this->writeTernaryExpression(expr.as<TernaryExpression>(), parentPrecedence);
+ break;
+ case Expression::Kind::kIndex:
+ this->writeIndexExpression(expr.as<IndexExpression>());
+ break;
+ default:
+ SkDEBUGFAILF("unsupported expression: %s", expr.description().c_str());
+ break;
+ }
+}
+
+static bool is_abs(Expression& expr) {
+ return expr.is<FunctionCall>() &&
+ expr.as<FunctionCall>().function().intrinsicKind() == k_abs_IntrinsicKind;
+}
+
+// turns min(abs(x), y) into ((tmpVar1 = abs(x)) < (tmpVar2 = y) ? tmpVar1 : tmpVar2) to avoid a
+// Tegra3 compiler bug.
+void GLSLCodeGenerator::writeMinAbsHack(Expression& absExpr, Expression& otherExpr) {
+ SkASSERT(!this->caps().fCanUseMinAndAbsTogether);
+ std::string tmpVar1 = "minAbsHackVar" + std::to_string(fVarCount++);
+ std::string tmpVar2 = "minAbsHackVar" + std::to_string(fVarCount++);
+ this->fFunctionHeader += std::string(" ") + this->getTypePrecision(absExpr.type()) +
+ this->getTypeName(absExpr.type()) + " " + tmpVar1 + ";\n";
+ this->fFunctionHeader += std::string(" ") + this->getTypePrecision(otherExpr.type()) +
+ this->getTypeName(otherExpr.type()) + " " + tmpVar2 + ";\n";
+ this->write("((" + tmpVar1 + " = ");
+ this->writeExpression(absExpr, Precedence::kTopLevel);
+ this->write(") < (" + tmpVar2 + " = ");
+ this->writeExpression(otherExpr, Precedence::kAssignment);
+ this->write(") ? " + tmpVar1 + " : " + tmpVar2 + ")");
+}
+
+void GLSLCodeGenerator::writeInverseSqrtHack(const Expression& x) {
+ this->write("(1.0 / sqrt(");
+ this->writeExpression(x, Precedence::kTopLevel);
+ this->write("))");
+}
+
+static constexpr char kDeterminant2[] = R"(
+float _determinant2(mat2 m) {
+return m[0].x*m[1].y - m[0].y*m[1].x;
+}
+)";
+
+static constexpr char kDeterminant3[] = R"(
+float _determinant3(mat3 m) {
+float
+ a00 = m[0].x, a01 = m[0].y, a02 = m[0].z,
+ a10 = m[1].x, a11 = m[1].y, a12 = m[1].z,
+ a20 = m[2].x, a21 = m[2].y, a22 = m[2].z,
+ b01 = a22*a11 - a12*a21,
+ b11 =-a22*a10 + a12*a20,
+ b21 = a21*a10 - a11*a20;
+return a00*b01 + a01*b11 + a02*b21;
+}
+)";
+
+static constexpr char kDeterminant4[] = R"(
+mat4 _determinant4(mat4 m) {
+float
+ a00 = m[0].x, a01 = m[0].y, a02 = m[0].z, a03 = m[0].w,
+ a10 = m[1].x, a11 = m[1].y, a12 = m[1].z, a13 = m[1].w,
+ a20 = m[2].x, a21 = m[2].y, a22 = m[2].z, a23 = m[2].w,
+ a30 = m[3].x, a31 = m[3].y, a32 = m[3].z, a33 = m[3].w,
+ b00 = a00*a11 - a01*a10,
+ b01 = a00*a12 - a02*a10,
+ b02 = a00*a13 - a03*a10,
+ b03 = a01*a12 - a02*a11,
+ b04 = a01*a13 - a03*a11,
+ b05 = a02*a13 - a03*a12,
+ b06 = a20*a31 - a21*a30,
+ b07 = a20*a32 - a22*a30,
+ b08 = a20*a33 - a23*a30,
+ b09 = a21*a32 - a22*a31,
+ b10 = a21*a33 - a23*a31,
+ b11 = a22*a33 - a23*a32;
+return b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06;
+}
+)";
+
+void GLSLCodeGenerator::writeDeterminantHack(const Expression& mat) {
+ const Type& type = mat.type();
+ if (type.matches(*fContext.fTypes.fFloat2x2) ||
+ type.matches(*fContext.fTypes.fHalf2x2)) {
+ this->write("_determinant2(");
+ if (!fWrittenDeterminant2) {
+ fWrittenDeterminant2 = true;
+ fExtraFunctions.writeText(kDeterminant2);
+ }
+ } else if (type.matches(*fContext.fTypes.fFloat3x3) ||
+ type.matches(*fContext.fTypes.fHalf3x3)) {
+ this->write("_determinant3(");
+ if (!fWrittenDeterminant3) {
+ fWrittenDeterminant3 = true;
+ fExtraFunctions.writeText(kDeterminant3);
+ }
+ } else if (type.matches(*fContext.fTypes.fFloat4x4) ||
+ type.matches(*fContext.fTypes.fHalf4x4)) {
+ this->write("_determinant4(");
+ if (!fWrittenDeterminant4) {
+ fWrittenDeterminant4 = true;
+ fExtraFunctions.writeText(kDeterminant4);
+ }
+ } else {
+ SkDEBUGFAILF("no polyfill for determinant(%s)", type.description().c_str());
+ this->write("determinant(");
+ }
+ this->writeExpression(mat, Precedence::kTopLevel);
+ this->write(")");
+}
+
+static constexpr char kInverse2[] = R"(
+mat2 _inverse2(mat2 m) {
+return mat2(m[1].y, -m[0].y, -m[1].x, m[0].x) / (m[0].x * m[1].y - m[0].y * m[1].x);
+}
+)";
+
+static constexpr char kInverse3[] = R"(
+mat3 _inverse3(mat3 m) {
+float
+ a00 = m[0].x, a01 = m[0].y, a02 = m[0].z,
+ a10 = m[1].x, a11 = m[1].y, a12 = m[1].z,
+ a20 = m[2].x, a21 = m[2].y, a22 = m[2].z,
+ b01 = a22*a11 - a12*a21,
+ b11 =-a22*a10 + a12*a20,
+ b21 = a21*a10 - a11*a20,
+ det = a00*b01 + a01*b11 + a02*b21;
+return mat3(
+ b01, (-a22*a01 + a02*a21), ( a12*a01 - a02*a11),
+ b11, ( a22*a00 - a02*a20), (-a12*a00 + a02*a10),
+ b21, (-a21*a00 + a01*a20), ( a11*a00 - a01*a10)) / det;
+}
+)";
+
+static constexpr char kInverse4[] = R"(
+mat4 _inverse4(mat4 m) {
+float
+ a00 = m[0].x, a01 = m[0].y, a02 = m[0].z, a03 = m[0].w,
+ a10 = m[1].x, a11 = m[1].y, a12 = m[1].z, a13 = m[1].w,
+ a20 = m[2].x, a21 = m[2].y, a22 = m[2].z, a23 = m[2].w,
+ a30 = m[3].x, a31 = m[3].y, a32 = m[3].z, a33 = m[3].w,
+ b00 = a00*a11 - a01*a10,
+ b01 = a00*a12 - a02*a10,
+ b02 = a00*a13 - a03*a10,
+ b03 = a01*a12 - a02*a11,
+ b04 = a01*a13 - a03*a11,
+ b05 = a02*a13 - a03*a12,
+ b06 = a20*a31 - a21*a30,
+ b07 = a20*a32 - a22*a30,
+ b08 = a20*a33 - a23*a30,
+ b09 = a21*a32 - a22*a31,
+ b10 = a21*a33 - a23*a31,
+ b11 = a22*a33 - a23*a32,
+ det = b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06;
+return mat4(
+ a11*b11 - a12*b10 + a13*b09,
+ a02*b10 - a01*b11 - a03*b09,
+ a31*b05 - a32*b04 + a33*b03,
+ a22*b04 - a21*b05 - a23*b03,
+ a12*b08 - a10*b11 - a13*b07,
+ a00*b11 - a02*b08 + a03*b07,
+ a32*b02 - a30*b05 - a33*b01,
+ a20*b05 - a22*b02 + a23*b01,
+ a10*b10 - a11*b08 + a13*b06,
+ a01*b08 - a00*b10 - a03*b06,
+ a30*b04 - a31*b02 + a33*b00,
+ a21*b02 - a20*b04 - a23*b00,
+ a11*b07 - a10*b09 - a12*b06,
+ a00*b09 - a01*b07 + a02*b06,
+ a31*b01 - a30*b03 - a32*b00,
+ a20*b03 - a21*b01 + a22*b00) / det;
+}
+)";
+
+void GLSLCodeGenerator::writeInverseHack(const Expression& mat) {
+ const Type& type = mat.type();
+ if (type.matches(*fContext.fTypes.fFloat2x2) || type.matches(*fContext.fTypes.fHalf2x2)) {
+ this->write("_inverse2(");
+ if (!fWrittenInverse2) {
+ fWrittenInverse2 = true;
+ fExtraFunctions.writeText(kInverse2);
+ }
+ } else if (type.matches(*fContext.fTypes.fFloat3x3) ||
+ type.matches(*fContext.fTypes.fHalf3x3)) {
+ this->write("_inverse3(");
+ if (!fWrittenInverse3) {
+ fWrittenInverse3 = true;
+ fExtraFunctions.writeText(kInverse3);
+ }
+ } else if (type.matches(*fContext.fTypes.fFloat4x4) ||
+ type.matches(*fContext.fTypes.fHalf4x4)) {
+ this->write("_inverse4(");
+ if (!fWrittenInverse4) {
+ fWrittenInverse4 = true;
+ fExtraFunctions.writeText(kInverse4);
+ }
+ } else {
+ SkDEBUGFAILF("no polyfill for inverse(%s)", type.description().c_str());
+ this->write("inverse(");
+ }
+ this->writeExpression(mat, Precedence::kTopLevel);
+ this->write(")");
+}
+
+void GLSLCodeGenerator::writeTransposeHack(const Expression& mat) {
+ const Type& type = mat.type();
+ int c = type.columns();
+ int r = type.rows();
+ std::string name = "transpose" + std::to_string(c) + std::to_string(r);
+
+ SkASSERT(c >= 2 && c <= 4);
+ SkASSERT(r >= 2 && r <= 4);
+ bool* writtenThisTranspose = &fWrittenTranspose[c - 2][r - 2];
+ if (!*writtenThisTranspose) {
+ *writtenThisTranspose = true;
+ std::string typeName = this->getTypeName(type);
+ const Type& base = type.componentType();
+ std::string transposed = this->getTypeName(base.toCompound(fContext, r, c));
+ fExtraFunctions.writeText((transposed + " " + name + "(" + typeName + " m) { return " +
+ transposed + "(").c_str());
+ auto separator = SkSL::String::Separator();
+ for (int row = 0; row < r; ++row) {
+ for (int column = 0; column < c; ++column) {
+ fExtraFunctions.writeText(separator().c_str());
+ fExtraFunctions.writeText(("m[" + std::to_string(column) + "][" +
+ std::to_string(row) + "]").c_str());
+ }
+ }
+ fExtraFunctions.writeText("); }\n");
+ }
+ this->write(name + "(");
+ this->writeExpression(mat, Precedence::kTopLevel);
+ this->write(")");
+}
+
+void GLSLCodeGenerator::writeFunctionCall(const FunctionCall& c) {
+ const FunctionDeclaration& function = c.function();
+ const ExpressionArray& arguments = c.arguments();
+ bool isTextureFunctionWithBias = false;
+ bool nameWritten = false;
+ const char* closingParen = ")";
+ switch (c.function().intrinsicKind()) {
+ case k_abs_IntrinsicKind: {
+ if (!this->caps().fEmulateAbsIntFunction)
+ break;
+ SkASSERT(arguments.size() == 1);
+ if (!arguments[0]->type().matches(*fContext.fTypes.fInt)) {
+ break;
+ }
+ // abs(int) on Intel OSX is incorrect, so emulate it:
+ this->write("_absemulation");
+ nameWritten = true;
+ if (!fWrittenAbsEmulation) {
+ fWrittenAbsEmulation = true;
+ fExtraFunctions.writeText("int _absemulation(int x) { return x * sign(x); }\n");
+ }
+ break;
+ }
+ case k_atan_IntrinsicKind:
+ if (this->caps().fMustForceNegatedAtanParamToFloat &&
+ arguments.size() == 2 &&
+ arguments[1]->kind() == Expression::Kind::kPrefix) {
+ const PrefixExpression& p = (PrefixExpression&) *arguments[1];
+ if (p.getOperator().kind() == Operator::Kind::MINUS) {
+ this->write("atan(");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write(", -1.0 * ");
+ this->writeExpression(*p.operand(), Precedence::kMultiplicative);
+ this->write(")");
+ return;
+ }
+ }
+ break;
+ case k_ldexp_IntrinsicKind:
+ if (this->caps().fMustForceNegatedLdexpParamToMultiply &&
+ arguments.size() == 2 &&
+ arguments[1]->is<PrefixExpression>()) {
+ const PrefixExpression& p = arguments[1]->as<PrefixExpression>();
+ if (p.getOperator().kind() == Operator::Kind::MINUS) {
+ this->write("ldexp(");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write(", ");
+ this->writeExpression(*p.operand(), Precedence::kMultiplicative);
+ this->write(" * -1)");
+ return;
+ }
+ }
+ break;
+ case k_dFdy_IntrinsicKind:
+ // Flipping Y also negates the Y derivatives.
+ closingParen = "))";
+ this->write("(");
+ if (!fProgram.fConfig->fSettings.fForceNoRTFlip) {
+ this->write(SKSL_RTFLIP_NAME ".y * ");
+ }
+ this->write("dFdy");
+ nameWritten = true;
+ [[fallthrough]];
+ case k_dFdx_IntrinsicKind:
+ case k_fwidth_IntrinsicKind:
+ if (!fFoundDerivatives &&
+ this->caps().shaderDerivativeExtensionString()) {
+ this->writeExtension(this->caps().shaderDerivativeExtensionString());
+ fFoundDerivatives = true;
+ }
+ break;
+ case k_determinant_IntrinsicKind:
+ if (!this->caps().fBuiltinDeterminantSupport) {
+ SkASSERT(arguments.size() == 1);
+ this->writeDeterminantHack(*arguments[0]);
+ return;
+ }
+ break;
+ case k_fma_IntrinsicKind:
+ if (!this->caps().fBuiltinFMASupport) {
+ SkASSERT(arguments.size() == 3);
+ this->write("((");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write(") * (");
+ this->writeExpression(*arguments[1], Precedence::kSequence);
+ this->write(") + (");
+ this->writeExpression(*arguments[2], Precedence::kSequence);
+ this->write("))");
+ return;
+ }
+ break;
+ case k_fract_IntrinsicKind:
+ if (!this->caps().fCanUseFractForNegativeValues) {
+ SkASSERT(arguments.size() == 1);
+ this->write("(0.5 - sign(");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write(") * (0.5 - fract(abs(");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write("))))");
+ return;
+ }
+ break;
+ case k_inverse_IntrinsicKind:
+ if (this->caps().fGLSLGeneration < SkSL::GLSLGeneration::k140) {
+ SkASSERT(arguments.size() == 1);
+ this->writeInverseHack(*arguments[0]);
+ return;
+ }
+ break;
+ case k_inversesqrt_IntrinsicKind:
+ if (this->caps().fGLSLGeneration < SkSL::GLSLGeneration::k130) {
+ SkASSERT(arguments.size() == 1);
+ this->writeInverseSqrtHack(*arguments[0]);
+ return;
+ }
+ break;
+ case k_min_IntrinsicKind:
+ if (!this->caps().fCanUseMinAndAbsTogether) {
+ SkASSERT(arguments.size() == 2);
+ if (is_abs(*arguments[0])) {
+ this->writeMinAbsHack(*arguments[0], *arguments[1]);
+ return;
+ }
+ if (is_abs(*arguments[1])) {
+ // note that this violates the GLSL left-to-right evaluation semantics.
+ // I doubt it will ever end up mattering, but it's worth calling out.
+ this->writeMinAbsHack(*arguments[1], *arguments[0]);
+ return;
+ }
+ }
+ break;
+ case k_pow_IntrinsicKind:
+ if (!this->caps().fRemovePowWithConstantExponent) {
+ break;
+ }
+ // pow(x, y) on some NVIDIA drivers causes crashes if y is a constant.
+ // It's hard to tell what constitutes "constant" here, so just replace in all cases.
+
+ // Change pow(x, y) into exp2(y * log2(x))
+ this->write("exp2(");
+ this->writeExpression(*arguments[1], Precedence::kMultiplicative);
+ this->write(" * log2(");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write("))");
+ return;
+ case k_saturate_IntrinsicKind:
+ SkASSERT(arguments.size() == 1);
+ this->write("clamp(");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write(", 0.0, 1.0)");
+ return;
+ case k_sample_IntrinsicKind: {
+ const char* dim = "";
+ bool proj = false;
+ const Type& arg0Type = arguments[0]->type();
+ const Type& arg1Type = arguments[1]->type();
+ switch (arg0Type.dimensions()) {
+ case SpvDim1D:
+ dim = "1D";
+ isTextureFunctionWithBias = true;
+ if (arg1Type.matches(*fContext.fTypes.fFloat)) {
+ proj = false;
+ } else {
+ SkASSERT(arg1Type.matches(*fContext.fTypes.fFloat2));
+ proj = true;
+ }
+ break;
+ case SpvDim2D:
+ dim = "2D";
+ if (!arg0Type.matches(*fContext.fTypes.fSamplerExternalOES)) {
+ isTextureFunctionWithBias = true;
+ }
+ if (arg1Type.matches(*fContext.fTypes.fFloat2)) {
+ proj = false;
+ } else {
+ SkASSERT(arg1Type.matches(*fContext.fTypes.fFloat3));
+ proj = true;
+ }
+ break;
+ case SpvDim3D:
+ dim = "3D";
+ isTextureFunctionWithBias = true;
+ if (arg1Type.matches(*fContext.fTypes.fFloat3)) {
+ proj = false;
+ } else {
+ SkASSERT(arg1Type.matches(*fContext.fTypes.fFloat4));
+ proj = true;
+ }
+ break;
+ case SpvDimCube:
+ dim = "Cube";
+ isTextureFunctionWithBias = true;
+ proj = false;
+ break;
+ case SpvDimRect:
+ dim = "2DRect";
+ proj = false;
+ break;
+ case SpvDimBuffer:
+ SkASSERT(false); // doesn't exist
+ dim = "Buffer";
+ proj = false;
+ break;
+ case SpvDimSubpassData:
+ SkASSERT(false); // doesn't exist
+ dim = "SubpassData";
+ proj = false;
+ break;
+ }
+ this->write("texture");
+ if (this->caps().fGLSLGeneration < SkSL::GLSLGeneration::k130) {
+ this->write(dim);
+ }
+ if (proj) {
+ this->write("Proj");
+ }
+ nameWritten = true;
+ break;
+ }
+ case k_sampleGrad_IntrinsicKind: {
+ SkASSERT(arguments.size() == 4);
+ this->write("textureGrad");
+ nameWritten = true;
+ break;
+ }
+ case k_sampleLod_IntrinsicKind: {
+ SkASSERT(arguments.size() == 3);
+ this->write("textureLod");
+ nameWritten = true;
+ break;
+ }
+ case k_transpose_IntrinsicKind:
+ if (this->caps().fGLSLGeneration < SkSL::GLSLGeneration::k130) {
+ SkASSERT(arguments.size() == 1);
+ this->writeTransposeHack(*arguments[0]);
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (!nameWritten) {
+ this->writeIdentifier(function.mangledName());
+ }
+ this->write("(");
+ auto separator = SkSL::String::Separator();
+ for (const auto& arg : arguments) {
+ this->write(separator());
+ this->writeExpression(*arg, Precedence::kSequence);
+ }
+ if (fProgram.fConfig->fSettings.fSharpenTextures && isTextureFunctionWithBias) {
+ this->write(String::printf(", %g", kSharpenTexturesBias));
+ }
+ this->write(closingParen);
+}
+
+void GLSLCodeGenerator::writeConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c,
+ Precedence parentPrecedence) {
+ if (c.type().columns() == 4 && c.type().rows() == 2) {
+ // Due to a longstanding bug in glslang and Mesa, several GPU drivers generate diagonal 4x2
+ // matrices incorrectly. (skia:12003, https://github.com/KhronosGroup/glslang/pull/2646)
+ // We can work around this issue by multiplying a scalar by the identity matrix.
+ // In practice, this doesn't come up naturally in real code and we don't know every affected
+ // driver, so we just apply this workaround everywhere.
+ this->write("(");
+ this->writeType(c.type());
+ this->write("(1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0) * ");
+ this->writeExpression(*c.argument(), Precedence::kMultiplicative);
+ this->write(")");
+ return;
+ }
+ this->writeAnyConstructor(c, parentPrecedence);
+}
+
+void GLSLCodeGenerator::writeConstructorCompound(const ConstructorCompound& c,
+ Precedence parentPrecedence) {
+ // If this is a 2x2 matrix constructor containing a single argument...
+ if (c.type().isMatrix() && c.arguments().size() == 1) {
+ // ... and that argument is a vec4...
+ const Expression& expr = *c.arguments().front();
+ if (expr.type().isVector() && expr.type().columns() == 4) {
+ // ... let's rewrite the cast to dodge issues on very old GPUs. (skia:13559)
+ if (Analysis::IsTrivialExpression(expr)) {
+ this->writeType(c.type());
+ this->write("(");
+ this->writeExpression(expr, Precedence::kPostfix);
+ this->write(".xy, ");
+ this->writeExpression(expr, Precedence::kPostfix);
+ this->write(".zw)");
+ } else {
+ std::string tempVec = "_tempVec" + std::to_string(fVarCount++);
+ this->fFunctionHeader += std::string(" ") + this->getTypePrecision(expr.type()) +
+ this->getTypeName(expr.type()) + " " + tempVec + ";\n";
+ this->write("((");
+ this->write(tempVec);
+ this->write(" = ");
+ this->writeExpression(expr, Precedence::kAssignment);
+ this->write("), ");
+ this->writeType(c.type());
+ this->write("(");
+ this->write(tempVec);
+ this->write(".xy, ");
+ this->write(tempVec);
+ this->write(".zw))");
+ }
+ return;
+ }
+ }
+ this->writeAnyConstructor(c, parentPrecedence);
+}
+
+void GLSLCodeGenerator::writeCastConstructor(const AnyConstructor& c, Precedence parentPrecedence) {
+ const auto arguments = c.argumentSpan();
+ SkASSERT(arguments.size() == 1);
+
+ const Expression& argument = *arguments.front();
+ if ((this->getTypeName(c.type()) == this->getTypeName(argument.type()) ||
+ (argument.type().matches(*fContext.fTypes.fFloatLiteral)))) {
+ // In cases like half(float), they're different types as far as SkSL is concerned but
+ // the same type as far as GLSL is concerned. We avoid a redundant float(float) by just
+ // writing out the inner expression here.
+ this->writeExpression(argument, parentPrecedence);
+ return;
+ }
+
+ // This cast should be emitted as-is.
+ return this->writeAnyConstructor(c, parentPrecedence);
+}
+
+void GLSLCodeGenerator::writeAnyConstructor(const AnyConstructor& c, Precedence parentPrecedence) {
+ this->writeType(c.type());
+ this->write("(");
+ auto separator = SkSL::String::Separator();
+ for (const auto& arg : c.argumentSpan()) {
+ this->write(separator());
+ this->writeExpression(*arg, Precedence::kSequence);
+ }
+ this->write(")");
+}
+
+void GLSLCodeGenerator::writeFragCoord() {
+ if (!this->caps().fCanUseFragCoord) {
+ if (!fSetupFragCoordWorkaround) {
+ const char* precision = this->usesPrecisionModifiers() ? "highp " : "";
+ fFunctionHeader += precision;
+ fFunctionHeader += " float sk_FragCoord_InvW = 1. / sk_FragCoord_Workaround.w;\n";
+ fFunctionHeader += precision;
+ fFunctionHeader += " vec4 sk_FragCoord_Resolved = "
+ "vec4(sk_FragCoord_Workaround.xyz * sk_FragCoord_InvW, sk_FragCoord_InvW);\n";
+ // Ensure that we get exact .5 values for x and y.
+ fFunctionHeader += " sk_FragCoord_Resolved.xy = floor(sk_FragCoord_Resolved.xy) + "
+ "vec2(.5);\n";
+ fSetupFragCoordWorkaround = true;
+ }
+ this->writeIdentifier("sk_FragCoord_Resolved");
+ return;
+ }
+
+ if (!fSetupFragPosition) {
+ fFunctionHeader += this->usesPrecisionModifiers() ? "highp " : "";
+ fFunctionHeader += " vec4 sk_FragCoord = vec4("
+ "gl_FragCoord.x, ";
+ if (fProgram.fConfig->fSettings.fForceNoRTFlip) {
+ fFunctionHeader += "gl_FragCoord.y, ";
+ } else {
+ fFunctionHeader += SKSL_RTFLIP_NAME ".x + " SKSL_RTFLIP_NAME ".y * gl_FragCoord.y, ";
+ }
+ fFunctionHeader +=
+ "gl_FragCoord.z, "
+ "gl_FragCoord.w);\n";
+ fSetupFragPosition = true;
+ }
+ this->writeIdentifier("sk_FragCoord");
+}
+
+void GLSLCodeGenerator::writeVariableReference(const VariableReference& ref) {
+ switch (ref.variable()->modifiers().fLayout.fBuiltin) {
+ case SK_FRAGCOLOR_BUILTIN:
+ if (this->caps().mustDeclareFragmentShaderOutput()) {
+ this->writeIdentifier("sk_FragColor");
+ } else {
+ this->writeIdentifier("gl_FragColor");
+ }
+ break;
+ case SK_SECONDARYFRAGCOLOR_BUILTIN:
+ this->writeIdentifier("gl_SecondaryFragColorEXT");
+ break;
+ case SK_FRAGCOORD_BUILTIN:
+ this->writeFragCoord();
+ break;
+ case SK_CLOCKWISE_BUILTIN:
+ if (!fSetupClockwise) {
+ fFunctionHeader += " bool sk_Clockwise = gl_FrontFacing;\n";
+ if (!fProgram.fConfig->fSettings.fForceNoRTFlip) {
+ fFunctionHeader += " if (" SKSL_RTFLIP_NAME ".y < 0.0) {\n"
+ " sk_Clockwise = !sk_Clockwise;\n"
+ " }\n";
+ }
+ fSetupClockwise = true;
+ }
+ this->writeIdentifier("sk_Clockwise");
+ break;
+ case SK_VERTEXID_BUILTIN:
+ this->writeIdentifier("gl_VertexID");
+ break;
+ case SK_INSTANCEID_BUILTIN:
+ this->writeIdentifier("gl_InstanceID");
+ break;
+ case SK_LASTFRAGCOLOR_BUILTIN:
+ if (this->caps().fFBFetchSupport) {
+ this->write(this->caps().fFBFetchColorName);
+ } else {
+ fContext.fErrors->error(ref.fPosition,
+ "sk_LastFragColor requires framebuffer fetch support");
+ }
+ break;
+ default:
+ this->writeIdentifier(ref.variable()->mangledName());
+ break;
+ }
+}
+
+void GLSLCodeGenerator::writeIndexExpression(const IndexExpression& expr) {
+ this->writeExpression(*expr.base(), Precedence::kPostfix);
+ this->write("[");
+ this->writeExpression(*expr.index(), Precedence::kTopLevel);
+ this->write("]");
+}
+
+bool is_sk_position(const FieldAccess& f) {
+ return f.base()->type().fields()[f.fieldIndex()].fModifiers.fLayout.fBuiltin ==
+ SK_POSITION_BUILTIN;
+}
+
+void GLSLCodeGenerator::writeFieldAccess(const FieldAccess& f) {
+ if (f.ownerKind() == FieldAccess::OwnerKind::kDefault) {
+ this->writeExpression(*f.base(), Precedence::kPostfix);
+ this->write(".");
+ }
+ const Type& baseType = f.base()->type();
+ int builtin = baseType.fields()[f.fieldIndex()].fModifiers.fLayout.fBuiltin;
+ if (builtin == SK_POSITION_BUILTIN) {
+ this->writeIdentifier("gl_Position");
+ } else if (builtin == SK_POINTSIZE_BUILTIN) {
+ this->writeIdentifier("gl_PointSize");
+ } else {
+ this->writeIdentifier(baseType.fields()[f.fieldIndex()].fName);
+ }
+}
+
+void GLSLCodeGenerator::writeSwizzle(const Swizzle& swizzle) {
+ this->writeExpression(*swizzle.base(), Precedence::kPostfix);
+ this->write(".");
+ for (int c : swizzle.components()) {
+ SkASSERT(c >= 0 && c <= 3);
+ this->write(&("x\0y\0z\0w\0"[c * 2]));
+ }
+}
+
+void GLSLCodeGenerator::writeMatrixComparisonWorkaround(const BinaryExpression& b) {
+ const Expression& left = *b.left();
+ const Expression& right = *b.right();
+ Operator op = b.getOperator();
+
+ SkASSERT(op.kind() == Operator::Kind::EQEQ || op.kind() == Operator::Kind::NEQ);
+ SkASSERT(left.type().isMatrix());
+ SkASSERT(right.type().isMatrix());
+
+ std::string tempMatrix1 = "_tempMatrix" + std::to_string(fVarCount++);
+ std::string tempMatrix2 = "_tempMatrix" + std::to_string(fVarCount++);
+
+ this->fFunctionHeader += std::string(" ") + this->getTypePrecision(left.type()) +
+ this->getTypeName(left.type()) + " " + tempMatrix1 + ";\n " +
+ this->getTypePrecision(right.type()) +
+ this->getTypeName(right.type()) + " " + tempMatrix2 + ";\n";
+ this->write("((" + tempMatrix1 + " = ");
+ this->writeExpression(left, Precedence::kAssignment);
+ this->write("), (" + tempMatrix2 + " = ");
+ this->writeExpression(right, Precedence::kAssignment);
+ this->write("), (" + tempMatrix1);
+ this->write(op.operatorName());
+ this->write(tempMatrix2 + "))");
+}
+
+void GLSLCodeGenerator::writeBinaryExpression(const BinaryExpression& b,
+ Precedence parentPrecedence) {
+ const Expression& left = *b.left();
+ const Expression& right = *b.right();
+ Operator op = b.getOperator();
+ if (this->caps().fUnfoldShortCircuitAsTernary &&
+ (op.kind() == Operator::Kind::LOGICALAND || op.kind() == Operator::Kind::LOGICALOR)) {
+ this->writeShortCircuitWorkaroundExpression(b, parentPrecedence);
+ return;
+ }
+
+ if (this->caps().fRewriteMatrixComparisons &&
+ left.type().isMatrix() && right.type().isMatrix() &&
+ (op.kind() == Operator::Kind::EQEQ || op.kind() == Operator::Kind::NEQ)) {
+ this->writeMatrixComparisonWorkaround(b);
+ return;
+ }
+
+ Precedence precedence = op.getBinaryPrecedence();
+ if (precedence >= parentPrecedence) {
+ this->write("(");
+ }
+ bool positionWorkaround = ProgramConfig::IsVertex(fProgram.fConfig->fKind) &&
+ op.isAssignment() &&
+ left.is<FieldAccess>() &&
+ is_sk_position(left.as<FieldAccess>()) &&
+ !Analysis::ContainsRTAdjust(right) &&
+ !this->caps().fCanUseFragCoord;
+ if (positionWorkaround) {
+ this->write("sk_FragCoord_Workaround = (");
+ }
+ this->writeExpression(left, precedence);
+ this->write(op.operatorName());
+ this->writeExpression(right, precedence);
+ if (positionWorkaround) {
+ this->write(")");
+ }
+ if (precedence >= parentPrecedence) {
+ this->write(")");
+ }
+}
+
+void GLSLCodeGenerator::writeShortCircuitWorkaroundExpression(const BinaryExpression& b,
+ Precedence parentPrecedence) {
+ if (Precedence::kTernary >= parentPrecedence) {
+ this->write("(");
+ }
+
+ // Transform:
+ // a && b => a ? b : false
+ // a || b => a ? true : b
+ this->writeExpression(*b.left(), Precedence::kTernary);
+ this->write(" ? ");
+ if (b.getOperator().kind() == Operator::Kind::LOGICALAND) {
+ this->writeExpression(*b.right(), Precedence::kTernary);
+ } else {
+ Literal boolTrue(Position(), /*value=*/1, fContext.fTypes.fBool.get());
+ this->writeLiteral(boolTrue);
+ }
+ this->write(" : ");
+ if (b.getOperator().kind() == Operator::Kind::LOGICALAND) {
+ Literal boolFalse(Position(), /*value=*/0, fContext.fTypes.fBool.get());
+ this->writeLiteral(boolFalse);
+ } else {
+ this->writeExpression(*b.right(), Precedence::kTernary);
+ }
+ if (Precedence::kTernary >= parentPrecedence) {
+ this->write(")");
+ }
+}
+
+void GLSLCodeGenerator::writeTernaryExpression(const TernaryExpression& t,
+ Precedence parentPrecedence) {
+ if (Precedence::kTernary >= parentPrecedence) {
+ this->write("(");
+ }
+ this->writeExpression(*t.test(), Precedence::kTernary);
+ this->write(" ? ");
+ this->writeExpression(*t.ifTrue(), Precedence::kTernary);
+ this->write(" : ");
+ this->writeExpression(*t.ifFalse(), Precedence::kTernary);
+ if (Precedence::kTernary >= parentPrecedence) {
+ this->write(")");
+ }
+}
+
+void GLSLCodeGenerator::writePrefixExpression(const PrefixExpression& p,
+ Precedence parentPrecedence) {
+ if (Precedence::kPrefix >= parentPrecedence) {
+ this->write("(");
+ }
+ this->write(p.getOperator().tightOperatorName());
+ this->writeExpression(*p.operand(), Precedence::kPrefix);
+ if (Precedence::kPrefix >= parentPrecedence) {
+ this->write(")");
+ }
+}
+
+void GLSLCodeGenerator::writePostfixExpression(const PostfixExpression& p,
+ Precedence parentPrecedence) {
+ if (Precedence::kPostfix >= parentPrecedence) {
+ this->write("(");
+ }
+ this->writeExpression(*p.operand(), Precedence::kPostfix);
+ this->write(p.getOperator().tightOperatorName());
+ if (Precedence::kPostfix >= parentPrecedence) {
+ this->write(")");
+ }
+}
+
+void GLSLCodeGenerator::writeLiteral(const Literal& l) {
+ const Type& type = l.type();
+ if (type.isInteger()) {
+ if (type.matches(*fContext.fTypes.fUInt)) {
+ this->write(std::to_string(l.intValue() & 0xffffffff) + "u");
+ } else if (type.matches(*fContext.fTypes.fUShort)) {
+ this->write(std::to_string(l.intValue() & 0xffff) + "u");
+ } else {
+ this->write(std::to_string(l.intValue()));
+ }
+ return;
+ }
+ this->write(l.description(OperatorPrecedence::kTopLevel));
+}
+
+void GLSLCodeGenerator::writeFunctionDeclaration(const FunctionDeclaration& f) {
+ this->writeTypePrecision(f.returnType());
+ this->writeType(f.returnType());
+ this->write(" ");
+ this->writeIdentifier(f.mangledName());
+ this->write("(");
+ auto separator = SkSL::String::Separator();
+ for (size_t index = 0; index < f.parameters().size(); ++index) {
+ const Variable* param = f.parameters()[index];
+
+ // This is a workaround for our test files. They use the runtime effect signature, so main
+ // takes a coords parameter. The IR generator tags those with a builtin ID (sk_FragCoord),
+ // and we omit them from the declaration here, so the function is valid GLSL.
+ if (f.isMain() && param->modifiers().fLayout.fBuiltin != -1) {
+ continue;
+ }
+ this->write(separator());
+ Modifiers modifiers = param->modifiers();
+ if (this->caps().fRemoveConstFromFunctionParameters) {
+ modifiers.fFlags &= ~Modifiers::kConst_Flag;
+ }
+ this->writeModifiers(modifiers, false);
+ std::vector<int> sizes;
+ const Type* type = &param->type();
+ if (type->isArray()) {
+ sizes.push_back(type->columns());
+ type = &type->componentType();
+ }
+ this->writeTypePrecision(*type);
+ this->writeType(*type);
+ this->write(" ");
+ if (!param->name().empty()) {
+ this->writeIdentifier(param->mangledName());
+ } else {
+ // By the spec, GLSL does not require function parameters to be named (see
+ // `single_declaration` in the Shading Language Grammar), but some older versions of
+ // GLSL report "formal parameter lacks a name" if a parameter is not named.
+ this->write("_skAnonymousParam");
+ this->write(std::to_string(index));
+ }
+ for (int s : sizes) {
+ this->write("[" + std::to_string(s) + "]");
+ }
+ }
+ this->write(")");
+}
+
+void GLSLCodeGenerator::writeFunction(const FunctionDefinition& f) {
+ fSetupFragPosition = false;
+ fSetupFragCoordWorkaround = false;
+
+ this->writeFunctionDeclaration(f.declaration());
+ this->writeLine(" {");
+ fIndentation++;
+
+ fFunctionHeader.clear();
+ OutputStream* oldOut = fOut;
+ StringStream buffer;
+ fOut = &buffer;
+ for (const std::unique_ptr<Statement>& stmt : f.body()->as<Block>().children()) {
+ if (!stmt->isEmpty()) {
+ this->writeStatement(*stmt);
+ this->finishLine();
+ }
+ }
+
+ fIndentation--;
+ this->writeLine("}");
+
+ fOut = oldOut;
+ this->write(fFunctionHeader);
+ this->write(buffer.str());
+}
+
+void GLSLCodeGenerator::writeFunctionPrototype(const FunctionPrototype& f) {
+ this->writeFunctionDeclaration(f.declaration());
+ this->writeLine(";");
+}
+
+void GLSLCodeGenerator::writeModifiers(const Modifiers& modifiers,
+ bool globalContext) {
+ std::string layout = modifiers.fLayout.description();
+ if (layout.size()) {
+ this->write(layout + " ");
+ }
+
+ // For GLSL 4.1 and below, qualifier-order matters! These are written out in Modifier-bit order.
+ if (modifiers.fFlags & Modifiers::kFlat_Flag) {
+ this->write("flat ");
+ }
+ if (modifiers.fFlags & Modifiers::kNoPerspective_Flag) {
+ this->write("noperspective ");
+ }
+
+ if (modifiers.fFlags & Modifiers::kConst_Flag) {
+ this->write("const ");
+ }
+ if (modifiers.fFlags & Modifiers::kUniform_Flag) {
+ this->write("uniform ");
+ }
+ if ((modifiers.fFlags & Modifiers::kIn_Flag) &&
+ (modifiers.fFlags & Modifiers::kOut_Flag)) {
+ this->write("inout ");
+ } else if (modifiers.fFlags & Modifiers::kIn_Flag) {
+ if (globalContext && this->caps().fGLSLGeneration < SkSL::GLSLGeneration::k130) {
+ this->write(ProgramConfig::IsVertex(fProgram.fConfig->fKind) ? "attribute "
+ : "varying ");
+ } else {
+ this->write("in ");
+ }
+ } else if (modifiers.fFlags & Modifiers::kOut_Flag) {
+ if (globalContext &&
+ this->caps().fGLSLGeneration < SkSL::GLSLGeneration::k130) {
+ this->write("varying ");
+ } else {
+ this->write("out ");
+ }
+ }
+
+ if (modifiers.fFlags & Modifiers::kReadOnly_Flag) {
+ this->write("readonly ");
+ }
+ if (modifiers.fFlags & Modifiers::kWriteOnly_Flag) {
+ this->write("writeonly ");
+ }
+ if (modifiers.fFlags & Modifiers::kBuffer_Flag) {
+ this->write("buffer ");
+ }
+}
+
+void GLSLCodeGenerator::writeInterfaceBlock(const InterfaceBlock& intf) {
+ if (intf.typeName() == "sk_PerVertex") {
+ return;
+ }
+ const Type* structType = &intf.var()->type().componentType();
+ this->writeModifiers(intf.var()->modifiers(), true);
+ this->writeType(*structType);
+ this->writeLine(" {");
+ fIndentation++;
+ for (const auto& f : structType->fields()) {
+ this->writeModifiers(f.fModifiers, false);
+ this->writeTypePrecision(*f.fType);
+ this->writeType(*f.fType);
+ this->write(" ");
+ this->writeIdentifier(f.fName);
+ this->writeLine(";");
+ }
+ fIndentation--;
+ this->write("}");
+ if (intf.instanceName().size()) {
+ this->write(" ");
+ this->writeIdentifier(intf.instanceName());
+ if (intf.arraySize() > 0) {
+ this->write("[");
+ this->write(std::to_string(intf.arraySize()));
+ this->write("]");
+ }
+ }
+ this->writeLine(";");
+}
+
+void GLSLCodeGenerator::writeVarInitializer(const Variable& var, const Expression& value) {
+ this->writeExpression(value, Precedence::kTopLevel);
+}
+
+const char* GLSLCodeGenerator::getTypePrecision(const Type& type) {
+ if (this->usesPrecisionModifiers()) {
+ switch (type.typeKind()) {
+ case Type::TypeKind::kScalar:
+ if (type.matches(*fContext.fTypes.fShort) ||
+ type.matches(*fContext.fTypes.fUShort)) {
+ if (fProgram.fConfig->fSettings.fForceHighPrecision ||
+ this->caps().fIncompleteShortIntPrecision) {
+ return "highp ";
+ }
+ return "mediump ";
+ }
+ if (type.matches(*fContext.fTypes.fHalf)) {
+ return fProgram.fConfig->fSettings.fForceHighPrecision ? "highp " : "mediump ";
+ }
+ if (type.matches(*fContext.fTypes.fFloat) || type.matches(*fContext.fTypes.fInt) ||
+ type.matches(*fContext.fTypes.fUInt)) {
+ return "highp ";
+ }
+ return "";
+ case Type::TypeKind::kVector: // fall through
+ case Type::TypeKind::kMatrix:
+ case Type::TypeKind::kArray:
+ return this->getTypePrecision(type.componentType());
+ default:
+ break;
+ }
+ }
+ return "";
+}
+
+void GLSLCodeGenerator::writeTypePrecision(const Type& type) {
+ this->write(this->getTypePrecision(type));
+}
+
+void GLSLCodeGenerator::writeVarDeclaration(const VarDeclaration& var, bool global) {
+ this->writeModifiers(var.var()->modifiers(), global);
+ this->writeTypePrecision(var.baseType());
+ this->writeType(var.baseType());
+ this->write(" ");
+ this->writeIdentifier(var.var()->mangledName());
+ if (var.arraySize() > 0) {
+ this->write("[");
+ this->write(std::to_string(var.arraySize()));
+ this->write("]");
+ }
+ if (var.value()) {
+ this->write(" = ");
+ this->writeVarInitializer(*var.var(), *var.value());
+ }
+ if (!fFoundExternalSamplerDecl &&
+ var.var()->type().matches(*fContext.fTypes.fSamplerExternalOES)) {
+ if (this->caps().externalTextureExtensionString()) {
+ this->writeExtension(this->caps().externalTextureExtensionString());
+ }
+ if (this->caps().secondExternalTextureExtensionString()) {
+ this->writeExtension(this->caps().secondExternalTextureExtensionString());
+ }
+ fFoundExternalSamplerDecl = true;
+ }
+ if (!fFoundRectSamplerDecl && var.var()->type().matches(*fContext.fTypes.fSampler2DRect)) {
+ fFoundRectSamplerDecl = true;
+ }
+ this->write(";");
+}
+
+void GLSLCodeGenerator::writeStatement(const Statement& s) {
+ switch (s.kind()) {
+ case Statement::Kind::kBlock:
+ this->writeBlock(s.as<Block>());
+ break;
+ case Statement::Kind::kExpression:
+ this->writeExpressionStatement(s.as<ExpressionStatement>());
+ break;
+ case Statement::Kind::kReturn:
+ this->writeReturnStatement(s.as<ReturnStatement>());
+ break;
+ case Statement::Kind::kVarDeclaration:
+ this->writeVarDeclaration(s.as<VarDeclaration>(), false);
+ break;
+ case Statement::Kind::kIf:
+ this->writeIfStatement(s.as<IfStatement>());
+ break;
+ case Statement::Kind::kFor:
+ this->writeForStatement(s.as<ForStatement>());
+ break;
+ case Statement::Kind::kDo:
+ this->writeDoStatement(s.as<DoStatement>());
+ break;
+ case Statement::Kind::kSwitch:
+ this->writeSwitchStatement(s.as<SwitchStatement>());
+ break;
+ case Statement::Kind::kBreak:
+ this->write("break;");
+ break;
+ case Statement::Kind::kContinue:
+ this->write("continue;");
+ break;
+ case Statement::Kind::kDiscard:
+ this->write("discard;");
+ break;
+ case Statement::Kind::kNop:
+ this->write(";");
+ break;
+ default:
+ SkDEBUGFAILF("unsupported statement: %s", s.description().c_str());
+ break;
+ }
+}
+
+void GLSLCodeGenerator::writeBlock(const Block& b) {
+ // Write scope markers if this block is a scope, or if the block is empty (since we need to emit
+ // something here to make the code valid).
+ bool isScope = b.isScope() || b.isEmpty();
+ if (isScope) {
+ this->writeLine("{");
+ fIndentation++;
+ }
+ for (const std::unique_ptr<Statement>& stmt : b.children()) {
+ if (!stmt->isEmpty()) {
+ this->writeStatement(*stmt);
+ this->finishLine();
+ }
+ }
+ if (isScope) {
+ fIndentation--;
+ this->write("}");
+ }
+}
+
+void GLSLCodeGenerator::writeIfStatement(const IfStatement& stmt) {
+ this->write("if (");
+ this->writeExpression(*stmt.test(), Precedence::kTopLevel);
+ this->write(") ");
+ this->writeStatement(*stmt.ifTrue());
+ if (stmt.ifFalse()) {
+ this->write(" else ");
+ this->writeStatement(*stmt.ifFalse());
+ }
+}
+
+void GLSLCodeGenerator::writeForStatement(const ForStatement& f) {
+ // Emit loops of the form 'for(;test;)' as 'while(test)', which is probably how they started
+ if (!f.initializer() && f.test() && !f.next()) {
+ this->write("while (");
+ this->writeExpression(*f.test(), Precedence::kTopLevel);
+ this->write(") ");
+ this->writeStatement(*f.statement());
+ return;
+ }
+
+ this->write("for (");
+ if (f.initializer() && !f.initializer()->isEmpty()) {
+ this->writeStatement(*f.initializer());
+ } else {
+ this->write("; ");
+ }
+ if (f.test()) {
+ if (this->caps().fAddAndTrueToLoopCondition) {
+ std::unique_ptr<Expression> and_true(new BinaryExpression(
+ Position(), f.test()->clone(), Operator::Kind::LOGICALAND,
+ Literal::MakeBool(fContext, Position(), /*value=*/true),
+ fContext.fTypes.fBool.get()));
+ this->writeExpression(*and_true, Precedence::kTopLevel);
+ } else {
+ this->writeExpression(*f.test(), Precedence::kTopLevel);
+ }
+ }
+ this->write("; ");
+ if (f.next()) {
+ this->writeExpression(*f.next(), Precedence::kTopLevel);
+ }
+ this->write(") ");
+ this->writeStatement(*f.statement());
+}
+
+void GLSLCodeGenerator::writeDoStatement(const DoStatement& d) {
+ if (!this->caps().fRewriteDoWhileLoops) {
+ this->write("do ");
+ this->writeStatement(*d.statement());
+ this->write(" while (");
+ this->writeExpression(*d.test(), Precedence::kTopLevel);
+ this->write(");");
+ return;
+ }
+
+ // Otherwise, do the do while loop workaround, to rewrite loops of the form:
+ // do {
+ // CODE;
+ // } while (CONDITION)
+ //
+ // to loops of the form
+ // bool temp = false;
+ // while (true) {
+ // if (temp) {
+ // if (!CONDITION) {
+ // break;
+ // }
+ // }
+ // temp = true;
+ // CODE;
+ // }
+ std::string tmpVar = "_tmpLoopSeenOnce" + std::to_string(fVarCount++);
+ this->write("bool ");
+ this->write(tmpVar);
+ this->writeLine(" = false;");
+ this->writeLine("while (true) {");
+ fIndentation++;
+ this->write("if (");
+ this->write(tmpVar);
+ this->writeLine(") {");
+ fIndentation++;
+ this->write("if (!");
+ this->writeExpression(*d.test(), Precedence::kPrefix);
+ this->writeLine(") {");
+ fIndentation++;
+ this->writeLine("break;");
+ fIndentation--;
+ this->writeLine("}");
+ fIndentation--;
+ this->writeLine("}");
+ this->write(tmpVar);
+ this->writeLine(" = true;");
+ this->writeStatement(*d.statement());
+ this->finishLine();
+ fIndentation--;
+ this->write("}");
+}
+
+void GLSLCodeGenerator::writeExpressionStatement(const ExpressionStatement& s) {
+ if (fProgram.fConfig->fSettings.fOptimize && !Analysis::HasSideEffects(*s.expression())) {
+ // Don't emit dead expressions.
+ return;
+ }
+ this->writeExpression(*s.expression(), Precedence::kTopLevel);
+ this->write(";");
+}
+
+void GLSLCodeGenerator::writeSwitchStatement(const SwitchStatement& s) {
+ if (this->caps().fRewriteSwitchStatements) {
+ std::string fallthroughVar = "_tmpSwitchFallthrough" + std::to_string(fVarCount++);
+ std::string valueVar = "_tmpSwitchValue" + std::to_string(fVarCount++);
+ std::string loopVar = "_tmpSwitchLoop" + std::to_string(fVarCount++);
+ this->write("int ");
+ this->write(valueVar);
+ this->write(" = ");
+ this->writeExpression(*s.value(), Precedence::kAssignment);
+ this->write(", ");
+ this->write(fallthroughVar);
+ this->writeLine(" = 0;");
+ this->write("for (int ");
+ this->write(loopVar);
+ this->write(" = 0; ");
+ this->write(loopVar);
+ this->write(" < 1; ");
+ this->write(loopVar);
+ this->writeLine("++) {");
+ fIndentation++;
+
+ bool firstCase = true;
+ for (const std::unique_ptr<Statement>& stmt : s.cases()) {
+ const SwitchCase& c = stmt->as<SwitchCase>();
+ if (!c.isDefault()) {
+ this->write("if ((");
+ if (firstCase) {
+ firstCase = false;
+ } else {
+ this->write(fallthroughVar);
+ this->write(" > 0) || (");
+ }
+ this->write(valueVar);
+ this->write(" == ");
+ this->write(std::to_string(c.value()));
+ this->writeLine(")) {");
+ fIndentation++;
+
+ // We write the entire case-block statement here, and then set `switchFallthrough`
+ // to 1. If the case-block had a break statement in it, we break out of the outer
+ // for-loop entirely, meaning the `switchFallthrough` assignment never occurs, nor
+ // does any code after it inside the switch. We've forbidden `continue` statements
+ // inside switch case-blocks entirely, so we don't need to consider their effect on
+ // control flow; see the Finalizer in FunctionDefinition::Convert.
+ this->writeStatement(*c.statement());
+ this->finishLine();
+ this->write(fallthroughVar);
+ this->write(" = 1;");
+ this->writeLine();
+
+ fIndentation--;
+ this->writeLine("}");
+ } else {
+ // This is the default case. Since it's always last, we can just dump in the code.
+ this->writeStatement(*c.statement());
+ this->finishLine();
+ }
+ }
+
+ fIndentation--;
+ this->writeLine("}");
+ return;
+ }
+
+ this->write("switch (");
+ this->writeExpression(*s.value(), Precedence::kTopLevel);
+ this->writeLine(") {");
+ fIndentation++;
+ // If a switch contains only a `default` case and nothing else, this confuses some drivers and
+ // can lead to a crash. Adding a real case before the default seems to work around the bug,
+ // and doesn't change the meaning of the switch. (skia:12465)
+ if (s.cases().size() == 1 && s.cases().front()->as<SwitchCase>().isDefault()) {
+ this->writeLine("case 0:");
+ }
+
+ // The GLSL spec insists that the last case in a switch statement must have an associated
+ // statement. In practice, the Apple GLSL compiler crashes if that statement is a no-op, such as
+ // a semicolon or an empty brace pair. (This is filed as FB11992149.) It also crashes if we put
+ // two `break` statements in a row. To work around this while honoring the rules of the
+ // standard, we inject an extra break if and only if the last switch-case block is empty.
+ bool foundEmptyCase = false;
+
+ for (const std::unique_ptr<Statement>& stmt : s.cases()) {
+ const SwitchCase& c = stmt->as<SwitchCase>();
+ if (c.isDefault()) {
+ this->writeLine("default:");
+ } else {
+ this->write("case ");
+ this->write(std::to_string(c.value()));
+ this->writeLine(":");
+ }
+ if (c.statement()->isEmpty()) {
+ foundEmptyCase = true;
+ } else {
+ foundEmptyCase = false;
+ fIndentation++;
+ this->writeStatement(*c.statement());
+ this->finishLine();
+ fIndentation--;
+ }
+ }
+ if (foundEmptyCase) {
+ fIndentation++;
+ this->writeLine("break;");
+ fIndentation--;
+ }
+ fIndentation--;
+ this->finishLine();
+ this->write("}");
+}
+
+void GLSLCodeGenerator::writeReturnStatement(const ReturnStatement& r) {
+ this->write("return");
+ if (r.expression()) {
+ this->write(" ");
+ this->writeExpression(*r.expression(), Precedence::kTopLevel);
+ }
+ this->write(";");
+}
+
+void GLSLCodeGenerator::writeHeader() {
+ if (this->caps().fVersionDeclString) {
+ this->write(this->caps().fVersionDeclString);
+ this->finishLine();
+ }
+}
+
+void GLSLCodeGenerator::writeProgramElement(const ProgramElement& e) {
+ switch (e.kind()) {
+ case ProgramElement::Kind::kExtension:
+ this->writeExtension(e.as<Extension>().name());
+ break;
+ case ProgramElement::Kind::kGlobalVar: {
+ const VarDeclaration& decl = e.as<GlobalVarDeclaration>().varDeclaration();
+ int builtin = decl.var()->modifiers().fLayout.fBuiltin;
+ if (builtin == -1) {
+ // normal var
+ this->writeVarDeclaration(decl, true);
+ this->finishLine();
+ } else if (builtin == SK_FRAGCOLOR_BUILTIN &&
+ this->caps().mustDeclareFragmentShaderOutput()) {
+ if (fProgram.fConfig->fSettings.fFragColorIsInOut) {
+ this->write("inout ");
+ } else {
+ this->write("out ");
+ }
+ if (this->usesPrecisionModifiers()) {
+ this->write("mediump ");
+ }
+ this->writeLine("vec4 sk_FragColor;");
+ }
+ break;
+ }
+ case ProgramElement::Kind::kInterfaceBlock:
+ this->writeInterfaceBlock(e.as<InterfaceBlock>());
+ break;
+ case ProgramElement::Kind::kFunction:
+ this->writeFunction(e.as<FunctionDefinition>());
+ break;
+ case ProgramElement::Kind::kFunctionPrototype:
+ this->writeFunctionPrototype(e.as<FunctionPrototype>());
+ break;
+ case ProgramElement::Kind::kModifiers: {
+ const Modifiers& modifiers = e.as<ModifiersDeclaration>().modifiers();
+ this->writeModifiers(modifiers, true);
+ this->writeLine(";");
+ break;
+ }
+ case ProgramElement::Kind::kStructDefinition:
+ this->writeStructDefinition(e.as<StructDefinition>());
+ break;
+ default:
+ SkDEBUGFAILF("unsupported program element %s\n", e.description().c_str());
+ break;
+ }
+}
+
+void GLSLCodeGenerator::writeInputVars() {
+ if (fProgram.fInputs.fUseFlipRTUniform) {
+ const char* precision = this->usesPrecisionModifiers() ? "highp " : "";
+ fGlobals.writeText("uniform ");
+ fGlobals.writeText(precision);
+ fGlobals.writeText("vec2 " SKSL_RTFLIP_NAME ";\n");
+ }
+}
+
+bool GLSLCodeGenerator::generateCode() {
+ this->writeHeader();
+ OutputStream* rawOut = fOut;
+ StringStream body;
+ fOut = &body;
+ // Write all the program elements except for functions.
+ for (const ProgramElement* e : fProgram.elements()) {
+ if (!e->is<FunctionDefinition>()) {
+ this->writeProgramElement(*e);
+ }
+ }
+ // Emit prototypes for every built-in function; these aren't always added in perfect order.
+ for (const ProgramElement* e : fProgram.fSharedElements) {
+ if (e->is<FunctionDefinition>()) {
+ this->writeFunctionDeclaration(e->as<FunctionDefinition>().declaration());
+ this->writeLine(";");
+ }
+ }
+ // Write the functions last.
+ // Why don't we write things in their original order? Because the Inliner likes to move function
+ // bodies around. After inlining, code can inadvertently move upwards, above ProgramElements
+ // that the code relies on.
+ for (const ProgramElement* e : fProgram.elements()) {
+ if (e->is<FunctionDefinition>()) {
+ this->writeProgramElement(*e);
+ }
+ }
+ fOut = rawOut;
+
+ write_stringstream(fExtensions, *rawOut);
+ this->writeInputVars();
+ write_stringstream(fGlobals, *rawOut);
+
+ if (!this->caps().fCanUseFragCoord) {
+ Layout layout;
+ if (ProgramConfig::IsVertex(fProgram.fConfig->fKind)) {
+ Modifiers modifiers(layout, Modifiers::kOut_Flag);
+ this->writeModifiers(modifiers, true);
+ if (this->usesPrecisionModifiers()) {
+ this->write("highp ");
+ }
+ this->write("vec4 sk_FragCoord_Workaround;\n");
+ } else if (ProgramConfig::IsFragment(fProgram.fConfig->fKind)) {
+ Modifiers modifiers(layout, Modifiers::kIn_Flag);
+ this->writeModifiers(modifiers, true);
+ if (this->usesPrecisionModifiers()) {
+ this->write("highp ");
+ }
+ this->write("vec4 sk_FragCoord_Workaround;\n");
+ }
+ }
+
+ if (this->usesPrecisionModifiers()) {
+ const char* precision =
+ fProgram.fConfig->fSettings.fForceHighPrecision ? "highp" : "mediump";
+ this->write(String::printf("precision %s float;\n", precision));
+ this->write(String::printf("precision %s sampler2D;\n", precision));
+ if (fFoundExternalSamplerDecl && !this->caps().fNoDefaultPrecisionForExternalSamplers) {
+ this->write(String::printf("precision %s samplerExternalOES;\n", precision));
+ }
+ if (fFoundRectSamplerDecl) {
+ this->write(String::printf("precision %s sampler2DRect;\n", precision));
+ }
+ }
+ write_stringstream(fExtraFunctions, *rawOut);
+ write_stringstream(body, *rawOut);
+ return fContext.fErrors->errorCount() == 0;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLGLSLCodeGenerator.h b/gfx/skia/skia/src/sksl/codegen/SkSLGLSLCodeGenerator.h
new file mode 100644
index 0000000000..e6672ab45f
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/codegen/SkSLGLSLCodeGenerator.h
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_GLSLCODEGENERATOR
+#define SKSL_GLSLCODEGENERATOR
+
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLStringStream.h"
+#include "src/sksl/codegen/SkSLCodeGenerator.h"
+
+#include <cstdint>
+#include <string>
+#include <string_view>
+
+namespace SkSL {
+
+class AnyConstructor;
+class BinaryExpression;
+class Block;
+class ConstructorCompound;
+class ConstructorDiagonalMatrix;
+class DoStatement;
+class Expression;
+class ExpressionStatement;
+class FieldAccess;
+class ForStatement;
+class FunctionCall;
+class FunctionDeclaration;
+class FunctionDefinition;
+class FunctionPrototype;
+class IfStatement;
+class IndexExpression;
+class InterfaceBlock;
+class Literal;
+class OutputStream;
+class PostfixExpression;
+class PrefixExpression;
+class ProgramElement;
+class ReturnStatement;
+class Statement;
+class StructDefinition;
+class SwitchStatement;
+class Swizzle;
+class TernaryExpression;
+class Type;
+class VarDeclaration;
+class Variable;
+class VariableReference;
+enum class OperatorPrecedence : uint8_t;
+struct Layout;
+struct Modifiers;
+struct Program;
+struct ShaderCaps;
+
+/**
+ * Converts a Program into GLSL code.
+ */
+class GLSLCodeGenerator : public CodeGenerator {
+public:
+ GLSLCodeGenerator(const Context* context, const Program* program, OutputStream* out)
+ : INHERITED(context, program, out) {}
+
+ bool generateCode() override;
+
+protected:
+ using Precedence = OperatorPrecedence;
+
+ void write(std::string_view s);
+
+ void writeLine(std::string_view s = std::string_view());
+
+ void finishLine();
+
+ virtual void writeHeader();
+
+ bool usesPrecisionModifiers() const;
+
+ void writeIdentifier(std::string_view identifier);
+
+ virtual std::string getTypeName(const Type& type);
+
+ void writeStructDefinition(const StructDefinition& s);
+
+ void writeType(const Type& type);
+
+ void writeExtension(std::string_view name, bool require = true);
+
+ void writeInterfaceBlock(const InterfaceBlock& intf);
+
+ void writeFunctionDeclaration(const FunctionDeclaration& f);
+
+ void writeFunctionPrototype(const FunctionPrototype& f);
+
+ virtual void writeFunction(const FunctionDefinition& f);
+
+ void writeLayout(const Layout& layout);
+
+ void writeModifiers(const Modifiers& modifiers, bool globalContext);
+
+ virtual void writeInputVars();
+
+ virtual void writeVarInitializer(const Variable& var, const Expression& value);
+
+ const char* getTypePrecision(const Type& type);
+
+ void writeTypePrecision(const Type& type);
+
+ void writeVarDeclaration(const VarDeclaration& var, bool global);
+
+ void writeFragCoord();
+
+ virtual void writeVariableReference(const VariableReference& ref);
+
+ void writeExpression(const Expression& expr, Precedence parentPrecedence);
+
+ void writeIntrinsicCall(const FunctionCall& c);
+
+ void writeMinAbsHack(Expression& absExpr, Expression& otherExpr);
+
+ void writeDeterminantHack(const Expression& mat);
+
+ void writeInverseHack(const Expression& mat);
+
+ void writeTransposeHack(const Expression& mat);
+
+ void writeInverseSqrtHack(const Expression& x);
+
+ void writeMatrixComparisonWorkaround(const BinaryExpression& x);
+
+ virtual void writeFunctionCall(const FunctionCall& c);
+
+ void writeConstructorCompound(const ConstructorCompound& c, Precedence parentPrecedence);
+
+ void writeConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c,
+ Precedence parentPrecedence);
+
+ virtual void writeAnyConstructor(const AnyConstructor& c, Precedence parentPrecedence);
+
+ virtual void writeCastConstructor(const AnyConstructor& c, Precedence parentPrecedence);
+
+ virtual void writeFieldAccess(const FieldAccess& f);
+
+ virtual void writeSwizzle(const Swizzle& swizzle);
+
+ virtual void writeBinaryExpression(const BinaryExpression& b, Precedence parentPrecedence);
+
+ void writeShortCircuitWorkaroundExpression(const BinaryExpression& b,
+ Precedence parentPrecedence);
+
+ virtual void writeTernaryExpression(const TernaryExpression& t, Precedence parentPrecedence);
+
+ virtual void writeIndexExpression(const IndexExpression& expr);
+
+ void writePrefixExpression(const PrefixExpression& p, Precedence parentPrecedence);
+
+ void writePostfixExpression(const PostfixExpression& p, Precedence parentPrecedence);
+
+ virtual void writeLiteral(const Literal& l);
+
+ void writeStatement(const Statement& s);
+
+ void writeBlock(const Block& b);
+
+ virtual void writeIfStatement(const IfStatement& stmt);
+
+ void writeForStatement(const ForStatement& f);
+
+ void writeDoStatement(const DoStatement& d);
+
+ void writeExpressionStatement(const ExpressionStatement& s);
+
+ virtual void writeSwitchStatement(const SwitchStatement& s);
+
+ virtual void writeReturnStatement(const ReturnStatement& r);
+
+ virtual void writeProgramElement(const ProgramElement& e);
+
+ const ShaderCaps& caps() const { return *fContext.fCaps; }
+
+ StringStream fExtensions;
+ StringStream fGlobals;
+ StringStream fExtraFunctions;
+ std::string fFunctionHeader;
+ int fVarCount = 0;
+ int fIndentation = 0;
+ bool fAtLineStart = false;
+ // true if we have run into usages of dFdx / dFdy
+ bool fFoundDerivatives = false;
+ bool fFoundExternalSamplerDecl = false;
+ bool fFoundRectSamplerDecl = false;
+ bool fSetupClockwise = false;
+ bool fSetupFragPosition = false;
+ bool fSetupFragCoordWorkaround = false;
+
+ // Workaround/polyfill flags
+ bool fWrittenAbsEmulation = false;
+ bool fWrittenDeterminant2 = false, fWrittenDeterminant3 = false, fWrittenDeterminant4 = false;
+ bool fWrittenInverse2 = false, fWrittenInverse3 = false, fWrittenInverse4 = false;
+ bool fWrittenTranspose[3][3] = {};
+
+ using INHERITED = CodeGenerator;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLMetalCodeGenerator.cpp b/gfx/skia/skia/src/sksl/codegen/SkSLMetalCodeGenerator.cpp
new file mode 100644
index 0000000000..d173fae687
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/codegen/SkSLMetalCodeGenerator.cpp
@@ -0,0 +1,3226 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/codegen/SkSLMetalCodeGenerator.h"
+
+#include "include/core/SkSpan.h"
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLLayout.h"
+#include "include/private/SkSLModifiers.h"
+#include "include/private/SkSLProgramElement.h"
+#include "include/private/SkSLStatement.h"
+#include "include/private/SkSLString.h"
+#include "include/private/base/SkTo.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "include/sksl/SkSLOperator.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/base/SkScopeExit.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLIntrinsicList.h"
+#include "src/sksl/SkSLMemoryLayout.h"
+#include "src/sksl/SkSLOutputStream.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/SkSLUtil.h"
+#include "src/sksl/analysis/SkSLProgramVisitor.h"
+#include "src/sksl/ir/SkSLBinaryExpression.h"
+#include "src/sksl/ir/SkSLBlock.h"
+#include "src/sksl/ir/SkSLConstructor.h"
+#include "src/sksl/ir/SkSLConstructorArrayCast.h"
+#include "src/sksl/ir/SkSLConstructorCompound.h"
+#include "src/sksl/ir/SkSLConstructorMatrixResize.h"
+#include "src/sksl/ir/SkSLDoStatement.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLExpressionStatement.h"
+#include "src/sksl/ir/SkSLExtension.h"
+#include "src/sksl/ir/SkSLFieldAccess.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/SkSLFunctionPrototype.h"
+#include "src/sksl/ir/SkSLIfStatement.h"
+#include "src/sksl/ir/SkSLIndexExpression.h"
+#include "src/sksl/ir/SkSLInterfaceBlock.h"
+#include "src/sksl/ir/SkSLLiteral.h"
+#include "src/sksl/ir/SkSLModifiersDeclaration.h"
+#include "src/sksl/ir/SkSLNop.h"
+#include "src/sksl/ir/SkSLPostfixExpression.h"
+#include "src/sksl/ir/SkSLPrefixExpression.h"
+#include "src/sksl/ir/SkSLProgram.h"
+#include "src/sksl/ir/SkSLReturnStatement.h"
+#include "src/sksl/ir/SkSLSetting.h"
+#include "src/sksl/ir/SkSLStructDefinition.h"
+#include "src/sksl/ir/SkSLSwitchCase.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/SkSLVariable.h"
+#include "src/sksl/ir/SkSLVariableReference.h"
+#include "src/sksl/spirv.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <functional>
+#include <limits>
+#include <memory>
+
+namespace SkSL {
+
+static const char* operator_name(Operator op) {
+ switch (op.kind()) {
+ case Operator::Kind::LOGICALXOR: return " != ";
+ default: return op.operatorName();
+ }
+}
+
+class MetalCodeGenerator::GlobalStructVisitor {
+public:
+ virtual ~GlobalStructVisitor() = default;
+ virtual void visitInterfaceBlock(const InterfaceBlock& block, std::string_view blockName) {}
+ virtual void visitTexture(const Type& type, const Modifiers& modifiers,
+ std::string_view name) {}
+ virtual void visitSampler(const Type& type, std::string_view name) {}
+ virtual void visitConstantVariable(const VarDeclaration& decl) {}
+ virtual void visitNonconstantVariable(const Variable& var, const Expression* value) {}
+};
+
+class MetalCodeGenerator::ThreadgroupStructVisitor {
+public:
+ virtual ~ThreadgroupStructVisitor() = default;
+ virtual void visitNonconstantVariable(const Variable& var) = 0;
+};
+
+void MetalCodeGenerator::write(std::string_view s) {
+ if (s.empty()) {
+ return;
+ }
+ if (fAtLineStart) {
+ for (int i = 0; i < fIndentation; i++) {
+ fOut->writeText(" ");
+ }
+ }
+ fOut->writeText(std::string(s).c_str());
+ fAtLineStart = false;
+}
+
+void MetalCodeGenerator::writeLine(std::string_view s) {
+ this->write(s);
+ fOut->writeText(fLineEnding);
+ fAtLineStart = true;
+}
+
+void MetalCodeGenerator::finishLine() {
+ if (!fAtLineStart) {
+ this->writeLine();
+ }
+}
+
+void MetalCodeGenerator::writeExtension(const Extension& ext) {
+ this->writeLine("#extension " + std::string(ext.name()) + " : enable");
+}
+
+std::string MetalCodeGenerator::typeName(const Type& type) {
+ // we need to know the modifiers for textures
+ switch (type.typeKind()) {
+ case Type::TypeKind::kArray:
+ SkASSERT(!type.isUnsizedArray());
+ SkASSERTF(type.columns() > 0, "invalid array size: %s", type.description().c_str());
+ return String::printf("array<%s, %d>",
+ this->typeName(type.componentType()).c_str(), type.columns());
+
+ case Type::TypeKind::kVector:
+ return this->typeName(type.componentType()) + std::to_string(type.columns());
+
+ case Type::TypeKind::kMatrix:
+ return this->typeName(type.componentType()) + std::to_string(type.columns()) + "x" +
+ std::to_string(type.rows());
+
+ case Type::TypeKind::kSampler:
+ if (type.dimensions() != SpvDim2D) {
+ fContext.fErrors->error(Position(), "Unsupported texture dimensions");
+ }
+ return "sampler2D";
+
+ case Type::TypeKind::kTexture:
+ switch (type.textureAccess()) {
+ case Type::TextureAccess::kSample: return "texture2d<half>";
+ case Type::TextureAccess::kRead: return "texture2d<half, access::read>";
+ case Type::TextureAccess::kWrite: return "texture2d<half, access::write>";
+ case Type::TextureAccess::kReadWrite: return "texture2d<half, access::read_write>";
+ default: break;
+ }
+ SkUNREACHABLE;
+ case Type::TypeKind::kAtomic:
+ // SkSL currently only supports the atomicUint type.
+ SkASSERT(type.matches(*fContext.fTypes.fAtomicUInt));
+ return "atomic_uint";
+ default:
+ return std::string(type.name());
+ }
+}
+
+void MetalCodeGenerator::writeStructDefinition(const StructDefinition& s) {
+ const Type& type = s.type();
+ this->writeLine("struct " + type.displayName() + " {");
+ fIndentation++;
+ this->writeFields(type.fields(), type.fPosition);
+ fIndentation--;
+ this->writeLine("};");
+}
+
+void MetalCodeGenerator::writeType(const Type& type) {
+ this->write(this->typeName(type));
+}
+
+void MetalCodeGenerator::writeExpression(const Expression& expr, Precedence parentPrecedence) {
+ switch (expr.kind()) {
+ case Expression::Kind::kBinary:
+ this->writeBinaryExpression(expr.as<BinaryExpression>(), parentPrecedence);
+ break;
+ case Expression::Kind::kConstructorArray:
+ case Expression::Kind::kConstructorStruct:
+ this->writeAnyConstructor(expr.asAnyConstructor(), "{", "}", parentPrecedence);
+ break;
+ case Expression::Kind::kConstructorArrayCast:
+ this->writeConstructorArrayCast(expr.as<ConstructorArrayCast>(), parentPrecedence);
+ break;
+ case Expression::Kind::kConstructorCompound:
+ this->writeConstructorCompound(expr.as<ConstructorCompound>(), parentPrecedence);
+ break;
+ case Expression::Kind::kConstructorDiagonalMatrix:
+ case Expression::Kind::kConstructorSplat:
+ this->writeAnyConstructor(expr.asAnyConstructor(), "(", ")", parentPrecedence);
+ break;
+ case Expression::Kind::kConstructorMatrixResize:
+ this->writeConstructorMatrixResize(expr.as<ConstructorMatrixResize>(),
+ parentPrecedence);
+ break;
+ case Expression::Kind::kConstructorScalarCast:
+ case Expression::Kind::kConstructorCompoundCast:
+ this->writeCastConstructor(expr.asAnyConstructor(), "(", ")", parentPrecedence);
+ break;
+ case Expression::Kind::kFieldAccess:
+ this->writeFieldAccess(expr.as<FieldAccess>());
+ break;
+ case Expression::Kind::kLiteral:
+ this->writeLiteral(expr.as<Literal>());
+ break;
+ case Expression::Kind::kFunctionCall:
+ this->writeFunctionCall(expr.as<FunctionCall>());
+ break;
+ case Expression::Kind::kPrefix:
+ this->writePrefixExpression(expr.as<PrefixExpression>(), parentPrecedence);
+ break;
+ case Expression::Kind::kPostfix:
+ this->writePostfixExpression(expr.as<PostfixExpression>(), parentPrecedence);
+ break;
+ case Expression::Kind::kSetting:
+ this->writeExpression(*expr.as<Setting>().toLiteral(fContext), parentPrecedence);
+ break;
+ case Expression::Kind::kSwizzle:
+ this->writeSwizzle(expr.as<Swizzle>());
+ break;
+ case Expression::Kind::kVariableReference:
+ this->writeVariableReference(expr.as<VariableReference>());
+ break;
+ case Expression::Kind::kTernary:
+ this->writeTernaryExpression(expr.as<TernaryExpression>(), parentPrecedence);
+ break;
+ case Expression::Kind::kIndex:
+ this->writeIndexExpression(expr.as<IndexExpression>());
+ break;
+ default:
+ SkDEBUGFAILF("unsupported expression: %s", expr.description().c_str());
+ break;
+ }
+}
+
+// returns true if we should pass by reference instead of by value
+static bool pass_by_reference(const Type& type, const Modifiers& modifiers) {
+ return (modifiers.fFlags & Modifiers::kOut_Flag) && !type.isUnsizedArray();
+}
+
+// returns true if we need to specify an address space modifier
+static bool needs_address_space(const Type& type, const Modifiers& modifiers) {
+ return type.isUnsizedArray() || pass_by_reference(type, modifiers);
+}
+
+// returns true if the InterfaceBlock has the `buffer` modifier
+static bool is_buffer(const InterfaceBlock& block) {
+ return block.var()->modifiers().fFlags & Modifiers::kBuffer_Flag;
+}
+
+// returns true if the InterfaceBlock has the `readonly` modifier
+static bool is_readonly(const InterfaceBlock& block) {
+ return block.var()->modifiers().fFlags & Modifiers::kReadOnly_Flag;
+}
+
+std::string MetalCodeGenerator::getOutParamHelper(const FunctionCall& call,
+ const ExpressionArray& arguments,
+ const SkTArray<VariableReference*>& outVars) {
+ // It's possible for out-param function arguments to contain an out-param function call
+ // expression. Emit the function into a temporary stream to prevent the nested helper from
+ // clobbering the current helper as we recursively evaluate argument expressions.
+ StringStream tmpStream;
+ AutoOutputStream outputToExtraFunctions(this, &tmpStream, &fIndentation);
+
+ const FunctionDeclaration& function = call.function();
+
+ std::string name = "_skOutParamHelper" + std::to_string(fSwizzleHelperCount++) +
+ "_" + function.mangledName();
+ const char* separator = "";
+
+ // Emit a prototype for the function we'll be calling through to in our helper.
+ if (!function.isBuiltin()) {
+ this->writeFunctionDeclaration(function);
+ this->writeLine(";");
+ }
+
+ // Synthesize a helper function that takes the same inputs as `function`, except in places where
+ // `outVars` is non-null; in those places, we take the type of the VariableReference.
+ //
+ // float _skOutParamHelper0_originalFuncName(float _var0, float _var1, float& outParam) {
+ this->writeType(call.type());
+ this->write(" ");
+ this->write(name);
+ this->write("(");
+ this->writeFunctionRequirementParams(function, separator);
+
+ SkASSERT(outVars.size() == arguments.size());
+ SkASSERT(SkToSizeT(outVars.size()) == function.parameters().size());
+
+ // We need to detect cases where the caller passes the same variable as an out-param more than
+ // once, and avoid reusing the variable name. (In those cases we can actually just ignore the
+ // redundant input parameter entirely, and not give it any name.)
+ SkTHashSet<const Variable*> writtenVars;
+
+ for (int index = 0; index < arguments.size(); ++index) {
+ this->write(separator);
+ separator = ", ";
+
+ const Variable* param = function.parameters()[index];
+ this->writeModifiers(param->modifiers());
+
+ const Type* type = outVars[index] ? &outVars[index]->type() : &arguments[index]->type();
+ this->writeType(*type);
+
+ if (pass_by_reference(param->type(), param->modifiers())) {
+ this->write("&");
+ }
+ if (outVars[index]) {
+ const Variable* var = outVars[index]->variable();
+ if (!writtenVars.contains(var)) {
+ writtenVars.add(var);
+
+ this->write(" ");
+ fIgnoreVariableReferenceModifiers = true;
+ this->writeVariableReference(*outVars[index]);
+ fIgnoreVariableReferenceModifiers = false;
+ }
+ } else {
+ this->write(" _var");
+ this->write(std::to_string(index));
+ }
+ }
+ this->writeLine(") {");
+
+ ++fIndentation;
+ for (int index = 0; index < outVars.size(); ++index) {
+ if (!outVars[index]) {
+ continue;
+ }
+ // float3 _var2[ = outParam.zyx];
+ this->writeType(arguments[index]->type());
+ this->write(" _var");
+ this->write(std::to_string(index));
+
+ const Variable* param = function.parameters()[index];
+ if (param->modifiers().fFlags & Modifiers::kIn_Flag) {
+ this->write(" = ");
+ fIgnoreVariableReferenceModifiers = true;
+ this->writeExpression(*arguments[index], Precedence::kAssignment);
+ fIgnoreVariableReferenceModifiers = false;
+ }
+
+ this->writeLine(";");
+ }
+
+ // [int _skResult = ] myFunction(inputs, outputs, _globals, _var0, _var1, _var2, _var3);
+ bool hasResult = (call.type().name() != "void");
+ if (hasResult) {
+ this->writeType(call.type());
+ this->write(" _skResult = ");
+ }
+
+ this->writeName(function.mangledName());
+ this->write("(");
+ separator = "";
+ this->writeFunctionRequirementArgs(function, separator);
+
+ for (int index = 0; index < arguments.size(); ++index) {
+ this->write(separator);
+ separator = ", ";
+
+ this->write("_var");
+ this->write(std::to_string(index));
+ }
+ this->writeLine(");");
+
+ for (int index = 0; index < outVars.size(); ++index) {
+ if (!outVars[index]) {
+ continue;
+ }
+ // outParam.zyx = _var2;
+ fIgnoreVariableReferenceModifiers = true;
+ this->writeExpression(*arguments[index], Precedence::kAssignment);
+ fIgnoreVariableReferenceModifiers = false;
+ this->write(" = _var");
+ this->write(std::to_string(index));
+ this->writeLine(";");
+ }
+
+ if (hasResult) {
+ this->writeLine("return _skResult;");
+ }
+
+ --fIndentation;
+ this->writeLine("}");
+
+ // Write the function out to `fExtraFunctions`.
+ write_stringstream(tmpStream, fExtraFunctions);
+
+ return name;
+}
+
+std::string MetalCodeGenerator::getBitcastIntrinsic(const Type& outType) {
+ return "as_type<" + outType.displayName() + ">";
+}
+
+void MetalCodeGenerator::writeFunctionCall(const FunctionCall& c) {
+ const FunctionDeclaration& function = c.function();
+
+ // Many intrinsics need to be rewritten in Metal.
+ if (function.isIntrinsic()) {
+ if (this->writeIntrinsicCall(c, function.intrinsicKind())) {
+ return;
+ }
+ }
+
+ // Determine whether or not we need to emulate GLSL's out-param semantics for Metal using a
+ // helper function. (Specifically, out-parameters in GLSL are only written back to the original
+ // variable at the end of the function call; also, swizzles are supported, whereas Metal doesn't
+ // allow a swizzle to be passed to a `floatN&`.)
+ const ExpressionArray& arguments = c.arguments();
+ const std::vector<Variable*>& parameters = function.parameters();
+ SkASSERT(SkToSizeT(arguments.size()) == parameters.size());
+
+ bool foundOutParam = false;
+ SkSTArray<16, VariableReference*> outVars;
+ outVars.push_back_n(arguments.size(), (VariableReference*)nullptr);
+
+ for (int index = 0; index < arguments.size(); ++index) {
+ // If this is an out parameter...
+ if (parameters[index]->modifiers().fFlags & Modifiers::kOut_Flag) {
+ // Find the expression's inner variable being written to.
+ Analysis::AssignmentInfo info;
+ // Assignability was verified at IRGeneration time, so this should always succeed.
+ SkAssertResult(Analysis::IsAssignable(*arguments[index], &info));
+ outVars[index] = info.fAssignedVar;
+ foundOutParam = true;
+ }
+ }
+
+ if (foundOutParam) {
+ // Out parameters need to be written back to at the end of the function. To do this, we
+ // synthesize a helper function which evaluates the out-param expression into a temporary
+ // variable, calls the original function, then writes the temp var back into the out param
+ // using the original out-param expression. (This lets us support things like swizzles and
+ // array indices.)
+ this->write(getOutParamHelper(c, arguments, outVars));
+ } else {
+ this->write(function.mangledName());
+ }
+
+ this->write("(");
+ const char* separator = "";
+ this->writeFunctionRequirementArgs(function, separator);
+ for (int i = 0; i < arguments.size(); ++i) {
+ this->write(separator);
+ separator = ", ";
+
+ if (outVars[i]) {
+ this->writeExpression(*outVars[i], Precedence::kSequence);
+ } else {
+ this->writeExpression(*arguments[i], Precedence::kSequence);
+ }
+ }
+ this->write(")");
+}
+
+static constexpr char kInverse2x2[] = R"(
+template <typename T>
+matrix<T, 2, 2> mat2_inverse(matrix<T, 2, 2> m) {
+return matrix<T, 2, 2>(m[1].y, -m[0].y, -m[1].x, m[0].x) * (1/determinant(m));
+}
+)";
+
+static constexpr char kInverse3x3[] = R"(
+template <typename T>
+matrix<T, 3, 3> mat3_inverse(matrix<T, 3, 3> m) {
+T
+ a00 = m[0].x, a01 = m[0].y, a02 = m[0].z,
+ a10 = m[1].x, a11 = m[1].y, a12 = m[1].z,
+ a20 = m[2].x, a21 = m[2].y, a22 = m[2].z,
+ b01 = a22*a11 - a12*a21,
+ b11 = -a22*a10 + a12*a20,
+ b21 = a21*a10 - a11*a20,
+ det = a00*b01 + a01*b11 + a02*b21;
+return matrix<T, 3, 3>(
+ b01, (-a22*a01 + a02*a21), ( a12*a01 - a02*a11),
+ b11, ( a22*a00 - a02*a20), (-a12*a00 + a02*a10),
+ b21, (-a21*a00 + a01*a20), ( a11*a00 - a01*a10)) * (1/det);
+}
+)";
+
+static constexpr char kInverse4x4[] = R"(
+template <typename T>
+matrix<T, 4, 4> mat4_inverse(matrix<T, 4, 4> m) {
+T
+ a00 = m[0].x, a01 = m[0].y, a02 = m[0].z, a03 = m[0].w,
+ a10 = m[1].x, a11 = m[1].y, a12 = m[1].z, a13 = m[1].w,
+ a20 = m[2].x, a21 = m[2].y, a22 = m[2].z, a23 = m[2].w,
+ a30 = m[3].x, a31 = m[3].y, a32 = m[3].z, a33 = m[3].w,
+ b00 = a00*a11 - a01*a10,
+ b01 = a00*a12 - a02*a10,
+ b02 = a00*a13 - a03*a10,
+ b03 = a01*a12 - a02*a11,
+ b04 = a01*a13 - a03*a11,
+ b05 = a02*a13 - a03*a12,
+ b06 = a20*a31 - a21*a30,
+ b07 = a20*a32 - a22*a30,
+ b08 = a20*a33 - a23*a30,
+ b09 = a21*a32 - a22*a31,
+ b10 = a21*a33 - a23*a31,
+ b11 = a22*a33 - a23*a32,
+ det = b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06;
+return matrix<T, 4, 4>(
+ a11*b11 - a12*b10 + a13*b09,
+ a02*b10 - a01*b11 - a03*b09,
+ a31*b05 - a32*b04 + a33*b03,
+ a22*b04 - a21*b05 - a23*b03,
+ a12*b08 - a10*b11 - a13*b07,
+ a00*b11 - a02*b08 + a03*b07,
+ a32*b02 - a30*b05 - a33*b01,
+ a20*b05 - a22*b02 + a23*b01,
+ a10*b10 - a11*b08 + a13*b06,
+ a01*b08 - a00*b10 - a03*b06,
+ a30*b04 - a31*b02 + a33*b00,
+ a21*b02 - a20*b04 - a23*b00,
+ a11*b07 - a10*b09 - a12*b06,
+ a00*b09 - a01*b07 + a02*b06,
+ a31*b01 - a30*b03 - a32*b00,
+ a20*b03 - a21*b01 + a22*b00) * (1/det);
+}
+)";
+
+std::string MetalCodeGenerator::getInversePolyfill(const ExpressionArray& arguments) {
+ // Only use polyfills for a function taking a single-argument square matrix.
+ SkASSERT(arguments.size() == 1);
+ const Type& type = arguments.front()->type();
+ if (type.isMatrix() && type.rows() == type.columns()) {
+ switch (type.rows()) {
+ case 2:
+ if (!fWrittenInverse2) {
+ fWrittenInverse2 = true;
+ fExtraFunctions.writeText(kInverse2x2);
+ }
+ return "mat2_inverse";
+ case 3:
+ if (!fWrittenInverse3) {
+ fWrittenInverse3 = true;
+ fExtraFunctions.writeText(kInverse3x3);
+ }
+ return "mat3_inverse";
+ case 4:
+ if (!fWrittenInverse4) {
+ fWrittenInverse4 = true;
+ fExtraFunctions.writeText(kInverse4x4);
+ }
+ return "mat4_inverse";
+ }
+ }
+ SkDEBUGFAILF("no polyfill for inverse(%s)", type.description().c_str());
+ return "inverse";
+}
+
+void MetalCodeGenerator::writeMatrixCompMult() {
+ static constexpr char kMatrixCompMult[] = R"(
+template <typename T, int C, int R>
+matrix<T, C, R> matrixCompMult(matrix<T, C, R> a, const matrix<T, C, R> b) {
+ for (int c = 0; c < C; ++c) { a[c] *= b[c]; }
+ return a;
+}
+)";
+ if (!fWrittenMatrixCompMult) {
+ fWrittenMatrixCompMult = true;
+ fExtraFunctions.writeText(kMatrixCompMult);
+ }
+}
+
+void MetalCodeGenerator::writeOuterProduct() {
+ static constexpr char kOuterProduct[] = R"(
+template <typename T, int C, int R>
+matrix<T, C, R> outerProduct(const vec<T, R> a, const vec<T, C> b) {
+ matrix<T, C, R> m;
+ for (int c = 0; c < C; ++c) { m[c] = a * b[c]; }
+ return m;
+}
+)";
+ if (!fWrittenOuterProduct) {
+ fWrittenOuterProduct = true;
+ fExtraFunctions.writeText(kOuterProduct);
+ }
+}
+
+std::string MetalCodeGenerator::getTempVariable(const Type& type) {
+ std::string tempVar = "_skTemp" + std::to_string(fVarCount++);
+ this->fFunctionHeader += " " + this->typeName(type) + " " + tempVar + ";\n";
+ return tempVar;
+}
+
+void MetalCodeGenerator::writeSimpleIntrinsic(const FunctionCall& c) {
+ // Write out an intrinsic function call exactly as-is. No muss no fuss.
+ this->write(c.function().name());
+ this->writeArgumentList(c.arguments());
+}
+
+void MetalCodeGenerator::writeArgumentList(const ExpressionArray& arguments) {
+ this->write("(");
+ const char* separator = "";
+ for (const std::unique_ptr<Expression>& arg : arguments) {
+ this->write(separator);
+ separator = ", ";
+ this->writeExpression(*arg, Precedence::kSequence);
+ }
+ this->write(")");
+}
+
+bool MetalCodeGenerator::writeIntrinsicCall(const FunctionCall& c, IntrinsicKind kind) {
+ const ExpressionArray& arguments = c.arguments();
+ switch (kind) {
+ case k_read_IntrinsicKind: {
+ this->writeExpression(*arguments[0], Precedence::kTopLevel);
+ this->write(".read(");
+ this->writeExpression(*arguments[1], Precedence::kSequence);
+ this->write(")");
+ return true;
+ }
+ case k_write_IntrinsicKind: {
+ this->writeExpression(*arguments[0], Precedence::kTopLevel);
+ this->write(".write(");
+ this->writeExpression(*arguments[2], Precedence::kSequence);
+ this->write(", ");
+ this->writeExpression(*arguments[1], Precedence::kSequence);
+ this->write(")");
+ return true;
+ }
+ case k_width_IntrinsicKind: {
+ this->writeExpression(*arguments[0], Precedence::kTopLevel);
+ this->write(".get_width()");
+ return true;
+ }
+ case k_height_IntrinsicKind: {
+ this->writeExpression(*arguments[0], Precedence::kTopLevel);
+ this->write(".get_height()");
+ return true;
+ }
+ case k_mod_IntrinsicKind: {
+ // fmod(x, y) in metal calculates x - y * trunc(x / y) instead of x - y * floor(x / y)
+ std::string tmpX = this->getTempVariable(arguments[0]->type());
+ std::string tmpY = this->getTempVariable(arguments[1]->type());
+ this->write("(" + tmpX + " = ");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write(", " + tmpY + " = ");
+ this->writeExpression(*arguments[1], Precedence::kSequence);
+ this->write(", " + tmpX + " - " + tmpY + " * floor(" + tmpX + " / " + tmpY + "))");
+ return true;
+ }
+ // GLSL declares scalar versions of most geometric intrinsics, but these don't exist in MSL
+ case k_distance_IntrinsicKind: {
+ if (arguments[0]->type().columns() == 1) {
+ this->write("abs(");
+ this->writeExpression(*arguments[0], Precedence::kAdditive);
+ this->write(" - ");
+ this->writeExpression(*arguments[1], Precedence::kAdditive);
+ this->write(")");
+ } else {
+ this->writeSimpleIntrinsic(c);
+ }
+ return true;
+ }
+ case k_dot_IntrinsicKind: {
+ if (arguments[0]->type().columns() == 1) {
+ this->write("(");
+ this->writeExpression(*arguments[0], Precedence::kMultiplicative);
+ this->write(" * ");
+ this->writeExpression(*arguments[1], Precedence::kMultiplicative);
+ this->write(")");
+ } else {
+ this->writeSimpleIntrinsic(c);
+ }
+ return true;
+ }
+ case k_faceforward_IntrinsicKind: {
+ if (arguments[0]->type().columns() == 1) {
+ // ((((Nref) * (I) < 0) ? 1 : -1) * (N))
+ this->write("((((");
+ this->writeExpression(*arguments[2], Precedence::kSequence);
+ this->write(") * (");
+ this->writeExpression(*arguments[1], Precedence::kSequence);
+ this->write(") < 0) ? 1 : -1) * (");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write("))");
+ } else {
+ this->writeSimpleIntrinsic(c);
+ }
+ return true;
+ }
+ case k_length_IntrinsicKind: {
+ this->write(arguments[0]->type().columns() == 1 ? "abs(" : "length(");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write(")");
+ return true;
+ }
+ case k_normalize_IntrinsicKind: {
+ this->write(arguments[0]->type().columns() == 1 ? "sign(" : "normalize(");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write(")");
+ return true;
+ }
+ case k_packUnorm2x16_IntrinsicKind: {
+ this->write("pack_float_to_unorm2x16(");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write(")");
+ return true;
+ }
+ case k_unpackUnorm2x16_IntrinsicKind: {
+ this->write("unpack_unorm2x16_to_float(");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write(")");
+ return true;
+ }
+ case k_packSnorm2x16_IntrinsicKind: {
+ this->write("pack_float_to_snorm2x16(");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write(")");
+ return true;
+ }
+ case k_unpackSnorm2x16_IntrinsicKind: {
+ this->write("unpack_snorm2x16_to_float(");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write(")");
+ return true;
+ }
+ case k_packUnorm4x8_IntrinsicKind: {
+ this->write("pack_float_to_unorm4x8(");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write(")");
+ return true;
+ }
+ case k_unpackUnorm4x8_IntrinsicKind: {
+ this->write("unpack_unorm4x8_to_float(");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write(")");
+ return true;
+ }
+ case k_packSnorm4x8_IntrinsicKind: {
+ this->write("pack_float_to_snorm4x8(");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write(")");
+ return true;
+ }
+ case k_unpackSnorm4x8_IntrinsicKind: {
+ this->write("unpack_snorm4x8_to_float(");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write(")");
+ return true;
+ }
+ case k_packHalf2x16_IntrinsicKind: {
+ this->write("as_type<uint>(half2(");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write("))");
+ return true;
+ }
+ case k_unpackHalf2x16_IntrinsicKind: {
+ this->write("float2(as_type<half2>(");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write("))");
+ return true;
+ }
+ case k_floatBitsToInt_IntrinsicKind:
+ case k_floatBitsToUint_IntrinsicKind:
+ case k_intBitsToFloat_IntrinsicKind:
+ case k_uintBitsToFloat_IntrinsicKind: {
+ this->write(this->getBitcastIntrinsic(c.type()));
+ this->write("(");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write(")");
+ return true;
+ }
+ case k_degrees_IntrinsicKind: {
+ this->write("((");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write(") * 57.2957795)");
+ return true;
+ }
+ case k_radians_IntrinsicKind: {
+ this->write("((");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write(") * 0.0174532925)");
+ return true;
+ }
+ case k_dFdx_IntrinsicKind: {
+ this->write("dfdx");
+ this->writeArgumentList(c.arguments());
+ return true;
+ }
+ case k_dFdy_IntrinsicKind: {
+ if (!fRTFlipName.empty()) {
+ this->write("(" + fRTFlipName + ".y * dfdy");
+ } else {
+ this->write("(dfdy");
+ }
+ this->writeArgumentList(c.arguments());
+ this->write(")");
+ return true;
+ }
+ case k_inverse_IntrinsicKind: {
+ this->write(this->getInversePolyfill(arguments));
+ this->writeArgumentList(c.arguments());
+ return true;
+ }
+ case k_inversesqrt_IntrinsicKind: {
+ this->write("rsqrt");
+ this->writeArgumentList(c.arguments());
+ return true;
+ }
+ case k_atan_IntrinsicKind: {
+ this->write(c.arguments().size() == 2 ? "atan2" : "atan");
+ this->writeArgumentList(c.arguments());
+ return true;
+ }
+ case k_reflect_IntrinsicKind: {
+ if (arguments[0]->type().columns() == 1) {
+ // We need to synthesize `I - 2 * N * I * N`.
+ std::string tmpI = this->getTempVariable(arguments[0]->type());
+ std::string tmpN = this->getTempVariable(arguments[1]->type());
+
+ // (_skTempI = ...
+ this->write("(" + tmpI + " = ");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+
+ // , _skTempN = ...
+ this->write(", " + tmpN + " = ");
+ this->writeExpression(*arguments[1], Precedence::kSequence);
+
+ // , _skTempI - 2 * _skTempN * _skTempI * _skTempN)
+ this->write(", " + tmpI + " - 2 * " + tmpN + " * " + tmpI + " * " + tmpN + ")");
+ } else {
+ this->writeSimpleIntrinsic(c);
+ }
+ return true;
+ }
+ case k_refract_IntrinsicKind: {
+ if (arguments[0]->type().columns() == 1) {
+ // Metal does implement refract for vectors; rather than reimplementing refract from
+ // scratch, we can replace the call with `refract(float2(I,0), float2(N,0), eta).x`.
+ this->write("(refract(float2(");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write(", 0), float2(");
+ this->writeExpression(*arguments[1], Precedence::kSequence);
+ this->write(", 0), ");
+ this->writeExpression(*arguments[2], Precedence::kSequence);
+ this->write(").x)");
+ } else {
+ this->writeSimpleIntrinsic(c);
+ }
+ return true;
+ }
+ case k_roundEven_IntrinsicKind: {
+ this->write("rint");
+ this->writeArgumentList(c.arguments());
+ return true;
+ }
+ case k_bitCount_IntrinsicKind: {
+ this->write("popcount(");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write(")");
+ return true;
+ }
+ case k_findLSB_IntrinsicKind: {
+ // Create a temp variable to store the expression, to avoid double-evaluating it.
+ std::string skTemp = this->getTempVariable(arguments[0]->type());
+ std::string exprType = this->typeName(arguments[0]->type());
+
+ // ctz returns numbits(type) on zero inputs; GLSL documents it as generating -1 instead.
+ // Use select to detect zero inputs and force a -1 result.
+
+ // (_skTemp1 = (.....), select(ctz(_skTemp1), int4(-1), _skTemp1 == int4(0)))
+ this->write("(");
+ this->write(skTemp);
+ this->write(" = (");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write("), select(ctz(");
+ this->write(skTemp);
+ this->write("), ");
+ this->write(exprType);
+ this->write("(-1), ");
+ this->write(skTemp);
+ this->write(" == ");
+ this->write(exprType);
+ this->write("(0)))");
+ return true;
+ }
+ case k_findMSB_IntrinsicKind: {
+ // Create a temp variable to store the expression, to avoid double-evaluating it.
+ std::string skTemp1 = this->getTempVariable(arguments[0]->type());
+ std::string exprType = this->typeName(arguments[0]->type());
+
+ // GLSL findMSB is actually quite different from Metal's clz:
+ // - For signed negative numbers, it returns the first zero bit, not the first one bit!
+ // - For an empty input (0/~0 depending on sign), findMSB gives -1; clz is numbits(type)
+
+ // (_skTemp1 = (.....),
+ this->write("(");
+ this->write(skTemp1);
+ this->write(" = (");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write("), ");
+
+ // Signed input types might be negative; we need another helper variable to negate the
+ // input (since we can only find one bits, not zero bits).
+ std::string skTemp2;
+ if (arguments[0]->type().isSigned()) {
+ // ... _skTemp2 = (select(_skTemp1, ~_skTemp1, _skTemp1 < 0)),
+ skTemp2 = this->getTempVariable(arguments[0]->type());
+ this->write(skTemp2);
+ this->write(" = (select(");
+ this->write(skTemp1);
+ this->write(", ~");
+ this->write(skTemp1);
+ this->write(", ");
+ this->write(skTemp1);
+ this->write(" < 0)), ");
+ } else {
+ skTemp2 = skTemp1;
+ }
+
+ // ... select(int4(clz(_skTemp2)), int4(-1), _skTemp2 == int4(0)))
+ this->write("select(");
+ this->write(this->typeName(c.type()));
+ this->write("(clz(");
+ this->write(skTemp2);
+ this->write(")), ");
+ this->write(this->typeName(c.type()));
+ this->write("(-1), ");
+ this->write(skTemp2);
+ this->write(" == ");
+ this->write(exprType);
+ this->write("(0)))");
+ return true;
+ }
+ case k_sign_IntrinsicKind: {
+ if (arguments[0]->type().componentType().isInteger()) {
+ // Create a temp variable to store the expression, to avoid double-evaluating it.
+ std::string skTemp = this->getTempVariable(arguments[0]->type());
+ std::string exprType = this->typeName(arguments[0]->type());
+
+ // (_skTemp = (.....),
+ this->write("(");
+ this->write(skTemp);
+ this->write(" = (");
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+ this->write("), ");
+
+ // ... select(select(int4(0), int4(-1), _skTemp < 0), int4(1), _skTemp > 0))
+ this->write("select(select(");
+ this->write(exprType);
+ this->write("(0), ");
+ this->write(exprType);
+ this->write("(-1), ");
+ this->write(skTemp);
+ this->write(" < 0), ");
+ this->write(exprType);
+ this->write("(1), ");
+ this->write(skTemp);
+ this->write(" > 0))");
+ } else {
+ this->writeSimpleIntrinsic(c);
+ }
+ return true;
+ }
+ case k_matrixCompMult_IntrinsicKind: {
+ this->writeMatrixCompMult();
+ this->writeSimpleIntrinsic(c);
+ return true;
+ }
+ case k_outerProduct_IntrinsicKind: {
+ this->writeOuterProduct();
+ this->writeSimpleIntrinsic(c);
+ return true;
+ }
+ case k_mix_IntrinsicKind: {
+ SkASSERT(c.arguments().size() == 3);
+ if (arguments[2]->type().componentType().isBoolean()) {
+ // The Boolean forms of GLSL mix() use the select() intrinsic in Metal.
+ this->write("select");
+ this->writeArgumentList(c.arguments());
+ return true;
+ }
+ // The basic form of mix() is supported by Metal as-is.
+ this->writeSimpleIntrinsic(c);
+ return true;
+ }
+ case k_equal_IntrinsicKind:
+ case k_greaterThan_IntrinsicKind:
+ case k_greaterThanEqual_IntrinsicKind:
+ case k_lessThan_IntrinsicKind:
+ case k_lessThanEqual_IntrinsicKind:
+ case k_notEqual_IntrinsicKind: {
+ this->write("(");
+ this->writeExpression(*c.arguments()[0], Precedence::kRelational);
+ switch (kind) {
+ case k_equal_IntrinsicKind:
+ this->write(" == ");
+ break;
+ case k_notEqual_IntrinsicKind:
+ this->write(" != ");
+ break;
+ case k_lessThan_IntrinsicKind:
+ this->write(" < ");
+ break;
+ case k_lessThanEqual_IntrinsicKind:
+ this->write(" <= ");
+ break;
+ case k_greaterThan_IntrinsicKind:
+ this->write(" > ");
+ break;
+ case k_greaterThanEqual_IntrinsicKind:
+ this->write(" >= ");
+ break;
+ default:
+ SK_ABORT("unsupported comparison intrinsic kind");
+ }
+ this->writeExpression(*c.arguments()[1], Precedence::kRelational);
+ this->write(")");
+ return true;
+ }
+ case k_storageBarrier_IntrinsicKind:
+ this->write("threadgroup_barrier(mem_flags::mem_device)");
+ return true;
+ case k_workgroupBarrier_IntrinsicKind:
+ this->write("threadgroup_barrier(mem_flags::mem_threadgroup)");
+ return true;
+ case k_atomicAdd_IntrinsicKind:
+ this->write("atomic_fetch_add_explicit(&");
+ this->writeExpression(*c.arguments()[0], Precedence::kSequence);
+ this->write(", ");
+ this->writeExpression(*c.arguments()[1], Precedence::kSequence);
+ this->write(", memory_order_relaxed)");
+ return true;
+ case k_atomicLoad_IntrinsicKind:
+ this->write("atomic_load_explicit(&");
+ this->writeExpression(*c.arguments()[0], Precedence::kSequence);
+ this->write(", memory_order_relaxed)");
+ return true;
+ case k_atomicStore_IntrinsicKind:
+ this->write("atomic_store_explicit(&");
+ this->writeExpression(*c.arguments()[0], Precedence::kSequence);
+ this->write(", ");
+ this->writeExpression(*c.arguments()[1], Precedence::kSequence);
+ this->write(", memory_order_relaxed)");
+ return true;
+ default:
+ return false;
+ }
+}
+
+// Assembles a matrix of type floatRxC by resizing another matrix named `x0`.
+// Cells that don't exist in the source matrix will be populated with identity-matrix values.
+void MetalCodeGenerator::assembleMatrixFromMatrix(const Type& sourceMatrix, int rows, int columns) {
+ SkASSERT(rows <= 4);
+ SkASSERT(columns <= 4);
+
+ std::string matrixType = this->typeName(sourceMatrix.componentType());
+
+ const char* separator = "";
+ for (int c = 0; c < columns; ++c) {
+ fExtraFunctions.printf("%s%s%d(", separator, matrixType.c_str(), rows);
+ separator = "), ";
+
+ // Determine how many values to take from the source matrix for this row.
+ int swizzleLength = 0;
+ if (c < sourceMatrix.columns()) {
+ swizzleLength = std::min<>(rows, sourceMatrix.rows());
+ }
+
+ // Emit all the values from the source matrix row.
+ bool firstItem;
+ switch (swizzleLength) {
+ case 0: firstItem = true; break;
+ case 1: firstItem = false; fExtraFunctions.printf("x0[%d].x", c); break;
+ case 2: firstItem = false; fExtraFunctions.printf("x0[%d].xy", c); break;
+ case 3: firstItem = false; fExtraFunctions.printf("x0[%d].xyz", c); break;
+ case 4: firstItem = false; fExtraFunctions.printf("x0[%d].xyzw", c); break;
+ default: SkUNREACHABLE;
+ }
+
+ // Emit the placeholder identity-matrix cells.
+ for (int r = swizzleLength; r < rows; ++r) {
+ fExtraFunctions.printf("%s%s", firstItem ? "" : ", ", (r == c) ? "1.0" : "0.0");
+ firstItem = false;
+ }
+ }
+
+ fExtraFunctions.writeText(")");
+}
+
+// Assembles a matrix of type floatCxR by concatenating an arbitrary mix of values, named `x0`,
+// `x1`, etc. An error is written if the expression list don't contain exactly C*R scalars.
+void MetalCodeGenerator::assembleMatrixFromExpressions(const AnyConstructor& ctor,
+ int columns, int rows) {
+ SkASSERT(rows <= 4);
+ SkASSERT(columns <= 4);
+
+ std::string matrixType = this->typeName(ctor.type().componentType());
+ size_t argIndex = 0;
+ int argPosition = 0;
+ auto args = ctor.argumentSpan();
+
+ static constexpr char kSwizzle[] = "xyzw";
+ const char* separator = "";
+ for (int c = 0; c < columns; ++c) {
+ fExtraFunctions.printf("%s%s%d(", separator, matrixType.c_str(), rows);
+ separator = "), ";
+
+ const char* columnSeparator = "";
+ for (int r = 0; r < rows;) {
+ fExtraFunctions.writeText(columnSeparator);
+ columnSeparator = ", ";
+
+ if (argIndex < args.size()) {
+ const Type& argType = args[argIndex]->type();
+ switch (argType.typeKind()) {
+ case Type::TypeKind::kScalar: {
+ fExtraFunctions.printf("x%zu", argIndex);
+ ++r;
+ ++argPosition;
+ break;
+ }
+ case Type::TypeKind::kVector: {
+ fExtraFunctions.printf("x%zu.", argIndex);
+ do {
+ fExtraFunctions.write8(kSwizzle[argPosition]);
+ ++r;
+ ++argPosition;
+ } while (r < rows && argPosition < argType.columns());
+ break;
+ }
+ case Type::TypeKind::kMatrix: {
+ fExtraFunctions.printf("x%zu[%d].", argIndex, argPosition / argType.rows());
+ do {
+ fExtraFunctions.write8(kSwizzle[argPosition]);
+ ++r;
+ ++argPosition;
+ } while (r < rows && (argPosition % argType.rows()) != 0);
+ break;
+ }
+ default: {
+ SkDEBUGFAIL("incorrect type of argument for matrix constructor");
+ fExtraFunctions.writeText("<error>");
+ break;
+ }
+ }
+
+ if (argPosition >= argType.columns() * argType.rows()) {
+ ++argIndex;
+ argPosition = 0;
+ }
+ } else {
+ SkDEBUGFAIL("not enough arguments for matrix constructor");
+ fExtraFunctions.writeText("<error>");
+ }
+ }
+ }
+
+ if (argPosition != 0 || argIndex != args.size()) {
+ SkDEBUGFAIL("incorrect number of arguments for matrix constructor");
+ fExtraFunctions.writeText(", <error>");
+ }
+
+ fExtraFunctions.writeText(")");
+}
+
+// Generates a constructor for 'matrix' which reorganizes the input arguments into the proper shape.
+// Keeps track of previously generated constructors so that we won't generate more than one
+// constructor for any given permutation of input argument types. Returns the name of the
+// generated constructor method.
+std::string MetalCodeGenerator::getMatrixConstructHelper(const AnyConstructor& c) {
+ const Type& type = c.type();
+ int columns = type.columns();
+ int rows = type.rows();
+ auto args = c.argumentSpan();
+ std::string typeName = this->typeName(type);
+
+ // Create the helper-method name and use it as our lookup key.
+ std::string name = String::printf("%s_from", typeName.c_str());
+ for (const std::unique_ptr<Expression>& expr : args) {
+ String::appendf(&name, "_%s", this->typeName(expr->type()).c_str());
+ }
+
+ // If a helper-method has not been synthesized yet, create it now.
+ if (!fHelpers.contains(name)) {
+ fHelpers.add(name);
+
+ // Unlike GLSL, Metal requires that matrices are initialized with exactly R vectors of C
+ // components apiece. (In Metal 2.0, you can also supply R*C scalars, but you still cannot
+ // supply a mixture of scalars and vectors.)
+ fExtraFunctions.printf("%s %s(", typeName.c_str(), name.c_str());
+
+ size_t argIndex = 0;
+ const char* argSeparator = "";
+ for (const std::unique_ptr<Expression>& expr : args) {
+ fExtraFunctions.printf("%s%s x%zu", argSeparator,
+ this->typeName(expr->type()).c_str(), argIndex++);
+ argSeparator = ", ";
+ }
+
+ fExtraFunctions.printf(") {\n return %s(", typeName.c_str());
+
+ if (args.size() == 1 && args.front()->type().isMatrix()) {
+ this->assembleMatrixFromMatrix(args.front()->type(), rows, columns);
+ } else {
+ this->assembleMatrixFromExpressions(c, columns, rows);
+ }
+
+ fExtraFunctions.writeText(");\n}\n");
+ }
+ return name;
+}
+
+bool MetalCodeGenerator::matrixConstructHelperIsNeeded(const ConstructorCompound& c) {
+ SkASSERT(c.type().isMatrix());
+
+ // GLSL is fairly free-form about inputs to its matrix constructors, but Metal is not; it
+ // expects exactly R vectors of C components apiece. (Metal 2.0 also allows a list of R*C
+ // scalars.) Some cases are simple to translate and so we handle those inline--e.g. a list of
+ // scalars can be constructed trivially. In more complex cases, we generate a helper function
+ // that converts our inputs into a properly-shaped matrix.
+ // A matrix construct helper method is always used if any input argument is a matrix.
+ // Helper methods are also necessary when any argument would span multiple rows. For instance:
+ //
+ // float2 x = (1, 2);
+ // float3x2(x, 3, 4, 5, 6) = | 1 3 5 | = no helper needed; conversion can be done inline
+ // | 2 4 6 |
+ //
+ // float2 x = (2, 3);
+ // float3x2(1, x, 4, 5, 6) = | 1 3 5 | = x spans multiple rows; a helper method will be used
+ // | 2 4 6 |
+ //
+ // float4 x = (1, 2, 3, 4);
+ // float2x2(x) = | 1 3 | = x spans multiple rows; a helper method will be used
+ // | 2 4 |
+ //
+
+ int position = 0;
+ for (const std::unique_ptr<Expression>& expr : c.arguments()) {
+ // If an input argument is a matrix, we need a helper function.
+ if (expr->type().isMatrix()) {
+ return true;
+ }
+ position += expr->type().columns();
+ if (position > c.type().rows()) {
+ // An input argument would span multiple rows; a helper function is required.
+ return true;
+ }
+ if (position == c.type().rows()) {
+ // We've advanced to the end of a row. Wrap to the start of the next row.
+ position = 0;
+ }
+ }
+
+ return false;
+}
+
+void MetalCodeGenerator::writeConstructorMatrixResize(const ConstructorMatrixResize& c,
+ Precedence parentPrecedence) {
+ // Matrix-resize via casting doesn't natively exist in Metal at all, so we always need to use a
+ // matrix-construct helper here.
+ this->write(this->getMatrixConstructHelper(c));
+ this->write("(");
+ this->writeExpression(*c.argument(), Precedence::kSequence);
+ this->write(")");
+}
+
+void MetalCodeGenerator::writeConstructorCompound(const ConstructorCompound& c,
+ Precedence parentPrecedence) {
+ if (c.type().isVector()) {
+ this->writeConstructorCompoundVector(c, parentPrecedence);
+ } else if (c.type().isMatrix()) {
+ this->writeConstructorCompoundMatrix(c, parentPrecedence);
+ } else {
+ fContext.fErrors->error(c.fPosition, "unsupported compound constructor");
+ }
+}
+
+void MetalCodeGenerator::writeConstructorArrayCast(const ConstructorArrayCast& c,
+ Precedence parentPrecedence) {
+ const Type& inType = c.argument()->type().componentType();
+ const Type& outType = c.type().componentType();
+ std::string inTypeName = this->typeName(inType);
+ std::string outTypeName = this->typeName(outType);
+
+ std::string name = "array_of_" + outTypeName + "_from_" + inTypeName;
+ if (!fHelpers.contains(name)) {
+ fHelpers.add(name);
+ fExtraFunctions.printf(R"(
+template <size_t N>
+array<%s, N> %s(thread const array<%s, N>& x) {
+ array<%s, N> result;
+ for (int i = 0; i < N; ++i) {
+ result[i] = %s(x[i]);
+ }
+ return result;
+}
+)",
+ outTypeName.c_str(), name.c_str(), inTypeName.c_str(),
+ outTypeName.c_str(),
+ outTypeName.c_str());
+ }
+
+ this->write(name);
+ this->write("(");
+ this->writeExpression(*c.argument(), Precedence::kSequence);
+ this->write(")");
+}
+
+std::string MetalCodeGenerator::getVectorFromMat2x2ConstructorHelper(const Type& matrixType) {
+ SkASSERT(matrixType.isMatrix());
+ SkASSERT(matrixType.rows() == 2);
+ SkASSERT(matrixType.columns() == 2);
+
+ std::string baseType = this->typeName(matrixType.componentType());
+ std::string name = String::printf("%s4_from_%s2x2", baseType.c_str(), baseType.c_str());
+ if (!fHelpers.contains(name)) {
+ fHelpers.add(name);
+
+ fExtraFunctions.printf(R"(
+%s4 %s(%s2x2 x) {
+ return %s4(x[0].xy, x[1].xy);
+}
+)", baseType.c_str(), name.c_str(), baseType.c_str(), baseType.c_str());
+ }
+
+ return name;
+}
+
+void MetalCodeGenerator::writeConstructorCompoundVector(const ConstructorCompound& c,
+ Precedence parentPrecedence) {
+ SkASSERT(c.type().isVector());
+
+ // Metal supports constructing vectors from a mix of scalars and vectors, but not matrices.
+ // GLSL supports vec4(mat2x2), so we detect that case here and emit a helper function.
+ if (c.type().columns() == 4 && c.argumentSpan().size() == 1) {
+ const Expression& expr = *c.argumentSpan().front();
+ if (expr.type().isMatrix()) {
+ this->write(this->getVectorFromMat2x2ConstructorHelper(expr.type()));
+ this->write("(");
+ this->writeExpression(expr, Precedence::kSequence);
+ this->write(")");
+ return;
+ }
+ }
+
+ this->writeAnyConstructor(c, "(", ")", parentPrecedence);
+}
+
+void MetalCodeGenerator::writeConstructorCompoundMatrix(const ConstructorCompound& c,
+ Precedence parentPrecedence) {
+ SkASSERT(c.type().isMatrix());
+
+ // Emit and invoke a matrix-constructor helper method if one is necessary.
+ if (this->matrixConstructHelperIsNeeded(c)) {
+ this->write(this->getMatrixConstructHelper(c));
+ this->write("(");
+ const char* separator = "";
+ for (const std::unique_ptr<Expression>& expr : c.arguments()) {
+ this->write(separator);
+ separator = ", ";
+ this->writeExpression(*expr, Precedence::kSequence);
+ }
+ this->write(")");
+ return;
+ }
+
+ // Metal doesn't allow creating matrices by passing in scalars and vectors in a jumble; it
+ // requires your scalars to be grouped up into columns. Because `matrixConstructHelperIsNeeded`
+ // returned false, we know that none of our scalars/vectors "wrap" across across a column, so we
+ // can group our inputs up and synthesize a constructor for each column.
+ const Type& matrixType = c.type();
+ const Type& columnType = matrixType.componentType().toCompound(
+ fContext, /*columns=*/matrixType.rows(), /*rows=*/1);
+
+ this->writeType(matrixType);
+ this->write("(");
+ const char* separator = "";
+ int scalarCount = 0;
+ for (const std::unique_ptr<Expression>& arg : c.arguments()) {
+ this->write(separator);
+ separator = ", ";
+ if (arg->type().columns() < matrixType.rows()) {
+ // Write a `floatN(` constructor to group scalars and smaller vectors together.
+ if (!scalarCount) {
+ this->writeType(columnType);
+ this->write("(");
+ }
+ scalarCount += arg->type().columns();
+ }
+ this->writeExpression(*arg, Precedence::kSequence);
+ if (scalarCount && scalarCount == matrixType.rows()) {
+ // Close our `floatN(...` constructor block from above.
+ this->write(")");
+ scalarCount = 0;
+ }
+ }
+ this->write(")");
+}
+
+void MetalCodeGenerator::writeAnyConstructor(const AnyConstructor& c,
+ const char* leftBracket,
+ const char* rightBracket,
+ Precedence parentPrecedence) {
+ this->writeType(c.type());
+ this->write(leftBracket);
+ const char* separator = "";
+ for (const std::unique_ptr<Expression>& arg : c.argumentSpan()) {
+ this->write(separator);
+ separator = ", ";
+ this->writeExpression(*arg, Precedence::kSequence);
+ }
+ this->write(rightBracket);
+}
+
+void MetalCodeGenerator::writeCastConstructor(const AnyConstructor& c,
+ const char* leftBracket,
+ const char* rightBracket,
+ Precedence parentPrecedence) {
+ return this->writeAnyConstructor(c, leftBracket, rightBracket, parentPrecedence);
+}
+
+void MetalCodeGenerator::writeFragCoord() {
+ if (!fRTFlipName.empty()) {
+ this->write("float4(_fragCoord.x, ");
+ this->write(fRTFlipName.c_str());
+ this->write(".x + ");
+ this->write(fRTFlipName.c_str());
+ this->write(".y * _fragCoord.y, 0.0, _fragCoord.w)");
+ } else {
+ this->write("float4(_fragCoord.x, _fragCoord.y, 0.0, _fragCoord.w)");
+ }
+}
+
+static bool is_compute_builtin(const Variable& var) {
+ switch (var.modifiers().fLayout.fBuiltin) {
+ case SK_NUMWORKGROUPS_BUILTIN:
+ case SK_WORKGROUPID_BUILTIN:
+ case SK_LOCALINVOCATIONID_BUILTIN:
+ case SK_GLOBALINVOCATIONID_BUILTIN:
+ case SK_LOCALINVOCATIONINDEX_BUILTIN:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+// true if the var is part of the Inputs struct
+static bool is_input(const Variable& var) {
+ SkASSERT(var.storage() == VariableStorage::kGlobal);
+ return var.modifiers().fFlags & Modifiers::kIn_Flag &&
+ (var.modifiers().fLayout.fBuiltin == -1 || is_compute_builtin(var)) &&
+ var.type().typeKind() != Type::TypeKind::kTexture;
+}
+
+// true if the var is part of the Outputs struct
+static bool is_output(const Variable& var) {
+ SkASSERT(var.storage() == VariableStorage::kGlobal);
+ // inout vars get written into the Inputs struct, so we exclude them from Outputs
+ return (var.modifiers().fFlags & Modifiers::kOut_Flag) &&
+ !(var.modifiers().fFlags & Modifiers::kIn_Flag) &&
+ var.modifiers().fLayout.fBuiltin == -1 &&
+ var.type().typeKind() != Type::TypeKind::kTexture;
+}
+
+// true if the var is part of the Uniforms struct
+static bool is_uniforms(const Variable& var) {
+ SkASSERT(var.storage() == VariableStorage::kGlobal);
+ return var.modifiers().fFlags & Modifiers::kUniform_Flag &&
+ var.type().typeKind() != Type::TypeKind::kSampler;
+}
+
+// true if the var is part of the Threadgroups struct
+static bool is_threadgroup(const Variable& var) {
+ SkASSERT(var.storage() == VariableStorage::kGlobal);
+ return var.modifiers().fFlags & Modifiers::kWorkgroup_Flag;
+}
+
+// true if the var is part of the Globals struct
+static bool is_in_globals(const Variable& var) {
+ SkASSERT(var.storage() == VariableStorage::kGlobal);
+ return !(var.modifiers().fFlags & Modifiers::kConst_Flag);
+}
+
+void MetalCodeGenerator::writeVariableReference(const VariableReference& ref) {
+ // When assembling out-param helper functions, we copy variables into local clones with matching
+ // names. We never want to prepend "_in." or "_globals." when writing these variables since
+ // we're actually targeting the clones.
+ if (fIgnoreVariableReferenceModifiers) {
+ this->writeName(ref.variable()->mangledName());
+ return;
+ }
+
+ switch (ref.variable()->modifiers().fLayout.fBuiltin) {
+ case SK_FRAGCOLOR_BUILTIN:
+ this->write("_out.sk_FragColor");
+ break;
+ case SK_FRAGCOORD_BUILTIN:
+ this->writeFragCoord();
+ break;
+ case SK_VERTEXID_BUILTIN:
+ this->write("sk_VertexID");
+ break;
+ case SK_INSTANCEID_BUILTIN:
+ this->write("sk_InstanceID");
+ break;
+ case SK_CLOCKWISE_BUILTIN:
+ // We'd set the front facing winding in the MTLRenderCommandEncoder to be counter
+ // clockwise to match Skia convention.
+ if (!fRTFlipName.empty()) {
+ this->write("(" + fRTFlipName + ".y < 0 ? _frontFacing : !_frontFacing)");
+ } else {
+ this->write("_frontFacing");
+ }
+ break;
+ default:
+ const Variable& var = *ref.variable();
+ if (var.storage() == Variable::Storage::kGlobal) {
+ if (is_input(var)) {
+ this->write("_in.");
+ } else if (is_output(var)) {
+ this->write("_out.");
+ } else if (is_uniforms(var)) {
+ this->write("_uniforms.");
+ } else if (is_threadgroup(var)) {
+ this->write("_threadgroups.");
+ } else if (is_in_globals(var)) {
+ this->write("_globals.");
+ }
+ }
+ this->writeName(var.mangledName());
+ }
+}
+
+void MetalCodeGenerator::writeIndexExpression(const IndexExpression& expr) {
+ // Metal does not seem to handle assignment into `vec.zyx[i]` properly--it compiles, but the
+ // results are wrong. We rewrite the expression as `vec[uint3(2,1,0)[i]]` instead. (Filed with
+ // Apple as FB12055941.)
+ if (expr.base()->is<Swizzle>()) {
+ const Swizzle& swizzle = expr.base()->as<Swizzle>();
+ if (swizzle.components().size() > 1) {
+ this->writeExpression(*swizzle.base(), Precedence::kPostfix);
+ this->write("[uint" + std::to_string(swizzle.components().size()) + "(");
+ auto separator = SkSL::String::Separator();
+ for (int8_t component : swizzle.components()) {
+ this->write(separator());
+ this->write(std::to_string(component));
+ }
+ this->write(")[");
+ this->writeExpression(*expr.index(), Precedence::kTopLevel);
+ this->write("]]");
+ return;
+ }
+ }
+
+ this->writeExpression(*expr.base(), Precedence::kPostfix);
+ this->write("[");
+ this->writeExpression(*expr.index(), Precedence::kTopLevel);
+ this->write("]");
+}
+
+void MetalCodeGenerator::writeFieldAccess(const FieldAccess& f) {
+ const Type::Field* field = &f.base()->type().fields()[f.fieldIndex()];
+ if (FieldAccess::OwnerKind::kDefault == f.ownerKind()) {
+ this->writeExpression(*f.base(), Precedence::kPostfix);
+ this->write(".");
+ }
+ switch (field->fModifiers.fLayout.fBuiltin) {
+ case SK_POSITION_BUILTIN:
+ this->write("_out.sk_Position");
+ break;
+ case SK_POINTSIZE_BUILTIN:
+ this->write("_out.sk_PointSize");
+ break;
+ default:
+ if (FieldAccess::OwnerKind::kAnonymousInterfaceBlock == f.ownerKind()) {
+ this->write("_globals.");
+ this->write(fInterfaceBlockNameMap[fInterfaceBlockMap[field]]);
+ this->write("->");
+ }
+ this->writeName(field->fName);
+ }
+}
+
+void MetalCodeGenerator::writeSwizzle(const Swizzle& swizzle) {
+ this->writeExpression(*swizzle.base(), Precedence::kPostfix);
+ this->write(".");
+ for (int c : swizzle.components()) {
+ SkASSERT(c >= 0 && c <= 3);
+ this->write(&("x\0y\0z\0w\0"[c * 2]));
+ }
+}
+
+void MetalCodeGenerator::writeMatrixTimesEqualHelper(const Type& left, const Type& right,
+ const Type& result) {
+ SkASSERT(left.isMatrix());
+ SkASSERT(right.isMatrix());
+ SkASSERT(result.isMatrix());
+
+ std::string key = "Matrix *= " + this->typeName(left) + ":" + this->typeName(right);
+
+ if (!fHelpers.contains(key)) {
+ fHelpers.add(key);
+ fExtraFunctions.printf("thread %s& operator*=(thread %s& left, thread const %s& right) {\n"
+ " left = left * right;\n"
+ " return left;\n"
+ "}\n",
+ this->typeName(result).c_str(), this->typeName(left).c_str(),
+ this->typeName(right).c_str());
+ }
+}
+
+void MetalCodeGenerator::writeMatrixEqualityHelpers(const Type& left, const Type& right) {
+ SkASSERT(left.isMatrix());
+ SkASSERT(right.isMatrix());
+ SkASSERT(left.rows() == right.rows());
+ SkASSERT(left.columns() == right.columns());
+
+ std::string key = "Matrix == " + this->typeName(left) + ":" + this->typeName(right);
+
+ if (!fHelpers.contains(key)) {
+ fHelpers.add(key);
+ fExtraFunctionPrototypes.printf(R"(
+thread bool operator==(const %s left, const %s right);
+thread bool operator!=(const %s left, const %s right);
+)",
+ this->typeName(left).c_str(),
+ this->typeName(right).c_str(),
+ this->typeName(left).c_str(),
+ this->typeName(right).c_str());
+
+ fExtraFunctions.printf(
+ "thread bool operator==(const %s left, const %s right) {\n"
+ " return ",
+ this->typeName(left).c_str(), this->typeName(right).c_str());
+
+ const char* separator = "";
+ for (int index=0; index<left.columns(); ++index) {
+ fExtraFunctions.printf("%sall(left[%d] == right[%d])", separator, index, index);
+ separator = " &&\n ";
+ }
+
+ fExtraFunctions.printf(
+ ";\n"
+ "}\n"
+ "thread bool operator!=(const %s left, const %s right) {\n"
+ " return !(left == right);\n"
+ "}\n",
+ this->typeName(left).c_str(), this->typeName(right).c_str());
+ }
+}
+
+void MetalCodeGenerator::writeMatrixDivisionHelpers(const Type& type) {
+ SkASSERT(type.isMatrix());
+
+ std::string key = "Matrix / " + this->typeName(type);
+
+ if (!fHelpers.contains(key)) {
+ fHelpers.add(key);
+ std::string typeName = this->typeName(type);
+
+ fExtraFunctions.printf(
+ "thread %s operator/(const %s left, const %s right) {\n"
+ " return %s(",
+ typeName.c_str(), typeName.c_str(), typeName.c_str(), typeName.c_str());
+
+ const char* separator = "";
+ for (int index=0; index<type.columns(); ++index) {
+ fExtraFunctions.printf("%sleft[%d] / right[%d]", separator, index, index);
+ separator = ", ";
+ }
+
+ fExtraFunctions.printf(");\n"
+ "}\n"
+ "thread %s& operator/=(thread %s& left, thread const %s& right) {\n"
+ " left = left / right;\n"
+ " return left;\n"
+ "}\n",
+ typeName.c_str(), typeName.c_str(), typeName.c_str());
+ }
+}
+
+void MetalCodeGenerator::writeArrayEqualityHelpers(const Type& type) {
+ SkASSERT(type.isArray());
+
+ // If the array's component type needs a helper as well, we need to emit that one first.
+ this->writeEqualityHelpers(type.componentType(), type.componentType());
+
+ std::string key = "ArrayEquality []";
+ if (!fHelpers.contains(key)) {
+ fHelpers.add(key);
+ fExtraFunctionPrototypes.writeText(R"(
+template <typename T1, typename T2>
+bool operator==(const array_ref<T1> left, const array_ref<T2> right);
+template <typename T1, typename T2>
+bool operator!=(const array_ref<T1> left, const array_ref<T2> right);
+)");
+ fExtraFunctions.writeText(R"(
+template <typename T1, typename T2>
+bool operator==(const array_ref<T1> left, const array_ref<T2> right) {
+ if (left.size() != right.size()) {
+ return false;
+ }
+ for (size_t index = 0; index < left.size(); ++index) {
+ if (!all(left[index] == right[index])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+template <typename T1, typename T2>
+bool operator!=(const array_ref<T1> left, const array_ref<T2> right) {
+ return !(left == right);
+}
+)");
+ }
+}
+
+void MetalCodeGenerator::writeStructEqualityHelpers(const Type& type) {
+ SkASSERT(type.isStruct());
+ std::string key = "StructEquality " + this->typeName(type);
+
+ if (!fHelpers.contains(key)) {
+ fHelpers.add(key);
+ // If one of the struct's fields needs a helper as well, we need to emit that one first.
+ for (const Type::Field& field : type.fields()) {
+ this->writeEqualityHelpers(*field.fType, *field.fType);
+ }
+
+ // Write operator== and operator!= for this struct, since those are assumed to exist in SkSL
+ // and GLSL but do not exist by default in Metal.
+ fExtraFunctionPrototypes.printf(R"(
+thread bool operator==(thread const %s& left, thread const %s& right);
+thread bool operator!=(thread const %s& left, thread const %s& right);
+)",
+ this->typeName(type).c_str(),
+ this->typeName(type).c_str(),
+ this->typeName(type).c_str(),
+ this->typeName(type).c_str());
+
+ fExtraFunctions.printf(
+ "thread bool operator==(thread const %s& left, thread const %s& right) {\n"
+ " return ",
+ this->typeName(type).c_str(),
+ this->typeName(type).c_str());
+
+ const char* separator = "";
+ for (const Type::Field& field : type.fields()) {
+ if (field.fType->isArray()) {
+ fExtraFunctions.printf(
+ "%s(make_array_ref(left.%.*s) == make_array_ref(right.%.*s))",
+ separator,
+ (int)field.fName.size(), field.fName.data(),
+ (int)field.fName.size(), field.fName.data());
+ } else {
+ fExtraFunctions.printf("%sall(left.%.*s == right.%.*s)",
+ separator,
+ (int)field.fName.size(), field.fName.data(),
+ (int)field.fName.size(), field.fName.data());
+ }
+ separator = " &&\n ";
+ }
+ fExtraFunctions.printf(
+ ";\n"
+ "}\n"
+ "thread bool operator!=(thread const %s& left, thread const %s& right) {\n"
+ " return !(left == right);\n"
+ "}\n",
+ this->typeName(type).c_str(),
+ this->typeName(type).c_str());
+ }
+}
+
+void MetalCodeGenerator::writeEqualityHelpers(const Type& leftType, const Type& rightType) {
+ if (leftType.isArray() && rightType.isArray()) {
+ this->writeArrayEqualityHelpers(leftType);
+ return;
+ }
+ if (leftType.isStruct() && rightType.isStruct()) {
+ this->writeStructEqualityHelpers(leftType);
+ return;
+ }
+ if (leftType.isMatrix() && rightType.isMatrix()) {
+ this->writeMatrixEqualityHelpers(leftType, rightType);
+ return;
+ }
+}
+
+void MetalCodeGenerator::writeNumberAsMatrix(const Expression& expr, const Type& matrixType) {
+ SkASSERT(expr.type().isNumber());
+ SkASSERT(matrixType.isMatrix());
+
+ // Componentwise multiply the scalar against a matrix of the desired size which contains all 1s.
+ this->write("(");
+ this->writeType(matrixType);
+ this->write("(");
+
+ const char* separator = "";
+ for (int index = matrixType.slotCount(); index--;) {
+ this->write(separator);
+ this->write("1.0");
+ separator = ", ";
+ }
+
+ this->write(") * ");
+ this->writeExpression(expr, Precedence::kMultiplicative);
+ this->write(")");
+}
+
+void MetalCodeGenerator::writeBinaryExpressionElement(const Expression& expr,
+ Operator op,
+ const Expression& other,
+ Precedence precedence) {
+ bool needMatrixSplatOnScalar = other.type().isMatrix() && expr.type().isNumber() &&
+ op.isValidForMatrixOrVector() &&
+ op.removeAssignment().kind() != Operator::Kind::STAR;
+ if (needMatrixSplatOnScalar) {
+ this->writeNumberAsMatrix(expr, other.type());
+ } else if (op.isEquality() && expr.type().isArray()) {
+ this->write("make_array_ref(");
+ this->writeExpression(expr, precedence);
+ this->write(")");
+ } else {
+ this->writeExpression(expr, precedence);
+ }
+}
+
+void MetalCodeGenerator::writeBinaryExpression(const BinaryExpression& b,
+ Precedence parentPrecedence) {
+ const Expression& left = *b.left();
+ const Expression& right = *b.right();
+ const Type& leftType = left.type();
+ const Type& rightType = right.type();
+ Operator op = b.getOperator();
+ Precedence precedence = op.getBinaryPrecedence();
+ bool needParens = precedence >= parentPrecedence;
+ switch (op.kind()) {
+ case Operator::Kind::EQEQ:
+ this->writeEqualityHelpers(leftType, rightType);
+ if (leftType.isVector()) {
+ this->write("all");
+ needParens = true;
+ }
+ break;
+ case Operator::Kind::NEQ:
+ this->writeEqualityHelpers(leftType, rightType);
+ if (leftType.isVector()) {
+ this->write("any");
+ needParens = true;
+ }
+ break;
+ default:
+ break;
+ }
+ if (leftType.isMatrix() && rightType.isMatrix() && op.kind() == Operator::Kind::STAREQ) {
+ this->writeMatrixTimesEqualHelper(leftType, rightType, b.type());
+ }
+ if (op.removeAssignment().kind() == Operator::Kind::SLASH &&
+ ((leftType.isMatrix() && rightType.isMatrix()) ||
+ (leftType.isScalar() && rightType.isMatrix()) ||
+ (leftType.isMatrix() && rightType.isScalar()))) {
+ this->writeMatrixDivisionHelpers(leftType.isMatrix() ? leftType : rightType);
+ }
+
+ if (needParens) {
+ this->write("(");
+ }
+
+ this->writeBinaryExpressionElement(left, op, right, precedence);
+
+ if (op.kind() != Operator::Kind::EQ && op.isAssignment() &&
+ left.kind() == Expression::Kind::kSwizzle && !Analysis::HasSideEffects(left)) {
+ // This doesn't compile in Metal:
+ // float4 x = float4(1);
+ // x.xy *= float2x2(...);
+ // with the error message "non-const reference cannot bind to vector element",
+ // but switching it to x.xy = x.xy * float2x2(...) fixes it. We perform this tranformation
+ // as long as the LHS has no side effects, and hope for the best otherwise.
+ this->write(" = ");
+ this->writeExpression(left, Precedence::kAssignment);
+ this->write(operator_name(op.removeAssignment()));
+ precedence = op.removeAssignment().getBinaryPrecedence();
+ } else {
+ this->write(operator_name(op));
+ }
+
+ this->writeBinaryExpressionElement(right, op, left, precedence);
+
+ if (needParens) {
+ this->write(")");
+ }
+}
+
+void MetalCodeGenerator::writeTernaryExpression(const TernaryExpression& t,
+ Precedence parentPrecedence) {
+ if (Precedence::kTernary >= parentPrecedence) {
+ this->write("(");
+ }
+ this->writeExpression(*t.test(), Precedence::kTernary);
+ this->write(" ? ");
+ this->writeExpression(*t.ifTrue(), Precedence::kTernary);
+ this->write(" : ");
+ this->writeExpression(*t.ifFalse(), Precedence::kTernary);
+ if (Precedence::kTernary >= parentPrecedence) {
+ this->write(")");
+ }
+}
+
+void MetalCodeGenerator::writePrefixExpression(const PrefixExpression& p,
+ Precedence parentPrecedence) {
+ // According to the MSL specification, the arithmetic unary operators (+ and –) do not act
+ // upon matrix type operands. We treat the unary "+" as NOP for all operands.
+ const Operator op = p.getOperator();
+ if (op.kind() == Operator::Kind::PLUS) {
+ return this->writeExpression(*p.operand(), Precedence::kPrefix);
+ }
+
+ const bool matrixNegation =
+ op.kind() == Operator::Kind::MINUS && p.operand()->type().isMatrix();
+ const bool needParens = Precedence::kPrefix >= parentPrecedence || matrixNegation;
+
+ if (needParens) {
+ this->write("(");
+ }
+
+ // Transform the unary "-" on a matrix type to a multiplication by -1.
+ if (matrixNegation) {
+ this->write("-1.0 * ");
+ } else {
+ this->write(p.getOperator().tightOperatorName());
+ }
+ this->writeExpression(*p.operand(), Precedence::kPrefix);
+
+ if (needParens) {
+ this->write(")");
+ }
+}
+
+void MetalCodeGenerator::writePostfixExpression(const PostfixExpression& p,
+ Precedence parentPrecedence) {
+ if (Precedence::kPostfix >= parentPrecedence) {
+ this->write("(");
+ }
+ this->writeExpression(*p.operand(), Precedence::kPostfix);
+ this->write(p.getOperator().tightOperatorName());
+ if (Precedence::kPostfix >= parentPrecedence) {
+ this->write(")");
+ }
+}
+
+void MetalCodeGenerator::writeLiteral(const Literal& l) {
+ const Type& type = l.type();
+ if (type.isFloat()) {
+ this->write(l.description(OperatorPrecedence::kTopLevel));
+ if (!l.type().highPrecision()) {
+ this->write("h");
+ }
+ return;
+ }
+ if (type.isInteger()) {
+ if (type.matches(*fContext.fTypes.fUInt)) {
+ this->write(std::to_string(l.intValue() & 0xffffffff));
+ this->write("u");
+ } else if (type.matches(*fContext.fTypes.fUShort)) {
+ this->write(std::to_string(l.intValue() & 0xffff));
+ this->write("u");
+ } else {
+ this->write(std::to_string(l.intValue()));
+ }
+ return;
+ }
+ SkASSERT(type.isBoolean());
+ this->write(l.description(OperatorPrecedence::kTopLevel));
+}
+
+void MetalCodeGenerator::writeFunctionRequirementArgs(const FunctionDeclaration& f,
+ const char*& separator) {
+ Requirements requirements = this->requirements(f);
+ if (requirements & kInputs_Requirement) {
+ this->write(separator);
+ this->write("_in");
+ separator = ", ";
+ }
+ if (requirements & kOutputs_Requirement) {
+ this->write(separator);
+ this->write("_out");
+ separator = ", ";
+ }
+ if (requirements & kUniforms_Requirement) {
+ this->write(separator);
+ this->write("_uniforms");
+ separator = ", ";
+ }
+ if (requirements & kGlobals_Requirement) {
+ this->write(separator);
+ this->write("_globals");
+ separator = ", ";
+ }
+ if (requirements & kFragCoord_Requirement) {
+ this->write(separator);
+ this->write("_fragCoord");
+ separator = ", ";
+ }
+ if (requirements & kThreadgroups_Requirement) {
+ this->write(separator);
+ this->write("_threadgroups");
+ separator = ", ";
+ }
+}
+
+void MetalCodeGenerator::writeFunctionRequirementParams(const FunctionDeclaration& f,
+ const char*& separator) {
+ Requirements requirements = this->requirements(f);
+ if (requirements & kInputs_Requirement) {
+ this->write(separator);
+ this->write("Inputs _in");
+ separator = ", ";
+ }
+ if (requirements & kOutputs_Requirement) {
+ this->write(separator);
+ this->write("thread Outputs& _out");
+ separator = ", ";
+ }
+ if (requirements & kUniforms_Requirement) {
+ this->write(separator);
+ this->write("Uniforms _uniforms");
+ separator = ", ";
+ }
+ if (requirements & kGlobals_Requirement) {
+ this->write(separator);
+ this->write("thread Globals& _globals");
+ separator = ", ";
+ }
+ if (requirements & kFragCoord_Requirement) {
+ this->write(separator);
+ this->write("float4 _fragCoord");
+ separator = ", ";
+ }
+ if (requirements & kThreadgroups_Requirement) {
+ this->write(separator);
+ this->write("threadgroup Threadgroups& _threadgroups");
+ separator = ", ";
+ }
+}
+
+int MetalCodeGenerator::getUniformBinding(const Modifiers& m) {
+ return (m.fLayout.fBinding >= 0) ? m.fLayout.fBinding
+ : fProgram.fConfig->fSettings.fDefaultUniformBinding;
+}
+
+int MetalCodeGenerator::getUniformSet(const Modifiers& m) {
+ return (m.fLayout.fSet >= 0) ? m.fLayout.fSet
+ : fProgram.fConfig->fSettings.fDefaultUniformSet;
+}
+
+bool MetalCodeGenerator::writeFunctionDeclaration(const FunctionDeclaration& f) {
+ fRTFlipName = fProgram.fInputs.fUseFlipRTUniform
+ ? "_globals._anonInterface0->" SKSL_RTFLIP_NAME
+ : "";
+ const char* separator = "";
+ if (f.isMain()) {
+ if (ProgramConfig::IsFragment(fProgram.fConfig->fKind)) {
+ this->write("fragment Outputs fragmentMain");
+ } else if (ProgramConfig::IsVertex(fProgram.fConfig->fKind)) {
+ this->write("vertex Outputs vertexMain");
+ } else if (ProgramConfig::IsCompute(fProgram.fConfig->fKind)) {
+ this->write("kernel void computeMain");
+ } else {
+ fContext.fErrors->error(Position(), "unsupported kind of program");
+ return false;
+ }
+ this->write("(");
+ if (!ProgramConfig::IsCompute(fProgram.fConfig->fKind)) {
+ this->write("Inputs _in [[stage_in]]");
+ separator = ", ";
+ }
+ if (-1 != fUniformBuffer) {
+ this->write(separator);
+ this->write("constant Uniforms& _uniforms [[buffer(" +
+ std::to_string(fUniformBuffer) + ")]]");
+ separator = ", ";
+ }
+ for (const ProgramElement* e : fProgram.elements()) {
+ if (e->is<GlobalVarDeclaration>()) {
+ const GlobalVarDeclaration& decls = e->as<GlobalVarDeclaration>();
+ const VarDeclaration& decl = decls.varDeclaration();
+ const Variable* var = decl.var();
+ const SkSL::Type::TypeKind varKind = var->type().typeKind();
+
+ if (varKind == Type::TypeKind::kSampler || varKind == Type::TypeKind::kTexture) {
+ if (var->type().dimensions() != SpvDim2D) {
+ // Not yet implemented--Skia currently only uses 2D textures.
+ fContext.fErrors->error(decls.fPosition, "Unsupported texture dimensions");
+ return false;
+ }
+
+ int binding = getUniformBinding(var->modifiers());
+ this->write(separator);
+ separator = ", ";
+
+ if (varKind == Type::TypeKind::kSampler) {
+ this->writeType(var->type().textureType());
+ this->write(" ");
+ this->writeName(var->mangledName());
+ this->write(kTextureSuffix);
+ this->write(" [[texture(");
+ this->write(std::to_string(binding));
+ this->write(")]], sampler ");
+ this->writeName(var->mangledName());
+ this->write(kSamplerSuffix);
+ this->write(" [[sampler(");
+ this->write(std::to_string(binding));
+ this->write(")]]");
+ } else {
+ SkASSERT(varKind == Type::TypeKind::kTexture);
+ this->writeType(var->type());
+ this->write(" ");
+ this->writeName(var->mangledName());
+ this->write(" [[texture(");
+ this->write(std::to_string(binding));
+ this->write(")]]");
+ }
+ } else if (ProgramConfig::IsCompute(fProgram.fConfig->fKind)) {
+ std::string type, attr;
+ switch (var->modifiers().fLayout.fBuiltin) {
+ case SK_NUMWORKGROUPS_BUILTIN:
+ type = "uint3 ";
+ attr = " [[threadgroups_per_grid]]";
+ break;
+ case SK_WORKGROUPID_BUILTIN:
+ type = "uint3 ";
+ attr = " [[threadgroup_position_in_grid]]";
+ break;
+ case SK_LOCALINVOCATIONID_BUILTIN:
+ type = "uint3 ";
+ attr = " [[thread_position_in_threadgroup]]";
+ break;
+ case SK_GLOBALINVOCATIONID_BUILTIN:
+ type = "uint3 ";
+ attr = " [[thread_position_in_grid]]";
+ break;
+ case SK_LOCALINVOCATIONINDEX_BUILTIN:
+ type = "uint ";
+ attr = " [[thread_index_in_threadgroup]]";
+ break;
+ default:
+ break;
+ }
+ if (!attr.empty()) {
+ this->write(separator);
+ this->write(type);
+ this->write(var->name());
+ this->write(attr);
+ separator = ", ";
+ }
+ }
+ } else if (e->is<InterfaceBlock>()) {
+ const InterfaceBlock& intf = e->as<InterfaceBlock>();
+ if (intf.typeName() == "sk_PerVertex") {
+ continue;
+ }
+ this->write(separator);
+ if (is_readonly(intf)) {
+ this->write("const ");
+ }
+ this->write(is_buffer(intf) ? "device " : "constant ");
+ this->writeType(intf.var()->type());
+ this->write("& " );
+ this->write(fInterfaceBlockNameMap[&intf]);
+ this->write(" [[buffer(");
+ this->write(std::to_string(this->getUniformBinding(intf.var()->modifiers())));
+ this->write(")]]");
+ separator = ", ";
+ }
+ }
+ if (ProgramConfig::IsFragment(fProgram.fConfig->fKind)) {
+ if (fProgram.fInputs.fUseFlipRTUniform && fInterfaceBlockNameMap.empty()) {
+ this->write(separator);
+ this->write("constant sksl_synthetic_uniforms& _anonInterface0 [[buffer(1)]]");
+ fRTFlipName = "_anonInterface0." SKSL_RTFLIP_NAME;
+ separator = ", ";
+ }
+ this->write(separator);
+ this->write("bool _frontFacing [[front_facing]]");
+ this->write(", float4 _fragCoord [[position]]");
+ separator = ", ";
+ } else if (ProgramConfig::IsVertex(fProgram.fConfig->fKind)) {
+ this->write(separator);
+ this->write("uint sk_VertexID [[vertex_id]], uint sk_InstanceID [[instance_id]]");
+ separator = ", ";
+ }
+ } else {
+ this->writeType(f.returnType());
+ this->write(" ");
+ this->writeName(f.mangledName());
+ this->write("(");
+ this->writeFunctionRequirementParams(f, separator);
+ }
+ for (const Variable* param : f.parameters()) {
+ if (f.isMain() && param->modifiers().fLayout.fBuiltin != -1) {
+ continue;
+ }
+ this->write(separator);
+ separator = ", ";
+ this->writeModifiers(param->modifiers());
+ this->writeType(param->type());
+ if (pass_by_reference(param->type(), param->modifiers())) {
+ this->write("&");
+ }
+ this->write(" ");
+ this->writeName(param->mangledName());
+ }
+ this->write(")");
+ return true;
+}
+
+void MetalCodeGenerator::writeFunctionPrototype(const FunctionPrototype& f) {
+ this->writeFunctionDeclaration(f.declaration());
+ this->writeLine(";");
+}
+
+static bool is_block_ending_with_return(const Statement* stmt) {
+ // This function detects (potentially nested) blocks that end in a return statement.
+ if (!stmt->is<Block>()) {
+ return false;
+ }
+ const StatementArray& block = stmt->as<Block>().children();
+ for (int index = block.size(); index--; ) {
+ stmt = block[index].get();
+ if (stmt->is<ReturnStatement>()) {
+ return true;
+ }
+ if (stmt->is<Block>()) {
+ return is_block_ending_with_return(stmt);
+ }
+ if (!stmt->is<Nop>()) {
+ break;
+ }
+ }
+ return false;
+}
+
+void MetalCodeGenerator::writeComputeMainInputs() {
+ // Compute shaders only have input variables (e.g. sk_GlobalInvocationID) and access program
+ // inputs/outputs via the Globals and Uniforms structs. We collect the allowed "in" parameters
+ // into an Input struct here, since the rest of the code expects the normal _in / _out pattern.
+ this->write("Inputs _in = { ");
+ const char* separator = "";
+ for (const ProgramElement* e : fProgram.elements()) {
+ if (e->is<GlobalVarDeclaration>()) {
+ const GlobalVarDeclaration& decls = e->as<GlobalVarDeclaration>();
+ const Variable* var = decls.varDeclaration().var();
+ if (is_input(*var)) {
+ this->write(separator);
+ separator = ", ";
+ this->writeName(var->mangledName());
+ }
+ }
+ }
+ this->writeLine(" };");
+}
+
+void MetalCodeGenerator::writeFunction(const FunctionDefinition& f) {
+ SkASSERT(!fProgram.fConfig->fSettings.fFragColorIsInOut);
+
+ if (!this->writeFunctionDeclaration(f.declaration())) {
+ return;
+ }
+
+ fCurrentFunction = &f.declaration();
+ SkScopeExit clearCurrentFunction([&] { fCurrentFunction = nullptr; });
+
+ this->writeLine(" {");
+
+ if (f.declaration().isMain()) {
+ fIndentation++;
+ this->writeGlobalInit();
+ if (ProgramConfig::IsCompute(fProgram.fConfig->fKind)) {
+ this->writeThreadgroupInit();
+ this->writeComputeMainInputs();
+ }
+ else {
+ this->writeLine("Outputs _out;");
+ this->writeLine("(void)_out;");
+ }
+ fIndentation--;
+ }
+
+ fFunctionHeader.clear();
+ StringStream buffer;
+ {
+ AutoOutputStream outputToBuffer(this, &buffer);
+ fIndentation++;
+ for (const std::unique_ptr<Statement>& stmt : f.body()->as<Block>().children()) {
+ if (!stmt->isEmpty()) {
+ this->writeStatement(*stmt);
+ this->finishLine();
+ }
+ }
+ if (f.declaration().isMain()) {
+ // If the main function doesn't end with a return, we need to synthesize one here.
+ if (!is_block_ending_with_return(f.body().get())) {
+ this->writeReturnStatementFromMain();
+ this->finishLine();
+ }
+ }
+ fIndentation--;
+ this->writeLine("}");
+ }
+ this->write(fFunctionHeader);
+ this->write(buffer.str());
+}
+
+void MetalCodeGenerator::writeModifiers(const Modifiers& modifiers) {
+ if (ProgramConfig::IsCompute(fProgram.fConfig->fKind) &&
+ (modifiers.fFlags & (Modifiers::kIn_Flag | Modifiers::kOut_Flag))) {
+ this->write("device ");
+ } else if (modifiers.fFlags & Modifiers::kOut_Flag) {
+ this->write("thread ");
+ }
+ if (modifiers.fFlags & Modifiers::kConst_Flag) {
+ this->write("const ");
+ }
+}
+
+void MetalCodeGenerator::writeInterfaceBlock(const InterfaceBlock& intf) {
+ if (intf.typeName() == "sk_PerVertex") {
+ return;
+ }
+ const Type* structType = &intf.var()->type().componentType();
+ this->writeModifiers(intf.var()->modifiers());
+ this->write("struct ");
+ this->writeType(*structType);
+ this->writeLine(" {");
+ fIndentation++;
+ this->writeFields(structType->fields(), structType->fPosition, &intf);
+ if (fProgram.fInputs.fUseFlipRTUniform) {
+ this->writeLine("float2 " SKSL_RTFLIP_NAME ";");
+ }
+ fIndentation--;
+ this->write("}");
+ if (intf.instanceName().size()) {
+ this->write(" ");
+ this->write(intf.instanceName());
+ if (intf.arraySize() > 0) {
+ this->write("[");
+ this->write(std::to_string(intf.arraySize()));
+ this->write("]");
+ }
+ fInterfaceBlockNameMap.set(&intf, intf.instanceName());
+ } else {
+ fInterfaceBlockNameMap.set(&intf, *fProgram.fSymbols->takeOwnershipOfString(
+ "_anonInterface" + std::to_string(fAnonInterfaceCount++)));
+ }
+ this->writeLine(";");
+}
+
+void MetalCodeGenerator::writeFields(const std::vector<Type::Field>& fields, Position parentPos,
+ const InterfaceBlock* parentIntf) {
+ MemoryLayout memoryLayout(MemoryLayout::Standard::kMetal);
+ int currentOffset = 0;
+ for (const Type::Field& field : fields) {
+ int fieldOffset = field.fModifiers.fLayout.fOffset;
+ const Type* fieldType = field.fType;
+ if (!memoryLayout.isSupported(*fieldType)) {
+ fContext.fErrors->error(parentPos, "type '" + std::string(fieldType->name()) +
+ "' is not permitted here");
+ return;
+ }
+ if (fieldOffset != -1) {
+ if (currentOffset > fieldOffset) {
+ fContext.fErrors->error(field.fPosition,
+ "offset of field '" + std::string(field.fName) +
+ "' must be at least " + std::to_string(currentOffset));
+ return;
+ } else if (currentOffset < fieldOffset) {
+ this->write("char pad");
+ this->write(std::to_string(fPaddingCount++));
+ this->write("[");
+ this->write(std::to_string(fieldOffset - currentOffset));
+ this->writeLine("];");
+ currentOffset = fieldOffset;
+ }
+ int alignment = memoryLayout.alignment(*fieldType);
+ if (fieldOffset % alignment) {
+ fContext.fErrors->error(field.fPosition,
+ "offset of field '" + std::string(field.fName) +
+ "' must be a multiple of " + std::to_string(alignment));
+ return;
+ }
+ }
+ if (fieldType->isUnsizedArray()) {
+ // An unsized array always appears as the last member of a storage block. We declare
+ // it as a one-element array and allow dereferencing past the capacity.
+ // TODO(armansito): This is because C++ does not support flexible array members like C99
+ // does. This generally works but it can lead to UB as compilers are free to insert
+ // padding past the first element of the array. An alternative approach is to declare
+ // the struct without the unsized array member and replace variable references with a
+ // buffer offset calculation based on sizeof().
+ this->writeModifiers(field.fModifiers);
+ this->writeType(fieldType->componentType());
+ this->write(" ");
+ this->writeName(field.fName);
+ this->write("[1]");
+ } else {
+ size_t fieldSize = memoryLayout.size(*fieldType);
+ if (fieldSize > static_cast<size_t>(std::numeric_limits<int>::max() - currentOffset)) {
+ fContext.fErrors->error(parentPos, "field offset overflow");
+ return;
+ }
+ currentOffset += fieldSize;
+ this->writeModifiers(field.fModifiers);
+ this->writeType(*fieldType);
+ this->write(" ");
+ this->writeName(field.fName);
+ }
+ this->writeLine(";");
+ if (parentIntf) {
+ fInterfaceBlockMap.set(&field, parentIntf);
+ }
+ }
+}
+
+void MetalCodeGenerator::writeVarInitializer(const Variable& var, const Expression& value) {
+ this->writeExpression(value, Precedence::kTopLevel);
+}
+
+void MetalCodeGenerator::writeName(std::string_view name) {
+ if (fReservedWords.contains(name)) {
+ this->write("_"); // adding underscore before name to avoid conflict with reserved words
+ }
+ this->write(name);
+}
+
+void MetalCodeGenerator::writeVarDeclaration(const VarDeclaration& varDecl) {
+ this->writeModifiers(varDecl.var()->modifiers());
+ this->writeType(varDecl.var()->type());
+ this->write(" ");
+ this->writeName(varDecl.var()->mangledName());
+ if (varDecl.value()) {
+ this->write(" = ");
+ this->writeVarInitializer(*varDecl.var(), *varDecl.value());
+ }
+ this->write(";");
+}
+
+void MetalCodeGenerator::writeStatement(const Statement& s) {
+ switch (s.kind()) {
+ case Statement::Kind::kBlock:
+ this->writeBlock(s.as<Block>());
+ break;
+ case Statement::Kind::kExpression:
+ this->writeExpressionStatement(s.as<ExpressionStatement>());
+ break;
+ case Statement::Kind::kReturn:
+ this->writeReturnStatement(s.as<ReturnStatement>());
+ break;
+ case Statement::Kind::kVarDeclaration:
+ this->writeVarDeclaration(s.as<VarDeclaration>());
+ break;
+ case Statement::Kind::kIf:
+ this->writeIfStatement(s.as<IfStatement>());
+ break;
+ case Statement::Kind::kFor:
+ this->writeForStatement(s.as<ForStatement>());
+ break;
+ case Statement::Kind::kDo:
+ this->writeDoStatement(s.as<DoStatement>());
+ break;
+ case Statement::Kind::kSwitch:
+ this->writeSwitchStatement(s.as<SwitchStatement>());
+ break;
+ case Statement::Kind::kBreak:
+ this->write("break;");
+ break;
+ case Statement::Kind::kContinue:
+ this->write("continue;");
+ break;
+ case Statement::Kind::kDiscard:
+ this->write("discard_fragment();");
+ break;
+ case Statement::Kind::kNop:
+ this->write(";");
+ break;
+ default:
+ SkDEBUGFAILF("unsupported statement: %s", s.description().c_str());
+ break;
+ }
+}
+
+void MetalCodeGenerator::writeBlock(const Block& b) {
+ // Write scope markers if this block is a scope, or if the block is empty (since we need to emit
+ // something here to make the code valid).
+ bool isScope = b.isScope() || b.isEmpty();
+ if (isScope) {
+ this->writeLine("{");
+ fIndentation++;
+ }
+ for (const std::unique_ptr<Statement>& stmt : b.children()) {
+ if (!stmt->isEmpty()) {
+ this->writeStatement(*stmt);
+ this->finishLine();
+ }
+ }
+ if (isScope) {
+ fIndentation--;
+ this->write("}");
+ }
+}
+
+void MetalCodeGenerator::writeIfStatement(const IfStatement& stmt) {
+ this->write("if (");
+ this->writeExpression(*stmt.test(), Precedence::kTopLevel);
+ this->write(") ");
+ this->writeStatement(*stmt.ifTrue());
+ if (stmt.ifFalse()) {
+ this->write(" else ");
+ this->writeStatement(*stmt.ifFalse());
+ }
+}
+
+void MetalCodeGenerator::writeForStatement(const ForStatement& f) {
+ // Emit loops of the form 'for(;test;)' as 'while(test)', which is probably how they started
+ if (!f.initializer() && f.test() && !f.next()) {
+ this->write("while (");
+ this->writeExpression(*f.test(), Precedence::kTopLevel);
+ this->write(") ");
+ this->writeStatement(*f.statement());
+ return;
+ }
+
+ this->write("for (");
+ if (f.initializer() && !f.initializer()->isEmpty()) {
+ this->writeStatement(*f.initializer());
+ } else {
+ this->write("; ");
+ }
+ if (f.test()) {
+ this->writeExpression(*f.test(), Precedence::kTopLevel);
+ }
+ this->write("; ");
+ if (f.next()) {
+ this->writeExpression(*f.next(), Precedence::kTopLevel);
+ }
+ this->write(") ");
+ this->writeStatement(*f.statement());
+}
+
+void MetalCodeGenerator::writeDoStatement(const DoStatement& d) {
+ this->write("do ");
+ this->writeStatement(*d.statement());
+ this->write(" while (");
+ this->writeExpression(*d.test(), Precedence::kTopLevel);
+ this->write(");");
+}
+
+void MetalCodeGenerator::writeExpressionStatement(const ExpressionStatement& s) {
+ if (fProgram.fConfig->fSettings.fOptimize && !Analysis::HasSideEffects(*s.expression())) {
+ // Don't emit dead expressions.
+ return;
+ }
+ this->writeExpression(*s.expression(), Precedence::kTopLevel);
+ this->write(";");
+}
+
+void MetalCodeGenerator::writeSwitchStatement(const SwitchStatement& s) {
+ this->write("switch (");
+ this->writeExpression(*s.value(), Precedence::kTopLevel);
+ this->writeLine(") {");
+ fIndentation++;
+ for (const std::unique_ptr<Statement>& stmt : s.cases()) {
+ const SwitchCase& c = stmt->as<SwitchCase>();
+ if (c.isDefault()) {
+ this->writeLine("default:");
+ } else {
+ this->write("case ");
+ this->write(std::to_string(c.value()));
+ this->writeLine(":");
+ }
+ if (!c.statement()->isEmpty()) {
+ fIndentation++;
+ this->writeStatement(*c.statement());
+ this->finishLine();
+ fIndentation--;
+ }
+ }
+ fIndentation--;
+ this->write("}");
+}
+
+void MetalCodeGenerator::writeReturnStatementFromMain() {
+ // main functions in Metal return a magic _out parameter that doesn't exist in SkSL.
+ if (ProgramConfig::IsVertex(fProgram.fConfig->fKind) ||
+ ProgramConfig::IsFragment(fProgram.fConfig->fKind)) {
+ this->write("return _out;");
+ } else if (ProgramConfig::IsCompute(fProgram.fConfig->fKind)) {
+ this->write("return;");
+ } else {
+ SkDEBUGFAIL("unsupported kind of program");
+ }
+}
+
+void MetalCodeGenerator::writeReturnStatement(const ReturnStatement& r) {
+ if (fCurrentFunction && fCurrentFunction->isMain()) {
+ if (r.expression()) {
+ if (r.expression()->type().matches(*fContext.fTypes.fHalf4)) {
+ this->write("_out.sk_FragColor = ");
+ this->writeExpression(*r.expression(), Precedence::kTopLevel);
+ this->writeLine(";");
+ } else {
+ fContext.fErrors->error(r.fPosition,
+ "Metal does not support returning '" +
+ r.expression()->type().description() + "' from main()");
+ }
+ }
+ this->writeReturnStatementFromMain();
+ return;
+ }
+
+ this->write("return");
+ if (r.expression()) {
+ this->write(" ");
+ this->writeExpression(*r.expression(), Precedence::kTopLevel);
+ }
+ this->write(";");
+}
+
+void MetalCodeGenerator::writeHeader() {
+ this->write("#include <metal_stdlib>\n");
+ this->write("#include <simd/simd.h>\n");
+ this->write("using namespace metal;\n");
+}
+
+void MetalCodeGenerator::writeSampler2DPolyfill() {
+ class : public GlobalStructVisitor {
+ public:
+ void visitSampler(const Type&, std::string_view) override {
+ if (fWrotePolyfill) {
+ return;
+ }
+ fWrotePolyfill = true;
+
+ std::string polyfill = SkSL::String::printf(R"(
+struct sampler2D {
+ texture2d<half> tex;
+ sampler smp;
+};
+half4 sample(sampler2D i, float2 p, float b=%g) { return i.tex.sample(i.smp, p, bias(b)); }
+half4 sample(sampler2D i, float3 p, float b=%g) { return i.tex.sample(i.smp, p.xy / p.z, bias(b)); }
+half4 sampleLod(sampler2D i, float2 p, float lod) { return i.tex.sample(i.smp, p, level(lod)); }
+half4 sampleLod(sampler2D i, float3 p, float lod) {
+ return i.tex.sample(i.smp, p.xy / p.z, level(lod));
+}
+half4 sampleGrad(sampler2D i, float2 p, float2 dPdx, float2 dPdy) {
+ return i.tex.sample(i.smp, p, gradient2d(dPdx, dPdy));
+}
+
+)",
+ fTextureBias,
+ fTextureBias);
+ fCodeGen->write(polyfill.c_str());
+ }
+
+ MetalCodeGenerator* fCodeGen = nullptr;
+ float fTextureBias = 0.0f;
+ bool fWrotePolyfill = false;
+ } visitor;
+
+ visitor.fCodeGen = this;
+ visitor.fTextureBias = fProgram.fConfig->fSettings.fSharpenTextures ? kSharpenTexturesBias
+ : 0.0f;
+ this->visitGlobalStruct(&visitor);
+}
+
+void MetalCodeGenerator::writeUniformStruct() {
+ for (const ProgramElement* e : fProgram.elements()) {
+ if (e->is<GlobalVarDeclaration>()) {
+ const GlobalVarDeclaration& decls = e->as<GlobalVarDeclaration>();
+ const Variable& var = *decls.varDeclaration().var();
+ if (var.modifiers().fFlags & Modifiers::kUniform_Flag &&
+ var.type().typeKind() != Type::TypeKind::kSampler &&
+ var.type().typeKind() != Type::TypeKind::kTexture) {
+ int uniformSet = this->getUniformSet(var.modifiers());
+ // Make sure that the program's uniform-set value is consistent throughout.
+ if (-1 == fUniformBuffer) {
+ this->write("struct Uniforms {\n");
+ fUniformBuffer = uniformSet;
+ } else if (uniformSet != fUniformBuffer) {
+ fContext.fErrors->error(decls.fPosition,
+ "Metal backend requires all uniforms to have the same "
+ "'layout(set=...)'");
+ }
+ this->write(" ");
+ this->writeType(var.type());
+ this->write(" ");
+ this->writeName(var.mangledName());
+ this->write(";\n");
+ }
+ }
+ }
+ if (-1 != fUniformBuffer) {
+ this->write("};\n");
+ }
+}
+
+void MetalCodeGenerator::writeInputStruct() {
+ this->write("struct Inputs {\n");
+ for (const ProgramElement* e : fProgram.elements()) {
+ if (e->is<GlobalVarDeclaration>()) {
+ const GlobalVarDeclaration& decls = e->as<GlobalVarDeclaration>();
+ const Variable& var = *decls.varDeclaration().var();
+ if (is_input(var)) {
+ this->write(" ");
+ if (ProgramConfig::IsCompute(fProgram.fConfig->fKind) &&
+ needs_address_space(var.type(), var.modifiers())) {
+ // TODO: address space support
+ this->write("device ");
+ }
+ this->writeType(var.type());
+ if (pass_by_reference(var.type(), var.modifiers())) {
+ this->write("&");
+ }
+ this->write(" ");
+ this->writeName(var.mangledName());
+ if (-1 != var.modifiers().fLayout.fLocation) {
+ if (ProgramConfig::IsVertex(fProgram.fConfig->fKind)) {
+ this->write(" [[attribute(" +
+ std::to_string(var.modifiers().fLayout.fLocation) + ")]]");
+ } else if (ProgramConfig::IsFragment(fProgram.fConfig->fKind)) {
+ this->write(" [[user(locn" +
+ std::to_string(var.modifiers().fLayout.fLocation) + ")]]");
+ }
+ }
+ this->write(";\n");
+ }
+ }
+ }
+ this->write("};\n");
+}
+
+void MetalCodeGenerator::writeOutputStruct() {
+ this->write("struct Outputs {\n");
+ if (ProgramConfig::IsVertex(fProgram.fConfig->fKind)) {
+ this->write(" float4 sk_Position [[position]];\n");
+ } else if (ProgramConfig::IsFragment(fProgram.fConfig->fKind)) {
+ this->write(" half4 sk_FragColor [[color(0)]];\n");
+ }
+ for (const ProgramElement* e : fProgram.elements()) {
+ if (e->is<GlobalVarDeclaration>()) {
+ const GlobalVarDeclaration& decls = e->as<GlobalVarDeclaration>();
+ const Variable& var = *decls.varDeclaration().var();
+ if (is_output(var)) {
+ this->write(" ");
+ if (ProgramConfig::IsCompute(fProgram.fConfig->fKind) &&
+ needs_address_space(var.type(), var.modifiers())) {
+ // TODO: address space support
+ this->write("device ");
+ }
+ this->writeType(var.type());
+ if (ProgramConfig::IsCompute(fProgram.fConfig->fKind) &&
+ pass_by_reference(var.type(), var.modifiers())) {
+ this->write("&");
+ }
+ this->write(" ");
+ this->writeName(var.mangledName());
+
+ int location = var.modifiers().fLayout.fLocation;
+ if (!ProgramConfig::IsCompute(fProgram.fConfig->fKind) && location < 0 &&
+ var.type().typeKind() != Type::TypeKind::kTexture) {
+ fContext.fErrors->error(var.fPosition,
+ "Metal out variables must have 'layout(location=...)'");
+ } else if (ProgramConfig::IsVertex(fProgram.fConfig->fKind)) {
+ this->write(" [[user(locn" + std::to_string(location) + ")]]");
+ } else if (ProgramConfig::IsFragment(fProgram.fConfig->fKind)) {
+ this->write(" [[color(" + std::to_string(location) + ")");
+ int colorIndex = var.modifiers().fLayout.fIndex;
+ if (colorIndex) {
+ this->write(", index(" + std::to_string(colorIndex) + ")");
+ }
+ this->write("]]");
+ }
+ this->write(";\n");
+ }
+ }
+ }
+ if (ProgramConfig::IsVertex(fProgram.fConfig->fKind)) {
+ this->write(" float sk_PointSize [[point_size]];\n");
+ }
+ this->write("};\n");
+}
+
+void MetalCodeGenerator::writeInterfaceBlocks() {
+ bool wroteInterfaceBlock = false;
+ for (const ProgramElement* e : fProgram.elements()) {
+ if (e->is<InterfaceBlock>()) {
+ this->writeInterfaceBlock(e->as<InterfaceBlock>());
+ wroteInterfaceBlock = true;
+ }
+ }
+ if (!wroteInterfaceBlock && fProgram.fInputs.fUseFlipRTUniform) {
+ this->writeLine("struct sksl_synthetic_uniforms {");
+ this->writeLine(" float2 " SKSL_RTFLIP_NAME ";");
+ this->writeLine("};");
+ }
+}
+
+void MetalCodeGenerator::writeStructDefinitions() {
+ for (const ProgramElement* e : fProgram.elements()) {
+ if (e->is<StructDefinition>()) {
+ this->writeStructDefinition(e->as<StructDefinition>());
+ }
+ }
+}
+
+void MetalCodeGenerator::writeConstantVariables() {
+ class : public GlobalStructVisitor {
+ public:
+ void visitConstantVariable(const VarDeclaration& decl) override {
+ fCodeGen->write("constant ");
+ fCodeGen->writeVarDeclaration(decl);
+ fCodeGen->finishLine();
+ }
+
+ MetalCodeGenerator* fCodeGen = nullptr;
+ } visitor;
+
+ visitor.fCodeGen = this;
+ this->visitGlobalStruct(&visitor);
+}
+
+void MetalCodeGenerator::visitGlobalStruct(GlobalStructVisitor* visitor) {
+ for (const ProgramElement* element : fProgram.elements()) {
+ if (element->is<InterfaceBlock>()) {
+ const auto* ib = &element->as<InterfaceBlock>();
+ if (ib->typeName() != "sk_PerVertex") {
+ visitor->visitInterfaceBlock(*ib, fInterfaceBlockNameMap[ib]);
+ }
+ continue;
+ }
+ if (!element->is<GlobalVarDeclaration>()) {
+ continue;
+ }
+ const GlobalVarDeclaration& global = element->as<GlobalVarDeclaration>();
+ const VarDeclaration& decl = global.varDeclaration();
+ const Variable& var = *decl.var();
+ if (var.type().typeKind() == Type::TypeKind::kSampler) {
+ visitor->visitSampler(var.type(), var.mangledName());
+ continue;
+ }
+ if (var.type().typeKind() == Type::TypeKind::kTexture) {
+ visitor->visitTexture(var.type(), var.modifiers(), var.mangledName());
+ continue;
+ }
+ if (!(var.modifiers().fFlags & ~Modifiers::kConst_Flag) &&
+ var.modifiers().fLayout.fBuiltin == -1) {
+ if (is_in_globals(var)) {
+ // Visit a regular global variable.
+ visitor->visitNonconstantVariable(var, decl.value().get());
+ } else {
+ // Visit a constant-expression variable.
+ SkASSERT(var.modifiers().fFlags & Modifiers::kConst_Flag);
+ visitor->visitConstantVariable(decl);
+ }
+ }
+ }
+}
+
+void MetalCodeGenerator::writeGlobalStruct() {
+ class : public GlobalStructVisitor {
+ public:
+ void visitInterfaceBlock(const InterfaceBlock& block,
+ std::string_view blockName) override {
+ this->addElement();
+ fCodeGen->write(" ");
+ if (is_readonly(block)) {
+ fCodeGen->write("const ");
+ }
+ fCodeGen->write(is_buffer(block) ? "device " : "constant ");
+ fCodeGen->write(block.typeName());
+ fCodeGen->write("* ");
+ fCodeGen->writeName(blockName);
+ fCodeGen->write(";\n");
+ }
+ void visitTexture(const Type& type, const Modifiers& modifiers,
+ std::string_view name) override {
+ this->addElement();
+ fCodeGen->write(" ");
+ fCodeGen->writeType(type);
+ fCodeGen->write(" ");
+ fCodeGen->writeName(name);
+ fCodeGen->write(";\n");
+ }
+ void visitSampler(const Type&, std::string_view name) override {
+ this->addElement();
+ fCodeGen->write(" sampler2D ");
+ fCodeGen->writeName(name);
+ fCodeGen->write(";\n");
+ }
+ void visitConstantVariable(const VarDeclaration& decl) override {
+ // Constants aren't added to the global struct.
+ }
+ void visitNonconstantVariable(const Variable& var, const Expression* value) override {
+ this->addElement();
+ fCodeGen->write(" ");
+ fCodeGen->writeModifiers(var.modifiers());
+ fCodeGen->writeType(var.type());
+ fCodeGen->write(" ");
+ fCodeGen->writeName(var.mangledName());
+ fCodeGen->write(";\n");
+ }
+ void addElement() {
+ if (fFirst) {
+ fCodeGen->write("struct Globals {\n");
+ fFirst = false;
+ }
+ }
+ void finish() {
+ if (!fFirst) {
+ fCodeGen->writeLine("};");
+ fFirst = true;
+ }
+ }
+
+ MetalCodeGenerator* fCodeGen = nullptr;
+ bool fFirst = true;
+ } visitor;
+
+ visitor.fCodeGen = this;
+ this->visitGlobalStruct(&visitor);
+ visitor.finish();
+}
+
+void MetalCodeGenerator::writeGlobalInit() {
+ class : public GlobalStructVisitor {
+ public:
+ void visitInterfaceBlock(const InterfaceBlock& blockType,
+ std::string_view blockName) override {
+ this->addElement();
+ fCodeGen->write("&");
+ fCodeGen->writeName(blockName);
+ }
+ void visitTexture(const Type&, const Modifiers& modifiers, std::string_view name) override {
+ this->addElement();
+ fCodeGen->writeName(name);
+ }
+ void visitSampler(const Type&, std::string_view name) override {
+ this->addElement();
+ fCodeGen->write("{");
+ fCodeGen->writeName(name);
+ fCodeGen->write(kTextureSuffix);
+ fCodeGen->write(", ");
+ fCodeGen->writeName(name);
+ fCodeGen->write(kSamplerSuffix);
+ fCodeGen->write("}");
+ }
+ void visitConstantVariable(const VarDeclaration& decl) override {
+ // Constant-expression variables aren't put in the global struct.
+ }
+ void visitNonconstantVariable(const Variable& var, const Expression* value) override {
+ this->addElement();
+ if (value) {
+ fCodeGen->writeVarInitializer(var, *value);
+ } else {
+ fCodeGen->write("{}");
+ }
+ }
+ void addElement() {
+ if (fFirst) {
+ fCodeGen->write("Globals _globals{");
+ fFirst = false;
+ } else {
+ fCodeGen->write(", ");
+ }
+ }
+ void finish() {
+ if (!fFirst) {
+ fCodeGen->writeLine("};");
+ fCodeGen->writeLine("(void)_globals;");
+ }
+ }
+ MetalCodeGenerator* fCodeGen = nullptr;
+ bool fFirst = true;
+ } visitor;
+
+ visitor.fCodeGen = this;
+ this->visitGlobalStruct(&visitor);
+ visitor.finish();
+}
+
+void MetalCodeGenerator::visitThreadgroupStruct(ThreadgroupStructVisitor* visitor) {
+ for (const ProgramElement* element : fProgram.elements()) {
+ if (!element->is<GlobalVarDeclaration>()) {
+ continue;
+ }
+ const GlobalVarDeclaration& global = element->as<GlobalVarDeclaration>();
+ const VarDeclaration& decl = global.varDeclaration();
+ const Variable& var = *decl.var();
+ if (var.modifiers().fFlags & Modifiers::kWorkgroup_Flag) {
+ SkASSERT(!decl.value());
+ SkASSERT(!(var.modifiers().fFlags & Modifiers::kConst_Flag));
+ visitor->visitNonconstantVariable(var);
+ }
+ }
+}
+
+void MetalCodeGenerator::writeThreadgroupStruct() {
+ class : public ThreadgroupStructVisitor {
+ public:
+ void visitNonconstantVariable(const Variable& var) override {
+ this->addElement();
+ fCodeGen->write(" ");
+ fCodeGen->writeModifiers(var.modifiers());
+ fCodeGen->writeType(var.type());
+ fCodeGen->write(" ");
+ fCodeGen->writeName(var.mangledName());
+ fCodeGen->write(";\n");
+ }
+ void addElement() {
+ if (fFirst) {
+ fCodeGen->write("struct Threadgroups {\n");
+ fFirst = false;
+ }
+ }
+ void finish() {
+ if (!fFirst) {
+ fCodeGen->writeLine("};");
+ fFirst = true;
+ }
+ }
+
+ MetalCodeGenerator* fCodeGen = nullptr;
+ bool fFirst = true;
+ } visitor;
+
+ visitor.fCodeGen = this;
+ this->visitThreadgroupStruct(&visitor);
+ visitor.finish();
+}
+
+void MetalCodeGenerator::writeThreadgroupInit() {
+ class : public ThreadgroupStructVisitor {
+ public:
+ void visitNonconstantVariable(const Variable& var) override {
+ this->addElement();
+ fCodeGen->write("{}");
+ }
+ void addElement() {
+ if (fFirst) {
+ fCodeGen->write("threadgroup Threadgroups _threadgroups{");
+ fFirst = false;
+ } else {
+ fCodeGen->write(", ");
+ }
+ }
+ void finish() {
+ if (!fFirst) {
+ fCodeGen->writeLine("};");
+ fCodeGen->writeLine("(void)_threadgroups;");
+ }
+ }
+ MetalCodeGenerator* fCodeGen = nullptr;
+ bool fFirst = true;
+ } visitor;
+
+ visitor.fCodeGen = this;
+ this->visitThreadgroupStruct(&visitor);
+ visitor.finish();
+}
+
+void MetalCodeGenerator::writeProgramElement(const ProgramElement& e) {
+ switch (e.kind()) {
+ case ProgramElement::Kind::kExtension:
+ break;
+ case ProgramElement::Kind::kGlobalVar:
+ break;
+ case ProgramElement::Kind::kInterfaceBlock:
+ // handled in writeInterfaceBlocks, do nothing
+ break;
+ case ProgramElement::Kind::kStructDefinition:
+ // Handled in writeStructDefinitions. Do nothing.
+ break;
+ case ProgramElement::Kind::kFunction:
+ this->writeFunction(e.as<FunctionDefinition>());
+ break;
+ case ProgramElement::Kind::kFunctionPrototype:
+ this->writeFunctionPrototype(e.as<FunctionPrototype>());
+ break;
+ case ProgramElement::Kind::kModifiers:
+ this->writeModifiers(e.as<ModifiersDeclaration>().modifiers());
+ this->writeLine(";");
+ break;
+ default:
+ SkDEBUGFAILF("unsupported program element: %s\n", e.description().c_str());
+ break;
+ }
+}
+
+MetalCodeGenerator::Requirements MetalCodeGenerator::requirements(const Statement* s) {
+ class RequirementsVisitor : public ProgramVisitor {
+ public:
+ using ProgramVisitor::visitStatement;
+
+ bool visitExpression(const Expression& e) override {
+ switch (e.kind()) {
+ case Expression::Kind::kFunctionCall: {
+ const FunctionCall& f = e.as<FunctionCall>();
+ fRequirements |= fCodeGen->requirements(f.function());
+ break;
+ }
+ case Expression::Kind::kFieldAccess: {
+ const FieldAccess& f = e.as<FieldAccess>();
+ if (f.ownerKind() == FieldAccess::OwnerKind::kAnonymousInterfaceBlock) {
+ fRequirements |= kGlobals_Requirement;
+ return false; // don't recurse into the base variable
+ }
+ break;
+ }
+ case Expression::Kind::kVariableReference: {
+ const Variable& var = *e.as<VariableReference>().variable();
+
+ if (var.modifiers().fLayout.fBuiltin == SK_FRAGCOORD_BUILTIN) {
+ fRequirements |= kGlobals_Requirement | kFragCoord_Requirement;
+ } else if (var.storage() == Variable::Storage::kGlobal) {
+ if (is_input(var)) {
+ fRequirements |= kInputs_Requirement;
+ } else if (is_output(var)) {
+ fRequirements |= kOutputs_Requirement;
+ } else if (is_uniforms(var)) {
+ fRequirements |= kUniforms_Requirement;
+ } else if (is_threadgroup(var)) {
+ fRequirements |= kThreadgroups_Requirement;
+ } else if (is_in_globals(var)) {
+ fRequirements |= kGlobals_Requirement;
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ return INHERITED::visitExpression(e);
+ }
+
+ MetalCodeGenerator* fCodeGen;
+ Requirements fRequirements = kNo_Requirements;
+ using INHERITED = ProgramVisitor;
+ };
+
+ RequirementsVisitor visitor;
+ if (s) {
+ visitor.fCodeGen = this;
+ visitor.visitStatement(*s);
+ }
+ return visitor.fRequirements;
+}
+
+MetalCodeGenerator::Requirements MetalCodeGenerator::requirements(const FunctionDeclaration& f) {
+ Requirements* found = fRequirements.find(&f);
+ if (!found) {
+ fRequirements.set(&f, kNo_Requirements);
+ for (const ProgramElement* e : fProgram.elements()) {
+ if (e->is<FunctionDefinition>()) {
+ const FunctionDefinition& def = e->as<FunctionDefinition>();
+ if (&def.declaration() == &f) {
+ Requirements reqs = this->requirements(def.body().get());
+ fRequirements.set(&f, reqs);
+ return reqs;
+ }
+ }
+ }
+ // We never found a definition for this declared function, but it's legal to prototype a
+ // function without ever giving a definition, as long as you don't call it.
+ return kNo_Requirements;
+ }
+ return *found;
+}
+
+bool MetalCodeGenerator::generateCode() {
+ StringStream header;
+ {
+ AutoOutputStream outputToHeader(this, &header, &fIndentation);
+ this->writeHeader();
+ this->writeConstantVariables();
+ this->writeSampler2DPolyfill();
+ this->writeStructDefinitions();
+ this->writeUniformStruct();
+ this->writeInputStruct();
+ if (!ProgramConfig::IsCompute(fProgram.fConfig->fKind)) {
+ this->writeOutputStruct();
+ }
+ this->writeInterfaceBlocks();
+ this->writeGlobalStruct();
+ this->writeThreadgroupStruct();
+
+ // Emit prototypes for every built-in function; these aren't always added in perfect order.
+ for (const ProgramElement* e : fProgram.fSharedElements) {
+ if (e->is<FunctionDefinition>()) {
+ this->writeFunctionDeclaration(e->as<FunctionDefinition>().declaration());
+ this->writeLine(";");
+ }
+ }
+ }
+ StringStream body;
+ {
+ AutoOutputStream outputToBody(this, &body, &fIndentation);
+
+ for (const ProgramElement* e : fProgram.elements()) {
+ this->writeProgramElement(*e);
+ }
+ }
+ write_stringstream(header, *fOut);
+ write_stringstream(fExtraFunctionPrototypes, *fOut);
+ write_stringstream(fExtraFunctions, *fOut);
+ write_stringstream(body, *fOut);
+ return fContext.fErrors->errorCount() == 0;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLMetalCodeGenerator.h b/gfx/skia/skia/src/sksl/codegen/SkSLMetalCodeGenerator.h
new file mode 100644
index 0000000000..621b99edb4
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/codegen/SkSLMetalCodeGenerator.h
@@ -0,0 +1,330 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_METALCODEGENERATOR
+#define SKSL_METALCODEGENERATOR
+
+#include "include/private/SkSLDefines.h"
+#include "include/private/base/SkTArray.h"
+#include "src/core/SkTHash.h"
+#include "src/sksl/SkSLStringStream.h"
+#include "src/sksl/codegen/SkSLCodeGenerator.h"
+#include "src/sksl/ir/SkSLType.h"
+
+#include <cstdint>
+#include <initializer_list>
+#include <string>
+#include <string_view>
+#include <vector>
+
+namespace SkSL {
+
+class AnyConstructor;
+class BinaryExpression;
+class Block;
+class ConstructorArrayCast;
+class ConstructorCompound;
+class ConstructorMatrixResize;
+class Context;
+class DoStatement;
+class Expression;
+class ExpressionStatement;
+class Extension;
+class FieldAccess;
+class ForStatement;
+class FunctionCall;
+class FunctionDeclaration;
+class FunctionDefinition;
+class FunctionPrototype;
+class IfStatement;
+class IndexExpression;
+class InterfaceBlock;
+class Literal;
+class Operator;
+class OutputStream;
+class Position;
+class PostfixExpression;
+class PrefixExpression;
+class ProgramElement;
+class ReturnStatement;
+class Statement;
+class StructDefinition;
+class SwitchStatement;
+class Swizzle;
+class TernaryExpression;
+class VarDeclaration;
+class Variable;
+class VariableReference;
+enum class OperatorPrecedence : uint8_t;
+enum IntrinsicKind : int8_t;
+struct Layout;
+struct Modifiers;
+struct Program;
+
+/**
+ * Converts a Program into Metal code.
+ */
+class MetalCodeGenerator : public CodeGenerator {
+public:
+ MetalCodeGenerator(const Context* context, const Program* program, OutputStream* out)
+ : INHERITED(context, program, out)
+ , fReservedWords({"atan2", "rsqrt", "rint", "dfdx", "dfdy", "vertex", "fragment"})
+ , fLineEnding("\n") {}
+
+ bool generateCode() override;
+
+protected:
+ using Precedence = OperatorPrecedence;
+
+ typedef int Requirements;
+ inline static constexpr Requirements kNo_Requirements = 0;
+ inline static constexpr Requirements kInputs_Requirement = 1 << 0;
+ inline static constexpr Requirements kOutputs_Requirement = 1 << 1;
+ inline static constexpr Requirements kUniforms_Requirement = 1 << 2;
+ inline static constexpr Requirements kGlobals_Requirement = 1 << 3;
+ inline static constexpr Requirements kFragCoord_Requirement = 1 << 4;
+ inline static constexpr Requirements kThreadgroups_Requirement = 1 << 5;
+
+ class GlobalStructVisitor;
+ void visitGlobalStruct(GlobalStructVisitor* visitor);
+
+ class ThreadgroupStructVisitor;
+ void visitThreadgroupStruct(ThreadgroupStructVisitor* visitor);
+
+ void write(std::string_view s);
+
+ void writeLine(std::string_view s = std::string_view());
+
+ void finishLine();
+
+ void writeHeader();
+
+ void writeSampler2DPolyfill();
+
+ void writeUniformStruct();
+
+ void writeInputStruct();
+
+ void writeOutputStruct();
+
+ void writeInterfaceBlocks();
+
+ void writeStructDefinitions();
+
+ void writeConstantVariables();
+
+ void writeFields(const std::vector<Type::Field>& fields, Position pos,
+ const InterfaceBlock* parentIntf = nullptr);
+
+ int size(const Type* type, bool isPacked) const;
+
+ int alignment(const Type* type, bool isPacked) const;
+
+ void writeGlobalStruct();
+
+ void writeGlobalInit();
+
+ void writeThreadgroupStruct();
+
+ void writeThreadgroupInit();
+
+ void writePrecisionModifier();
+
+ std::string typeName(const Type& type);
+
+ void writeStructDefinition(const StructDefinition& s);
+
+ void writeType(const Type& type);
+
+ void writeExtension(const Extension& ext);
+
+ void writeInterfaceBlock(const InterfaceBlock& intf);
+
+ void writeFunctionRequirementParams(const FunctionDeclaration& f,
+ const char*& separator);
+
+ void writeFunctionRequirementArgs(const FunctionDeclaration& f, const char*& separator);
+
+ bool writeFunctionDeclaration(const FunctionDeclaration& f);
+
+ void writeFunction(const FunctionDefinition& f);
+
+ void writeFunctionPrototype(const FunctionPrototype& f);
+
+ void writeLayout(const Layout& layout);
+
+ void writeModifiers(const Modifiers& modifiers);
+
+ void writeVarInitializer(const Variable& var, const Expression& value);
+
+ void writeName(std::string_view name);
+
+ void writeVarDeclaration(const VarDeclaration& decl);
+
+ void writeFragCoord();
+
+ void writeVariableReference(const VariableReference& ref);
+
+ void writeExpression(const Expression& expr, Precedence parentPrecedence);
+
+ void writeMinAbsHack(Expression& absExpr, Expression& otherExpr);
+
+ std::string getOutParamHelper(const FunctionCall& c,
+ const ExpressionArray& arguments,
+ const SkTArray<VariableReference*>& outVars);
+
+ std::string getInversePolyfill(const ExpressionArray& arguments);
+
+ std::string getBitcastIntrinsic(const Type& outType);
+
+ std::string getTempVariable(const Type& varType);
+
+ void writeFunctionCall(const FunctionCall& c);
+
+ bool matrixConstructHelperIsNeeded(const ConstructorCompound& c);
+ std::string getMatrixConstructHelper(const AnyConstructor& c);
+ void assembleMatrixFromMatrix(const Type& sourceMatrix, int rows, int columns);
+ void assembleMatrixFromExpressions(const AnyConstructor& ctor, int rows, int columns);
+
+ void writeMatrixCompMult();
+
+ void writeOuterProduct();
+
+ void writeMatrixTimesEqualHelper(const Type& left, const Type& right, const Type& result);
+
+ void writeMatrixDivisionHelpers(const Type& type);
+
+ void writeMatrixEqualityHelpers(const Type& left, const Type& right);
+
+ std::string getVectorFromMat2x2ConstructorHelper(const Type& matrixType);
+
+ void writeArrayEqualityHelpers(const Type& type);
+
+ void writeStructEqualityHelpers(const Type& type);
+
+ void writeEqualityHelpers(const Type& leftType, const Type& rightType);
+
+ void writeArgumentList(const ExpressionArray& arguments);
+
+ void writeSimpleIntrinsic(const FunctionCall& c);
+
+ bool writeIntrinsicCall(const FunctionCall& c, IntrinsicKind kind);
+
+ void writeConstructorCompound(const ConstructorCompound& c, Precedence parentPrecedence);
+
+ void writeConstructorCompoundVector(const ConstructorCompound& c, Precedence parentPrecedence);
+
+ void writeConstructorCompoundMatrix(const ConstructorCompound& c, Precedence parentPrecedence);
+
+ void writeConstructorMatrixResize(const ConstructorMatrixResize& c,
+ Precedence parentPrecedence);
+
+ void writeAnyConstructor(const AnyConstructor& c,
+ const char* leftBracket,
+ const char* rightBracket,
+ Precedence parentPrecedence);
+
+ void writeCastConstructor(const AnyConstructor& c,
+ const char* leftBracket,
+ const char* rightBracket,
+ Precedence parentPrecedence);
+
+ void writeConstructorArrayCast(const ConstructorArrayCast& c, Precedence parentPrecedence);
+
+ void writeFieldAccess(const FieldAccess& f);
+
+ void writeSwizzle(const Swizzle& swizzle);
+
+ // Splats a scalar expression across a matrix of arbitrary size.
+ void writeNumberAsMatrix(const Expression& expr, const Type& matrixType);
+
+ void writeBinaryExpressionElement(const Expression& expr,
+ Operator op,
+ const Expression& other,
+ Precedence precedence);
+
+ void writeBinaryExpression(const BinaryExpression& b, Precedence parentPrecedence);
+
+ void writeTernaryExpression(const TernaryExpression& t, Precedence parentPrecedence);
+
+ void writeIndexExpression(const IndexExpression& expr);
+
+ void writePrefixExpression(const PrefixExpression& p, Precedence parentPrecedence);
+
+ void writePostfixExpression(const PostfixExpression& p, Precedence parentPrecedence);
+
+ void writeLiteral(const Literal& f);
+
+ void writeStatement(const Statement& s);
+
+ void writeStatements(const StatementArray& statements);
+
+ void writeBlock(const Block& b);
+
+ void writeIfStatement(const IfStatement& stmt);
+
+ void writeForStatement(const ForStatement& f);
+
+ void writeDoStatement(const DoStatement& d);
+
+ void writeExpressionStatement(const ExpressionStatement& s);
+
+ void writeSwitchStatement(const SwitchStatement& s);
+
+ void writeReturnStatementFromMain();
+
+ void writeReturnStatement(const ReturnStatement& r);
+
+ void writeProgramElement(const ProgramElement& e);
+
+ Requirements requirements(const FunctionDeclaration& f);
+
+ Requirements requirements(const Statement* s);
+
+ // For compute shader main functions, writes and initializes the _in and _out structs (the
+ // instances, not the types themselves)
+ void writeComputeMainInputs();
+
+ int getUniformBinding(const Modifiers& m);
+
+ int getUniformSet(const Modifiers& m);
+
+ SkTHashSet<std::string_view> fReservedWords;
+ SkTHashMap<const Type::Field*, const InterfaceBlock*> fInterfaceBlockMap;
+ SkTHashMap<const InterfaceBlock*, std::string_view> fInterfaceBlockNameMap;
+ int fAnonInterfaceCount = 0;
+ int fPaddingCount = 0;
+ const char* fLineEnding;
+ std::string fFunctionHeader;
+ StringStream fExtraFunctions;
+ StringStream fExtraFunctionPrototypes;
+ int fVarCount = 0;
+ int fIndentation = 0;
+ bool fAtLineStart = false;
+ // true if we have run into usages of dFdx / dFdy
+ bool fFoundDerivatives = false;
+ SkTHashMap<const FunctionDeclaration*, Requirements> fRequirements;
+ SkTHashSet<std::string> fHelpers;
+ int fUniformBuffer = -1;
+ std::string fRTFlipName;
+ const FunctionDeclaration* fCurrentFunction = nullptr;
+ int fSwizzleHelperCount = 0;
+ bool fIgnoreVariableReferenceModifiers = false;
+ static constexpr char kTextureSuffix[] = "_Tex";
+ static constexpr char kSamplerSuffix[] = "_Smplr";
+
+ // Workaround/polyfill flags
+ bool fWrittenInverse2 = false, fWrittenInverse3 = false, fWrittenInverse4 = false;
+ bool fWrittenMatrixCompMult = false;
+ bool fWrittenOuterProduct = false;
+
+ using INHERITED = CodeGenerator;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLPipelineStageCodeGenerator.cpp b/gfx/skia/skia/src/sksl/codegen/SkSLPipelineStageCodeGenerator.cpp
new file mode 100644
index 0000000000..20466a922d
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/codegen/SkSLPipelineStageCodeGenerator.cpp
@@ -0,0 +1,814 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/codegen/SkSLPipelineStageCodeGenerator.h"
+
+#if defined(SKSL_STANDALONE) || defined(SK_GANESH) || defined(SK_GRAPHITE)
+
+#include "include/core/SkSpan.h"
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLLayout.h"
+#include "include/private/SkSLModifiers.h"
+#include "include/private/SkSLProgramElement.h"
+#include "include/private/SkSLProgramKind.h"
+#include "include/private/SkSLStatement.h"
+#include "include/private/SkSLString.h"
+#include "include/private/base/SkTArray.h"
+#include "include/sksl/SkSLOperator.h"
+#include "src/core/SkTHash.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLIntrinsicList.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/SkSLStringStream.h"
+#include "src/sksl/ir/SkSLBinaryExpression.h"
+#include "src/sksl/ir/SkSLBlock.h"
+#include "src/sksl/ir/SkSLChildCall.h"
+#include "src/sksl/ir/SkSLConstructor.h"
+#include "src/sksl/ir/SkSLDoStatement.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLExpressionStatement.h"
+#include "src/sksl/ir/SkSLFieldAccess.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/SkSLPostfixExpression.h"
+#include "src/sksl/ir/SkSLPrefixExpression.h"
+#include "src/sksl/ir/SkSLProgram.h"
+#include "src/sksl/ir/SkSLReturnStatement.h"
+#include "src/sksl/ir/SkSLStructDefinition.h"
+#include "src/sksl/ir/SkSLSwitchCase.h"
+#include "src/sksl/ir/SkSLSwitchStatement.h"
+#include "src/sksl/ir/SkSLSwizzle.h"
+#include "src/sksl/ir/SkSLTernaryExpression.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 <memory>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace SkSL {
+namespace PipelineStage {
+
+class PipelineStageCodeGenerator {
+public:
+ PipelineStageCodeGenerator(const Program& program,
+ const char* sampleCoords,
+ const char* inputColor,
+ const char* destColor,
+ Callbacks* callbacks)
+ : fProgram(program)
+ , fSampleCoords(sampleCoords)
+ , fInputColor(inputColor)
+ , fDestColor(destColor)
+ , fCallbacks(callbacks) {}
+
+ void generateCode();
+
+private:
+ using Precedence = OperatorPrecedence;
+
+ void write(std::string_view s);
+ void writeLine(std::string_view s = std::string_view());
+
+ std::string typeName(const Type& type);
+ void writeType(const Type& type);
+
+ std::string functionName(const FunctionDeclaration& decl);
+ void writeFunction(const FunctionDefinition& f);
+ void writeFunctionDeclaration(const FunctionDeclaration& decl);
+
+ std::string modifierString(const Modifiers& modifiers);
+ std::string functionDeclaration(const FunctionDeclaration& decl);
+
+ // Handles arrays correctly, eg: `float x[2]`
+ std::string typedVariable(const Type& type, std::string_view name);
+
+ void writeVarDeclaration(const VarDeclaration& var);
+ void writeGlobalVarDeclaration(const GlobalVarDeclaration& g);
+ void writeStructDefinition(const StructDefinition& s);
+
+ void writeExpression(const Expression& expr, Precedence parentPrecedence);
+ void writeChildCall(const ChildCall& c);
+ void writeFunctionCall(const FunctionCall& c);
+ void writeAnyConstructor(const AnyConstructor& c, Precedence parentPrecedence);
+ void writeFieldAccess(const FieldAccess& f);
+ void writeSwizzle(const Swizzle& swizzle);
+ void writeBinaryExpression(const BinaryExpression& b, Precedence parentPrecedence);
+ void writeTernaryExpression(const TernaryExpression& t, Precedence parentPrecedence);
+ void writeIndexExpression(const IndexExpression& expr);
+ void writePrefixExpression(const PrefixExpression& p, Precedence parentPrecedence);
+ void writePostfixExpression(const PostfixExpression& p, Precedence parentPrecedence);
+ void writeVariableReference(const VariableReference& ref);
+
+ void writeStatement(const Statement& s);
+ void writeBlock(const Block& b);
+ void writeIfStatement(const IfStatement& stmt);
+ void writeDoStatement(const DoStatement& d);
+ void writeForStatement(const ForStatement& f);
+ void writeReturnStatement(const ReturnStatement& r);
+ void writeSwitchStatement(const SwitchStatement& s);
+
+ void writeProgramElementFirstPass(const ProgramElement& e);
+ void writeProgramElementSecondPass(const ProgramElement& e);
+
+ struct AutoOutputBuffer {
+ AutoOutputBuffer(PipelineStageCodeGenerator* generator) : fGenerator(generator) {
+ fOldBuffer = fGenerator->fBuffer;
+ fGenerator->fBuffer = &fBuffer;
+ }
+
+ ~AutoOutputBuffer() {
+ fGenerator->fBuffer = fOldBuffer;
+ }
+
+ PipelineStageCodeGenerator* fGenerator;
+ StringStream* fOldBuffer;
+ StringStream fBuffer;
+ };
+
+ const Program& fProgram;
+ const char* fSampleCoords;
+ const char* fInputColor;
+ const char* fDestColor;
+ Callbacks* fCallbacks;
+
+ SkTHashMap<const Variable*, std::string> fVariableNames;
+ SkTHashMap<const FunctionDeclaration*, std::string> fFunctionNames;
+ SkTHashMap<const Type*, std::string> fStructNames;
+
+ StringStream* fBuffer = nullptr;
+ bool fCastReturnsToHalf = false;
+};
+
+
+void PipelineStageCodeGenerator::write(std::string_view s) {
+ fBuffer->write(s.data(), s.length());
+}
+
+void PipelineStageCodeGenerator::writeLine(std::string_view s) {
+ fBuffer->write(s.data(), s.length());
+ fBuffer->writeText("\n");
+}
+
+void PipelineStageCodeGenerator::writeChildCall(const ChildCall& c) {
+ const ExpressionArray& arguments = c.arguments();
+ SkASSERT(arguments.size() >= 1);
+ int index = 0;
+ bool found = false;
+ for (const ProgramElement* p : fProgram.elements()) {
+ if (p->is<GlobalVarDeclaration>()) {
+ const GlobalVarDeclaration& global = p->as<GlobalVarDeclaration>();
+ const VarDeclaration& decl = global.varDeclaration();
+ if (decl.var() == &c.child()) {
+ found = true;
+ } else if (decl.var()->type().isEffectChild()) {
+ ++index;
+ }
+ }
+ if (found) {
+ break;
+ }
+ }
+ SkASSERT(found);
+
+ // Shaders require a coordinate argument. Color filters require a color argument.
+ // Blenders require two color arguments.
+ std::string sampleOutput;
+ {
+ AutoOutputBuffer exprBuffer(this);
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+
+ switch (c.child().type().typeKind()) {
+ case Type::TypeKind::kShader: {
+ SkASSERT(arguments.size() == 1);
+ SkASSERT(arguments[0]->type().matches(*fProgram.fContext->fTypes.fFloat2));
+ sampleOutput = fCallbacks->sampleShader(index, exprBuffer.fBuffer.str());
+ break;
+ }
+ case Type::TypeKind::kColorFilter: {
+ SkASSERT(arguments.size() == 1);
+ SkASSERT(arguments[0]->type().matches(*fProgram.fContext->fTypes.fHalf4) ||
+ arguments[0]->type().matches(*fProgram.fContext->fTypes.fFloat4));
+ sampleOutput = fCallbacks->sampleColorFilter(index, exprBuffer.fBuffer.str());
+ break;
+ }
+ case Type::TypeKind::kBlender: {
+ SkASSERT(arguments.size() == 2);
+ SkASSERT(arguments[0]->type().matches(*fProgram.fContext->fTypes.fHalf4) ||
+ arguments[0]->type().matches(*fProgram.fContext->fTypes.fFloat4));
+ SkASSERT(arguments[1]->type().matches(*fProgram.fContext->fTypes.fHalf4) ||
+ arguments[1]->type().matches(*fProgram.fContext->fTypes.fFloat4));
+
+ AutoOutputBuffer exprBuffer2(this);
+ this->writeExpression(*arguments[1], Precedence::kSequence);
+
+ sampleOutput = fCallbacks->sampleBlender(index, exprBuffer.fBuffer.str(),
+ exprBuffer2.fBuffer.str());
+ break;
+ }
+ default: {
+ SkDEBUGFAILF("cannot sample from type '%s'",
+ c.child().type().description().c_str());
+ }
+ }
+ }
+ this->write(sampleOutput);
+ return;
+}
+
+void PipelineStageCodeGenerator::writeFunctionCall(const FunctionCall& c) {
+ const FunctionDeclaration& function = c.function();
+
+ if (function.intrinsicKind() == IntrinsicKind::k_toLinearSrgb_IntrinsicKind ||
+ function.intrinsicKind() == IntrinsicKind::k_fromLinearSrgb_IntrinsicKind) {
+ SkASSERT(c.arguments().size() == 1);
+ std::string colorArg;
+ {
+ AutoOutputBuffer exprBuffer(this);
+ this->writeExpression(*c.arguments()[0], Precedence::kSequence);
+ colorArg = exprBuffer.fBuffer.str();
+ }
+
+ switch (function.intrinsicKind()) {
+ case IntrinsicKind::k_toLinearSrgb_IntrinsicKind:
+ this->write(fCallbacks->toLinearSrgb(std::move(colorArg)));
+ break;
+ case IntrinsicKind::k_fromLinearSrgb_IntrinsicKind:
+ this->write(fCallbacks->fromLinearSrgb(std::move(colorArg)));
+ break;
+ default:
+ SkUNREACHABLE;
+ }
+
+ return;
+ }
+
+ if (function.isBuiltin()) {
+ this->write(function.name());
+ } else {
+ this->write(this->functionName(function));
+ }
+
+ this->write("(");
+ auto separator = SkSL::String::Separator();
+ for (const auto& arg : c.arguments()) {
+ this->write(separator());
+ this->writeExpression(*arg, Precedence::kSequence);
+ }
+ this->write(")");
+}
+
+void PipelineStageCodeGenerator::writeVariableReference(const VariableReference& ref) {
+ const Variable* var = ref.variable();
+ const Modifiers& modifiers = var->modifiers();
+
+ if (modifiers.fLayout.fBuiltin == SK_MAIN_COORDS_BUILTIN) {
+ this->write(fSampleCoords);
+ return;
+ } else if (modifiers.fLayout.fBuiltin == SK_INPUT_COLOR_BUILTIN) {
+ this->write(fInputColor);
+ return;
+ } else if (modifiers.fLayout.fBuiltin == SK_DEST_COLOR_BUILTIN) {
+ this->write(fDestColor);
+ return;
+ }
+
+ std::string* name = fVariableNames.find(var);
+ this->write(name ? *name : var->name());
+}
+
+void PipelineStageCodeGenerator::writeIfStatement(const IfStatement& stmt) {
+ this->write("if (");
+ this->writeExpression(*stmt.test(), Precedence::kTopLevel);
+ this->write(") ");
+ this->writeStatement(*stmt.ifTrue());
+ if (stmt.ifFalse()) {
+ this->write(" else ");
+ this->writeStatement(*stmt.ifFalse());
+ }
+}
+
+void PipelineStageCodeGenerator::writeReturnStatement(const ReturnStatement& r) {
+ this->write("return");
+ if (r.expression()) {
+ this->write(" ");
+ if (fCastReturnsToHalf) {
+ this->write("half4(");
+ }
+ this->writeExpression(*r.expression(), Precedence::kTopLevel);
+ if (fCastReturnsToHalf) {
+ this->write(")");
+ }
+ }
+ this->write(";");
+}
+
+void PipelineStageCodeGenerator::writeSwitchStatement(const SwitchStatement& s) {
+ this->write("switch (");
+ this->writeExpression(*s.value(), Precedence::kTopLevel);
+ this->writeLine(") {");
+ for (const std::unique_ptr<Statement>& stmt : s.cases()) {
+ const SwitchCase& c = stmt->as<SwitchCase>();
+ if (c.isDefault()) {
+ this->writeLine("default:");
+ } else {
+ this->write("case ");
+ this->write(std::to_string(c.value()));
+ this->writeLine(":");
+ }
+ if (!c.statement()->isEmpty()) {
+ this->writeStatement(*c.statement());
+ this->writeLine();
+ }
+ }
+ this->writeLine();
+ this->write("}");
+}
+
+std::string PipelineStageCodeGenerator::functionName(const FunctionDeclaration& decl) {
+ if (decl.isMain()) {
+ return std::string(fCallbacks->getMainName());
+ }
+
+ std::string* name = fFunctionNames.find(&decl);
+ if (name) {
+ return *name;
+ }
+
+ std::string mangledName = fCallbacks->getMangledName(std::string(decl.name()).c_str());
+ fFunctionNames.set(&decl, mangledName);
+ return mangledName;
+}
+
+void PipelineStageCodeGenerator::writeFunction(const FunctionDefinition& f) {
+ if (f.declaration().isBuiltin()) {
+ // Don't re-emit builtin functions.
+ return;
+ }
+
+ AutoOutputBuffer body(this);
+
+ // We allow public SkSL's main() to return half4 -or- float4 (ie vec4). When we emit
+ // our code in the processor, the surrounding code is going to expect half4, so we
+ // explicitly cast any returns (from main) to half4. This is only strictly necessary
+ // if the return type is float4 - injecting it unconditionally reduces the risk of an
+ // obscure bug.
+ const FunctionDeclaration& decl = f.declaration();
+ if (decl.isMain() &&
+ fProgram.fConfig->fKind != SkSL::ProgramKind::kMeshVertex &&
+ fProgram.fConfig->fKind != SkSL::ProgramKind::kMeshFragment) {
+ fCastReturnsToHalf = true;
+ }
+
+ for (const std::unique_ptr<Statement>& stmt : f.body()->as<Block>().children()) {
+ this->writeStatement(*stmt);
+ this->writeLine();
+ }
+
+ if (decl.isMain()) {
+ fCastReturnsToHalf = false;
+ }
+
+ fCallbacks->defineFunction(this->functionDeclaration(decl).c_str(),
+ body.fBuffer.str().c_str(),
+ decl.isMain());
+}
+
+std::string PipelineStageCodeGenerator::functionDeclaration(const FunctionDeclaration& decl) {
+ // This is similar to decl.description(), but substitutes a mangled name, and handles modifiers
+ // on the function (e.g. `inline`) and its parameters (e.g. `inout`).
+ std::string declString =
+ String::printf("%s%s%s %s(",
+ (decl.modifiers().fFlags & Modifiers::kInline_Flag) ? "inline " : "",
+ (decl.modifiers().fFlags & Modifiers::kNoInline_Flag) ? "noinline " : "",
+ this->typeName(decl.returnType()).c_str(),
+ this->functionName(decl).c_str());
+ auto separator = SkSL::String::Separator();
+ for (const Variable* p : decl.parameters()) {
+ declString.append(separator());
+ declString.append(this->modifierString(p->modifiers()));
+ declString.append(this->typedVariable(p->type(), p->name()).c_str());
+ }
+
+ return declString + ")";
+}
+
+void PipelineStageCodeGenerator::writeFunctionDeclaration(const FunctionDeclaration& decl) {
+ if (!decl.isMain() && !decl.isBuiltin()) {
+ fCallbacks->declareFunction(this->functionDeclaration(decl).c_str());
+ }
+}
+
+void PipelineStageCodeGenerator::writeGlobalVarDeclaration(const GlobalVarDeclaration& g) {
+ const VarDeclaration& decl = g.varDeclaration();
+ const Variable& var = *decl.var();
+
+ if (var.isBuiltin() || var.type().isOpaque()) {
+ // Don't re-declare these. (eg, sk_FragCoord, or fragmentProcessor children)
+ } else if (var.modifiers().fFlags & Modifiers::kUniform_Flag) {
+ std::string uniformName = fCallbacks->declareUniform(&decl);
+ fVariableNames.set(&var, std::move(uniformName));
+ } else {
+ std::string mangledName = fCallbacks->getMangledName(std::string(var.name()).c_str());
+ std::string declaration = this->modifierString(var.modifiers()) +
+ this->typedVariable(var.type(),
+ std::string_view(mangledName.c_str()));
+ if (decl.value()) {
+ AutoOutputBuffer outputToBuffer(this);
+ this->writeExpression(*decl.value(), Precedence::kTopLevel);
+ declaration += " = ";
+ declaration += outputToBuffer.fBuffer.str();
+ }
+ declaration += ";\n";
+ fCallbacks->declareGlobal(declaration.c_str());
+ fVariableNames.set(&var, std::move(mangledName));
+ }
+}
+
+void PipelineStageCodeGenerator::writeStructDefinition(const StructDefinition& s) {
+ const Type& type = s.type();
+ std::string mangledName = fCallbacks->getMangledName(type.displayName().c_str());
+ std::string definition = "struct " + mangledName + " {\n";
+ for (const auto& f : type.fields()) {
+ definition += this->typedVariable(*f.fType, f.fName) + ";\n";
+ }
+ definition += "};\n";
+ fStructNames.set(&type, std::move(mangledName));
+ fCallbacks->defineStruct(definition.c_str());
+}
+
+void PipelineStageCodeGenerator::writeProgramElementFirstPass(const ProgramElement& e) {
+ switch (e.kind()) {
+ case ProgramElement::Kind::kGlobalVar:
+ this->writeGlobalVarDeclaration(e.as<GlobalVarDeclaration>());
+ break;
+ case ProgramElement::Kind::kFunction:
+ this->writeFunctionDeclaration(e.as<FunctionDefinition>().declaration());
+ break;
+ case ProgramElement::Kind::kFunctionPrototype:
+ // Skip this; we're already emitting prototypes for every FunctionDefinition.
+ // (See case kFunction, directly above.)
+ break;
+ case ProgramElement::Kind::kStructDefinition:
+ this->writeStructDefinition(e.as<StructDefinition>());
+ break;
+
+ case ProgramElement::Kind::kExtension:
+ case ProgramElement::Kind::kInterfaceBlock:
+ case ProgramElement::Kind::kModifiers:
+ default:
+ SkDEBUGFAILF("unsupported program element %s\n", e.description().c_str());
+ break;
+ }
+}
+
+void PipelineStageCodeGenerator::writeProgramElementSecondPass(const ProgramElement& e) {
+ if (e.is<FunctionDefinition>()) {
+ this->writeFunction(e.as<FunctionDefinition>());
+ }
+}
+
+std::string PipelineStageCodeGenerator::typeName(const Type& raw) {
+ const Type& type = raw.resolve();
+ if (type.isArray()) {
+ // This is necessary so that name mangling on arrays-of-structs works properly.
+ std::string arrayName = this->typeName(type.componentType());
+ arrayName.push_back('[');
+ arrayName += std::to_string(type.columns());
+ arrayName.push_back(']');
+ return arrayName;
+ }
+
+ std::string* name = fStructNames.find(&type);
+ return name ? *name : std::string(type.name());
+}
+
+void PipelineStageCodeGenerator::writeType(const Type& type) {
+ this->write(this->typeName(type));
+}
+
+void PipelineStageCodeGenerator::writeExpression(const Expression& expr,
+ Precedence parentPrecedence) {
+ switch (expr.kind()) {
+ case Expression::Kind::kBinary:
+ this->writeBinaryExpression(expr.as<BinaryExpression>(), parentPrecedence);
+ break;
+ case Expression::Kind::kLiteral:
+ this->write(expr.description());
+ break;
+ case Expression::Kind::kChildCall:
+ this->writeChildCall(expr.as<ChildCall>());
+ break;
+ case Expression::Kind::kConstructorArray:
+ case Expression::Kind::kConstructorArrayCast:
+ case Expression::Kind::kConstructorCompound:
+ case Expression::Kind::kConstructorCompoundCast:
+ case Expression::Kind::kConstructorDiagonalMatrix:
+ case Expression::Kind::kConstructorMatrixResize:
+ case Expression::Kind::kConstructorScalarCast:
+ case Expression::Kind::kConstructorSplat:
+ case Expression::Kind::kConstructorStruct:
+ this->writeAnyConstructor(expr.asAnyConstructor(), parentPrecedence);
+ break;
+ case Expression::Kind::kFieldAccess:
+ this->writeFieldAccess(expr.as<FieldAccess>());
+ break;
+ case Expression::Kind::kFunctionCall:
+ this->writeFunctionCall(expr.as<FunctionCall>());
+ break;
+ case Expression::Kind::kPrefix:
+ this->writePrefixExpression(expr.as<PrefixExpression>(), parentPrecedence);
+ break;
+ case Expression::Kind::kPostfix:
+ this->writePostfixExpression(expr.as<PostfixExpression>(), parentPrecedence);
+ break;
+ case Expression::Kind::kSwizzle:
+ this->writeSwizzle(expr.as<Swizzle>());
+ break;
+ case Expression::Kind::kVariableReference:
+ this->writeVariableReference(expr.as<VariableReference>());
+ break;
+ case Expression::Kind::kTernary:
+ this->writeTernaryExpression(expr.as<TernaryExpression>(), parentPrecedence);
+ break;
+ case Expression::Kind::kIndex:
+ this->writeIndexExpression(expr.as<IndexExpression>());
+ break;
+ case Expression::Kind::kSetting:
+ default:
+ SkDEBUGFAILF("unsupported expression: %s", expr.description().c_str());
+ break;
+ }
+}
+
+void PipelineStageCodeGenerator::writeAnyConstructor(const AnyConstructor& c,
+ Precedence parentPrecedence) {
+ this->writeType(c.type());
+ this->write("(");
+ auto separator = SkSL::String::Separator();
+ for (const auto& arg : c.argumentSpan()) {
+ this->write(separator());
+ this->writeExpression(*arg, Precedence::kSequence);
+ }
+ this->write(")");
+}
+
+void PipelineStageCodeGenerator::writeIndexExpression(const IndexExpression& expr) {
+ this->writeExpression(*expr.base(), Precedence::kPostfix);
+ this->write("[");
+ this->writeExpression(*expr.index(), Precedence::kTopLevel);
+ this->write("]");
+}
+
+void PipelineStageCodeGenerator::writeFieldAccess(const FieldAccess& f) {
+ if (f.ownerKind() == FieldAccess::OwnerKind::kDefault) {
+ this->writeExpression(*f.base(), Precedence::kPostfix);
+ this->write(".");
+ }
+ const Type& baseType = f.base()->type();
+ this->write(baseType.fields()[f.fieldIndex()].fName);
+}
+
+void PipelineStageCodeGenerator::writeSwizzle(const Swizzle& swizzle) {
+ this->writeExpression(*swizzle.base(), Precedence::kPostfix);
+ this->write(".");
+ for (int c : swizzle.components()) {
+ SkASSERT(c >= 0 && c <= 3);
+ this->write(&("x\0y\0z\0w\0"[c * 2]));
+ }
+}
+
+void PipelineStageCodeGenerator::writeBinaryExpression(const BinaryExpression& b,
+ Precedence parentPrecedence) {
+ const Expression& left = *b.left();
+ const Expression& right = *b.right();
+ Operator op = b.getOperator();
+
+ Precedence precedence = op.getBinaryPrecedence();
+ if (precedence >= parentPrecedence) {
+ this->write("(");
+ }
+ this->writeExpression(left, precedence);
+ this->write(op.operatorName());
+ this->writeExpression(right, precedence);
+ if (precedence >= parentPrecedence) {
+ this->write(")");
+ }
+}
+
+void PipelineStageCodeGenerator::writeTernaryExpression(const TernaryExpression& t,
+ Precedence parentPrecedence) {
+ if (Precedence::kTernary >= parentPrecedence) {
+ this->write("(");
+ }
+ this->writeExpression(*t.test(), Precedence::kTernary);
+ this->write(" ? ");
+ this->writeExpression(*t.ifTrue(), Precedence::kTernary);
+ this->write(" : ");
+ this->writeExpression(*t.ifFalse(), Precedence::kTernary);
+ if (Precedence::kTernary >= parentPrecedence) {
+ this->write(")");
+ }
+}
+
+void PipelineStageCodeGenerator::writePrefixExpression(const PrefixExpression& p,
+ Precedence parentPrecedence) {
+ if (Precedence::kPrefix >= parentPrecedence) {
+ this->write("(");
+ }
+ this->write(p.getOperator().tightOperatorName());
+ this->writeExpression(*p.operand(), Precedence::kPrefix);
+ if (Precedence::kPrefix >= parentPrecedence) {
+ this->write(")");
+ }
+}
+
+void PipelineStageCodeGenerator::writePostfixExpression(const PostfixExpression& p,
+ Precedence parentPrecedence) {
+ if (Precedence::kPostfix >= parentPrecedence) {
+ this->write("(");
+ }
+ this->writeExpression(*p.operand(), Precedence::kPostfix);
+ this->write(p.getOperator().tightOperatorName());
+ if (Precedence::kPostfix >= parentPrecedence) {
+ this->write(")");
+ }
+}
+
+std::string PipelineStageCodeGenerator::modifierString(const Modifiers& modifiers) {
+ std::string result;
+ if (modifiers.fFlags & Modifiers::kConst_Flag) {
+ result.append("const ");
+ }
+
+ if ((modifiers.fFlags & Modifiers::kIn_Flag) && (modifiers.fFlags & Modifiers::kOut_Flag)) {
+ result.append("inout ");
+ } else if (modifiers.fFlags & Modifiers::kIn_Flag) {
+ result.append("in ");
+ } else if (modifiers.fFlags & Modifiers::kOut_Flag) {
+ result.append("out ");
+ }
+
+ return result;
+}
+
+std::string PipelineStageCodeGenerator::typedVariable(const Type& type, std::string_view name) {
+ const Type& baseType = type.isArray() ? type.componentType() : type;
+
+ std::string decl = this->typeName(baseType) + " " + std::string(name);
+ if (type.isArray()) {
+ decl += "[" + std::to_string(type.columns()) + "]";
+ }
+ return decl;
+}
+
+void PipelineStageCodeGenerator::writeVarDeclaration(const VarDeclaration& var) {
+ this->write(this->modifierString(var.var()->modifiers()));
+ this->write(this->typedVariable(var.var()->type(), var.var()->name()));
+ if (var.value()) {
+ this->write(" = ");
+ this->writeExpression(*var.value(), Precedence::kTopLevel);
+ }
+ this->write(";");
+}
+
+void PipelineStageCodeGenerator::writeStatement(const Statement& s) {
+ switch (s.kind()) {
+ case Statement::Kind::kBlock:
+ this->writeBlock(s.as<Block>());
+ break;
+ case Statement::Kind::kBreak:
+ this->write("break;");
+ break;
+ case Statement::Kind::kContinue:
+ this->write("continue;");
+ break;
+ case Statement::Kind::kExpression:
+ this->writeExpression(*s.as<ExpressionStatement>().expression(), Precedence::kTopLevel);
+ this->write(";");
+ break;
+ case Statement::Kind::kDo:
+ this->writeDoStatement(s.as<DoStatement>());
+ break;
+ case Statement::Kind::kFor:
+ this->writeForStatement(s.as<ForStatement>());
+ break;
+ case Statement::Kind::kIf:
+ this->writeIfStatement(s.as<IfStatement>());
+ break;
+ case Statement::Kind::kReturn:
+ this->writeReturnStatement(s.as<ReturnStatement>());
+ break;
+ case Statement::Kind::kSwitch:
+ this->writeSwitchStatement(s.as<SwitchStatement>());
+ break;
+ case Statement::Kind::kVarDeclaration:
+ this->writeVarDeclaration(s.as<VarDeclaration>());
+ break;
+ case Statement::Kind::kDiscard:
+ SkDEBUGFAIL("Unsupported control flow");
+ break;
+ case Statement::Kind::kNop:
+ this->write(";");
+ break;
+ default:
+ SkDEBUGFAILF("unsupported statement: %s", s.description().c_str());
+ break;
+ }
+}
+
+void PipelineStageCodeGenerator::writeBlock(const Block& b) {
+ // Write scope markers if this block is a scope, or if the block is empty (since we need to emit
+ // something here to make the code valid).
+ bool isScope = b.isScope() || b.isEmpty();
+ if (isScope) {
+ this->writeLine("{");
+ }
+ for (const std::unique_ptr<Statement>& stmt : b.children()) {
+ if (!stmt->isEmpty()) {
+ this->writeStatement(*stmt);
+ this->writeLine();
+ }
+ }
+ if (isScope) {
+ this->write("}");
+ }
+}
+
+void PipelineStageCodeGenerator::writeDoStatement(const DoStatement& d) {
+ this->write("do ");
+ this->writeStatement(*d.statement());
+ this->write(" while (");
+ this->writeExpression(*d.test(), Precedence::kTopLevel);
+ this->write(");");
+ return;
+}
+
+void PipelineStageCodeGenerator::writeForStatement(const ForStatement& f) {
+ // Emit loops of the form 'for(;test;)' as 'while(test)', which is probably how they started
+ if (!f.initializer() && f.test() && !f.next()) {
+ this->write("while (");
+ this->writeExpression(*f.test(), Precedence::kTopLevel);
+ this->write(") ");
+ this->writeStatement(*f.statement());
+ return;
+ }
+
+ this->write("for (");
+ if (f.initializer() && !f.initializer()->isEmpty()) {
+ this->writeStatement(*f.initializer());
+ } else {
+ this->write("; ");
+ }
+ if (f.test()) {
+ this->writeExpression(*f.test(), Precedence::kTopLevel);
+ }
+ this->write("; ");
+ if (f.next()) {
+ this->writeExpression(*f.next(), Precedence::kTopLevel);
+ }
+ this->write(") ");
+ this->writeStatement(*f.statement());
+}
+
+void PipelineStageCodeGenerator::generateCode() {
+ // Write all the program elements except for functions; prototype all the functions.
+ for (const ProgramElement* e : fProgram.elements()) {
+ this->writeProgramElementFirstPass(*e);
+ }
+
+ // We always place FunctionDefinition elements last, because the inliner likes to move function
+ // bodies around. After inlining, code can inadvertently move upwards, above ProgramElements
+ // that the code relies on.
+ for (const ProgramElement* e : fProgram.elements()) {
+ this->writeProgramElementSecondPass(*e);
+ }
+}
+
+void ConvertProgram(const Program& program,
+ const char* sampleCoords,
+ const char* inputColor,
+ const char* destColor,
+ Callbacks* callbacks) {
+ PipelineStageCodeGenerator generator(program, sampleCoords, inputColor, destColor, callbacks);
+ generator.generateCode();
+}
+
+} // namespace PipelineStage
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLPipelineStageCodeGenerator.h b/gfx/skia/skia/src/sksl/codegen/SkSLPipelineStageCodeGenerator.h
new file mode 100644
index 0000000000..7efb0e187e
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/codegen/SkSLPipelineStageCodeGenerator.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_PIPELINESTAGECODEGENERATOR
+#define SKSL_PIPELINESTAGECODEGENERATOR
+
+#include "include/core/SkTypes.h"
+
+#if defined(SKSL_STANDALONE) || defined(SK_GANESH) || defined(SK_GRAPHITE)
+
+#include <string>
+
+namespace SkSL {
+
+struct Program;
+class VarDeclaration;
+
+namespace PipelineStage {
+ class Callbacks {
+ public:
+ virtual ~Callbacks() = default;
+
+ virtual std::string getMainName() { return "main"; }
+ virtual std::string getMangledName(const char* name) { return name; }
+ virtual void defineFunction(const char* declaration, const char* body, bool isMain) = 0;
+ virtual void declareFunction(const char* declaration) = 0;
+ virtual void defineStruct(const char* definition) = 0;
+ virtual void declareGlobal(const char* declaration) = 0;
+
+ virtual std::string declareUniform(const VarDeclaration*) = 0;
+ virtual std::string sampleShader(int index, std::string coords) = 0;
+ virtual std::string sampleColorFilter(int index, std::string color) = 0;
+ virtual std::string sampleBlender(int index, std::string src, std::string dst) = 0;
+
+ virtual std::string toLinearSrgb(std::string color) = 0;
+ virtual std::string fromLinearSrgb(std::string color) = 0;
+ };
+
+ /*
+ * Processes 'program' for use in a GrFragmentProcessor, or other context that wants SkSL-like
+ * code as input. To support fragment processor usage, there are callbacks that allow elements
+ * to be declared programmatically and to rename those elements (mangling to avoid collisions).
+ *
+ * - Any reference to the main coords builtin variable will be replaced with 'sampleCoords'.
+ * - Any reference to the input color builtin variable will be replaced with 'inputColor'.
+ * - Any reference to the dest color builtin variable will be replaced with 'destColor'.
+ * Dest-color is used in blend programs.
+ * - Each uniform variable declaration triggers a call to 'declareUniform', which should emit
+ * the declaration, and return the (possibly different) name to use for the variable.
+ * - Each function definition triggers a call to 'defineFunction', which should emit the
+ * definition, and return the (possibly different) name to use for calls to that function.
+ * - Each invocation of sample() triggers a call to 'sampleChild', which should return the full
+ * text of the call expression.
+ */
+ void ConvertProgram(const Program& program,
+ const char* sampleCoords,
+ const char* inputColor,
+ const char* destColor,
+ Callbacks* callbacks);
+} // namespace PipelineStage
+
+} // namespace SkSL
+
+#endif
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineBuilder.cpp b/gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineBuilder.cpp
new file mode 100644
index 0000000000..48d9f26d74
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineBuilder.cpp
@@ -0,0 +1,2861 @@
+/*
+ * Copyright 2022 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/core/SkStream.h"
+#include "include/private/SkSLString.h"
+#include "include/private/base/SkMalloc.h"
+#include "include/private/base/SkTo.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/base/SkArenaAlloc.h"
+#include "src/core/SkOpts.h"
+#include "src/core/SkRasterPipelineOpContexts.h"
+#include "src/core/SkRasterPipelineOpList.h"
+#include "src/sksl/codegen/SkSLRasterPipelineBuilder.h"
+#include "src/sksl/tracing/SkRPDebugTrace.h"
+#include "src/sksl/tracing/SkSLDebugInfo.h"
+#include "src/utils/SkBitSet.h"
+
+#if !defined(SKSL_STANDALONE)
+#include "src/core/SkRasterPipeline.h"
+#endif
+
+#include <algorithm>
+#include <cmath>
+#include <cstring>
+#include <iterator>
+#include <string>
+#include <string_view>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+namespace SkSL {
+namespace RP {
+
+#define ALL_SINGLE_SLOT_UNARY_OP_CASES \
+ BuilderOp::acos_float: \
+ case BuilderOp::asin_float: \
+ case BuilderOp::atan_float: \
+ case BuilderOp::cos_float: \
+ case BuilderOp::exp_float: \
+ case BuilderOp::exp2_float: \
+ case BuilderOp::log_float: \
+ case BuilderOp::log2_float: \
+ case BuilderOp::sin_float: \
+ case BuilderOp::sqrt_float: \
+ case BuilderOp::tan_float
+
+#define ALL_MULTI_SLOT_UNARY_OP_CASES \
+ BuilderOp::abs_float: \
+ case BuilderOp::abs_int: \
+ case BuilderOp::bitwise_not_int: \
+ case BuilderOp::cast_to_float_from_int: \
+ case BuilderOp::cast_to_float_from_uint: \
+ case BuilderOp::cast_to_int_from_float: \
+ case BuilderOp::cast_to_uint_from_float: \
+ case BuilderOp::ceil_float: \
+ case BuilderOp::floor_float: \
+ case BuilderOp::invsqrt_float
+
+#define ALL_N_WAY_BINARY_OP_CASES \
+ BuilderOp::atan2_n_floats: \
+ case BuilderOp::pow_n_floats
+
+#define ALL_MULTI_SLOT_BINARY_OP_CASES \
+ BuilderOp::add_n_floats: \
+ case BuilderOp::add_n_ints: \
+ case BuilderOp::sub_n_floats: \
+ case BuilderOp::sub_n_ints: \
+ case BuilderOp::mul_n_floats: \
+ case BuilderOp::mul_n_ints: \
+ case BuilderOp::div_n_floats: \
+ case BuilderOp::div_n_ints: \
+ case BuilderOp::div_n_uints: \
+ case BuilderOp::bitwise_and_n_ints: \
+ case BuilderOp::bitwise_or_n_ints: \
+ case BuilderOp::bitwise_xor_n_ints: \
+ case BuilderOp::mod_n_floats: \
+ case BuilderOp::min_n_floats: \
+ case BuilderOp::min_n_ints: \
+ case BuilderOp::min_n_uints: \
+ case BuilderOp::max_n_floats: \
+ case BuilderOp::max_n_ints: \
+ case BuilderOp::max_n_uints: \
+ case BuilderOp::cmple_n_floats: \
+ case BuilderOp::cmple_n_ints: \
+ case BuilderOp::cmple_n_uints: \
+ case BuilderOp::cmplt_n_floats: \
+ case BuilderOp::cmplt_n_ints: \
+ case BuilderOp::cmplt_n_uints: \
+ case BuilderOp::cmpeq_n_floats: \
+ case BuilderOp::cmpeq_n_ints: \
+ case BuilderOp::cmpne_n_floats: \
+ case BuilderOp::cmpne_n_ints
+
+#define ALL_N_WAY_TERNARY_OP_CASES \
+ BuilderOp::smoothstep_n_floats
+
+#define ALL_MULTI_SLOT_TERNARY_OP_CASES \
+ BuilderOp::mix_n_floats: \
+ case BuilderOp::mix_n_ints
+
+void Builder::unary_op(BuilderOp op, int32_t slots) {
+ switch (op) {
+ case ALL_SINGLE_SLOT_UNARY_OP_CASES:
+ case ALL_MULTI_SLOT_UNARY_OP_CASES:
+ fInstructions.push_back({op, {}, slots});
+ break;
+
+ default:
+ SkDEBUGFAIL("not a unary op");
+ break;
+ }
+}
+
+void Builder::binary_op(BuilderOp op, int32_t slots) {
+ switch (op) {
+ case ALL_N_WAY_BINARY_OP_CASES:
+ case ALL_MULTI_SLOT_BINARY_OP_CASES:
+ fInstructions.push_back({op, {}, slots});
+ break;
+
+ default:
+ SkDEBUGFAIL("not a binary op");
+ break;
+ }
+}
+
+void Builder::ternary_op(BuilderOp op, int32_t slots) {
+ switch (op) {
+ case ALL_N_WAY_TERNARY_OP_CASES:
+ case ALL_MULTI_SLOT_TERNARY_OP_CASES:
+ fInstructions.push_back({op, {}, slots});
+ break;
+
+ default:
+ SkDEBUGFAIL("not a ternary op");
+ break;
+ }
+}
+
+void Builder::dot_floats(int32_t slots) {
+ switch (slots) {
+ case 1: fInstructions.push_back({BuilderOp::mul_n_floats, {}, slots}); break;
+ case 2: fInstructions.push_back({BuilderOp::dot_2_floats, {}, slots}); break;
+ case 3: fInstructions.push_back({BuilderOp::dot_3_floats, {}, slots}); break;
+ case 4: fInstructions.push_back({BuilderOp::dot_4_floats, {}, slots}); break;
+
+ default:
+ SkDEBUGFAIL("invalid number of slots");
+ break;
+ }
+}
+
+void Builder::refract_floats() {
+ fInstructions.push_back({BuilderOp::refract_4_floats, {}});
+}
+
+void Builder::inverse_matrix(int32_t n) {
+ switch (n) {
+ case 2: fInstructions.push_back({BuilderOp::inverse_mat2, {}, 4}); break;
+ case 3: fInstructions.push_back({BuilderOp::inverse_mat3, {}, 9}); break;
+ case 4: fInstructions.push_back({BuilderOp::inverse_mat4, {}, 16}); break;
+ default: SkUNREACHABLE;
+ }
+}
+
+void Builder::discard_stack(int32_t count) {
+ // If we pushed something onto the stack and then immediately discarded part of it, we can
+ // shrink or eliminate the push.
+ while (count > 0 && !fInstructions.empty()) {
+ Instruction& lastInstruction = fInstructions.back();
+
+ switch (lastInstruction.fOp) {
+ case BuilderOp::discard_stack:
+ // Our last op was actually a separate discard_stack; combine the discards.
+ lastInstruction.fImmA += count;
+ return;
+
+ case BuilderOp::push_zeros:
+ case BuilderOp::push_clone:
+ case BuilderOp::push_clone_from_stack:
+ case BuilderOp::push_clone_indirect_from_stack:
+ case BuilderOp::push_slots:
+ case BuilderOp::push_slots_indirect:
+ case BuilderOp::push_uniform:
+ case BuilderOp::push_uniform_indirect:
+ // Our last op was a multi-slot push; cancel out one discard and eliminate the op
+ // if its count reached zero.
+ --count;
+ --lastInstruction.fImmA;
+ if (lastInstruction.fImmA == 0) {
+ fInstructions.pop_back();
+ }
+ continue;
+
+ case BuilderOp::push_literal:
+ case BuilderOp::push_condition_mask:
+ case BuilderOp::push_loop_mask:
+ case BuilderOp::push_return_mask:
+ // Our last op was a single-slot push; cancel out one discard and eliminate the op.
+ --count;
+ fInstructions.pop_back();
+ continue;
+
+ default:
+ break;
+ }
+
+ // This instruction wasn't a push.
+ break;
+ }
+
+ if (count > 0) {
+ fInstructions.push_back({BuilderOp::discard_stack, {}, count});
+ }
+}
+
+void Builder::label(int labelID) {
+ SkASSERT(labelID >= 0 && labelID < fNumLabels);
+
+ // If the previous instruction was a branch to this label, it's a no-op; jumping to the very
+ // next instruction is effectively meaningless.
+ while (!fInstructions.empty()) {
+ Instruction& lastInstruction = fInstructions.back();
+ switch (lastInstruction.fOp) {
+ case BuilderOp::jump:
+ case BuilderOp::branch_if_all_lanes_active:
+ case BuilderOp::branch_if_any_lanes_active:
+ case BuilderOp::branch_if_no_lanes_active:
+ case BuilderOp::branch_if_no_active_lanes_on_stack_top_equal:
+ if (lastInstruction.fImmA == labelID) {
+ fInstructions.pop_back();
+ continue;
+ }
+ break;
+
+ default:
+ break;
+ }
+ break;
+ }
+ fInstructions.push_back({BuilderOp::label, {}, labelID});
+}
+
+void Builder::jump(int labelID) {
+ SkASSERT(labelID >= 0 && labelID < fNumLabels);
+ if (!fInstructions.empty() && fInstructions.back().fOp == BuilderOp::jump) {
+ // The previous instruction was also `jump`, so this branch could never possibly occur.
+ return;
+ }
+ fInstructions.push_back({BuilderOp::jump, {}, labelID});
+}
+
+void Builder::branch_if_any_lanes_active(int labelID) {
+ if (!this->executionMaskWritesAreEnabled()) {
+ this->jump(labelID);
+ return;
+ }
+
+ SkASSERT(labelID >= 0 && labelID < fNumLabels);
+ if (!fInstructions.empty() &&
+ (fInstructions.back().fOp == BuilderOp::branch_if_any_lanes_active ||
+ fInstructions.back().fOp == BuilderOp::jump)) {
+ // The previous instruction was `jump` or `branch_if_any_lanes_active`, so this branch
+ // could never possibly occur.
+ return;
+ }
+ fInstructions.push_back({BuilderOp::branch_if_any_lanes_active, {}, labelID});
+}
+
+void Builder::branch_if_all_lanes_active(int labelID) {
+ if (!this->executionMaskWritesAreEnabled()) {
+ this->jump(labelID);
+ return;
+ }
+
+ SkASSERT(labelID >= 0 && labelID < fNumLabels);
+ if (!fInstructions.empty() &&
+ (fInstructions.back().fOp == BuilderOp::branch_if_all_lanes_active ||
+ fInstructions.back().fOp == BuilderOp::jump)) {
+ // The previous instruction was `jump` or `branch_if_all_lanes_active`, so this branch
+ // could never possibly occur.
+ return;
+ }
+ fInstructions.push_back({BuilderOp::branch_if_all_lanes_active, {}, labelID});
+}
+
+void Builder::branch_if_no_lanes_active(int labelID) {
+ if (!this->executionMaskWritesAreEnabled()) {
+ return;
+ }
+
+ SkASSERT(labelID >= 0 && labelID < fNumLabels);
+ if (!fInstructions.empty() &&
+ (fInstructions.back().fOp == BuilderOp::branch_if_no_lanes_active ||
+ fInstructions.back().fOp == BuilderOp::jump)) {
+ // The previous instruction was `jump` or `branch_if_no_lanes_active`, so this branch
+ // could never possibly occur.
+ return;
+ }
+ fInstructions.push_back({BuilderOp::branch_if_no_lanes_active, {}, labelID});
+}
+
+void Builder::branch_if_no_active_lanes_on_stack_top_equal(int value, int labelID) {
+ SkASSERT(labelID >= 0 && labelID < fNumLabels);
+ if (!fInstructions.empty() &&
+ (fInstructions.back().fOp == BuilderOp::jump ||
+ (fInstructions.back().fOp == BuilderOp::branch_if_no_active_lanes_on_stack_top_equal &&
+ fInstructions.back().fImmB == value))) {
+ // The previous instruction was `jump` or `branch_if_no_active_lanes_on_stack_top_equal`
+ // (checking against the same value), so this branch could never possibly occur.
+ return;
+ }
+ fInstructions.push_back({BuilderOp::branch_if_no_active_lanes_on_stack_top_equal,
+ {}, labelID, value});
+}
+
+void Builder::push_slots(SlotRange src) {
+ SkASSERT(src.count >= 0);
+ if (!fInstructions.empty()) {
+ Instruction& lastInstruction = fInstructions.back();
+
+ // If the previous instruction was pushing slots contiguous to this range, we can collapse
+ // the two pushes into one larger push.
+ if (lastInstruction.fOp == BuilderOp::push_slots &&
+ lastInstruction.fSlotA + lastInstruction.fImmA == src.index) {
+ lastInstruction.fImmA += src.count;
+ return;
+ }
+
+ // If the previous instruction was discarding an equal number of slots...
+ if (lastInstruction.fOp == BuilderOp::discard_stack && lastInstruction.fImmA == src.count) {
+ // ... and the instruction before that was copying from the stack to the same slots...
+ Instruction& prevInstruction = fInstructions.fromBack(1);
+ if ((prevInstruction.fOp == BuilderOp::copy_stack_to_slots ||
+ prevInstruction.fOp == BuilderOp::copy_stack_to_slots_unmasked) &&
+ prevInstruction.fSlotA == src.index &&
+ prevInstruction.fImmA == src.count) {
+ // ... we are emitting `copy stack to X, discard stack, copy X to stack`. This is a
+ // common pattern when multiple operations in a row affect the same variable. We can
+ // eliminate the discard and just leave X on the stack.
+ fInstructions.pop_back();
+ return;
+ }
+ }
+ }
+
+ if (src.count > 0) {
+ fInstructions.push_back({BuilderOp::push_slots, {src.index}, src.count});
+ }
+}
+
+void Builder::push_slots_indirect(SlotRange fixedRange, int dynamicStackID, SlotRange limitRange) {
+ // SlotA: fixed-range start
+ // SlotB: limit-range end
+ // immA: number of slots
+ // immB: dynamic stack ID
+ fInstructions.push_back({BuilderOp::push_slots_indirect,
+ {fixedRange.index, limitRange.index + limitRange.count},
+ fixedRange.count,
+ dynamicStackID});
+}
+
+void Builder::push_uniform(SlotRange src) {
+ SkASSERT(src.count >= 0);
+ if (!fInstructions.empty()) {
+ Instruction& lastInstruction = fInstructions.back();
+
+ // If the previous instruction was pushing uniforms contiguous to this range, we can
+ // collapse the two pushes into one larger push.
+ if (lastInstruction.fOp == BuilderOp::push_uniform &&
+ lastInstruction.fSlotA + lastInstruction.fImmA == src.index) {
+ lastInstruction.fImmA += src.count;
+ return;
+ }
+ }
+
+ if (src.count > 0) {
+ fInstructions.push_back({BuilderOp::push_uniform, {src.index}, src.count});
+ }
+}
+
+void Builder::push_uniform_indirect(SlotRange fixedRange,
+ int dynamicStackID,
+ SlotRange limitRange) {
+ // SlotA: fixed-range start
+ // SlotB: limit-range end
+ // immA: number of slots
+ // immB: dynamic stack ID
+ fInstructions.push_back({BuilderOp::push_uniform_indirect,
+ {fixedRange.index, limitRange.index + limitRange.count},
+ fixedRange.count,
+ dynamicStackID});
+}
+
+void Builder::push_duplicates(int count) {
+ if (!fInstructions.empty()) {
+ Instruction& lastInstruction = fInstructions.back();
+
+ // If the previous op is pushing a zero, we can just push more of them.
+ if (lastInstruction.fOp == BuilderOp::push_zeros) {
+ lastInstruction.fImmA += count;
+ return;
+ }
+ }
+ SkASSERT(count >= 0);
+ if (count >= 3) {
+ // Use a swizzle to splat the input into a 4-slot value.
+ this->swizzle(/*consumedSlots=*/1, {0, 0, 0, 0});
+ count -= 3;
+ }
+ for (; count >= 4; count -= 4) {
+ // Clone the splatted value four slots at a time.
+ this->push_clone(/*numSlots=*/4);
+ }
+ // Use a swizzle or clone to handle the trailing items.
+ switch (count) {
+ case 3: this->swizzle(/*consumedSlots=*/1, {0, 0, 0, 0}); break;
+ case 2: this->swizzle(/*consumedSlots=*/1, {0, 0, 0}); break;
+ case 1: this->push_clone(/*numSlots=*/1); break;
+ default: break;
+ }
+}
+
+void Builder::push_clone_from_stack(SlotRange range, int otherStackID, int offsetFromStackTop) {
+ // immA: number of slots
+ // immB: other stack ID
+ // immC: offset from stack top
+ offsetFromStackTop -= range.index;
+
+ if (!fInstructions.empty()) {
+ Instruction& lastInstruction = fInstructions.back();
+
+ // If the previous op is also pushing a clone...
+ if (lastInstruction.fOp == BuilderOp::push_clone_from_stack &&
+ // ... from the same stack...
+ lastInstruction.fImmB == otherStackID &&
+ // ... and this clone starts at the same place that the last clone ends...
+ lastInstruction.fImmC - lastInstruction.fImmA == offsetFromStackTop) {
+ // ... just extend the existing clone-op.
+ lastInstruction.fImmA += range.count;
+ return;
+ }
+ }
+
+ fInstructions.push_back({BuilderOp::push_clone_from_stack, {},
+ range.count, otherStackID, offsetFromStackTop});
+}
+
+void Builder::push_clone_indirect_from_stack(SlotRange fixedOffset,
+ int dynamicStackID,
+ int otherStackID,
+ int offsetFromStackTop) {
+ // immA: number of slots
+ // immB: other stack ID
+ // immC: offset from stack top
+ // immD: dynamic stack ID
+ offsetFromStackTop -= fixedOffset.index;
+
+ fInstructions.push_back({BuilderOp::push_clone_indirect_from_stack, {},
+ fixedOffset.count, otherStackID, offsetFromStackTop, dynamicStackID});
+}
+
+void Builder::pop_slots(SlotRange dst) {
+ if (!this->executionMaskWritesAreEnabled()) {
+ this->pop_slots_unmasked(dst);
+ return;
+ }
+
+ this->copy_stack_to_slots(dst);
+ this->discard_stack(dst.count);
+}
+
+void Builder::simplifyPopSlotsUnmasked(SlotRange* dst) {
+ if (!dst->count || fInstructions.empty()) {
+ // There's nothing left to simplify.
+ return;
+ }
+
+ Instruction& lastInstruction = fInstructions.back();
+
+ // If the last instruction is pushing a constant, we can simplify it by copying the constant
+ // directly into the destination slot.
+ if (lastInstruction.fOp == BuilderOp::push_literal) {
+ // Remove the constant-push instruction.
+ int value = lastInstruction.fImmA;
+ fInstructions.pop_back();
+
+ // Consume one destination slot.
+ dst->count--;
+ Slot destinationSlot = dst->index + dst->count;
+
+ // Continue simplifying if possible.
+ this->simplifyPopSlotsUnmasked(dst);
+
+ // Write the constant directly to the destination slot.
+ this->copy_constant(destinationSlot, value);
+ return;
+ }
+
+ // If the last instruction is pushing a zero, we can save a step by directly zeroing out
+ // the destination slot.
+ if (lastInstruction.fOp == BuilderOp::push_zeros) {
+ // Remove one zero-push.
+ lastInstruction.fImmA--;
+ if (lastInstruction.fImmA == 0) {
+ fInstructions.pop_back();
+ }
+
+ // Consume one destination slot.
+ dst->count--;
+ Slot destinationSlot = dst->index + dst->count;
+
+ // Continue simplifying if possible.
+ this->simplifyPopSlotsUnmasked(dst);
+
+ // Zero the destination slot directly.
+ this->zero_slots_unmasked({destinationSlot, 1});
+ return;
+ }
+
+ // If the last instruction is pushing a slot, we can just copy that slot.
+ if (lastInstruction.fOp == BuilderOp::push_slots) {
+ // Get the last slot.
+ Slot sourceSlot = lastInstruction.fSlotA + lastInstruction.fImmA - 1;
+ lastInstruction.fImmA--;
+ if (lastInstruction.fImmA == 0) {
+ fInstructions.pop_back();
+ }
+
+ // Consume one destination slot.
+ dst->count--;
+ Slot destinationSlot = dst->index + dst->count;
+
+ // Try once more.
+ this->simplifyPopSlotsUnmasked(dst);
+
+ // Copy the slot directly.
+ if (destinationSlot != sourceSlot) {
+ this->copy_slots_unmasked({destinationSlot, 1}, {sourceSlot, 1});
+ }
+ return;
+ }
+}
+
+void Builder::pop_slots_unmasked(SlotRange dst) {
+ SkASSERT(dst.count >= 0);
+
+ // If we are popping immediately after a push, we can simplify the code by writing the pushed
+ // value directly to the destination range.
+ this->simplifyPopSlotsUnmasked(&dst);
+
+ // Pop from the stack normally.
+ if (dst.count > 0) {
+ this->copy_stack_to_slots_unmasked(dst);
+ this->discard_stack(dst.count);
+ }
+}
+
+void Builder::copy_stack_to_slots(SlotRange dst, int offsetFromStackTop) {
+ // If the execution mask is known to be all-true, then we can ignore the write mask.
+ if (!this->executionMaskWritesAreEnabled()) {
+ this->copy_stack_to_slots_unmasked(dst, offsetFromStackTop);
+ return;
+ }
+
+ // If the last instruction copied the previous stack slots, just extend it.
+ if (!fInstructions.empty()) {
+ Instruction& lastInstruction = fInstructions.back();
+
+ // If the last op is copy-stack-to-slots...
+ if (lastInstruction.fOp == BuilderOp::copy_stack_to_slots &&
+ // and this op's destination is immediately after the last copy-slots-op's destination
+ lastInstruction.fSlotA + lastInstruction.fImmA == dst.index &&
+ // and this op's source is immediately after the last copy-slots-op's source
+ lastInstruction.fImmB - lastInstruction.fImmA == offsetFromStackTop) {
+ // then we can just extend the copy!
+ lastInstruction.fImmA += dst.count;
+ return;
+ }
+ }
+
+ fInstructions.push_back({BuilderOp::copy_stack_to_slots, {dst.index},
+ dst.count, offsetFromStackTop});
+}
+
+void Builder::copy_stack_to_slots_indirect(SlotRange fixedRange,
+ int dynamicStackID,
+ SlotRange limitRange) {
+ // SlotA: fixed-range start
+ // SlotB: limit-range end
+ // immA: number of slots
+ // immB: dynamic stack ID
+ fInstructions.push_back({BuilderOp::copy_stack_to_slots_indirect,
+ {fixedRange.index, limitRange.index + limitRange.count},
+ fixedRange.count,
+ dynamicStackID});
+}
+
+static bool slot_ranges_overlap(SlotRange x, SlotRange y) {
+ return x.index < y.index + y.count &&
+ y.index < x.index + x.count;
+}
+
+void Builder::copy_slots_unmasked(SlotRange dst, SlotRange src) {
+ // If the last instruction copied adjacent slots, just extend it.
+ if (!fInstructions.empty()) {
+ Instruction& lastInstr = fInstructions.back();
+
+ // If the last op is copy-slots-unmasked...
+ if (lastInstr.fOp == BuilderOp::copy_slot_unmasked &&
+ // and this op's destination is immediately after the last copy-slots-op's destination
+ lastInstr.fSlotA + lastInstr.fImmA == dst.index &&
+ // and this op's source is immediately after the last copy-slots-op's source
+ lastInstr.fSlotB + lastInstr.fImmA == src.index &&
+ // and the source/dest ranges will not overlap
+ !slot_ranges_overlap({lastInstr.fSlotB, lastInstr.fImmA + dst.count},
+ {lastInstr.fSlotA, lastInstr.fImmA + dst.count})) {
+ // then we can just extend the copy!
+ lastInstr.fImmA += dst.count;
+ return;
+ }
+ }
+
+ SkASSERT(dst.count == src.count);
+ fInstructions.push_back({BuilderOp::copy_slot_unmasked, {dst.index, src.index}, dst.count});
+}
+
+void Builder::copy_stack_to_slots_unmasked(SlotRange dst, int offsetFromStackTop) {
+ // If the last instruction copied the previous stack slots, just extend it.
+ if (!fInstructions.empty()) {
+ Instruction& lastInstruction = fInstructions.back();
+
+ // If the last op is copy-stack-to-slots-unmasked...
+ if (lastInstruction.fOp == BuilderOp::copy_stack_to_slots_unmasked &&
+ // and this op's destination is immediately after the last copy-slots-op's destination
+ lastInstruction.fSlotA + lastInstruction.fImmA == dst.index &&
+ // and this op's source is immediately after the last copy-slots-op's source
+ lastInstruction.fImmB - lastInstruction.fImmA == offsetFromStackTop) {
+ // then we can just extend the copy!
+ lastInstruction.fImmA += dst.count;
+ return;
+ }
+ }
+
+ fInstructions.push_back({BuilderOp::copy_stack_to_slots_unmasked, {dst.index},
+ dst.count, offsetFromStackTop});
+}
+
+void Builder::pop_return_mask() {
+ SkASSERT(this->executionMaskWritesAreEnabled());
+
+ // This instruction is going to overwrite the return mask. If the previous instruction was
+ // masking off the return mask, that's wasted work and it can be eliminated.
+ if (!fInstructions.empty()) {
+ Instruction& lastInstruction = fInstructions.back();
+
+ if (lastInstruction.fOp == BuilderOp::mask_off_return_mask) {
+ fInstructions.pop_back();
+ }
+ }
+
+ fInstructions.push_back({BuilderOp::pop_return_mask, {}});
+}
+
+void Builder::zero_slots_unmasked(SlotRange dst) {
+ if (!fInstructions.empty()) {
+ Instruction& lastInstruction = fInstructions.back();
+
+ if (lastInstruction.fOp == BuilderOp::zero_slot_unmasked) {
+ if (lastInstruction.fSlotA + lastInstruction.fImmA == dst.index) {
+ // The previous instruction was zeroing the range immediately before this range.
+ // Combine the ranges.
+ lastInstruction.fImmA += dst.count;
+ return;
+ }
+ }
+
+ if (lastInstruction.fOp == BuilderOp::zero_slot_unmasked) {
+ if (lastInstruction.fSlotA == dst.index + dst.count) {
+ // The previous instruction was zeroing the range immediately after this range.
+ // Combine the ranges.
+ lastInstruction.fSlotA = dst.index;
+ lastInstruction.fImmA += dst.count;
+ return;
+ }
+ }
+ }
+
+ fInstructions.push_back({BuilderOp::zero_slot_unmasked, {dst.index}, dst.count});
+}
+
+static int pack_nybbles(SkSpan<const int8_t> components) {
+ // Pack up to 8 elements into nybbles, in reverse order.
+ int packed = 0;
+ for (auto iter = components.rbegin(); iter != components.rend(); ++iter) {
+ SkASSERT(*iter >= 0 && *iter <= 0xF);
+ packed <<= 4;
+ packed |= *iter;
+ }
+ return packed;
+}
+
+static void unpack_nybbles_to_offsets(uint32_t components, SkSpan<uint16_t> offsets) {
+ // Unpack component nybbles into byte-offsets pointing at stack slots.
+ for (size_t index = 0; index < offsets.size(); ++index) {
+ offsets[index] = (components & 0xF) * SkOpts::raster_pipeline_highp_stride * sizeof(float);
+ components >>= 4;
+ }
+}
+
+static int max_packed_nybble(uint32_t components, size_t numComponents) {
+ int largest = 0;
+ for (size_t index = 0; index < numComponents; ++index) {
+ largest = std::max<int>(largest, components & 0xF);
+ components >>= 4;
+ }
+ return largest;
+}
+
+void Builder::swizzle_copy_stack_to_slots(SlotRange dst,
+ SkSpan<const int8_t> components,
+ int offsetFromStackTop) {
+ // When the execution-mask writes-enabled flag is off, we could squeeze out a little bit of
+ // extra speed here by implementing and using an unmasked version of this op.
+
+ // SlotA: fixed-range start
+ // immA: number of swizzle components
+ // immB: swizzle components
+ // immC: offset from stack top
+ fInstructions.push_back({BuilderOp::swizzle_copy_stack_to_slots, {dst.index},
+ (int)components.size(),
+ pack_nybbles(components),
+ offsetFromStackTop});
+}
+
+void Builder::swizzle_copy_stack_to_slots_indirect(SlotRange fixedRange,
+ int dynamicStackID,
+ SlotRange limitRange,
+ SkSpan<const int8_t> components,
+ int offsetFromStackTop) {
+ // When the execution-mask writes-enabled flag is off, we could squeeze out a little bit of
+ // extra speed here by implementing and using an unmasked version of this op.
+
+ // SlotA: fixed-range start
+ // SlotB: limit-range end
+ // immA: number of swizzle components
+ // immB: swizzle components
+ // immC: offset from stack top
+ // immD: dynamic stack ID
+ fInstructions.push_back({BuilderOp::swizzle_copy_stack_to_slots_indirect,
+ {fixedRange.index, limitRange.index + limitRange.count},
+ (int)components.size(),
+ pack_nybbles(components),
+ offsetFromStackTop,
+ dynamicStackID});
+}
+
+void Builder::swizzle(int consumedSlots, SkSpan<const int8_t> components) {
+ // Consumes `consumedSlots` elements on the stack, then generates `elementSpan.size()` elements.
+ SkASSERT(consumedSlots >= 0);
+
+ // We only allow up to 16 elements, and they can only reach 0-15 slots, due to nybble packing.
+ int numElements = components.size();
+ SkASSERT(numElements <= 16);
+ SkASSERT(std::all_of(components.begin(), components.end(), [](int8_t e){ return e >= 0; }));
+ SkASSERT(std::all_of(components.begin(), components.end(), [](int8_t e){ return e <= 0xF; }));
+
+ // Make a local copy of the element array.
+ int8_t elements[16] = {};
+ std::copy(components.begin(), components.end(), std::begin(elements));
+
+ while (numElements > 0) {
+ // If the first element of the swizzle is zero...
+ if (elements[0] != 0) {
+ break;
+ }
+ // ...and zero isn't used elsewhere in the swizzle...
+ if (std::any_of(&elements[1], &elements[numElements], [](int8_t e) { return e == 0; })) {
+ break;
+ }
+ // We can omit the first slot from the swizzle entirely.
+ // Slide everything forward by one slot, and reduce the element index by one.
+ for (int index = 1; index < numElements; ++index) {
+ elements[index - 1] = elements[index] - 1;
+ }
+ elements[numElements - 1] = 0;
+ --consumedSlots;
+ --numElements;
+ }
+
+ // A completely empty swizzle is a no-op.
+ if (numElements == 0) {
+ this->discard_stack(consumedSlots);
+ return;
+ }
+
+ if (consumedSlots <= 4 && numElements <= 4) {
+ // We can fit everything into a little swizzle.
+ int op = (int)BuilderOp::swizzle_1 + numElements - 1;
+ fInstructions.push_back({(BuilderOp)op, {}, consumedSlots,
+ pack_nybbles(SkSpan(elements, numElements))});
+ return;
+ }
+
+ // This is a big swizzle. We use the `shuffle` op to handle these.
+ // Slot usage is packed into immA. The top 16 bits of immA count the consumed slots; the bottom
+ // 16 bits count the generated slots.
+ int slotUsage = consumedSlots << 16;
+ slotUsage |= numElements;
+
+ // Pack immB and immC with the shuffle list in packed-nybble form.
+ fInstructions.push_back({BuilderOp::shuffle, {}, slotUsage,
+ pack_nybbles(SkSpan(&elements[0], 8)),
+ pack_nybbles(SkSpan(&elements[8], 8))});
+}
+
+void Builder::transpose(int columns, int rows) {
+ // Transposes a matrix of size CxR on the stack (into a matrix of size RxC).
+ int8_t elements[16] = {};
+ size_t index = 0;
+ for (int r = 0; r < rows; ++r) {
+ for (int c = 0; c < columns; ++c) {
+ elements[index++] = (c * rows) + r;
+ }
+ }
+ this->swizzle(/*consumedSlots=*/columns * rows, SkSpan(elements, index));
+}
+
+void Builder::diagonal_matrix(int columns, int rows) {
+ // Generates a CxR diagonal matrix from the top two scalars on the stack.
+ int8_t elements[16] = {};
+ size_t index = 0;
+ for (int c = 0; c < columns; ++c) {
+ for (int r = 0; r < rows; ++r) {
+ elements[index++] = (c == r) ? 1 : 0;
+ }
+ }
+ this->swizzle(/*consumedSlots=*/2, SkSpan(elements, index));
+}
+
+void Builder::matrix_resize(int origColumns, int origRows, int newColumns, int newRows) {
+ // Resizes a CxR matrix at the top of the stack to C'xR'.
+ int8_t elements[16] = {};
+ size_t index = 0;
+
+ size_t consumedSlots = origColumns * origRows;
+ size_t zeroOffset = 0, oneOffset = 0;
+
+ for (int c = 0; c < newColumns; ++c) {
+ for (int r = 0; r < newRows; ++r) {
+ if (c < origColumns && r < origRows) {
+ // Push an element from the original matrix.
+ elements[index++] = (c * origRows) + r;
+ } else {
+ // This element is outside the original matrix; push 1 or 0.
+ if (c == r) {
+ // We need to synthesize a literal 1.
+ if (oneOffset == 0) {
+ this->push_literal_f(1.0f);
+ oneOffset = consumedSlots++;
+ }
+ elements[index++] = oneOffset;
+ } else {
+ // We need to synthesize a literal 0.
+ if (zeroOffset == 0) {
+ this->push_zeros(1);
+ zeroOffset = consumedSlots++;
+ }
+ elements[index++] = zeroOffset;
+ }
+ }
+ }
+ }
+ this->swizzle(consumedSlots, SkSpan(elements, index));
+}
+
+std::unique_ptr<Program> Builder::finish(int numValueSlots,
+ int numUniformSlots,
+ SkRPDebugTrace* debugTrace) {
+ // Verify that calls to enableExecutionMaskWrites and disableExecutionMaskWrites are balanced.
+ SkASSERT(fExecutionMaskWritesEnabled == 0);
+
+ return std::make_unique<Program>(std::move(fInstructions), numValueSlots, numUniformSlots,
+ fNumLabels, debugTrace);
+}
+
+void Program::optimize() {
+ // TODO(johnstiles): perform any last-minute cleanup of the instruction stream here
+}
+
+static int stack_usage(const Instruction& inst) {
+ switch (inst.fOp) {
+ case BuilderOp::push_literal:
+ case BuilderOp::push_condition_mask:
+ case BuilderOp::push_loop_mask:
+ case BuilderOp::push_return_mask:
+ return 1;
+
+ case BuilderOp::push_src_rgba:
+ case BuilderOp::push_dst_rgba:
+ return 4;
+
+ case BuilderOp::push_slots:
+ case BuilderOp::push_slots_indirect:
+ case BuilderOp::push_uniform:
+ case BuilderOp::push_uniform_indirect:
+ case BuilderOp::push_zeros:
+ case BuilderOp::push_clone:
+ case BuilderOp::push_clone_from_stack:
+ case BuilderOp::push_clone_indirect_from_stack:
+ return inst.fImmA;
+
+ case BuilderOp::pop_condition_mask:
+ case BuilderOp::pop_loop_mask:
+ case BuilderOp::pop_and_reenable_loop_mask:
+ case BuilderOp::pop_return_mask:
+ return -1;
+
+ case BuilderOp::pop_src_rg:
+ return -2;
+
+ case BuilderOp::pop_src_rgba:
+ case BuilderOp::pop_dst_rgba:
+ return -4;
+
+ case ALL_N_WAY_BINARY_OP_CASES:
+ case ALL_MULTI_SLOT_BINARY_OP_CASES:
+ case BuilderOp::discard_stack:
+ case BuilderOp::select:
+ return -inst.fImmA;
+
+ case ALL_N_WAY_TERNARY_OP_CASES:
+ case ALL_MULTI_SLOT_TERNARY_OP_CASES:
+ return 2 * -inst.fImmA;
+
+ case BuilderOp::swizzle_1:
+ return 1 - inst.fImmA; // consumes immA slots and emits a scalar
+ case BuilderOp::swizzle_2:
+ return 2 - inst.fImmA; // consumes immA slots and emits a 2-slot vector
+ case BuilderOp::swizzle_3:
+ return 3 - inst.fImmA; // consumes immA slots and emits a 3-slot vector
+ case BuilderOp::swizzle_4:
+ return 4 - inst.fImmA; // consumes immA slots and emits a 4-slot vector
+
+ case BuilderOp::dot_2_floats:
+ return -3; // consumes two 2-slot vectors and emits one scalar
+ case BuilderOp::dot_3_floats:
+ return -5; // consumes two 3-slot vectors and emits one scalar
+ case BuilderOp::dot_4_floats:
+ return -7; // consumes two 4-slot vectors and emits one scalar
+
+ case BuilderOp::refract_4_floats:
+ return -5; // consumes nine slots (N + I + eta) and emits a 4-slot vector (R)
+
+ case BuilderOp::shuffle: {
+ int consumed = inst.fImmA >> 16;
+ int generated = inst.fImmA & 0xFFFF;
+ return generated - consumed;
+ }
+ case ALL_SINGLE_SLOT_UNARY_OP_CASES:
+ case ALL_MULTI_SLOT_UNARY_OP_CASES:
+ default:
+ return 0;
+ }
+}
+
+Program::StackDepthMap Program::tempStackMaxDepths() const {
+ StackDepthMap largest;
+ StackDepthMap current;
+
+ int curIdx = 0;
+ for (const Instruction& inst : fInstructions) {
+ if (inst.fOp == BuilderOp::set_current_stack) {
+ curIdx = inst.fImmA;
+ }
+ current[curIdx] += stack_usage(inst);
+ largest[curIdx] = std::max(current[curIdx], largest[curIdx]);
+ SkASSERTF(current[curIdx] >= 0, "unbalanced temp stack push/pop on stack %d", curIdx);
+ }
+
+ for (const auto& [stackIdx, depth] : current) {
+ (void)stackIdx;
+ SkASSERTF(depth == 0, "unbalanced temp stack push/pop");
+ }
+
+ return largest;
+}
+
+Program::Program(SkTArray<Instruction> instrs,
+ int numValueSlots,
+ int numUniformSlots,
+ int numLabels,
+ SkRPDebugTrace* debugTrace)
+ : fInstructions(std::move(instrs))
+ , fNumValueSlots(numValueSlots)
+ , fNumUniformSlots(numUniformSlots)
+ , fNumLabels(numLabels)
+ , fDebugTrace(debugTrace) {
+ this->optimize();
+
+ fTempStackMaxDepths = this->tempStackMaxDepths();
+
+ fNumTempStackSlots = 0;
+ for (const auto& [stackIdx, depth] : fTempStackMaxDepths) {
+ (void)stackIdx;
+ fNumTempStackSlots += depth;
+ }
+}
+
+void Program::appendCopy(SkTArray<Stage>* pipeline,
+ SkArenaAlloc* alloc,
+ ProgramOp baseStage,
+ float* dst, int dstStride,
+ const float* src, int srcStride,
+ int numSlots) const {
+ SkASSERT(numSlots >= 0);
+ while (numSlots > 4) {
+ this->appendCopy(pipeline, alloc, baseStage, dst, dstStride, src, srcStride,/*numSlots=*/4);
+ dst += 4 * dstStride;
+ src += 4 * srcStride;
+ numSlots -= 4;
+ }
+
+ if (numSlots > 0) {
+ SkASSERT(numSlots <= 4);
+ auto stage = (ProgramOp)((int)baseStage + numSlots - 1);
+ auto* ctx = alloc->make<SkRasterPipeline_BinaryOpCtx>();
+ ctx->dst = dst;
+ ctx->src = src;
+ pipeline->push_back({stage, ctx});
+ }
+}
+
+void Program::appendCopySlotsUnmasked(SkTArray<Stage>* pipeline,
+ SkArenaAlloc* alloc,
+ float* dst,
+ const float* src,
+ int numSlots) const {
+ this->appendCopy(pipeline, alloc,
+ ProgramOp::copy_slot_unmasked,
+ dst, /*dstStride=*/SkOpts::raster_pipeline_highp_stride,
+ src, /*srcStride=*/SkOpts::raster_pipeline_highp_stride,
+ numSlots);
+}
+
+void Program::appendCopySlotsMasked(SkTArray<Stage>* pipeline,
+ SkArenaAlloc* alloc,
+ float* dst,
+ const float* src,
+ int numSlots) const {
+ this->appendCopy(pipeline, alloc,
+ ProgramOp::copy_slot_masked,
+ dst, /*dstStride=*/SkOpts::raster_pipeline_highp_stride,
+ src, /*srcStride=*/SkOpts::raster_pipeline_highp_stride,
+ numSlots);
+}
+
+void Program::appendCopyConstants(SkTArray<Stage>* pipeline,
+ SkArenaAlloc* alloc,
+ float* dst,
+ const float* src,
+ int numSlots) const {
+ this->appendCopy(pipeline, alloc,
+ ProgramOp::copy_constant,
+ dst, /*dstStride=*/SkOpts::raster_pipeline_highp_stride,
+ src, /*srcStride=*/1,
+ numSlots);
+}
+
+void Program::appendSingleSlotUnaryOp(SkTArray<Stage>* pipeline, ProgramOp stage,
+ float* dst, int numSlots) const {
+ SkASSERT(numSlots >= 0);
+ while (numSlots--) {
+ pipeline->push_back({stage, dst});
+ dst += SkOpts::raster_pipeline_highp_stride;
+ }
+}
+
+void Program::appendMultiSlotUnaryOp(SkTArray<Stage>* pipeline, ProgramOp baseStage,
+ float* dst, int numSlots) const {
+ SkASSERT(numSlots >= 0);
+ while (numSlots > 4) {
+ this->appendMultiSlotUnaryOp(pipeline, baseStage, dst, /*numSlots=*/4);
+ dst += 4 * SkOpts::raster_pipeline_highp_stride;
+ numSlots -= 4;
+ }
+
+ SkASSERT(numSlots <= 4);
+ auto stage = (ProgramOp)((int)baseStage + numSlots - 1);
+ pipeline->push_back({stage, dst});
+}
+
+void Program::appendAdjacentNWayBinaryOp(SkTArray<Stage>* pipeline, SkArenaAlloc* alloc,
+ ProgramOp stage,
+ float* dst, const float* src, int numSlots) const {
+ // The source and destination must be directly next to one another.
+ SkASSERT(numSlots >= 0);
+ SkASSERT((dst + SkOpts::raster_pipeline_highp_stride * numSlots) == src);
+
+ if (numSlots > 0) {
+ auto ctx = alloc->make<SkRasterPipeline_BinaryOpCtx>();
+ ctx->dst = dst;
+ ctx->src = src;
+ pipeline->push_back({stage, ctx});
+ }
+}
+
+void Program::appendAdjacentMultiSlotBinaryOp(SkTArray<Stage>* pipeline, SkArenaAlloc* alloc,
+ ProgramOp baseStage,
+ float* dst, const float* src, int numSlots) const {
+ // The source and destination must be directly next to one another.
+ SkASSERT(numSlots >= 0);
+ SkASSERT((dst + SkOpts::raster_pipeline_highp_stride * numSlots) == src);
+
+ if (numSlots > 4) {
+ this->appendAdjacentNWayBinaryOp(pipeline, alloc, baseStage, dst, src, numSlots);
+ return;
+ }
+ if (numSlots > 0) {
+ auto specializedStage = (ProgramOp)((int)baseStage + numSlots);
+ pipeline->push_back({specializedStage, dst});
+ }
+}
+
+void Program::appendAdjacentNWayTernaryOp(SkTArray<Stage>* pipeline, SkArenaAlloc* alloc,
+ ProgramOp stage, float* dst, const float* src0,
+ const float* src1, int numSlots) const {
+ // The float pointers must all be immediately adjacent to each other.
+ SkASSERT(numSlots >= 0);
+ SkASSERT((dst + SkOpts::raster_pipeline_highp_stride * numSlots) == src0);
+ SkASSERT((src0 + SkOpts::raster_pipeline_highp_stride * numSlots) == src1);
+
+ if (numSlots > 0) {
+ auto ctx = alloc->make<SkRasterPipeline_TernaryOpCtx>();
+ ctx->dst = dst;
+ ctx->src0 = src0;
+ ctx->src1 = src1;
+ pipeline->push_back({stage, ctx});
+ }
+}
+
+void Program::appendAdjacentMultiSlotTernaryOp(SkTArray<Stage>* pipeline, SkArenaAlloc* alloc,
+ ProgramOp baseStage, float* dst, const float* src0,
+ const float* src1, int numSlots) const {
+ // The float pointers must all be immediately adjacent to each other.
+ SkASSERT(numSlots >= 0);
+ SkASSERT((dst + SkOpts::raster_pipeline_highp_stride * numSlots) == src0);
+ SkASSERT((src0 + SkOpts::raster_pipeline_highp_stride * numSlots) == src1);
+
+ if (numSlots > 4) {
+ this->appendAdjacentNWayTernaryOp(pipeline, alloc, baseStage, dst, src0, src1, numSlots);
+ return;
+ }
+ if (numSlots > 0) {
+ auto specializedStage = (ProgramOp)((int)baseStage + numSlots);
+ pipeline->push_back({specializedStage, dst});
+ }
+}
+
+void Program::appendStackRewind(SkTArray<Stage>* pipeline) const {
+#if defined(SKSL_STANDALONE) || !SK_HAS_MUSTTAIL
+ pipeline->push_back({ProgramOp::stack_rewind, nullptr});
+#endif
+}
+
+static void* context_bit_pun(intptr_t val) {
+ return sk_bit_cast<void*>(val);
+}
+
+Program::SlotData Program::allocateSlotData(SkArenaAlloc* alloc) const {
+ // Allocate a contiguous slab of slot data for values and stack entries.
+ const int N = SkOpts::raster_pipeline_highp_stride;
+ const int vectorWidth = N * sizeof(float);
+ const int allocSize = vectorWidth * (fNumValueSlots + fNumTempStackSlots);
+ float* slotPtr = static_cast<float*>(alloc->makeBytesAlignedTo(allocSize, vectorWidth));
+ sk_bzero(slotPtr, allocSize);
+
+ // Store the temp stack immediately after the values.
+ SlotData s;
+ s.values = SkSpan(slotPtr, N * fNumValueSlots);
+ s.stack = SkSpan(s.values.end(), N * fNumTempStackSlots);
+ return s;
+}
+
+#if !defined(SKSL_STANDALONE)
+
+bool Program::appendStages(SkRasterPipeline* pipeline,
+ SkArenaAlloc* alloc,
+ RP::Callbacks* callbacks,
+ SkSpan<const float> uniforms) const {
+ // Convert our Instruction list to an array of ProgramOps.
+ SkTArray<Stage> stages;
+ this->makeStages(&stages, alloc, uniforms, this->allocateSlotData(alloc));
+
+ // Allocate buffers for branch targets and labels; these are needed to convert labels into
+ // actual offsets into the pipeline and fix up branches.
+ SkTArray<SkRasterPipeline_BranchCtx*> branchContexts;
+ branchContexts.reserve_back(fNumLabels);
+ SkTArray<int> labelOffsets;
+ labelOffsets.push_back_n(fNumLabels, -1);
+ SkTArray<int> branchGoesToLabel;
+ branchGoesToLabel.reserve_back(fNumLabels);
+
+ for (const Stage& stage : stages) {
+ switch (stage.op) {
+ case ProgramOp::stack_rewind:
+ pipeline->append_stack_rewind();
+ break;
+
+ case ProgramOp::invoke_shader:
+ if (!callbacks || !callbacks->appendShader(sk_bit_cast<intptr_t>(stage.ctx))) {
+ return false;
+ }
+ break;
+
+ case ProgramOp::invoke_color_filter:
+ if (!callbacks || !callbacks->appendColorFilter(sk_bit_cast<intptr_t>(stage.ctx))) {
+ return false;
+ }
+ break;
+
+ case ProgramOp::invoke_blender:
+ if (!callbacks || !callbacks->appendBlender(sk_bit_cast<intptr_t>(stage.ctx))) {
+ return false;
+ }
+ break;
+
+ case ProgramOp::invoke_to_linear_srgb:
+ if (!callbacks) {
+ return false;
+ }
+ callbacks->toLinearSrgb();
+ break;
+
+ case ProgramOp::invoke_from_linear_srgb:
+ if (!callbacks) {
+ return false;
+ }
+ callbacks->fromLinearSrgb();
+ break;
+
+ case ProgramOp::label: {
+ // Remember the absolute pipeline position of this label.
+ int labelID = sk_bit_cast<intptr_t>(stage.ctx);
+ SkASSERT(labelID >= 0 && labelID < fNumLabels);
+ labelOffsets[labelID] = pipeline->getNumStages();
+ break;
+ }
+ case ProgramOp::jump:
+ case ProgramOp::branch_if_all_lanes_active:
+ case ProgramOp::branch_if_any_lanes_active:
+ case ProgramOp::branch_if_no_lanes_active:
+ case ProgramOp::branch_if_no_active_lanes_eq: {
+ // The branch context contain a valid label ID at this point.
+ auto* branchCtx = static_cast<SkRasterPipeline_BranchCtx*>(stage.ctx);
+ int labelID = branchCtx->offset;
+ SkASSERT(labelID >= 0 && labelID < fNumLabels);
+
+ // Replace the label ID in the branch context with the absolute pipeline position.
+ // We will go back over the branch targets at the end and fix them up.
+ branchCtx->offset = pipeline->getNumStages();
+
+ SkASSERT(branchContexts.size() == branchGoesToLabel.size());
+ branchContexts.push_back(branchCtx);
+ branchGoesToLabel.push_back(labelID);
+ [[fallthrough]];
+ }
+ default:
+ // Append a regular op to the program.
+ SkASSERT((int)stage.op < kNumRasterPipelineHighpOps);
+ pipeline->append((SkRasterPipelineOp)stage.op, stage.ctx);
+ break;
+ }
+ }
+
+ // Now that we have assembled the program and know the pipeline positions of each label and
+ // branch, fix up every branch target.
+ SkASSERT(branchContexts.size() == branchGoesToLabel.size());
+ for (int index = 0; index < branchContexts.size(); ++index) {
+ int branchFromIdx = branchContexts[index]->offset;
+ int branchToIdx = labelOffsets[branchGoesToLabel[index]];
+ branchContexts[index]->offset = branchToIdx - branchFromIdx;
+ }
+
+ return true;
+}
+
+#endif
+
+void Program::makeStages(SkTArray<Stage>* pipeline,
+ SkArenaAlloc* alloc,
+ SkSpan<const float> uniforms,
+ const SlotData& slots) const {
+ SkASSERT(fNumUniformSlots == SkToInt(uniforms.size()));
+
+ const int N = SkOpts::raster_pipeline_highp_stride;
+ StackDepthMap tempStackDepth;
+ int currentStack = 0;
+ int mostRecentRewind = 0;
+
+ // Assemble a map holding the current stack-top for each temporary stack. Position each temp
+ // stack immediately after the previous temp stack; temp stacks are never allowed to overlap.
+ int pos = 0;
+ SkTHashMap<int, float*> tempStackMap;
+ for (auto& [idx, depth] : fTempStackMaxDepths) {
+ tempStackMap[idx] = slots.stack.begin() + (pos * N);
+ pos += depth;
+ }
+
+ // Track labels that we have reached in processing.
+ SkBitSet labelsEncountered(fNumLabels);
+
+ auto EmitStackRewindForBackwardsBranch = [&](int labelID) {
+ // If we have already encountered the label associated with this branch, this is a
+ // backwards branch. Add a stack-rewind immediately before the branch to ensure that
+ // long-running loops don't use an unbounded amount of stack space.
+ if (labelsEncountered.test(labelID)) {
+ this->appendStackRewind(pipeline);
+ mostRecentRewind = pipeline->size();
+ }
+ };
+
+ // We can reuse constants from our arena by placing them in this map.
+ SkTHashMap<int, int*> constantLookupMap; // <constant value, pointer into arena>
+
+ // Write each BuilderOp to the pipeline array.
+ pipeline->reserve_back(fInstructions.size());
+ for (const Instruction& inst : fInstructions) {
+ auto SlotA = [&]() { return &slots.values[N * inst.fSlotA]; };
+ auto SlotB = [&]() { return &slots.values[N * inst.fSlotB]; };
+ auto UniformA = [&]() { return &uniforms[inst.fSlotA]; };
+ float*& tempStackPtr = tempStackMap[currentStack];
+
+ switch (inst.fOp) {
+ case BuilderOp::label:
+ SkASSERT(inst.fImmA >= 0 && inst.fImmA < fNumLabels);
+ labelsEncountered.set(inst.fImmA);
+ pipeline->push_back({ProgramOp::label, context_bit_pun(inst.fImmA)});
+ break;
+
+ case BuilderOp::jump:
+ case BuilderOp::branch_if_all_lanes_active:
+ case BuilderOp::branch_if_any_lanes_active:
+ case BuilderOp::branch_if_no_lanes_active: {
+ SkASSERT(inst.fImmA >= 0 && inst.fImmA < fNumLabels);
+ EmitStackRewindForBackwardsBranch(inst.fImmA);
+
+ auto* ctx = alloc->make<SkRasterPipeline_BranchCtx>();
+ ctx->offset = inst.fImmA;
+ pipeline->push_back({(ProgramOp)inst.fOp, ctx});
+ break;
+ }
+ case BuilderOp::branch_if_no_active_lanes_on_stack_top_equal: {
+ SkASSERT(inst.fImmA >= 0 && inst.fImmA < fNumLabels);
+ EmitStackRewindForBackwardsBranch(inst.fImmA);
+
+ auto* ctx = alloc->make<SkRasterPipeline_BranchIfEqualCtx>();
+ ctx->offset = inst.fImmA;
+ ctx->value = inst.fImmB;
+ ctx->ptr = reinterpret_cast<int*>(tempStackPtr - N);
+ pipeline->push_back({ProgramOp::branch_if_no_active_lanes_eq, ctx});
+ break;
+ }
+ case BuilderOp::init_lane_masks:
+ pipeline->push_back({ProgramOp::init_lane_masks, nullptr});
+ break;
+
+ case BuilderOp::store_src_rg:
+ pipeline->push_back({ProgramOp::store_src_rg, SlotA()});
+ break;
+
+ case BuilderOp::store_src:
+ pipeline->push_back({ProgramOp::store_src, SlotA()});
+ break;
+
+ case BuilderOp::store_dst:
+ pipeline->push_back({ProgramOp::store_dst, SlotA()});
+ break;
+
+ case BuilderOp::store_device_xy01:
+ pipeline->push_back({ProgramOp::store_device_xy01, SlotA()});
+ break;
+
+ case BuilderOp::load_src:
+ pipeline->push_back({ProgramOp::load_src, SlotA()});
+ break;
+
+ case BuilderOp::load_dst:
+ pipeline->push_back({ProgramOp::load_dst, SlotA()});
+ break;
+
+ case ALL_SINGLE_SLOT_UNARY_OP_CASES: {
+ float* dst = tempStackPtr - (inst.fImmA * N);
+ this->appendSingleSlotUnaryOp(pipeline, (ProgramOp)inst.fOp, dst, inst.fImmA);
+ break;
+ }
+ case ALL_MULTI_SLOT_UNARY_OP_CASES: {
+ float* dst = tempStackPtr - (inst.fImmA * N);
+ this->appendMultiSlotUnaryOp(pipeline, (ProgramOp)inst.fOp, dst, inst.fImmA);
+ break;
+ }
+ case ALL_N_WAY_BINARY_OP_CASES: {
+ float* src = tempStackPtr - (inst.fImmA * N);
+ float* dst = tempStackPtr - (inst.fImmA * 2 * N);
+ this->appendAdjacentNWayBinaryOp(pipeline, alloc, (ProgramOp)inst.fOp,
+ dst, src, inst.fImmA);
+ break;
+ }
+ case ALL_MULTI_SLOT_BINARY_OP_CASES: {
+ float* src = tempStackPtr - (inst.fImmA * N);
+ float* dst = tempStackPtr - (inst.fImmA * 2 * N);
+ this->appendAdjacentMultiSlotBinaryOp(pipeline, alloc, (ProgramOp)inst.fOp,
+ dst, src, inst.fImmA);
+ break;
+ }
+ case ALL_N_WAY_TERNARY_OP_CASES: {
+ float* src1 = tempStackPtr - (inst.fImmA * N);
+ float* src0 = tempStackPtr - (inst.fImmA * 2 * N);
+ float* dst = tempStackPtr - (inst.fImmA * 3 * N);
+ this->appendAdjacentNWayTernaryOp(pipeline, alloc, (ProgramOp)inst.fOp,
+ dst, src0, src1, inst.fImmA);
+ break;
+ }
+ case ALL_MULTI_SLOT_TERNARY_OP_CASES: {
+ float* src1 = tempStackPtr - (inst.fImmA * N);
+ float* src0 = tempStackPtr - (inst.fImmA * 2 * N);
+ float* dst = tempStackPtr - (inst.fImmA * 3 * N);
+ this->appendAdjacentMultiSlotTernaryOp(pipeline, alloc, (ProgramOp)inst.fOp,
+ dst, src0, src1, inst.fImmA);
+ break;
+ }
+ case BuilderOp::select: {
+ float* src = tempStackPtr - (inst.fImmA * N);
+ float* dst = tempStackPtr - (inst.fImmA * 2 * N);
+ this->appendCopySlotsMasked(pipeline, alloc, dst, src, inst.fImmA);
+ break;
+ }
+ case BuilderOp::copy_slot_masked:
+ this->appendCopySlotsMasked(pipeline, alloc, SlotA(), SlotB(), inst.fImmA);
+ break;
+
+ case BuilderOp::copy_slot_unmasked:
+ this->appendCopySlotsUnmasked(pipeline, alloc, SlotA(), SlotB(), inst.fImmA);
+ break;
+
+ case BuilderOp::zero_slot_unmasked:
+ this->appendMultiSlotUnaryOp(pipeline, ProgramOp::zero_slot_unmasked,
+ SlotA(), inst.fImmA);
+ break;
+
+ case BuilderOp::refract_4_floats: {
+ float* dst = tempStackPtr - (9 * N);
+ pipeline->push_back({ProgramOp::refract_4_floats, dst});
+ break;
+ }
+ case BuilderOp::inverse_mat2:
+ case BuilderOp::inverse_mat3:
+ case BuilderOp::inverse_mat4: {
+ float* dst = tempStackPtr - (inst.fImmA * N);
+ pipeline->push_back({(ProgramOp)inst.fOp, dst});
+ break;
+ }
+ case BuilderOp::dot_2_floats:
+ case BuilderOp::dot_3_floats:
+ case BuilderOp::dot_4_floats: {
+ float* dst = tempStackPtr - (inst.fImmA * 2 * N);
+ pipeline->push_back({(ProgramOp)inst.fOp, dst});
+ break;
+ }
+ case BuilderOp::swizzle_1:
+ case BuilderOp::swizzle_2:
+ case BuilderOp::swizzle_3:
+ case BuilderOp::swizzle_4: {
+ auto* ctx = alloc->make<SkRasterPipeline_SwizzleCtx>();
+ ctx->ptr = tempStackPtr - (N * inst.fImmA);
+ // Unpack component nybbles into byte-offsets pointing at stack slots.
+ unpack_nybbles_to_offsets(inst.fImmB, SkSpan(ctx->offsets));
+ pipeline->push_back({(ProgramOp)inst.fOp, ctx});
+ break;
+ }
+ case BuilderOp::shuffle: {
+ int consumed = inst.fImmA >> 16;
+ int generated = inst.fImmA & 0xFFFF;
+
+ auto* ctx = alloc->make<SkRasterPipeline_ShuffleCtx>();
+ ctx->ptr = tempStackPtr - (N * consumed);
+ ctx->count = generated;
+ // Unpack immB and immC from nybble form into the offset array.
+ unpack_nybbles_to_offsets(inst.fImmB, SkSpan(&ctx->offsets[0], 8));
+ unpack_nybbles_to_offsets(inst.fImmC, SkSpan(&ctx->offsets[8], 8));
+ pipeline->push_back({ProgramOp::shuffle, ctx});
+ break;
+ }
+ case BuilderOp::push_src_rgba: {
+ float* dst = tempStackPtr;
+ pipeline->push_back({ProgramOp::store_src, dst});
+ break;
+ }
+ case BuilderOp::push_dst_rgba: {
+ float* dst = tempStackPtr;
+ pipeline->push_back({ProgramOp::store_dst, dst});
+ break;
+ }
+ case BuilderOp::pop_src_rg: {
+ float* src = tempStackPtr - (2 * N);
+ pipeline->push_back({ProgramOp::load_src_rg, src});
+ break;
+ }
+ case BuilderOp::pop_src_rgba: {
+ float* src = tempStackPtr - (4 * N);
+ pipeline->push_back({ProgramOp::load_src, src});
+ break;
+ }
+ case BuilderOp::pop_dst_rgba: {
+ float* src = tempStackPtr - (4 * N);
+ pipeline->push_back({ProgramOp::load_dst, src});
+ break;
+ }
+ case BuilderOp::push_slots: {
+ float* dst = tempStackPtr;
+ this->appendCopySlotsUnmasked(pipeline, alloc, dst, SlotA(), inst.fImmA);
+ break;
+ }
+ case BuilderOp::copy_stack_to_slots_indirect:
+ case BuilderOp::push_slots_indirect:
+ case BuilderOp::push_uniform_indirect: {
+ // SlotA: fixed-range start
+ // SlotB: limit-range end
+ // immA: number of slots to copy
+ // immB: dynamic stack ID
+ ProgramOp op;
+ auto* ctx = alloc->make<SkRasterPipeline_CopyIndirectCtx>();
+ ctx->indirectOffset =
+ reinterpret_cast<const uint32_t*>(tempStackMap[inst.fImmB]) - (1 * N);
+ ctx->indirectLimit = inst.fSlotB - inst.fSlotA - inst.fImmA;
+ ctx->slots = inst.fImmA;
+ if (inst.fOp == BuilderOp::push_slots_indirect) {
+ op = ProgramOp::copy_from_indirect_unmasked;
+ ctx->src = SlotA();
+ ctx->dst = tempStackPtr;
+ } else if (inst.fOp == BuilderOp::push_uniform_indirect) {
+ op = ProgramOp::copy_from_indirect_uniform_unmasked;
+ ctx->src = UniformA();
+ ctx->dst = tempStackPtr;
+ } else {
+ op = ProgramOp::copy_to_indirect_masked;
+ ctx->src = tempStackPtr - (ctx->slots * N);
+ ctx->dst = SlotA();
+ }
+ pipeline->push_back({op, ctx});
+ break;
+ }
+ case BuilderOp::push_uniform: {
+ float* dst = tempStackPtr;
+ this->appendCopyConstants(pipeline, alloc, dst, UniformA(), inst.fImmA);
+ break;
+ }
+ case BuilderOp::push_zeros: {
+ float* dst = tempStackPtr;
+ this->appendMultiSlotUnaryOp(pipeline, ProgramOp::zero_slot_unmasked, dst,
+ inst.fImmA);
+ break;
+ }
+ case BuilderOp::push_condition_mask: {
+ float* dst = tempStackPtr;
+ pipeline->push_back({ProgramOp::store_condition_mask, dst});
+ break;
+ }
+ case BuilderOp::pop_condition_mask: {
+ float* src = tempStackPtr - (1 * N);
+ pipeline->push_back({ProgramOp::load_condition_mask, src});
+ break;
+ }
+ case BuilderOp::merge_condition_mask: {
+ float* ptr = tempStackPtr - (2 * N);
+ pipeline->push_back({ProgramOp::merge_condition_mask, ptr});
+ break;
+ }
+ case BuilderOp::push_loop_mask: {
+ float* dst = tempStackPtr;
+ pipeline->push_back({ProgramOp::store_loop_mask, dst});
+ break;
+ }
+ case BuilderOp::pop_loop_mask: {
+ float* src = tempStackPtr - (1 * N);
+ pipeline->push_back({ProgramOp::load_loop_mask, src});
+ break;
+ }
+ case BuilderOp::pop_and_reenable_loop_mask: {
+ float* src = tempStackPtr - (1 * N);
+ pipeline->push_back({ProgramOp::reenable_loop_mask, src});
+ break;
+ }
+ case BuilderOp::reenable_loop_mask:
+ pipeline->push_back({ProgramOp::reenable_loop_mask, SlotA()});
+ break;
+
+ case BuilderOp::mask_off_loop_mask:
+ pipeline->push_back({ProgramOp::mask_off_loop_mask, nullptr});
+ break;
+
+ case BuilderOp::merge_loop_mask: {
+ float* src = tempStackPtr - (1 * N);
+ pipeline->push_back({ProgramOp::merge_loop_mask, src});
+ break;
+ }
+ case BuilderOp::push_return_mask: {
+ float* dst = tempStackPtr;
+ pipeline->push_back({ProgramOp::store_return_mask, dst});
+ break;
+ }
+ case BuilderOp::pop_return_mask: {
+ float* src = tempStackPtr - (1 * N);
+ pipeline->push_back({ProgramOp::load_return_mask, src});
+ break;
+ }
+ case BuilderOp::mask_off_return_mask:
+ pipeline->push_back({ProgramOp::mask_off_return_mask, nullptr});
+ break;
+
+ case BuilderOp::copy_constant:
+ case BuilderOp::push_literal: {
+ float* dst = (inst.fOp == BuilderOp::push_literal) ? tempStackPtr : SlotA();
+ int* constantPtr;
+ if (int** lookup = constantLookupMap.find(inst.fImmA)) {
+ constantPtr = *lookup;
+ } else {
+ constantPtr = alloc->make<int>(inst.fImmA);
+ constantLookupMap[inst.fImmA] = constantPtr;
+ }
+ SkASSERT(constantPtr);
+ this->appendCopyConstants(pipeline, alloc, dst, (float*)constantPtr,/*numSlots=*/1);
+ break;
+ }
+ case BuilderOp::copy_stack_to_slots: {
+ float* src = tempStackPtr - (inst.fImmB * N);
+ this->appendCopySlotsMasked(pipeline, alloc, SlotA(), src, inst.fImmA);
+ break;
+ }
+ case BuilderOp::copy_stack_to_slots_unmasked: {
+ float* src = tempStackPtr - (inst.fImmB * N);
+ this->appendCopySlotsUnmasked(pipeline, alloc, SlotA(), src, inst.fImmA);
+ break;
+ }
+ case BuilderOp::swizzle_copy_stack_to_slots: {
+ // SlotA: fixed-range start
+ // immA: number of swizzle components
+ // immB: swizzle components
+ // immC: offset from stack top
+ auto stage = (ProgramOp)((int)ProgramOp::swizzle_copy_slot_masked + inst.fImmA - 1);
+ auto* ctx = alloc->make<SkRasterPipeline_SwizzleCopyCtx>();
+ ctx->src = tempStackPtr - (inst.fImmC * N);
+ ctx->dst = SlotA();
+ unpack_nybbles_to_offsets(inst.fImmB, SkSpan(ctx->offsets));
+ pipeline->push_back({stage, ctx});
+ break;
+ }
+ case BuilderOp::push_clone: {
+ float* src = tempStackPtr - (inst.fImmB * N);
+ float* dst = tempStackPtr;
+ this->appendCopySlotsUnmasked(pipeline, alloc, dst, src, inst.fImmA);
+ break;
+ }
+ case BuilderOp::push_clone_from_stack: {
+ // immA: number of slots
+ // immB: other stack ID
+ // immC: offset from stack top
+ float* sourceStackPtr = tempStackMap[inst.fImmB];
+ float* src = sourceStackPtr - (inst.fImmC * N);
+ float* dst = tempStackPtr;
+ this->appendCopySlotsUnmasked(pipeline, alloc, dst, src, inst.fImmA);
+ break;
+ }
+ case BuilderOp::push_clone_indirect_from_stack: {
+ // immA: number of slots
+ // immB: other stack ID
+ // immC: offset from stack top
+ // immD: dynamic stack ID
+ float* sourceStackPtr = tempStackMap[inst.fImmB];
+
+ auto* ctx = alloc->make<SkRasterPipeline_CopyIndirectCtx>();
+ ctx->dst = tempStackPtr;
+ ctx->src = sourceStackPtr - (inst.fImmC * N);
+ ctx->indirectOffset =
+ reinterpret_cast<const uint32_t*>(tempStackMap[inst.fImmD]) - (1 * N);
+ ctx->indirectLimit = inst.fImmC - inst.fImmA;
+ ctx->slots = inst.fImmA;
+ pipeline->push_back({ProgramOp::copy_from_indirect_unmasked, ctx});
+ break;
+ }
+ case BuilderOp::swizzle_copy_stack_to_slots_indirect: {
+ // SlotA: fixed-range start
+ // SlotB: limit-range end
+ // immA: number of swizzle components
+ // immB: swizzle components
+ // immC: offset from stack top
+ // immD: dynamic stack ID
+ auto* ctx = alloc->make<SkRasterPipeline_SwizzleCopyIndirectCtx>();
+ ctx->src = tempStackPtr - (inst.fImmC * N);
+ ctx->dst = SlotA();
+ ctx->indirectOffset =
+ reinterpret_cast<const uint32_t*>(tempStackMap[inst.fImmD]) - (1 * N);
+ ctx->indirectLimit =
+ inst.fSlotB - inst.fSlotA - (max_packed_nybble(inst.fImmB, inst.fImmA) + 1);
+ ctx->slots = inst.fImmA;
+ unpack_nybbles_to_offsets(inst.fImmB, SkSpan(ctx->offsets));
+ pipeline->push_back({ProgramOp::swizzle_copy_to_indirect_masked, ctx});
+ break;
+ }
+ case BuilderOp::case_op: {
+ auto* ctx = alloc->make<SkRasterPipeline_CaseOpCtx>();
+ ctx->ptr = reinterpret_cast<int*>(tempStackPtr - 2 * N);
+ ctx->expectedValue = inst.fImmA;
+ pipeline->push_back({ProgramOp::case_op, ctx});
+ break;
+ }
+ case BuilderOp::discard_stack:
+ break;
+
+ case BuilderOp::set_current_stack:
+ currentStack = inst.fImmA;
+ break;
+
+ case BuilderOp::invoke_shader:
+ case BuilderOp::invoke_color_filter:
+ case BuilderOp::invoke_blender:
+ pipeline->push_back({(ProgramOp)inst.fOp, context_bit_pun(inst.fImmA)});
+ break;
+
+ case BuilderOp::invoke_to_linear_srgb:
+ case BuilderOp::invoke_from_linear_srgb:
+ pipeline->push_back({(ProgramOp)inst.fOp, nullptr});
+ break;
+
+ default:
+ SkDEBUGFAILF("Raster Pipeline: unsupported instruction %d", (int)inst.fOp);
+ break;
+ }
+
+ tempStackPtr += stack_usage(inst) * N;
+ SkASSERT(tempStackPtr >= slots.stack.begin());
+ SkASSERT(tempStackPtr <= slots.stack.end());
+
+ // Periodically rewind the stack every 500 instructions. When SK_HAS_MUSTTAIL is set,
+ // rewinds are not actually used; the appendStackRewind call becomes a no-op. On platforms
+ // that don't support SK_HAS_MUSTTAIL, rewinding the stack periodically can prevent a
+ // potential stack overflow when running a long program.
+ int numPipelineStages = pipeline->size();
+ if (numPipelineStages - mostRecentRewind > 500) {
+ this->appendStackRewind(pipeline);
+ mostRecentRewind = numPipelineStages;
+ }
+ }
+}
+
+// Finds duplicate names in the program and disambiguates them with subscripts.
+SkTArray<std::string> build_unique_slot_name_list(const SkRPDebugTrace* debugTrace) {
+ SkTArray<std::string> slotName;
+ if (debugTrace) {
+ slotName.reserve_back(debugTrace->fSlotInfo.size());
+
+ // The map consists of <variable name, <source position, unique name>>.
+ SkTHashMap<std::string_view, SkTHashMap<int, std::string>> uniqueNameMap;
+
+ for (const SlotDebugInfo& slotInfo : debugTrace->fSlotInfo) {
+ // Look up this variable by its name and source position.
+ int pos = slotInfo.pos.valid() ? slotInfo.pos.startOffset() : 0;
+ SkTHashMap<int, std::string>& positionMap = uniqueNameMap[slotInfo.name];
+ std::string& uniqueName = positionMap[pos];
+
+ // Have we seen this variable name/position combination before?
+ if (uniqueName.empty()) {
+ // This is a unique name/position pair.
+ uniqueName = slotInfo.name;
+
+ // But if it's not a unique _name_, it deserves a subscript to disambiguate it.
+ int subscript = positionMap.count() - 1;
+ if (subscript > 0) {
+ for (char digit : std::to_string(subscript)) {
+ // U+2080 through U+2089 (₀₁₂₃₄₅₆₇₈₉) in UTF8:
+ uniqueName.push_back((char)0xE2);
+ uniqueName.push_back((char)0x82);
+ uniqueName.push_back((char)(0x80 + digit - '0'));
+ }
+ }
+ }
+
+ slotName.push_back(uniqueName);
+ }
+ }
+ return slotName;
+}
+
+void Program::dump(SkWStream* out) const {
+ // Allocate memory for the slot and uniform data, even though the program won't ever be
+ // executed. The program requires pointer ranges for managing its data, and ASAN will report
+ // errors if those pointers are pointing at unallocated memory.
+ SkArenaAlloc alloc(/*firstHeapAllocation=*/1000);
+ const int N = SkOpts::raster_pipeline_highp_stride;
+ SlotData slots = this->allocateSlotData(&alloc);
+ float* uniformPtr = alloc.makeArray<float>(fNumUniformSlots);
+ SkSpan<float> uniforms = SkSpan(uniformPtr, fNumUniformSlots);
+
+ // Turn this program into an array of Raster Pipeline stages.
+ SkTArray<Stage> stages;
+ this->makeStages(&stages, &alloc, uniforms, slots);
+
+ // Find the labels in the program, and keep track of their offsets.
+ SkTHashMap<int, int> labelToStageMap; // <label ID, stage index>
+ for (int index = 0; index < stages.size(); ++index) {
+ if (stages[index].op == ProgramOp::label) {
+ int labelID = sk_bit_cast<intptr_t>(stages[index].ctx);
+ SkASSERT(!labelToStageMap.find(labelID));
+ labelToStageMap[labelID] = index;
+ }
+ }
+
+ // Assign unique names to each variable slot; our trace might have multiple variables with the
+ // same name, which can make a dump hard to read.
+ SkTArray<std::string> slotName = build_unique_slot_name_list(fDebugTrace);
+
+ // Emit the program's instruction list.
+ for (int index = 0; index < stages.size(); ++index) {
+ const Stage& stage = stages[index];
+
+ // Interpret the context value as a branch offset.
+ auto BranchOffset = [&](const SkRasterPipeline_BranchCtx* ctx) -> std::string {
+ // The context's offset field contains a label ID
+ int labelID = ctx->offset;
+ SkASSERT(labelToStageMap.find(labelID));
+ int labelIndex = labelToStageMap[labelID];
+ return SkSL::String::printf("%+d (label %d at #%d)",
+ labelIndex - index, labelID, labelIndex + 1);
+ };
+
+ // Print a 32-bit immediate value of unknown type (int/float).
+ auto Imm = [&](float immFloat, bool showAsFloat = true) -> std::string {
+ // Start with `0x3F800000` as a baseline.
+ uint32_t immUnsigned;
+ memcpy(&immUnsigned, &immFloat, sizeof(uint32_t));
+ auto text = SkSL::String::printf("0x%08X", immUnsigned);
+
+ // Extend it to `0x3F800000 (1.0)` for finite floating point values.
+ if (showAsFloat && std::isfinite(immFloat)) {
+ text += " (";
+ text += skstd::to_string(immFloat);
+ text += ")";
+ }
+ return text;
+ };
+
+ // Interpret the context pointer as a 32-bit immediate value of unknown type (int/float).
+ auto ImmCtx = [&](const void* ctx, bool showAsFloat = true) -> std::string {
+ float f;
+ memcpy(&f, &ctx, sizeof(float));
+ return Imm(f, showAsFloat);
+ };
+
+ // Print `1` for single slots and `1..3` for ranges of slots.
+ auto AsRange = [](int first, int count) -> std::string {
+ std::string text = std::to_string(first);
+ if (count > 1) {
+ text += ".." + std::to_string(first + count - 1);
+ }
+ return text;
+ };
+
+ // Come up with a reasonable name for a range of slots, e.g.:
+ // `val`: slot range points at one variable, named val
+ // `val(0..1)`: slot range points at the first and second slot of val (which has 3+ slots)
+ // `foo, bar`: slot range fully covers two variables, named foo and bar
+ // `foo(3), bar(0)`: slot range covers the fourth slot of foo and the first slot of bar
+ auto SlotName = [&](SkSpan<const SlotDebugInfo> debugInfo,
+ SkSpan<const std::string> names,
+ SlotRange range) -> std::string {
+ SkASSERT(range.index >= 0 && (range.index + range.count) <= (int)debugInfo.size());
+
+ std::string text;
+ auto separator = SkSL::String::Separator();
+ while (range.count > 0) {
+ const SlotDebugInfo& slotInfo = debugInfo[range.index];
+ text += separator();
+ text += names.empty() ? slotInfo.name : names[range.index];
+
+ // Figure out how many slots we can chomp in this iteration.
+ int entireVariable = slotInfo.columns * slotInfo.rows;
+ int slotsToChomp = std::min(range.count, entireVariable - slotInfo.componentIndex);
+ // If we aren't consuming an entire variable, from first slot to last...
+ if (slotsToChomp != entireVariable) {
+ // ... decorate it with a range suffix.
+ text += "(" + AsRange(slotInfo.componentIndex, slotsToChomp) + ")";
+ }
+ range.index += slotsToChomp;
+ range.count -= slotsToChomp;
+ }
+
+ return text;
+ };
+
+ // Attempts to interpret the passed-in pointer as a uniform range.
+ auto UniformPtrCtx = [&](const float* ptr, int numSlots) -> std::string {
+ const float* end = ptr + numSlots;
+ if (ptr >= uniforms.begin() && end <= uniforms.end()) {
+ int uniformIdx = ptr - uniforms.begin();
+ if (fDebugTrace) {
+ // Handle pointers to named uniform slots.
+ std::string name = SlotName(fDebugTrace->fUniformInfo, /*names=*/{},
+ {uniformIdx, numSlots});
+ if (!name.empty()) {
+ return name;
+ }
+ }
+ // Handle pointers to uniforms (when no debug info exists).
+ return "u" + AsRange(uniformIdx, numSlots);
+ }
+ return {};
+ };
+
+ // Attempts to interpret the passed-in pointer as a value slot range.
+ auto ValuePtrCtx = [&](const float* ptr, int numSlots) -> std::string {
+ const float* end = ptr + (N * numSlots);
+ if (ptr >= slots.values.begin() && end <= slots.values.end()) {
+ int valueIdx = ptr - slots.values.begin();
+ SkASSERT((valueIdx % N) == 0);
+ valueIdx /= N;
+ if (fDebugTrace) {
+ // Handle pointers to named value slots.
+ std::string name = SlotName(fDebugTrace->fSlotInfo, slotName,
+ {valueIdx, numSlots});
+ if (!name.empty()) {
+ return name;
+ }
+ }
+ // Handle pointers to value slots (when no debug info exists).
+ return "v" + AsRange(valueIdx, numSlots);
+ }
+ return {};
+ };
+
+ // Interpret the context value as a pointer to `count` immediate values.
+ auto MultiImmCtx = [&](const float* ptr, int count) -> std::string {
+ // If this is a uniform, print it by name.
+ if (std::string text = UniformPtrCtx(ptr, count); !text.empty()) {
+ return text;
+ }
+ // Emit a single unbracketed immediate.
+ if (count == 1) {
+ return Imm(*ptr);
+ }
+ // Emit a list like `[0x00000000 (0.0), 0x3F80000 (1.0)]`.
+ std::string text = "[";
+ auto separator = SkSL::String::Separator();
+ while (count--) {
+ text += separator();
+ text += Imm(*ptr++);
+ }
+ return text + "]";
+ };
+
+ // Interpret the context value as a generic pointer.
+ auto PtrCtx = [&](const void* ctx, int numSlots) -> std::string {
+ const float *ctxAsSlot = static_cast<const float*>(ctx);
+ // Check for uniform and value pointers.
+ if (std::string uniform = UniformPtrCtx(ctxAsSlot, numSlots); !uniform.empty()) {
+ return uniform;
+ }
+ if (std::string value = ValuePtrCtx(ctxAsSlot, numSlots); !value.empty()) {
+ return value;
+ }
+ // Handle pointers to temporary stack slots.
+ if (ctxAsSlot >= slots.stack.begin() && ctxAsSlot < slots.stack.end()) {
+ int stackIdx = ctxAsSlot - slots.stack.begin();
+ SkASSERT((stackIdx % N) == 0);
+ return "$" + AsRange(stackIdx / N, numSlots);
+ }
+ // This pointer is out of our expected bounds; this generally isn't expected to happen.
+ return "ExternalPtr(" + AsRange(0, numSlots) + ")";
+ };
+
+ // Interpret the context value as a pointer to two adjacent values.
+ auto AdjacentPtrCtx = [&](const void* ctx,
+ int numSlots) -> std::tuple<std::string, std::string> {
+ const float *ctxAsSlot = static_cast<const float*>(ctx);
+ return std::make_tuple(PtrCtx(ctxAsSlot, numSlots),
+ PtrCtx(ctxAsSlot + (N * numSlots), numSlots));
+ };
+
+ // Interpret the context value as a pointer to three adjacent values.
+ auto Adjacent3PtrCtx = [&](const void* ctx, int numSlots) ->
+ std::tuple<std::string, std::string, std::string> {
+ const float *ctxAsSlot = static_cast<const float*>(ctx);
+ return std::make_tuple(PtrCtx(ctxAsSlot, numSlots),
+ PtrCtx(ctxAsSlot + (N * numSlots), numSlots),
+ PtrCtx(ctxAsSlot + (2 * N * numSlots), numSlots));
+ };
+
+ // Interpret the context value as a BinaryOp structure for copy_n_slots (numSlots is
+ // dictated by the op itself).
+ auto BinaryOpCtx = [&](const void* v,
+ int numSlots) -> std::tuple<std::string, std::string> {
+ const auto *ctx = static_cast<const SkRasterPipeline_BinaryOpCtx*>(v);
+ return std::make_tuple(PtrCtx(ctx->dst, numSlots),
+ PtrCtx(ctx->src, numSlots));
+ };
+
+ // Interpret the context value as a BinaryOp structure for copy_n_constants (numSlots is
+ // dictated by the op itself).
+ auto CopyConstantCtx = [&](const void* v,
+ int numSlots) -> std::tuple<std::string, std::string> {
+ const auto *ctx = static_cast<const SkRasterPipeline_BinaryOpCtx*>(v);
+ return std::make_tuple(PtrCtx(ctx->dst, numSlots),
+ MultiImmCtx(ctx->src, numSlots));
+ };
+
+ // Interpret the context value as a BinaryOp structure (numSlots is inferred from the
+ // distance between pointers).
+ auto AdjacentBinaryOpCtx = [&](const void* v) -> std::tuple<std::string, std::string> {
+ const auto *ctx = static_cast<const SkRasterPipeline_BinaryOpCtx*>(v);
+ int numSlots = (ctx->src - ctx->dst) / N;
+ return AdjacentPtrCtx(ctx->dst, numSlots);
+ };
+
+ // Interpret the context value as a TernaryOp structure (numSlots is inferred from the
+ // distance between pointers).
+ auto AdjacentTernaryOpCtx = [&](const void* v) ->
+ std::tuple<std::string, std::string, std::string> {
+ const auto* ctx = static_cast<const SkRasterPipeline_TernaryOpCtx*>(v);
+ int numSlots = (ctx->src0 - ctx->dst) / N;
+ return Adjacent3PtrCtx(ctx->dst, numSlots);
+ };
+
+ // Stringize a span of swizzle offsets to the textual equivalent (`xyzw`).
+ auto SwizzleOffsetSpan = [&](SkSpan<const uint16_t> offsets) {
+ std::string src;
+ for (uint16_t offset : offsets) {
+ if (offset == (0 * N * sizeof(float))) {
+ src.push_back('x');
+ } else if (offset == (1 * N * sizeof(float))) {
+ src.push_back('y');
+ } else if (offset == (2 * N * sizeof(float))) {
+ src.push_back('z');
+ } else if (offset == (3 * N * sizeof(float))) {
+ src.push_back('w');
+ } else {
+ src.push_back('?');
+ }
+ }
+ return src;
+ };
+
+ // When we decode a swizzle, we don't know the slot width of the original value; that's not
+ // preserved in the instruction encoding. (e.g., myFloat4.y would be indistinguishable from
+ // myFloat2.y.) We do our best to make a readable dump using the data we have.
+ auto SwizzleWidth = [&](SkSpan<const uint16_t> offsets) {
+ size_t highestComponent = *std::max_element(offsets.begin(), offsets.end()) /
+ (N * sizeof(float));
+ size_t swizzleWidth = offsets.size();
+ return std::max(swizzleWidth, highestComponent + 1);
+ };
+
+ // Stringize a swizzled pointer.
+ auto SwizzlePtr = [&](const float* ptr, SkSpan<const uint16_t> offsets) {
+ return "(" + PtrCtx(ptr, SwizzleWidth(offsets)) + ")." + SwizzleOffsetSpan(offsets);
+ };
+
+ // Interpret the context value as a Swizzle structure.
+ auto SwizzleCtx = [&](ProgramOp op, const void* v) -> std::tuple<std::string, std::string> {
+ const auto* ctx = static_cast<const SkRasterPipeline_SwizzleCtx*>(v);
+ int destSlots = (int)op - (int)BuilderOp::swizzle_1 + 1;
+
+ return std::make_tuple(PtrCtx(ctx->ptr, destSlots),
+ SwizzlePtr(ctx->ptr, SkSpan(ctx->offsets, destSlots)));
+ };
+
+ // Interpret the context value as a SwizzleCopy structure.
+ auto SwizzleCopyCtx = [&](ProgramOp op,
+ const void* v) -> std::tuple<std::string, std::string> {
+ const auto* ctx = static_cast<const SkRasterPipeline_SwizzleCopyCtx*>(v);
+ int destSlots = (int)op - (int)BuilderOp::swizzle_copy_slot_masked + 1;
+
+ return std::make_tuple(SwizzlePtr(ctx->dst, SkSpan(ctx->offsets, destSlots)),
+ PtrCtx(ctx->src, destSlots));
+ };
+
+ // Interpret the context value as a Shuffle structure.
+ auto ShuffleCtx = [&](const void* v) -> std::tuple<std::string, std::string> {
+ const auto* ctx = static_cast<const SkRasterPipeline_ShuffleCtx*>(v);
+
+ std::string dst = PtrCtx(ctx->ptr, ctx->count);
+ std::string src = "(" + dst + ")[";
+ for (int index = 0; index < ctx->count; ++index) {
+ if (ctx->offsets[index] % (N * sizeof(float))) {
+ src.push_back('?');
+ } else {
+ src += std::to_string(ctx->offsets[index] / (N * sizeof(float)));
+ }
+ src.push_back(' ');
+ }
+ src.back() = ']';
+ return std::make_tuple(dst, src);
+ };
+
+ std::string opArg1, opArg2, opArg3, opSwizzle;
+ using POp = ProgramOp;
+ switch (stage.op) {
+ case POp::label:
+ case POp::invoke_shader:
+ case POp::invoke_color_filter:
+ case POp::invoke_blender:
+ opArg1 = ImmCtx(stage.ctx, /*showAsFloat=*/false);
+ break;
+
+ case POp::case_op: {
+ const auto* ctx = static_cast<SkRasterPipeline_CaseOpCtx*>(stage.ctx);
+ opArg1 = PtrCtx(ctx->ptr, 1);
+ opArg2 = PtrCtx(ctx->ptr + N, 1);
+ opArg3 = Imm(sk_bit_cast<float>(ctx->expectedValue), /*showAsFloat=*/false);
+ break;
+ }
+ case POp::swizzle_1:
+ case POp::swizzle_2:
+ case POp::swizzle_3:
+ case POp::swizzle_4:
+ std::tie(opArg1, opArg2) = SwizzleCtx(stage.op, stage.ctx);
+ break;
+
+ case POp::swizzle_copy_slot_masked:
+ case POp::swizzle_copy_2_slots_masked:
+ case POp::swizzle_copy_3_slots_masked:
+ case POp::swizzle_copy_4_slots_masked:
+ std::tie(opArg1, opArg2) = SwizzleCopyCtx(stage.op, stage.ctx);
+ break;
+
+ case POp::refract_4_floats:
+ std::tie(opArg1, opArg2) = AdjacentPtrCtx(stage.ctx, 4);
+ opArg3 = PtrCtx((const float*)(stage.ctx) + (8 * N), 1);
+ break;
+
+ case POp::dot_2_floats:
+ opArg1 = PtrCtx(stage.ctx, 1);
+ std::tie(opArg2, opArg3) = AdjacentPtrCtx(stage.ctx, 2);
+ break;
+
+ case POp::dot_3_floats:
+ opArg1 = PtrCtx(stage.ctx, 1);
+ std::tie(opArg2, opArg3) = AdjacentPtrCtx(stage.ctx, 3);
+ break;
+
+ case POp::dot_4_floats:
+ opArg1 = PtrCtx(stage.ctx, 1);
+ std::tie(opArg2, opArg3) = AdjacentPtrCtx(stage.ctx, 4);
+ break;
+
+ case POp::shuffle:
+ std::tie(opArg1, opArg2) = ShuffleCtx(stage.ctx);
+ break;
+
+ case POp::load_condition_mask:
+ case POp::store_condition_mask:
+ case POp::load_loop_mask:
+ case POp::store_loop_mask:
+ case POp::merge_loop_mask:
+ case POp::reenable_loop_mask:
+ case POp::load_return_mask:
+ case POp::store_return_mask:
+ case POp::zero_slot_unmasked:
+ case POp::bitwise_not_int:
+ case POp::cast_to_float_from_int: case POp::cast_to_float_from_uint:
+ case POp::cast_to_int_from_float: case POp::cast_to_uint_from_float:
+ case POp::abs_float: case POp::abs_int:
+ case POp::acos_float:
+ case POp::asin_float:
+ case POp::atan_float:
+ case POp::ceil_float:
+ case POp::cos_float:
+ case POp::exp_float:
+ case POp::exp2_float:
+ case POp::log_float:
+ case POp::log2_float:
+ case POp::floor_float:
+ case POp::invsqrt_float:
+ case POp::sin_float:
+ case POp::sqrt_float:
+ case POp::tan_float:
+ opArg1 = PtrCtx(stage.ctx, 1);
+ break;
+
+ case POp::zero_2_slots_unmasked:
+ case POp::bitwise_not_2_ints:
+ case POp::load_src_rg: case POp::store_src_rg:
+ case POp::cast_to_float_from_2_ints: case POp::cast_to_float_from_2_uints:
+ case POp::cast_to_int_from_2_floats: case POp::cast_to_uint_from_2_floats:
+ case POp::abs_2_floats: case POp::abs_2_ints:
+ case POp::ceil_2_floats:
+ case POp::floor_2_floats:
+ case POp::invsqrt_2_floats:
+ opArg1 = PtrCtx(stage.ctx, 2);
+ break;
+
+ case POp::zero_3_slots_unmasked:
+ case POp::bitwise_not_3_ints:
+ case POp::cast_to_float_from_3_ints: case POp::cast_to_float_from_3_uints:
+ case POp::cast_to_int_from_3_floats: case POp::cast_to_uint_from_3_floats:
+ case POp::abs_3_floats: case POp::abs_3_ints:
+ case POp::ceil_3_floats:
+ case POp::floor_3_floats:
+ case POp::invsqrt_3_floats:
+ opArg1 = PtrCtx(stage.ctx, 3);
+ break;
+
+ case POp::load_src:
+ case POp::load_dst:
+ case POp::store_src:
+ case POp::store_dst:
+ case POp::store_device_xy01:
+ case POp::zero_4_slots_unmasked:
+ case POp::bitwise_not_4_ints:
+ case POp::cast_to_float_from_4_ints: case POp::cast_to_float_from_4_uints:
+ case POp::cast_to_int_from_4_floats: case POp::cast_to_uint_from_4_floats:
+ case POp::abs_4_floats: case POp::abs_4_ints:
+ case POp::ceil_4_floats:
+ case POp::floor_4_floats:
+ case POp::invsqrt_4_floats:
+ case POp::inverse_mat2:
+ opArg1 = PtrCtx(stage.ctx, 4);
+ break;
+
+ case POp::inverse_mat3:
+ opArg1 = PtrCtx(stage.ctx, 9);
+ break;
+
+ case POp::inverse_mat4:
+ opArg1 = PtrCtx(stage.ctx, 16);
+ break;
+
+
+ case POp::copy_constant:
+ std::tie(opArg1, opArg2) = CopyConstantCtx(stage.ctx, 1);
+ break;
+
+ case POp::copy_2_constants:
+ std::tie(opArg1, opArg2) = CopyConstantCtx(stage.ctx, 2);
+ break;
+
+ case POp::copy_3_constants:
+ std::tie(opArg1, opArg2) = CopyConstantCtx(stage.ctx, 3);
+ break;
+
+ case POp::copy_4_constants:
+ std::tie(opArg1, opArg2) = CopyConstantCtx(stage.ctx, 4);
+ break;
+
+ case POp::copy_slot_masked:
+ case POp::copy_slot_unmasked:
+ std::tie(opArg1, opArg2) = BinaryOpCtx(stage.ctx, 1);
+ break;
+
+ case POp::copy_2_slots_masked:
+ case POp::copy_2_slots_unmasked:
+ std::tie(opArg1, opArg2) = BinaryOpCtx(stage.ctx, 2);
+ break;
+
+ case POp::copy_3_slots_masked:
+ case POp::copy_3_slots_unmasked:
+ std::tie(opArg1, opArg2) = BinaryOpCtx(stage.ctx, 3);
+ break;
+
+ case POp::copy_4_slots_masked:
+ case POp::copy_4_slots_unmasked:
+ std::tie(opArg1, opArg2) = BinaryOpCtx(stage.ctx, 4);
+ break;
+
+ case POp::copy_from_indirect_unmasked:
+ case POp::copy_to_indirect_masked: {
+ const auto* ctx = static_cast<SkRasterPipeline_CopyIndirectCtx*>(stage.ctx);
+ // We don't incorporate the indirect-limit in the output
+ opArg1 = PtrCtx(ctx->dst, ctx->slots);
+ opArg2 = PtrCtx(ctx->src, ctx->slots);
+ opArg3 = PtrCtx(ctx->indirectOffset, 1);
+ break;
+ }
+ case POp::copy_from_indirect_uniform_unmasked: {
+ const auto* ctx = static_cast<SkRasterPipeline_CopyIndirectCtx*>(stage.ctx);
+ opArg1 = PtrCtx(ctx->dst, ctx->slots);
+ opArg2 = UniformPtrCtx(ctx->src, ctx->slots);
+ opArg3 = PtrCtx(ctx->indirectOffset, 1);
+ break;
+ }
+ case POp::swizzle_copy_to_indirect_masked: {
+ const auto* ctx = static_cast<SkRasterPipeline_SwizzleCopyIndirectCtx*>(stage.ctx);
+ opArg1 = PtrCtx(ctx->dst, SwizzleWidth(SkSpan(ctx->offsets, ctx->slots)));
+ opArg2 = PtrCtx(ctx->src, ctx->slots);
+ opArg3 = PtrCtx(ctx->indirectOffset, 1);
+ opSwizzle = SwizzleOffsetSpan(SkSpan(ctx->offsets, ctx->slots));
+ break;
+ }
+ case POp::merge_condition_mask:
+ case POp::add_float: case POp::add_int:
+ case POp::sub_float: case POp::sub_int:
+ case POp::mul_float: case POp::mul_int:
+ case POp::div_float: case POp::div_int: case POp::div_uint:
+ case POp::bitwise_and_int:
+ case POp::bitwise_or_int:
+ case POp::bitwise_xor_int:
+ case POp::mod_float:
+ case POp::min_float: case POp::min_int: case POp::min_uint:
+ case POp::max_float: case POp::max_int: case POp::max_uint:
+ case POp::cmplt_float: case POp::cmplt_int: case POp::cmplt_uint:
+ case POp::cmple_float: case POp::cmple_int: case POp::cmple_uint:
+ case POp::cmpeq_float: case POp::cmpeq_int:
+ case POp::cmpne_float: case POp::cmpne_int:
+ std::tie(opArg1, opArg2) = AdjacentPtrCtx(stage.ctx, 1);
+ break;
+
+ case POp::mix_float: case POp::mix_int:
+ std::tie(opArg1, opArg2, opArg3) = Adjacent3PtrCtx(stage.ctx, 1);
+ break;
+
+ case POp::add_2_floats: case POp::add_2_ints:
+ case POp::sub_2_floats: case POp::sub_2_ints:
+ case POp::mul_2_floats: case POp::mul_2_ints:
+ case POp::div_2_floats: case POp::div_2_ints: case POp::div_2_uints:
+ case POp::bitwise_and_2_ints:
+ case POp::bitwise_or_2_ints:
+ case POp::bitwise_xor_2_ints:
+ case POp::mod_2_floats:
+ case POp::min_2_floats: case POp::min_2_ints: case POp::min_2_uints:
+ case POp::max_2_floats: case POp::max_2_ints: case POp::max_2_uints:
+ case POp::cmplt_2_floats: case POp::cmplt_2_ints: case POp::cmplt_2_uints:
+ case POp::cmple_2_floats: case POp::cmple_2_ints: case POp::cmple_2_uints:
+ case POp::cmpeq_2_floats: case POp::cmpeq_2_ints:
+ case POp::cmpne_2_floats: case POp::cmpne_2_ints:
+ std::tie(opArg1, opArg2) = AdjacentPtrCtx(stage.ctx, 2);
+ break;
+
+ case POp::mix_2_floats: case POp::mix_2_ints:
+ std::tie(opArg1, opArg2, opArg3) = Adjacent3PtrCtx(stage.ctx, 2);
+ break;
+
+ case POp::add_3_floats: case POp::add_3_ints:
+ case POp::sub_3_floats: case POp::sub_3_ints:
+ case POp::mul_3_floats: case POp::mul_3_ints:
+ case POp::div_3_floats: case POp::div_3_ints: case POp::div_3_uints:
+ case POp::bitwise_and_3_ints:
+ case POp::bitwise_or_3_ints:
+ case POp::bitwise_xor_3_ints:
+ case POp::mod_3_floats:
+ case POp::min_3_floats: case POp::min_3_ints: case POp::min_3_uints:
+ case POp::max_3_floats: case POp::max_3_ints: case POp::max_3_uints:
+ case POp::cmplt_3_floats: case POp::cmplt_3_ints: case POp::cmplt_3_uints:
+ case POp::cmple_3_floats: case POp::cmple_3_ints: case POp::cmple_3_uints:
+ case POp::cmpeq_3_floats: case POp::cmpeq_3_ints:
+ case POp::cmpne_3_floats: case POp::cmpne_3_ints:
+ std::tie(opArg1, opArg2) = AdjacentPtrCtx(stage.ctx, 3);
+ break;
+
+ case POp::mix_3_floats: case POp::mix_3_ints:
+ std::tie(opArg1, opArg2, opArg3) = Adjacent3PtrCtx(stage.ctx, 3);
+ break;
+
+ case POp::add_4_floats: case POp::add_4_ints:
+ case POp::sub_4_floats: case POp::sub_4_ints:
+ case POp::mul_4_floats: case POp::mul_4_ints:
+ case POp::div_4_floats: case POp::div_4_ints: case POp::div_4_uints:
+ case POp::bitwise_and_4_ints:
+ case POp::bitwise_or_4_ints:
+ case POp::bitwise_xor_4_ints:
+ case POp::mod_4_floats:
+ case POp::min_4_floats: case POp::min_4_ints: case POp::min_4_uints:
+ case POp::max_4_floats: case POp::max_4_ints: case POp::max_4_uints:
+ case POp::cmplt_4_floats: case POp::cmplt_4_ints: case POp::cmplt_4_uints:
+ case POp::cmple_4_floats: case POp::cmple_4_ints: case POp::cmple_4_uints:
+ case POp::cmpeq_4_floats: case POp::cmpeq_4_ints:
+ case POp::cmpne_4_floats: case POp::cmpne_4_ints:
+ std::tie(opArg1, opArg2) = AdjacentPtrCtx(stage.ctx, 4);
+ break;
+
+ case POp::mix_4_floats: case POp::mix_4_ints:
+ std::tie(opArg1, opArg2, opArg3) = Adjacent3PtrCtx(stage.ctx, 4);
+ break;
+
+ case POp::add_n_floats: case POp::add_n_ints:
+ case POp::sub_n_floats: case POp::sub_n_ints:
+ case POp::mul_n_floats: case POp::mul_n_ints:
+ case POp::div_n_floats: case POp::div_n_ints: case POp::div_n_uints:
+ case POp::bitwise_and_n_ints:
+ case POp::bitwise_or_n_ints:
+ case POp::bitwise_xor_n_ints:
+ case POp::mod_n_floats:
+ case POp::min_n_floats: case POp::min_n_ints: case POp::min_n_uints:
+ case POp::max_n_floats: case POp::max_n_ints: case POp::max_n_uints:
+ case POp::cmplt_n_floats: case POp::cmplt_n_ints: case POp::cmplt_n_uints:
+ case POp::cmple_n_floats: case POp::cmple_n_ints: case POp::cmple_n_uints:
+ case POp::cmpeq_n_floats: case POp::cmpeq_n_ints:
+ case POp::cmpne_n_floats: case POp::cmpne_n_ints:
+ case POp::atan2_n_floats:
+ case POp::pow_n_floats:
+ std::tie(opArg1, opArg2) = AdjacentBinaryOpCtx(stage.ctx);
+ break;
+
+ case POp::mix_n_floats: case POp::mix_n_ints:
+ case POp::smoothstep_n_floats:
+ std::tie(opArg1, opArg2, opArg3) = AdjacentTernaryOpCtx(stage.ctx);
+ break;
+
+ case POp::jump:
+ case POp::branch_if_all_lanes_active:
+ case POp::branch_if_any_lanes_active:
+ case POp::branch_if_no_lanes_active:
+ opArg1 = BranchOffset(static_cast<SkRasterPipeline_BranchCtx*>(stage.ctx));
+ break;
+
+ case POp::branch_if_no_active_lanes_eq: {
+ const auto* ctx = static_cast<SkRasterPipeline_BranchIfEqualCtx*>(stage.ctx);
+ opArg1 = BranchOffset(ctx);
+ opArg2 = PtrCtx(ctx->ptr, 1);
+ opArg3 = Imm(sk_bit_cast<float>(ctx->value));
+ break;
+ }
+ default:
+ break;
+ }
+
+ std::string_view opName;
+ switch (stage.op) {
+ #define M(x) case POp::x: opName = #x; break;
+ SK_RASTER_PIPELINE_OPS_ALL(M)
+ #undef M
+ case POp::label: opName = "label"; break;
+ case POp::invoke_shader: opName = "invoke_shader"; break;
+ case POp::invoke_color_filter: opName = "invoke_color_filter"; break;
+ case POp::invoke_blender: opName = "invoke_blender"; break;
+ case POp::invoke_to_linear_srgb: opName = "invoke_to_linear_srgb"; break;
+ case POp::invoke_from_linear_srgb: opName = "invoke_from_linear_srgb"; break;
+ }
+
+ std::string opText;
+ switch (stage.op) {
+ case POp::init_lane_masks:
+ opText = "CondMask = LoopMask = RetMask = true";
+ break;
+
+ case POp::load_condition_mask:
+ opText = "CondMask = " + opArg1;
+ break;
+
+ case POp::store_condition_mask:
+ opText = opArg1 + " = CondMask";
+ break;
+
+ case POp::merge_condition_mask:
+ opText = "CondMask = " + opArg1 + " & " + opArg2;
+ break;
+
+ case POp::load_loop_mask:
+ opText = "LoopMask = " + opArg1;
+ break;
+
+ case POp::store_loop_mask:
+ opText = opArg1 + " = LoopMask";
+ break;
+
+ case POp::mask_off_loop_mask:
+ opText = "LoopMask &= ~(CondMask & LoopMask & RetMask)";
+ break;
+
+ case POp::reenable_loop_mask:
+ opText = "LoopMask |= " + opArg1;
+ break;
+
+ case POp::merge_loop_mask:
+ opText = "LoopMask &= " + opArg1;
+ break;
+
+ case POp::load_return_mask:
+ opText = "RetMask = " + opArg1;
+ break;
+
+ case POp::store_return_mask:
+ opText = opArg1 + " = RetMask";
+ break;
+
+ case POp::mask_off_return_mask:
+ opText = "RetMask &= ~(CondMask & LoopMask & RetMask)";
+ break;
+
+ case POp::store_src_rg:
+ opText = opArg1 + " = src.rg";
+ break;
+
+ case POp::store_src:
+ opText = opArg1 + " = src.rgba";
+ break;
+
+ case POp::store_dst:
+ opText = opArg1 + " = dst.rgba";
+ break;
+
+ case POp::store_device_xy01:
+ opText = opArg1 + " = DeviceCoords.xy01";
+ break;
+
+ case POp::load_src_rg:
+ opText = "src.rg = " + opArg1;
+ break;
+
+ case POp::load_src:
+ opText = "src.rgba = " + opArg1;
+ break;
+
+ case POp::load_dst:
+ opText = "dst.rgba = " + opArg1;
+ break;
+
+ case POp::bitwise_and_int:
+ case POp::bitwise_and_2_ints:
+ case POp::bitwise_and_3_ints:
+ case POp::bitwise_and_4_ints:
+ case POp::bitwise_and_n_ints:
+ opText = opArg1 + " &= " + opArg2;
+ break;
+
+ case POp::bitwise_or_int:
+ case POp::bitwise_or_2_ints:
+ case POp::bitwise_or_3_ints:
+ case POp::bitwise_or_4_ints:
+ case POp::bitwise_or_n_ints:
+ opText = opArg1 + " |= " + opArg2;
+ break;
+
+ case POp::bitwise_xor_int:
+ case POp::bitwise_xor_2_ints:
+ case POp::bitwise_xor_3_ints:
+ case POp::bitwise_xor_4_ints:
+ case POp::bitwise_xor_n_ints:
+ opText = opArg1 + " ^= " + opArg2;
+ break;
+
+ case POp::bitwise_not_int:
+ case POp::bitwise_not_2_ints:
+ case POp::bitwise_not_3_ints:
+ case POp::bitwise_not_4_ints:
+ opText = opArg1 + " = ~" + opArg1;
+ break;
+
+ case POp::cast_to_float_from_int:
+ case POp::cast_to_float_from_2_ints:
+ case POp::cast_to_float_from_3_ints:
+ case POp::cast_to_float_from_4_ints:
+ opText = opArg1 + " = IntToFloat(" + opArg1 + ")";
+ break;
+
+ case POp::cast_to_float_from_uint:
+ case POp::cast_to_float_from_2_uints:
+ case POp::cast_to_float_from_3_uints:
+ case POp::cast_to_float_from_4_uints:
+ opText = opArg1 + " = UintToFloat(" + opArg1 + ")";
+ break;
+
+ case POp::cast_to_int_from_float:
+ case POp::cast_to_int_from_2_floats:
+ case POp::cast_to_int_from_3_floats:
+ case POp::cast_to_int_from_4_floats:
+ opText = opArg1 + " = FloatToInt(" + opArg1 + ")";
+ break;
+
+ case POp::cast_to_uint_from_float:
+ case POp::cast_to_uint_from_2_floats:
+ case POp::cast_to_uint_from_3_floats:
+ case POp::cast_to_uint_from_4_floats:
+ opText = opArg1 + " = FloatToUint(" + opArg1 + ")";
+ break;
+
+ case POp::copy_slot_masked: case POp::copy_2_slots_masked:
+ case POp::copy_3_slots_masked: case POp::copy_4_slots_masked:
+ case POp::swizzle_copy_slot_masked: case POp::swizzle_copy_2_slots_masked:
+ case POp::swizzle_copy_3_slots_masked: case POp::swizzle_copy_4_slots_masked:
+ opText = opArg1 + " = Mask(" + opArg2 + ")";
+ break;
+
+ case POp::copy_constant: case POp::copy_2_constants:
+ case POp::copy_3_constants: case POp::copy_4_constants:
+ case POp::copy_slot_unmasked: case POp::copy_2_slots_unmasked:
+ case POp::copy_3_slots_unmasked: case POp::copy_4_slots_unmasked:
+ case POp::swizzle_1: case POp::swizzle_2:
+ case POp::swizzle_3: case POp::swizzle_4:
+ case POp::shuffle:
+ opText = opArg1 + " = " + opArg2;
+ break;
+
+ case POp::copy_from_indirect_unmasked:
+ case POp::copy_from_indirect_uniform_unmasked:
+ opText = opArg1 + " = Indirect(" + opArg2 + " + " + opArg3 + ")";
+ break;
+
+ case POp::copy_to_indirect_masked:
+ opText = "Indirect(" + opArg1 + " + " + opArg3 + ") = Mask(" + opArg2 + ")";
+ break;
+
+ case POp::swizzle_copy_to_indirect_masked:
+ opText = "Indirect(" + opArg1 + " + " + opArg3 + ")." + opSwizzle + " = Mask(" +
+ opArg2 + ")";
+ break;
+
+ case POp::zero_slot_unmasked: case POp::zero_2_slots_unmasked:
+ case POp::zero_3_slots_unmasked: case POp::zero_4_slots_unmasked:
+ opText = opArg1 + " = 0";
+ break;
+
+ case POp::abs_float: case POp::abs_int:
+ case POp::abs_2_floats: case POp::abs_2_ints:
+ case POp::abs_3_floats: case POp::abs_3_ints:
+ case POp::abs_4_floats: case POp::abs_4_ints:
+ opText = opArg1 + " = abs(" + opArg1 + ")";
+ break;
+
+ case POp::acos_float:
+ opText = opArg1 + " = acos(" + opArg1 + ")";
+ break;
+
+ case POp::asin_float:
+ opText = opArg1 + " = asin(" + opArg1 + ")";
+ break;
+
+ case POp::atan_float:
+ opText = opArg1 + " = atan(" + opArg1 + ")";
+ break;
+
+ case POp::atan2_n_floats:
+ opText = opArg1 + " = atan2(" + opArg1 + ", " + opArg2 + ")";
+ break;
+
+ case POp::ceil_float:
+ case POp::ceil_2_floats:
+ case POp::ceil_3_floats:
+ case POp::ceil_4_floats:
+ opText = opArg1 + " = ceil(" + opArg1 + ")";
+ break;
+
+ case POp::cos_float:
+ opText = opArg1 + " = cos(" + opArg1 + ")";
+ break;
+
+ case POp::refract_4_floats:
+ opText = opArg1 + " = refract(" + opArg1 + ", " + opArg2 + ", " + opArg3 + ")";
+ break;
+
+ case POp::dot_2_floats:
+ case POp::dot_3_floats:
+ case POp::dot_4_floats:
+ opText = opArg1 + " = dot(" + opArg2 + ", " + opArg3 + ")";
+ break;
+
+ case POp::exp_float:
+ opText = opArg1 + " = exp(" + opArg1 + ")";
+ break;
+
+ case POp::exp2_float:
+ opText = opArg1 + " = exp2(" + opArg1 + ")";
+ break;
+
+ case POp::log_float:
+ opText = opArg1 + " = log(" + opArg1 + ")";
+ break;
+
+ case POp::log2_float:
+ opText = opArg1 + " = log2(" + opArg1 + ")";
+ break;
+
+ case POp::pow_n_floats:
+ opText = opArg1 + " = pow(" + opArg1 + ", " + opArg2 + ")";
+ break;
+
+ case POp::sin_float:
+ opText = opArg1 + " = sin(" + opArg1 + ")";
+ break;
+
+ case POp::sqrt_float:
+ opText = opArg1 + " = sqrt(" + opArg1 + ")";
+ break;
+
+ case POp::tan_float:
+ opText = opArg1 + " = tan(" + opArg1 + ")";
+ break;
+
+ case POp::floor_float:
+ case POp::floor_2_floats:
+ case POp::floor_3_floats:
+ case POp::floor_4_floats:
+ opText = opArg1 + " = floor(" + opArg1 + ")";
+ break;
+
+ case POp::invsqrt_float:
+ case POp::invsqrt_2_floats:
+ case POp::invsqrt_3_floats:
+ case POp::invsqrt_4_floats:
+ opText = opArg1 + " = inversesqrt(" + opArg1 + ")";
+ break;
+
+ case POp::inverse_mat2:
+ case POp::inverse_mat3:
+ case POp::inverse_mat4:
+ opText = opArg1 + " = inverse(" + opArg1 + ")";
+ break;
+
+ case POp::add_float: case POp::add_int:
+ case POp::add_2_floats: case POp::add_2_ints:
+ case POp::add_3_floats: case POp::add_3_ints:
+ case POp::add_4_floats: case POp::add_4_ints:
+ case POp::add_n_floats: case POp::add_n_ints:
+ opText = opArg1 + " += " + opArg2;
+ break;
+
+ case POp::sub_float: case POp::sub_int:
+ case POp::sub_2_floats: case POp::sub_2_ints:
+ case POp::sub_3_floats: case POp::sub_3_ints:
+ case POp::sub_4_floats: case POp::sub_4_ints:
+ case POp::sub_n_floats: case POp::sub_n_ints:
+ opText = opArg1 + " -= " + opArg2;
+ break;
+
+ case POp::mul_float: case POp::mul_int:
+ case POp::mul_2_floats: case POp::mul_2_ints:
+ case POp::mul_3_floats: case POp::mul_3_ints:
+ case POp::mul_4_floats: case POp::mul_4_ints:
+ case POp::mul_n_floats: case POp::mul_n_ints:
+ opText = opArg1 + " *= " + opArg2;
+ break;
+
+ case POp::div_float: case POp::div_int: case POp::div_uint:
+ case POp::div_2_floats: case POp::div_2_ints: case POp::div_2_uints:
+ case POp::div_3_floats: case POp::div_3_ints: case POp::div_3_uints:
+ case POp::div_4_floats: case POp::div_4_ints: case POp::div_4_uints:
+ case POp::div_n_floats: case POp::div_n_ints: case POp::div_n_uints:
+ opText = opArg1 + " /= " + opArg2;
+ break;
+
+ case POp::mod_float:
+ case POp::mod_2_floats:
+ case POp::mod_3_floats:
+ case POp::mod_4_floats:
+ case POp::mod_n_floats:
+ opText = opArg1 + " = mod(" + opArg1 + ", " + opArg2 + ")";
+ break;
+
+ case POp::min_float: case POp::min_int: case POp::min_uint:
+ case POp::min_2_floats: case POp::min_2_ints: case POp::min_2_uints:
+ case POp::min_3_floats: case POp::min_3_ints: case POp::min_3_uints:
+ case POp::min_4_floats: case POp::min_4_ints: case POp::min_4_uints:
+ case POp::min_n_floats: case POp::min_n_ints: case POp::min_n_uints:
+ opText = opArg1 + " = min(" + opArg1 + ", " + opArg2 + ")";
+ break;
+
+ case POp::max_float: case POp::max_int: case POp::max_uint:
+ case POp::max_2_floats: case POp::max_2_ints: case POp::max_2_uints:
+ case POp::max_3_floats: case POp::max_3_ints: case POp::max_3_uints:
+ case POp::max_4_floats: case POp::max_4_ints: case POp::max_4_uints:
+ case POp::max_n_floats: case POp::max_n_ints: case POp::max_n_uints:
+ opText = opArg1 + " = max(" + opArg1 + ", " + opArg2 + ")";
+ break;
+
+ case POp::cmplt_float: case POp::cmplt_int: case POp::cmplt_uint:
+ case POp::cmplt_2_floats: case POp::cmplt_2_ints: case POp::cmplt_2_uints:
+ case POp::cmplt_3_floats: case POp::cmplt_3_ints: case POp::cmplt_3_uints:
+ case POp::cmplt_4_floats: case POp::cmplt_4_ints: case POp::cmplt_4_uints:
+ case POp::cmplt_n_floats: case POp::cmplt_n_ints: case POp::cmplt_n_uints:
+ opText = opArg1 + " = lessThan(" + opArg1 + ", " + opArg2 + ")";
+ break;
+
+ case POp::cmple_float: case POp::cmple_int: case POp::cmple_uint:
+ case POp::cmple_2_floats: case POp::cmple_2_ints: case POp::cmple_2_uints:
+ case POp::cmple_3_floats: case POp::cmple_3_ints: case POp::cmple_3_uints:
+ case POp::cmple_4_floats: case POp::cmple_4_ints: case POp::cmple_4_uints:
+ case POp::cmple_n_floats: case POp::cmple_n_ints: case POp::cmple_n_uints:
+ opText = opArg1 + " = lessThanEqual(" + opArg1 + ", " + opArg2 + ")";
+ break;
+
+ case POp::cmpeq_float: case POp::cmpeq_int:
+ case POp::cmpeq_2_floats: case POp::cmpeq_2_ints:
+ case POp::cmpeq_3_floats: case POp::cmpeq_3_ints:
+ case POp::cmpeq_4_floats: case POp::cmpeq_4_ints:
+ case POp::cmpeq_n_floats: case POp::cmpeq_n_ints:
+ opText = opArg1 + " = equal(" + opArg1 + ", " + opArg2 + ")";
+ break;
+
+ case POp::cmpne_float: case POp::cmpne_int:
+ case POp::cmpne_2_floats: case POp::cmpne_2_ints:
+ case POp::cmpne_3_floats: case POp::cmpne_3_ints:
+ case POp::cmpne_4_floats: case POp::cmpne_4_ints:
+ case POp::cmpne_n_floats: case POp::cmpne_n_ints:
+ opText = opArg1 + " = notEqual(" + opArg1 + ", " + opArg2 + ")";
+ break;
+
+ case POp::mix_float: case POp::mix_int:
+ case POp::mix_2_floats: case POp::mix_2_ints:
+ case POp::mix_3_floats: case POp::mix_3_ints:
+ case POp::mix_4_floats: case POp::mix_4_ints:
+ case POp::mix_n_floats: case POp::mix_n_ints:
+ opText = opArg1 + " = mix(" + opArg2 + ", " + opArg3 + ", " + opArg1 + ")";
+ break;
+
+ case POp::smoothstep_n_floats:
+ opText = opArg1 + " = smoothstep(" + opArg1 + ", " + opArg2 + ", " + opArg3 + ")";
+ break;
+
+ case POp::jump:
+ case POp::branch_if_all_lanes_active:
+ case POp::branch_if_any_lanes_active:
+ case POp::branch_if_no_lanes_active:
+ case POp::invoke_shader:
+ case POp::invoke_color_filter:
+ case POp::invoke_blender:
+ opText = std::string(opName) + " " + opArg1;
+ break;
+
+ case POp::invoke_to_linear_srgb:
+ opText = "src.rgba = toLinearSrgb(src.rgba)";
+ break;
+
+ case POp::invoke_from_linear_srgb:
+ opText = "src.rgba = fromLinearSrgb(src.rgba)";
+ break;
+
+ case POp::branch_if_no_active_lanes_eq:
+ opText = "branch " + opArg1 + " if no lanes of " + opArg2 + " == " + opArg3;
+ break;
+
+ case POp::label:
+ opText = "label " + opArg1;
+ break;
+
+ case POp::case_op: {
+ opText = "if (" + opArg1 + " == " + opArg3 +
+ ") { LoopMask = true; " + opArg2 + " = false; }";
+ break;
+ }
+ default:
+ break;
+ }
+
+ opName = opName.substr(0, 30);
+ if (!opText.empty()) {
+ out->writeText(SkSL::String::printf("% 5d. %-30.*s %s\n",
+ index + 1,
+ (int)opName.size(), opName.data(),
+ opText.c_str()).c_str());
+ } else {
+ out->writeText(SkSL::String::printf("% 5d. %.*s\n",
+ index + 1,
+ (int)opName.size(), opName.data()).c_str());
+ }
+ }
+}
+
+} // namespace RP
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineBuilder.h b/gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineBuilder.h
new file mode 100644
index 0000000000..a0717fa539
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineBuilder.h
@@ -0,0 +1,655 @@
+/*
+ * Copyright 2022 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_RASTERPIPELINECODEBUILDER
+#define SKSL_RASTERPIPELINECODEBUILDER
+
+#include "include/core/SkSpan.h"
+#include "include/core/SkTypes.h"
+#include "include/private/base/SkTArray.h"
+#include "src/base/SkUtils.h"
+#include "src/core/SkRasterPipelineOpList.h"
+#include "src/core/SkTHash.h"
+
+#include <cstdint>
+#include <initializer_list>
+#include <memory>
+
+class SkArenaAlloc;
+class SkRasterPipeline;
+class SkWStream;
+
+namespace SkSL {
+
+class SkRPDebugTrace;
+
+namespace RP {
+
+// A single scalar in our program consumes one slot.
+using Slot = int;
+constexpr Slot NA = -1;
+
+// Scalars, vectors, and matrices can be represented as a range of slot indices.
+struct SlotRange {
+ Slot index = 0;
+ int count = 0;
+};
+
+// An RP::Program will consist entirely of ProgramOps. The ProgramOps list is a superset of the
+// native SkRasterPipelineOps op-list. It also has a few extra ops to indicate child-effect
+// invocation, and a `label` op to indicate branch targets.
+enum class ProgramOp {
+ // A finished program can contain any native Raster Pipeline op...
+ #define M(stage) stage,
+ SK_RASTER_PIPELINE_OPS_ALL(M)
+ #undef M
+
+ // ... has branch targets...
+ label,
+
+ // ... can invoke child programs ...
+ invoke_shader,
+ invoke_color_filter,
+ invoke_blender,
+
+ // ... and can invoke color space transforms.
+ invoke_to_linear_srgb,
+ invoke_from_linear_srgb,
+};
+
+// BuilderOps are a superset of ProgramOps. They are used by the RP::Builder, which works in terms
+// of Instructions; Instructions are slightly more expressive than raw SkRasterPipelineOps. In
+// particular, the Builder supports stacks for pushing and popping scratch values.
+// RP::Program::makeStages is responsible for rewriting Instructions/BuilderOps into an array of
+// RP::Program::Stages, which will contain only native SkRasterPipelineOps and (optionally)
+// child-effect invocations.
+enum class BuilderOp {
+ // An in-flight program can contain all the native Raster Pipeline ops...
+ #define M(stage) stage,
+ SK_RASTER_PIPELINE_OPS_ALL(M)
+ #undef M
+
+ // ... has branch targets...
+ label,
+
+ // ... can invoke child programs...
+ invoke_shader,
+ invoke_color_filter,
+ invoke_blender,
+
+ // ... can invoke color space transforms ...
+ invoke_to_linear_srgb,
+ invoke_from_linear_srgb,
+
+ // ... and also has Builder-specific ops. These ops generally interface with the stack, and are
+ // converted into ProgramOps during `makeStages`.
+ push_literal,
+ push_slots,
+ push_slots_indirect,
+ push_uniform,
+ push_uniform_indirect,
+ push_zeros,
+ push_clone,
+ push_clone_from_stack,
+ push_clone_indirect_from_stack,
+ copy_stack_to_slots,
+ copy_stack_to_slots_unmasked,
+ copy_stack_to_slots_indirect,
+ swizzle_copy_stack_to_slots,
+ swizzle_copy_stack_to_slots_indirect,
+ discard_stack,
+ select,
+ push_condition_mask,
+ pop_condition_mask,
+ push_loop_mask,
+ pop_loop_mask,
+ pop_and_reenable_loop_mask,
+ push_return_mask,
+ pop_return_mask,
+ push_src_rgba,
+ push_dst_rgba,
+ pop_src_rg,
+ pop_src_rgba,
+ pop_dst_rgba,
+ set_current_stack,
+ branch_if_no_active_lanes_on_stack_top_equal,
+ unsupported
+};
+
+// If the child-invocation enums are not in sync between enums, program creation will not work.
+static_assert((int)ProgramOp::label == (int)BuilderOp::label);
+static_assert((int)ProgramOp::invoke_shader == (int)BuilderOp::invoke_shader);
+static_assert((int)ProgramOp::invoke_color_filter == (int)BuilderOp::invoke_color_filter);
+static_assert((int)ProgramOp::invoke_blender == (int)BuilderOp::invoke_blender);
+static_assert((int)ProgramOp::invoke_to_linear_srgb == (int)BuilderOp::invoke_to_linear_srgb);
+static_assert((int)ProgramOp::invoke_from_linear_srgb == (int)BuilderOp::invoke_from_linear_srgb);
+
+// Represents a single raster-pipeline SkSL instruction.
+struct Instruction {
+ Instruction(BuilderOp op, std::initializer_list<Slot> slots,
+ int a = 0, int b = 0, int c = 0, int d = 0)
+ : fOp(op), fImmA(a), fImmB(b), fImmC(c), fImmD(d) {
+ auto iter = slots.begin();
+ if (iter != slots.end()) { fSlotA = *iter++; }
+ if (iter != slots.end()) { fSlotB = *iter++; }
+ SkASSERT(iter == slots.end());
+ }
+
+ BuilderOp fOp;
+ Slot fSlotA = NA;
+ Slot fSlotB = NA;
+ int fImmA = 0;
+ int fImmB = 0;
+ int fImmC = 0;
+ int fImmD = 0;
+};
+
+class Callbacks {
+public:
+ virtual ~Callbacks() = default;
+
+ virtual bool appendShader(int index) = 0;
+ virtual bool appendColorFilter(int index) = 0;
+ virtual bool appendBlender(int index) = 0;
+
+ virtual void toLinearSrgb() = 0;
+ virtual void fromLinearSrgb() = 0;
+};
+
+class Program {
+public:
+ Program(SkTArray<Instruction> instrs,
+ int numValueSlots,
+ int numUniformSlots,
+ int numLabels,
+ SkRPDebugTrace* debugTrace);
+
+#if !defined(SKSL_STANDALONE)
+ bool appendStages(SkRasterPipeline* pipeline,
+ SkArenaAlloc* alloc,
+ Callbacks* callbacks,
+ SkSpan<const float> uniforms) const;
+#endif
+
+ void dump(SkWStream* out) const;
+
+private:
+ using StackDepthMap = SkTHashMap<int, int>; // <stack index, depth of stack>
+
+ struct SlotData {
+ SkSpan<float> values;
+ SkSpan<float> stack;
+ };
+ SlotData allocateSlotData(SkArenaAlloc* alloc) const;
+
+ struct Stage {
+ ProgramOp op;
+ void* ctx;
+ };
+ void makeStages(SkTArray<Stage>* pipeline,
+ SkArenaAlloc* alloc,
+ SkSpan<const float> uniforms,
+ const SlotData& slots) const;
+ void optimize();
+ StackDepthMap tempStackMaxDepths() const;
+
+ // These methods are used to split up large multi-slot operations into multiple ops as needed.
+ void appendCopy(SkTArray<Stage>* pipeline, SkArenaAlloc* alloc,
+ ProgramOp baseStage,
+ float* dst, int dstStride, const float* src, int srcStride, int numSlots) const;
+ void appendCopySlotsUnmasked(SkTArray<Stage>* pipeline, SkArenaAlloc* alloc,
+ float* dst, const float* src, int numSlots) const;
+ void appendCopySlotsMasked(SkTArray<Stage>* pipeline, SkArenaAlloc* alloc,
+ float* dst, const float* src, int numSlots) const;
+ void appendCopyConstants(SkTArray<Stage>* pipeline, SkArenaAlloc* alloc,
+ float* dst, const float* src, int numSlots) const;
+
+ // Appends a single-slot single-input math operation to the pipeline. The op `stage` will
+ // appended `numSlots` times, starting at position `dst` and advancing one slot for each
+ // subsequent invocation.
+ void appendSingleSlotUnaryOp(SkTArray<Stage>* pipeline, ProgramOp stage,
+ float* dst, int numSlots) const;
+
+ // Appends a multi-slot single-input math operation to the pipeline. `baseStage` must refer to
+ // an single-slot "apply_op" stage, which must be immediately followed by specializations for
+ // 2-4 slots. For instance, {`zero_slot`, `zero_2_slots`, `zero_3_slots`, `zero_4_slots`}
+ // must be contiguous ops in the stage list, listed in that order; pass `zero_slot` and we
+ // pick the appropriate op based on `numSlots`.
+ void appendMultiSlotUnaryOp(SkTArray<Stage>* pipeline, ProgramOp baseStage,
+ float* dst, int numSlots) const;
+
+ // Appends a two-input math operation to the pipeline. `src` must be _immediately_ after `dst`
+ // in memory. `baseStage` must refer to an unbounded "apply_to_n_slots" stage. A BinaryOpCtx
+ // will be used to pass pointers to the destination and source; the delta between the two
+ // pointers implicitly gives the number of slots.
+ void appendAdjacentNWayBinaryOp(SkTArray<Stage>* pipeline, SkArenaAlloc* alloc,
+ ProgramOp stage,
+ float* dst, const float* src, int numSlots) const;
+
+ // Appends a multi-slot two-input math operation to the pipeline. `src` must be _immediately_
+ // after `dst` in memory. `baseStage` must refer to an unbounded "apply_to_n_slots" stage, which
+ // must be immediately followed by specializations for 1-4 slots. For instance, {`add_n_floats`,
+ // `add_float`, `add_2_floats`, `add_3_floats`, `add_4_floats`} must be contiguous ops in the
+ // stage list, listed in that order; pass `add_n_floats` and we pick the appropriate op based on
+ // `numSlots`.
+ void appendAdjacentMultiSlotBinaryOp(SkTArray<Stage>* pipeline, SkArenaAlloc* alloc,
+ ProgramOp baseStage,
+ float* dst, const float* src, int numSlots) const;
+
+ // Appends a multi-slot math operation having three inputs (dst, src0, src1) and one output
+ // (dst) to the pipeline. The three inputs must be _immediately_ adjacent in memory. `baseStage`
+ // must refer to an unbounded "apply_to_n_slots" stage, which must be immediately followed by
+ // specializations for 1-4 slots.
+ void appendAdjacentMultiSlotTernaryOp(SkTArray<Stage>* pipeline, SkArenaAlloc* alloc,
+ ProgramOp stage, float* dst,
+ const float* src0, const float* src1, int numSlots) const;
+
+ // Appends a math operation having three inputs (dst, src0, src1) and one output (dst) to the
+ // pipeline. The three inputs must be _immediately_ adjacent in memory. `baseStage` must refer
+ // to an unbounded "apply_to_n_slots" stage. A TernaryOpCtx will be used to pass pointers to the
+ // destination and sources; the delta between the each pointer implicitly gives the slot count.
+ void appendAdjacentNWayTernaryOp(SkTArray<Stage>* pipeline, SkArenaAlloc* alloc,
+ ProgramOp stage, float* dst,
+ const float* src0, const float* src1, int numSlots) const;
+
+ // Appends a stack_rewind op on platforms where it is needed (when SK_HAS_MUSTTAIL is not set).
+ void appendStackRewind(SkTArray<Stage>* pipeline) const;
+
+ SkTArray<Instruction> fInstructions;
+ int fNumValueSlots = 0;
+ int fNumUniformSlots = 0;
+ int fNumTempStackSlots = 0;
+ int fNumLabels = 0;
+ SkTHashMap<int, int> fTempStackMaxDepths;
+ SkRPDebugTrace* fDebugTrace = nullptr;
+};
+
+class Builder {
+public:
+ /** Finalizes and optimizes the program. */
+ std::unique_ptr<Program> finish(int numValueSlots,
+ int numUniformSlots,
+ SkRPDebugTrace* debugTrace = nullptr);
+ /**
+ * Peels off a label ID for use in the program. Set the label's position in the program with
+ * the `label` instruction. Actually branch to the target with an instruction like
+ * `branch_if_any_lanes_active` or `jump`.
+ */
+ int nextLabelID() {
+ return fNumLabels++;
+ }
+
+ /**
+ * The builder keeps track of the state of execution masks; when we know that the execution
+ * mask is unaltered, we can generate simpler code. Code which alters the execution mask is
+ * required to enable this flag.
+ */
+ void enableExecutionMaskWrites() {
+ ++fExecutionMaskWritesEnabled;
+ }
+
+ void disableExecutionMaskWrites() {
+ SkASSERT(this->executionMaskWritesAreEnabled());
+ --fExecutionMaskWritesEnabled;
+ }
+
+ bool executionMaskWritesAreEnabled() {
+ return fExecutionMaskWritesEnabled > 0;
+ }
+
+ /** Assemble a program from the Raster Pipeline instructions below. */
+ void init_lane_masks() {
+ fInstructions.push_back({BuilderOp::init_lane_masks, {}});
+ }
+
+ void store_src_rg(SlotRange slots) {
+ SkASSERT(slots.count == 2);
+ fInstructions.push_back({BuilderOp::store_src_rg, {slots.index}});
+ }
+
+ void store_src(SlotRange slots) {
+ SkASSERT(slots.count == 4);
+ fInstructions.push_back({BuilderOp::store_src, {slots.index}});
+ }
+
+ void store_dst(SlotRange slots) {
+ SkASSERT(slots.count == 4);
+ fInstructions.push_back({BuilderOp::store_dst, {slots.index}});
+ }
+
+ void store_device_xy01(SlotRange slots) {
+ SkASSERT(slots.count == 4);
+ fInstructions.push_back({BuilderOp::store_device_xy01, {slots.index}});
+ }
+
+ void load_src(SlotRange slots) {
+ SkASSERT(slots.count == 4);
+ fInstructions.push_back({BuilderOp::load_src, {slots.index}});
+ }
+
+ void load_dst(SlotRange slots) {
+ SkASSERT(slots.count == 4);
+ fInstructions.push_back({BuilderOp::load_dst, {slots.index}});
+ }
+
+ void set_current_stack(int stackIdx) {
+ fInstructions.push_back({BuilderOp::set_current_stack, {}, stackIdx});
+ }
+
+ // Inserts a label into the instruction stream.
+ void label(int labelID);
+
+ // Unconditionally branches to a label.
+ void jump(int labelID);
+
+ // Branches to a label if the execution mask is active in every lane.
+ void branch_if_all_lanes_active(int labelID);
+
+ // Branches to a label if the execution mask is active in any lane.
+ void branch_if_any_lanes_active(int labelID);
+
+ // Branches to a label if the execution mask is inactive across all lanes.
+ void branch_if_no_lanes_active(int labelID);
+
+ // Branches to a label if the top value on the stack is _not_ equal to `value` in any lane.
+ void branch_if_no_active_lanes_on_stack_top_equal(int value, int labelID);
+
+ // We use the same SkRasterPipeline op regardless of the literal type, and bitcast the value.
+ void push_literal_f(float val) {
+ this->push_literal_i(sk_bit_cast<int32_t>(val));
+ }
+
+ void push_literal_i(int32_t val) {
+ if (val == 0) {
+ this->push_zeros(1);
+ } else {
+ fInstructions.push_back({BuilderOp::push_literal, {}, val});
+ }
+ }
+
+ void push_literal_u(uint32_t val) {
+ this->push_literal_i(sk_bit_cast<int32_t>(val));
+ }
+
+ // Translates into copy_constants (from uniforms into temp stack) in Raster Pipeline.
+ void push_uniform(SlotRange src);
+
+ // Translates into copy_from_indirect_uniform_unmasked (from values into temp stack) in Raster
+ // Pipeline. `fixedRange` denotes a fixed set of slots; this range is pushed forward by the
+ // value at the top of stack `dynamicStack`. Pass the range of the uniform being indexed as
+ // `limitRange`; this is used as a hard cap, to avoid indexing outside of bounds.
+ void push_uniform_indirect(SlotRange fixedRange, int dynamicStack, SlotRange limitRange);
+
+ void push_zeros(int count) {
+ // Translates into zero_slot_unmasked in Raster Pipeline.
+ SkASSERT(count >= 0);
+ if (count > 0) {
+ if (!fInstructions.empty() && fInstructions.back().fOp == BuilderOp::push_zeros) {
+ // Coalesce adjacent push_zero ops into a single op.
+ fInstructions.back().fImmA += count;
+ } else {
+ fInstructions.push_back({BuilderOp::push_zeros, {}, count});
+ }
+ }
+ }
+
+ // Translates into copy_slots_unmasked (from values into temp stack) in Raster Pipeline.
+ void push_slots(SlotRange src);
+
+ // Translates into copy_from_indirect_unmasked (from values into temp stack) in Raster Pipeline.
+ // `fixedRange` denotes a fixed set of slots; this range is pushed forward by the value at the
+ // top of stack `dynamicStack`. Pass the slot range of the variable being indexed as
+ // `limitRange`; this is used as a hard cap, to avoid indexing outside of bounds.
+ void push_slots_indirect(SlotRange fixedRange, int dynamicStack, SlotRange limitRange);
+
+ // Translates into copy_slots_masked (from temp stack to values) in Raster Pipeline.
+ // Does not discard any values on the temp stack.
+ void copy_stack_to_slots(SlotRange dst) {
+ this->copy_stack_to_slots(dst, /*offsetFromStackTop=*/dst.count);
+ }
+
+ void copy_stack_to_slots(SlotRange dst, int offsetFromStackTop);
+
+ // Translates into swizzle_copy_slots_masked (from temp stack to values) in Raster Pipeline.
+ // Does not discard any values on the temp stack.
+ void swizzle_copy_stack_to_slots(SlotRange dst,
+ SkSpan<const int8_t> components,
+ int offsetFromStackTop);
+
+ // Translates into swizzle_copy_to_indirect_masked (from temp stack to values) in Raster
+ // Pipeline. Does not discard any values on the temp stack.
+ void swizzle_copy_stack_to_slots_indirect(SlotRange fixedRange,
+ int dynamicStackID,
+ SlotRange limitRange,
+ SkSpan<const int8_t> components,
+ int offsetFromStackTop);
+
+ // Translates into copy_slots_unmasked (from temp stack to values) in Raster Pipeline.
+ // Does not discard any values on the temp stack.
+ void copy_stack_to_slots_unmasked(SlotRange dst) {
+ this->copy_stack_to_slots_unmasked(dst, /*offsetFromStackTop=*/dst.count);
+ }
+
+ void copy_stack_to_slots_unmasked(SlotRange dst, int offsetFromStackTop);
+
+ // Translates into copy_to_indirect_masked (from temp stack into values) in Raster Pipeline.
+ // `fixedRange` denotes a fixed set of slots; this range is pushed forward by the value at the
+ // top of stack `dynamicStack`. Pass the slot range of the variable being indexed as
+ // `limitRange`; this is used as a hard cap, to avoid indexing outside of bounds.
+ void copy_stack_to_slots_indirect(SlotRange fixedRange,
+ int dynamicStackID,
+ SlotRange limitRange);
+
+ // Copies from temp stack to slots, including an indirect offset, then shrinks the temp stack.
+ void pop_slots_indirect(SlotRange fixedRange, int dynamicStackID, SlotRange limitRange) {
+ this->copy_stack_to_slots_indirect(fixedRange, dynamicStackID, limitRange);
+ this->discard_stack(fixedRange.count);
+ }
+
+ // Performs a unary op (like `bitwise_not`), given a slot count of `slots`. The stack top is
+ // replaced with the result.
+ void unary_op(BuilderOp op, int32_t slots);
+
+ // Performs a binary op (like `add_n_floats` or `cmpeq_n_ints`), given a slot count of
+ // `slots`. Two n-slot input values are consumed, and the result is pushed onto the stack.
+ void binary_op(BuilderOp op, int32_t slots);
+
+ // Performs a ternary op (like `mix` or `smoothstep`), given a slot count of
+ // `slots`. Three n-slot input values are consumed, and the result is pushed onto the stack.
+ void ternary_op(BuilderOp op, int32_t slots);
+
+ // Computes a dot product on the stack. The slots consumed (`slots`) must be between 1 and 4.
+ // Two n-slot input vectors are consumed, and a scalar result is pushed onto the stack.
+ void dot_floats(int32_t slots);
+
+ // Computes refract(N, I, eta) on the stack. N and I are assumed to be 4-slot vectors, and can
+ // be padded with zeros for smaller inputs. Eta is a scalar. The result is a 4-slot vector.
+ void refract_floats();
+
+ // Computes inverse(matN) on the stack. Pass 2, 3 or 4 for n to specify matrix size.
+ void inverse_matrix(int32_t n);
+
+ // Shrinks the temp stack, discarding values on top.
+ void discard_stack(int32_t count = 1);
+
+ // Copies vales from the temp stack into slots, and then shrinks the temp stack.
+ void pop_slots(SlotRange dst);
+
+ // Creates many clones of the top single-slot item on the temp stack.
+ void push_duplicates(int count);
+
+ // Creates a single clone of an item on the current temp stack. The cloned item can consist of
+ // any number of slots, and can be copied from an earlier position on the stack.
+ void push_clone(int numSlots, int offsetFromStackTop = 0) {
+ fInstructions.push_back({BuilderOp::push_clone, {}, numSlots,
+ numSlots + offsetFromStackTop});
+ }
+
+ // Clones a range of slots from another stack onto this stack.
+ void push_clone_from_stack(SlotRange range, int otherStackID, int offsetFromStackTop);
+
+ // Translates into copy_from_indirect_unmasked (from one temp stack to another) in Raster
+ // Pipeline. `fixedOffset` denotes a range of slots within the top `offsetFromStackTop` slots of
+ // `otherStackID`. This range is pushed forward by the value at the top of `dynamicStackID`.
+ void push_clone_indirect_from_stack(SlotRange fixedOffset,
+ int dynamicStackID,
+ int otherStackID,
+ int offsetFromStackTop);
+
+ // Compares the stack top with the passed-in value; if it matches, enables the loop mask.
+ void case_op(int value) {
+ fInstructions.push_back({BuilderOp::case_op, {}, value});
+ }
+
+ void select(int slots) {
+ // Overlays the top two entries on the stack, making one hybrid entry. The execution mask
+ // is used to select which lanes are preserved.
+ SkASSERT(slots > 0);
+ fInstructions.push_back({BuilderOp::select, {}, slots});
+ }
+
+ // The opposite of push_slots; copies values from the temp stack into value slots, then
+ // shrinks the temp stack.
+ void pop_slots_unmasked(SlotRange dst);
+
+ void copy_slots_masked(SlotRange dst, SlotRange src) {
+ SkASSERT(dst.count == src.count);
+ fInstructions.push_back({BuilderOp::copy_slot_masked, {dst.index, src.index}, dst.count});
+ }
+
+ void copy_slots_unmasked(SlotRange dst, SlotRange src);
+
+ void copy_constant(Slot slot, int constantValue) {
+ fInstructions.push_back({BuilderOp::copy_constant, {slot}, constantValue});
+ }
+
+ // Stores zeros across the entire slot range.
+ void zero_slots_unmasked(SlotRange dst);
+
+ // Consumes `consumedSlots` elements on the stack, then generates `components.size()` elements.
+ void swizzle(int consumedSlots, SkSpan<const int8_t> components);
+
+ // Transposes a matrix of size CxR on the stack (into a matrix of size RxC).
+ void transpose(int columns, int rows);
+
+ // Generates a CxR diagonal matrix from the top two scalars on the stack. The second scalar is
+ // used as the diagonal value; the first scalar (usually zero) fills in the rest of the slots.
+ void diagonal_matrix(int columns, int rows);
+
+ // Resizes a CxR matrix at the top of the stack to C'xR'.
+ void matrix_resize(int origColumns, int origRows, int newColumns, int newRows);
+
+ void push_condition_mask() {
+ SkASSERT(this->executionMaskWritesAreEnabled());
+ fInstructions.push_back({BuilderOp::push_condition_mask, {}});
+ }
+
+ void pop_condition_mask() {
+ SkASSERT(this->executionMaskWritesAreEnabled());
+ fInstructions.push_back({BuilderOp::pop_condition_mask, {}});
+ }
+
+ void merge_condition_mask() {
+ SkASSERT(this->executionMaskWritesAreEnabled());
+ fInstructions.push_back({BuilderOp::merge_condition_mask, {}});
+ }
+
+ void push_loop_mask() {
+ SkASSERT(this->executionMaskWritesAreEnabled());
+ fInstructions.push_back({BuilderOp::push_loop_mask, {}});
+ }
+
+ void pop_loop_mask() {
+ SkASSERT(this->executionMaskWritesAreEnabled());
+ fInstructions.push_back({BuilderOp::pop_loop_mask, {}});
+ }
+
+ void push_src_rgba() {
+ fInstructions.push_back({BuilderOp::push_src_rgba, {}});
+ }
+
+ void push_dst_rgba() {
+ fInstructions.push_back({BuilderOp::push_dst_rgba, {}});
+ }
+
+ void pop_src_rg() {
+ fInstructions.push_back({BuilderOp::pop_src_rg, {}});
+ }
+
+ void pop_src_rgba() {
+ fInstructions.push_back({BuilderOp::pop_src_rgba, {}});
+ }
+
+ void pop_dst_rgba() {
+ fInstructions.push_back({BuilderOp::pop_dst_rgba, {}});
+ }
+
+ void mask_off_loop_mask() {
+ SkASSERT(this->executionMaskWritesAreEnabled());
+ fInstructions.push_back({BuilderOp::mask_off_loop_mask, {}});
+ }
+
+ void reenable_loop_mask(SlotRange src) {
+ SkASSERT(this->executionMaskWritesAreEnabled());
+ SkASSERT(src.count == 1);
+ fInstructions.push_back({BuilderOp::reenable_loop_mask, {src.index}});
+ }
+
+ void pop_and_reenable_loop_mask() {
+ SkASSERT(this->executionMaskWritesAreEnabled());
+ fInstructions.push_back({BuilderOp::pop_and_reenable_loop_mask, {}});
+ }
+
+ void merge_loop_mask() {
+ SkASSERT(this->executionMaskWritesAreEnabled());
+ fInstructions.push_back({BuilderOp::merge_loop_mask, {}});
+ }
+
+ void push_return_mask() {
+ SkASSERT(this->executionMaskWritesAreEnabled());
+ fInstructions.push_back({BuilderOp::push_return_mask, {}});
+ }
+
+ void pop_return_mask();
+
+ void mask_off_return_mask() {
+ SkASSERT(this->executionMaskWritesAreEnabled());
+ fInstructions.push_back({BuilderOp::mask_off_return_mask, {}});
+ }
+
+ void invoke_shader(int childIdx) {
+ fInstructions.push_back({BuilderOp::invoke_shader, {}, childIdx});
+ }
+
+ void invoke_color_filter(int childIdx) {
+ fInstructions.push_back({BuilderOp::invoke_color_filter, {}, childIdx});
+ }
+
+ void invoke_blender(int childIdx) {
+ fInstructions.push_back({BuilderOp::invoke_blender, {}, childIdx});
+ }
+
+ void invoke_to_linear_srgb() {
+ fInstructions.push_back({BuilderOp::invoke_to_linear_srgb, {}});
+ }
+
+ void invoke_from_linear_srgb() {
+ fInstructions.push_back({BuilderOp::invoke_from_linear_srgb, {}});
+ }
+
+private:
+ void simplifyPopSlotsUnmasked(SlotRange* dst);
+
+ SkTArray<Instruction> fInstructions;
+ int fNumLabels = 0;
+ int fExecutionMaskWritesEnabled = 0;
+};
+
+} // namespace RP
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineCodeGenerator.cpp b/gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineCodeGenerator.cpp
new file mode 100644
index 0000000000..4be7d38936
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineCodeGenerator.cpp
@@ -0,0 +1,3444 @@
+/*
+ * 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/SkSLProgramElement.h"
+#include "include/private/SkSLStatement.h"
+#include "include/private/base/SkTArray.h"
+#include "include/sksl/SkSLOperator.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/base/SkStringView.h"
+#include "src/core/SkTHash.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLConstantFolder.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLIntrinsicList.h"
+#include "src/sksl/SkSLModifiersPool.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/SkSLBreakStatement.h"
+#include "src/sksl/ir/SkSLChildCall.h"
+#include "src/sksl/ir/SkSLConstructor.h"
+#include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h"
+#include "src/sksl/ir/SkSLConstructorMatrixResize.h"
+#include "src/sksl/ir/SkSLConstructorSplat.h"
+#include "src/sksl/ir/SkSLContinueStatement.h"
+#include "src/sksl/ir/SkSLDoStatement.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLExpressionStatement.h"
+#include "src/sksl/ir/SkSLFieldAccess.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/SkSLLiteral.h"
+#include "src/sksl/ir/SkSLPostfixExpression.h"
+#include "src/sksl/ir/SkSLPrefixExpression.h"
+#include "src/sksl/ir/SkSLProgram.h"
+#include "src/sksl/ir/SkSLReturnStatement.h"
+#include "src/sksl/ir/SkSLSwitchCase.h"
+#include "src/sksl/ir/SkSLSwitchStatement.h"
+#include "src/sksl/ir/SkSLSwizzle.h"
+#include "src/sksl/ir/SkSLTernaryExpression.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 "src/sksl/transform/SkSLTransform.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <float.h>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace SkSL {
+namespace RP {
+
+static bool unsupported() {
+ // If MakeRasterPipelineProgram returns false, set a breakpoint here for more information.
+ return false;
+}
+
+class SlotManager {
+public:
+ SlotManager(std::vector<SlotDebugInfo>* i) : fSlotDebugInfo(i) {}
+
+ /** Used by `create` to add this variable to SlotDebugInfo inside SkRPDebugTrace. */
+ void addSlotDebugInfoForGroup(const std::string& varName,
+ const Type& type,
+ Position pos,
+ int* groupIndex,
+ bool isFunctionReturnValue);
+ void addSlotDebugInfo(const std::string& varName,
+ const Type& type,
+ Position pos,
+ bool isFunctionReturnValue);
+
+ /** 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 getVariableSlots(const Variable& v);
+
+ /**
+ * 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);
+
+ /** Returns the total number of slots consumed. */
+ int slotCount() const { return fSlotCount; }
+
+private:
+ SkTHashMap<const IRNode*, SlotRange> fSlotMap;
+ int fSlotCount = 0;
+ std::vector<SlotDebugInfo>* fSlotDebugInfo;
+};
+
+class AutoContinueMask;
+class LValue;
+
+class Generator {
+public:
+ Generator(const SkSL::Program& program, SkRPDebugTrace* debugTrace)
+ : fProgram(program)
+ , fContext(fProgram.fContext->fTypes,
+ fProgram.fContext->fCaps,
+ *fProgram.fContext->fErrors)
+ , fDebugTrace(debugTrace)
+ , fProgramSlots(debugTrace ? &debugTrace->fSlotInfo : nullptr)
+ , fUniformSlots(debugTrace ? &debugTrace->fUniformInfo : nullptr) {
+ fContext.fModifiersPool = &fModifiersPool;
+ fContext.fConfig = fProgram.fConfig.get();
+ fContext.fModule = fProgram.fContext->fModule;
+ }
+
+ /** Converts the SkSL main() function into a set of Instructions. */
+ bool writeProgram(const FunctionDefinition& function);
+
+ /** Returns the generated program. */
+ std::unique_ptr<RP::Program> finish();
+
+ /**
+ * 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);
+
+ /**
+ * 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 getFunctionDebugInfo(const FunctionDeclaration& decl);
+
+ /** Looks up the slots associated with an SkSL variable; creates the slot if necessary. */
+ SlotRange getVariableSlots(const Variable& v) {
+ SkASSERT(!IsUniform(v));
+ return fProgramSlots.getVariableSlots(v);
+ }
+
+ /** Looks up the slots associated with an SkSL uniform; creates the slot if necessary. */
+ SlotRange getUniformSlots(const Variable& v) {
+ SkASSERT(IsUniform(v));
+ return fUniformSlots.getVariableSlots(v);
+ }
+
+ /**
+ * 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) {
+ return fProgramSlots.getFunctionSlots(callSite, f);
+ }
+
+ /**
+ * Creates an additional stack for the program to push values onto. The stack will not become
+ * actively in-use until `setCurrentStack` is called.
+ */
+ int createStack();
+
+ /** Frees a stack generated by `createStack`. The freed stack must be completely empty. */
+ void recycleStack(int stackID);
+
+ /** Redirects builder ops to point to a different stack (created by `createStack`). */
+ void setCurrentStack(int stackID);
+
+ /** Reports the currently active stack. */
+ int currentStack() {
+ return fCurrentStack;
+ }
+
+ /**
+ * Returns an LValue for the passed-in expression; if the expression isn't supported as an
+ * LValue, returns nullptr.
+ */
+ std::unique_ptr<LValue> makeLValue(const Expression& e, bool allowScratch = false);
+
+ /** Copies the top-of-stack value into this lvalue, without discarding it from the stack. */
+ [[nodiscard]] bool store(LValue& lvalue);
+
+ /** Pushes the lvalue onto the top-of-stack. */
+ [[nodiscard]] bool push(LValue& lvalue);
+
+ /** The Builder stitches our instructions together into Raster Pipeline code. */
+ Builder* builder() { return &fBuilder; }
+
+ /** Appends a statement to the program. */
+ [[nodiscard]] bool writeStatement(const Statement& s);
+ [[nodiscard]] bool writeBlock(const Block& b);
+ [[nodiscard]] bool writeBreakStatement(const BreakStatement& b);
+ [[nodiscard]] bool writeContinueStatement(const ContinueStatement& b);
+ [[nodiscard]] bool writeDoStatement(const DoStatement& d);
+ [[nodiscard]] bool writeExpressionStatement(const ExpressionStatement& e);
+ [[nodiscard]] bool writeMasklessForStatement(const ForStatement& f);
+ [[nodiscard]] bool writeForStatement(const ForStatement& f);
+ [[nodiscard]] bool writeGlobals();
+ [[nodiscard]] bool writeIfStatement(const IfStatement& i);
+ [[nodiscard]] bool writeDynamicallyUniformIfStatement(const IfStatement& i);
+ [[nodiscard]] bool writeReturnStatement(const ReturnStatement& r);
+ [[nodiscard]] bool writeSwitchStatement(const SwitchStatement& s);
+ [[nodiscard]] bool writeVarDeclaration(const VarDeclaration& v);
+
+ /** Pushes an expression to the value stack. */
+ [[nodiscard]] bool pushBinaryExpression(const BinaryExpression& e);
+ [[nodiscard]] bool pushBinaryExpression(const Expression& left,
+ Operator op,
+ const Expression& right);
+ [[nodiscard]] bool pushChildCall(const ChildCall& c);
+ [[nodiscard]] bool pushConstructorCast(const AnyConstructor& c);
+ [[nodiscard]] bool pushConstructorCompound(const AnyConstructor& c);
+ [[nodiscard]] bool pushConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c);
+ [[nodiscard]] bool pushConstructorMatrixResize(const ConstructorMatrixResize& c);
+ [[nodiscard]] bool pushConstructorSplat(const ConstructorSplat& c);
+ [[nodiscard]] bool pushExpression(const Expression& e, bool usesResult = true);
+ [[nodiscard]] bool pushFieldAccess(const FieldAccess& f);
+ [[nodiscard]] bool pushFunctionCall(const FunctionCall& c);
+ [[nodiscard]] bool pushIndexExpression(const IndexExpression& i);
+ [[nodiscard]] bool pushIntrinsic(const FunctionCall& c);
+ [[nodiscard]] bool pushIntrinsic(IntrinsicKind intrinsic, const Expression& arg0);
+ [[nodiscard]] bool pushIntrinsic(IntrinsicKind intrinsic,
+ const Expression& arg0,
+ const Expression& arg1);
+ [[nodiscard]] bool pushIntrinsic(IntrinsicKind intrinsic,
+ const Expression& arg0,
+ const Expression& arg1,
+ const Expression& arg2);
+ [[nodiscard]] bool pushLiteral(const Literal& l);
+ [[nodiscard]] bool pushPostfixExpression(const PostfixExpression& p, bool usesResult);
+ [[nodiscard]] bool pushPrefixExpression(const PrefixExpression& p);
+ [[nodiscard]] bool pushPrefixExpression(Operator op, const Expression& expr);
+ [[nodiscard]] bool pushSwizzle(const Swizzle& s);
+ [[nodiscard]] bool pushTernaryExpression(const TernaryExpression& t);
+ [[nodiscard]] bool pushTernaryExpression(const Expression& test,
+ const Expression& ifTrue,
+ const Expression& ifFalse);
+ [[nodiscard]] bool pushDynamicallyUniformTernaryExpression(const Expression& test,
+ const Expression& ifTrue,
+ const Expression& ifFalse);
+ [[nodiscard]] bool pushVariableReference(const VariableReference& v);
+
+ /** 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 TypedOps {
+ BuilderOp fFloatOp;
+ BuilderOp fSignedOp;
+ BuilderOp fUnsignedOp;
+ BuilderOp fBooleanOp;
+ };
+
+ static BuilderOp GetTypedOp(const SkSL::Type& type, const TypedOps& ops);
+
+ [[nodiscard]] bool unaryOp(const SkSL::Type& type, const TypedOps& ops);
+ [[nodiscard]] bool binaryOp(const SkSL::Type& type, const TypedOps& ops);
+ [[nodiscard]] bool ternaryOp(const SkSL::Type& type, const TypedOps& ops);
+ [[nodiscard]] bool pushIntrinsic(const TypedOps& ops, const Expression& arg0);
+ [[nodiscard]] bool pushIntrinsic(const TypedOps& ops,
+ const Expression& arg0,
+ const Expression& arg1);
+ [[nodiscard]] bool pushIntrinsic(BuilderOp builderOp, const Expression& arg0);
+ [[nodiscard]] bool pushIntrinsic(BuilderOp builderOp,
+ const Expression& arg0,
+ const Expression& arg1);
+ [[nodiscard]] bool pushLengthIntrinsic(int slotCount);
+ [[nodiscard]] bool pushVectorizedExpression(const Expression& expr, const Type& vectorType);
+ [[nodiscard]] bool pushVariableReferencePartial(const VariableReference& v, SlotRange subset);
+ [[nodiscard]] bool pushLValueOrExpression(LValue* lvalue, const Expression& expr);
+ [[nodiscard]] bool pushMatrixMultiply(LValue* lvalue,
+ const Expression& left,
+ const Expression& right,
+ int leftColumns, int leftRows,
+ int rightColumns, int rightRows);
+ [[nodiscard]] bool pushStructuredComparison(LValue* left,
+ Operator op,
+ LValue* right,
+ const Type& type);
+
+ void foldWithMultiOp(BuilderOp op, int elements);
+ void foldComparisonOp(Operator op, int elements);
+
+ BuilderOp getTypedOp(const SkSL::Type& type, const TypedOps& ops) const;
+
+ Analysis::ReturnComplexity returnComplexity(const FunctionDefinition* func) {
+ Analysis::ReturnComplexity* complexity = fReturnComplexityMap.find(fCurrentFunction);
+ if (!complexity) {
+ complexity = fReturnComplexityMap.set(fCurrentFunction,
+ Analysis::GetReturnComplexity(*fCurrentFunction));
+ }
+ return *complexity;
+ }
+
+ bool needsReturnMask() {
+ return this->returnComplexity(fCurrentFunction) >=
+ Analysis::ReturnComplexity::kEarlyReturns;
+ }
+
+ bool needsFunctionResultSlots() {
+ return this->returnComplexity(fCurrentFunction) >
+ Analysis::ReturnComplexity::kSingleSafeReturn;
+ }
+
+ static bool IsUniform(const Variable& var) {
+ return var.modifiers().fFlags & Modifiers::kUniform_Flag;
+ }
+
+ static bool IsOutParameter(const Variable& var) {
+ return (var.modifiers().fFlags & (Modifiers::kIn_Flag | Modifiers::kOut_Flag)) ==
+ Modifiers::kOut_Flag;
+ }
+
+ static bool IsInoutParameter(const Variable& var) {
+ return (var.modifiers().fFlags & (Modifiers::kIn_Flag | Modifiers::kOut_Flag)) ==
+ (Modifiers::kIn_Flag | Modifiers::kOut_Flag);
+ }
+
+private:
+ const SkSL::Program& fProgram;
+ SkSL::Context fContext;
+ SkSL::ModifiersPool fModifiersPool;
+ Builder fBuilder;
+ SkRPDebugTrace* fDebugTrace = nullptr;
+ SkTHashMap<const Variable*, int> fChildEffectMap;
+
+ SlotManager fProgramSlots;
+ SlotManager fUniformSlots;
+
+ const FunctionDefinition* fCurrentFunction = nullptr;
+ SlotRange fCurrentFunctionResult;
+ AutoContinueMask* fCurrentContinueMask = nullptr;
+ int fCurrentBreakTarget = -1;
+ int fCurrentStack = 0;
+ int fNextStackID = 0;
+ SkTArray<int> fRecycledStacks;
+
+ SkTHashMap<const FunctionDefinition*, Analysis::ReturnComplexity> fReturnComplexityMap;
+
+ static constexpr auto kAbsOps = TypedOps{BuilderOp::abs_float,
+ BuilderOp::abs_int,
+ BuilderOp::unsupported,
+ BuilderOp::unsupported};
+ static constexpr auto kAddOps = TypedOps{BuilderOp::add_n_floats,
+ BuilderOp::add_n_ints,
+ BuilderOp::add_n_ints,
+ BuilderOp::unsupported};
+ static constexpr auto kSubtractOps = TypedOps{BuilderOp::sub_n_floats,
+ BuilderOp::sub_n_ints,
+ BuilderOp::sub_n_ints,
+ BuilderOp::unsupported};
+ static constexpr auto kMultiplyOps = TypedOps{BuilderOp::mul_n_floats,
+ BuilderOp::mul_n_ints,
+ BuilderOp::mul_n_ints,
+ BuilderOp::unsupported};
+ static constexpr auto kDivideOps = TypedOps{BuilderOp::div_n_floats,
+ BuilderOp::div_n_ints,
+ BuilderOp::div_n_uints,
+ BuilderOp::unsupported};
+ static constexpr auto kLessThanOps = TypedOps{BuilderOp::cmplt_n_floats,
+ BuilderOp::cmplt_n_ints,
+ BuilderOp::cmplt_n_uints,
+ BuilderOp::unsupported};
+ static constexpr auto kLessThanEqualOps = TypedOps{BuilderOp::cmple_n_floats,
+ BuilderOp::cmple_n_ints,
+ BuilderOp::cmple_n_uints,
+ BuilderOp::unsupported};
+ static constexpr auto kEqualOps = TypedOps{BuilderOp::cmpeq_n_floats,
+ BuilderOp::cmpeq_n_ints,
+ BuilderOp::cmpeq_n_ints,
+ BuilderOp::cmpeq_n_ints};
+ static constexpr auto kNotEqualOps = TypedOps{BuilderOp::cmpne_n_floats,
+ BuilderOp::cmpne_n_ints,
+ BuilderOp::cmpne_n_ints,
+ BuilderOp::cmpne_n_ints};
+ static constexpr auto kModOps = TypedOps{BuilderOp::mod_n_floats,
+ BuilderOp::unsupported,
+ BuilderOp::unsupported,
+ BuilderOp::unsupported};
+ static constexpr auto kMinOps = TypedOps{BuilderOp::min_n_floats,
+ BuilderOp::min_n_ints,
+ BuilderOp::min_n_uints,
+ BuilderOp::min_n_uints};
+ static constexpr auto kMaxOps = TypedOps{BuilderOp::max_n_floats,
+ BuilderOp::max_n_ints,
+ BuilderOp::max_n_uints,
+ BuilderOp::max_n_uints};
+ static constexpr auto kMixOps = TypedOps{BuilderOp::mix_n_floats,
+ BuilderOp::unsupported,
+ BuilderOp::unsupported,
+ BuilderOp::unsupported};
+ static constexpr auto kInverseSqrtOps = TypedOps{BuilderOp::invsqrt_float,
+ BuilderOp::unsupported,
+ BuilderOp::unsupported,
+ BuilderOp::unsupported};
+ friend class AutoContinueMask;
+};
+
+class AutoStack {
+public:
+ explicit AutoStack(Generator* g)
+ : fGenerator(g)
+ , fStackID(g->createStack()) {}
+
+ ~AutoStack() {
+ fGenerator->recycleStack(fStackID);
+ }
+
+ void enter() {
+ fParentStackID = fGenerator->currentStack();
+ fGenerator->setCurrentStack(fStackID);
+ }
+
+ void exit() {
+ SkASSERT(fGenerator->currentStack() == fStackID);
+ fGenerator->setCurrentStack(fParentStackID);
+ }
+
+ void pushClone(int slots) {
+ this->pushClone(SlotRange{0, slots}, /*offsetFromStackTop=*/slots);
+ }
+
+ void pushClone(SlotRange range, int offsetFromStackTop) {
+ fGenerator->builder()->push_clone_from_stack(range, fStackID, offsetFromStackTop);
+ }
+
+ void pushCloneIndirect(SlotRange range, int dynamicStackID, int offsetFromStackTop) {
+ fGenerator->builder()->push_clone_indirect_from_stack(
+ range, dynamicStackID, /*otherStackID=*/fStackID, offsetFromStackTop);
+ }
+
+ int stackID() const {
+ return fStackID;
+ }
+
+private:
+ Generator* fGenerator;
+ int fStackID = 0;
+ int fParentStackID = 0;
+};
+
+class AutoContinueMask {
+public:
+ AutoContinueMask(Generator* gen) : fGenerator(gen) {}
+
+ ~AutoContinueMask() {
+ if (fPreviousContinueMask) {
+ fGenerator->fCurrentContinueMask = fPreviousContinueMask;
+ }
+ }
+
+ void enable() {
+ SkASSERT(!fContinueMaskStack.has_value());
+
+ fContinueMaskStack.emplace(fGenerator);
+ fPreviousContinueMask = fGenerator->fCurrentContinueMask;
+ fGenerator->fCurrentContinueMask = this;
+ }
+
+ void enter() {
+ SkASSERT(fContinueMaskStack.has_value());
+ fContinueMaskStack->enter();
+ }
+
+ void exit() {
+ SkASSERT(fContinueMaskStack.has_value());
+ fContinueMaskStack->exit();
+ }
+
+ void enterLoopBody() {
+ if (fContinueMaskStack.has_value()) {
+ fContinueMaskStack->enter();
+ fGenerator->builder()->push_literal_i(0);
+ fContinueMaskStack->exit();
+ }
+ }
+
+ void exitLoopBody() {
+ if (fContinueMaskStack.has_value()) {
+ fContinueMaskStack->enter();
+ fGenerator->builder()->pop_and_reenable_loop_mask();
+ fContinueMaskStack->exit();
+ }
+ }
+
+private:
+ std::optional<AutoStack> fContinueMaskStack;
+ Generator* fGenerator = nullptr;
+ AutoContinueMask* fPreviousContinueMask = nullptr;
+};
+
+class AutoLoopTarget {
+public:
+ AutoLoopTarget(Generator* gen, int* targetPtr) : fGenerator(gen), fLoopTargetPtr(targetPtr) {
+ fLabelID = fGenerator->builder()->nextLabelID();
+ fPreviousLoopTarget = *fLoopTargetPtr;
+ *fLoopTargetPtr = fLabelID;
+ }
+
+ ~AutoLoopTarget() {
+ *fLoopTargetPtr = fPreviousLoopTarget;
+ }
+
+ int labelID() {
+ return fLabelID;
+ }
+
+private:
+ Generator* fGenerator = nullptr;
+ int* fLoopTargetPtr = nullptr;
+ int fPreviousLoopTarget;
+ int fLabelID;
+};
+
+class LValue {
+public:
+ virtual ~LValue() = default;
+
+ /** Returns true if this lvalue is actually writable--temporaries and uniforms are not. */
+ virtual bool isWritable() const = 0;
+
+ /**
+ * Returns the fixed slot range of the lvalue, after it is winnowed down to the selected
+ * field/index. The range is calculated assuming every dynamic index will evaluate to zero.
+ */
+ virtual SlotRange fixedSlotRange(Generator* gen) = 0;
+
+ /**
+ * Returns a stack which holds a single integer, representing the dynamic offset of the lvalue.
+ * This value does not incorporate the fixed offset. If null is returned, the lvalue doesn't
+ * have a dynamic offset. `evaluateDynamicIndices` must be called before this is used.
+ */
+ virtual AutoStack* dynamicSlotRange() = 0;
+
+ /** Returns the swizzle components of the lvalue, or an empty span for non-swizzle LValues. */
+ virtual SkSpan<const int8_t> swizzle() { return {}; }
+
+ /** Pushes values directly onto the stack. */
+ [[nodiscard]] virtual bool push(Generator* gen,
+ SlotRange fixedOffset,
+ AutoStack* dynamicOffset,
+ SkSpan<const int8_t> swizzle) = 0;
+
+ /** Stores topmost values from the stack directly into the lvalue. */
+ [[nodiscard]] virtual bool store(Generator* gen,
+ SlotRange fixedOffset,
+ AutoStack* dynamicOffset,
+ SkSpan<const int8_t> swizzle) = 0;
+ /**
+ * Some lvalues refer to a temporary expression; these temps can be held in the
+ * scratch-expression field to ensure that they exist for the lifetime of the lvalue.
+ */
+ std::unique_ptr<Expression> fScratchExpression;
+};
+
+class ScratchLValue final : public LValue {
+public:
+ explicit ScratchLValue(const Expression& e)
+ : fExpression(&e)
+ , fNumSlots(e.type().slotCount()) {}
+
+ ~ScratchLValue() override {
+ if (fGenerator && fDedicatedStack.has_value()) {
+ // Jettison the scratch expression.
+ fDedicatedStack->enter();
+ fGenerator->discardExpression(fNumSlots);
+ fDedicatedStack->exit();
+ }
+ }
+
+ bool isWritable() const override {
+ return false;
+ }
+
+ SlotRange fixedSlotRange(Generator* gen) override {
+ return SlotRange{0, fNumSlots};
+ }
+
+ AutoStack* dynamicSlotRange() override {
+ return nullptr;
+ }
+
+ [[nodiscard]] bool push(Generator* gen,
+ SlotRange fixedOffset,
+ AutoStack* dynamicOffset,
+ SkSpan<const int8_t> swizzle) override {
+ if (!fDedicatedStack.has_value()) {
+ // Push the scratch expression onto a dedicated stack.
+ fGenerator = gen;
+ fDedicatedStack.emplace(fGenerator);
+ fDedicatedStack->enter();
+ if (!fGenerator->pushExpression(*fExpression)) {
+ return unsupported();
+ }
+ fDedicatedStack->exit();
+ }
+
+ if (dynamicOffset) {
+ fDedicatedStack->pushCloneIndirect(fixedOffset, dynamicOffset->stackID(), fNumSlots);
+ } else {
+ fDedicatedStack->pushClone(fixedOffset, fNumSlots);
+ }
+ if (!swizzle.empty()) {
+ gen->builder()->swizzle(fixedOffset.count, swizzle);
+ }
+ return true;
+ }
+
+ [[nodiscard]] bool store(Generator*, SlotRange, AutoStack*, SkSpan<const int8_t>) override {
+ SkDEBUGFAIL("scratch lvalues cannot be stored into");
+ return unsupported();
+ }
+
+private:
+ Generator* fGenerator = nullptr;
+ const Expression* fExpression = nullptr;
+ std::optional<AutoStack> fDedicatedStack;
+ int fNumSlots = 0;
+};
+
+class VariableLValue final : public LValue {
+public:
+ explicit VariableLValue(const Variable* v) : fVariable(v) {}
+
+ bool isWritable() const override {
+ return !Generator::IsUniform(*fVariable);
+ }
+
+ SlotRange fixedSlotRange(Generator* gen) override {
+ return Generator::IsUniform(*fVariable) ? gen->getUniformSlots(*fVariable)
+ : gen->getVariableSlots(*fVariable);
+ }
+
+ AutoStack* dynamicSlotRange() override {
+ return nullptr;
+ }
+
+ [[nodiscard]] bool push(Generator* gen,
+ SlotRange fixedOffset,
+ AutoStack* dynamicOffset,
+ SkSpan<const int8_t> swizzle) override {
+ if (Generator::IsUniform(*fVariable)) {
+ if (dynamicOffset) {
+ gen->builder()->push_uniform_indirect(fixedOffset, dynamicOffset->stackID(),
+ this->fixedSlotRange(gen));
+ } else {
+ gen->builder()->push_uniform(fixedOffset);
+ }
+ } else {
+ if (dynamicOffset) {
+ gen->builder()->push_slots_indirect(fixedOffset, dynamicOffset->stackID(),
+ this->fixedSlotRange(gen));
+ } else {
+ gen->builder()->push_slots(fixedOffset);
+ }
+ }
+ if (!swizzle.empty()) {
+ gen->builder()->swizzle(fixedOffset.count, swizzle);
+ }
+ return true;
+ }
+
+ [[nodiscard]] bool store(Generator* gen,
+ SlotRange fixedOffset,
+ AutoStack* dynamicOffset,
+ SkSpan<const int8_t> swizzle) override {
+ SkASSERT(!Generator::IsUniform(*fVariable));
+
+ if (swizzle.empty()) {
+ if (dynamicOffset) {
+ gen->builder()->copy_stack_to_slots_indirect(fixedOffset, dynamicOffset->stackID(),
+ this->fixedSlotRange(gen));
+ } else {
+ gen->builder()->copy_stack_to_slots(fixedOffset);
+ }
+ } else {
+ if (dynamicOffset) {
+ gen->builder()->swizzle_copy_stack_to_slots_indirect(fixedOffset,
+ dynamicOffset->stackID(),
+ this->fixedSlotRange(gen),
+ swizzle,
+ swizzle.size());
+ } else {
+ gen->builder()->swizzle_copy_stack_to_slots(fixedOffset, swizzle, swizzle.size());
+ }
+ }
+ return true;
+ }
+
+private:
+ const Variable* fVariable;
+};
+
+class SwizzleLValue final : public LValue {
+public:
+ explicit SwizzleLValue(std::unique_ptr<LValue> p, const ComponentArray& c)
+ : fParent(std::move(p))
+ , fComponents(c) {
+ SkASSERT(!fComponents.empty() && fComponents.size() <= 4);
+ }
+
+ bool isWritable() const override {
+ return fParent->isWritable();
+ }
+
+ SlotRange fixedSlotRange(Generator* gen) override {
+ return fParent->fixedSlotRange(gen);
+ }
+
+ AutoStack* dynamicSlotRange() override {
+ return fParent->dynamicSlotRange();
+ }
+
+ SkSpan<const int8_t> swizzle() override {
+ return fComponents;
+ }
+
+ [[nodiscard]] bool push(Generator* gen,
+ SlotRange fixedOffset,
+ AutoStack* dynamicOffset,
+ SkSpan<const int8_t> swizzle) override {
+ if (!swizzle.empty()) {
+ SkDEBUGFAIL("swizzle-of-a-swizzle should have been folded out in front end");
+ return unsupported();
+ }
+ return fParent->push(gen, fixedOffset, dynamicOffset, fComponents);
+ }
+
+ [[nodiscard]] bool store(Generator* gen,
+ SlotRange fixedOffset,
+ AutoStack* dynamicOffset,
+ SkSpan<const int8_t> swizzle) override {
+ if (!swizzle.empty()) {
+ SkDEBUGFAIL("swizzle-of-a-swizzle should have been folded out in front end");
+ return unsupported();
+ }
+ return fParent->store(gen, fixedOffset, dynamicOffset, fComponents);
+ }
+
+private:
+ std::unique_ptr<LValue> fParent;
+ const ComponentArray& fComponents;
+};
+
+class UnownedLValueSlice : public LValue {
+public:
+ explicit UnownedLValueSlice(LValue* p, int initialSlot, int numSlots)
+ : fParent(p)
+ , fInitialSlot(initialSlot)
+ , fNumSlots(numSlots) {
+ SkASSERT(fInitialSlot >= 0);
+ SkASSERT(fNumSlots > 0);
+ }
+
+ bool isWritable() const override {
+ return fParent->isWritable();
+ }
+
+ SlotRange fixedSlotRange(Generator* gen) override {
+ SlotRange range = fParent->fixedSlotRange(gen);
+ SlotRange adjusted = range;
+ adjusted.index += fInitialSlot;
+ adjusted.count = fNumSlots;
+ SkASSERT((adjusted.index + adjusted.count) <= (range.index + range.count));
+ return adjusted;
+ }
+
+ AutoStack* dynamicSlotRange() override {
+ return fParent->dynamicSlotRange();
+ }
+
+ [[nodiscard]] bool push(Generator* gen,
+ SlotRange fixedOffset,
+ AutoStack* dynamicOffset,
+ SkSpan<const int8_t> swizzle) override {
+ return fParent->push(gen, fixedOffset, dynamicOffset, swizzle);
+ }
+
+ [[nodiscard]] bool store(Generator* gen,
+ SlotRange fixedOffset,
+ AutoStack* dynamicOffset,
+ SkSpan<const int8_t> swizzle) override {
+ return fParent->store(gen, fixedOffset, dynamicOffset, swizzle);
+ }
+
+protected:
+ LValue* fParent;
+
+private:
+ int fInitialSlot = 0;
+ int fNumSlots = 0;
+};
+
+class LValueSlice final : public UnownedLValueSlice {
+public:
+ explicit LValueSlice(std::unique_ptr<LValue> p, int initialSlot, int numSlots)
+ : UnownedLValueSlice(p.release(), initialSlot, numSlots) {}
+
+ ~LValueSlice() override {
+ delete fParent;
+ }
+};
+
+class DynamicIndexLValue final : public LValue {
+public:
+ explicit DynamicIndexLValue(std::unique_ptr<LValue> p, const IndexExpression& i)
+ : fParent(std::move(p))
+ , fIndexExpr(&i) {
+ SkASSERT(fIndexExpr->index()->type().isInteger());
+ }
+
+ ~DynamicIndexLValue() override {
+ if (fDedicatedStack.has_value()) {
+ SkASSERT(fGenerator);
+
+ // Jettison the index expression.
+ fDedicatedStack->enter();
+ fGenerator->discardExpression(/*slots=*/1);
+ fDedicatedStack->exit();
+ }
+ }
+
+ bool isWritable() const override {
+ return fParent->isWritable();
+ }
+
+ [[nodiscard]] bool evaluateDynamicIndices(Generator* gen) {
+ // The index must only be computed once; the index-expression could have side effects.
+ // Once it has been computed, the offset lives on `fDedicatedStack`.
+ SkASSERT(!fDedicatedStack.has_value());
+ SkASSERT(!fGenerator);
+ fGenerator = gen;
+ fDedicatedStack.emplace(fGenerator);
+
+ if (!fParent->swizzle().empty()) {
+ SkDEBUGFAIL("an indexed-swizzle should have been handled by RewriteIndexedSwizzle");
+ return unsupported();
+ }
+
+ // Push the index expression onto the dedicated stack.
+ fDedicatedStack->enter();
+ if (!fGenerator->pushExpression(*fIndexExpr->index())) {
+ return unsupported();
+ }
+
+ // Multiply the index-expression result by the per-value slot count.
+ int slotCount = fIndexExpr->type().slotCount();
+ if (slotCount != 1) {
+ fGenerator->builder()->push_literal_i(fIndexExpr->type().slotCount());
+ fGenerator->builder()->binary_op(BuilderOp::mul_n_ints, 1);
+ }
+
+ // Check to see if a parent LValue already has a dynamic index. If so, we need to
+ // incorporate its value into our own.
+ if (AutoStack* parentDynamicIndexStack = fParent->dynamicSlotRange()) {
+ parentDynamicIndexStack->pushClone(/*slots=*/1);
+ fGenerator->builder()->binary_op(BuilderOp::add_n_ints, 1);
+ }
+ fDedicatedStack->exit();
+ return true;
+ }
+
+ SlotRange fixedSlotRange(Generator* gen) override {
+ // Compute the fixed slot range as if we are indexing into position zero.
+ SlotRange range = fParent->fixedSlotRange(gen);
+ range.count = fIndexExpr->type().slotCount();
+ return range;
+ }
+
+ AutoStack* dynamicSlotRange() override {
+ // We incorporated any parent dynamic offsets when `evaluateDynamicIndices` was called.
+ SkASSERT(fDedicatedStack.has_value());
+ return &*fDedicatedStack;
+ }
+
+ [[nodiscard]] bool push(Generator* gen,
+ SlotRange fixedOffset,
+ AutoStack* dynamicOffset,
+ SkSpan<const int8_t> swizzle) override {
+ return fParent->push(gen, fixedOffset, dynamicOffset, swizzle);
+ }
+
+ [[nodiscard]] bool store(Generator* gen,
+ SlotRange fixedOffset,
+ AutoStack* dynamicOffset,
+ SkSpan<const int8_t> swizzle) override {
+ return fParent->store(gen, fixedOffset, dynamicOffset, swizzle);
+ }
+
+private:
+ Generator* fGenerator = nullptr;
+ std::unique_ptr<LValue> fParent;
+ std::optional<AutoStack> fDedicatedStack;
+ const IndexExpression* fIndexExpr = nullptr;
+};
+
+void SlotManager::addSlotDebugInfoForGroup(const std::string& varName,
+ const Type& type,
+ Position pos,
+ int* groupIndex,
+ bool isFunctionReturnValue) {
+ SkASSERT(fSlotDebugInfo);
+ switch (type.typeKind()) {
+ case Type::TypeKind::kArray: {
+ int nslots = type.columns();
+ const Type& elemType = type.componentType();
+ for (int slot = 0; slot < nslots; ++slot) {
+ this->addSlotDebugInfoForGroup(varName + "[" + std::to_string(slot) + "]", elemType,
+ pos, groupIndex, isFunctionReturnValue);
+ }
+ break;
+ }
+ case Type::TypeKind::kStruct: {
+ for (const Type::Field& field : type.fields()) {
+ this->addSlotDebugInfoForGroup(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;
+ fSlotDebugInfo->push_back(std::move(slotInfo));
+ }
+ break;
+ }
+ }
+}
+
+void SlotManager::addSlotDebugInfo(const std::string& varName,
+ const Type& type,
+ Position pos,
+ bool isFunctionReturnValue) {
+ int groupIndex = 0;
+ this->addSlotDebugInfoForGroup(varName, type, pos, &groupIndex, isFunctionReturnValue);
+ SkASSERT((size_t)groupIndex == type.slotCount());
+}
+
+SlotRange SlotManager::createSlots(std::string name,
+ const Type& type,
+ Position pos,
+ bool isFunctionReturnValue) {
+ size_t nslots = type.slotCount();
+ if (nslots == 0) {
+ return {};
+ }
+ if (fSlotDebugInfo) {
+ // Our debug slot-info table should have the same length as the actual slot table.
+ SkASSERT(fSlotDebugInfo->size() == (size_t)fSlotCount);
+
+ // Append slot names and types to our debug slot-info table.
+ fSlotDebugInfo->reserve(fSlotCount + nslots);
+ this->addSlotDebugInfo(name, type, pos, isFunctionReturnValue);
+
+ // Confirm that we added the expected number of slots.
+ SkASSERT(fSlotDebugInfo->size() == (size_t)(fSlotCount + nslots));
+ }
+
+ SlotRange result = {fSlotCount, (int)nslots};
+ fSlotCount += nslots;
+ return result;
+}
+
+SlotRange SlotManager::getVariableSlots(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 SlotManager::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;
+}
+
+static bool is_sliceable_swizzle(SkSpan<const int8_t> components) {
+ // Determine if the swizzle rearranges its elements, or if it's a simple subset of its elements.
+ // (A simple subset would be a sequential non-repeating range of components, like `.xyz` or
+ // `.yzw` or `.z`, but not `.xx` or `.xz`, which can be accessed as a slice of the variable.)
+ for (size_t index = 1; index < components.size(); ++index) {
+ if (components[index] != int8_t(components[0] + index)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+std::unique_ptr<LValue> Generator::makeLValue(const Expression& e, bool allowScratch) {
+ if (e.is<VariableReference>()) {
+ return std::make_unique<VariableLValue>(e.as<VariableReference>().variable());
+ }
+ if (e.is<Swizzle>()) {
+ const Swizzle& swizzleExpr = e.as<Swizzle>();
+ if (std::unique_ptr<LValue> base = this->makeLValue(*swizzleExpr.base(),
+ allowScratch)) {
+ const ComponentArray& components = swizzleExpr.components();
+ if (is_sliceable_swizzle(components)) {
+ // If the swizzle is a contiguous subset, we can represent it with a fixed slice.
+ return std::make_unique<LValueSlice>(std::move(base), components[0],
+ components.size());
+ }
+ return std::make_unique<SwizzleLValue>(std::move(base), components);
+ }
+ return nullptr;
+ }
+ if (e.is<FieldAccess>()) {
+ const FieldAccess& fieldExpr = e.as<FieldAccess>();
+ if (std::unique_ptr<LValue> base = this->makeLValue(*fieldExpr.base(),
+ allowScratch)) {
+ // Represent field access with a slice.
+ return std::make_unique<LValueSlice>(std::move(base), fieldExpr.initialSlot(),
+ fieldExpr.type().slotCount());
+ }
+ return nullptr;
+ }
+ if (e.is<IndexExpression>()) {
+ const IndexExpression& indexExpr = e.as<IndexExpression>();
+
+ // If the index base is swizzled (`vec.zyx[idx]`), rewrite it into an equivalent
+ // non-swizzled form (`vec[uint3(2,1,0)[idx]]`).
+ if (std::unique_ptr<Expression> rewritten = Transform::RewriteIndexedSwizzle(fContext,
+ indexExpr)) {
+ // Convert the rewritten expression into an lvalue.
+ std::unique_ptr<LValue> lvalue = this->makeLValue(*rewritten, allowScratch);
+ if (!lvalue) {
+ return nullptr;
+ }
+ // We need to hold onto the rewritten expression for the lifetime of the lvalue.
+ lvalue->fScratchExpression = std::move(rewritten);
+ return lvalue;
+ }
+ if (std::unique_ptr<LValue> base = this->makeLValue(*indexExpr.base(),
+ allowScratch)) {
+ // If the index is a compile-time constant, we can represent it with a fixed slice.
+ SKSL_INT indexValue;
+ if (ConstantFolder::GetConstantInt(*indexExpr.index(), &indexValue)) {
+ int numSlots = indexExpr.type().slotCount();
+ return std::make_unique<LValueSlice>(std::move(base), numSlots * indexValue,
+ numSlots);
+ }
+
+ // Represent non-constant indexing via a dynamic index.
+ auto dynLValue = std::make_unique<DynamicIndexLValue>(std::move(base), indexExpr);
+ return dynLValue->evaluateDynamicIndices(this) ? std::move(dynLValue)
+ : nullptr;
+ }
+ return nullptr;
+ }
+ if (allowScratch) {
+ // This path allows us to perform field- and index-accesses on an expression as if it were
+ // an lvalue, but is a temporary and shouldn't be written back to.
+ return std::make_unique<ScratchLValue>(e);
+ }
+ return nullptr;
+}
+
+bool Generator::push(LValue& lvalue) {
+ return lvalue.push(this,
+ lvalue.fixedSlotRange(this),
+ lvalue.dynamicSlotRange(),
+ /*swizzle=*/{});
+}
+
+bool Generator::store(LValue& lvalue) {
+ SkASSERT(lvalue.isWritable());
+ return lvalue.store(this,
+ lvalue.fixedSlotRange(this),
+ lvalue.dynamicSlotRange(),
+ /*swizzle=*/{});
+}
+
+int Generator::getFunctionDebugInfo(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;
+}
+
+int Generator::createStack() {
+ if (!fRecycledStacks.empty()) {
+ int stackID = fRecycledStacks.back();
+ fRecycledStacks.pop_back();
+ return stackID;
+ }
+ return ++fNextStackID;
+}
+
+void Generator::recycleStack(int stackID) {
+ fRecycledStacks.push_back(stackID);
+}
+
+void Generator::setCurrentStack(int stackID) {
+ if (fCurrentStack != stackID) {
+ fCurrentStack = stackID;
+ fBuilder.set_current_stack(stackID);
+ }
+}
+
+std::optional<SlotRange> Generator::writeFunction(const IRNode& callSite,
+ const FunctionDefinition& function) {
+ [[maybe_unused]] int funcIndex = -1;
+ if (fDebugTrace) {
+ funcIndex = this->getFunctionDebugInfo(function.declaration());
+ SkASSERT(funcIndex >= 0);
+ // TODO(debugger): add trace for function-enter
+ }
+
+ SlotRange lastFunctionResult = fCurrentFunctionResult;
+ fCurrentFunctionResult = this->getFunctionSlots(callSite, function.declaration());
+
+ if (!this->writeStatement(*function.body())) {
+ return std::nullopt;
+ }
+
+ SlotRange functionResult = fCurrentFunctionResult;
+ fCurrentFunctionResult = lastFunctionResult;
+
+ if (fDebugTrace) {
+ // TODO(debugger): add trace for function-exit
+ }
+
+ return functionResult;
+}
+
+bool Generator::writeGlobals() {
+ for (const ProgramElement* e : fProgram.elements()) {
+ if (e->is<GlobalVarDeclaration>()) {
+ const GlobalVarDeclaration& gvd = e->as<GlobalVarDeclaration>();
+ const VarDeclaration& decl = gvd.varDeclaration();
+ const Variable* var = decl.var();
+
+ if (var->type().isEffectChild()) {
+ // Associate each child effect variable with its numeric index.
+ SkASSERT(!fChildEffectMap.find(var));
+ int childEffectIndex = fChildEffectMap.count();
+ fChildEffectMap[var] = childEffectIndex;
+ continue;
+ }
+
+ // Opaque types include child processors and GL objects (samplers, textures, etc).
+ // Of those, only child processors are legal variables.
+ SkASSERT(!var->type().isVoid());
+ SkASSERT(!var->type().isOpaque());
+
+ // Builtin variables are system-defined, with special semantics.
+ if (int builtin = var->modifiers().fLayout.fBuiltin; builtin >= 0) {
+ if (builtin == SK_FRAGCOORD_BUILTIN) {
+ fBuilder.store_device_xy01(this->getVariableSlots(*var));
+ continue;
+ }
+ // The only builtin variable exposed to runtime effects is sk_FragCoord.
+ return unsupported();
+ }
+
+ if (IsUniform(*var)) {
+ // Create the uniform slot map in first-to-last order.
+ (void)this->getUniformSlots(*var);
+ continue;
+ }
+
+ // Other globals are treated as normal variable declarations.
+ if (!this->writeVarDeclaration(decl)) {
+ return unsupported();
+ }
+ }
+ }
+
+ return true;
+}
+
+bool Generator::writeStatement(const Statement& s) {
+ switch (s.kind()) {
+ case Statement::Kind::kBlock:
+ return this->writeBlock(s.as<Block>());
+
+ case Statement::Kind::kBreak:
+ return this->writeBreakStatement(s.as<BreakStatement>());
+
+ case Statement::Kind::kContinue:
+ return this->writeContinueStatement(s.as<ContinueStatement>());
+
+ case Statement::Kind::kDo:
+ return this->writeDoStatement(s.as<DoStatement>());
+
+ case Statement::Kind::kExpression:
+ return this->writeExpressionStatement(s.as<ExpressionStatement>());
+
+ case Statement::Kind::kFor:
+ return this->writeForStatement(s.as<ForStatement>());
+
+ 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::kSwitch:
+ return this->writeSwitchStatement(s.as<SwitchStatement>());
+
+ case Statement::Kind::kVarDeclaration:
+ return this->writeVarDeclaration(s.as<VarDeclaration>());
+
+ default:
+ return unsupported();
+ }
+}
+
+bool Generator::writeBlock(const Block& b) {
+ for (const std::unique_ptr<Statement>& stmt : b.children()) {
+ if (!this->writeStatement(*stmt)) {
+ return unsupported();
+ }
+ }
+ return true;
+}
+
+bool Generator::writeBreakStatement(const BreakStatement&) {
+ // If all lanes have reached this break, we can just branch straight to the break target instead
+ // of updating masks.
+ fBuilder.branch_if_all_lanes_active(fCurrentBreakTarget);
+ fBuilder.mask_off_loop_mask();
+ return true;
+}
+
+bool Generator::writeContinueStatement(const ContinueStatement&) {
+ // This could be written as one hand-tuned RasterPipeline op, but for now, we reuse existing ops
+ // to assemble a continue op.
+
+ // Set any currently-executing lanes in the continue-mask to true via `select.`
+ fCurrentContinueMask->enter();
+ fBuilder.push_literal_i(~0);
+ fBuilder.select(/*slots=*/1);
+
+ // Disable any currently-executing lanes from the loop mask.
+ fBuilder.mask_off_loop_mask();
+ fCurrentContinueMask->exit();
+
+ return true;
+}
+
+bool Generator::writeDoStatement(const DoStatement& d) {
+ // Set up a break target.
+ AutoLoopTarget breakTarget(this, &fCurrentBreakTarget);
+
+ // Save off the original loop mask.
+ fBuilder.enableExecutionMaskWrites();
+ fBuilder.push_loop_mask();
+
+ // If `continue` is used in the loop...
+ Analysis::LoopControlFlowInfo loopInfo = Analysis::GetLoopControlFlowInfo(*d.statement());
+ AutoContinueMask autoContinueMask(this);
+ if (loopInfo.fHasContinue) {
+ // ... create a temporary slot for continue-mask storage.
+ autoContinueMask.enable();
+ }
+
+ // Write the do-loop body.
+ int labelID = fBuilder.nextLabelID();
+ fBuilder.label(labelID);
+
+ autoContinueMask.enterLoopBody();
+
+ if (!this->writeStatement(*d.statement())) {
+ return false;
+ }
+
+ autoContinueMask.exitLoopBody();
+
+ // Emit the test-expression, in order to combine it with the loop mask.
+ if (!this->pushExpression(*d.test())) {
+ return false;
+ }
+
+ // Mask off any lanes in the loop mask where the test-expression is false; this breaks the loop.
+ // We don't use the test expression for anything else, so jettison it.
+ fBuilder.merge_loop_mask();
+ this->discardExpression(/*slots=*/1);
+
+ // If any lanes are still running, go back to the top and run the loop body again.
+ fBuilder.branch_if_any_lanes_active(labelID);
+
+ // If we hit a break statement on all lanes, we will branch here to escape from the loop.
+ fBuilder.label(breakTarget.labelID());
+
+ // Restore the loop mask.
+ fBuilder.pop_loop_mask();
+ fBuilder.disableExecutionMaskWrites();
+
+ return true;
+}
+
+bool Generator::writeMasklessForStatement(const ForStatement& f) {
+ SkASSERT(f.unrollInfo());
+ SkASSERT(f.unrollInfo()->fCount > 0);
+ SkASSERT(f.initializer());
+ SkASSERT(f.test());
+ SkASSERT(f.next());
+
+ // If no lanes are active, skip over the loop entirely. This guards against looping forever;
+ // with no lanes active, we wouldn't be able to write the loop variable back to its slot, so
+ // we'd never make forward progress.
+ int loopExitID = fBuilder.nextLabelID();
+ int loopBodyID = fBuilder.nextLabelID();
+ fBuilder.branch_if_no_lanes_active(loopExitID);
+
+ // Run the loop initializer.
+ if (!this->writeStatement(*f.initializer())) {
+ return unsupported();
+ }
+
+ // Write the for-loop body. We know the for-loop has a standard ES2 unrollable structure, and
+ // that it runs for at least one iteration, so we can plow straight ahead into the loop body
+ // instead of running the loop-test first.
+ fBuilder.label(loopBodyID);
+
+ if (!this->writeStatement(*f.statement())) {
+ return unsupported();
+ }
+
+ // If the loop only runs for a single iteration, we are already done. If not...
+ if (f.unrollInfo()->fCount > 1) {
+ // ... run the next-expression, and immediately discard its result.
+ if (!this->pushExpression(*f.next(), /*usesResult=*/false)) {
+ return unsupported();
+ }
+ this->discardExpression(f.next()->type().slotCount());
+
+ // Run the test-expression, and repeat the loop until the test-expression evaluates false.
+ if (!this->pushExpression(*f.test())) {
+ return unsupported();
+ }
+ fBuilder.branch_if_no_active_lanes_on_stack_top_equal(0, loopBodyID);
+
+ // Jettison the test-expression.
+ this->discardExpression(/*slots=*/1);
+ }
+
+ fBuilder.label(loopExitID);
+ return true;
+}
+
+bool Generator::writeForStatement(const ForStatement& f) {
+ // If we've determined that the loop does not run, omit its code entirely.
+ if (f.unrollInfo() && f.unrollInfo()->fCount == 0) {
+ return true;
+ }
+
+ // If the loop doesn't escape early due to a `continue`, `break` or `return`, and the loop
+ // conforms to ES2 structure, we know that we will run the full number of iterations across all
+ // lanes and don't need to use a loop mask.
+ Analysis::LoopControlFlowInfo loopInfo = Analysis::GetLoopControlFlowInfo(*f.statement());
+ if (!loopInfo.fHasContinue && !loopInfo.fHasBreak && !loopInfo.fHasReturn && f.unrollInfo()) {
+ return this->writeMasklessForStatement(f);
+ }
+
+ // Set up a break target.
+ AutoLoopTarget breakTarget(this, &fCurrentBreakTarget);
+
+ // Run the loop initializer.
+ if (f.initializer() && !this->writeStatement(*f.initializer())) {
+ return unsupported();
+ }
+
+ AutoContinueMask autoContinueMask(this);
+ if (loopInfo.fHasContinue) {
+ // Acquire a temporary slot for continue-mask storage.
+ autoContinueMask.enable();
+ }
+
+ // Save off the original loop mask.
+ fBuilder.enableExecutionMaskWrites();
+ fBuilder.push_loop_mask();
+
+ int loopTestID = fBuilder.nextLabelID();
+ int loopBodyID = fBuilder.nextLabelID();
+
+ // Jump down to the loop test so we can fall out of the loop immediately if it's zero-iteration.
+ fBuilder.jump(loopTestID);
+
+ // Write the for-loop body.
+ fBuilder.label(loopBodyID);
+
+ autoContinueMask.enterLoopBody();
+
+ if (!this->writeStatement(*f.statement())) {
+ return unsupported();
+ }
+
+ autoContinueMask.exitLoopBody();
+
+ // Run the next-expression. Immediately discard its result.
+ if (f.next()) {
+ if (!this->pushExpression(*f.next(), /*usesResult=*/false)) {
+ return unsupported();
+ }
+ this->discardExpression(f.next()->type().slotCount());
+ }
+
+ fBuilder.label(loopTestID);
+ if (f.test()) {
+ // Emit the test-expression, in order to combine it with the loop mask.
+ if (!this->pushExpression(*f.test())) {
+ return unsupported();
+ }
+ // Mask off any lanes in the loop mask where the test-expression is false; this breaks the
+ // loop. We don't use the test expression for anything else, so jettison it.
+ fBuilder.merge_loop_mask();
+ this->discardExpression(/*slots=*/1);
+ }
+
+ // If any lanes are still running, go back to the top and run the loop body again.
+ fBuilder.branch_if_any_lanes_active(loopBodyID);
+
+ // If we hit a break statement on all lanes, we will branch here to escape from the loop.
+ fBuilder.label(breakTarget.labelID());
+
+ // Restore the loop mask.
+ fBuilder.pop_loop_mask();
+ fBuilder.disableExecutionMaskWrites();
+
+ return true;
+}
+
+bool Generator::writeExpressionStatement(const ExpressionStatement& e) {
+ if (!this->pushExpression(*e.expression(), /*usesResult=*/false)) {
+ return unsupported();
+ }
+ this->discardExpression(e.expression()->type().slotCount());
+ return true;
+}
+
+bool Generator::writeDynamicallyUniformIfStatement(const IfStatement& i) {
+ SkASSERT(Analysis::IsDynamicallyUniformExpression(*i.test()));
+
+ int falseLabelID = fBuilder.nextLabelID();
+ int exitLabelID = fBuilder.nextLabelID();
+
+ if (!this->pushExpression(*i.test())) {
+ return unsupported();
+ }
+
+ fBuilder.branch_if_no_active_lanes_on_stack_top_equal(~0, falseLabelID);
+
+ if (!this->writeStatement(*i.ifTrue())) {
+ return unsupported();
+ }
+
+ if (!i.ifFalse()) {
+ // We don't have an if-false condition at all.
+ fBuilder.label(falseLabelID);
+ } else {
+ // We do have an if-false condition. We've just completed the if-true block, so we need to
+ // jump past the if-false block to avoid executing it.
+ fBuilder.jump(exitLabelID);
+
+ // The if-false block starts here.
+ fBuilder.label(falseLabelID);
+
+ if (!this->writeStatement(*i.ifFalse())) {
+ return unsupported();
+ }
+
+ fBuilder.label(exitLabelID);
+ }
+
+ // Jettison the test-expression.
+ this->discardExpression(/*slots=*/1);
+ return true;
+}
+
+bool Generator::writeIfStatement(const IfStatement& i) {
+ // If the test condition is known to be uniform, we can skip over the untrue portion entirely.
+ if (Analysis::IsDynamicallyUniformExpression(*i.test())) {
+ return this->writeDynamicallyUniformIfStatement(i);
+ }
+
+ // Save the current condition-mask.
+ fBuilder.enableExecutionMaskWrites();
+ fBuilder.push_condition_mask();
+
+ // Push the test condition mask.
+ if (!this->pushExpression(*i.test())) {
+ return unsupported();
+ }
+
+ // Merge the current condition-mask with the test condition, then run the if-true branch.
+ fBuilder.merge_condition_mask();
+ if (!this->writeStatement(*i.ifTrue())) {
+ return unsupported();
+ }
+
+ if (i.ifFalse()) {
+ // Negate the test-condition, then reapply it to the condition-mask.
+ // Then, run the if-false branch.
+ fBuilder.unary_op(BuilderOp::bitwise_not_int, /*slots=*/1);
+ fBuilder.merge_condition_mask();
+ if (!this->writeStatement(*i.ifFalse())) {
+ return unsupported();
+ }
+ }
+
+ // Jettison the test-expression, and restore the the condition-mask.
+ this->discardExpression(/*slots=*/1);
+ fBuilder.pop_condition_mask();
+ fBuilder.disableExecutionMaskWrites();
+
+ return true;
+}
+
+bool Generator::writeReturnStatement(const ReturnStatement& r) {
+ if (r.expression()) {
+ if (!this->pushExpression(*r.expression())) {
+ return unsupported();
+ }
+ if (this->needsFunctionResultSlots()) {
+ this->popToSlotRange(fCurrentFunctionResult);
+ }
+ }
+ if (fBuilder.executionMaskWritesAreEnabled() && this->needsReturnMask()) {
+ fBuilder.mask_off_return_mask();
+ }
+ return true;
+}
+
+bool Generator::writeSwitchStatement(const SwitchStatement& s) {
+ const StatementArray& cases = s.cases();
+ SkASSERT(std::all_of(cases.begin(), cases.end(), [](const std::unique_ptr<Statement>& stmt) {
+ return stmt->is<SwitchCase>();
+ }));
+
+ // Set up a break target.
+ AutoLoopTarget breakTarget(this, &fCurrentBreakTarget);
+
+ // Save off the original loop mask.
+ fBuilder.enableExecutionMaskWrites();
+ fBuilder.push_loop_mask();
+
+ // Push the switch-case value, and write a default-mask that enables every lane which already
+ // has an active loop mask. As we match cases, the default mask will get pared down.
+ if (!this->pushExpression(*s.value())) {
+ return unsupported();
+ }
+ fBuilder.push_loop_mask();
+
+ // Zero out the loop mask; each case op will re-enable it as we go.
+ fBuilder.mask_off_loop_mask();
+
+ // Write each switch-case.
+ bool foundDefaultCase = false;
+ for (const std::unique_ptr<Statement>& stmt : cases) {
+ int skipLabelID = fBuilder.nextLabelID();
+
+ const SwitchCase& sc = stmt->as<SwitchCase>();
+ if (sc.isDefault()) {
+ foundDefaultCase = true;
+ if (stmt.get() != cases.back().get()) {
+ // We only support a default case when it is the very last case. If that changes,
+ // this logic will need to be updated.
+ return unsupported();
+ }
+ // Keep whatever lanes are executing now, and also enable any lanes in the default mask.
+ fBuilder.pop_and_reenable_loop_mask();
+ // Execute the switch-case block, if any lanes are alive to see it.
+ fBuilder.branch_if_no_lanes_active(skipLabelID);
+ if (!this->writeStatement(*sc.statement())) {
+ return unsupported();
+ }
+ } else {
+ // The case-op will enable the loop mask if the switch-value matches, and mask off lanes
+ // from the default-mask.
+ fBuilder.case_op(sc.value());
+ // Execute the switch-case block, if any lanes are alive to see it.
+ fBuilder.branch_if_no_lanes_active(skipLabelID);
+ if (!this->writeStatement(*sc.statement())) {
+ return unsupported();
+ }
+ }
+ fBuilder.label(skipLabelID);
+ }
+
+ // Jettison the switch value, and the default case mask if it was never consumed above.
+ this->discardExpression(/*slots=*/foundDefaultCase ? 1 : 2);
+
+ // If we hit a break statement on all lanes, we will branch here to escape from the switch.
+ fBuilder.label(breakTarget.labelID());
+
+ // Restore the loop mask.
+ fBuilder.pop_loop_mask();
+ fBuilder.disableExecutionMaskWrites();
+ return true;
+}
+
+bool Generator::writeVarDeclaration(const VarDeclaration& v) {
+ if (v.value()) {
+ if (!this->pushExpression(*v.value())) {
+ return unsupported();
+ }
+ this->popToSlotRangeUnmasked(this->getVariableSlots(*v.var()));
+ } else {
+ this->zeroSlotRangeUnmasked(this->getVariableSlots(*v.var()));
+ }
+ return true;
+}
+
+bool Generator::pushExpression(const Expression& e, bool usesResult) {
+ switch (e.kind()) {
+ case Expression::Kind::kBinary:
+ return this->pushBinaryExpression(e.as<BinaryExpression>());
+
+ case Expression::Kind::kChildCall:
+ return this->pushChildCall(e.as<ChildCall>());
+
+ case Expression::Kind::kConstructorArray:
+ case Expression::Kind::kConstructorArrayCast:
+ case Expression::Kind::kConstructorCompound:
+ case Expression::Kind::kConstructorStruct:
+ return this->pushConstructorCompound(e.asAnyConstructor());
+
+ case Expression::Kind::kConstructorCompoundCast:
+ case Expression::Kind::kConstructorScalarCast:
+ return this->pushConstructorCast(e.asAnyConstructor());
+
+ case Expression::Kind::kConstructorDiagonalMatrix:
+ return this->pushConstructorDiagonalMatrix(e.as<ConstructorDiagonalMatrix>());
+
+ case Expression::Kind::kConstructorMatrixResize:
+ return this->pushConstructorMatrixResize(e.as<ConstructorMatrixResize>());
+
+ case Expression::Kind::kConstructorSplat:
+ return this->pushConstructorSplat(e.as<ConstructorSplat>());
+
+ case Expression::Kind::kFieldAccess:
+ return this->pushFieldAccess(e.as<FieldAccess>());
+
+ case Expression::Kind::kFunctionCall:
+ return this->pushFunctionCall(e.as<FunctionCall>());
+
+ case Expression::Kind::kIndex:
+ return this->pushIndexExpression(e.as<IndexExpression>());
+
+ case Expression::Kind::kLiteral:
+ return this->pushLiteral(e.as<Literal>());
+
+ case Expression::Kind::kPrefix:
+ return this->pushPrefixExpression(e.as<PrefixExpression>());
+
+ case Expression::Kind::kPostfix:
+ return this->pushPostfixExpression(e.as<PostfixExpression>(), usesResult);
+
+ case Expression::Kind::kSwizzle:
+ return this->pushSwizzle(e.as<Swizzle>());
+
+ case Expression::Kind::kTernary:
+ return this->pushTernaryExpression(e.as<TernaryExpression>());
+
+ case Expression::Kind::kVariableReference:
+ return this->pushVariableReference(e.as<VariableReference>());
+
+ default:
+ return unsupported();
+ }
+}
+
+BuilderOp Generator::GetTypedOp(const SkSL::Type& type, const TypedOps& ops) {
+ switch (type.componentType().numberKind()) {
+ case Type::NumberKind::kFloat: return ops.fFloatOp;
+ case Type::NumberKind::kSigned: return ops.fSignedOp;
+ case Type::NumberKind::kUnsigned: return ops.fUnsignedOp;
+ case Type::NumberKind::kBoolean: return ops.fBooleanOp;
+ default: return BuilderOp::unsupported;
+ }
+}
+
+bool Generator::unaryOp(const SkSL::Type& type, const TypedOps& ops) {
+ BuilderOp op = GetTypedOp(type, ops);
+ if (op == BuilderOp::unsupported) {
+ return unsupported();
+ }
+ fBuilder.unary_op(op, type.slotCount());
+ return true;
+}
+
+bool Generator::binaryOp(const SkSL::Type& type, const TypedOps& ops) {
+ BuilderOp op = GetTypedOp(type, ops);
+ if (op == BuilderOp::unsupported) {
+ return unsupported();
+ }
+ fBuilder.binary_op(op, type.slotCount());
+ return true;
+}
+
+bool Generator::ternaryOp(const SkSL::Type& type, const TypedOps& ops) {
+ BuilderOp op = GetTypedOp(type, ops);
+ if (op == BuilderOp::unsupported) {
+ return unsupported();
+ }
+ fBuilder.ternary_op(op, type.slotCount());
+ return true;
+}
+
+void Generator::foldWithMultiOp(BuilderOp op, int elements) {
+ // Fold the top N elements on the stack using an op that supports multiple slots, e.g.:
+ // (A + B + C + D) -> add_2_floats $0..1 += $2..3
+ // add_float $0 += $1
+ for (; elements >= 8; elements -= 4) {
+ fBuilder.binary_op(op, /*slots=*/4);
+ }
+ for (; elements >= 6; elements -= 3) {
+ fBuilder.binary_op(op, /*slots=*/3);
+ }
+ for (; elements >= 4; elements -= 2) {
+ fBuilder.binary_op(op, /*slots=*/2);
+ }
+ for (; elements >= 2; elements -= 1) {
+ fBuilder.binary_op(op, /*slots=*/1);
+ }
+}
+
+bool Generator::pushLValueOrExpression(LValue* lvalue, const Expression& expr) {
+ return lvalue ? this->push(*lvalue)
+ : this->pushExpression(expr);
+}
+
+bool Generator::pushMatrixMultiply(LValue* lvalue,
+ const Expression& left,
+ const Expression& right,
+ int leftColumns,
+ int leftRows,
+ int rightColumns,
+ int rightRows) {
+ SkASSERT(left.type().isMatrix() || left.type().isVector());
+ SkASSERT(right.type().isMatrix() || right.type().isVector());
+
+ SkASSERT(leftColumns == rightRows);
+ int outColumns = rightColumns,
+ outRows = leftRows;
+
+ // Push the left matrix onto the adjacent-neighbor stack. We transpose it so that we can copy
+ // rows from it in a single op, instead of gathering one element at a time.
+ AutoStack matrixStack(this);
+ matrixStack.enter();
+ if (!this->pushLValueOrExpression(lvalue, left)) {
+ return unsupported();
+ }
+ fBuilder.transpose(leftColumns, leftRows);
+
+ // Push the right matrix as well, then go back to the primary stack.
+ if (!this->pushExpression(right)) {
+ return unsupported();
+ }
+ matrixStack.exit();
+
+ // Calculate the offsets of the left- and right-matrix, relative to the stack-top.
+ int leftMtxBase = left.type().slotCount() + right.type().slotCount();
+ int rightMtxBase = right.type().slotCount();
+
+ // Emit each matrix element.
+ for (int c = 0; c < outColumns; ++c) {
+ for (int r = 0; r < outRows; ++r) {
+ // Dot a vector from left[*][r] with right[c][*].
+ // (Because the left=matrix has been transposed, we actually pull left[r][*], which
+ // allows us to clone a column at once instead of cloning each slot individually.)
+ matrixStack.pushClone(SlotRange{r * leftColumns, leftColumns}, leftMtxBase);
+ matrixStack.pushClone(SlotRange{c * leftColumns, leftColumns}, rightMtxBase);
+ fBuilder.dot_floats(leftColumns);
+ }
+ }
+
+ // Dispose of the source matrices on the adjacent-neighbor stack.
+ matrixStack.enter();
+ this->discardExpression(left.type().slotCount());
+ this->discardExpression(right.type().slotCount());
+ matrixStack.exit();
+
+ // If this multiply was actually an assignment (via *=), write the result back to the lvalue.
+ return lvalue ? this->store(*lvalue)
+ : true;
+}
+
+void Generator::foldComparisonOp(Operator op, int elements) {
+ switch (op.kind()) {
+ case OperatorKind::EQEQ:
+ // equal(x,y) returns a vector; use & to fold into a scalar.
+ this->foldWithMultiOp(BuilderOp::bitwise_and_n_ints, elements);
+ break;
+
+ case OperatorKind::NEQ:
+ // notEqual(x,y) returns a vector; use | to fold into a scalar.
+ this->foldWithMultiOp(BuilderOp::bitwise_or_n_ints, elements);
+ break;
+
+ default:
+ SkDEBUGFAIL("comparison only allows == and !=");
+ break;
+ }
+}
+
+bool Generator::pushStructuredComparison(LValue* left,
+ Operator op,
+ LValue* right,
+ const Type& type) {
+ if (type.isStruct()) {
+ // Compare every field in the struct.
+ SkSpan<const Type::Field> fields = type.fields();
+ int currentSlot = 0;
+ for (size_t index = 0; index < fields.size(); ++index) {
+ const Type& fieldType = *fields[index].fType;
+ const int fieldSlotCount = fieldType.slotCount();
+ UnownedLValueSlice fieldLeft {left, currentSlot, fieldSlotCount};
+ UnownedLValueSlice fieldRight{right, currentSlot, fieldSlotCount};
+ if (!this->pushStructuredComparison(&fieldLeft, op, &fieldRight, fieldType)) {
+ return unsupported();
+ }
+ currentSlot += fieldSlotCount;
+ }
+
+ this->foldComparisonOp(op, fields.size());
+ return true;
+ }
+
+ if (type.isArray()) {
+ const Type& indexedType = type.componentType();
+ if (indexedType.numberKind() == Type::NumberKind::kNonnumeric) {
+ // Compare every element in the array.
+ const int indexedSlotCount = indexedType.slotCount();
+ int currentSlot = 0;
+ for (int index = 0; index < type.columns(); ++index) {
+ UnownedLValueSlice indexedLeft {left, currentSlot, indexedSlotCount};
+ UnownedLValueSlice indexedRight{right, currentSlot, indexedSlotCount};
+ if (!this->pushStructuredComparison(&indexedLeft, op, &indexedRight, indexedType)) {
+ return unsupported();
+ }
+ currentSlot += indexedSlotCount;
+ }
+
+ this->foldComparisonOp(op, type.columns());
+ return true;
+ }
+ }
+
+ // We've winnowed down to a single element, or an array of homogeneous numeric elements.
+ // Push the elements onto the stack, then compare them.
+ if (!this->push(*left) || !this->push(*right)) {
+ return unsupported();
+ }
+ switch (op.kind()) {
+ case OperatorKind::EQEQ:
+ if (!this->binaryOp(type, kEqualOps)) {
+ return unsupported();
+ }
+ break;
+
+ case OperatorKind::NEQ:
+ if (!this->binaryOp(type, kNotEqualOps)) {
+ return unsupported();
+ }
+ break;
+
+ default:
+ SkDEBUGFAIL("comparison only allows == and !=");
+ break;
+ }
+
+ this->foldComparisonOp(op, type.slotCount());
+ return true;
+}
+
+bool Generator::pushBinaryExpression(const BinaryExpression& e) {
+ return this->pushBinaryExpression(*e.left(), e.getOperator(), *e.right());
+}
+
+bool Generator::pushBinaryExpression(const Expression& left, Operator op, const Expression& right) {
+ switch (op.kind()) {
+ // Rewrite greater-than ops as their less-than equivalents.
+ case OperatorKind::GT:
+ return this->pushBinaryExpression(right, OperatorKind::LT, left);
+
+ case OperatorKind::GTEQ:
+ return this->pushBinaryExpression(right, OperatorKind::LTEQ, left);
+
+ // Handle struct and array comparisons.
+ case OperatorKind::EQEQ:
+ case OperatorKind::NEQ:
+ if (left.type().isStruct() || left.type().isArray()) {
+ SkASSERT(left.type().matches(right.type()));
+ std::unique_ptr<LValue> lvLeft = this->makeLValue(left, /*allowScratch=*/true);
+ std::unique_ptr<LValue> lvRight = this->makeLValue(right, /*allowScratch=*/true);
+ return this->pushStructuredComparison(lvLeft.get(), op, lvRight.get(), left.type());
+ }
+ break;
+
+ // Emit comma expressions.
+ case OperatorKind::COMMA:
+ if (Analysis::HasSideEffects(left)) {
+ if (!this->pushExpression(left, /*usesResult=*/false)) {
+ return unsupported();
+ }
+ this->discardExpression(left.type().slotCount());
+ }
+ return this->pushExpression(right);
+
+ default:
+ break;
+ }
+
+ // Handle binary expressions with mismatched types.
+ bool vectorizeLeft = false, vectorizeRight = false;
+ if (!left.type().matches(right.type())) {
+ if (left.type().componentType().numberKind() != right.type().componentType().numberKind()) {
+ return unsupported();
+ }
+ if (left.type().isScalar() && (right.type().isVector() || right.type().isMatrix())) {
+ vectorizeLeft = true;
+ } else if ((left.type().isVector() || left.type().isMatrix()) && right.type().isScalar()) {
+ vectorizeRight = true;
+ }
+ }
+
+ const Type& type = vectorizeLeft ? right.type() : left.type();
+
+ // If this is an assignment...
+ std::unique_ptr<LValue> lvalue;
+ if (op.isAssignment()) {
+ // ... turn the left side into an lvalue.
+ lvalue = this->makeLValue(left);
+ if (!lvalue) {
+ return unsupported();
+ }
+
+ // Handle simple assignment (`var = expr`).
+ if (op.kind() == OperatorKind::EQ) {
+ return this->pushExpression(right) &&
+ this->store(*lvalue);
+ }
+
+ // Strip off the assignment from the op (turning += into +).
+ op = op.removeAssignment();
+ }
+
+ // Handle matrix multiplication (MxM/MxV/VxM).
+ if (op.kind() == OperatorKind::STAR) {
+ // Matrix * matrix:
+ if (left.type().isMatrix() && right.type().isMatrix()) {
+ return this->pushMatrixMultiply(lvalue.get(), left, right,
+ left.type().columns(), left.type().rows(),
+ right.type().columns(), right.type().rows());
+ }
+
+ // Vector * matrix:
+ if (left.type().isVector() && right.type().isMatrix()) {
+ return this->pushMatrixMultiply(lvalue.get(), left, right,
+ left.type().columns(), 1,
+ right.type().columns(), right.type().rows());
+ }
+
+ // Matrix * vector:
+ if (left.type().isMatrix() && right.type().isVector()) {
+ return this->pushMatrixMultiply(lvalue.get(), left, right,
+ left.type().columns(), left.type().rows(),
+ 1, right.type().columns());
+ }
+ }
+
+ if (!vectorizeLeft && !vectorizeRight && !type.matches(right.type())) {
+ // We have mismatched types but don't know how to handle them.
+ return unsupported();
+ }
+
+ // Handle binary ops which require short-circuiting.
+ switch (op.kind()) {
+ case OperatorKind::LOGICALAND:
+ if (Analysis::HasSideEffects(right)) {
+ // If the RHS has side effects, we rewrite `a && b` as `a ? b : false`. This
+ // generates pretty solid code and gives us the required short-circuit behavior.
+ SkASSERT(!op.isAssignment());
+ SkASSERT(type.componentType().isBoolean());
+ SkASSERT(type.slotCount() == 1); // operator&& only works with scalar types
+ Literal falseLiteral{Position{}, 0.0, &right.type()};
+ return this->pushTernaryExpression(left, right, falseLiteral);
+ }
+ break;
+
+ case OperatorKind::LOGICALOR:
+ if (Analysis::HasSideEffects(right)) {
+ // If the RHS has side effects, we rewrite `a || b` as `a ? true : b`.
+ SkASSERT(!op.isAssignment());
+ SkASSERT(type.componentType().isBoolean());
+ SkASSERT(type.slotCount() == 1); // operator|| only works with scalar types
+ Literal trueLiteral{Position{}, 1.0, &right.type()};
+ return this->pushTernaryExpression(left, trueLiteral, right);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ // Push the left- and right-expressions onto the stack.
+ if (!this->pushLValueOrExpression(lvalue.get(), left)) {
+ return unsupported();
+ }
+ if (vectorizeLeft) {
+ fBuilder.push_duplicates(right.type().slotCount() - 1);
+ }
+ if (!this->pushExpression(right)) {
+ return unsupported();
+ }
+ if (vectorizeRight) {
+ fBuilder.push_duplicates(left.type().slotCount() - 1);
+ }
+
+ switch (op.kind()) {
+ case OperatorKind::PLUS:
+ if (!this->binaryOp(type, kAddOps)) {
+ return unsupported();
+ }
+ break;
+
+ case OperatorKind::MINUS:
+ if (!this->binaryOp(type, kSubtractOps)) {
+ return unsupported();
+ }
+ break;
+
+ case OperatorKind::STAR:
+ if (!this->binaryOp(type, kMultiplyOps)) {
+ return unsupported();
+ }
+ break;
+
+ case OperatorKind::SLASH:
+ if (!this->binaryOp(type, kDivideOps)) {
+ return unsupported();
+ }
+ break;
+
+ case OperatorKind::LT:
+ case OperatorKind::GT:
+ if (!this->binaryOp(type, kLessThanOps)) {
+ return unsupported();
+ }
+ SkASSERT(type.slotCount() == 1); // operator< only works with scalar types
+ break;
+
+ case OperatorKind::LTEQ:
+ case OperatorKind::GTEQ:
+ if (!this->binaryOp(type, kLessThanEqualOps)) {
+ return unsupported();
+ }
+ SkASSERT(type.slotCount() == 1); // operator<= only works with scalar types
+ break;
+
+ case OperatorKind::EQEQ:
+ if (!this->binaryOp(type, kEqualOps)) {
+ return unsupported();
+ }
+ this->foldComparisonOp(op, type.slotCount());
+ break;
+
+ case OperatorKind::NEQ:
+ if (!this->binaryOp(type, kNotEqualOps)) {
+ return unsupported();
+ }
+ this->foldComparisonOp(op, type.slotCount());
+ break;
+
+ case OperatorKind::LOGICALAND:
+ case OperatorKind::BITWISEAND:
+ // For logical-and, we verified above that the RHS does not have side effects, so we
+ // don't need to worry about short-circuiting side effects.
+ fBuilder.binary_op(BuilderOp::bitwise_and_n_ints, type.slotCount());
+ break;
+
+ case OperatorKind::LOGICALOR:
+ case OperatorKind::BITWISEOR:
+ // For logical-or, we verified above that the RHS does not have side effects.
+ fBuilder.binary_op(BuilderOp::bitwise_or_n_ints, type.slotCount());
+ break;
+
+ case OperatorKind::LOGICALXOR:
+ case OperatorKind::BITWISEXOR:
+ // Logical-xor does not short circuit.
+ fBuilder.binary_op(BuilderOp::bitwise_xor_n_ints, type.slotCount());
+ break;
+
+ default:
+ return unsupported();
+ }
+
+ // If we have an lvalue, we need to write the result back into it.
+ return lvalue ? this->store(*lvalue)
+ : true;
+}
+
+bool Generator::pushConstructorCompound(const AnyConstructor& c) {
+ for (const std::unique_ptr<Expression> &arg : c.argumentSpan()) {
+ if (!this->pushExpression(*arg)) {
+ return unsupported();
+ }
+ }
+ return true;
+}
+
+bool Generator::pushChildCall(const ChildCall& c) {
+ int* childIdx = fChildEffectMap.find(&c.child());
+ SkASSERT(childIdx != nullptr);
+ SkASSERT(!c.arguments().empty());
+
+ // Save the dst.rgba fields; these hold our execution masks, and could potentially be
+ // clobbered by the child effect.
+ fBuilder.push_dst_rgba();
+
+ // All child calls have at least one argument.
+ const Expression* arg = c.arguments()[0].get();
+ if (!this->pushExpression(*arg)) {
+ return unsupported();
+ }
+
+ // Copy arguments from the stack into src/dst as required by this particular child-call.
+ switch (c.child().type().typeKind()) {
+ case Type::TypeKind::kShader: {
+ // The argument must be a float2.
+ SkASSERT(c.arguments().size() == 1);
+ SkASSERT(arg->type().matches(*fContext.fTypes.fFloat2));
+ fBuilder.pop_src_rg();
+ fBuilder.invoke_shader(*childIdx);
+ break;
+ }
+ case Type::TypeKind::kColorFilter: {
+ // The argument must be a half4/float4.
+ SkASSERT(c.arguments().size() == 1);
+ SkASSERT(arg->type().matches(*fContext.fTypes.fHalf4) ||
+ arg->type().matches(*fContext.fTypes.fFloat4));
+ fBuilder.pop_src_rgba();
+ fBuilder.invoke_color_filter(*childIdx);
+ break;
+ }
+ case Type::TypeKind::kBlender: {
+ // The first argument must be a half4/float4.
+ SkASSERT(c.arguments().size() == 2);
+ SkASSERT(arg->type().matches(*fContext.fTypes.fHalf4) ||
+ arg->type().matches(*fContext.fTypes.fFloat4));
+
+ // The second argument must also be a half4/float4.
+ arg = c.arguments()[1].get();
+ SkASSERT(arg->type().matches(*fContext.fTypes.fHalf4) ||
+ arg->type().matches(*fContext.fTypes.fFloat4));
+
+ if (!this->pushExpression(*arg)) {
+ return unsupported();
+ }
+
+ fBuilder.pop_dst_rgba();
+ fBuilder.pop_src_rgba();
+ fBuilder.invoke_blender(*childIdx);
+ break;
+ }
+ default: {
+ SkDEBUGFAILF("cannot sample from type '%s'", c.child().type().description().c_str());
+ }
+ }
+
+ // Restore dst.rgba so our execution masks are back to normal.
+ fBuilder.pop_dst_rgba();
+
+ // The child call has returned the result color via src.rgba; push it onto the stack.
+ fBuilder.push_src_rgba();
+ return true;
+}
+
+bool Generator::pushConstructorCast(const AnyConstructor& c) {
+ SkASSERT(c.argumentSpan().size() == 1);
+ const Expression& inner = *c.argumentSpan().front();
+ SkASSERT(inner.type().slotCount() == c.type().slotCount());
+
+ if (!this->pushExpression(inner)) {
+ return unsupported();
+ }
+ if (inner.type().componentType().numberKind() == c.type().componentType().numberKind()) {
+ // Since we ignore type precision, this cast is effectively a no-op.
+ return true;
+ }
+ if (inner.type().componentType().isSigned() && c.type().componentType().isUnsigned()) {
+ // Treat uint(int) as a no-op.
+ return true;
+ }
+ if (inner.type().componentType().isUnsigned() && c.type().componentType().isSigned()) {
+ // Treat int(uint) as a no-op.
+ return true;
+ }
+
+ if (c.type().componentType().isBoolean()) {
+ // Converting int or float to boolean can be accomplished via `notEqual(x, 0)`.
+ fBuilder.push_zeros(c.type().slotCount());
+ return this->binaryOp(inner.type(), kNotEqualOps);
+ }
+ if (inner.type().componentType().isBoolean()) {
+ // Converting boolean to int or float can be accomplished via bitwise-and.
+ if (c.type().componentType().isFloat()) {
+ fBuilder.push_literal_f(1.0f);
+ } else if (c.type().componentType().isSigned() || c.type().componentType().isUnsigned()) {
+ fBuilder.push_literal_i(1);
+ } else {
+ SkDEBUGFAILF("unexpected cast from bool to %s", c.type().description().c_str());
+ return unsupported();
+ }
+ fBuilder.push_duplicates(c.type().slotCount() - 1);
+ fBuilder.binary_op(BuilderOp::bitwise_and_n_ints, c.type().slotCount());
+ return true;
+ }
+ // We have dedicated ops to cast between float and integer types.
+ if (inner.type().componentType().isFloat()) {
+ if (c.type().componentType().isSigned()) {
+ fBuilder.unary_op(BuilderOp::cast_to_int_from_float, c.type().slotCount());
+ return true;
+ }
+ if (c.type().componentType().isUnsigned()) {
+ fBuilder.unary_op(BuilderOp::cast_to_uint_from_float, c.type().slotCount());
+ return true;
+ }
+ } else if (c.type().componentType().isFloat()) {
+ if (inner.type().componentType().isSigned()) {
+ fBuilder.unary_op(BuilderOp::cast_to_float_from_int, c.type().slotCount());
+ return true;
+ }
+ if (inner.type().componentType().isUnsigned()) {
+ fBuilder.unary_op(BuilderOp::cast_to_float_from_uint, c.type().slotCount());
+ return true;
+ }
+ }
+
+ SkDEBUGFAILF("unexpected cast from %s to %s",
+ c.type().description().c_str(), inner.type().description().c_str());
+ return unsupported();
+}
+
+bool Generator::pushConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c) {
+ fBuilder.push_zeros(1);
+ if (!this->pushExpression(*c.argument())) {
+ return unsupported();
+ }
+ fBuilder.diagonal_matrix(c.type().columns(), c.type().rows());
+
+ return true;
+}
+
+bool Generator::pushConstructorMatrixResize(const ConstructorMatrixResize& c) {
+ if (!this->pushExpression(*c.argument())) {
+ return unsupported();
+ }
+ fBuilder.matrix_resize(c.argument()->type().columns(),
+ c.argument()->type().rows(),
+ c.type().columns(),
+ c.type().rows());
+ return true;
+}
+
+bool Generator::pushConstructorSplat(const ConstructorSplat& c) {
+ if (!this->pushExpression(*c.argument())) {
+ return unsupported();
+ }
+ fBuilder.push_duplicates(c.type().slotCount() - 1);
+ return true;
+}
+
+bool Generator::pushFieldAccess(const FieldAccess& f) {
+ // If possible, get direct field access via the lvalue.
+ std::unique_ptr<LValue> lvalue = this->makeLValue(f, /*allowScratch=*/true);
+ return lvalue && this->push(*lvalue);
+}
+
+bool Generator::pushFunctionCall(const FunctionCall& c) {
+ if (c.function().isIntrinsic()) {
+ return this->pushIntrinsic(c);
+ }
+
+ // Keep track of the current function.
+ const FunctionDefinition* lastFunction = fCurrentFunction;
+ fCurrentFunction = c.function().definition();
+
+ // Skip over the function body entirely if there are no active lanes.
+ // (If the function call was trivial, it would likely have been inlined in the frontend, so this
+ // is likely to save a significant amount of work if the lanes are all dead.)
+ int skipLabelID = fBuilder.nextLabelID();
+ fBuilder.branch_if_no_lanes_active(skipLabelID);
+
+ // Save off the return mask.
+ if (this->needsReturnMask()) {
+ fBuilder.enableExecutionMaskWrites();
+ fBuilder.push_return_mask();
+ }
+
+ // Write all the arguments into their parameter's variable slots. Because we never allow
+ // recursion, we don't need to worry about overwriting any existing values in those slots.
+ // (In fact, we don't even need to apply the write mask.)
+ SkTArray<std::unique_ptr<LValue>> lvalues;
+ lvalues.resize(c.arguments().size());
+
+ for (int index = 0; index < c.arguments().size(); ++index) {
+ const Expression& arg = *c.arguments()[index];
+ const Variable& param = *c.function().parameters()[index];
+
+ // Use LValues for out-parameters and inout-parameters, so we can store back to them later.
+ if (IsInoutParameter(param) || IsOutParameter(param)) {
+ lvalues[index] = this->makeLValue(arg);
+ if (!lvalues[index]) {
+ return unsupported();
+ }
+ // There are no guarantees on the starting value of an out-parameter, so we only need to
+ // store the lvalues associated with an inout parameter.
+ if (IsInoutParameter(param)) {
+ if (!this->push(*lvalues[index])) {
+ return unsupported();
+ }
+ this->popToSlotRangeUnmasked(this->getVariableSlots(param));
+ }
+ } else {
+ // Copy input arguments into their respective parameter slots.
+ if (!this->pushExpression(arg)) {
+ return unsupported();
+ }
+ this->popToSlotRangeUnmasked(this->getVariableSlots(param));
+ }
+ }
+
+ // Emit the function body.
+ std::optional<SlotRange> r = this->writeFunction(c, *fCurrentFunction);
+ if (!r.has_value()) {
+ return unsupported();
+ }
+
+ // Restore the original return mask.
+ if (this->needsReturnMask()) {
+ fBuilder.pop_return_mask();
+ fBuilder.disableExecutionMaskWrites();
+ }
+
+ // If the function uses result slots, move its result from slots onto the stack.
+ if (this->needsFunctionResultSlots()) {
+ fBuilder.push_slots(*r);
+ }
+
+ // We've returned back to the last function.
+ fCurrentFunction = lastFunction;
+
+ // Copy out-parameters and inout-parameters back to their homes.
+ for (int index = 0; index < c.arguments().size(); ++index) {
+ if (lvalues[index]) {
+ // Only out- and inout-parameters should have an associated lvalue.
+ const Variable& param = *c.function().parameters()[index];
+ SkASSERT(IsInoutParameter(param) || IsOutParameter(param));
+
+ // Copy the parameter's slots directly into the lvalue.
+ fBuilder.push_slots(this->getVariableSlots(param));
+ if (!this->store(*lvalues[index])) {
+ return unsupported();
+ }
+ this->discardExpression(param.type().slotCount());
+ }
+ }
+
+ // Copy the function result from its slots onto the stack.
+ fBuilder.label(skipLabelID);
+ return true;
+}
+
+bool Generator::pushIndexExpression(const IndexExpression& i) {
+ std::unique_ptr<LValue> lvalue = this->makeLValue(i, /*allowScratch=*/true);
+ return lvalue && this->push(*lvalue);
+}
+
+bool Generator::pushIntrinsic(const FunctionCall& c) {
+ const ExpressionArray& args = c.arguments();
+ switch (args.size()) {
+ case 1:
+ return this->pushIntrinsic(c.function().intrinsicKind(), *args[0]);
+
+ case 2:
+ return this->pushIntrinsic(c.function().intrinsicKind(), *args[0], *args[1]);
+
+ case 3:
+ return this->pushIntrinsic(c.function().intrinsicKind(), *args[0], *args[1], *args[2]);
+
+ default:
+ break;
+ }
+
+ return unsupported();
+}
+
+bool Generator::pushLengthIntrinsic(int slotCount) {
+ if (slotCount > 1) {
+ // Implement `length(vec)` as `sqrt(dot(x, x))`.
+ fBuilder.push_clone(slotCount);
+ fBuilder.dot_floats(slotCount);
+ fBuilder.unary_op(BuilderOp::sqrt_float, 1);
+ } else {
+ // `length(scalar)` is `sqrt(x^2)`, which is equivalent to `abs(x)`.
+ fBuilder.unary_op(BuilderOp::abs_float, 1);
+ }
+ return true;
+}
+
+bool Generator::pushVectorizedExpression(const Expression& expr, const Type& vectorType) {
+ if (!this->pushExpression(expr)) {
+ return unsupported();
+ }
+ if (vectorType.slotCount() > expr.type().slotCount()) {
+ SkASSERT(expr.type().slotCount() == 1);
+ fBuilder.push_duplicates(vectorType.slotCount() - expr.type().slotCount());
+ }
+ return true;
+}
+
+bool Generator::pushIntrinsic(const TypedOps& ops, const Expression& arg0) {
+ if (!this->pushExpression(arg0)) {
+ return unsupported();
+ }
+ return this->unaryOp(arg0.type(), ops);
+}
+
+bool Generator::pushIntrinsic(BuilderOp builderOp, const Expression& arg0) {
+ if (!this->pushExpression(arg0)) {
+ return unsupported();
+ }
+ fBuilder.unary_op(builderOp, arg0.type().slotCount());
+ return true;
+}
+
+bool Generator::pushIntrinsic(IntrinsicKind intrinsic, const Expression& arg0) {
+ switch (intrinsic) {
+ case IntrinsicKind::k_abs_IntrinsicKind:
+ return this->pushIntrinsic(kAbsOps, arg0);
+
+ case IntrinsicKind::k_any_IntrinsicKind:
+ if (!this->pushExpression(arg0)) {
+ return unsupported();
+ }
+ this->foldWithMultiOp(BuilderOp::bitwise_or_n_ints, arg0.type().slotCount());
+ return true;
+
+ case IntrinsicKind::k_all_IntrinsicKind:
+ if (!this->pushExpression(arg0)) {
+ return unsupported();
+ }
+ this->foldWithMultiOp(BuilderOp::bitwise_and_n_ints, arg0.type().slotCount());
+ return true;
+
+ case IntrinsicKind::k_acos_IntrinsicKind:
+ return this->pushIntrinsic(BuilderOp::acos_float, arg0);
+
+ case IntrinsicKind::k_asin_IntrinsicKind:
+ return this->pushIntrinsic(BuilderOp::asin_float, arg0);
+
+ case IntrinsicKind::k_atan_IntrinsicKind:
+ return this->pushIntrinsic(BuilderOp::atan_float, arg0);
+
+ case IntrinsicKind::k_ceil_IntrinsicKind:
+ return this->pushIntrinsic(BuilderOp::ceil_float, arg0);
+
+ case IntrinsicKind::k_cos_IntrinsicKind:
+ return this->pushIntrinsic(BuilderOp::cos_float, arg0);
+
+ case IntrinsicKind::k_degrees_IntrinsicKind: {
+ Literal lit180OverPi{Position{}, 57.2957795131f, &arg0.type().componentType()};
+ return this->pushBinaryExpression(arg0, OperatorKind::STAR, lit180OverPi);
+ }
+ case IntrinsicKind::k_floatBitsToInt_IntrinsicKind:
+ case IntrinsicKind::k_floatBitsToUint_IntrinsicKind:
+ case IntrinsicKind::k_intBitsToFloat_IntrinsicKind:
+ case IntrinsicKind::k_uintBitsToFloat_IntrinsicKind:
+ return this->pushExpression(arg0);
+
+ case IntrinsicKind::k_exp_IntrinsicKind:
+ return this->pushIntrinsic(BuilderOp::exp_float, arg0);
+
+ case IntrinsicKind::k_exp2_IntrinsicKind:
+ return this->pushIntrinsic(BuilderOp::exp2_float, arg0);
+
+ case IntrinsicKind::k_floor_IntrinsicKind:
+ return this->pushIntrinsic(BuilderOp::floor_float, arg0);
+
+ case IntrinsicKind::k_fract_IntrinsicKind:
+ // Implement fract as `x - floor(x)`.
+ if (!this->pushExpression(arg0)) {
+ return unsupported();
+ }
+ fBuilder.push_clone(arg0.type().slotCount());
+ fBuilder.unary_op(BuilderOp::floor_float, arg0.type().slotCount());
+ return this->binaryOp(arg0.type(), kSubtractOps);
+
+ case IntrinsicKind::k_inverse_IntrinsicKind:
+ SkASSERT(arg0.type().isMatrix());
+ SkASSERT(arg0.type().rows() == arg0.type().columns());
+ if (!this->pushExpression(arg0)) {
+ return unsupported();
+ }
+ fBuilder.inverse_matrix(arg0.type().rows());
+ return true;
+
+ case IntrinsicKind::k_inversesqrt_IntrinsicKind:
+ return this->pushIntrinsic(kInverseSqrtOps, arg0);
+
+ case IntrinsicKind::k_length_IntrinsicKind:
+ return this->pushExpression(arg0) &&
+ this->pushLengthIntrinsic(arg0.type().slotCount());
+
+ case IntrinsicKind::k_log_IntrinsicKind:
+ if (!this->pushExpression(arg0)) {
+ return unsupported();
+ }
+ fBuilder.unary_op(BuilderOp::log_float, arg0.type().slotCount());
+ return true;
+
+ case IntrinsicKind::k_log2_IntrinsicKind:
+ if (!this->pushExpression(arg0)) {
+ return unsupported();
+ }
+ fBuilder.unary_op(BuilderOp::log2_float, arg0.type().slotCount());
+ return true;
+
+ case IntrinsicKind::k_normalize_IntrinsicKind: {
+ // Implement normalize as `x / length(x)`. First, push the expression.
+ if (!this->pushExpression(arg0)) {
+ return unsupported();
+ }
+ int slotCount = arg0.type().slotCount();
+ if (slotCount > 1) {
+ // Instead of `x / sqrt(dot(x, x))`, we can get roughly the same result in less time
+ // by computing `x * invsqrt(dot(x, x))`.
+ fBuilder.push_clone(slotCount);
+ fBuilder.push_clone(slotCount);
+ fBuilder.dot_floats(slotCount);
+
+ // Compute `vec(inversesqrt(dot(x, x)))`.
+ fBuilder.unary_op(BuilderOp::invsqrt_float, 1);
+ fBuilder.push_duplicates(slotCount - 1);
+
+ // Return `x * vec(inversesqrt(dot(x, x)))`.
+ return this->binaryOp(arg0.type(), kMultiplyOps);
+ } else {
+ // For single-slot normalization, we can simplify `sqrt(x * x)` into `abs(x)`.
+ fBuilder.push_clone(slotCount);
+ fBuilder.unary_op(BuilderOp::abs_float, 1);
+ return this->binaryOp(arg0.type(), kDivideOps);
+ }
+ }
+ case IntrinsicKind::k_not_IntrinsicKind:
+ return this->pushPrefixExpression(OperatorKind::LOGICALNOT, arg0);
+
+ case IntrinsicKind::k_radians_IntrinsicKind: {
+ Literal litPiOver180{Position{}, 0.01745329251f, &arg0.type().componentType()};
+ return this->pushBinaryExpression(arg0, OperatorKind::STAR, litPiOver180);
+ }
+ case IntrinsicKind::k_saturate_IntrinsicKind: {
+ // Implement saturate as clamp(arg, 0, 1).
+ Literal zeroLiteral{Position{}, 0.0, &arg0.type().componentType()};
+ Literal oneLiteral{Position{}, 1.0, &arg0.type().componentType()};
+ return this->pushIntrinsic(k_clamp_IntrinsicKind, arg0, zeroLiteral, oneLiteral);
+ }
+ case IntrinsicKind::k_sign_IntrinsicKind: {
+ // Implement floating-point sign() as `clamp(arg * FLT_MAX, -1, 1)`.
+ // FLT_MIN * FLT_MAX evaluates to 4, so multiplying any float value against FLT_MAX is
+ // sufficient to ensure that |value| is always 1 or greater (excluding zero and nan).
+ // Integer sign() doesn't need to worry about fractional values or nans, and can simply
+ // be `clamp(arg, -1, 1)`.
+ if (!this->pushExpression(arg0)) {
+ return unsupported();
+ }
+ if (arg0.type().componentType().isFloat()) {
+ Literal fltMaxLiteral{Position{}, FLT_MAX, &arg0.type().componentType()};
+ if (!this->pushVectorizedExpression(fltMaxLiteral, arg0.type())) {
+ return unsupported();
+ }
+ if (!this->binaryOp(arg0.type(), kMultiplyOps)) {
+ return unsupported();
+ }
+ }
+ Literal neg1Literal{Position{}, -1.0, &arg0.type().componentType()};
+ if (!this->pushVectorizedExpression(neg1Literal, arg0.type())) {
+ return unsupported();
+ }
+ if (!this->binaryOp(arg0.type(), kMaxOps)) {
+ return unsupported();
+ }
+ Literal pos1Literal{Position{}, 1.0, &arg0.type().componentType()};
+ if (!this->pushVectorizedExpression(pos1Literal, arg0.type())) {
+ return unsupported();
+ }
+ return this->binaryOp(arg0.type(), kMinOps);
+ }
+ case IntrinsicKind::k_sin_IntrinsicKind:
+ return this->pushIntrinsic(BuilderOp::sin_float, arg0);
+
+ case IntrinsicKind::k_sqrt_IntrinsicKind:
+ return this->pushIntrinsic(BuilderOp::sqrt_float, arg0);
+
+ case IntrinsicKind::k_tan_IntrinsicKind:
+ return this->pushIntrinsic(BuilderOp::tan_float, arg0);
+
+ case IntrinsicKind::k_transpose_IntrinsicKind:
+ SkASSERT(arg0.type().isMatrix());
+ if (!this->pushExpression(arg0)) {
+ return unsupported();
+ }
+ fBuilder.transpose(arg0.type().columns(), arg0.type().rows());
+ return true;
+
+ case IntrinsicKind::k_trunc_IntrinsicKind:
+ // Implement trunc as `float(int(x))`, since float-to-int rounds toward zero.
+ if (!this->pushExpression(arg0)) {
+ return unsupported();
+ }
+ fBuilder.unary_op(BuilderOp::cast_to_int_from_float, arg0.type().slotCount());
+ fBuilder.unary_op(BuilderOp::cast_to_float_from_int, arg0.type().slotCount());
+ return true;
+
+ case IntrinsicKind::k_fromLinearSrgb_IntrinsicKind:
+ case IntrinsicKind::k_toLinearSrgb_IntrinsicKind: {
+ // The argument must be a half3.
+ SkASSERT(arg0.type().matches(*fContext.fTypes.fHalf3));
+ if (!this->pushExpression(arg0)) {
+ return unsupported();
+ }
+ // The intrinsics accept a three-component value; add alpha for the push/pop_src_rgba
+ fBuilder.push_literal_f(1.0f);
+ // Copy arguments from the stack into src
+ fBuilder.pop_src_rgba();
+
+ if (intrinsic == IntrinsicKind::k_fromLinearSrgb_IntrinsicKind) {
+ fBuilder.invoke_from_linear_srgb();
+ } else {
+ fBuilder.invoke_to_linear_srgb();
+ }
+
+ // The xform has left the result color in src.rgba; push it onto the stack
+ fBuilder.push_src_rgba();
+ // The intrinsic returns a three-component value; discard alpha
+ this->discardExpression(/*slots=*/1);
+ return true;
+ }
+
+ default:
+ break;
+ }
+ return unsupported();
+}
+
+bool Generator::pushIntrinsic(const TypedOps& ops, const Expression& arg0, const Expression& arg1) {
+ if (!this->pushExpression(arg0) || !this->pushVectorizedExpression(arg1, arg0.type())) {
+ return unsupported();
+ }
+ return this->binaryOp(arg0.type(), ops);
+}
+
+bool Generator::pushIntrinsic(BuilderOp builderOp, const Expression& arg0, const Expression& arg1) {
+ if (!this->pushExpression(arg0) || !this->pushVectorizedExpression(arg1, arg0.type())) {
+ return unsupported();
+ }
+ fBuilder.binary_op(builderOp, arg0.type().slotCount());
+ return true;
+}
+
+bool Generator::pushIntrinsic(IntrinsicKind intrinsic,
+ const Expression& arg0,
+ const Expression& arg1) {
+ switch (intrinsic) {
+ case IntrinsicKind::k_atan_IntrinsicKind:
+ return this->pushIntrinsic(BuilderOp::atan2_n_floats, arg0, arg1);
+
+ case IntrinsicKind::k_cross_IntrinsicKind: {
+ // Implement cross as `arg0.yzx * arg1.zxy - arg0.zxy * arg1.yzx`. We use two stacks so
+ // that each subexpression can be multiplied separately.
+ SkASSERT(arg0.type().matches(arg1.type()));
+ SkASSERT(arg0.type().slotCount() == 3);
+ SkASSERT(arg1.type().slotCount() == 3);
+
+ // Push `arg0.yzx` onto this stack and `arg0.zxy` onto a separate subexpression stack.
+ AutoStack subexpressionStack(this);
+ subexpressionStack.enter();
+ if (!this->pushExpression(arg0)) {
+ return unsupported();
+ }
+ subexpressionStack.exit();
+ subexpressionStack.pushClone(/*slots=*/3);
+
+ fBuilder.swizzle(/*consumedSlots=*/3, {1, 2, 0});
+ subexpressionStack.enter();
+ fBuilder.swizzle(/*consumedSlots=*/3, {2, 0, 1});
+ subexpressionStack.exit();
+
+ // Push `arg1.zxy` onto this stack and `arg1.yzx` onto the next stack. Perform the
+ // multiply on each subexpression (`arg0.yzx * arg1.zxy` on the first stack, and
+ // `arg0.zxy * arg1.yzx` on the next).
+ subexpressionStack.enter();
+ if (!this->pushExpression(arg1)) {
+ return unsupported();
+ }
+ subexpressionStack.exit();
+ subexpressionStack.pushClone(/*slots=*/3);
+
+ fBuilder.swizzle(/*consumedSlots=*/3, {2, 0, 1});
+ fBuilder.binary_op(BuilderOp::mul_n_floats, 3);
+
+ subexpressionStack.enter();
+ fBuilder.swizzle(/*consumedSlots=*/3, {1, 2, 0});
+ fBuilder.binary_op(BuilderOp::mul_n_floats, 3);
+ subexpressionStack.exit();
+
+ // Migrate the result of the second subexpression (`arg0.zxy * arg1.yzx`) back onto the
+ // main stack and subtract it from the first subexpression (`arg0.yzx * arg1.zxy`).
+ subexpressionStack.pushClone(/*slots=*/3);
+ fBuilder.binary_op(BuilderOp::sub_n_floats, 3);
+
+ // Now that the calculation is complete, discard the subexpression on the next stack.
+ subexpressionStack.enter();
+ this->discardExpression(/*slots=*/3);
+ subexpressionStack.exit();
+ return true;
+ }
+ case IntrinsicKind::k_distance_IntrinsicKind:
+ // Implement distance as `length(a - b)`.
+ SkASSERT(arg0.type().slotCount() == arg1.type().slotCount());
+ return this->pushBinaryExpression(arg0, OperatorKind::MINUS, arg1) &&
+ this->pushLengthIntrinsic(arg0.type().slotCount());
+
+ case IntrinsicKind::k_dot_IntrinsicKind:
+ SkASSERT(arg0.type().matches(arg1.type()));
+ if (!this->pushExpression(arg0) || !this->pushExpression(arg1)) {
+ return unsupported();
+ }
+ fBuilder.dot_floats(arg0.type().slotCount());
+ return true;
+
+ case IntrinsicKind::k_equal_IntrinsicKind:
+ SkASSERT(arg0.type().matches(arg1.type()));
+ return this->pushIntrinsic(kEqualOps, arg0, arg1);
+
+ case IntrinsicKind::k_notEqual_IntrinsicKind:
+ SkASSERT(arg0.type().matches(arg1.type()));
+ return this->pushIntrinsic(kNotEqualOps, arg0, arg1);
+
+ case IntrinsicKind::k_lessThan_IntrinsicKind:
+ SkASSERT(arg0.type().matches(arg1.type()));
+ return this->pushIntrinsic(kLessThanOps, arg0, arg1);
+
+ case IntrinsicKind::k_greaterThan_IntrinsicKind:
+ SkASSERT(arg0.type().matches(arg1.type()));
+ return this->pushIntrinsic(kLessThanOps, arg1, arg0);
+
+ case IntrinsicKind::k_lessThanEqual_IntrinsicKind:
+ SkASSERT(arg0.type().matches(arg1.type()));
+ return this->pushIntrinsic(kLessThanEqualOps, arg0, arg1);
+
+ case IntrinsicKind::k_greaterThanEqual_IntrinsicKind:
+ SkASSERT(arg0.type().matches(arg1.type()));
+ return this->pushIntrinsic(kLessThanEqualOps, arg1, arg0);
+
+ case IntrinsicKind::k_min_IntrinsicKind:
+ SkASSERT(arg0.type().componentType().matches(arg1.type().componentType()));
+ return this->pushIntrinsic(kMinOps, arg0, arg1);
+
+ case IntrinsicKind::k_matrixCompMult_IntrinsicKind:
+ SkASSERT(arg0.type().matches(arg1.type()));
+ return this->pushIntrinsic(kMultiplyOps, arg0, arg1);
+
+ case IntrinsicKind::k_max_IntrinsicKind:
+ SkASSERT(arg0.type().componentType().matches(arg1.type().componentType()));
+ return this->pushIntrinsic(kMaxOps, arg0, arg1);
+
+ case IntrinsicKind::k_mod_IntrinsicKind:
+ SkASSERT(arg0.type().componentType().matches(arg1.type().componentType()));
+ return this->pushIntrinsic(kModOps, arg0, arg1);
+
+ case IntrinsicKind::k_pow_IntrinsicKind:
+ SkASSERT(arg0.type().matches(arg1.type()));
+ return this->pushIntrinsic(BuilderOp::pow_n_floats, arg0, arg1);
+
+ case IntrinsicKind::k_reflect_IntrinsicKind: {
+ // Implement reflect as `I - (N * dot(I,N) * 2)`.
+ SkASSERT(arg0.type().matches(arg1.type()));
+ SkASSERT(arg0.type().slotCount() == arg1.type().slotCount());
+ SkASSERT(arg0.type().componentType().isFloat());
+ int slotCount = arg0.type().slotCount();
+
+ // Stack: I, N.
+ if (!this->pushExpression(arg0) || !this->pushExpression(arg1)) {
+ return unsupported();
+ }
+ // Stack: I, N, I, N.
+ fBuilder.push_clone(2 * slotCount);
+ // Stack: I, N, dot(I,N)
+ fBuilder.dot_floats(slotCount);
+ // Stack: I, N, dot(I,N), 2
+ fBuilder.push_literal_f(2.0);
+ // Stack: I, N, dot(I,N) * 2
+ fBuilder.binary_op(BuilderOp::mul_n_floats, 1);
+ // Stack: I, N * dot(I,N) * 2
+ fBuilder.push_duplicates(slotCount - 1);
+ fBuilder.binary_op(BuilderOp::mul_n_floats, slotCount);
+ // Stack: I - (N * dot(I,N) * 2)
+ fBuilder.binary_op(BuilderOp::sub_n_floats, slotCount);
+ return true;
+ }
+ case IntrinsicKind::k_step_IntrinsicKind: {
+ // Compute step as `float(lessThan(edge, x))`. We convert from boolean 0/~0 to floating
+ // point zero/one by using a bitwise-and against the bit-pattern of 1.0.
+ SkASSERT(arg0.type().componentType().matches(arg1.type().componentType()));
+ if (!this->pushVectorizedExpression(arg0, arg1.type()) || !this->pushExpression(arg1)) {
+ return unsupported();
+ }
+ if (!this->binaryOp(arg1.type(), kLessThanOps)) {
+ return unsupported();
+ }
+ Literal pos1Literal{Position{}, 1.0, &arg1.type().componentType()};
+ if (!this->pushVectorizedExpression(pos1Literal, arg1.type())) {
+ return unsupported();
+ }
+ fBuilder.binary_op(BuilderOp::bitwise_and_n_ints, arg1.type().slotCount());
+ return true;
+ }
+
+ default:
+ break;
+ }
+ return unsupported();
+}
+
+bool Generator::pushIntrinsic(IntrinsicKind intrinsic,
+ const Expression& arg0,
+ const Expression& arg1,
+ const Expression& arg2) {
+ switch (intrinsic) {
+ case IntrinsicKind::k_clamp_IntrinsicKind:
+ // Implement clamp as min(max(arg, low), high).
+ SkASSERT(arg0.type().componentType().matches(arg1.type().componentType()));
+ SkASSERT(arg0.type().componentType().matches(arg2.type().componentType()));
+ if (!this->pushExpression(arg0) || !this->pushVectorizedExpression(arg1, arg0.type())) {
+ return unsupported();
+ }
+ if (!this->binaryOp(arg0.type(), kMaxOps)) {
+ return unsupported();
+ }
+ if (!this->pushVectorizedExpression(arg2, arg0.type())) {
+ return unsupported();
+ }
+ if (!this->binaryOp(arg0.type(), kMinOps)) {
+ return unsupported();
+ }
+ return true;
+
+ case IntrinsicKind::k_faceforward_IntrinsicKind: {
+ // Implement faceforward as `N ^ ((0 <= dot(I, NRef)) & 0x80000000)`.
+ // In other words, flip the sign bit of N if `0 <= dot(I, NRef)`.
+ SkASSERT(arg0.type().matches(arg1.type()));
+ SkASSERT(arg0.type().matches(arg2.type()));
+ int slotCount = arg0.type().slotCount();
+
+ // Stack: N, 0, I, Nref
+ if (!this->pushExpression(arg0)) {
+ return unsupported();
+ }
+ fBuilder.push_literal_f(0.0);
+ if (!this->pushExpression(arg1) || !this->pushExpression(arg2)) {
+ return unsupported();
+ }
+ // Stack: N, 0, dot(I,NRef)
+ fBuilder.dot_floats(slotCount);
+ // Stack: N, (0 <= dot(I,NRef))
+ fBuilder.binary_op(BuilderOp::cmple_n_floats, 1);
+ // Stack: N, (0 <= dot(I,NRef)), 0x80000000
+ fBuilder.push_literal_i(0x80000000);
+ // Stack: N, (0 <= dot(I,NRef)) & 0x80000000)
+ fBuilder.binary_op(BuilderOp::bitwise_and_n_ints, 1);
+ // Stack: N, vec(0 <= dot(I,NRef)) & 0x80000000)
+ fBuilder.push_duplicates(slotCount - 1);
+ // Stack: N ^ vec((0 <= dot(I,NRef)) & 0x80000000)
+ fBuilder.binary_op(BuilderOp::bitwise_xor_n_ints, slotCount);
+ return true;
+ }
+ case IntrinsicKind::k_mix_IntrinsicKind:
+ // Note: our SkRP mix op takes the interpolation point first, not the interpolants.
+ SkASSERT(arg0.type().matches(arg1.type()));
+ if (arg2.type().componentType().isFloat()) {
+ SkASSERT(arg0.type().componentType().matches(arg2.type().componentType()));
+ if (!this->pushVectorizedExpression(arg2, arg0.type())) {
+ return unsupported();
+ }
+ if (!this->pushExpression(arg0) || !this->pushExpression(arg1)) {
+ return unsupported();
+ }
+ return this->ternaryOp(arg0.type(), kMixOps);
+ }
+ if (arg2.type().componentType().isBoolean()) {
+ if (!this->pushExpression(arg2)) {
+ return unsupported();
+ }
+ if (!this->pushExpression(arg0) || !this->pushExpression(arg1)) {
+ return unsupported();
+ }
+ // The `mix_int` op isn't doing a lerp; it uses the third argument to select values
+ // from the first and second arguments. It's safe for use with any type in arguments
+ // 0 and 1.
+ fBuilder.ternary_op(BuilderOp::mix_n_ints, arg0.type().slotCount());
+ return true;
+ }
+ return unsupported();
+
+ case IntrinsicKind::k_refract_IntrinsicKind: {
+ // We always calculate refraction using vec4s, so we pad out unused N/I slots with zero.
+ int padding = 4 - arg0.type().slotCount();
+ if (!this->pushExpression(arg0)) {
+ return unsupported();
+ }
+ fBuilder.push_zeros(padding);
+
+ if (!this->pushExpression(arg1)) {
+ return unsupported();
+ }
+ fBuilder.push_zeros(padding);
+
+ // eta is always a scalar and doesn't need padding.
+ if (!this->pushExpression(arg2)) {
+ return unsupported();
+ }
+ fBuilder.refract_floats();
+
+ // The result vector was returned as a vec4, so discard the extra columns.
+ fBuilder.discard_stack(padding);
+ return true;
+ }
+ case IntrinsicKind::k_smoothstep_IntrinsicKind:
+ SkASSERT(arg0.type().componentType().isFloat());
+ SkASSERT(arg1.type().matches(arg0.type()));
+ SkASSERT(arg2.type().componentType().isFloat());
+
+ if (!this->pushVectorizedExpression(arg0, arg2.type()) ||
+ !this->pushVectorizedExpression(arg1, arg2.type()) ||
+ !this->pushExpression(arg2)) {
+ return unsupported();
+ }
+ fBuilder.ternary_op(BuilderOp::smoothstep_n_floats, arg2.type().slotCount());
+ return true;
+
+ default:
+ break;
+ }
+ return unsupported();
+}
+
+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::pushPostfixExpression(const PostfixExpression& p, bool usesResult) {
+ // If the result is ignored...
+ if (!usesResult) {
+ // ... just emit a prefix expression instead.
+ return this->pushPrefixExpression(p.getOperator(), *p.operand());
+ }
+ // Get the operand as an lvalue, and push it onto the stack as-is.
+ std::unique_ptr<LValue> lvalue = this->makeLValue(*p.operand());
+ if (!lvalue || !this->push(*lvalue)) {
+ return unsupported();
+ }
+
+ // Push a scratch copy of the operand.
+ fBuilder.push_clone(p.type().slotCount());
+
+ // Increment or decrement the scratch copy by one.
+ Literal oneLiteral{Position{}, 1.0, &p.type().componentType()};
+ if (!this->pushVectorizedExpression(oneLiteral, p.type())) {
+ return unsupported();
+ }
+
+ switch (p.getOperator().kind()) {
+ case OperatorKind::PLUSPLUS:
+ if (!this->binaryOp(p.type(), kAddOps)) {
+ return unsupported();
+ }
+ break;
+
+ case OperatorKind::MINUSMINUS:
+ if (!this->binaryOp(p.type(), kSubtractOps)) {
+ return unsupported();
+ }
+ break;
+
+ default:
+ SkUNREACHABLE;
+ }
+
+ // Write the new value back to the operand.
+ if (!this->store(*lvalue)) {
+ return unsupported();
+ }
+
+ // Discard the scratch copy, leaving only the original value as-is.
+ this->discardExpression(p.type().slotCount());
+ return true;
+}
+
+bool Generator::pushPrefixExpression(const PrefixExpression& p) {
+ return this->pushPrefixExpression(p.getOperator(), *p.operand());
+}
+
+bool Generator::pushPrefixExpression(Operator op, const Expression& expr) {
+ switch (op.kind()) {
+ case OperatorKind::BITWISENOT:
+ case OperatorKind::LOGICALNOT:
+ // Handle operators ! and ~.
+ if (!this->pushExpression(expr)) {
+ return unsupported();
+ }
+ fBuilder.unary_op(BuilderOp::bitwise_not_int, expr.type().slotCount());
+ return true;
+
+ case OperatorKind::MINUS:
+ // Handle negation as a componentwise `0 - expr`.
+ fBuilder.push_zeros(expr.type().slotCount());
+ if (!this->pushExpression(expr)) {
+ return unsupported();
+ }
+ return this->binaryOp(expr.type(), kSubtractOps);
+
+ case OperatorKind::PLUSPLUS: {
+ // Rewrite as `expr += 1`.
+ Literal oneLiteral{Position{}, 1.0, &expr.type().componentType()};
+ return this->pushBinaryExpression(expr, OperatorKind::PLUSEQ, oneLiteral);
+ }
+ case OperatorKind::MINUSMINUS: {
+ // Rewrite as `expr -= 1`.
+ Literal oneLiteral{Position{}, 1.0, &expr.type().componentType()};
+ return this->pushBinaryExpression(expr, OperatorKind::MINUSEQ, oneLiteral);
+ }
+ default:
+ break;
+ }
+
+ return unsupported();
+}
+
+bool Generator::pushSwizzle(const Swizzle& s) {
+ SkASSERT(!s.components().empty() && s.components().size() <= 4);
+
+ // If this is a simple subset of a variable's slots...
+ bool isSimpleSubset = is_sliceable_swizzle(s.components());
+ if (isSimpleSubset && s.base()->is<VariableReference>()) {
+ // ... we can just push part of the variable directly onto the stack, rather than pushing
+ // the whole expression and then immediately cutting it down. (Either way works, but this
+ // saves a step.)
+ return this->pushVariableReferencePartial(
+ s.base()->as<VariableReference>(),
+ SlotRange{/*index=*/s.components()[0], /*count=*/s.components().size()});
+ }
+ // Push the base expression.
+ if (!this->pushExpression(*s.base())) {
+ return false;
+ }
+ // An identity swizzle doesn't rearrange the data; it just (potentially) discards tail elements.
+ if (isSimpleSubset && s.components()[0] == 0) {
+ int discardedElements = s.base()->type().slotCount() - s.components().size();
+ SkASSERT(discardedElements >= 0);
+ fBuilder.discard_stack(discardedElements);
+ return true;
+ }
+ // Perform the swizzle.
+ fBuilder.swizzle(s.base()->type().slotCount(), s.components());
+ return true;
+}
+
+bool Generator::pushTernaryExpression(const TernaryExpression& t) {
+ return this->pushTernaryExpression(*t.test(), *t.ifTrue(), *t.ifFalse());
+}
+
+bool Generator::pushDynamicallyUniformTernaryExpression(const Expression& test,
+ const Expression& ifTrue,
+ const Expression& ifFalse) {
+ SkASSERT(Analysis::IsDynamicallyUniformExpression(test));
+
+ int falseLabelID = fBuilder.nextLabelID();
+ int exitLabelID = fBuilder.nextLabelID();
+
+ // First, push the test-expression into a separate stack.
+ AutoStack testStack(this);
+ testStack.enter();
+ if (!this->pushExpression(test)) {
+ return unsupported();
+ }
+
+ // Branch to the true- or false-expression based on the test-expression. We can skip the
+ // non-true path entirely since the test is known to be uniform.
+ fBuilder.branch_if_no_active_lanes_on_stack_top_equal(~0, falseLabelID);
+ testStack.exit();
+
+ if (!this->pushExpression(ifTrue)) {
+ return unsupported();
+ }
+
+ fBuilder.jump(exitLabelID);
+
+ // The builder doesn't understand control flow, and assumes that every push moves the stack-top
+ // forwards. We need to manually balance out the `pushExpression` from the if-true path by
+ // moving the stack position backwards, so that the if-false path pushes its expression into the
+ // same as the if-true result.
+ this->discardExpression(/*slots=*/ifTrue.type().slotCount());
+
+ fBuilder.label(falseLabelID);
+
+ if (!this->pushExpression(ifFalse)) {
+ return unsupported();
+ }
+
+ fBuilder.label(exitLabelID);
+
+ // Jettison the text-expression from the separate stack.
+ testStack.enter();
+ this->discardExpression(/*slots=*/1);
+ testStack.exit();
+ return true;
+}
+
+bool Generator::pushTernaryExpression(const Expression& test,
+ const Expression& ifTrue,
+ const Expression& ifFalse) {
+ // If the test-expression is dynamically-uniform, we can skip over the non-true expressions
+ // entirely, and not need to involve the condition mask.
+ if (Analysis::IsDynamicallyUniformExpression(test)) {
+ return this->pushDynamicallyUniformTernaryExpression(test, ifTrue, ifFalse);
+ }
+
+ // Analyze the ternary to see which corners we can safely cut.
+ bool ifFalseHasSideEffects = Analysis::HasSideEffects(ifFalse);
+ bool ifTrueHasSideEffects = Analysis::HasSideEffects(ifTrue);
+ bool ifTrueIsTrivial = Analysis::IsTrivialExpression(ifTrue);
+ int cleanupLabelID = fBuilder.nextLabelID();
+
+ // If the true- and false-expressions both lack side effects, we evaluate both of them safely
+ // without masking off their effects. In that case, we can emit both sides and use boolean mix
+ // to select the correct result without using the condition mask at all.
+ if (!ifFalseHasSideEffects && !ifTrueHasSideEffects && ifTrueIsTrivial) {
+ // Push all of the arguments to mix.
+ if (!this->pushVectorizedExpression(test, ifTrue.type())) {
+ return unsupported();
+ }
+ if (!this->pushExpression(ifFalse)) {
+ return unsupported();
+ }
+ if (!this->pushExpression(ifTrue)) {
+ return unsupported();
+ }
+ // Use boolean mix to select the true- or false-expression via the test-expression.
+ fBuilder.ternary_op(BuilderOp::mix_n_ints, ifTrue.type().slotCount());
+ return true;
+ }
+
+ // First, push the current condition-mask and the test-expression into a separate stack.
+ fBuilder.enableExecutionMaskWrites();
+ AutoStack testStack(this);
+ testStack.enter();
+ fBuilder.push_condition_mask();
+ if (!this->pushExpression(test)) {
+ return unsupported();
+ }
+ testStack.exit();
+
+ // We can take some shortcuts with condition-mask handling if the false-expression is entirely
+ // side-effect free. (We can evaluate it without masking off its effects.) We always handle the
+ // condition mask properly for the test-expression and true-expression properly.
+ if (!ifFalseHasSideEffects) {
+ // Push the false-expression onto the primary stack.
+ if (!this->pushExpression(ifFalse)) {
+ return unsupported();
+ }
+
+ // Next, merge the condition mask (on the separate stack) with the test expression.
+ testStack.enter();
+ fBuilder.merge_condition_mask();
+ testStack.exit();
+
+ // If no lanes are active, we can skip the true-expression entirely. This isn't super likely
+ // to happen, so it's probably only a win for non-trivial true-expressions.
+ if (!ifTrueIsTrivial) {
+ fBuilder.branch_if_no_lanes_active(cleanupLabelID);
+ }
+
+ // Push the true-expression onto the primary stack, immediately after the false-expression.
+ if (!this->pushExpression(ifTrue)) {
+ return unsupported();
+ }
+
+ // Use a select to conditionally mask-merge the true-expression and false-expression lanes.
+ fBuilder.select(/*slots=*/ifTrue.type().slotCount());
+ fBuilder.label(cleanupLabelID);
+ } else {
+ // Merge the condition mask (on the separate stack) with the test expression.
+ testStack.enter();
+ fBuilder.merge_condition_mask();
+ testStack.exit();
+
+ // Push the true-expression onto the primary stack.
+ if (!this->pushExpression(ifTrue)) {
+ return unsupported();
+ }
+
+ // Switch back to the test-expression stack temporarily, and negate the test condition.
+ testStack.enter();
+ fBuilder.unary_op(BuilderOp::bitwise_not_int, /*slots=*/1);
+ fBuilder.merge_condition_mask();
+ testStack.exit();
+
+ // Push the false-expression onto the primary stack, immediately after the true-expression.
+ if (!this->pushExpression(ifFalse)) {
+ return unsupported();
+ }
+
+ // Use a select to conditionally mask-merge the true-expression and false-expression lanes;
+ // the mask is already set up for this.
+ fBuilder.select(/*slots=*/ifTrue.type().slotCount());
+ }
+
+ // Restore the condition-mask to its original state and jettison the test-expression.
+ testStack.enter();
+ this->discardExpression(/*slots=*/1);
+ fBuilder.pop_condition_mask();
+ testStack.exit();
+
+ fBuilder.disableExecutionMaskWrites();
+ return true;
+}
+
+bool Generator::pushVariableReference(const VariableReference& v) {
+ return this->pushVariableReferencePartial(v, SlotRange{0, (int)v.type().slotCount()});
+}
+
+bool Generator::pushVariableReferencePartial(const VariableReference& v, SlotRange subset) {
+ const Variable& var = *v.variable();
+ SlotRange r;
+ if (IsUniform(var)) {
+ r = this->getUniformSlots(var);
+ SkASSERT(r.count == (int)var.type().slotCount());
+ r.index += subset.index;
+ r.count = subset.count;
+ fBuilder.push_uniform(r);
+ } else {
+ r = this->getVariableSlots(var);
+ SkASSERT(r.count == (int)var.type().slotCount());
+ r.index += subset.index;
+ r.count = subset.count;
+ fBuilder.push_slots(r);
+ }
+ return true;
+}
+
+bool Generator::writeProgram(const FunctionDefinition& function) {
+ fCurrentFunction = &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.
+ 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->getVariableSlots(*param);
+ SkASSERT(fragCoord.count == 2);
+ fBuilder.store_src_rg(fragCoord);
+ break;
+ }
+ case SK_INPUT_COLOR_BUILTIN: {
+ // Input colors are passed via RGBA.
+ SlotRange srcColor = this->getVariableSlots(*param);
+ SkASSERT(srcColor.count == 4);
+ fBuilder.store_src(srcColor);
+ break;
+ }
+ case SK_DEST_COLOR_BUILTIN: {
+ // Dest colors are passed via dRGBA.
+ SlotRange destColor = this->getVariableSlots(*param);
+ SkASSERT(destColor.count == 4);
+ fBuilder.store_dst(destColor);
+ break;
+ }
+ default: {
+ SkDEBUGFAIL("Invalid parameter to main()");
+ return unsupported();
+ }
+ }
+ }
+
+ // Initialize the program.
+ fBuilder.init_lane_masks();
+
+ // Emit global variables.
+ if (!this->writeGlobals()) {
+ return unsupported();
+ }
+
+ // Invoke main().
+ if (this->needsReturnMask()) {
+ fBuilder.enableExecutionMaskWrites();
+ }
+
+ std::optional<SlotRange> mainResult = this->writeFunction(function, function);
+ if (!mainResult.has_value()) {
+ return unsupported();
+ }
+
+ if (this->needsReturnMask()) {
+ fBuilder.disableExecutionMaskWrites();
+ }
+
+ // Move the result of main() from slots into RGBA. Allow dRGBA to remain in a trashed state.
+ SkASSERT(mainResult->count == 4);
+ if (this->needsFunctionResultSlots()) {
+ fBuilder.load_src(*mainResult);
+ } else {
+ fBuilder.pop_src_rgba();
+ }
+ return true;
+}
+
+std::unique_ptr<RP::Program> Generator::finish() {
+ return fBuilder.finish(fProgramSlots.slotCount(), fUniformSlots.slotCount(), fDebugTrace);
+}
+
+} // namespace RP
+
+std::unique_ptr<RP::Program> MakeRasterPipelineProgram(const SkSL::Program& program,
+ const FunctionDefinition& function,
+ SkRPDebugTrace* debugTrace) {
+ RP::Generator generator(program, debugTrace);
+ if (!generator.writeProgram(function)) {
+ return nullptr;
+ }
+ return generator.finish();
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineCodeGenerator.h b/gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineCodeGenerator.h
new file mode 100644
index 0000000000..c49a8d571d
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineCodeGenerator.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_RASTERPIPELINECODEGENERATOR
+#define SKSL_RASTERPIPELINECODEGENERATOR
+
+#include "include/core/SkTypes.h"
+#include <memory>
+
+namespace SkSL {
+
+class FunctionDefinition;
+struct Program;
+class SkRPDebugTrace;
+namespace RP { class Program; }
+
+// Convert 'function' to Raster Pipeline stages, for use by blends, shaders, and color filters.
+// The arguments to the function are passed in registers:
+// -- coordinates in src.rg for shaders
+// -- color in src.rgba for color filters
+// -- src/dst in src.rgba and dst.rgba for blenders
+std::unique_ptr<RP::Program> MakeRasterPipelineProgram(const Program& program,
+ const FunctionDefinition& function,
+ SkRPDebugTrace* debugTrace = nullptr);
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLSPIRVCodeGenerator.cpp b/gfx/skia/skia/src/sksl/codegen/SkSLSPIRVCodeGenerator.cpp
new file mode 100644
index 0000000000..f355a64a83
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/codegen/SkSLSPIRVCodeGenerator.cpp
@@ -0,0 +1,4365 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/codegen/SkSLSPIRVCodeGenerator.h"
+
+#include "include/core/SkSpan.h"
+#include "include/core/SkTypes.h"
+#include "include/private/SkOpts_spi.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLProgramElement.h"
+#include "include/private/SkSLStatement.h"
+#include "include/private/SkSLSymbol.h"
+#include "include/private/base/SkTArray.h"
+#include "include/sksl/DSLCore.h"
+#include "include/sksl/DSLExpression.h"
+#include "include/sksl/DSLType.h"
+#include "include/sksl/DSLVar.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "include/sksl/SkSLOperator.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/GLSL.std.450.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLConstantFolder.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLIntrinsicList.h"
+#include "src/sksl/SkSLModifiersPool.h"
+#include "src/sksl/SkSLOutputStream.h"
+#include "src/sksl/SkSLPool.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/SkSLThreadContext.h"
+#include "src/sksl/SkSLUtil.h"
+#include "src/sksl/analysis/SkSLProgramUsage.h"
+#include "src/sksl/ir/SkSLBinaryExpression.h"
+#include "src/sksl/ir/SkSLBlock.h"
+#include "src/sksl/ir/SkSLConstructor.h"
+#include "src/sksl/ir/SkSLConstructorArrayCast.h"
+#include "src/sksl/ir/SkSLConstructorCompound.h"
+#include "src/sksl/ir/SkSLConstructorCompoundCast.h"
+#include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h"
+#include "src/sksl/ir/SkSLConstructorMatrixResize.h"
+#include "src/sksl/ir/SkSLConstructorScalarCast.h"
+#include "src/sksl/ir/SkSLConstructorSplat.h"
+#include "src/sksl/ir/SkSLDoStatement.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLExpressionStatement.h"
+#include "src/sksl/ir/SkSLExtension.h"
+#include "src/sksl/ir/SkSLField.h"
+#include "src/sksl/ir/SkSLFieldAccess.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/SkSLInterfaceBlock.h"
+#include "src/sksl/ir/SkSLLiteral.h"
+#include "src/sksl/ir/SkSLPostfixExpression.h"
+#include "src/sksl/ir/SkSLPrefixExpression.h"
+#include "src/sksl/ir/SkSLProgram.h"
+#include "src/sksl/ir/SkSLReturnStatement.h"
+#include "src/sksl/ir/SkSLSetting.h"
+#include "src/sksl/ir/SkSLSwitchCase.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/transform/SkSLTransform.h"
+#include "src/utils/SkBitSet.h"
+
+#include <cstring>
+#include <set>
+#include <string>
+#include <utility>
+
+#define kLast_Capability SpvCapabilityMultiViewport
+
+constexpr int DEVICE_FRAGCOORDS_BUILTIN = -1000;
+constexpr int DEVICE_CLOCKWISE_BUILTIN = -1001;
+
+namespace SkSL {
+
+// Equality and hash operators for Instructions.
+bool SPIRVCodeGenerator::Instruction::operator==(const SPIRVCodeGenerator::Instruction& that) const {
+ return fOp == that.fOp &&
+ fResultKind == that.fResultKind &&
+ fWords == that.fWords;
+}
+
+struct SPIRVCodeGenerator::Instruction::Hash {
+ uint32_t operator()(const SPIRVCodeGenerator::Instruction& key) const {
+ uint32_t hash = key.fResultKind;
+ hash = SkOpts::hash_fn(&key.fOp, sizeof(key.fOp), hash);
+ hash = SkOpts::hash_fn(key.fWords.data(), key.fWords.size() * sizeof(int32_t), hash);
+ return hash;
+ }
+};
+
+// This class is used to pass values and result placeholder slots to writeInstruction.
+struct SPIRVCodeGenerator::Word {
+ enum Kind {
+ kNone, // intended for use as a sentinel, not part of any Instruction
+ kSpvId,
+ kNumber,
+ kDefaultPrecisionResult,
+ kRelaxedPrecisionResult,
+ kUniqueResult,
+ kKeyedResult,
+ };
+
+ Word(SpvId id) : fValue(id), fKind(Kind::kSpvId) {}
+ Word(int32_t val, Kind kind) : fValue(val), fKind(kind) {}
+
+ static Word Number(int32_t val) {
+ return Word{val, Kind::kNumber};
+ }
+
+ static Word Result(const Type& type) {
+ return (type.hasPrecision() && !type.highPrecision()) ? RelaxedResult() : Result();
+ }
+
+ static Word RelaxedResult() {
+ return Word{(int32_t)NA, kRelaxedPrecisionResult};
+ }
+
+ static Word UniqueResult() {
+ return Word{(int32_t)NA, kUniqueResult};
+ }
+
+ static Word Result() {
+ return Word{(int32_t)NA, kDefaultPrecisionResult};
+ }
+
+ // Unlike a Result (where the result ID is always deduplicated to its first instruction) or a
+ // UniqueResult (which always produces a new instruction), a KeyedResult allows an instruction
+ // to be deduplicated among those that share the same `key`.
+ static Word KeyedResult(int32_t key) { return Word{key, Kind::kKeyedResult}; }
+
+ bool isResult() const { return fKind >= Kind::kDefaultPrecisionResult; }
+
+ int32_t fValue;
+ Kind fKind;
+};
+
+// Skia's magic number is 31 and goes in the top 16 bits. We can use the lower bits to version the
+// sksl generator if we want.
+// https://github.com/KhronosGroup/SPIRV-Headers/blob/master/include/spirv/spir-v.xml#L84
+static const int32_t SKSL_MAGIC = 0x001F0000;
+
+SPIRVCodeGenerator::Intrinsic SPIRVCodeGenerator::getIntrinsic(IntrinsicKind ik) const {
+
+#define ALL_GLSL(x) Intrinsic{kGLSL_STD_450_IntrinsicOpcodeKind, GLSLstd450 ## x, \
+ GLSLstd450 ## x, GLSLstd450 ## x, GLSLstd450 ## x}
+#define BY_TYPE_GLSL(ifFloat, ifInt, ifUInt) Intrinsic{kGLSL_STD_450_IntrinsicOpcodeKind, \
+ GLSLstd450 ## ifFloat, \
+ GLSLstd450 ## ifInt, \
+ GLSLstd450 ## ifUInt, \
+ SpvOpUndef}
+#define ALL_SPIRV(x) Intrinsic{kSPIRV_IntrinsicOpcodeKind, \
+ SpvOp ## x, SpvOp ## x, SpvOp ## x, SpvOp ## x}
+#define BOOL_SPIRV(x) Intrinsic{kSPIRV_IntrinsicOpcodeKind, \
+ SpvOpUndef, SpvOpUndef, SpvOpUndef, SpvOp ## x}
+#define FLOAT_SPIRV(x) Intrinsic{kSPIRV_IntrinsicOpcodeKind, \
+ SpvOp ## x, SpvOpUndef, SpvOpUndef, SpvOpUndef}
+#define SPECIAL(x) Intrinsic{kSpecial_IntrinsicOpcodeKind, k ## x ## _SpecialIntrinsic, \
+ k ## x ## _SpecialIntrinsic, k ## x ## _SpecialIntrinsic, \
+ k ## x ## _SpecialIntrinsic}
+
+ switch (ik) {
+ case k_round_IntrinsicKind: return ALL_GLSL(Round);
+ case k_roundEven_IntrinsicKind: return ALL_GLSL(RoundEven);
+ case k_trunc_IntrinsicKind: return ALL_GLSL(Trunc);
+ case k_abs_IntrinsicKind: return BY_TYPE_GLSL(FAbs, SAbs, SAbs);
+ case k_sign_IntrinsicKind: return BY_TYPE_GLSL(FSign, SSign, SSign);
+ case k_floor_IntrinsicKind: return ALL_GLSL(Floor);
+ case k_ceil_IntrinsicKind: return ALL_GLSL(Ceil);
+ case k_fract_IntrinsicKind: return ALL_GLSL(Fract);
+ case k_radians_IntrinsicKind: return ALL_GLSL(Radians);
+ case k_degrees_IntrinsicKind: return ALL_GLSL(Degrees);
+ case k_sin_IntrinsicKind: return ALL_GLSL(Sin);
+ case k_cos_IntrinsicKind: return ALL_GLSL(Cos);
+ case k_tan_IntrinsicKind: return ALL_GLSL(Tan);
+ case k_asin_IntrinsicKind: return ALL_GLSL(Asin);
+ case k_acos_IntrinsicKind: return ALL_GLSL(Acos);
+ case k_atan_IntrinsicKind: return SPECIAL(Atan);
+ case k_sinh_IntrinsicKind: return ALL_GLSL(Sinh);
+ case k_cosh_IntrinsicKind: return ALL_GLSL(Cosh);
+ case k_tanh_IntrinsicKind: return ALL_GLSL(Tanh);
+ case k_asinh_IntrinsicKind: return ALL_GLSL(Asinh);
+ case k_acosh_IntrinsicKind: return ALL_GLSL(Acosh);
+ case k_atanh_IntrinsicKind: return ALL_GLSL(Atanh);
+ case k_pow_IntrinsicKind: return ALL_GLSL(Pow);
+ case k_exp_IntrinsicKind: return ALL_GLSL(Exp);
+ case k_log_IntrinsicKind: return ALL_GLSL(Log);
+ case k_exp2_IntrinsicKind: return ALL_GLSL(Exp2);
+ case k_log2_IntrinsicKind: return ALL_GLSL(Log2);
+ case k_sqrt_IntrinsicKind: return ALL_GLSL(Sqrt);
+ case k_inverse_IntrinsicKind: return ALL_GLSL(MatrixInverse);
+ case k_outerProduct_IntrinsicKind: return ALL_SPIRV(OuterProduct);
+ case k_transpose_IntrinsicKind: return ALL_SPIRV(Transpose);
+ case k_isinf_IntrinsicKind: return ALL_SPIRV(IsInf);
+ case k_isnan_IntrinsicKind: return ALL_SPIRV(IsNan);
+ case k_inversesqrt_IntrinsicKind: return ALL_GLSL(InverseSqrt);
+ case k_determinant_IntrinsicKind: return ALL_GLSL(Determinant);
+ case k_matrixCompMult_IntrinsicKind: return SPECIAL(MatrixCompMult);
+ case k_matrixInverse_IntrinsicKind: return ALL_GLSL(MatrixInverse);
+ case k_mod_IntrinsicKind: return SPECIAL(Mod);
+ case k_modf_IntrinsicKind: return ALL_GLSL(Modf);
+ case k_min_IntrinsicKind: return SPECIAL(Min);
+ case k_max_IntrinsicKind: return SPECIAL(Max);
+ case k_clamp_IntrinsicKind: return SPECIAL(Clamp);
+ case k_saturate_IntrinsicKind: return SPECIAL(Saturate);
+ case k_dot_IntrinsicKind: return FLOAT_SPIRV(Dot);
+ case k_mix_IntrinsicKind: return SPECIAL(Mix);
+ case k_step_IntrinsicKind: return SPECIAL(Step);
+ case k_smoothstep_IntrinsicKind: return SPECIAL(SmoothStep);
+ case k_fma_IntrinsicKind: return ALL_GLSL(Fma);
+ case k_frexp_IntrinsicKind: return ALL_GLSL(Frexp);
+ case k_ldexp_IntrinsicKind: return ALL_GLSL(Ldexp);
+
+#define PACK(type) case k_pack##type##_IntrinsicKind: return ALL_GLSL(Pack##type); \
+ case k_unpack##type##_IntrinsicKind: return ALL_GLSL(Unpack##type)
+ PACK(Snorm4x8);
+ PACK(Unorm4x8);
+ PACK(Snorm2x16);
+ PACK(Unorm2x16);
+ PACK(Half2x16);
+ PACK(Double2x32);
+#undef PACK
+
+ case k_length_IntrinsicKind: return ALL_GLSL(Length);
+ case k_distance_IntrinsicKind: return ALL_GLSL(Distance);
+ case k_cross_IntrinsicKind: return ALL_GLSL(Cross);
+ case k_normalize_IntrinsicKind: return ALL_GLSL(Normalize);
+ case k_faceforward_IntrinsicKind: return ALL_GLSL(FaceForward);
+ case k_reflect_IntrinsicKind: return ALL_GLSL(Reflect);
+ case k_refract_IntrinsicKind: return ALL_GLSL(Refract);
+ case k_bitCount_IntrinsicKind: return ALL_SPIRV(BitCount);
+ case k_findLSB_IntrinsicKind: return ALL_GLSL(FindILsb);
+ case k_findMSB_IntrinsicKind: return BY_TYPE_GLSL(FindSMsb, FindSMsb, FindUMsb);
+ case k_dFdx_IntrinsicKind: return FLOAT_SPIRV(DPdx);
+ case k_dFdy_IntrinsicKind: return SPECIAL(DFdy);
+ case k_fwidth_IntrinsicKind: return FLOAT_SPIRV(Fwidth);
+ case k_makeSampler2D_IntrinsicKind: return SPECIAL(SampledImage);
+
+ case k_sample_IntrinsicKind: return SPECIAL(Texture);
+ case k_sampleGrad_IntrinsicKind: return SPECIAL(TextureGrad);
+ case k_sampleLod_IntrinsicKind: return SPECIAL(TextureLod);
+ case k_subpassLoad_IntrinsicKind: return SPECIAL(SubpassLoad);
+
+ case k_floatBitsToInt_IntrinsicKind: return ALL_SPIRV(Bitcast);
+ case k_floatBitsToUint_IntrinsicKind: return ALL_SPIRV(Bitcast);
+ case k_intBitsToFloat_IntrinsicKind: return ALL_SPIRV(Bitcast);
+ case k_uintBitsToFloat_IntrinsicKind: return ALL_SPIRV(Bitcast);
+
+ case k_any_IntrinsicKind: return BOOL_SPIRV(Any);
+ case k_all_IntrinsicKind: return BOOL_SPIRV(All);
+ case k_not_IntrinsicKind: return BOOL_SPIRV(LogicalNot);
+
+ case k_equal_IntrinsicKind:
+ return Intrinsic{kSPIRV_IntrinsicOpcodeKind,
+ SpvOpFOrdEqual,
+ SpvOpIEqual,
+ SpvOpIEqual,
+ SpvOpLogicalEqual};
+ case k_notEqual_IntrinsicKind:
+ return Intrinsic{kSPIRV_IntrinsicOpcodeKind,
+ SpvOpFUnordNotEqual,
+ SpvOpINotEqual,
+ SpvOpINotEqual,
+ SpvOpLogicalNotEqual};
+ case k_lessThan_IntrinsicKind:
+ return Intrinsic{kSPIRV_IntrinsicOpcodeKind,
+ SpvOpFOrdLessThan,
+ SpvOpSLessThan,
+ SpvOpULessThan,
+ SpvOpUndef};
+ case k_lessThanEqual_IntrinsicKind:
+ return Intrinsic{kSPIRV_IntrinsicOpcodeKind,
+ SpvOpFOrdLessThanEqual,
+ SpvOpSLessThanEqual,
+ SpvOpULessThanEqual,
+ SpvOpUndef};
+ case k_greaterThan_IntrinsicKind:
+ return Intrinsic{kSPIRV_IntrinsicOpcodeKind,
+ SpvOpFOrdGreaterThan,
+ SpvOpSGreaterThan,
+ SpvOpUGreaterThan,
+ SpvOpUndef};
+ case k_greaterThanEqual_IntrinsicKind:
+ return Intrinsic{kSPIRV_IntrinsicOpcodeKind,
+ SpvOpFOrdGreaterThanEqual,
+ SpvOpSGreaterThanEqual,
+ SpvOpUGreaterThanEqual,
+ SpvOpUndef};
+ default:
+ return Intrinsic{kInvalid_IntrinsicOpcodeKind, 0, 0, 0, 0};
+ }
+}
+
+void SPIRVCodeGenerator::writeWord(int32_t word, OutputStream& out) {
+ out.write((const char*) &word, sizeof(word));
+}
+
+static bool is_float(const Type& type) {
+ return (type.isScalar() || type.isVector() || type.isMatrix()) &&
+ type.componentType().isFloat();
+}
+
+static bool is_signed(const Type& type) {
+ return (type.isScalar() || type.isVector()) && type.componentType().isSigned();
+}
+
+static bool is_unsigned(const Type& type) {
+ return (type.isScalar() || type.isVector()) && type.componentType().isUnsigned();
+}
+
+static bool is_bool(const Type& type) {
+ return (type.isScalar() || type.isVector()) && type.componentType().isBoolean();
+}
+
+template <typename T>
+static T pick_by_type(const Type& type, T ifFloat, T ifInt, T ifUInt, T ifBool) {
+ if (is_float(type)) {
+ return ifFloat;
+ }
+ if (is_signed(type)) {
+ return ifInt;
+ }
+ if (is_unsigned(type)) {
+ return ifUInt;
+ }
+ if (is_bool(type)) {
+ return ifBool;
+ }
+ SkDEBUGFAIL("unrecognized type");
+ return ifFloat;
+}
+
+static bool is_out(const Modifiers& m) {
+ return (m.fFlags & Modifiers::kOut_Flag) != 0;
+}
+
+static bool is_in(const Modifiers& m) {
+ switch (m.fFlags & (Modifiers::kOut_Flag | Modifiers::kIn_Flag)) {
+ case Modifiers::kOut_Flag: // out
+ return false;
+
+ case 0: // implicit in
+ case Modifiers::kIn_Flag: // explicit in
+ case Modifiers::kOut_Flag | Modifiers::kIn_Flag: // inout
+ return true;
+
+ default: SkUNREACHABLE;
+ }
+}
+
+static bool is_control_flow_op(SpvOp_ op) {
+ switch (op) {
+ case SpvOpReturn:
+ case SpvOpReturnValue:
+ case SpvOpKill:
+ case SpvOpSwitch:
+ case SpvOpBranch:
+ case SpvOpBranchConditional:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool is_globally_reachable_op(SpvOp_ op) {
+ switch (op) {
+ case SpvOpConstant:
+ case SpvOpConstantTrue:
+ case SpvOpConstantFalse:
+ case SpvOpConstantComposite:
+ case SpvOpTypeVoid:
+ case SpvOpTypeInt:
+ case SpvOpTypeFloat:
+ case SpvOpTypeBool:
+ case SpvOpTypeVector:
+ case SpvOpTypeMatrix:
+ case SpvOpTypeArray:
+ case SpvOpTypePointer:
+ case SpvOpTypeFunction:
+ case SpvOpTypeRuntimeArray:
+ case SpvOpTypeStruct:
+ case SpvOpTypeImage:
+ case SpvOpTypeSampledImage:
+ case SpvOpTypeSampler:
+ case SpvOpVariable:
+ case SpvOpFunction:
+ case SpvOpFunctionParameter:
+ case SpvOpFunctionEnd:
+ case SpvOpExecutionMode:
+ case SpvOpMemoryModel:
+ case SpvOpCapability:
+ case SpvOpExtInstImport:
+ case SpvOpEntryPoint:
+ case SpvOpSource:
+ case SpvOpSourceExtension:
+ case SpvOpName:
+ case SpvOpMemberName:
+ case SpvOpDecorate:
+ case SpvOpMemberDecorate:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void SPIRVCodeGenerator::writeOpCode(SpvOp_ opCode, int length, OutputStream& out) {
+ SkASSERT(opCode != SpvOpLoad || &out != &fConstantBuffer);
+ SkASSERT(opCode != SpvOpUndef);
+ bool foundDeadCode = false;
+ if (is_control_flow_op(opCode)) {
+ // This instruction causes us to leave the current block.
+ foundDeadCode = (fCurrentBlock == 0);
+ fCurrentBlock = 0;
+ } else if (!is_globally_reachable_op(opCode)) {
+ foundDeadCode = (fCurrentBlock == 0);
+ }
+
+ if (foundDeadCode) {
+ // We just encountered dead code--an instruction that don't have an associated block.
+ // Synthesize a label if this happens; this is necessary to satisfy the validator.
+ this->writeLabel(this->nextId(nullptr), kBranchlessBlock, out);
+ }
+
+ this->writeWord((length << 16) | opCode, out);
+}
+
+void SPIRVCodeGenerator::writeLabel(SpvId label, StraightLineLabelType, OutputStream& out) {
+ // The straight-line label type is not important; in any case, no caches are invalidated.
+ SkASSERT(!fCurrentBlock);
+ fCurrentBlock = label;
+ this->writeInstruction(SpvOpLabel, label, out);
+}
+
+void SPIRVCodeGenerator::writeLabel(SpvId label, BranchingLabelType type,
+ ConditionalOpCounts ops, OutputStream& out) {
+ switch (type) {
+ case kBranchIsBelow:
+ case kBranchesOnBothSides:
+ // With a backward or bidirectional branch, we haven't seen the code between the label
+ // and the branch yet, so any stored value is potentially suspect. Without scanning
+ // ahead to check, the only safe option is to ditch the store cache entirely.
+ fStoreCache.reset();
+ [[fallthrough]];
+
+ case kBranchIsAbove:
+ // With a forward branch, we can rely on stores that we had cached at the start of the
+ // statement/expression, if they haven't been touched yet. Anything newer than that is
+ // pruned.
+ this->pruneConditionalOps(ops);
+ break;
+ }
+
+ // Emit the label.
+ this->writeLabel(label, kBranchlessBlock, out);
+}
+
+void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, OutputStream& out) {
+ this->writeOpCode(opCode, 1, out);
+}
+
+void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, OutputStream& out) {
+ this->writeOpCode(opCode, 2, out);
+ this->writeWord(word1, out);
+}
+
+void SPIRVCodeGenerator::writeString(std::string_view s, OutputStream& out) {
+ out.write(s.data(), s.length());
+ switch (s.length() % 4) {
+ case 1:
+ out.write8(0);
+ [[fallthrough]];
+ case 2:
+ out.write8(0);
+ [[fallthrough]];
+ case 3:
+ out.write8(0);
+ break;
+ default:
+ this->writeWord(0, out);
+ break;
+ }
+}
+
+void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, std::string_view string,
+ OutputStream& out) {
+ this->writeOpCode(opCode, 1 + (string.length() + 4) / 4, out);
+ this->writeString(string, out);
+}
+
+void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, std::string_view string,
+ OutputStream& out) {
+ this->writeOpCode(opCode, 2 + (string.length() + 4) / 4, out);
+ this->writeWord(word1, out);
+ this->writeString(string, out);
+}
+
+void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2,
+ std::string_view string, OutputStream& out) {
+ this->writeOpCode(opCode, 3 + (string.length() + 4) / 4, out);
+ this->writeWord(word1, out);
+ this->writeWord(word2, out);
+ this->writeString(string, out);
+}
+
+void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2,
+ OutputStream& out) {
+ this->writeOpCode(opCode, 3, out);
+ this->writeWord(word1, out);
+ this->writeWord(word2, out);
+}
+
+void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2,
+ int32_t word3, OutputStream& out) {
+ this->writeOpCode(opCode, 4, out);
+ this->writeWord(word1, out);
+ this->writeWord(word2, out);
+ this->writeWord(word3, out);
+}
+
+void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2,
+ int32_t word3, int32_t word4, OutputStream& out) {
+ this->writeOpCode(opCode, 5, out);
+ this->writeWord(word1, out);
+ this->writeWord(word2, out);
+ this->writeWord(word3, out);
+ this->writeWord(word4, out);
+}
+
+void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2,
+ int32_t word3, int32_t word4, int32_t word5,
+ OutputStream& out) {
+ this->writeOpCode(opCode, 6, out);
+ this->writeWord(word1, out);
+ this->writeWord(word2, out);
+ this->writeWord(word3, out);
+ this->writeWord(word4, out);
+ this->writeWord(word5, out);
+}
+
+void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2,
+ int32_t word3, int32_t word4, int32_t word5,
+ int32_t word6, OutputStream& out) {
+ this->writeOpCode(opCode, 7, out);
+ this->writeWord(word1, out);
+ this->writeWord(word2, out);
+ this->writeWord(word3, out);
+ this->writeWord(word4, out);
+ this->writeWord(word5, out);
+ this->writeWord(word6, out);
+}
+
+void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2,
+ int32_t word3, int32_t word4, int32_t word5,
+ int32_t word6, int32_t word7, OutputStream& out) {
+ this->writeOpCode(opCode, 8, out);
+ this->writeWord(word1, out);
+ this->writeWord(word2, out);
+ this->writeWord(word3, out);
+ this->writeWord(word4, out);
+ this->writeWord(word5, out);
+ this->writeWord(word6, out);
+ this->writeWord(word7, out);
+}
+
+void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2,
+ int32_t word3, int32_t word4, int32_t word5,
+ int32_t word6, int32_t word7, int32_t word8,
+ OutputStream& out) {
+ this->writeOpCode(opCode, 9, out);
+ this->writeWord(word1, out);
+ this->writeWord(word2, out);
+ this->writeWord(word3, out);
+ this->writeWord(word4, out);
+ this->writeWord(word5, out);
+ this->writeWord(word6, out);
+ this->writeWord(word7, out);
+ this->writeWord(word8, out);
+}
+
+SPIRVCodeGenerator::Instruction SPIRVCodeGenerator::BuildInstructionKey(
+ SpvOp_ opCode, const SkTArray<Word>& words) {
+ // Assemble a cache key for this instruction.
+ Instruction key;
+ key.fOp = opCode;
+ key.fWords.resize(words.size());
+ key.fResultKind = Word::Kind::kNone;
+
+ for (int index = 0; index < words.size(); ++index) {
+ const Word& word = words[index];
+ key.fWords[index] = word.fValue;
+ if (word.isResult()) {
+ SkASSERT(key.fResultKind == Word::Kind::kNone);
+ key.fResultKind = word.fKind;
+ }
+ }
+
+ return key;
+}
+
+SpvId SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode,
+ const SkTArray<Word>& words,
+ OutputStream& out) {
+ // writeOpLoad and writeOpStore have dedicated code.
+ SkASSERT(opCode != SpvOpLoad);
+ SkASSERT(opCode != SpvOpStore);
+
+ // If this instruction exists in our op cache, return the cached SpvId.
+ Instruction key = BuildInstructionKey(opCode, words);
+ if (SpvId* cachedOp = fOpCache.find(key)) {
+ return *cachedOp;
+ }
+
+ SpvId result = NA;
+ Precision precision = Precision::kDefault;
+
+ switch (key.fResultKind) {
+ case Word::Kind::kUniqueResult:
+ // The instruction returns a SpvId, but we do not want deduplication.
+ result = this->nextId(Precision::kDefault);
+ fSpvIdCache.set(result, key);
+ break;
+
+ case Word::Kind::kNone:
+ // The instruction doesn't return a SpvId, but we can still cache and deduplicate it.
+ fOpCache.set(key, result);
+ break;
+
+ case Word::Kind::kRelaxedPrecisionResult:
+ precision = Precision::kRelaxed;
+ [[fallthrough]];
+
+ case Word::Kind::kKeyedResult:
+ [[fallthrough]];
+
+ case Word::Kind::kDefaultPrecisionResult:
+ // Consume a new SpvId.
+ result = this->nextId(precision);
+ fOpCache.set(key, result);
+ fSpvIdCache.set(result, key);
+
+ // Globally-reachable ops are not subject to the whims of flow control.
+ if (!is_globally_reachable_op(opCode)) {
+ fReachableOps.push_back(result);
+ }
+ break;
+
+ default:
+ SkDEBUGFAIL("unexpected result kind");
+ break;
+ }
+
+ // Write the requested instruction.
+ this->writeOpCode(opCode, words.size() + 1, out);
+ for (const Word& word : words) {
+ if (word.isResult()) {
+ SkASSERT(result != NA);
+ this->writeWord(result, out);
+ } else {
+ this->writeWord(word.fValue, out);
+ }
+ }
+
+ // Return the result.
+ return result;
+}
+
+SpvId SPIRVCodeGenerator::writeOpLoad(SpvId type,
+ Precision precision,
+ SpvId pointer,
+ OutputStream& out) {
+ // Look for this pointer in our load-cache.
+ if (SpvId* cachedOp = fStoreCache.find(pointer)) {
+ return *cachedOp;
+ }
+
+ // Write the requested OpLoad instruction.
+ SpvId result = this->nextId(precision);
+ this->writeInstruction(SpvOpLoad, type, result, pointer, out);
+ return result;
+}
+
+void SPIRVCodeGenerator::writeOpStore(SpvStorageClass_ storageClass,
+ SpvId pointer,
+ SpvId value,
+ OutputStream& out) {
+ // Write the uncached SpvOpStore directly.
+ this->writeInstruction(SpvOpStore, pointer, value, out);
+
+ if (storageClass == SpvStorageClassFunction) {
+ // Insert a pointer-to-SpvId mapping into the load cache. A writeOpLoad to this pointer will
+ // return the cached value as-is.
+ fStoreCache.set(pointer, value);
+ fStoreOps.push_back(pointer);
+ }
+}
+
+SpvId SPIRVCodeGenerator::writeOpConstantTrue(const Type& type) {
+ return this->writeInstruction(SpvOpConstantTrue,
+ Words{this->getType(type), Word::Result()},
+ fConstantBuffer);
+}
+
+SpvId SPIRVCodeGenerator::writeOpConstantFalse(const Type& type) {
+ return this->writeInstruction(SpvOpConstantFalse,
+ Words{this->getType(type), Word::Result()},
+ fConstantBuffer);
+}
+
+SpvId SPIRVCodeGenerator::writeOpConstant(const Type& type, int32_t valueBits) {
+ return this->writeInstruction(
+ SpvOpConstant,
+ Words{this->getType(type), Word::Result(), Word::Number(valueBits)},
+ fConstantBuffer);
+}
+
+SpvId SPIRVCodeGenerator::writeOpConstantComposite(const Type& type,
+ const SkTArray<SpvId>& values) {
+ SkASSERT(values.size() == (type.isStruct() ? (int)type.fields().size() : type.columns()));
+
+ Words words;
+ words.push_back(this->getType(type));
+ words.push_back(Word::Result());
+ for (SpvId value : values) {
+ words.push_back(value);
+ }
+ return this->writeInstruction(SpvOpConstantComposite, words, fConstantBuffer);
+}
+
+bool SPIRVCodeGenerator::toConstants(SpvId value, SkTArray<SpvId>* constants) {
+ Instruction* instr = fSpvIdCache.find(value);
+ if (!instr) {
+ return false;
+ }
+ switch (instr->fOp) {
+ case SpvOpConstant:
+ case SpvOpConstantTrue:
+ case SpvOpConstantFalse:
+ constants->push_back(value);
+ return true;
+
+ case SpvOpConstantComposite: // OpConstantComposite ResultType ResultID Constituents...
+ // Start at word 2 to skip past ResultType and ResultID.
+ for (int i = 2; i < instr->fWords.size(); ++i) {
+ if (!this->toConstants(instr->fWords[i], constants)) {
+ return false;
+ }
+ }
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+bool SPIRVCodeGenerator::toConstants(SkSpan<const SpvId> values, SkTArray<SpvId>* constants) {
+ for (SpvId value : values) {
+ if (!this->toConstants(value, constants)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+SpvId SPIRVCodeGenerator::writeOpCompositeConstruct(const Type& type,
+ const SkTArray<SpvId>& values,
+ OutputStream& out) {
+ // If this is a vector composed entirely of literals, write a constant-composite instead.
+ if (type.isVector()) {
+ SkSTArray<4, SpvId> constants;
+ if (this->toConstants(SkSpan(values), &constants)) {
+ // Create a vector from literals.
+ return this->writeOpConstantComposite(type, constants);
+ }
+ }
+
+ // If this is a matrix composed entirely of literals, constant-composite them instead.
+ if (type.isMatrix()) {
+ SkSTArray<16, SpvId> constants;
+ if (this->toConstants(SkSpan(values), &constants)) {
+ // Create each matrix column.
+ SkASSERT(type.isMatrix());
+ const Type& vecType = type.componentType().toCompound(fContext,
+ /*columns=*/type.rows(),
+ /*rows=*/1);
+ SkSTArray<4, SpvId> columnIDs;
+ for (int index=0; index < type.columns(); ++index) {
+ SkSTArray<4, SpvId> columnConstants(&constants[index * type.rows()],
+ type.rows());
+ columnIDs.push_back(this->writeOpConstantComposite(vecType, columnConstants));
+ }
+ // Compose the matrix from its columns.
+ return this->writeOpConstantComposite(type, columnIDs);
+ }
+ }
+
+ Words words;
+ words.push_back(this->getType(type));
+ words.push_back(Word::Result(type));
+ for (SpvId value : values) {
+ words.push_back(value);
+ }
+
+ return this->writeInstruction(SpvOpCompositeConstruct, words, out);
+}
+
+SPIRVCodeGenerator::Instruction* SPIRVCodeGenerator::resultTypeForInstruction(
+ const Instruction& instr) {
+ // This list should contain every op that we cache that has a result and result-type.
+ // (If one is missing, we will not find some optimization opportunities.)
+ // Generally, the result type of an op is in the 0th word, but I'm not sure if this is
+ // universally true, so it's configurable on a per-op basis.
+ int resultTypeWord;
+ switch (instr.fOp) {
+ case SpvOpConstant:
+ case SpvOpConstantTrue:
+ case SpvOpConstantFalse:
+ case SpvOpConstantComposite:
+ case SpvOpCompositeConstruct:
+ case SpvOpCompositeExtract:
+ case SpvOpLoad:
+ resultTypeWord = 0;
+ break;
+
+ default:
+ return nullptr;
+ }
+
+ Instruction* typeInstr = fSpvIdCache.find(instr.fWords[resultTypeWord]);
+ SkASSERT(typeInstr);
+ return typeInstr;
+}
+
+int SPIRVCodeGenerator::numComponentsForVecInstruction(const Instruction& instr) {
+ // If an instruction is in the op cache, its type should be as well.
+ Instruction* typeInstr = this->resultTypeForInstruction(instr);
+ SkASSERT(typeInstr);
+ SkASSERT(typeInstr->fOp == SpvOpTypeVector || typeInstr->fOp == SpvOpTypeFloat ||
+ typeInstr->fOp == SpvOpTypeInt || typeInstr->fOp == SpvOpTypeBool);
+
+ // For vectors, extract their column count. Scalars have one component by definition.
+ // SpvOpTypeVector ResultID ComponentType NumComponents
+ return (typeInstr->fOp == SpvOpTypeVector) ? typeInstr->fWords[2]
+ : 1;
+}
+
+SpvId SPIRVCodeGenerator::toComponent(SpvId id, int component) {
+ Instruction* instr = fSpvIdCache.find(id);
+ if (!instr) {
+ return NA;
+ }
+ if (instr->fOp == SpvOpConstantComposite) {
+ // SpvOpConstantComposite ResultType ResultID [components...]
+ // Add 2 to the component index to skip past ResultType and ResultID.
+ return instr->fWords[2 + component];
+ }
+ if (instr->fOp == SpvOpCompositeConstruct) {
+ // SpvOpCompositeConstruct ResultType ResultID [components...]
+ // Vectors have special rules; check to see if we are composing a vector.
+ Instruction* composedType = fSpvIdCache.find(instr->fWords[0]);
+ SkASSERT(composedType);
+
+ // When composing a non-vector, each instruction word maps 1:1 to the component index.
+ // We can just extract out the associated component directly.
+ if (composedType->fOp != SpvOpTypeVector) {
+ return instr->fWords[2 + component];
+ }
+
+ // When composing a vector, components can be either scalars or vectors.
+ // This means we need to check the op type on each component. (+2 to skip ResultType/Result)
+ for (int index = 2; index < instr->fWords.size(); ++index) {
+ int32_t currentWord = instr->fWords[index];
+
+ // Retrieve the sub-instruction pointed to by OpCompositeConstruct.
+ Instruction* subinstr = fSpvIdCache.find(currentWord);
+ if (!subinstr) {
+ return NA;
+ }
+ // If this subinstruction contains the component we're looking for...
+ int numComponents = this->numComponentsForVecInstruction(*subinstr);
+ if (component < numComponents) {
+ if (numComponents == 1) {
+ // ... it's a scalar. Return it.
+ SkASSERT(component == 0);
+ return currentWord;
+ } else {
+ // ... it's a vector. Recurse into it.
+ return this->toComponent(currentWord, component);
+ }
+ }
+ // This sub-instruction doesn't contain our component. Keep walking forward.
+ component -= numComponents;
+ }
+ SkDEBUGFAIL("component index goes past the end of this composite value");
+ return NA;
+ }
+ return NA;
+}
+
+SpvId SPIRVCodeGenerator::writeOpCompositeExtract(const Type& type,
+ SpvId base,
+ int component,
+ OutputStream& out) {
+ // If the base op is a composite, we can extract from it directly.
+ SpvId result = this->toComponent(base, component);
+ if (result != NA) {
+ return result;
+ }
+ return this->writeInstruction(
+ SpvOpCompositeExtract,
+ {this->getType(type), Word::Result(type), base, Word::Number(component)},
+ out);
+}
+
+SpvId SPIRVCodeGenerator::writeOpCompositeExtract(const Type& type,
+ SpvId base,
+ int componentA,
+ int componentB,
+ OutputStream& out) {
+ // If the base op is a composite, we can extract from it directly.
+ SpvId result = this->toComponent(base, componentA);
+ if (result != NA) {
+ return this->writeOpCompositeExtract(type, result, componentB, out);
+ }
+ return this->writeInstruction(SpvOpCompositeExtract,
+ {this->getType(type),
+ Word::Result(type),
+ base,
+ Word::Number(componentA),
+ Word::Number(componentB)},
+ out);
+}
+
+void SPIRVCodeGenerator::writeCapabilities(OutputStream& out) {
+ for (uint64_t i = 0, bit = 1; i <= kLast_Capability; i++, bit <<= 1) {
+ if (fCapabilities & bit) {
+ this->writeInstruction(SpvOpCapability, (SpvId) i, out);
+ }
+ }
+ this->writeInstruction(SpvOpCapability, SpvCapabilityShader, out);
+}
+
+SpvId SPIRVCodeGenerator::nextId(const Type* type) {
+ return this->nextId(type && type->hasPrecision() && !type->highPrecision()
+ ? Precision::kRelaxed
+ : Precision::kDefault);
+}
+
+SpvId SPIRVCodeGenerator::nextId(Precision precision) {
+ if (precision == Precision::kRelaxed && !fProgram.fConfig->fSettings.fForceHighPrecision) {
+ this->writeInstruction(SpvOpDecorate, fIdCount, SpvDecorationRelaxedPrecision,
+ fDecorationBuffer);
+ }
+ return fIdCount++;
+}
+
+SpvId SPIRVCodeGenerator::writeStruct(const Type& type, const MemoryLayout& memoryLayout) {
+ // If we've already written out this struct, return its existing SpvId.
+ if (SpvId* cachedStructId = fStructMap.find(&type)) {
+ return *cachedStructId;
+ }
+
+ // Write all of the field types first, so we don't inadvertently write them while we're in the
+ // middle of writing the struct instruction.
+ Words words;
+ words.push_back(Word::UniqueResult());
+ for (const auto& f : type.fields()) {
+ words.push_back(this->getType(*f.fType, memoryLayout));
+ }
+ SpvId resultId = this->writeInstruction(SpvOpTypeStruct, words, fConstantBuffer);
+ this->writeInstruction(SpvOpName, resultId, type.name(), fNameBuffer);
+ fStructMap.set(&type, resultId);
+
+ size_t offset = 0;
+ for (int32_t i = 0; i < (int32_t) type.fields().size(); i++) {
+ const Type::Field& field = type.fields()[i];
+ if (!memoryLayout.isSupported(*field.fType)) {
+ fContext.fErrors->error(type.fPosition, "type '" + field.fType->displayName() +
+ "' is not permitted here");
+ return resultId;
+ }
+ size_t size = memoryLayout.size(*field.fType);
+ size_t alignment = memoryLayout.alignment(*field.fType);
+ const Layout& fieldLayout = field.fModifiers.fLayout;
+ if (fieldLayout.fOffset >= 0) {
+ if (fieldLayout.fOffset < (int) offset) {
+ fContext.fErrors->error(field.fPosition, "offset of field '" +
+ std::string(field.fName) + "' must be at least " + std::to_string(offset));
+ }
+ if (fieldLayout.fOffset % alignment) {
+ fContext.fErrors->error(field.fPosition,
+ "offset of field '" + std::string(field.fName) +
+ "' must be a multiple of " + std::to_string(alignment));
+ }
+ offset = fieldLayout.fOffset;
+ } else {
+ size_t mod = offset % alignment;
+ if (mod) {
+ offset += alignment - mod;
+ }
+ }
+ this->writeInstruction(SpvOpMemberName, resultId, i, field.fName, fNameBuffer);
+ this->writeFieldLayout(fieldLayout, resultId, i);
+ if (field.fModifiers.fLayout.fBuiltin < 0) {
+ this->writeInstruction(SpvOpMemberDecorate, resultId, (SpvId) i, SpvDecorationOffset,
+ (SpvId) offset, fDecorationBuffer);
+ }
+ if (field.fType->isMatrix()) {
+ this->writeInstruction(SpvOpMemberDecorate, resultId, i, SpvDecorationColMajor,
+ fDecorationBuffer);
+ this->writeInstruction(SpvOpMemberDecorate, resultId, i, SpvDecorationMatrixStride,
+ (SpvId) memoryLayout.stride(*field.fType),
+ fDecorationBuffer);
+ }
+ if (!field.fType->highPrecision()) {
+ this->writeInstruction(SpvOpMemberDecorate, resultId, (SpvId) i,
+ SpvDecorationRelaxedPrecision, fDecorationBuffer);
+ }
+ offset += size;
+ if ((field.fType->isArray() || field.fType->isStruct()) && offset % alignment != 0) {
+ offset += alignment - offset % alignment;
+ }
+ }
+
+ return resultId;
+}
+
+SpvId SPIRVCodeGenerator::getType(const Type& type) {
+ return this->getType(type, fDefaultLayout);
+}
+
+SpvId SPIRVCodeGenerator::getType(const Type& rawType, const MemoryLayout& layout) {
+ const Type* type = &rawType;
+
+ switch (type->typeKind()) {
+ case Type::TypeKind::kVoid: {
+ return this->writeInstruction(SpvOpTypeVoid, Words{Word::Result()}, fConstantBuffer);
+ }
+ case Type::TypeKind::kScalar:
+ case Type::TypeKind::kLiteral: {
+ if (type->isBoolean()) {
+ return this->writeInstruction(SpvOpTypeBool, {Word::Result()}, fConstantBuffer);
+ }
+ if (type->isSigned()) {
+ return this->writeInstruction(
+ SpvOpTypeInt,
+ Words{Word::Result(), Word::Number(32), Word::Number(1)},
+ fConstantBuffer);
+ }
+ if (type->isUnsigned()) {
+ return this->writeInstruction(
+ SpvOpTypeInt,
+ Words{Word::Result(), Word::Number(32), Word::Number(0)},
+ fConstantBuffer);
+ }
+ if (type->isFloat()) {
+ return this->writeInstruction(
+ SpvOpTypeFloat,
+ Words{Word::Result(), Word::Number(32)},
+ fConstantBuffer);
+ }
+ SkDEBUGFAILF("unrecognized scalar type '%s'", type->description().c_str());
+ return (SpvId)-1;
+ }
+ case Type::TypeKind::kVector: {
+ SpvId scalarTypeId = this->getType(type->componentType(), layout);
+ return this->writeInstruction(
+ SpvOpTypeVector,
+ Words{Word::Result(), scalarTypeId, Word::Number(type->columns())},
+ fConstantBuffer);
+ }
+ case Type::TypeKind::kMatrix: {
+ SpvId vectorTypeId = this->getType(IndexExpression::IndexType(fContext, *type), layout);
+ return this->writeInstruction(
+ SpvOpTypeMatrix,
+ Words{Word::Result(), vectorTypeId, Word::Number(type->columns())},
+ fConstantBuffer);
+ }
+ case Type::TypeKind::kArray: {
+ if (!layout.isSupported(*type)) {
+ fContext.fErrors->error(type->fPosition, "type '" + type->displayName() +
+ "' is not permitted here");
+ return NA;
+ }
+ size_t stride = layout.stride(*type);
+ SpvId typeId = this->getType(type->componentType(), layout);
+ SpvId result = NA;
+ if (type->isUnsizedArray()) {
+ result = this->writeInstruction(SpvOpTypeRuntimeArray,
+ Words{Word::KeyedResult(stride), typeId},
+ fConstantBuffer);
+ } else {
+ SpvId countId = this->writeLiteral(type->columns(), *fContext.fTypes.fInt);
+ result = this->writeInstruction(SpvOpTypeArray,
+ Words{Word::KeyedResult(stride), typeId, countId},
+ fConstantBuffer);
+ }
+ this->writeInstruction(SpvOpDecorate,
+ {result, SpvDecorationArrayStride, Word::Number(stride)},
+ fDecorationBuffer);
+ return result;
+ }
+ case Type::TypeKind::kStruct: {
+ return this->writeStruct(*type, layout);
+ }
+ case Type::TypeKind::kSeparateSampler: {
+ return this->writeInstruction(SpvOpTypeSampler, Words{Word::Result()}, fConstantBuffer);
+ }
+ case Type::TypeKind::kSampler: {
+ // Subpass inputs should use the Texture type, not a Sampler.
+ SkASSERT(type->dimensions() != SpvDimSubpassData);
+ if (SpvDimBuffer == type->dimensions()) {
+ fCapabilities |= 1ULL << SpvCapabilitySampledBuffer;
+ }
+ SpvId imageTypeId = this->getType(type->textureType(), layout);
+ return this->writeInstruction(SpvOpTypeSampledImage,
+ Words{Word::Result(), imageTypeId},
+ fConstantBuffer);
+ }
+ case Type::TypeKind::kTexture: {
+ SpvId floatTypeId = this->getType(*fContext.fTypes.fFloat, layout);
+ int sampled = (type->textureAccess() == Type::TextureAccess::kSample) ? 1 : 2;
+ return this->writeInstruction(SpvOpTypeImage,
+ Words{Word::Result(),
+ floatTypeId,
+ Word::Number(type->dimensions()),
+ Word::Number(type->isDepth()),
+ Word::Number(type->isArrayedTexture()),
+ Word::Number(type->isMultisampled()),
+ Word::Number(sampled),
+ SpvImageFormatUnknown},
+ fConstantBuffer);
+ }
+ default: {
+ SkDEBUGFAILF("invalid type: %s", type->description().c_str());
+ return NA;
+ }
+ }
+}
+
+SpvId SPIRVCodeGenerator::getFunctionType(const FunctionDeclaration& function) {
+ Words words;
+ words.push_back(Word::Result());
+ words.push_back(this->getType(function.returnType()));
+ for (const Variable* parameter : function.parameters()) {
+ if (parameter->type().typeKind() == Type::TypeKind::kSampler &&
+ fProgram.fConfig->fSettings.fSPIRVDawnCompatMode) {
+ words.push_back(this->getFunctionParameterType(parameter->type().textureType()));
+ words.push_back(this->getFunctionParameterType(*fContext.fTypes.fSampler));
+ } else {
+ words.push_back(this->getFunctionParameterType(parameter->type()));
+ }
+ }
+ return this->writeInstruction(SpvOpTypeFunction, words, fConstantBuffer);
+}
+
+SpvId SPIRVCodeGenerator::getFunctionParameterType(const Type& parameterType) {
+ // glslang treats all function arguments as pointers whether they need to be or
+ // not. I was initially puzzled by this until I ran bizarre failures with certain
+ // patterns of function calls and control constructs, as exemplified by this minimal
+ // failure case:
+ //
+ // void sphere(float x) {
+ // }
+ //
+ // void map() {
+ // sphere(1.0);
+ // }
+ //
+ // void main() {
+ // for (int i = 0; i < 1; i++) {
+ // map();
+ // }
+ // }
+ //
+ // As of this writing, compiling this in the "obvious" way (with sphere taking a float)
+ // crashes. Making it take a float* and storing the argument in a temporary variable,
+ // as glslang does, fixes it.
+ //
+ // The consensus among shader compiler authors seems to be that GPU driver generally don't
+ // handle value-based parameters consistently. It is highly likely that they fit their
+ // implementations to conform to glslang. We take care to do so ourselves.
+ //
+ // Our implementation first stores every parameter value into a function storage-class pointer
+ // before calling a function. The exception is for opaque handle types (samplers and textures)
+ // which must be stored in a pointer with UniformConstant storage-class. This prevents
+ // unnecessary temporaries (becuase opaque handles are always rooted in a pointer variable),
+ // matches glslang's behavior, and translates into WGSL more easily when targeting Dawn.
+ SpvStorageClass_ storageClass;
+ if (parameterType.typeKind() == Type::TypeKind::kSampler ||
+ parameterType.typeKind() == Type::TypeKind::kSeparateSampler ||
+ parameterType.typeKind() == Type::TypeKind::kTexture) {
+ storageClass = SpvStorageClassUniformConstant;
+ } else {
+ storageClass = SpvStorageClassFunction;
+ }
+ return this->getPointerType(parameterType, storageClass);
+}
+
+SpvId SPIRVCodeGenerator::getPointerType(const Type& type, SpvStorageClass_ storageClass) {
+ return this->getPointerType(
+ type, this->memoryLayoutForStorageClass(storageClass), storageClass);
+}
+
+SpvId SPIRVCodeGenerator::getPointerType(const Type& type, const MemoryLayout& layout,
+ SpvStorageClass_ storageClass) {
+ return this->writeInstruction(
+ SpvOpTypePointer,
+ Words{Word::Result(), Word::Number(storageClass), this->getType(type, layout)},
+ fConstantBuffer);
+}
+
+SpvId SPIRVCodeGenerator::writeExpression(const Expression& expr, OutputStream& out) {
+ switch (expr.kind()) {
+ case Expression::Kind::kBinary:
+ return this->writeBinaryExpression(expr.as<BinaryExpression>(), out);
+ case Expression::Kind::kConstructorArrayCast:
+ return this->writeExpression(*expr.as<ConstructorArrayCast>().argument(), out);
+ case Expression::Kind::kConstructorArray:
+ case Expression::Kind::kConstructorStruct:
+ return this->writeCompositeConstructor(expr.asAnyConstructor(), out);
+ case Expression::Kind::kConstructorDiagonalMatrix:
+ return this->writeConstructorDiagonalMatrix(expr.as<ConstructorDiagonalMatrix>(), out);
+ case Expression::Kind::kConstructorMatrixResize:
+ return this->writeConstructorMatrixResize(expr.as<ConstructorMatrixResize>(), out);
+ case Expression::Kind::kConstructorScalarCast:
+ return this->writeConstructorScalarCast(expr.as<ConstructorScalarCast>(), out);
+ case Expression::Kind::kConstructorSplat:
+ return this->writeConstructorSplat(expr.as<ConstructorSplat>(), out);
+ case Expression::Kind::kConstructorCompound:
+ return this->writeConstructorCompound(expr.as<ConstructorCompound>(), out);
+ case Expression::Kind::kConstructorCompoundCast:
+ return this->writeConstructorCompoundCast(expr.as<ConstructorCompoundCast>(), out);
+ case Expression::Kind::kFieldAccess:
+ return this->writeFieldAccess(expr.as<FieldAccess>(), out);
+ case Expression::Kind::kFunctionCall:
+ return this->writeFunctionCall(expr.as<FunctionCall>(), out);
+ case Expression::Kind::kLiteral:
+ return this->writeLiteral(expr.as<Literal>());
+ case Expression::Kind::kPrefix:
+ return this->writePrefixExpression(expr.as<PrefixExpression>(), out);
+ case Expression::Kind::kPostfix:
+ return this->writePostfixExpression(expr.as<PostfixExpression>(), out);
+ case Expression::Kind::kSwizzle:
+ return this->writeSwizzle(expr.as<Swizzle>(), out);
+ case Expression::Kind::kVariableReference:
+ return this->writeVariableReference(expr.as<VariableReference>(), out);
+ case Expression::Kind::kTernary:
+ return this->writeTernaryExpression(expr.as<TernaryExpression>(), out);
+ case Expression::Kind::kIndex:
+ return this->writeIndexExpression(expr.as<IndexExpression>(), out);
+ case Expression::Kind::kSetting:
+ return this->writeExpression(*expr.as<Setting>().toLiteral(fContext), out);
+ default:
+ SkDEBUGFAILF("unsupported expression: %s", expr.description().c_str());
+ break;
+ }
+ return NA;
+}
+
+SpvId SPIRVCodeGenerator::writeIntrinsicCall(const FunctionCall& c, OutputStream& out) {
+ const FunctionDeclaration& function = c.function();
+ Intrinsic intrinsic = this->getIntrinsic(function.intrinsicKind());
+ if (intrinsic.opKind == kInvalid_IntrinsicOpcodeKind) {
+ fContext.fErrors->error(c.fPosition, "unsupported intrinsic '" + function.description() +
+ "'");
+ return NA;
+ }
+ const ExpressionArray& arguments = c.arguments();
+ int32_t intrinsicId = intrinsic.floatOp;
+ if (arguments.size() > 0) {
+ const Type& type = arguments[0]->type();
+ if (intrinsic.opKind == kSpecial_IntrinsicOpcodeKind) {
+ // Keep the default float op.
+ } else {
+ intrinsicId = pick_by_type(type, intrinsic.floatOp, intrinsic.signedOp,
+ intrinsic.unsignedOp, intrinsic.boolOp);
+ }
+ }
+ switch (intrinsic.opKind) {
+ case kGLSL_STD_450_IntrinsicOpcodeKind: {
+ SpvId result = this->nextId(&c.type());
+ SkTArray<SpvId> argumentIds;
+ std::vector<TempVar> tempVars;
+ argumentIds.reserve_back(arguments.size());
+ for (int i = 0; i < arguments.size(); i++) {
+ argumentIds.push_back(this->writeFunctionCallArgument(c, i, &tempVars, out));
+ }
+ this->writeOpCode(SpvOpExtInst, 5 + (int32_t) argumentIds.size(), out);
+ this->writeWord(this->getType(c.type()), out);
+ this->writeWord(result, out);
+ this->writeWord(fGLSLExtendedInstructions, out);
+ this->writeWord(intrinsicId, out);
+ for (SpvId id : argumentIds) {
+ this->writeWord(id, out);
+ }
+ this->copyBackTempVars(tempVars, out);
+ return result;
+ }
+ case kSPIRV_IntrinsicOpcodeKind: {
+ // GLSL supports dot(float, float), but SPIR-V does not. Convert it to FMul
+ if (intrinsicId == SpvOpDot && arguments[0]->type().isScalar()) {
+ intrinsicId = SpvOpFMul;
+ }
+ SpvId result = this->nextId(&c.type());
+ SkTArray<SpvId> argumentIds;
+ std::vector<TempVar> tempVars;
+ argumentIds.reserve_back(arguments.size());
+ for (int i = 0; i < arguments.size(); i++) {
+ argumentIds.push_back(this->writeFunctionCallArgument(c, i, &tempVars, out));
+ }
+ if (!c.type().isVoid()) {
+ this->writeOpCode((SpvOp_) intrinsicId, 3 + (int32_t) arguments.size(), out);
+ this->writeWord(this->getType(c.type()), out);
+ this->writeWord(result, out);
+ } else {
+ this->writeOpCode((SpvOp_) intrinsicId, 1 + (int32_t) arguments.size(), out);
+ }
+ for (SpvId id : argumentIds) {
+ this->writeWord(id, out);
+ }
+ this->copyBackTempVars(tempVars, out);
+ return result;
+ }
+ case kSpecial_IntrinsicOpcodeKind:
+ return this->writeSpecialIntrinsic(c, (SpecialIntrinsic) intrinsicId, out);
+ default:
+ fContext.fErrors->error(c.fPosition, "unsupported intrinsic '" +
+ function.description() + "'");
+ return NA;
+ }
+}
+
+SpvId SPIRVCodeGenerator::vectorize(const Expression& arg, int vectorSize, OutputStream& out) {
+ SkASSERT(vectorSize >= 1 && vectorSize <= 4);
+ const Type& argType = arg.type();
+ if (argType.isScalar() && vectorSize > 1) {
+ ConstructorSplat splat{arg.fPosition,
+ argType.toCompound(fContext, vectorSize, /*rows=*/1),
+ arg.clone()};
+ return this->writeConstructorSplat(splat, out);
+ }
+
+ SkASSERT(vectorSize == argType.columns());
+ return this->writeExpression(arg, out);
+}
+
+SkTArray<SpvId> SPIRVCodeGenerator::vectorize(const ExpressionArray& args, OutputStream& out) {
+ int vectorSize = 1;
+ for (const auto& a : args) {
+ if (a->type().isVector()) {
+ if (vectorSize > 1) {
+ SkASSERT(a->type().columns() == vectorSize);
+ } else {
+ vectorSize = a->type().columns();
+ }
+ }
+ }
+ SkTArray<SpvId> result;
+ result.reserve_back(args.size());
+ for (const auto& arg : args) {
+ result.push_back(this->vectorize(*arg, vectorSize, out));
+ }
+ return result;
+}
+
+void SPIRVCodeGenerator::writeGLSLExtendedInstruction(const Type& type, SpvId id, SpvId floatInst,
+ SpvId signedInst, SpvId unsignedInst,
+ const SkTArray<SpvId>& args,
+ OutputStream& out) {
+ this->writeOpCode(SpvOpExtInst, 5 + args.size(), out);
+ this->writeWord(this->getType(type), out);
+ this->writeWord(id, out);
+ this->writeWord(fGLSLExtendedInstructions, out);
+ this->writeWord(pick_by_type(type, floatInst, signedInst, unsignedInst, NA), out);
+ for (SpvId a : args) {
+ this->writeWord(a, out);
+ }
+}
+
+SpvId SPIRVCodeGenerator::writeSpecialIntrinsic(const FunctionCall& c, SpecialIntrinsic kind,
+ OutputStream& out) {
+ const ExpressionArray& arguments = c.arguments();
+ const Type& callType = c.type();
+ SpvId result = this->nextId(nullptr);
+ switch (kind) {
+ case kAtan_SpecialIntrinsic: {
+ SkSTArray<2, SpvId> argumentIds;
+ for (const std::unique_ptr<Expression>& arg : arguments) {
+ argumentIds.push_back(this->writeExpression(*arg, out));
+ }
+ this->writeOpCode(SpvOpExtInst, 5 + (int32_t) argumentIds.size(), out);
+ this->writeWord(this->getType(callType), out);
+ this->writeWord(result, out);
+ this->writeWord(fGLSLExtendedInstructions, out);
+ this->writeWord(argumentIds.size() == 2 ? GLSLstd450Atan2 : GLSLstd450Atan, out);
+ for (SpvId id : argumentIds) {
+ this->writeWord(id, out);
+ }
+ break;
+ }
+ case kSampledImage_SpecialIntrinsic: {
+ SkASSERT(arguments.size() == 2);
+ SpvId img = this->writeExpression(*arguments[0], out);
+ SpvId sampler = this->writeExpression(*arguments[1], out);
+ this->writeInstruction(SpvOpSampledImage,
+ this->getType(callType),
+ result,
+ img,
+ sampler,
+ out);
+ break;
+ }
+ case kSubpassLoad_SpecialIntrinsic: {
+ SpvId img = this->writeExpression(*arguments[0], out);
+ ExpressionArray args;
+ args.reserve_back(2);
+ args.push_back(Literal::MakeInt(fContext, Position(), /*value=*/0));
+ args.push_back(Literal::MakeInt(fContext, Position(), /*value=*/0));
+ ConstructorCompound ctor(Position(), *fContext.fTypes.fInt2, std::move(args));
+ SpvId coords = this->writeExpression(ctor, out);
+ if (arguments.size() == 1) {
+ this->writeInstruction(SpvOpImageRead,
+ this->getType(callType),
+ result,
+ img,
+ coords,
+ out);
+ } else {
+ SkASSERT(arguments.size() == 2);
+ SpvId sample = this->writeExpression(*arguments[1], out);
+ this->writeInstruction(SpvOpImageRead,
+ this->getType(callType),
+ result,
+ img,
+ coords,
+ SpvImageOperandsSampleMask,
+ sample,
+ out);
+ }
+ break;
+ }
+ case kTexture_SpecialIntrinsic: {
+ SpvOp_ op = SpvOpImageSampleImplicitLod;
+ const Type& arg1Type = arguments[1]->type();
+ switch (arguments[0]->type().dimensions()) {
+ case SpvDim1D:
+ if (arg1Type.matches(*fContext.fTypes.fFloat2)) {
+ op = SpvOpImageSampleProjImplicitLod;
+ } else {
+ SkASSERT(arg1Type.matches(*fContext.fTypes.fFloat));
+ }
+ break;
+ case SpvDim2D:
+ if (arg1Type.matches(*fContext.fTypes.fFloat3)) {
+ op = SpvOpImageSampleProjImplicitLod;
+ } else {
+ SkASSERT(arg1Type.matches(*fContext.fTypes.fFloat2));
+ }
+ break;
+ case SpvDim3D:
+ if (arg1Type.matches(*fContext.fTypes.fFloat4)) {
+ op = SpvOpImageSampleProjImplicitLod;
+ } else {
+ SkASSERT(arg1Type.matches(*fContext.fTypes.fFloat3));
+ }
+ break;
+ case SpvDimCube: // fall through
+ case SpvDimRect: // fall through
+ case SpvDimBuffer: // fall through
+ case SpvDimSubpassData:
+ break;
+ }
+ SpvId type = this->getType(callType);
+ SpvId sampler = this->writeExpression(*arguments[0], out);
+ SpvId uv = this->writeExpression(*arguments[1], out);
+ if (arguments.size() == 3) {
+ this->writeInstruction(op, type, result, sampler, uv,
+ SpvImageOperandsBiasMask,
+ this->writeExpression(*arguments[2], out),
+ out);
+ } else {
+ SkASSERT(arguments.size() == 2);
+ if (fProgram.fConfig->fSettings.fSharpenTextures) {
+ SpvId lodBias = this->writeLiteral(kSharpenTexturesBias,
+ *fContext.fTypes.fFloat);
+ this->writeInstruction(op, type, result, sampler, uv,
+ SpvImageOperandsBiasMask, lodBias, out);
+ } else {
+ this->writeInstruction(op, type, result, sampler, uv,
+ out);
+ }
+ }
+ break;
+ }
+ case kTextureGrad_SpecialIntrinsic: {
+ SpvOp_ op = SpvOpImageSampleExplicitLod;
+ SkASSERT(arguments.size() == 4);
+ SkASSERT(arguments[0]->type().dimensions() == SpvDim2D);
+ SkASSERT(arguments[1]->type().matches(*fContext.fTypes.fFloat2));
+ SkASSERT(arguments[2]->type().matches(*fContext.fTypes.fFloat2));
+ SkASSERT(arguments[3]->type().matches(*fContext.fTypes.fFloat2));
+ SpvId type = this->getType(callType);
+ SpvId sampler = this->writeExpression(*arguments[0], out);
+ SpvId uv = this->writeExpression(*arguments[1], out);
+ SpvId dPdx = this->writeExpression(*arguments[2], out);
+ SpvId dPdy = this->writeExpression(*arguments[3], out);
+ this->writeInstruction(op, type, result, sampler, uv, SpvImageOperandsGradMask,
+ dPdx, dPdy, out);
+ break;
+ }
+ case kTextureLod_SpecialIntrinsic: {
+ SpvOp_ op = SpvOpImageSampleExplicitLod;
+ SkASSERT(arguments.size() == 3);
+ SkASSERT(arguments[0]->type().dimensions() == SpvDim2D);
+ SkASSERT(arguments[2]->type().matches(*fContext.fTypes.fFloat));
+ const Type& arg1Type = arguments[1]->type();
+ if (arg1Type.matches(*fContext.fTypes.fFloat3)) {
+ op = SpvOpImageSampleProjExplicitLod;
+ } else {
+ SkASSERT(arg1Type.matches(*fContext.fTypes.fFloat2));
+ }
+ SpvId type = this->getType(callType);
+ SpvId sampler = this->writeExpression(*arguments[0], out);
+ SpvId uv = this->writeExpression(*arguments[1], out);
+ this->writeInstruction(op, type, result, sampler, uv,
+ SpvImageOperandsLodMask,
+ this->writeExpression(*arguments[2], out),
+ out);
+ break;
+ }
+ case kMod_SpecialIntrinsic: {
+ SkTArray<SpvId> args = this->vectorize(arguments, out);
+ SkASSERT(args.size() == 2);
+ const Type& operandType = arguments[0]->type();
+ SpvOp_ op = pick_by_type(operandType, SpvOpFMod, SpvOpSMod, SpvOpUMod, SpvOpUndef);
+ SkASSERT(op != SpvOpUndef);
+ this->writeOpCode(op, 5, out);
+ this->writeWord(this->getType(operandType), out);
+ this->writeWord(result, out);
+ this->writeWord(args[0], out);
+ this->writeWord(args[1], out);
+ break;
+ }
+ case kDFdy_SpecialIntrinsic: {
+ SpvId fn = this->writeExpression(*arguments[0], out);
+ this->writeOpCode(SpvOpDPdy, 4, out);
+ this->writeWord(this->getType(callType), out);
+ this->writeWord(result, out);
+ this->writeWord(fn, out);
+ if (!fProgram.fConfig->fSettings.fForceNoRTFlip) {
+ this->addRTFlipUniform(c.fPosition);
+ using namespace dsl;
+ DSLExpression rtFlip(
+ ThreadContext::Compiler().convertIdentifier(Position(), SKSL_RTFLIP_NAME));
+ SpvId rtFlipY = this->vectorize(*rtFlip.y().release(), callType.columns(), out);
+ SpvId flipped = this->nextId(&callType);
+ this->writeInstruction(
+ SpvOpFMul, this->getType(callType), flipped, result, rtFlipY, out);
+ result = flipped;
+ }
+ break;
+ }
+ case kClamp_SpecialIntrinsic: {
+ SkTArray<SpvId> args = this->vectorize(arguments, out);
+ SkASSERT(args.size() == 3);
+ this->writeGLSLExtendedInstruction(callType, result, GLSLstd450FClamp, GLSLstd450SClamp,
+ GLSLstd450UClamp, args, out);
+ break;
+ }
+ case kMax_SpecialIntrinsic: {
+ SkTArray<SpvId> args = this->vectorize(arguments, out);
+ SkASSERT(args.size() == 2);
+ this->writeGLSLExtendedInstruction(callType, result, GLSLstd450FMax, GLSLstd450SMax,
+ GLSLstd450UMax, args, out);
+ break;
+ }
+ case kMin_SpecialIntrinsic: {
+ SkTArray<SpvId> args = this->vectorize(arguments, out);
+ SkASSERT(args.size() == 2);
+ this->writeGLSLExtendedInstruction(callType, result, GLSLstd450FMin, GLSLstd450SMin,
+ GLSLstd450UMin, args, out);
+ break;
+ }
+ case kMix_SpecialIntrinsic: {
+ SkTArray<SpvId> args = this->vectorize(arguments, out);
+ SkASSERT(args.size() == 3);
+ if (arguments[2]->type().componentType().isBoolean()) {
+ // Use OpSelect to implement Boolean mix().
+ SpvId falseId = this->writeExpression(*arguments[0], out);
+ SpvId trueId = this->writeExpression(*arguments[1], out);
+ SpvId conditionId = this->writeExpression(*arguments[2], out);
+ this->writeInstruction(SpvOpSelect, this->getType(arguments[0]->type()), result,
+ conditionId, trueId, falseId, out);
+ } else {
+ this->writeGLSLExtendedInstruction(callType, result, GLSLstd450FMix, SpvOpUndef,
+ SpvOpUndef, args, out);
+ }
+ break;
+ }
+ case kSaturate_SpecialIntrinsic: {
+ SkASSERT(arguments.size() == 1);
+ ExpressionArray finalArgs;
+ finalArgs.reserve_back(3);
+ finalArgs.push_back(arguments[0]->clone());
+ finalArgs.push_back(Literal::MakeFloat(fContext, Position(), /*value=*/0));
+ finalArgs.push_back(Literal::MakeFloat(fContext, Position(), /*value=*/1));
+ SkTArray<SpvId> spvArgs = this->vectorize(finalArgs, out);
+ this->writeGLSLExtendedInstruction(callType, result, GLSLstd450FClamp, GLSLstd450SClamp,
+ GLSLstd450UClamp, spvArgs, out);
+ break;
+ }
+ case kSmoothStep_SpecialIntrinsic: {
+ SkTArray<SpvId> args = this->vectorize(arguments, out);
+ SkASSERT(args.size() == 3);
+ this->writeGLSLExtendedInstruction(callType, result, GLSLstd450SmoothStep, SpvOpUndef,
+ SpvOpUndef, args, out);
+ break;
+ }
+ case kStep_SpecialIntrinsic: {
+ SkTArray<SpvId> args = this->vectorize(arguments, out);
+ SkASSERT(args.size() == 2);
+ this->writeGLSLExtendedInstruction(callType, result, GLSLstd450Step, SpvOpUndef,
+ SpvOpUndef, args, out);
+ break;
+ }
+ case kMatrixCompMult_SpecialIntrinsic: {
+ SkASSERT(arguments.size() == 2);
+ SpvId lhs = this->writeExpression(*arguments[0], out);
+ SpvId rhs = this->writeExpression(*arguments[1], out);
+ result = this->writeComponentwiseMatrixBinary(callType, lhs, rhs, SpvOpFMul, out);
+ break;
+ }
+ }
+ return result;
+}
+
+SpvId SPIRVCodeGenerator::writeFunctionCallArgument(const FunctionCall& call,
+ int argIndex,
+ std::vector<TempVar>* tempVars,
+ OutputStream& out,
+ SpvId* outSynthesizedSamplerId) {
+ const FunctionDeclaration& funcDecl = call.function();
+ const Expression& arg = *call.arguments()[argIndex];
+ const Modifiers& paramModifiers = funcDecl.parameters()[argIndex]->modifiers();
+
+ // ID of temporary variable that we will use to hold this argument, or 0 if it is being
+ // passed directly
+ SpvId tmpVar;
+ // if we need a temporary var to store this argument, this is the value to store in the var
+ SpvId tmpValueId = NA;
+
+ if (is_out(paramModifiers)) {
+ std::unique_ptr<LValue> lv = this->getLValue(arg, out);
+ // We handle out params with a temp var that we copy back to the original variable at the
+ // end of the call. GLSL guarantees that the original variable will be unchanged until the
+ // end of the call, and also that out params are written back to their original variables in
+ // a specific order (left-to-right), so it's unsafe to pass a pointer to the original value.
+ if (is_in(paramModifiers)) {
+ tmpValueId = lv->load(out);
+ }
+ tmpVar = this->nextId(&arg.type());
+ tempVars->push_back(TempVar{tmpVar, &arg.type(), std::move(lv)});
+ } else if (funcDecl.isIntrinsic()) {
+ // Unlike user function calls, non-out intrinsic arguments don't need pointer parameters.
+ return this->writeExpression(arg, out);
+ } else if (arg.is<VariableReference>() &&
+ (arg.type().typeKind() == Type::TypeKind::kSampler ||
+ arg.type().typeKind() == Type::TypeKind::kSeparateSampler ||
+ arg.type().typeKind() == Type::TypeKind::kTexture)) {
+ // Opaque handle (sampler/texture) arguments are always declared as pointers but never
+ // stored in intermediates when calling user-defined functions.
+ //
+ // The case for intrinsics (which take opaque arguments by value) is handled above just like
+ // regular pointers.
+ //
+ // See getFunctionParameterType for further explanation.
+ const Variable* var = arg.as<VariableReference>().variable();
+
+ // In Dawn-mode the texture and sampler arguments are forwarded to the helper function.
+ if (const auto* p = fSynthesizedSamplerMap.find(var)) {
+ SkASSERT(fProgram.fConfig->fSettings.fSPIRVDawnCompatMode);
+ SkASSERT(arg.type().typeKind() == Type::TypeKind::kSampler);
+ SkASSERT(outSynthesizedSamplerId);
+
+ SpvId* img = fVariableMap.find((*p)->fTexture.get());
+ SpvId* sampler = fVariableMap.find((*p)->fSampler.get());
+ SkASSERT(img);
+ SkASSERT(sampler);
+
+ *outSynthesizedSamplerId = *sampler;
+ return *img;
+ }
+
+ SpvId* entry = fVariableMap.find(var);
+ SkASSERTF(entry, "%s", arg.description().c_str());
+ return *entry;
+ } else {
+ // We always use pointer parameters when calling user functions.
+ // See getFunctionParameterType for further explanation.
+ tmpValueId = this->writeExpression(arg, out);
+ tmpVar = this->nextId(nullptr);
+ }
+ this->writeInstruction(SpvOpVariable,
+ this->getPointerType(arg.type(), SpvStorageClassFunction),
+ tmpVar,
+ SpvStorageClassFunction,
+ fVariableBuffer);
+ if (tmpValueId != NA) {
+ this->writeOpStore(SpvStorageClassFunction, tmpVar, tmpValueId, out);
+ }
+ return tmpVar;
+}
+
+void SPIRVCodeGenerator::copyBackTempVars(const std::vector<TempVar>& tempVars, OutputStream& out) {
+ for (const TempVar& tempVar : tempVars) {
+ SpvId load = this->nextId(tempVar.type);
+ this->writeInstruction(SpvOpLoad, this->getType(*tempVar.type), load, tempVar.spvId, out);
+ tempVar.lvalue->store(load, out);
+ }
+}
+
+SpvId SPIRVCodeGenerator::writeFunctionCall(const FunctionCall& c, OutputStream& out) {
+ const FunctionDeclaration& function = c.function();
+ if (function.isIntrinsic() && !function.definition()) {
+ return this->writeIntrinsicCall(c, out);
+ }
+ const ExpressionArray& arguments = c.arguments();
+ SpvId* entry = fFunctionMap.find(&function);
+ if (!entry) {
+ fContext.fErrors->error(c.fPosition, "function '" + function.description() +
+ "' is not defined");
+ return NA;
+ }
+ // Temp variables are used to write back out-parameters after the function call is complete.
+ std::vector<TempVar> tempVars;
+ SkTArray<SpvId> argumentIds;
+ argumentIds.reserve_back(arguments.size());
+ for (int i = 0; i < arguments.size(); i++) {
+ SpvId samplerId = NA;
+ argumentIds.push_back(this->writeFunctionCallArgument(c, i, &tempVars, out, &samplerId));
+ if (samplerId != NA) {
+ argumentIds.push_back(samplerId);
+ }
+ }
+ SpvId result = this->nextId(nullptr);
+ this->writeOpCode(SpvOpFunctionCall, 4 + (int32_t)argumentIds.size(), out);
+ this->writeWord(this->getType(c.type()), out);
+ this->writeWord(result, out);
+ this->writeWord(*entry, out);
+ for (SpvId id : argumentIds) {
+ this->writeWord(id, out);
+ }
+ // Now that the call is complete, we copy temp out-variables back to their real lvalues.
+ this->copyBackTempVars(tempVars, out);
+ return result;
+}
+
+SpvId SPIRVCodeGenerator::castScalarToType(SpvId inputExprId,
+ const Type& inputType,
+ const Type& outputType,
+ OutputStream& out) {
+ if (outputType.isFloat()) {
+ return this->castScalarToFloat(inputExprId, inputType, outputType, out);
+ }
+ if (outputType.isSigned()) {
+ return this->castScalarToSignedInt(inputExprId, inputType, outputType, out);
+ }
+ if (outputType.isUnsigned()) {
+ return this->castScalarToUnsignedInt(inputExprId, inputType, outputType, out);
+ }
+ if (outputType.isBoolean()) {
+ return this->castScalarToBoolean(inputExprId, inputType, outputType, out);
+ }
+
+ fContext.fErrors->error(Position(), "unsupported cast: " + inputType.description() + " to " +
+ outputType.description());
+ return inputExprId;
+}
+
+SpvId SPIRVCodeGenerator::writeFloatConstructor(const AnyConstructor& c, OutputStream& out) {
+ SkASSERT(c.argumentSpan().size() == 1);
+ SkASSERT(c.type().isFloat());
+ const Expression& ctorExpr = *c.argumentSpan().front();
+ SpvId expressionId = this->writeExpression(ctorExpr, out);
+ return this->castScalarToFloat(expressionId, ctorExpr.type(), c.type(), out);
+}
+
+SpvId SPIRVCodeGenerator::castScalarToFloat(SpvId inputId, const Type& inputType,
+ const Type& outputType, OutputStream& out) {
+ // Casting a float to float is a no-op.
+ if (inputType.isFloat()) {
+ return inputId;
+ }
+
+ // Given the input type, generate the appropriate instruction to cast to float.
+ SpvId result = this->nextId(&outputType);
+ if (inputType.isBoolean()) {
+ // Use OpSelect to convert the boolean argument to a literal 1.0 or 0.0.
+ const SpvId oneID = this->writeLiteral(1.0, *fContext.fTypes.fFloat);
+ const SpvId zeroID = this->writeLiteral(0.0, *fContext.fTypes.fFloat);
+ this->writeInstruction(SpvOpSelect, this->getType(outputType), result,
+ inputId, oneID, zeroID, out);
+ } else if (inputType.isSigned()) {
+ this->writeInstruction(SpvOpConvertSToF, this->getType(outputType), result, inputId, out);
+ } else if (inputType.isUnsigned()) {
+ this->writeInstruction(SpvOpConvertUToF, this->getType(outputType), result, inputId, out);
+ } else {
+ SkDEBUGFAILF("unsupported type for float typecast: %s", inputType.description().c_str());
+ return NA;
+ }
+ return result;
+}
+
+SpvId SPIRVCodeGenerator::writeIntConstructor(const AnyConstructor& c, OutputStream& out) {
+ SkASSERT(c.argumentSpan().size() == 1);
+ SkASSERT(c.type().isSigned());
+ const Expression& ctorExpr = *c.argumentSpan().front();
+ SpvId expressionId = this->writeExpression(ctorExpr, out);
+ return this->castScalarToSignedInt(expressionId, ctorExpr.type(), c.type(), out);
+}
+
+SpvId SPIRVCodeGenerator::castScalarToSignedInt(SpvId inputId, const Type& inputType,
+ const Type& outputType, OutputStream& out) {
+ // Casting a signed int to signed int is a no-op.
+ if (inputType.isSigned()) {
+ return inputId;
+ }
+
+ // Given the input type, generate the appropriate instruction to cast to signed int.
+ SpvId result = this->nextId(&outputType);
+ if (inputType.isBoolean()) {
+ // Use OpSelect to convert the boolean argument to a literal 1 or 0.
+ const SpvId oneID = this->writeLiteral(1.0, *fContext.fTypes.fInt);
+ const SpvId zeroID = this->writeLiteral(0.0, *fContext.fTypes.fInt);
+ this->writeInstruction(SpvOpSelect, this->getType(outputType), result,
+ inputId, oneID, zeroID, out);
+ } else if (inputType.isFloat()) {
+ this->writeInstruction(SpvOpConvertFToS, this->getType(outputType), result, inputId, out);
+ } else if (inputType.isUnsigned()) {
+ this->writeInstruction(SpvOpBitcast, this->getType(outputType), result, inputId, out);
+ } else {
+ SkDEBUGFAILF("unsupported type for signed int typecast: %s",
+ inputType.description().c_str());
+ return NA;
+ }
+ return result;
+}
+
+SpvId SPIRVCodeGenerator::writeUIntConstructor(const AnyConstructor& c, OutputStream& out) {
+ SkASSERT(c.argumentSpan().size() == 1);
+ SkASSERT(c.type().isUnsigned());
+ const Expression& ctorExpr = *c.argumentSpan().front();
+ SpvId expressionId = this->writeExpression(ctorExpr, out);
+ return this->castScalarToUnsignedInt(expressionId, ctorExpr.type(), c.type(), out);
+}
+
+SpvId SPIRVCodeGenerator::castScalarToUnsignedInt(SpvId inputId, const Type& inputType,
+ const Type& outputType, OutputStream& out) {
+ // Casting an unsigned int to unsigned int is a no-op.
+ if (inputType.isUnsigned()) {
+ return inputId;
+ }
+
+ // Given the input type, generate the appropriate instruction to cast to unsigned int.
+ SpvId result = this->nextId(&outputType);
+ if (inputType.isBoolean()) {
+ // Use OpSelect to convert the boolean argument to a literal 1u or 0u.
+ const SpvId oneID = this->writeLiteral(1.0, *fContext.fTypes.fUInt);
+ const SpvId zeroID = this->writeLiteral(0.0, *fContext.fTypes.fUInt);
+ this->writeInstruction(SpvOpSelect, this->getType(outputType), result,
+ inputId, oneID, zeroID, out);
+ } else if (inputType.isFloat()) {
+ this->writeInstruction(SpvOpConvertFToU, this->getType(outputType), result, inputId, out);
+ } else if (inputType.isSigned()) {
+ this->writeInstruction(SpvOpBitcast, this->getType(outputType), result, inputId, out);
+ } else {
+ SkDEBUGFAILF("unsupported type for unsigned int typecast: %s",
+ inputType.description().c_str());
+ return NA;
+ }
+ return result;
+}
+
+SpvId SPIRVCodeGenerator::writeBooleanConstructor(const AnyConstructor& c, OutputStream& out) {
+ SkASSERT(c.argumentSpan().size() == 1);
+ SkASSERT(c.type().isBoolean());
+ const Expression& ctorExpr = *c.argumentSpan().front();
+ SpvId expressionId = this->writeExpression(ctorExpr, out);
+ return this->castScalarToBoolean(expressionId, ctorExpr.type(), c.type(), out);
+}
+
+SpvId SPIRVCodeGenerator::castScalarToBoolean(SpvId inputId, const Type& inputType,
+ const Type& outputType, OutputStream& out) {
+ // Casting a bool to bool is a no-op.
+ if (inputType.isBoolean()) {
+ return inputId;
+ }
+
+ // Given the input type, generate the appropriate instruction to cast to bool.
+ SpvId result = this->nextId(nullptr);
+ if (inputType.isSigned()) {
+ // Synthesize a boolean result by comparing the input against a signed zero literal.
+ const SpvId zeroID = this->writeLiteral(0.0, *fContext.fTypes.fInt);
+ this->writeInstruction(SpvOpINotEqual, this->getType(outputType), result,
+ inputId, zeroID, out);
+ } else if (inputType.isUnsigned()) {
+ // Synthesize a boolean result by comparing the input against an unsigned zero literal.
+ const SpvId zeroID = this->writeLiteral(0.0, *fContext.fTypes.fUInt);
+ this->writeInstruction(SpvOpINotEqual, this->getType(outputType), result,
+ inputId, zeroID, out);
+ } else if (inputType.isFloat()) {
+ // Synthesize a boolean result by comparing the input against a floating-point zero literal.
+ const SpvId zeroID = this->writeLiteral(0.0, *fContext.fTypes.fFloat);
+ this->writeInstruction(SpvOpFUnordNotEqual, this->getType(outputType), result,
+ inputId, zeroID, out);
+ } else {
+ SkDEBUGFAILF("unsupported type for boolean typecast: %s", inputType.description().c_str());
+ return NA;
+ }
+ return result;
+}
+
+SpvId SPIRVCodeGenerator::writeMatrixCopy(SpvId src, const Type& srcType, const Type& dstType,
+ OutputStream& out) {
+ SkASSERT(srcType.isMatrix());
+ SkASSERT(dstType.isMatrix());
+ SkASSERT(srcType.componentType().matches(dstType.componentType()));
+ const Type& srcColumnType = srcType.componentType().toCompound(fContext, srcType.rows(), 1);
+ const Type& dstColumnType = dstType.componentType().toCompound(fContext, dstType.rows(), 1);
+ SkASSERT(dstType.componentType().isFloat());
+ SpvId dstColumnTypeId = this->getType(dstColumnType);
+ const SpvId zeroId = this->writeLiteral(0.0, dstType.componentType());
+ const SpvId oneId = this->writeLiteral(1.0, dstType.componentType());
+
+ SkSTArray<4, SpvId> columns;
+ for (int i = 0; i < dstType.columns(); i++) {
+ if (i < srcType.columns()) {
+ // we're still inside the src matrix, copy the column
+ SpvId srcColumn = this->writeOpCompositeExtract(srcColumnType, src, i, out);
+ SpvId dstColumn;
+ if (srcType.rows() == dstType.rows()) {
+ // columns are equal size, don't need to do anything
+ dstColumn = srcColumn;
+ }
+ else if (dstType.rows() > srcType.rows()) {
+ // dst column is bigger, need to zero-pad it
+ SkSTArray<4, SpvId> values;
+ values.push_back(srcColumn);
+ for (int j = srcType.rows(); j < dstType.rows(); ++j) {
+ values.push_back((i == j) ? oneId : zeroId);
+ }
+ dstColumn = this->writeOpCompositeConstruct(dstColumnType, values, out);
+ }
+ else {
+ // dst column is smaller, need to swizzle the src column
+ dstColumn = this->nextId(&dstType);
+ this->writeOpCode(SpvOpVectorShuffle, 5 + dstType.rows(), out);
+ this->writeWord(dstColumnTypeId, out);
+ this->writeWord(dstColumn, out);
+ this->writeWord(srcColumn, out);
+ this->writeWord(srcColumn, out);
+ for (int j = 0; j < dstType.rows(); j++) {
+ this->writeWord(j, out);
+ }
+ }
+ columns.push_back(dstColumn);
+ } else {
+ // we're past the end of the src matrix, need to synthesize an identity-matrix column
+ SkSTArray<4, SpvId> values;
+ for (int j = 0; j < dstType.rows(); ++j) {
+ values.push_back((i == j) ? oneId : zeroId);
+ }
+ columns.push_back(this->writeOpCompositeConstruct(dstColumnType, values, out));
+ }
+ }
+
+ return this->writeOpCompositeConstruct(dstType, columns, out);
+}
+
+void SPIRVCodeGenerator::addColumnEntry(const Type& columnType,
+ SkTArray<SpvId>* currentColumn,
+ SkTArray<SpvId>* columnIds,
+ int rows,
+ SpvId entry,
+ OutputStream& out) {
+ SkASSERT(currentColumn->size() < rows);
+ currentColumn->push_back(entry);
+ if (currentColumn->size() == rows) {
+ // Synthesize this column into a vector.
+ SpvId columnId = this->writeOpCompositeConstruct(columnType, *currentColumn, out);
+ columnIds->push_back(columnId);
+ currentColumn->clear();
+ }
+}
+
+SpvId SPIRVCodeGenerator::writeMatrixConstructor(const ConstructorCompound& c, OutputStream& out) {
+ const Type& type = c.type();
+ SkASSERT(type.isMatrix());
+ SkASSERT(!c.arguments().empty());
+ const Type& arg0Type = c.arguments()[0]->type();
+ // go ahead and write the arguments so we don't try to write new instructions in the middle of
+ // an instruction
+ SkSTArray<16, SpvId> arguments;
+ for (const std::unique_ptr<Expression>& arg : c.arguments()) {
+ arguments.push_back(this->writeExpression(*arg, out));
+ }
+
+ if (arguments.size() == 1 && arg0Type.isVector()) {
+ // Special-case handling of float4 -> mat2x2.
+ SkASSERT(type.rows() == 2 && type.columns() == 2);
+ SkASSERT(arg0Type.columns() == 4);
+ SpvId v[4];
+ for (int i = 0; i < 4; ++i) {
+ v[i] = this->writeOpCompositeExtract(type.componentType(), arguments[0], i, out);
+ }
+ const Type& vecType = type.componentType().toCompound(fContext, /*columns=*/2, /*rows=*/1);
+ SpvId v0v1 = this->writeOpCompositeConstruct(vecType, {v[0], v[1]}, out);
+ SpvId v2v3 = this->writeOpCompositeConstruct(vecType, {v[2], v[3]}, out);
+ return this->writeOpCompositeConstruct(type, {v0v1, v2v3}, out);
+ }
+
+ int rows = type.rows();
+ const Type& columnType = type.componentType().toCompound(fContext,
+ /*columns=*/rows, /*rows=*/1);
+ // SpvIds of completed columns of the matrix.
+ SkSTArray<4, SpvId> columnIds;
+ // SpvIds of scalars we have written to the current column so far.
+ SkSTArray<4, SpvId> currentColumn;
+ for (int i = 0; i < arguments.size(); i++) {
+ const Type& argType = c.arguments()[i]->type();
+ if (currentColumn.empty() && argType.isVector() && argType.columns() == rows) {
+ // This vector is a complete matrix column by itself and can be used as-is.
+ columnIds.push_back(arguments[i]);
+ } else if (argType.columns() == 1) {
+ // This argument is a lone scalar and can be added to the current column as-is.
+ this->addColumnEntry(columnType, &currentColumn, &columnIds, rows, arguments[i], out);
+ } else {
+ // This argument needs to be decomposed into its constituent scalars.
+ for (int j = 0; j < argType.columns(); ++j) {
+ SpvId swizzle = this->writeOpCompositeExtract(argType.componentType(),
+ arguments[i], j, out);
+ this->addColumnEntry(columnType, &currentColumn, &columnIds, rows, swizzle, out);
+ }
+ }
+ }
+ SkASSERT(columnIds.size() == type.columns());
+ return this->writeOpCompositeConstruct(type, columnIds, out);
+}
+
+SpvId SPIRVCodeGenerator::writeConstructorCompound(const ConstructorCompound& c,
+ OutputStream& out) {
+ return c.type().isMatrix() ? this->writeMatrixConstructor(c, out)
+ : this->writeVectorConstructor(c, out);
+}
+
+SpvId SPIRVCodeGenerator::writeVectorConstructor(const ConstructorCompound& c, OutputStream& out) {
+ const Type& type = c.type();
+ const Type& componentType = type.componentType();
+ SkASSERT(type.isVector());
+
+ SkSTArray<4, SpvId> arguments;
+ for (int i = 0; i < c.arguments().size(); i++) {
+ const Type& argType = c.arguments()[i]->type();
+ SkASSERT(componentType.numberKind() == argType.componentType().numberKind());
+
+ SpvId arg = this->writeExpression(*c.arguments()[i], out);
+ if (argType.isMatrix()) {
+ // CompositeConstruct cannot take a 2x2 matrix as an input, so we need to extract out
+ // each scalar separately.
+ SkASSERT(argType.rows() == 2);
+ SkASSERT(argType.columns() == 2);
+ for (int j = 0; j < 4; ++j) {
+ arguments.push_back(this->writeOpCompositeExtract(componentType, arg,
+ j / 2, j % 2, out));
+ }
+ } else if (argType.isVector()) {
+ // There's a bug in the Intel Vulkan driver where OpCompositeConstruct doesn't handle
+ // vector arguments at all, so we always extract each vector component and pass them
+ // into OpCompositeConstruct individually.
+ for (int j = 0; j < argType.columns(); j++) {
+ arguments.push_back(this->writeOpCompositeExtract(componentType, arg, j, out));
+ }
+ } else {
+ arguments.push_back(arg);
+ }
+ }
+
+ return this->writeOpCompositeConstruct(type, arguments, out);
+}
+
+SpvId SPIRVCodeGenerator::writeConstructorSplat(const ConstructorSplat& c, OutputStream& out) {
+ // Write the splat argument.
+ SpvId argument = this->writeExpression(*c.argument(), out);
+
+ // Generate a OpCompositeConstruct which repeats the argument N times.
+ SkSTArray<4, SpvId> values;
+ values.push_back_n(/*n=*/c.type().columns(), /*t=*/argument);
+ return this->writeOpCompositeConstruct(c.type(), values, out);
+}
+
+SpvId SPIRVCodeGenerator::writeCompositeConstructor(const AnyConstructor& c, OutputStream& out) {
+ SkASSERT(c.type().isArray() || c.type().isStruct());
+ auto ctorArgs = c.argumentSpan();
+
+ SkSTArray<4, SpvId> arguments;
+ for (const std::unique_ptr<Expression>& arg : ctorArgs) {
+ arguments.push_back(this->writeExpression(*arg, out));
+ }
+
+ return this->writeOpCompositeConstruct(c.type(), arguments, out);
+}
+
+SpvId SPIRVCodeGenerator::writeConstructorScalarCast(const ConstructorScalarCast& c,
+ OutputStream& out) {
+ const Type& type = c.type();
+ if (type.componentType().numberKind() == c.argument()->type().componentType().numberKind()) {
+ return this->writeExpression(*c.argument(), out);
+ }
+
+ const Expression& ctorExpr = *c.argument();
+ SpvId expressionId = this->writeExpression(ctorExpr, out);
+ return this->castScalarToType(expressionId, ctorExpr.type(), type, out);
+}
+
+SpvId SPIRVCodeGenerator::writeConstructorCompoundCast(const ConstructorCompoundCast& c,
+ OutputStream& out) {
+ const Type& ctorType = c.type();
+ const Type& argType = c.argument()->type();
+ SkASSERT(ctorType.isVector() || ctorType.isMatrix());
+
+ // Write the composite that we are casting. If the actual type matches, we are done.
+ SpvId compositeId = this->writeExpression(*c.argument(), out);
+ if (ctorType.componentType().numberKind() == argType.componentType().numberKind()) {
+ return compositeId;
+ }
+
+ // writeMatrixCopy can cast matrices to a different type.
+ if (ctorType.isMatrix()) {
+ return this->writeMatrixCopy(compositeId, argType, ctorType, out);
+ }
+
+ // SPIR-V doesn't support vector(vector-of-different-type) directly, so we need to extract the
+ // components and convert each one manually.
+ const Type& srcType = argType.componentType();
+ const Type& dstType = ctorType.componentType();
+
+ SkSTArray<4, SpvId> arguments;
+ for (int index = 0; index < argType.columns(); ++index) {
+ SpvId componentId = this->writeOpCompositeExtract(srcType, compositeId, index, out);
+ arguments.push_back(this->castScalarToType(componentId, srcType, dstType, out));
+ }
+
+ return this->writeOpCompositeConstruct(ctorType, arguments, out);
+}
+
+SpvId SPIRVCodeGenerator::writeConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c,
+ OutputStream& out) {
+ const Type& type = c.type();
+ SkASSERT(type.isMatrix());
+ SkASSERT(c.argument()->type().isScalar());
+
+ // Write out the scalar argument.
+ SpvId diagonal = this->writeExpression(*c.argument(), out);
+
+ // Build the diagonal matrix.
+ SpvId zeroId = this->writeLiteral(0.0, *fContext.fTypes.fFloat);
+
+ const Type& vecType = type.componentType().toCompound(fContext,
+ /*columns=*/type.rows(),
+ /*rows=*/1);
+ SkSTArray<4, SpvId> columnIds;
+ SkSTArray<4, SpvId> arguments;
+ arguments.resize(type.rows());
+ for (int column = 0; column < type.columns(); column++) {
+ for (int row = 0; row < type.rows(); row++) {
+ arguments[row] = (row == column) ? diagonal : zeroId;
+ }
+ columnIds.push_back(this->writeOpCompositeConstruct(vecType, arguments, out));
+ }
+ return this->writeOpCompositeConstruct(type, columnIds, out);
+}
+
+SpvId SPIRVCodeGenerator::writeConstructorMatrixResize(const ConstructorMatrixResize& c,
+ OutputStream& out) {
+ // Write the input matrix.
+ SpvId argument = this->writeExpression(*c.argument(), out);
+
+ // Use matrix-copy to resize the input matrix to its new size.
+ return this->writeMatrixCopy(argument, c.argument()->type(), c.type(), out);
+}
+
+static SpvStorageClass_ get_storage_class_for_global_variable(
+ const Variable& var, SpvStorageClass_ fallbackStorageClass) {
+ SkASSERT(var.storage() == Variable::Storage::kGlobal);
+
+ const Modifiers& modifiers = var.modifiers();
+ if (modifiers.fFlags & Modifiers::kIn_Flag) {
+ SkASSERT(!(modifiers.fLayout.fFlags & Layout::kPushConstant_Flag));
+ return SpvStorageClassInput;
+ }
+ if (modifiers.fFlags & Modifiers::kOut_Flag) {
+ SkASSERT(!(modifiers.fLayout.fFlags & Layout::kPushConstant_Flag));
+ return SpvStorageClassOutput;
+ }
+ if (modifiers.fFlags & Modifiers::kUniform_Flag) {
+ if (modifiers.fLayout.fFlags & Layout::kPushConstant_Flag) {
+ return SpvStorageClassPushConstant;
+ }
+ if (var.type().typeKind() == Type::TypeKind::kSampler ||
+ var.type().typeKind() == Type::TypeKind::kSeparateSampler ||
+ var.type().typeKind() == Type::TypeKind::kTexture) {
+ return SpvStorageClassUniformConstant;
+ }
+ return SpvStorageClassUniform;
+ }
+ if (modifiers.fFlags & Modifiers::kBuffer_Flag) {
+ // Note: In SPIR-V 1.3, a storage buffer can be declared with the "StorageBuffer"
+ // storage class and the "Block" decoration and the <1.3 approach we use here ("Uniform"
+ // storage class and the "BufferBlock" decoration) is deprecated. Since we target SPIR-V
+ // 1.0, we have to use the deprecated approach which is well supported in Vulkan and
+ // addresses SkSL use cases (notably SkSL currently doesn't support pointer features that
+ // would benefit from SPV_KHR_variable_pointers capabilities).
+ return SpvStorageClassUniform;
+ }
+ return fallbackStorageClass;
+}
+
+static SpvStorageClass_ get_storage_class(const Expression& expr) {
+ switch (expr.kind()) {
+ case Expression::Kind::kVariableReference: {
+ const Variable& var = *expr.as<VariableReference>().variable();
+ if (var.storage() != Variable::Storage::kGlobal) {
+ return SpvStorageClassFunction;
+ }
+ return get_storage_class_for_global_variable(var, SpvStorageClassPrivate);
+ }
+ case Expression::Kind::kFieldAccess:
+ return get_storage_class(*expr.as<FieldAccess>().base());
+ case Expression::Kind::kIndex:
+ return get_storage_class(*expr.as<IndexExpression>().base());
+ default:
+ return SpvStorageClassFunction;
+ }
+}
+
+SkTArray<SpvId> SPIRVCodeGenerator::getAccessChain(const Expression& expr, OutputStream& out) {
+ switch (expr.kind()) {
+ case Expression::Kind::kIndex: {
+ const IndexExpression& indexExpr = expr.as<IndexExpression>();
+ if (indexExpr.base()->is<Swizzle>()) {
+ // Access chains don't directly support dynamically indexing into a swizzle, but we
+ // can rewrite them into a supported form.
+ return this->getAccessChain(*Transform::RewriteIndexedSwizzle(fContext, indexExpr),
+ out);
+ }
+ // All other index-expressions can be represented as typical access chains.
+ SkTArray<SpvId> chain = this->getAccessChain(*indexExpr.base(), out);
+ chain.push_back(this->writeExpression(*indexExpr.index(), out));
+ return chain;
+ }
+ case Expression::Kind::kFieldAccess: {
+ const FieldAccess& fieldExpr = expr.as<FieldAccess>();
+ SkTArray<SpvId> chain = this->getAccessChain(*fieldExpr.base(), out);
+ chain.push_back(this->writeLiteral(fieldExpr.fieldIndex(), *fContext.fTypes.fInt));
+ return chain;
+ }
+ default: {
+ SpvId id = this->getLValue(expr, out)->getPointer();
+ SkASSERT(id != NA);
+ return SkTArray<SpvId>{id};
+ }
+ }
+ SkUNREACHABLE;
+}
+
+class PointerLValue : public SPIRVCodeGenerator::LValue {
+public:
+ PointerLValue(SPIRVCodeGenerator& gen, SpvId pointer, bool isMemoryObject, SpvId type,
+ SPIRVCodeGenerator::Precision precision, SpvStorageClass_ storageClass)
+ : fGen(gen)
+ , fPointer(pointer)
+ , fIsMemoryObject(isMemoryObject)
+ , fType(type)
+ , fPrecision(precision)
+ , fStorageClass(storageClass) {}
+
+ SpvId getPointer() override {
+ return fPointer;
+ }
+
+ bool isMemoryObjectPointer() const override {
+ return fIsMemoryObject;
+ }
+
+ SpvId load(OutputStream& out) override {
+ return fGen.writeOpLoad(fType, fPrecision, fPointer, out);
+ }
+
+ void store(SpvId value, OutputStream& out) override {
+ if (!fIsMemoryObject) {
+ // We are going to write into an access chain; this could represent one component of a
+ // vector, or one element of an array. This has the potential to invalidate other,
+ // *unknown* elements of our store cache. (e.g. if the store cache holds `%50 = myVec4`,
+ // and we store `%60 = myVec4.z`, this invalidates the cached value for %50.) To avoid
+ // relying on stale data, reset the store cache entirely when this happens.
+ fGen.fStoreCache.reset();
+ }
+
+ fGen.writeOpStore(fStorageClass, fPointer, value, out);
+ }
+
+private:
+ SPIRVCodeGenerator& fGen;
+ const SpvId fPointer;
+ const bool fIsMemoryObject;
+ const SpvId fType;
+ const SPIRVCodeGenerator::Precision fPrecision;
+ const SpvStorageClass_ fStorageClass;
+};
+
+class SwizzleLValue : public SPIRVCodeGenerator::LValue {
+public:
+ SwizzleLValue(SPIRVCodeGenerator& gen, SpvId vecPointer, const ComponentArray& components,
+ const Type& baseType, const Type& swizzleType, SpvStorageClass_ storageClass)
+ : fGen(gen)
+ , fVecPointer(vecPointer)
+ , fComponents(components)
+ , fBaseType(&baseType)
+ , fSwizzleType(&swizzleType)
+ , fStorageClass(storageClass) {}
+
+ bool applySwizzle(const ComponentArray& components, const Type& newType) override {
+ ComponentArray updatedSwizzle;
+ for (int8_t component : components) {
+ if (component < 0 || component >= fComponents.size()) {
+ SkDEBUGFAILF("swizzle accessed nonexistent component %d", (int)component);
+ return false;
+ }
+ updatedSwizzle.push_back(fComponents[component]);
+ }
+ fComponents = updatedSwizzle;
+ fSwizzleType = &newType;
+ return true;
+ }
+
+ SpvId load(OutputStream& out) override {
+ SpvId base = fGen.nextId(fBaseType);
+ fGen.writeInstruction(SpvOpLoad, fGen.getType(*fBaseType), base, fVecPointer, out);
+ SpvId result = fGen.nextId(fBaseType);
+ fGen.writeOpCode(SpvOpVectorShuffle, 5 + (int32_t) fComponents.size(), out);
+ fGen.writeWord(fGen.getType(*fSwizzleType), out);
+ fGen.writeWord(result, out);
+ fGen.writeWord(base, out);
+ fGen.writeWord(base, out);
+ for (int component : fComponents) {
+ fGen.writeWord(component, out);
+ }
+ return result;
+ }
+
+ void store(SpvId value, OutputStream& out) override {
+ // use OpVectorShuffle to mix and match the vector components. We effectively create
+ // a virtual vector out of the concatenation of the left and right vectors, and then
+ // select components from this virtual vector to make the result vector. For
+ // instance, given:
+ // float3L = ...;
+ // float3R = ...;
+ // L.xz = R.xy;
+ // we end up with the virtual vector (L.x, L.y, L.z, R.x, R.y, R.z). Then we want
+ // our result vector to look like (R.x, L.y, R.y), so we need to select indices
+ // (3, 1, 4).
+ SpvId base = fGen.nextId(fBaseType);
+ fGen.writeInstruction(SpvOpLoad, fGen.getType(*fBaseType), base, fVecPointer, out);
+ SpvId shuffle = fGen.nextId(fBaseType);
+ fGen.writeOpCode(SpvOpVectorShuffle, 5 + fBaseType->columns(), out);
+ fGen.writeWord(fGen.getType(*fBaseType), out);
+ fGen.writeWord(shuffle, out);
+ fGen.writeWord(base, out);
+ fGen.writeWord(value, out);
+ for (int i = 0; i < fBaseType->columns(); i++) {
+ // current offset into the virtual vector, defaults to pulling the unmodified
+ // value from the left side
+ int offset = i;
+ // check to see if we are writing this component
+ for (int j = 0; j < fComponents.size(); j++) {
+ if (fComponents[j] == i) {
+ // we're writing to this component, so adjust the offset to pull from
+ // the correct component of the right side instead of preserving the
+ // value from the left
+ offset = (int) (j + fBaseType->columns());
+ break;
+ }
+ }
+ fGen.writeWord(offset, out);
+ }
+ fGen.writeOpStore(fStorageClass, fVecPointer, shuffle, out);
+ }
+
+private:
+ SPIRVCodeGenerator& fGen;
+ const SpvId fVecPointer;
+ ComponentArray fComponents;
+ const Type* fBaseType;
+ const Type* fSwizzleType;
+ const SpvStorageClass_ fStorageClass;
+};
+
+int SPIRVCodeGenerator::findUniformFieldIndex(const Variable& var) const {
+ int* fieldIndex = fTopLevelUniformMap.find(&var);
+ return fieldIndex ? *fieldIndex : -1;
+}
+
+std::unique_ptr<SPIRVCodeGenerator::LValue> SPIRVCodeGenerator::getLValue(const Expression& expr,
+ OutputStream& out) {
+ const Type& type = expr.type();
+ Precision precision = type.highPrecision() ? Precision::kDefault : Precision::kRelaxed;
+ switch (expr.kind()) {
+ case Expression::Kind::kVariableReference: {
+ const Variable& var = *expr.as<VariableReference>().variable();
+ int uniformIdx = this->findUniformFieldIndex(var);
+ if (uniformIdx >= 0) {
+ SpvId memberId = this->nextId(nullptr);
+ SpvId typeId = this->getPointerType(type, SpvStorageClassUniform);
+ SpvId uniformIdxId = this->writeLiteral((double)uniformIdx, *fContext.fTypes.fInt);
+ this->writeInstruction(SpvOpAccessChain, typeId, memberId, fUniformBufferId,
+ uniformIdxId, out);
+ return std::make_unique<PointerLValue>(
+ *this,
+ memberId,
+ /*isMemoryObjectPointer=*/true,
+ this->getType(type, this->memoryLayoutForVariable(var)),
+ precision,
+ SpvStorageClassUniform);
+ }
+ SpvId typeId = this->getType(type, this->memoryLayoutForVariable(var));
+ SpvId* entry = fVariableMap.find(&var);
+ SkASSERTF(entry, "%s", expr.description().c_str());
+ return std::make_unique<PointerLValue>(*this, *entry,
+ /*isMemoryObjectPointer=*/true,
+ typeId, precision, get_storage_class(expr));
+ }
+ case Expression::Kind::kIndex: // fall through
+ case Expression::Kind::kFieldAccess: {
+ SkTArray<SpvId> chain = this->getAccessChain(expr, out);
+ SpvId member = this->nextId(nullptr);
+ SpvStorageClass_ storageClass = get_storage_class(expr);
+ this->writeOpCode(SpvOpAccessChain, (SpvId) (3 + chain.size()), out);
+ this->writeWord(this->getPointerType(type, storageClass), out);
+ this->writeWord(member, out);
+ for (SpvId idx : chain) {
+ this->writeWord(idx, out);
+ }
+ return std::make_unique<PointerLValue>(
+ *this,
+ member,
+ /*isMemoryObjectPointer=*/false,
+ this->getType(type, this->memoryLayoutForStorageClass(storageClass)),
+ precision,
+ storageClass);
+ }
+ case Expression::Kind::kSwizzle: {
+ const Swizzle& swizzle = expr.as<Swizzle>();
+ std::unique_ptr<LValue> lvalue = this->getLValue(*swizzle.base(), out);
+ if (lvalue->applySwizzle(swizzle.components(), type)) {
+ return lvalue;
+ }
+ SpvId base = lvalue->getPointer();
+ if (base == NA) {
+ fContext.fErrors->error(swizzle.fPosition,
+ "unable to retrieve lvalue from swizzle");
+ }
+ SpvStorageClass_ storageClass = get_storage_class(*swizzle.base());
+ if (swizzle.components().size() == 1) {
+ SpvId member = this->nextId(nullptr);
+ SpvId typeId = this->getPointerType(type, storageClass);
+ SpvId indexId = this->writeLiteral(swizzle.components()[0], *fContext.fTypes.fInt);
+ this->writeInstruction(SpvOpAccessChain, typeId, member, base, indexId, out);
+ return std::make_unique<PointerLValue>(*this, member,
+ /*isMemoryObjectPointer=*/false,
+ this->getType(type),
+ precision, storageClass);
+ } else {
+ return std::make_unique<SwizzleLValue>(*this, base, swizzle.components(),
+ swizzle.base()->type(), type, storageClass);
+ }
+ }
+ default: {
+ // expr isn't actually an lvalue, create a placeholder variable for it. This case
+ // happens due to the need to store values in temporary variables during function
+ // calls (see comments in getFunctionParameterType); erroneous uses of rvalues as
+ // lvalues should have been caught before code generation.
+ //
+ // This is with the exception of opaque handle types (textures/samplers) which are
+ // always defined as UniformConstant pointers and don't need to be explicitly stored
+ // into a temporary (which is handled explicitly in writeFunctionCallArgument).
+ SpvId result = this->nextId(nullptr);
+ SpvId pointerType = this->getPointerType(type, SpvStorageClassFunction);
+ this->writeInstruction(SpvOpVariable, pointerType, result, SpvStorageClassFunction,
+ fVariableBuffer);
+ this->writeOpStore(SpvStorageClassFunction, result, this->writeExpression(expr, out),
+ out);
+ return std::make_unique<PointerLValue>(*this, result, /*isMemoryObjectPointer=*/true,
+ this->getType(type), precision,
+ SpvStorageClassFunction);
+ }
+ }
+}
+
+SpvId SPIRVCodeGenerator::writeVariableReference(const VariableReference& ref, OutputStream& out) {
+ const Variable* variable = ref.variable();
+ switch (variable->modifiers().fLayout.fBuiltin) {
+ case DEVICE_FRAGCOORDS_BUILTIN: {
+ // Down below, we rewrite raw references to sk_FragCoord with expressions that reference
+ // DEVICE_FRAGCOORDS_BUILTIN. This is a fake variable that means we need to directly
+ // access the fragcoord; do so now.
+ dsl::DSLGlobalVar fragCoord("sk_FragCoord");
+ return this->getLValue(*dsl::DSLExpression(fragCoord).release(), out)->load(out);
+ }
+ case DEVICE_CLOCKWISE_BUILTIN: {
+ // Down below, we rewrite raw references to sk_Clockwise with expressions that reference
+ // DEVICE_CLOCKWISE_BUILTIN. This is a fake variable that means we need to directly
+ // access front facing; do so now.
+ dsl::DSLGlobalVar clockwise("sk_Clockwise");
+ return this->getLValue(*dsl::DSLExpression(clockwise).release(), out)->load(out);
+ }
+ case SK_SECONDARYFRAGCOLOR_BUILTIN: {
+ // sk_SecondaryFragColor corresponds to gl_SecondaryFragColorEXT, which isn't supposed
+ // to appear in a SPIR-V program (it's only valid in ES2). Report an error.
+ fContext.fErrors->error(ref.fPosition,
+ "sk_SecondaryFragColor is not allowed in SPIR-V");
+ return NA;
+ }
+ case SK_FRAGCOORD_BUILTIN: {
+ if (fProgram.fConfig->fSettings.fForceNoRTFlip) {
+ dsl::DSLGlobalVar fragCoord("sk_FragCoord");
+ return this->getLValue(*dsl::DSLExpression(fragCoord).release(), out)->load(out);
+ }
+
+ // Handle inserting use of uniform to flip y when referencing sk_FragCoord.
+ this->addRTFlipUniform(ref.fPosition);
+ // Use sk_RTAdjust to compute the flipped coordinate
+ using namespace dsl;
+ const char* DEVICE_COORDS_NAME = "$device_FragCoords";
+ SymbolTable& symbols = *ThreadContext::SymbolTable();
+ // Use a uniform to flip the Y coordinate. The new expression will be written in
+ // terms of $device_FragCoords, which is a fake variable that means "access the
+ // underlying fragcoords directly without flipping it".
+ DSLExpression rtFlip(ThreadContext::Compiler().convertIdentifier(Position(),
+ SKSL_RTFLIP_NAME));
+ if (!symbols.find(DEVICE_COORDS_NAME)) {
+ AutoAttachPoolToThread attach(fProgram.fPool.get());
+ Modifiers modifiers;
+ modifiers.fLayout.fBuiltin = DEVICE_FRAGCOORDS_BUILTIN;
+ auto coordsVar = std::make_unique<Variable>(/*pos=*/Position(),
+ /*modifiersPosition=*/Position(),
+ fContext.fModifiersPool->add(modifiers),
+ DEVICE_COORDS_NAME,
+ fContext.fTypes.fFloat4.get(),
+ /*builtin=*/true,
+ Variable::Storage::kGlobal);
+ fSPIRVBonusVariables.add(coordsVar.get());
+ symbols.add(std::move(coordsVar));
+ }
+ DSLGlobalVar deviceCoord(DEVICE_COORDS_NAME);
+ std::unique_ptr<Expression> rtFlipSkSLExpr = rtFlip.release();
+ DSLExpression x = DSLExpression(rtFlipSkSLExpr->clone()).x();
+ DSLExpression y = DSLExpression(std::move(rtFlipSkSLExpr)).y();
+ return this->writeExpression(*dsl::Float4(deviceCoord.x(),
+ std::move(x) + std::move(y) * deviceCoord.y(),
+ deviceCoord.z(),
+ deviceCoord.w()).release(),
+ out);
+ }
+ case SK_CLOCKWISE_BUILTIN: {
+ if (fProgram.fConfig->fSettings.fForceNoRTFlip) {
+ dsl::DSLGlobalVar clockwise("sk_Clockwise");
+ return this->getLValue(*dsl::DSLExpression(clockwise).release(), out)->load(out);
+ }
+
+ // Handle flipping sk_Clockwise.
+ this->addRTFlipUniform(ref.fPosition);
+ using namespace dsl;
+ const char* DEVICE_CLOCKWISE_NAME = "$device_Clockwise";
+ SymbolTable& symbols = *ThreadContext::SymbolTable();
+ // Use a uniform to flip the Y coordinate. The new expression will be written in
+ // terms of $device_Clockwise, which is a fake variable that means "access the
+ // underlying FrontFacing directly".
+ DSLExpression rtFlip(ThreadContext::Compiler().convertIdentifier(Position(),
+ SKSL_RTFLIP_NAME));
+ if (!symbols.find(DEVICE_CLOCKWISE_NAME)) {
+ AutoAttachPoolToThread attach(fProgram.fPool.get());
+ Modifiers modifiers;
+ modifiers.fLayout.fBuiltin = DEVICE_CLOCKWISE_BUILTIN;
+ auto clockwiseVar = std::make_unique<Variable>(/*pos=*/Position(),
+ /*modifiersPosition=*/Position(),
+ fContext.fModifiersPool->add(modifiers),
+ DEVICE_CLOCKWISE_NAME,
+ fContext.fTypes.fBool.get(),
+ /*builtin=*/true,
+ Variable::Storage::kGlobal);
+ fSPIRVBonusVariables.add(clockwiseVar.get());
+ symbols.add(std::move(clockwiseVar));
+ }
+ DSLGlobalVar deviceClockwise(DEVICE_CLOCKWISE_NAME);
+ // FrontFacing in Vulkan is defined in terms of a top-down render target. In skia,
+ // we use the default convention of "counter-clockwise face is front".
+ return this->writeExpression(*dsl::Bool(Select(rtFlip.y() > 0,
+ !deviceClockwise,
+ deviceClockwise)).release(),
+ out);
+ }
+ default: {
+ // Constant-propagate variables that have a known compile-time value.
+ if (const Expression* expr = ConstantFolder::GetConstantValueOrNullForVariable(ref)) {
+ return this->writeExpression(*expr, out);
+ }
+
+ // A reference to a sampler variable at global scope with synthesized texture/sampler
+ // backing should construct a function-scope combined image-sampler from the synthesized
+ // constituents. This is the case in which a sample intrinsic was invoked.
+ //
+ // Variable references to opaque handles (texture/sampler) that appear as the argument
+ // of a user-defined function call are explicitly handled in writeFunctionCallArgument.
+ if (const auto* p = fSynthesizedSamplerMap.find(variable)) {
+ SkASSERT(fProgram.fConfig->fSettings.fSPIRVDawnCompatMode);
+
+ SpvId* imgPtr = fVariableMap.find((*p)->fTexture.get());
+ SpvId* samplerPtr = fVariableMap.find((*p)->fSampler.get());
+ SkASSERT(imgPtr);
+ SkASSERT(samplerPtr);
+
+ SpvId img = this->writeOpLoad(
+ this->getType((*p)->fTexture->type()), Precision::kDefault, *imgPtr, out);
+ SpvId sampler = this->writeOpLoad(this->getType((*p)->fSampler->type()),
+ Precision::kDefault,
+ *samplerPtr,
+ out);
+
+ SpvId result = this->nextId(nullptr);
+ this->writeInstruction(SpvOpSampledImage,
+ this->getType(variable->type()),
+ result,
+ img,
+ sampler,
+ out);
+
+ return result;
+ }
+
+ return this->getLValue(ref, out)->load(out);
+ }
+ }
+}
+
+SpvId SPIRVCodeGenerator::writeIndexExpression(const IndexExpression& expr, OutputStream& out) {
+ if (expr.base()->type().isVector()) {
+ SpvId base = this->writeExpression(*expr.base(), out);
+ SpvId index = this->writeExpression(*expr.index(), out);
+ SpvId result = this->nextId(nullptr);
+ this->writeInstruction(SpvOpVectorExtractDynamic, this->getType(expr.type()), result, base,
+ index, out);
+ return result;
+ }
+ return getLValue(expr, out)->load(out);
+}
+
+SpvId SPIRVCodeGenerator::writeFieldAccess(const FieldAccess& f, OutputStream& out) {
+ return getLValue(f, out)->load(out);
+}
+
+SpvId SPIRVCodeGenerator::writeSwizzle(const Swizzle& swizzle, OutputStream& out) {
+ SpvId base = this->writeExpression(*swizzle.base(), out);
+ size_t count = swizzle.components().size();
+ if (count == 1) {
+ return this->writeOpCompositeExtract(swizzle.type(), base, swizzle.components()[0], out);
+ }
+
+ SpvId result = this->nextId(&swizzle.type());
+ this->writeOpCode(SpvOpVectorShuffle, 5 + (int32_t) count, out);
+ this->writeWord(this->getType(swizzle.type()), out);
+ this->writeWord(result, out);
+ this->writeWord(base, out);
+ this->writeWord(base, out);
+ for (int component : swizzle.components()) {
+ this->writeWord(component, out);
+ }
+ return result;
+}
+
+SpvId SPIRVCodeGenerator::writeBinaryOperation(const Type& resultType,
+ const Type& operandType, SpvId lhs,
+ SpvId rhs, SpvOp_ ifFloat, SpvOp_ ifInt,
+ SpvOp_ ifUInt, SpvOp_ ifBool, OutputStream& out) {
+ SpvId result = this->nextId(&resultType);
+ SpvOp_ op = pick_by_type(operandType, ifFloat, ifInt, ifUInt, ifBool);
+ if (op == SpvOpUndef) {
+ fContext.fErrors->error(operandType.fPosition,
+ "unsupported operand for binary expression: " + operandType.description());
+ return NA;
+ }
+ this->writeInstruction(op, this->getType(resultType), result, lhs, rhs, out);
+ return result;
+}
+
+SpvId SPIRVCodeGenerator::foldToBool(SpvId id, const Type& operandType, SpvOp op,
+ OutputStream& out) {
+ if (operandType.isVector()) {
+ SpvId result = this->nextId(nullptr);
+ this->writeInstruction(op, this->getType(*fContext.fTypes.fBool), result, id, out);
+ return result;
+ }
+ return id;
+}
+
+SpvId SPIRVCodeGenerator::writeMatrixComparison(const Type& operandType, SpvId lhs, SpvId rhs,
+ SpvOp_ floatOperator, SpvOp_ intOperator,
+ SpvOp_ vectorMergeOperator, SpvOp_ mergeOperator,
+ OutputStream& out) {
+ SpvOp_ compareOp = is_float(operandType) ? floatOperator : intOperator;
+ SkASSERT(operandType.isMatrix());
+ const Type& columnType = operandType.componentType().toCompound(fContext,
+ operandType.rows(),
+ 1);
+ SpvId bvecType = this->getType(fContext.fTypes.fBool->toCompound(fContext,
+ operandType.rows(),
+ 1));
+ SpvId boolType = this->getType(*fContext.fTypes.fBool);
+ SpvId result = 0;
+ for (int i = 0; i < operandType.columns(); i++) {
+ SpvId columnL = this->writeOpCompositeExtract(columnType, lhs, i, out);
+ SpvId columnR = this->writeOpCompositeExtract(columnType, rhs, i, out);
+ SpvId compare = this->nextId(&operandType);
+ this->writeInstruction(compareOp, bvecType, compare, columnL, columnR, out);
+ SpvId merge = this->nextId(nullptr);
+ this->writeInstruction(vectorMergeOperator, boolType, merge, compare, out);
+ if (result != 0) {
+ SpvId next = this->nextId(nullptr);
+ this->writeInstruction(mergeOperator, boolType, next, result, merge, out);
+ result = next;
+ } else {
+ result = merge;
+ }
+ }
+ return result;
+}
+
+SpvId SPIRVCodeGenerator::writeComponentwiseMatrixUnary(const Type& operandType,
+ SpvId operand,
+ SpvOp_ op,
+ OutputStream& out) {
+ SkASSERT(operandType.isMatrix());
+ const Type& columnType = operandType.componentType().toCompound(fContext,
+ /*columns=*/operandType.rows(),
+ /*rows=*/1);
+ SpvId columnTypeId = this->getType(columnType);
+
+ SkSTArray<4, SpvId> columns;
+ for (int i = 0; i < operandType.columns(); i++) {
+ SpvId srcColumn = this->writeOpCompositeExtract(columnType, operand, i, out);
+ SpvId dstColumn = this->nextId(&operandType);
+ this->writeInstruction(op, columnTypeId, dstColumn, srcColumn, out);
+ columns.push_back(dstColumn);
+ }
+
+ return this->writeOpCompositeConstruct(operandType, columns, out);
+}
+
+SpvId SPIRVCodeGenerator::writeComponentwiseMatrixBinary(const Type& operandType, SpvId lhs,
+ SpvId rhs, SpvOp_ op, OutputStream& out) {
+ SkASSERT(operandType.isMatrix());
+ const Type& columnType = operandType.componentType().toCompound(fContext,
+ /*columns=*/operandType.rows(),
+ /*rows=*/1);
+ SpvId columnTypeId = this->getType(columnType);
+
+ SkSTArray<4, SpvId> columns;
+ for (int i = 0; i < operandType.columns(); i++) {
+ SpvId columnL = this->writeOpCompositeExtract(columnType, lhs, i, out);
+ SpvId columnR = this->writeOpCompositeExtract(columnType, rhs, i, out);
+ columns.push_back(this->nextId(&operandType));
+ this->writeInstruction(op, columnTypeId, columns[i], columnL, columnR, out);
+ }
+ return this->writeOpCompositeConstruct(operandType, columns, out);
+}
+
+SpvId SPIRVCodeGenerator::writeReciprocal(const Type& type, SpvId value, OutputStream& out) {
+ SkASSERT(type.isFloat());
+ SpvId one = this->writeLiteral(1.0, type);
+ SpvId reciprocal = this->nextId(&type);
+ this->writeInstruction(SpvOpFDiv, this->getType(type), reciprocal, one, value, out);
+ return reciprocal;
+}
+
+SpvId SPIRVCodeGenerator::writeScalarToMatrixSplat(const Type& matrixType,
+ SpvId scalarId,
+ OutputStream& out) {
+ // Splat the scalar into a vector.
+ const Type& vectorType = matrixType.componentType().toCompound(fContext,
+ /*columns=*/matrixType.rows(),
+ /*rows=*/1);
+ SkSTArray<4, SpvId> vecArguments;
+ vecArguments.push_back_n(/*n=*/matrixType.rows(), /*t=*/scalarId);
+ SpvId vectorId = this->writeOpCompositeConstruct(vectorType, vecArguments, out);
+
+ // Splat the vector into a matrix.
+ SkSTArray<4, SpvId> matArguments;
+ matArguments.push_back_n(/*n=*/matrixType.columns(), /*t=*/vectorId);
+ return this->writeOpCompositeConstruct(matrixType, matArguments, out);
+}
+
+static bool types_match(const Type& a, const Type& b) {
+ if (a.matches(b)) {
+ return true;
+ }
+ return (a.typeKind() == b.typeKind()) &&
+ (a.isScalar() || a.isVector() || a.isMatrix()) &&
+ (a.columns() == b.columns() && a.rows() == b.rows()) &&
+ a.componentType().numberKind() == b.componentType().numberKind();
+}
+
+SpvId SPIRVCodeGenerator::writeBinaryExpression(const Type& leftType, SpvId lhs, Operator op,
+ const Type& rightType, SpvId rhs,
+ const Type& resultType, OutputStream& out) {
+ // The comma operator ignores the type of the left-hand side entirely.
+ if (op.kind() == Operator::Kind::COMMA) {
+ return rhs;
+ }
+ // overall type we are operating on: float2, int, uint4...
+ const Type* operandType;
+ if (types_match(leftType, rightType)) {
+ operandType = &leftType;
+ } else {
+ // IR allows mismatched types in expressions (e.g. float2 * float), but they need special
+ // handling in SPIR-V
+ if (leftType.isVector() && rightType.isNumber()) {
+ if (resultType.componentType().isFloat()) {
+ switch (op.kind()) {
+ case Operator::Kind::SLASH: {
+ rhs = this->writeReciprocal(rightType, rhs, out);
+ [[fallthrough]];
+ }
+ case Operator::Kind::STAR: {
+ SpvId result = this->nextId(&resultType);
+ this->writeInstruction(SpvOpVectorTimesScalar, this->getType(resultType),
+ result, lhs, rhs, out);
+ return result;
+ }
+ default:
+ break;
+ }
+ }
+ // Vectorize the right-hand side.
+ SkSTArray<4, SpvId> arguments;
+ arguments.push_back_n(/*n=*/leftType.columns(), /*t=*/rhs);
+ rhs = this->writeOpCompositeConstruct(leftType, arguments, out);
+ operandType = &leftType;
+ } else if (rightType.isVector() && leftType.isNumber()) {
+ if (resultType.componentType().isFloat()) {
+ if (op.kind() == Operator::Kind::STAR) {
+ SpvId result = this->nextId(&resultType);
+ this->writeInstruction(SpvOpVectorTimesScalar, this->getType(resultType),
+ result, rhs, lhs, out);
+ return result;
+ }
+ }
+ // Vectorize the left-hand side.
+ SkSTArray<4, SpvId> arguments;
+ arguments.push_back_n(/*n=*/rightType.columns(), /*t=*/lhs);
+ lhs = this->writeOpCompositeConstruct(rightType, arguments, out);
+ operandType = &rightType;
+ } else if (leftType.isMatrix()) {
+ if (op.kind() == Operator::Kind::STAR) {
+ // Matrix-times-vector and matrix-times-scalar have dedicated ops in SPIR-V.
+ SpvOp_ spvop;
+ if (rightType.isMatrix()) {
+ spvop = SpvOpMatrixTimesMatrix;
+ } else if (rightType.isVector()) {
+ spvop = SpvOpMatrixTimesVector;
+ } else {
+ SkASSERT(rightType.isScalar());
+ spvop = SpvOpMatrixTimesScalar;
+ }
+ SpvId result = this->nextId(&resultType);
+ this->writeInstruction(spvop, this->getType(resultType), result, lhs, rhs, out);
+ return result;
+ } else {
+ // Matrix-op-vector is not supported in GLSL/SkSL for non-multiplication ops; we
+ // expect to have a scalar here.
+ SkASSERT(rightType.isScalar());
+
+ // Splat rhs across an entire matrix so we can reuse the matrix-op-matrix path.
+ SpvId rhsMatrix = this->writeScalarToMatrixSplat(leftType, rhs, out);
+
+ // Perform this operation as matrix-op-matrix.
+ return this->writeBinaryExpression(leftType, lhs, op, leftType, rhsMatrix,
+ resultType, out);
+ }
+ } else if (rightType.isMatrix()) {
+ if (op.kind() == Operator::Kind::STAR) {
+ // Matrix-times-vector and matrix-times-scalar have dedicated ops in SPIR-V.
+ SpvId result = this->nextId(&resultType);
+ if (leftType.isVector()) {
+ this->writeInstruction(SpvOpVectorTimesMatrix, this->getType(resultType),
+ result, lhs, rhs, out);
+ } else {
+ SkASSERT(leftType.isScalar());
+ this->writeInstruction(SpvOpMatrixTimesScalar, this->getType(resultType),
+ result, rhs, lhs, out);
+ }
+ return result;
+ } else {
+ // Vector-op-matrix is not supported in GLSL/SkSL for non-multiplication ops; we
+ // expect to have a scalar here.
+ SkASSERT(leftType.isScalar());
+
+ // Splat lhs across an entire matrix so we can reuse the matrix-op-matrix path.
+ SpvId lhsMatrix = this->writeScalarToMatrixSplat(rightType, lhs, out);
+
+ // Perform this operation as matrix-op-matrix.
+ return this->writeBinaryExpression(rightType, lhsMatrix, op, rightType, rhs,
+ resultType, out);
+ }
+ } else {
+ fContext.fErrors->error(leftType.fPosition, "unsupported mixed-type expression");
+ return NA;
+ }
+ }
+
+ switch (op.kind()) {
+ case Operator::Kind::EQEQ: {
+ if (operandType->isMatrix()) {
+ return this->writeMatrixComparison(*operandType, lhs, rhs, SpvOpFOrdEqual,
+ SpvOpIEqual, SpvOpAll, SpvOpLogicalAnd, out);
+ }
+ if (operandType->isStruct()) {
+ return this->writeStructComparison(*operandType, lhs, op, rhs, out);
+ }
+ if (operandType->isArray()) {
+ return this->writeArrayComparison(*operandType, lhs, op, rhs, out);
+ }
+ SkASSERT(resultType.isBoolean());
+ const Type* tmpType;
+ if (operandType->isVector()) {
+ tmpType = &fContext.fTypes.fBool->toCompound(fContext,
+ operandType->columns(),
+ operandType->rows());
+ } else {
+ tmpType = &resultType;
+ }
+ if (lhs == rhs) {
+ // This ignores the effects of NaN.
+ return this->writeOpConstantTrue(*fContext.fTypes.fBool);
+ }
+ return this->foldToBool(this->writeBinaryOperation(*tmpType, *operandType, lhs, rhs,
+ SpvOpFOrdEqual, SpvOpIEqual,
+ SpvOpIEqual, SpvOpLogicalEqual, out),
+ *operandType, SpvOpAll, out);
+ }
+ case Operator::Kind::NEQ:
+ if (operandType->isMatrix()) {
+ return this->writeMatrixComparison(*operandType, lhs, rhs, SpvOpFUnordNotEqual,
+ SpvOpINotEqual, SpvOpAny, SpvOpLogicalOr, out);
+ }
+ if (operandType->isStruct()) {
+ return this->writeStructComparison(*operandType, lhs, op, rhs, out);
+ }
+ if (operandType->isArray()) {
+ return this->writeArrayComparison(*operandType, lhs, op, rhs, out);
+ }
+ [[fallthrough]];
+ case Operator::Kind::LOGICALXOR:
+ SkASSERT(resultType.isBoolean());
+ const Type* tmpType;
+ if (operandType->isVector()) {
+ tmpType = &fContext.fTypes.fBool->toCompound(fContext,
+ operandType->columns(),
+ operandType->rows());
+ } else {
+ tmpType = &resultType;
+ }
+ if (lhs == rhs) {
+ // This ignores the effects of NaN.
+ return this->writeOpConstantFalse(*fContext.fTypes.fBool);
+ }
+ return this->foldToBool(this->writeBinaryOperation(*tmpType, *operandType, lhs, rhs,
+ SpvOpFUnordNotEqual, SpvOpINotEqual,
+ SpvOpINotEqual, SpvOpLogicalNotEqual,
+ out),
+ *operandType, SpvOpAny, out);
+ case Operator::Kind::GT:
+ SkASSERT(resultType.isBoolean());
+ return this->writeBinaryOperation(resultType, *operandType, lhs, rhs,
+ SpvOpFOrdGreaterThan, SpvOpSGreaterThan,
+ SpvOpUGreaterThan, SpvOpUndef, out);
+ case Operator::Kind::LT:
+ SkASSERT(resultType.isBoolean());
+ return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFOrdLessThan,
+ SpvOpSLessThan, SpvOpULessThan, SpvOpUndef, out);
+ case Operator::Kind::GTEQ:
+ SkASSERT(resultType.isBoolean());
+ return this->writeBinaryOperation(resultType, *operandType, lhs, rhs,
+ SpvOpFOrdGreaterThanEqual, SpvOpSGreaterThanEqual,
+ SpvOpUGreaterThanEqual, SpvOpUndef, out);
+ case Operator::Kind::LTEQ:
+ SkASSERT(resultType.isBoolean());
+ return this->writeBinaryOperation(resultType, *operandType, lhs, rhs,
+ SpvOpFOrdLessThanEqual, SpvOpSLessThanEqual,
+ SpvOpULessThanEqual, SpvOpUndef, out);
+ case Operator::Kind::PLUS:
+ if (leftType.isMatrix() && rightType.isMatrix()) {
+ SkASSERT(leftType.matches(rightType));
+ return this->writeComponentwiseMatrixBinary(leftType, lhs, rhs, SpvOpFAdd, out);
+ }
+ return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFAdd,
+ SpvOpIAdd, SpvOpIAdd, SpvOpUndef, out);
+ case Operator::Kind::MINUS:
+ if (leftType.isMatrix() && rightType.isMatrix()) {
+ SkASSERT(leftType.matches(rightType));
+ return this->writeComponentwiseMatrixBinary(leftType, lhs, rhs, SpvOpFSub, out);
+ }
+ return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFSub,
+ SpvOpISub, SpvOpISub, SpvOpUndef, out);
+ case Operator::Kind::STAR:
+ if (leftType.isMatrix() && rightType.isMatrix()) {
+ // matrix multiply
+ SpvId result = this->nextId(&resultType);
+ this->writeInstruction(SpvOpMatrixTimesMatrix, this->getType(resultType), result,
+ lhs, rhs, out);
+ return result;
+ }
+ return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFMul,
+ SpvOpIMul, SpvOpIMul, SpvOpUndef, out);
+ case Operator::Kind::SLASH:
+ if (leftType.isMatrix() && rightType.isMatrix()) {
+ SkASSERT(leftType.matches(rightType));
+ return this->writeComponentwiseMatrixBinary(leftType, lhs, rhs, SpvOpFDiv, out);
+ }
+ return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFDiv,
+ SpvOpSDiv, SpvOpUDiv, SpvOpUndef, out);
+ case Operator::Kind::PERCENT:
+ return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFMod,
+ SpvOpSMod, SpvOpUMod, SpvOpUndef, out);
+ case Operator::Kind::SHL:
+ return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpUndef,
+ SpvOpShiftLeftLogical, SpvOpShiftLeftLogical,
+ SpvOpUndef, out);
+ case Operator::Kind::SHR:
+ return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpUndef,
+ SpvOpShiftRightArithmetic, SpvOpShiftRightLogical,
+ SpvOpUndef, out);
+ case Operator::Kind::BITWISEAND:
+ return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpUndef,
+ SpvOpBitwiseAnd, SpvOpBitwiseAnd, SpvOpUndef, out);
+ case Operator::Kind::BITWISEOR:
+ return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpUndef,
+ SpvOpBitwiseOr, SpvOpBitwiseOr, SpvOpUndef, out);
+ case Operator::Kind::BITWISEXOR:
+ return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpUndef,
+ SpvOpBitwiseXor, SpvOpBitwiseXor, SpvOpUndef, out);
+ default:
+ fContext.fErrors->error(Position(), "unsupported token");
+ return NA;
+ }
+}
+
+SpvId SPIRVCodeGenerator::writeArrayComparison(const Type& arrayType, SpvId lhs, Operator op,
+ SpvId rhs, OutputStream& out) {
+ // The inputs must be arrays, and the op must be == or !=.
+ SkASSERT(op.kind() == Operator::Kind::EQEQ || op.kind() == Operator::Kind::NEQ);
+ SkASSERT(arrayType.isArray());
+ const Type& componentType = arrayType.componentType();
+ const int arraySize = arrayType.columns();
+ SkASSERT(arraySize > 0);
+
+ // Synthesize equality checks for each item in the array.
+ const Type& boolType = *fContext.fTypes.fBool;
+ SpvId allComparisons = NA;
+ for (int index = 0; index < arraySize; ++index) {
+ // Get the left and right item in the array.
+ SpvId itemL = this->writeOpCompositeExtract(componentType, lhs, index, out);
+ SpvId itemR = this->writeOpCompositeExtract(componentType, rhs, index, out);
+ // Use `writeBinaryExpression` with the requested == or != operator on these items.
+ SpvId comparison = this->writeBinaryExpression(componentType, itemL, op,
+ componentType, itemR, boolType, out);
+ // Merge this comparison result with all the other comparisons we've done.
+ allComparisons = this->mergeComparisons(comparison, allComparisons, op, out);
+ }
+ return allComparisons;
+}
+
+SpvId SPIRVCodeGenerator::writeStructComparison(const Type& structType, SpvId lhs, Operator op,
+ SpvId rhs, OutputStream& out) {
+ // The inputs must be structs containing fields, and the op must be == or !=.
+ SkASSERT(op.kind() == Operator::Kind::EQEQ || op.kind() == Operator::Kind::NEQ);
+ SkASSERT(structType.isStruct());
+ const std::vector<Type::Field>& fields = structType.fields();
+ SkASSERT(!fields.empty());
+
+ // Synthesize equality checks for each field in the struct.
+ const Type& boolType = *fContext.fTypes.fBool;
+ SpvId allComparisons = NA;
+ for (int index = 0; index < (int)fields.size(); ++index) {
+ // Get the left and right versions of this field.
+ const Type& fieldType = *fields[index].fType;
+
+ SpvId fieldL = this->writeOpCompositeExtract(fieldType, lhs, index, out);
+ SpvId fieldR = this->writeOpCompositeExtract(fieldType, rhs, index, out);
+ // Use `writeBinaryExpression` with the requested == or != operator on these fields.
+ SpvId comparison = this->writeBinaryExpression(fieldType, fieldL, op, fieldType, fieldR,
+ boolType, out);
+ // Merge this comparison result with all the other comparisons we've done.
+ allComparisons = this->mergeComparisons(comparison, allComparisons, op, out);
+ }
+ return allComparisons;
+}
+
+SpvId SPIRVCodeGenerator::mergeComparisons(SpvId comparison, SpvId allComparisons, Operator op,
+ OutputStream& out) {
+ // If this is the first entry, we don't need to merge comparison results with anything.
+ if (allComparisons == NA) {
+ return comparison;
+ }
+ // Use LogicalAnd or LogicalOr to combine the comparison with all the other comparisons.
+ const Type& boolType = *fContext.fTypes.fBool;
+ SpvId boolTypeId = this->getType(boolType);
+ SpvId logicalOp = this->nextId(&boolType);
+ switch (op.kind()) {
+ case Operator::Kind::EQEQ:
+ this->writeInstruction(SpvOpLogicalAnd, boolTypeId, logicalOp,
+ comparison, allComparisons, out);
+ break;
+ case Operator::Kind::NEQ:
+ this->writeInstruction(SpvOpLogicalOr, boolTypeId, logicalOp,
+ comparison, allComparisons, out);
+ break;
+ default:
+ SkDEBUGFAILF("mergeComparisons only supports == and !=, not %s", op.operatorName());
+ return NA;
+ }
+ return logicalOp;
+}
+
+SpvId SPIRVCodeGenerator::writeBinaryExpression(const BinaryExpression& b, OutputStream& out) {
+ const Expression* left = b.left().get();
+ const Expression* right = b.right().get();
+ Operator op = b.getOperator();
+
+ switch (op.kind()) {
+ case Operator::Kind::EQ: {
+ // Handles assignment.
+ SpvId rhs = this->writeExpression(*right, out);
+ this->getLValue(*left, out)->store(rhs, out);
+ return rhs;
+ }
+ case Operator::Kind::LOGICALAND:
+ // Handles short-circuiting; we don't necessarily evaluate both LHS and RHS.
+ return this->writeLogicalAnd(*b.left(), *b.right(), out);
+
+ case Operator::Kind::LOGICALOR:
+ // Handles short-circuiting; we don't necessarily evaluate both LHS and RHS.
+ return this->writeLogicalOr(*b.left(), *b.right(), out);
+
+ default:
+ break;
+ }
+
+ std::unique_ptr<LValue> lvalue;
+ SpvId lhs;
+ if (op.isAssignment()) {
+ lvalue = this->getLValue(*left, out);
+ lhs = lvalue->load(out);
+ } else {
+ lvalue = nullptr;
+ lhs = this->writeExpression(*left, out);
+ }
+
+ SpvId rhs = this->writeExpression(*right, out);
+ SpvId result = this->writeBinaryExpression(left->type(), lhs, op.removeAssignment(),
+ right->type(), rhs, b.type(), out);
+ if (lvalue) {
+ lvalue->store(result, out);
+ }
+ return result;
+}
+
+SpvId SPIRVCodeGenerator::writeLogicalAnd(const Expression& left, const Expression& right,
+ OutputStream& out) {
+ SpvId falseConstant = this->writeLiteral(0.0, *fContext.fTypes.fBool);
+ SpvId lhs = this->writeExpression(left, out);
+
+ ConditionalOpCounts conditionalOps = this->getConditionalOpCounts();
+
+ SpvId rhsLabel = this->nextId(nullptr);
+ SpvId end = this->nextId(nullptr);
+ SpvId lhsBlock = fCurrentBlock;
+ this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out);
+ this->writeInstruction(SpvOpBranchConditional, lhs, rhsLabel, end, out);
+ this->writeLabel(rhsLabel, kBranchIsOnPreviousLine, out);
+ SpvId rhs = this->writeExpression(right, out);
+ SpvId rhsBlock = fCurrentBlock;
+ this->writeInstruction(SpvOpBranch, end, out);
+ this->writeLabel(end, kBranchIsAbove, conditionalOps, out);
+ SpvId result = this->nextId(nullptr);
+ this->writeInstruction(SpvOpPhi, this->getType(*fContext.fTypes.fBool), result, falseConstant,
+ lhsBlock, rhs, rhsBlock, out);
+
+ return result;
+}
+
+SpvId SPIRVCodeGenerator::writeLogicalOr(const Expression& left, const Expression& right,
+ OutputStream& out) {
+ SpvId trueConstant = this->writeLiteral(1.0, *fContext.fTypes.fBool);
+ SpvId lhs = this->writeExpression(left, out);
+
+ ConditionalOpCounts conditionalOps = this->getConditionalOpCounts();
+
+ SpvId rhsLabel = this->nextId(nullptr);
+ SpvId end = this->nextId(nullptr);
+ SpvId lhsBlock = fCurrentBlock;
+ this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out);
+ this->writeInstruction(SpvOpBranchConditional, lhs, end, rhsLabel, out);
+ this->writeLabel(rhsLabel, kBranchIsOnPreviousLine, out);
+ SpvId rhs = this->writeExpression(right, out);
+ SpvId rhsBlock = fCurrentBlock;
+ this->writeInstruction(SpvOpBranch, end, out);
+ this->writeLabel(end, kBranchIsAbove, conditionalOps, out);
+ SpvId result = this->nextId(nullptr);
+ this->writeInstruction(SpvOpPhi, this->getType(*fContext.fTypes.fBool), result, trueConstant,
+ lhsBlock, rhs, rhsBlock, out);
+
+ return result;
+}
+
+SpvId SPIRVCodeGenerator::writeTernaryExpression(const TernaryExpression& t, OutputStream& out) {
+ const Type& type = t.type();
+ SpvId test = this->writeExpression(*t.test(), out);
+ if (t.ifTrue()->type().columns() == 1 &&
+ Analysis::IsCompileTimeConstant(*t.ifTrue()) &&
+ Analysis::IsCompileTimeConstant(*t.ifFalse())) {
+ // both true and false are constants, can just use OpSelect
+ SpvId result = this->nextId(nullptr);
+ SpvId trueId = this->writeExpression(*t.ifTrue(), out);
+ SpvId falseId = this->writeExpression(*t.ifFalse(), out);
+ this->writeInstruction(SpvOpSelect, this->getType(type), result, test, trueId, falseId,
+ out);
+ return result;
+ }
+
+ ConditionalOpCounts conditionalOps = this->getConditionalOpCounts();
+
+ // was originally using OpPhi to choose the result, but for some reason that is crashing on
+ // Adreno. Switched to storing the result in a temp variable as glslang does.
+ SpvId var = this->nextId(nullptr);
+ this->writeInstruction(SpvOpVariable, this->getPointerType(type, SpvStorageClassFunction),
+ var, SpvStorageClassFunction, fVariableBuffer);
+ SpvId trueLabel = this->nextId(nullptr);
+ SpvId falseLabel = this->nextId(nullptr);
+ SpvId end = this->nextId(nullptr);
+ this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out);
+ this->writeInstruction(SpvOpBranchConditional, test, trueLabel, falseLabel, out);
+ this->writeLabel(trueLabel, kBranchIsOnPreviousLine, out);
+ this->writeOpStore(SpvStorageClassFunction, var, this->writeExpression(*t.ifTrue(), out), out);
+ this->writeInstruction(SpvOpBranch, end, out);
+ this->writeLabel(falseLabel, kBranchIsAbove, conditionalOps, out);
+ this->writeOpStore(SpvStorageClassFunction, var, this->writeExpression(*t.ifFalse(), out), out);
+ this->writeInstruction(SpvOpBranch, end, out);
+ this->writeLabel(end, kBranchIsAbove, conditionalOps, out);
+ SpvId result = this->nextId(&type);
+ this->writeInstruction(SpvOpLoad, this->getType(type), result, var, out);
+
+ return result;
+}
+
+SpvId SPIRVCodeGenerator::writePrefixExpression(const PrefixExpression& p, OutputStream& out) {
+ const Type& type = p.type();
+ if (p.getOperator().kind() == Operator::Kind::MINUS) {
+ SpvOp_ negateOp = pick_by_type(type, SpvOpFNegate, SpvOpSNegate, SpvOpSNegate, SpvOpUndef);
+ SkASSERT(negateOp != SpvOpUndef);
+ SpvId expr = this->writeExpression(*p.operand(), out);
+ if (type.isMatrix()) {
+ return this->writeComponentwiseMatrixUnary(type, expr, negateOp, out);
+ }
+ SpvId result = this->nextId(&type);
+ SpvId typeId = this->getType(type);
+ this->writeInstruction(negateOp, typeId, result, expr, out);
+ return result;
+ }
+ switch (p.getOperator().kind()) {
+ case Operator::Kind::PLUS:
+ return this->writeExpression(*p.operand(), out);
+ case Operator::Kind::PLUSPLUS: {
+ std::unique_ptr<LValue> lv = this->getLValue(*p.operand(), out);
+ SpvId one = this->writeLiteral(1.0, type);
+ SpvId result = this->writeBinaryOperation(type, type, lv->load(out), one,
+ SpvOpFAdd, SpvOpIAdd, SpvOpIAdd, SpvOpUndef,
+ out);
+ lv->store(result, out);
+ return result;
+ }
+ case Operator::Kind::MINUSMINUS: {
+ std::unique_ptr<LValue> lv = this->getLValue(*p.operand(), out);
+ SpvId one = this->writeLiteral(1.0, type);
+ SpvId result = this->writeBinaryOperation(type, type, lv->load(out), one, SpvOpFSub,
+ SpvOpISub, SpvOpISub, SpvOpUndef, out);
+ lv->store(result, out);
+ return result;
+ }
+ case Operator::Kind::LOGICALNOT: {
+ SkASSERT(p.operand()->type().isBoolean());
+ SpvId result = this->nextId(nullptr);
+ this->writeInstruction(SpvOpLogicalNot, this->getType(type), result,
+ this->writeExpression(*p.operand(), out), out);
+ return result;
+ }
+ case Operator::Kind::BITWISENOT: {
+ SpvId result = this->nextId(nullptr);
+ this->writeInstruction(SpvOpNot, this->getType(type), result,
+ this->writeExpression(*p.operand(), out), out);
+ return result;
+ }
+ default:
+ SkDEBUGFAILF("unsupported prefix expression: %s",
+ p.description(OperatorPrecedence::kTopLevel).c_str());
+ return NA;
+ }
+}
+
+SpvId SPIRVCodeGenerator::writePostfixExpression(const PostfixExpression& p, OutputStream& out) {
+ const Type& type = p.type();
+ std::unique_ptr<LValue> lv = this->getLValue(*p.operand(), out);
+ SpvId result = lv->load(out);
+ SpvId one = this->writeLiteral(1.0, type);
+ switch (p.getOperator().kind()) {
+ case Operator::Kind::PLUSPLUS: {
+ SpvId temp = this->writeBinaryOperation(type, type, result, one, SpvOpFAdd,
+ SpvOpIAdd, SpvOpIAdd, SpvOpUndef, out);
+ lv->store(temp, out);
+ return result;
+ }
+ case Operator::Kind::MINUSMINUS: {
+ SpvId temp = this->writeBinaryOperation(type, type, result, one, SpvOpFSub,
+ SpvOpISub, SpvOpISub, SpvOpUndef, out);
+ lv->store(temp, out);
+ return result;
+ }
+ default:
+ SkDEBUGFAILF("unsupported postfix expression %s",
+ p.description(OperatorPrecedence::kTopLevel).c_str());
+ return NA;
+ }
+}
+
+SpvId SPIRVCodeGenerator::writeLiteral(const Literal& l) {
+ return this->writeLiteral(l.value(), l.type());
+}
+
+SpvId SPIRVCodeGenerator::writeLiteral(double value, const Type& type) {
+ switch (type.numberKind()) {
+ case Type::NumberKind::kFloat: {
+ float floatVal = value;
+ int32_t valueBits;
+ memcpy(&valueBits, &floatVal, sizeof(valueBits));
+ return this->writeOpConstant(type, valueBits);
+ }
+ case Type::NumberKind::kBoolean: {
+ return value ? this->writeOpConstantTrue(type)
+ : this->writeOpConstantFalse(type);
+ }
+ default: {
+ return this->writeOpConstant(type, (SKSL_INT)value);
+ }
+ }
+}
+
+SpvId SPIRVCodeGenerator::writeFunctionStart(const FunctionDeclaration& f, OutputStream& out) {
+ SpvId result = fFunctionMap[&f];
+ SpvId returnTypeId = this->getType(f.returnType());
+ SpvId functionTypeId = this->getFunctionType(f);
+ this->writeInstruction(SpvOpFunction, returnTypeId, result,
+ SpvFunctionControlMaskNone, functionTypeId, out);
+ std::string mangledName = f.mangledName();
+ this->writeInstruction(SpvOpName,
+ result,
+ std::string_view(mangledName.c_str(), mangledName.size()),
+ fNameBuffer);
+ for (const Variable* parameter : f.parameters()) {
+ if (parameter->type().typeKind() == Type::TypeKind::kSampler &&
+ fProgram.fConfig->fSettings.fSPIRVDawnCompatMode) {
+ auto [texture, sampler] = this->synthesizeTextureAndSampler(*parameter);
+
+ SpvId textureId = this->nextId(nullptr);
+ SpvId samplerId = this->nextId(nullptr);
+ fVariableMap.set(texture, textureId);
+ fVariableMap.set(sampler, samplerId);
+
+ SpvId textureType = this->getFunctionParameterType(texture->type());
+ SpvId samplerType = this->getFunctionParameterType(sampler->type());
+
+ this->writeInstruction(SpvOpFunctionParameter, textureType, textureId, out);
+ this->writeInstruction(SpvOpFunctionParameter, samplerType, samplerId, out);
+ } else {
+ SpvId id = this->nextId(nullptr);
+ fVariableMap.set(parameter, id);
+
+ SpvId type = this->getFunctionParameterType(parameter->type());
+ this->writeInstruction(SpvOpFunctionParameter, type, id, out);
+ }
+ }
+ return result;
+}
+
+SpvId SPIRVCodeGenerator::writeFunction(const FunctionDefinition& f, OutputStream& out) {
+ ConditionalOpCounts conditionalOps = this->getConditionalOpCounts();
+
+ fVariableBuffer.reset();
+ SpvId result = this->writeFunctionStart(f.declaration(), out);
+ fCurrentBlock = 0;
+ this->writeLabel(this->nextId(nullptr), kBranchlessBlock, out);
+ StringStream bodyBuffer;
+ this->writeBlock(f.body()->as<Block>(), bodyBuffer);
+ write_stringstream(fVariableBuffer, out);
+ if (f.declaration().isMain()) {
+ write_stringstream(fGlobalInitializersBuffer, out);
+ }
+ write_stringstream(bodyBuffer, out);
+ if (fCurrentBlock) {
+ if (f.declaration().returnType().isVoid()) {
+ this->writeInstruction(SpvOpReturn, out);
+ } else {
+ this->writeInstruction(SpvOpUnreachable, out);
+ }
+ }
+ this->writeInstruction(SpvOpFunctionEnd, out);
+ this->pruneConditionalOps(conditionalOps);
+ return result;
+}
+
+void SPIRVCodeGenerator::writeLayout(const Layout& layout, SpvId target, Position pos) {
+ bool isPushConstant = (layout.fFlags & Layout::kPushConstant_Flag);
+ if (layout.fLocation >= 0) {
+ this->writeInstruction(SpvOpDecorate, target, SpvDecorationLocation, layout.fLocation,
+ fDecorationBuffer);
+ }
+ if (layout.fBinding >= 0) {
+ if (isPushConstant) {
+ fContext.fErrors->error(pos, "Can't apply 'binding' to push constants");
+ } else {
+ this->writeInstruction(SpvOpDecorate, target, SpvDecorationBinding, layout.fBinding,
+ fDecorationBuffer);
+ }
+ }
+ if (layout.fIndex >= 0) {
+ this->writeInstruction(SpvOpDecorate, target, SpvDecorationIndex, layout.fIndex,
+ fDecorationBuffer);
+ }
+ if (layout.fSet >= 0) {
+ if (isPushConstant) {
+ fContext.fErrors->error(pos, "Can't apply 'set' to push constants");
+ } else {
+ this->writeInstruction(SpvOpDecorate, target, SpvDecorationDescriptorSet, layout.fSet,
+ fDecorationBuffer);
+ }
+ }
+ if (layout.fInputAttachmentIndex >= 0) {
+ this->writeInstruction(SpvOpDecorate, target, SpvDecorationInputAttachmentIndex,
+ layout.fInputAttachmentIndex, fDecorationBuffer);
+ fCapabilities |= (((uint64_t) 1) << SpvCapabilityInputAttachment);
+ }
+ if (layout.fBuiltin >= 0 && layout.fBuiltin != SK_FRAGCOLOR_BUILTIN) {
+ this->writeInstruction(SpvOpDecorate, target, SpvDecorationBuiltIn, layout.fBuiltin,
+ fDecorationBuffer);
+ }
+}
+
+void SPIRVCodeGenerator::writeFieldLayout(const Layout& layout, SpvId target, int member) {
+ // 'binding' and 'set' can not be applied to struct members
+ SkASSERT(layout.fBinding == -1);
+ SkASSERT(layout.fSet == -1);
+ if (layout.fLocation >= 0) {
+ this->writeInstruction(SpvOpMemberDecorate, target, member, SpvDecorationLocation,
+ layout.fLocation, fDecorationBuffer);
+ }
+ if (layout.fIndex >= 0) {
+ this->writeInstruction(SpvOpMemberDecorate, target, member, SpvDecorationIndex,
+ layout.fIndex, fDecorationBuffer);
+ }
+ if (layout.fInputAttachmentIndex >= 0) {
+ this->writeInstruction(SpvOpDecorate, target, member, SpvDecorationInputAttachmentIndex,
+ layout.fInputAttachmentIndex, fDecorationBuffer);
+ }
+ if (layout.fBuiltin >= 0) {
+ this->writeInstruction(SpvOpMemberDecorate, target, member, SpvDecorationBuiltIn,
+ layout.fBuiltin, fDecorationBuffer);
+ }
+}
+
+MemoryLayout SPIRVCodeGenerator::memoryLayoutForStorageClass(SpvStorageClass_ storageClass) {
+ return storageClass == SpvStorageClassPushConstant ? MemoryLayout(MemoryLayout::Standard::k430)
+ : fDefaultLayout;
+}
+
+MemoryLayout SPIRVCodeGenerator::memoryLayoutForVariable(const Variable& v) const {
+ bool pushConstant = ((v.modifiers().fLayout.fFlags & Layout::kPushConstant_Flag) != 0);
+ return pushConstant ? MemoryLayout(MemoryLayout::Standard::k430) : fDefaultLayout;
+}
+
+SpvId SPIRVCodeGenerator::writeInterfaceBlock(const InterfaceBlock& intf, bool appendRTFlip) {
+ MemoryLayout memoryLayout = this->memoryLayoutForVariable(*intf.var());
+ SpvId result = this->nextId(nullptr);
+ const Variable& intfVar = *intf.var();
+ const Type& type = intfVar.type();
+ if (!memoryLayout.isSupported(type)) {
+ fContext.fErrors->error(type.fPosition, "type '" + type.displayName() +
+ "' is not permitted here");
+ return this->nextId(nullptr);
+ }
+ SpvStorageClass_ storageClass =
+ get_storage_class_for_global_variable(intfVar, SpvStorageClassFunction);
+ if (fProgram.fInputs.fUseFlipRTUniform && appendRTFlip && type.isStruct()) {
+ // We can only have one interface block (because we use push_constant and that is limited
+ // to one per program), so we need to append rtflip to this one rather than synthesize an
+ // entirely new block when the variable is referenced. And we can't modify the existing
+ // block, so we instead create a modified copy of it and write that.
+ std::vector<Type::Field> fields = type.fields();
+ fields.emplace_back(Position(),
+ Modifiers(Layout(/*flags=*/0,
+ /*location=*/-1,
+ fProgram.fConfig->fSettings.fRTFlipOffset,
+ /*binding=*/-1,
+ /*index=*/-1,
+ /*set=*/-1,
+ /*builtin=*/-1,
+ /*inputAttachmentIndex=*/-1),
+ /*flags=*/0),
+ SKSL_RTFLIP_NAME,
+ fContext.fTypes.fFloat2.get());
+ {
+ AutoAttachPoolToThread attach(fProgram.fPool.get());
+ const Type* rtFlipStructType = fProgram.fSymbols->takeOwnershipOfSymbol(
+ Type::MakeStructType(fContext,
+ type.fPosition,
+ type.name(),
+ std::move(fields),
+ /*interfaceBlock=*/true));
+ InterfaceBlockVariable* modifiedVar = fProgram.fSymbols->takeOwnershipOfSymbol(
+ std::make_unique<InterfaceBlockVariable>(intfVar.fPosition,
+ intfVar.modifiersPosition(),
+ &intfVar.modifiers(),
+ intfVar.name(),
+ rtFlipStructType,
+ intfVar.isBuiltin(),
+ intfVar.storage()));
+ fSPIRVBonusVariables.add(modifiedVar);
+ InterfaceBlock modifiedCopy(intf.fPosition, modifiedVar, intf.typeOwner());
+ result = this->writeInterfaceBlock(modifiedCopy, /*appendRTFlip=*/false);
+ fProgram.fSymbols->add(std::make_unique<Field>(
+ Position(), modifiedVar, rtFlipStructType->fields().size() - 1));
+ }
+ fVariableMap.set(&intfVar, result);
+ fWroteRTFlip = true;
+ return result;
+ }
+ const Modifiers& intfModifiers = intfVar.modifiers();
+ SpvId typeId = this->getType(type, memoryLayout);
+ if (intfModifiers.fLayout.fBuiltin == -1) {
+ // Note: In SPIR-V 1.3, a storage buffer can be declared with the "StorageBuffer"
+ // storage class and the "Block" decoration and the <1.3 approach we use here ("Uniform"
+ // storage class and the "BufferBlock" decoration) is deprecated. Since we target SPIR-V
+ // 1.0, we have to use the deprecated approach which is well supported in Vulkan and
+ // addresses SkSL use cases (notably SkSL currently doesn't support pointer features that
+ // would benefit from SPV_KHR_variable_pointers capabilities).
+ bool isStorageBuffer = intfModifiers.fFlags & Modifiers::kBuffer_Flag;
+ this->writeInstruction(SpvOpDecorate,
+ typeId,
+ isStorageBuffer ? SpvDecorationBufferBlock : SpvDecorationBlock,
+ fDecorationBuffer);
+ }
+ SpvId ptrType = this->nextId(nullptr);
+ this->writeInstruction(SpvOpTypePointer, ptrType, storageClass, typeId, fConstantBuffer);
+ this->writeInstruction(SpvOpVariable, ptrType, result, storageClass, fConstantBuffer);
+ Layout layout = intfModifiers.fLayout;
+ if (storageClass == SpvStorageClassUniform && layout.fSet < 0) {
+ layout.fSet = fProgram.fConfig->fSettings.fDefaultUniformSet;
+ }
+ this->writeLayout(layout, result, intfVar.fPosition);
+ fVariableMap.set(&intfVar, result);
+ return result;
+}
+
+bool SPIRVCodeGenerator::isDead(const Variable& var) const {
+ // During SPIR-V code generation, we synthesize some extra bonus variables that don't actually
+ // exist in the Program at all and aren't tracked by the ProgramUsage. They aren't dead, though.
+ if (fSPIRVBonusVariables.contains(&var)) {
+ return false;
+ }
+ ProgramUsage::VariableCounts counts = fProgram.usage()->get(var);
+ if (counts.fRead || counts.fWrite) {
+ return false;
+ }
+ // It's not entirely clear what the rules are for eliding interface variables. Generally, it
+ // causes problems to elide them, even when they're dead.
+ return !(var.modifiers().fFlags &
+ (Modifiers::kIn_Flag | Modifiers::kOut_Flag | Modifiers::kUniform_Flag));
+}
+
+// This function determines whether to skip an OpVariable (of pointer type) declaration for
+// compile-time constant scalars and vectors which we turn into OpConstant/OpConstantComposite and
+// always reference by value.
+//
+// Accessing a matrix or array member with a dynamic index requires the use of OpAccessChain which
+// requires a base operand of pointer type. However, a vector can always be accessed by value using
+// OpVectorExtractDynamic (see writeIndexExpression).
+//
+// This is why we always emit an OpVariable for all non-scalar and non-vector types in case they get
+// accessed via a dynamic index.
+static bool is_vardecl_compile_time_constant(const VarDeclaration& varDecl) {
+ return varDecl.var()->modifiers().fFlags & Modifiers::kConst_Flag &&
+ (varDecl.var()->type().isScalar() || varDecl.var()->type().isVector()) &&
+ (ConstantFolder::GetConstantValueOrNullForVariable(*varDecl.value()) ||
+ Analysis::IsCompileTimeConstant(*varDecl.value()));
+}
+
+bool SPIRVCodeGenerator::writeGlobalVarDeclaration(ProgramKind kind,
+ const VarDeclaration& varDecl) {
+ const Variable* var = varDecl.var();
+ const bool inDawnMode = fProgram.fConfig->fSettings.fSPIRVDawnCompatMode;
+ const int backendFlags = var->modifiers().fLayout.fFlags & Layout::kAllBackendFlagsMask;
+ const int permittedBackendFlags = Layout::kSPIRV_Flag | (inDawnMode ? Layout::kWGSL_Flag : 0);
+ if (backendFlags & ~permittedBackendFlags) {
+ fContext.fErrors->error(var->fPosition, "incompatible backend flag in SPIR-V codegen");
+ return false;
+ }
+
+ // If this global variable is a compile-time constant then we'll emit OpConstant or
+ // OpConstantComposite later when the variable is referenced. Avoid declaring an OpVariable now.
+ if (is_vardecl_compile_time_constant(varDecl)) {
+ return true;
+ }
+
+ SpvStorageClass_ storageClass =
+ get_storage_class_for_global_variable(*var, SpvStorageClassPrivate);
+ if (storageClass == SpvStorageClassUniform) {
+ // Top-level uniforms are emitted in writeUniformBuffer.
+ fTopLevelUniforms.push_back(&varDecl);
+ return true;
+ }
+
+ if (this->isDead(*var)) {
+ return true;
+ }
+
+ if (var->type().typeKind() == Type::TypeKind::kSampler && inDawnMode) {
+ if (var->modifiers().fLayout.fTexture == -1 || var->modifiers().fLayout.fSampler == -1 ||
+ !(var->modifiers().fLayout.fFlags & Layout::kWGSL_Flag)) {
+ fContext.fErrors->error(var->fPosition,
+ "SPIR-V dawn compatibility mode requires an explicit texture "
+ "and sampler index");
+ return false;
+ }
+ SkASSERT(storageClass == SpvStorageClassUniformConstant);
+
+ auto [texture, sampler] = this->synthesizeTextureAndSampler(*var);
+ this->writeGlobalVar(kind, storageClass, *texture);
+ this->writeGlobalVar(kind, storageClass, *sampler);
+
+ return true;
+ }
+
+ SpvId id = this->writeGlobalVar(kind, storageClass, *var);
+ if (id != NA && varDecl.value()) {
+ SkASSERT(!fCurrentBlock);
+ fCurrentBlock = NA;
+ SpvId value = this->writeExpression(*varDecl.value(), fGlobalInitializersBuffer);
+ this->writeOpStore(storageClass, id, value, fGlobalInitializersBuffer);
+ fCurrentBlock = 0;
+ }
+ return true;
+}
+
+SpvId SPIRVCodeGenerator::writeGlobalVar(ProgramKind kind,
+ SpvStorageClass_ storageClass,
+ const Variable& var) {
+ if (var.modifiers().fLayout.fBuiltin == SK_FRAGCOLOR_BUILTIN &&
+ !ProgramConfig::IsFragment(kind)) {
+ SkASSERT(!fProgram.fConfig->fSettings.fFragColorIsInOut);
+ return NA;
+ }
+
+ // Add this global to the variable map.
+ const Type& type = var.type();
+ SpvId id = this->nextId(&type);
+ fVariableMap.set(&var, id);
+
+ Layout layout = var.modifiers().fLayout;
+ if (layout.fSet < 0 && storageClass == SpvStorageClassUniformConstant) {
+ layout.fSet = fProgram.fConfig->fSettings.fDefaultUniformSet;
+ }
+
+ SpvId typeId = this->getPointerType(type, storageClass);
+ this->writeInstruction(SpvOpVariable, typeId, id, storageClass, fConstantBuffer);
+ this->writeInstruction(SpvOpName, id, var.name(), fNameBuffer);
+ this->writeLayout(layout, id, var.fPosition);
+ if (var.modifiers().fFlags & Modifiers::kFlat_Flag) {
+ this->writeInstruction(SpvOpDecorate, id, SpvDecorationFlat, fDecorationBuffer);
+ }
+ if (var.modifiers().fFlags & Modifiers::kNoPerspective_Flag) {
+ this->writeInstruction(SpvOpDecorate, id, SpvDecorationNoPerspective,
+ fDecorationBuffer);
+ }
+
+ return id;
+}
+
+void SPIRVCodeGenerator::writeVarDeclaration(const VarDeclaration& varDecl, OutputStream& out) {
+ // If this variable is a compile-time constant then we'll emit OpConstant or
+ // OpConstantComposite later when the variable is referenced. Avoid declaring an OpVariable now.
+ if (is_vardecl_compile_time_constant(varDecl)) {
+ return;
+ }
+
+ const Variable* var = varDecl.var();
+ SpvId id = this->nextId(&var->type());
+ fVariableMap.set(var, id);
+ SpvId type = this->getPointerType(var->type(), SpvStorageClassFunction);
+ this->writeInstruction(SpvOpVariable, type, id, SpvStorageClassFunction, fVariableBuffer);
+ this->writeInstruction(SpvOpName, id, var->name(), fNameBuffer);
+ if (varDecl.value()) {
+ SpvId value = this->writeExpression(*varDecl.value(), out);
+ this->writeOpStore(SpvStorageClassFunction, id, value, out);
+ }
+}
+
+void SPIRVCodeGenerator::writeStatement(const Statement& s, OutputStream& out) {
+ switch (s.kind()) {
+ case Statement::Kind::kNop:
+ break;
+ case Statement::Kind::kBlock:
+ this->writeBlock(s.as<Block>(), out);
+ break;
+ case Statement::Kind::kExpression:
+ this->writeExpression(*s.as<ExpressionStatement>().expression(), out);
+ break;
+ case Statement::Kind::kReturn:
+ this->writeReturnStatement(s.as<ReturnStatement>(), out);
+ break;
+ case Statement::Kind::kVarDeclaration:
+ this->writeVarDeclaration(s.as<VarDeclaration>(), out);
+ break;
+ case Statement::Kind::kIf:
+ this->writeIfStatement(s.as<IfStatement>(), out);
+ break;
+ case Statement::Kind::kFor:
+ this->writeForStatement(s.as<ForStatement>(), out);
+ break;
+ case Statement::Kind::kDo:
+ this->writeDoStatement(s.as<DoStatement>(), out);
+ break;
+ case Statement::Kind::kSwitch:
+ this->writeSwitchStatement(s.as<SwitchStatement>(), out);
+ break;
+ case Statement::Kind::kBreak:
+ this->writeInstruction(SpvOpBranch, fBreakTarget.back(), out);
+ break;
+ case Statement::Kind::kContinue:
+ this->writeInstruction(SpvOpBranch, fContinueTarget.back(), out);
+ break;
+ case Statement::Kind::kDiscard:
+ this->writeInstruction(SpvOpKill, out);
+ break;
+ default:
+ SkDEBUGFAILF("unsupported statement: %s", s.description().c_str());
+ break;
+ }
+}
+
+void SPIRVCodeGenerator::writeBlock(const Block& b, OutputStream& out) {
+ for (const std::unique_ptr<Statement>& stmt : b.children()) {
+ this->writeStatement(*stmt, out);
+ }
+}
+
+SPIRVCodeGenerator::ConditionalOpCounts SPIRVCodeGenerator::getConditionalOpCounts() {
+ return {fReachableOps.size(), fStoreOps.size()};
+}
+
+void SPIRVCodeGenerator::pruneConditionalOps(ConditionalOpCounts ops) {
+ // Remove ops which are no longer reachable.
+ while (fReachableOps.size() > ops.numReachableOps) {
+ SpvId prunableSpvId = fReachableOps.back();
+ const Instruction* prunableOp = fSpvIdCache.find(prunableSpvId);
+
+ if (prunableOp) {
+ fOpCache.remove(*prunableOp);
+ fSpvIdCache.remove(prunableSpvId);
+ } else {
+ SkDEBUGFAIL("reachable-op list contains unrecognized SpvId");
+ }
+
+ fReachableOps.pop_back();
+ }
+
+ // Remove any cached stores that occurred during the conditional block.
+ while (fStoreOps.size() > ops.numStoreOps) {
+ if (fStoreCache.find(fStoreOps.back())) {
+ fStoreCache.remove(fStoreOps.back());
+ }
+ fStoreOps.pop_back();
+ }
+}
+
+void SPIRVCodeGenerator::writeIfStatement(const IfStatement& stmt, OutputStream& out) {
+ SpvId test = this->writeExpression(*stmt.test(), out);
+ SpvId ifTrue = this->nextId(nullptr);
+ SpvId ifFalse = this->nextId(nullptr);
+
+ ConditionalOpCounts conditionalOps = this->getConditionalOpCounts();
+
+ if (stmt.ifFalse()) {
+ SpvId end = this->nextId(nullptr);
+ this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out);
+ this->writeInstruction(SpvOpBranchConditional, test, ifTrue, ifFalse, out);
+ this->writeLabel(ifTrue, kBranchIsOnPreviousLine, out);
+ this->writeStatement(*stmt.ifTrue(), out);
+ if (fCurrentBlock) {
+ this->writeInstruction(SpvOpBranch, end, out);
+ }
+ this->writeLabel(ifFalse, kBranchIsAbove, conditionalOps, out);
+ this->writeStatement(*stmt.ifFalse(), out);
+ if (fCurrentBlock) {
+ this->writeInstruction(SpvOpBranch, end, out);
+ }
+ this->writeLabel(end, kBranchIsAbove, conditionalOps, out);
+ } else {
+ this->writeInstruction(SpvOpSelectionMerge, ifFalse, SpvSelectionControlMaskNone, out);
+ this->writeInstruction(SpvOpBranchConditional, test, ifTrue, ifFalse, out);
+ this->writeLabel(ifTrue, kBranchIsOnPreviousLine, out);
+ this->writeStatement(*stmt.ifTrue(), out);
+ if (fCurrentBlock) {
+ this->writeInstruction(SpvOpBranch, ifFalse, out);
+ }
+ this->writeLabel(ifFalse, kBranchIsAbove, conditionalOps, out);
+ }
+}
+
+void SPIRVCodeGenerator::writeForStatement(const ForStatement& f, OutputStream& out) {
+ if (f.initializer()) {
+ this->writeStatement(*f.initializer(), out);
+ }
+
+ ConditionalOpCounts conditionalOps = this->getConditionalOpCounts();
+
+ // The store cache isn't trustworthy in the presence of branches; store caching only makes sense
+ // in the context of linear straight-line execution. If we wanted to be more clever, we could
+ // only invalidate store cache entries for variables affected by the loop body, but for now we
+ // simply clear the entire cache whenever branching occurs.
+ SpvId header = this->nextId(nullptr);
+ SpvId start = this->nextId(nullptr);
+ SpvId body = this->nextId(nullptr);
+ SpvId next = this->nextId(nullptr);
+ fContinueTarget.push_back(next);
+ SpvId end = this->nextId(nullptr);
+ fBreakTarget.push_back(end);
+ this->writeInstruction(SpvOpBranch, header, out);
+ this->writeLabel(header, kBranchIsBelow, conditionalOps, out);
+ this->writeInstruction(SpvOpLoopMerge, end, next, SpvLoopControlMaskNone, out);
+ this->writeInstruction(SpvOpBranch, start, out);
+ this->writeLabel(start, kBranchIsOnPreviousLine, out);
+ if (f.test()) {
+ SpvId test = this->writeExpression(*f.test(), out);
+ this->writeInstruction(SpvOpBranchConditional, test, body, end, out);
+ } else {
+ this->writeInstruction(SpvOpBranch, body, out);
+ }
+ this->writeLabel(body, kBranchIsOnPreviousLine, out);
+ this->writeStatement(*f.statement(), out);
+ if (fCurrentBlock) {
+ this->writeInstruction(SpvOpBranch, next, out);
+ }
+ this->writeLabel(next, kBranchIsAbove, conditionalOps, out);
+ if (f.next()) {
+ this->writeExpression(*f.next(), out);
+ }
+ this->writeInstruction(SpvOpBranch, header, out);
+ this->writeLabel(end, kBranchIsAbove, conditionalOps, out);
+ fBreakTarget.pop_back();
+ fContinueTarget.pop_back();
+}
+
+void SPIRVCodeGenerator::writeDoStatement(const DoStatement& d, OutputStream& out) {
+ ConditionalOpCounts conditionalOps = this->getConditionalOpCounts();
+
+ // The store cache isn't trustworthy in the presence of branches; store caching only makes sense
+ // in the context of linear straight-line execution. If we wanted to be more clever, we could
+ // only invalidate store cache entries for variables affected by the loop body, but for now we
+ // simply clear the entire cache whenever branching occurs.
+ SpvId header = this->nextId(nullptr);
+ SpvId start = this->nextId(nullptr);
+ SpvId next = this->nextId(nullptr);
+ SpvId continueTarget = this->nextId(nullptr);
+ fContinueTarget.push_back(continueTarget);
+ SpvId end = this->nextId(nullptr);
+ fBreakTarget.push_back(end);
+ this->writeInstruction(SpvOpBranch, header, out);
+ this->writeLabel(header, kBranchIsBelow, conditionalOps, out);
+ this->writeInstruction(SpvOpLoopMerge, end, continueTarget, SpvLoopControlMaskNone, out);
+ this->writeInstruction(SpvOpBranch, start, out);
+ this->writeLabel(start, kBranchIsOnPreviousLine, out);
+ this->writeStatement(*d.statement(), out);
+ if (fCurrentBlock) {
+ this->writeInstruction(SpvOpBranch, next, out);
+ this->writeLabel(next, kBranchIsOnPreviousLine, out);
+ this->writeInstruction(SpvOpBranch, continueTarget, out);
+ }
+ this->writeLabel(continueTarget, kBranchIsAbove, conditionalOps, out);
+ SpvId test = this->writeExpression(*d.test(), out);
+ this->writeInstruction(SpvOpBranchConditional, test, header, end, out);
+ this->writeLabel(end, kBranchIsAbove, conditionalOps, out);
+ fBreakTarget.pop_back();
+ fContinueTarget.pop_back();
+}
+
+void SPIRVCodeGenerator::writeSwitchStatement(const SwitchStatement& s, OutputStream& out) {
+ SpvId value = this->writeExpression(*s.value(), out);
+
+ ConditionalOpCounts conditionalOps = this->getConditionalOpCounts();
+
+ // The store cache isn't trustworthy in the presence of branches; store caching only makes sense
+ // in the context of linear straight-line execution. If we wanted to be more clever, we could
+ // only invalidate store cache entries for variables affected by the switch body, but for now we
+ // simply clear the entire cache whenever branching occurs.
+ SkTArray<SpvId> labels;
+ SpvId end = this->nextId(nullptr);
+ SpvId defaultLabel = end;
+ fBreakTarget.push_back(end);
+ int size = 3;
+ const StatementArray& cases = s.cases();
+ for (const std::unique_ptr<Statement>& stmt : cases) {
+ const SwitchCase& c = stmt->as<SwitchCase>();
+ SpvId label = this->nextId(nullptr);
+ labels.push_back(label);
+ if (!c.isDefault()) {
+ size += 2;
+ } else {
+ defaultLabel = label;
+ }
+ }
+
+ // We should have exactly one label for each case.
+ SkASSERT(labels.size() == cases.size());
+
+ // Collapse adjacent switch-cases into one; that is, reduce `case 1: case 2: case 3:` into a
+ // single OpLabel. The Tint SPIR-V reader does not support switch-case fallthrough, but it
+ // does support multiple switch-cases branching to the same label.
+ SkBitSet caseIsCollapsed(cases.size());
+ for (int index = cases.size() - 2; index >= 0; index--) {
+ if (cases[index]->as<SwitchCase>().statement()->isEmpty()) {
+ caseIsCollapsed.set(index);
+ labels[index] = labels[index + 1];
+ }
+ }
+
+ labels.push_back(end);
+
+ this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out);
+ this->writeOpCode(SpvOpSwitch, size, out);
+ this->writeWord(value, out);
+ this->writeWord(defaultLabel, out);
+ for (int i = 0; i < cases.size(); ++i) {
+ const SwitchCase& c = cases[i]->as<SwitchCase>();
+ if (c.isDefault()) {
+ continue;
+ }
+ this->writeWord(c.value(), out);
+ this->writeWord(labels[i], out);
+ }
+ for (int i = 0; i < cases.size(); ++i) {
+ if (caseIsCollapsed.test(i)) {
+ continue;
+ }
+ const SwitchCase& c = cases[i]->as<SwitchCase>();
+ if (i == 0) {
+ this->writeLabel(labels[i], kBranchIsOnPreviousLine, out);
+ } else {
+ this->writeLabel(labels[i], kBranchIsAbove, conditionalOps, out);
+ }
+ this->writeStatement(*c.statement(), out);
+ if (fCurrentBlock) {
+ this->writeInstruction(SpvOpBranch, labels[i + 1], out);
+ }
+ }
+ this->writeLabel(end, kBranchIsAbove, conditionalOps, out);
+ fBreakTarget.pop_back();
+}
+
+void SPIRVCodeGenerator::writeReturnStatement(const ReturnStatement& r, OutputStream& out) {
+ if (r.expression()) {
+ this->writeInstruction(SpvOpReturnValue, this->writeExpression(*r.expression(), out),
+ out);
+ } else {
+ this->writeInstruction(SpvOpReturn, out);
+ }
+}
+
+// Given any function, returns the top-level symbol table (OUTSIDE of the function's scope).
+static std::shared_ptr<SymbolTable> get_top_level_symbol_table(const FunctionDeclaration& anyFunc) {
+ return anyFunc.definition()->body()->as<Block>().symbolTable()->fParent;
+}
+
+SPIRVCodeGenerator::EntrypointAdapter SPIRVCodeGenerator::writeEntrypointAdapter(
+ const FunctionDeclaration& main) {
+ // Our goal is to synthesize a tiny helper function which looks like this:
+ // void _entrypoint() { sk_FragColor = main(); }
+
+ // Fish a symbol table out of main().
+ std::shared_ptr<SymbolTable> symbolTable = get_top_level_symbol_table(main);
+
+ // Get `sk_FragColor` as a writable reference.
+ const Symbol* skFragColorSymbol = symbolTable->find("sk_FragColor");
+ SkASSERT(skFragColorSymbol);
+ const Variable& skFragColorVar = skFragColorSymbol->as<Variable>();
+ auto skFragColorRef = std::make_unique<VariableReference>(Position(), &skFragColorVar,
+ VariableReference::RefKind::kWrite);
+ // Synthesize a call to the `main()` function.
+ if (!main.returnType().matches(skFragColorRef->type())) {
+ fContext.fErrors->error(main.fPosition, "SPIR-V does not support returning '" +
+ main.returnType().description() + "' from main()");
+ return {};
+ }
+ ExpressionArray args;
+ if (main.parameters().size() == 1) {
+ if (!main.parameters()[0]->type().matches(*fContext.fTypes.fFloat2)) {
+ fContext.fErrors->error(main.fPosition,
+ "SPIR-V does not support parameter of type '" +
+ main.parameters()[0]->type().description() + "' to main()");
+ return {};
+ }
+ args.push_back(dsl::Float2(0).release());
+ }
+ auto callMainFn = std::make_unique<FunctionCall>(Position(), &main.returnType(), &main,
+ std::move(args));
+
+ // Synthesize `skFragColor = main()` as a BinaryExpression.
+ auto assignmentStmt = std::make_unique<ExpressionStatement>(std::make_unique<BinaryExpression>(
+ Position(),
+ std::move(skFragColorRef),
+ Operator::Kind::EQ,
+ std::move(callMainFn),
+ &main.returnType()));
+
+ // Function bodies are always wrapped in a Block.
+ StatementArray entrypointStmts;
+ entrypointStmts.push_back(std::move(assignmentStmt));
+ auto entrypointBlock = Block::Make(Position(), std::move(entrypointStmts),
+ Block::Kind::kBracedScope, symbolTable);
+ // Declare an entrypoint function.
+ EntrypointAdapter adapter;
+ adapter.fLayout = {};
+ adapter.fModifiers = Modifiers{adapter.fLayout, Modifiers::kNo_Flag};
+ adapter.entrypointDecl =
+ std::make_unique<FunctionDeclaration>(Position(),
+ &adapter.fModifiers,
+ "_entrypoint",
+ /*parameters=*/std::vector<Variable*>{},
+ /*returnType=*/fContext.fTypes.fVoid.get(),
+ /*builtin=*/false);
+ // Define it.
+ adapter.entrypointDef = FunctionDefinition::Convert(fContext,
+ Position(),
+ *adapter.entrypointDecl,
+ std::move(entrypointBlock),
+ /*builtin=*/false);
+
+ adapter.entrypointDecl->setDefinition(adapter.entrypointDef.get());
+ return adapter;
+}
+
+void SPIRVCodeGenerator::writeUniformBuffer(std::shared_ptr<SymbolTable> topLevelSymbolTable) {
+ SkASSERT(!fTopLevelUniforms.empty());
+ static constexpr char kUniformBufferName[] = "_UniformBuffer";
+
+ // Convert the list of top-level uniforms into a matching struct named _UniformBuffer, and build
+ // a lookup table of variables to UniformBuffer field indices.
+ std::vector<Type::Field> fields;
+ fields.reserve(fTopLevelUniforms.size());
+ for (const VarDeclaration* topLevelUniform : fTopLevelUniforms) {
+ const Variable* var = topLevelUniform->var();
+ fTopLevelUniformMap.set(var, (int)fields.size());
+ Modifiers modifiers = var->modifiers();
+ modifiers.fFlags &= ~Modifiers::kUniform_Flag;
+ fields.emplace_back(var->fPosition, modifiers, var->name(), &var->type());
+ }
+ fUniformBuffer.fStruct = Type::MakeStructType(fContext,
+ Position(),
+ kUniformBufferName,
+ std::move(fields),
+ /*interfaceBlock=*/true);
+
+ // Create a global variable to contain this struct.
+ Layout layout;
+ layout.fBinding = fProgram.fConfig->fSettings.fDefaultUniformBinding;
+ layout.fSet = fProgram.fConfig->fSettings.fDefaultUniformSet;
+ Modifiers modifiers{layout, Modifiers::kUniform_Flag};
+
+ fUniformBuffer.fInnerVariable = std::make_unique<InterfaceBlockVariable>(
+ /*pos=*/Position(), /*modifiersPosition=*/Position(),
+ fContext.fModifiersPool->add(modifiers), kUniformBufferName,
+ fUniformBuffer.fStruct.get(), /*builtin=*/false, Variable::Storage::kGlobal);
+
+ // Create an interface block object for this global variable.
+ fUniformBuffer.fInterfaceBlock =
+ std::make_unique<InterfaceBlock>(Position(),
+ fUniformBuffer.fInnerVariable.get(),
+ topLevelSymbolTable);
+
+ // Generate an interface block and hold onto its ID.
+ fUniformBufferId = this->writeInterfaceBlock(*fUniformBuffer.fInterfaceBlock);
+}
+
+void SPIRVCodeGenerator::addRTFlipUniform(Position pos) {
+ SkASSERT(!fProgram.fConfig->fSettings.fForceNoRTFlip);
+
+ if (fWroteRTFlip) {
+ return;
+ }
+ // Flip variable hasn't been written yet. This means we don't have an existing
+ // interface block, so we're free to just synthesize one.
+ fWroteRTFlip = true;
+ std::vector<Type::Field> fields;
+ if (fProgram.fConfig->fSettings.fRTFlipOffset < 0) {
+ fContext.fErrors->error(pos, "RTFlipOffset is negative");
+ }
+ fields.emplace_back(pos,
+ Modifiers(Layout(/*flags=*/0,
+ /*location=*/-1,
+ fProgram.fConfig->fSettings.fRTFlipOffset,
+ /*binding=*/-1,
+ /*index=*/-1,
+ /*set=*/-1,
+ /*builtin=*/-1,
+ /*inputAttachmentIndex=*/-1),
+ /*flags=*/0),
+ SKSL_RTFLIP_NAME,
+ fContext.fTypes.fFloat2.get());
+ std::string_view name = "sksl_synthetic_uniforms";
+ const Type* intfStruct = fSynthetics.takeOwnershipOfSymbol(
+ Type::MakeStructType(fContext, Position(), name, fields, /*interfaceBlock=*/true));
+ bool usePushConstants = fProgram.fConfig->fSettings.fUsePushConstants;
+ int binding = -1, set = -1;
+ if (!usePushConstants) {
+ binding = fProgram.fConfig->fSettings.fRTFlipBinding;
+ if (binding == -1) {
+ fContext.fErrors->error(pos, "layout(binding=...) is required in SPIR-V");
+ }
+ set = fProgram.fConfig->fSettings.fRTFlipSet;
+ if (set == -1) {
+ fContext.fErrors->error(pos, "layout(set=...) is required in SPIR-V");
+ }
+ }
+ int flags = usePushConstants ? Layout::Flag::kPushConstant_Flag : 0;
+ const Modifiers* modsPtr;
+ {
+ AutoAttachPoolToThread attach(fProgram.fPool.get());
+ Modifiers modifiers(Layout(flags,
+ /*location=*/-1,
+ /*offset=*/-1,
+ binding,
+ /*index=*/-1,
+ set,
+ /*builtin=*/-1,
+ /*inputAttachmentIndex=*/-1),
+ Modifiers::kUniform_Flag);
+ modsPtr = fContext.fModifiersPool->add(modifiers);
+ }
+ InterfaceBlockVariable* intfVar = fSynthetics.takeOwnershipOfSymbol(
+ std::make_unique<InterfaceBlockVariable>(/*pos=*/Position(),
+ /*modifiersPosition=*/Position(),
+ modsPtr,
+ name,
+ intfStruct,
+ /*builtin=*/false,
+ Variable::Storage::kGlobal));
+ fSPIRVBonusVariables.add(intfVar);
+ {
+ AutoAttachPoolToThread attach(fProgram.fPool.get());
+ fProgram.fSymbols->add(std::make_unique<Field>(Position(), intfVar, /*field=*/0));
+ }
+ InterfaceBlock intf(Position(), intfVar, std::make_shared<SymbolTable>(/*builtin=*/false));
+ this->writeInterfaceBlock(intf, false);
+}
+
+std::tuple<const Variable*, const Variable*> SPIRVCodeGenerator::synthesizeTextureAndSampler(
+ const Variable& combinedSampler) {
+ SkASSERT(fProgram.fConfig->fSettings.fSPIRVDawnCompatMode);
+ SkASSERT(combinedSampler.type().typeKind() == Type::TypeKind::kSampler);
+
+ const Modifiers& modifiers = combinedSampler.modifiers();
+
+ auto data = std::make_unique<SynthesizedTextureSamplerPair>();
+
+ Modifiers texModifiers = modifiers;
+ texModifiers.fLayout.fBinding = modifiers.fLayout.fTexture;
+ data->fTextureName = std::string(combinedSampler.name()) + "_texture";
+ auto texture = std::make_unique<Variable>(/*pos=*/Position(),
+ /*modifierPosition=*/Position(),
+ fContext.fModifiersPool->add(texModifiers),
+ data->fTextureName,
+ &combinedSampler.type().textureType(),
+ /*builtin=*/false,
+ Variable::Storage::kGlobal);
+
+ Modifiers samplerModifiers = modifiers;
+ samplerModifiers.fLayout.fBinding = modifiers.fLayout.fSampler;
+ data->fSamplerName = std::string(combinedSampler.name()) + "_sampler";
+ auto sampler = std::make_unique<Variable>(/*pos=*/Position(),
+ /*modifierPosition=*/Position(),
+ fContext.fModifiersPool->add(samplerModifiers),
+ data->fSamplerName,
+ fContext.fTypes.fSampler.get(),
+ /*builtin=*/false,
+ Variable::Storage::kGlobal);
+
+ const Variable* t = texture.get();
+ const Variable* s = sampler.get();
+ data->fTexture = std::move(texture);
+ data->fSampler = std::move(sampler);
+ fSynthesizedSamplerMap.set(&combinedSampler, std::move(data));
+
+ return {t, s};
+}
+
+void SPIRVCodeGenerator::writeInstructions(const Program& program, OutputStream& out) {
+ fGLSLExtendedInstructions = this->nextId(nullptr);
+ StringStream body;
+ // Assign SpvIds to functions.
+ const FunctionDeclaration* main = nullptr;
+ for (const ProgramElement* e : program.elements()) {
+ if (e->is<FunctionDefinition>()) {
+ const FunctionDefinition& funcDef = e->as<FunctionDefinition>();
+ const FunctionDeclaration& funcDecl = funcDef.declaration();
+ fFunctionMap.set(&funcDecl, this->nextId(nullptr));
+ if (funcDecl.isMain()) {
+ main = &funcDecl;
+ }
+ }
+ }
+ // Make sure we have a main() function.
+ if (!main) {
+ fContext.fErrors->error(Position(), "program does not contain a main() function");
+ return;
+ }
+ // Emit interface blocks.
+ std::set<SpvId> interfaceVars;
+ for (const ProgramElement* e : program.elements()) {
+ if (e->is<InterfaceBlock>()) {
+ const InterfaceBlock& intf = e->as<InterfaceBlock>();
+ SpvId id = this->writeInterfaceBlock(intf);
+
+ const Modifiers& modifiers = intf.var()->modifiers();
+ if ((modifiers.fFlags & (Modifiers::kIn_Flag | Modifiers::kOut_Flag)) &&
+ modifiers.fLayout.fBuiltin == -1 && !this->isDead(*intf.var())) {
+ interfaceVars.insert(id);
+ }
+ }
+ }
+ // Emit global variable declarations.
+ for (const ProgramElement* e : program.elements()) {
+ if (e->is<GlobalVarDeclaration>()) {
+ if (!this->writeGlobalVarDeclaration(program.fConfig->fKind,
+ e->as<GlobalVarDeclaration>().varDeclaration())) {
+ return;
+ }
+ }
+ }
+ // Emit top-level uniforms into a dedicated uniform buffer.
+ if (!fTopLevelUniforms.empty()) {
+ this->writeUniformBuffer(get_top_level_symbol_table(*main));
+ }
+ // If main() returns a half4, synthesize a tiny entrypoint function which invokes the real
+ // main() and stores the result into sk_FragColor.
+ EntrypointAdapter adapter;
+ if (main->returnType().matches(*fContext.fTypes.fHalf4)) {
+ adapter = this->writeEntrypointAdapter(*main);
+ if (adapter.entrypointDecl) {
+ fFunctionMap.set(adapter.entrypointDecl.get(), this->nextId(nullptr));
+ this->writeFunction(*adapter.entrypointDef, body);
+ main = adapter.entrypointDecl.get();
+ }
+ }
+ // Emit all the functions.
+ for (const ProgramElement* e : program.elements()) {
+ if (e->is<FunctionDefinition>()) {
+ this->writeFunction(e->as<FunctionDefinition>(), body);
+ }
+ }
+ // Add global in/out variables to the list of interface variables.
+ for (const auto& [var, spvId] : fVariableMap) {
+ if (var->storage() == Variable::Storage::kGlobal &&
+ (var->modifiers().fFlags & (Modifiers::kIn_Flag | Modifiers::kOut_Flag)) &&
+ !this->isDead(*var)) {
+ interfaceVars.insert(spvId);
+ }
+ }
+ this->writeCapabilities(out);
+ this->writeInstruction(SpvOpExtInstImport, fGLSLExtendedInstructions, "GLSL.std.450", out);
+ this->writeInstruction(SpvOpMemoryModel, SpvAddressingModelLogical, SpvMemoryModelGLSL450, out);
+ this->writeOpCode(SpvOpEntryPoint, (SpvId) (3 + (main->name().length() + 4) / 4) +
+ (int32_t) interfaceVars.size(), out);
+ if (ProgramConfig::IsVertex(program.fConfig->fKind)) {
+ this->writeWord(SpvExecutionModelVertex, out);
+ } else if (ProgramConfig::IsFragment(program.fConfig->fKind)) {
+ this->writeWord(SpvExecutionModelFragment, out);
+ } else {
+ SK_ABORT("cannot write this kind of program to SPIR-V\n");
+ }
+ SpvId entryPoint = fFunctionMap[main];
+ this->writeWord(entryPoint, out);
+ this->writeString(main->name(), out);
+ for (int var : interfaceVars) {
+ this->writeWord(var, out);
+ }
+ if (ProgramConfig::IsFragment(program.fConfig->fKind)) {
+ this->writeInstruction(SpvOpExecutionMode,
+ fFunctionMap[main],
+ SpvExecutionModeOriginUpperLeft,
+ out);
+ }
+ for (const ProgramElement* e : program.elements()) {
+ if (e->is<Extension>()) {
+ this->writeInstruction(SpvOpSourceExtension, e->as<Extension>().name(), out);
+ }
+ }
+
+ write_stringstream(fNameBuffer, out);
+ write_stringstream(fDecorationBuffer, out);
+ write_stringstream(fConstantBuffer, out);
+ write_stringstream(body, out);
+}
+
+bool SPIRVCodeGenerator::generateCode() {
+ SkASSERT(!fContext.fErrors->errorCount());
+ this->writeWord(SpvMagicNumber, *fOut);
+ this->writeWord(SpvVersion, *fOut);
+ this->writeWord(SKSL_MAGIC, *fOut);
+ StringStream buffer;
+ this->writeInstructions(fProgram, buffer);
+ this->writeWord(fIdCount, *fOut);
+ this->writeWord(0, *fOut); // reserved, always zero
+ write_stringstream(buffer, *fOut);
+ return fContext.fErrors->errorCount() == 0;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLSPIRVCodeGenerator.h b/gfx/skia/skia/src/sksl/codegen/SkSLSPIRVCodeGenerator.h
new file mode 100644
index 0000000000..59fca83ddb
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/codegen/SkSLSPIRVCodeGenerator.h
@@ -0,0 +1,601 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_SPIRVCODEGENERATOR
+#define SKSL_SPIRVCODEGENERATOR
+
+#include "include/core/SkSpan.h"
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLLayout.h"
+#include "include/private/SkSLModifiers.h"
+#include "include/private/base/SkTArray.h"
+#include "src/core/SkTHash.h"
+#include "src/sksl/SkSLMemoryLayout.h"
+#include "src/sksl/SkSLStringStream.h"
+#include "src/sksl/codegen/SkSLCodeGenerator.h"
+#include "src/sksl/ir/SkSLFunctionDeclaration.h"
+#include "src/sksl/ir/SkSLFunctionDefinition.h"
+#include "src/sksl/ir/SkSLInterfaceBlock.h"
+#include "src/sksl/ir/SkSLSymbolTable.h"
+#include "src/sksl/ir/SkSLType.h"
+#include "src/sksl/ir/SkSLVariable.h"
+#include "src/sksl/spirv.h"
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <tuple>
+#include <vector>
+
+namespace SkSL {
+
+class AnyConstructor;
+class BinaryExpression;
+class Block;
+class ConstructorCompound;
+class ConstructorCompoundCast;
+class ConstructorDiagonalMatrix;
+class ConstructorMatrixResize;
+class ConstructorScalarCast;
+class ConstructorSplat;
+class Context;
+class DoStatement;
+class Expression;
+class FieldAccess;
+class ForStatement;
+class FunctionCall;
+class IfStatement;
+class IndexExpression;
+class Literal;
+class Operator;
+class OutputStream;
+class Position;
+class PostfixExpression;
+class PrefixExpression;
+class ProgramElement;
+class ReturnStatement;
+class Statement;
+class SwitchStatement;
+class Swizzle;
+class TernaryExpression;
+class VarDeclaration;
+class VariableReference;
+enum class ProgramKind : int8_t;
+enum IntrinsicKind : int8_t;
+struct Program;
+
+/**
+ * Converts a Program into a SPIR-V binary.
+ */
+class SPIRVCodeGenerator : public CodeGenerator {
+public:
+ // We reserve an impossible SpvId as a sentinel. (NA meaning none, n/a, etc.)
+ static constexpr SpvId NA = (SpvId)-1;
+
+ class LValue {
+ public:
+ virtual ~LValue() {}
+
+ // returns a pointer to the lvalue, if possible. If the lvalue cannot be directly referenced
+ // by a pointer (e.g. vector swizzles), returns NA.
+ virtual SpvId getPointer() { return NA; }
+
+ // Returns true if a valid pointer returned by getPointer represents a memory object
+ // (see https://github.com/KhronosGroup/SPIRV-Tools/issues/2892). Has no meaning if
+ // getPointer() returns NA.
+ virtual bool isMemoryObjectPointer() const { return true; }
+
+ // Applies a swizzle to the components of the LValue, if possible. This is used to create
+ // LValues that are swizzes-of-swizzles. Non-swizzle LValues can just return false.
+ virtual bool applySwizzle(const ComponentArray& components, const Type& newType) {
+ return false;
+ }
+
+ virtual SpvId load(OutputStream& out) = 0;
+
+ virtual void store(SpvId value, OutputStream& out) = 0;
+ };
+
+ SPIRVCodeGenerator(const Context* context, const Program* program, OutputStream* out)
+ : INHERITED(context, program, out)
+ , fDefaultLayout(MemoryLayout::Standard::k140)
+ , fCapabilities(0)
+ , fIdCount(1)
+ , fCurrentBlock(0)
+ , fSynthetics(/*builtin=*/true) {}
+
+ bool generateCode() override;
+
+private:
+ enum IntrinsicOpcodeKind {
+ kGLSL_STD_450_IntrinsicOpcodeKind,
+ kSPIRV_IntrinsicOpcodeKind,
+ kSpecial_IntrinsicOpcodeKind,
+ kInvalid_IntrinsicOpcodeKind,
+ };
+
+ enum SpecialIntrinsic {
+ kAtan_SpecialIntrinsic,
+ kClamp_SpecialIntrinsic,
+ kMatrixCompMult_SpecialIntrinsic,
+ kMax_SpecialIntrinsic,
+ kMin_SpecialIntrinsic,
+ kMix_SpecialIntrinsic,
+ kMod_SpecialIntrinsic,
+ kDFdy_SpecialIntrinsic,
+ kSaturate_SpecialIntrinsic,
+ kSampledImage_SpecialIntrinsic,
+ kSmoothStep_SpecialIntrinsic,
+ kStep_SpecialIntrinsic,
+ kSubpassLoad_SpecialIntrinsic,
+ kTexture_SpecialIntrinsic,
+ kTextureGrad_SpecialIntrinsic,
+ kTextureLod_SpecialIntrinsic,
+ };
+
+ enum class Precision {
+ kDefault,
+ kRelaxed,
+ };
+
+ struct TempVar {
+ SpvId spvId;
+ const Type* type;
+ std::unique_ptr<SPIRVCodeGenerator::LValue> lvalue;
+ };
+
+ /**
+ * Pass in the type to automatically add a RelaxedPrecision decoration for the id when
+ * appropriate, or null to never add one.
+ */
+ SpvId nextId(const Type* type);
+
+ SpvId nextId(Precision precision);
+
+ SpvId getType(const Type& type);
+
+ SpvId getType(const Type& type, const MemoryLayout& layout);
+
+ SpvId getFunctionType(const FunctionDeclaration& function);
+
+ SpvId getFunctionParameterType(const Type& parameterType);
+
+ SpvId getPointerType(const Type& type, SpvStorageClass_ storageClass);
+
+ SpvId getPointerType(const Type& type, const MemoryLayout& layout,
+ SpvStorageClass_ storageClass);
+
+ SkTArray<SpvId> getAccessChain(const Expression& expr, OutputStream& out);
+
+ void writeLayout(const Layout& layout, SpvId target, Position pos);
+
+ void writeFieldLayout(const Layout& layout, SpvId target, int member);
+
+ SpvId writeStruct(const Type& type, const MemoryLayout& memoryLayout);
+
+ void writeProgramElement(const ProgramElement& pe, OutputStream& out);
+
+ SpvId writeInterfaceBlock(const InterfaceBlock& intf, bool appendRTFlip = true);
+
+ SpvId writeFunctionStart(const FunctionDeclaration& f, OutputStream& out);
+
+ SpvId writeFunctionDeclaration(const FunctionDeclaration& f, OutputStream& out);
+
+ SpvId writeFunction(const FunctionDefinition& f, OutputStream& out);
+
+ bool writeGlobalVarDeclaration(ProgramKind kind, const VarDeclaration& v);
+
+ SpvId writeGlobalVar(ProgramKind kind, SpvStorageClass_, const Variable& v);
+
+ void writeVarDeclaration(const VarDeclaration& var, OutputStream& out);
+
+ SpvId writeVariableReference(const VariableReference& ref, OutputStream& out);
+
+ int findUniformFieldIndex(const Variable& var) const;
+
+ std::unique_ptr<LValue> getLValue(const Expression& value, OutputStream& out);
+
+ SpvId writeExpression(const Expression& expr, OutputStream& out);
+
+ SpvId writeIntrinsicCall(const FunctionCall& c, OutputStream& out);
+
+ SpvId writeFunctionCallArgument(const FunctionCall& call,
+ int argIndex,
+ std::vector<TempVar>* tempVars,
+ OutputStream& out,
+ SpvId* outSynthesizedSamplerId = nullptr);
+
+ void copyBackTempVars(const std::vector<TempVar>& tempVars, OutputStream& out);
+
+ SpvId writeFunctionCall(const FunctionCall& c, OutputStream& out);
+
+
+ void writeGLSLExtendedInstruction(const Type& type, SpvId id, SpvId floatInst,
+ SpvId signedInst, SpvId unsignedInst,
+ const SkTArray<SpvId>& args, OutputStream& out);
+
+ /**
+ * Promotes an expression to a vector. If the expression is already a vector with vectorSize
+ * columns, returns it unmodified. If the expression is a scalar, either promotes it to a
+ * vector (if vectorSize > 1) or returns it unmodified (if vectorSize == 1). Asserts if the
+ * expression is already a vector and it does not have vectorSize columns.
+ */
+ SpvId vectorize(const Expression& expr, int vectorSize, OutputStream& out);
+
+ /**
+ * Given a list of potentially mixed scalars and vectors, promotes the scalars to match the
+ * size of the vectors and returns the ids of the written expressions. e.g. given (float, vec2),
+ * returns (vec2(float), vec2). It is an error to use mismatched vector sizes, e.g. (float,
+ * vec2, vec3).
+ */
+ SkTArray<SpvId> vectorize(const ExpressionArray& args, OutputStream& out);
+
+ SpvId writeSpecialIntrinsic(const FunctionCall& c, SpecialIntrinsic kind, OutputStream& out);
+
+ SpvId writeScalarToMatrixSplat(const Type& matrixType, SpvId scalarId, OutputStream& out);
+
+ SpvId writeFloatConstructor(const AnyConstructor& c, OutputStream& out);
+
+ SpvId castScalarToFloat(SpvId inputId, const Type& inputType, const Type& outputType,
+ OutputStream& out);
+
+ SpvId writeIntConstructor(const AnyConstructor& c, OutputStream& out);
+
+ SpvId castScalarToSignedInt(SpvId inputId, const Type& inputType, const Type& outputType,
+ OutputStream& out);
+
+ SpvId writeUIntConstructor(const AnyConstructor& c, OutputStream& out);
+
+ SpvId castScalarToUnsignedInt(SpvId inputId, const Type& inputType, const Type& outputType,
+ OutputStream& out);
+
+ SpvId writeBooleanConstructor(const AnyConstructor& c, OutputStream& out);
+
+ SpvId castScalarToBoolean(SpvId inputId, const Type& inputType, const Type& outputType,
+ OutputStream& out);
+
+ SpvId castScalarToType(SpvId inputExprId, const Type& inputType, const Type& outputType,
+ OutputStream& out);
+
+ /**
+ * Writes a potentially-different-sized copy of a matrix. Entries which do not exist in the
+ * source matrix are filled with zero; entries which do not exist in the destination matrix are
+ * ignored.
+ */
+ SpvId writeMatrixCopy(SpvId src, const Type& srcType, const Type& dstType, OutputStream& out);
+
+ void addColumnEntry(const Type& columnType, SkTArray<SpvId>* currentColumn,
+ SkTArray<SpvId>* columnIds, int rows, SpvId entry, OutputStream& out);
+
+ SpvId writeConstructorCompound(const ConstructorCompound& c, OutputStream& out);
+
+ SpvId writeMatrixConstructor(const ConstructorCompound& c, OutputStream& out);
+
+ SpvId writeVectorConstructor(const ConstructorCompound& c, OutputStream& out);
+
+ SpvId writeCompositeConstructor(const AnyConstructor& c, OutputStream& out);
+
+ SpvId writeConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c, OutputStream& out);
+
+ SpvId writeConstructorMatrixResize(const ConstructorMatrixResize& c, OutputStream& out);
+
+ SpvId writeConstructorScalarCast(const ConstructorScalarCast& c, OutputStream& out);
+
+ SpvId writeConstructorSplat(const ConstructorSplat& c, OutputStream& out);
+
+ SpvId writeConstructorCompoundCast(const ConstructorCompoundCast& c, OutputStream& out);
+
+ SpvId writeFieldAccess(const FieldAccess& f, OutputStream& out);
+
+ SpvId writeSwizzle(const Swizzle& swizzle, OutputStream& out);
+
+ /**
+ * Folds the potentially-vector result of a logical operation down to a single bool. If
+ * operandType is a vector type, assumes that the intermediate result in id is a bvec of the
+ * same dimensions, and applys all() to it to fold it down to a single bool value. Otherwise,
+ * returns the original id value.
+ */
+ SpvId foldToBool(SpvId id, const Type& operandType, SpvOp op, OutputStream& out);
+
+ SpvId writeMatrixComparison(const Type& operandType, SpvId lhs, SpvId rhs, SpvOp_ floatOperator,
+ SpvOp_ intOperator, SpvOp_ vectorMergeOperator,
+ SpvOp_ mergeOperator, OutputStream& out);
+
+ SpvId writeStructComparison(const Type& structType, SpvId lhs, Operator op, SpvId rhs,
+ OutputStream& out);
+
+ SpvId writeArrayComparison(const Type& structType, SpvId lhs, Operator op, SpvId rhs,
+ OutputStream& out);
+
+ // Used by writeStructComparison and writeArrayComparison to logically combine field-by-field
+ // comparisons into an overall comparison result.
+ // - `a.x == b.x` merged with `a.y == b.y` generates `(a.x == b.x) && (a.y == b.y)`
+ // - `a.x != b.x` merged with `a.y != b.y` generates `(a.x != b.x) || (a.y != b.y)`
+ SpvId mergeComparisons(SpvId comparison, SpvId allComparisons, Operator op, OutputStream& out);
+
+ SpvId writeComponentwiseMatrixUnary(const Type& operandType,
+ SpvId operand,
+ SpvOp_ op,
+ OutputStream& out);
+
+ SpvId writeComponentwiseMatrixBinary(const Type& operandType, SpvId lhs, SpvId rhs,
+ SpvOp_ op, OutputStream& out);
+
+ SpvId writeBinaryOperation(const Type& resultType, const Type& operandType, SpvId lhs,
+ SpvId rhs, SpvOp_ ifFloat, SpvOp_ ifInt, SpvOp_ ifUInt,
+ SpvOp_ ifBool, OutputStream& out);
+
+ SpvId writeReciprocal(const Type& type, SpvId value, OutputStream& out);
+
+ SpvId writeBinaryExpression(const Type& leftType, SpvId lhs, Operator op,
+ const Type& rightType, SpvId rhs, const Type& resultType,
+ OutputStream& out);
+
+ SpvId writeBinaryExpression(const BinaryExpression& b, OutputStream& out);
+
+ SpvId writeTernaryExpression(const TernaryExpression& t, OutputStream& out);
+
+ SpvId writeIndexExpression(const IndexExpression& expr, OutputStream& out);
+
+ SpvId writeLogicalAnd(const Expression& left, const Expression& right, OutputStream& out);
+
+ SpvId writeLogicalOr(const Expression& left, const Expression& right, OutputStream& out);
+
+ SpvId writePrefixExpression(const PrefixExpression& p, OutputStream& out);
+
+ SpvId writePostfixExpression(const PostfixExpression& p, OutputStream& out);
+
+ SpvId writeLiteral(const Literal& f);
+
+ SpvId writeLiteral(double value, const Type& type);
+
+ void writeStatement(const Statement& s, OutputStream& out);
+
+ void writeBlock(const Block& b, OutputStream& out);
+
+ void writeIfStatement(const IfStatement& stmt, OutputStream& out);
+
+ void writeForStatement(const ForStatement& f, OutputStream& out);
+
+ void writeDoStatement(const DoStatement& d, OutputStream& out);
+
+ void writeSwitchStatement(const SwitchStatement& s, OutputStream& out);
+
+ void writeReturnStatement(const ReturnStatement& r, OutputStream& out);
+
+ void writeCapabilities(OutputStream& out);
+
+ void writeInstructions(const Program& program, OutputStream& out);
+
+ void writeOpCode(SpvOp_ opCode, int length, OutputStream& out);
+
+ void writeWord(int32_t word, OutputStream& out);
+
+ void writeString(std::string_view s, OutputStream& out);
+
+ void writeInstruction(SpvOp_ opCode, OutputStream& out);
+
+ void writeInstruction(SpvOp_ opCode, std::string_view string, OutputStream& out);
+
+ void writeInstruction(SpvOp_ opCode, int32_t word1, OutputStream& out);
+
+ void writeInstruction(SpvOp_ opCode, int32_t word1, std::string_view string,
+ OutputStream& out);
+
+ void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, std::string_view string,
+ OutputStream& out);
+
+ void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, OutputStream& out);
+
+ void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3,
+ OutputStream& out);
+
+ void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4,
+ OutputStream& out);
+
+ void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4,
+ int32_t word5, OutputStream& out);
+
+ void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4,
+ int32_t word5, int32_t word6, OutputStream& out);
+
+ void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4,
+ int32_t word5, int32_t word6, int32_t word7, OutputStream& out);
+
+ void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4,
+ int32_t word5, int32_t word6, int32_t word7, int32_t word8,
+ OutputStream& out);
+
+ // This form of writeInstruction can deduplicate redundant ops.
+ struct Word;
+ // 8 Words is enough for nearly all instructions (except variable-length instructions like
+ // OpAccessChain or OpConstantComposite).
+ using Words = SkSTArray<8, Word, true>;
+ SpvId writeInstruction(SpvOp_ opCode, const SkTArray<Word, true>& words, OutputStream& out);
+
+ struct Instruction {
+ SpvId fOp;
+ int32_t fResultKind;
+ SkSTArray<8, int32_t> fWords;
+
+ bool operator==(const Instruction& that) const;
+ struct Hash;
+ };
+
+ static Instruction BuildInstructionKey(SpvOp_ opCode, const SkTArray<Word, true>& words);
+
+ // The writeOpXxxxx calls will simplify and deduplicate ops where possible.
+ SpvId writeOpConstantTrue(const Type& type);
+ SpvId writeOpConstantFalse(const Type& type);
+ SpvId writeOpConstant(const Type& type, int32_t valueBits);
+ SpvId writeOpConstantComposite(const Type& type, const SkTArray<SpvId>& values);
+ SpvId writeOpCompositeConstruct(const Type& type, const SkTArray<SpvId>&, OutputStream& out);
+ SpvId writeOpCompositeExtract(const Type& type, SpvId base, int component, OutputStream& out);
+ SpvId writeOpCompositeExtract(const Type& type, SpvId base, int componentA, int componentB,
+ OutputStream& out);
+ SpvId writeOpLoad(SpvId type, Precision precision, SpvId pointer, OutputStream& out);
+ void writeOpStore(SpvStorageClass_ storageClass, SpvId pointer, SpvId value, OutputStream& out);
+
+ // Converts the provided SpvId(s) into an array of scalar OpConstants, if it can be done.
+ bool toConstants(SpvId value, SkTArray<SpvId>* constants);
+ bool toConstants(SkSpan<const SpvId> values, SkTArray<SpvId>* constants);
+
+ // Extracts the requested component SpvId from a composite instruction, if it can be done.
+ Instruction* resultTypeForInstruction(const Instruction& instr);
+ int numComponentsForVecInstruction(const Instruction& instr);
+ SpvId toComponent(SpvId id, int component);
+
+ struct ConditionalOpCounts {
+ int numReachableOps;
+ int numStoreOps;
+ };
+ ConditionalOpCounts getConditionalOpCounts();
+ void pruneConditionalOps(ConditionalOpCounts ops);
+
+ enum StraightLineLabelType {
+ // Use "BranchlessBlock" for blocks which are never explicitly branched-to at all. This
+ // happens at the start of a function, or when we find unreachable code.
+ kBranchlessBlock,
+
+ // Use "BranchIsOnPreviousLine" when writing a label that comes immediately after its
+ // associated branch. Example usage:
+ // - SPIR-V does not implicitly fall through from one block to the next, so you may need to
+ // use an OpBranch to explicitly jump to the next block, even when they are adjacent in
+ // the code.
+ // - The block immediately following an OpBranchConditional or OpSwitch.
+ kBranchIsOnPreviousLine,
+ };
+
+ enum BranchingLabelType {
+ // Use "BranchIsAbove" for labels which are referenced by OpBranch or OpBranchConditional
+ // ops that are above the label in the code--i.e., the branch skips forward in the code.
+ kBranchIsAbove,
+
+ // Use "BranchIsBelow" for labels which are referenced by OpBranch or OpBranchConditional
+ // ops below the label in the code--i.e., the branch jumps backward in the code.
+ kBranchIsBelow,
+
+ // Use "BranchesOnBothSides" for labels which have branches coming from both directions.
+ kBranchesOnBothSides,
+ };
+ void writeLabel(SpvId label, StraightLineLabelType type, OutputStream& out);
+ void writeLabel(SpvId label, BranchingLabelType type, ConditionalOpCounts ops,
+ OutputStream& out);
+
+ bool isDead(const Variable& var) const;
+
+ MemoryLayout memoryLayoutForStorageClass(SpvStorageClass_ storageClass);
+ MemoryLayout memoryLayoutForVariable(const Variable&) const;
+
+ struct EntrypointAdapter {
+ std::unique_ptr<FunctionDefinition> entrypointDef;
+ std::unique_ptr<FunctionDeclaration> entrypointDecl;
+ Layout fLayout;
+ Modifiers fModifiers;
+ };
+
+ EntrypointAdapter writeEntrypointAdapter(const FunctionDeclaration& main);
+
+ struct UniformBuffer {
+ std::unique_ptr<InterfaceBlock> fInterfaceBlock;
+ std::unique_ptr<Variable> fInnerVariable;
+ std::unique_ptr<Type> fStruct;
+ };
+
+ void writeUniformBuffer(std::shared_ptr<SymbolTable> topLevelSymbolTable);
+
+ void addRTFlipUniform(Position pos);
+
+ std::tuple<const Variable*, const Variable*> synthesizeTextureAndSampler(
+ const Variable& combinedSampler);
+
+ const MemoryLayout fDefaultLayout;
+
+ uint64_t fCapabilities;
+ SpvId fIdCount;
+ SpvId fGLSLExtendedInstructions;
+ struct Intrinsic {
+ IntrinsicOpcodeKind opKind;
+ int32_t floatOp;
+ int32_t signedOp;
+ int32_t unsignedOp;
+ int32_t boolOp;
+ };
+ Intrinsic getIntrinsic(IntrinsicKind) const;
+ SkTHashMap<const FunctionDeclaration*, SpvId> fFunctionMap;
+ SkTHashMap<const Variable*, SpvId> fVariableMap;
+ SkTHashMap<const Type*, SpvId> fStructMap;
+ StringStream fGlobalInitializersBuffer;
+ StringStream fConstantBuffer;
+ StringStream fVariableBuffer;
+ StringStream fNameBuffer;
+ StringStream fDecorationBuffer;
+
+ // Mapping from combined sampler declarations to synthesized texture/sampler variables.
+ // This is only used if the SPIRVDawnCompatMode setting is enabled.
+ // TODO(skia:14023): Remove when WGSL codegen is complete
+ struct SynthesizedTextureSamplerPair {
+ // The names of the synthesized variables. The Variable objects themselves store string
+ // views referencing these strings. It is important for the std::string instances to have a
+ // fixed memory location after the string views get created, which is why
+ // `fSynthesizedSamplerMap` stores unique_ptr instead of values.
+ std::string fTextureName;
+ std::string fSamplerName;
+ std::unique_ptr<Variable> fTexture;
+ std::unique_ptr<Variable> fSampler;
+ };
+ SkTHashMap<const Variable*, std::unique_ptr<SynthesizedTextureSamplerPair>>
+ fSynthesizedSamplerMap;
+
+ // These caches map SpvIds to Instructions, and vice-versa. This enables us to deduplicate code
+ // (by detecting an Instruction we've already issued and reusing the SpvId), and to introspect
+ // and simplify code we've already emitted (by taking a SpvId from an Instruction and following
+ // it back to its source).
+ SkTHashMap<Instruction, SpvId, Instruction::Hash> fOpCache; // maps instruction -> SpvId
+ SkTHashMap<SpvId, Instruction> fSpvIdCache; // maps SpvId -> instruction
+ SkTHashMap<SpvId, SpvId> fStoreCache; // maps ptr SpvId -> value SpvId
+
+ // "Reachable" ops are instructions which can safely be accessed from the current block.
+ // For instance, if our SPIR-V contains `%3 = OpFAdd %1 %2`, we would be able to access and
+ // reuse that computation on following lines. However, if that Add operation occurred inside an
+ // `if` block, then its SpvId becomes inaccessible once we complete the if statement (since
+ // depending on the if condition, we may or may not have actually done that computation). The
+ // same logic applies to other control-flow blocks as well. Once an instruction becomes
+ // unreachable, we remove it from both op-caches.
+ SkTArray<SpvId> fReachableOps;
+
+ // The "store-ops" list contains a running list of all the pointers in the store cache. If a
+ // store occurs inside of a conditional block, once that block exits, we no longer know what is
+ // stored in that particular SpvId. At that point, we must remove any associated entry from the
+ // store cache.
+ SkTArray<SpvId> fStoreOps;
+
+ // label of the current block, or 0 if we are not in a block
+ SpvId fCurrentBlock;
+ SkTArray<SpvId> fBreakTarget;
+ SkTArray<SpvId> fContinueTarget;
+ bool fWroteRTFlip = false;
+ // holds variables synthesized during output, for lifetime purposes
+ SymbolTable fSynthetics;
+ // Holds a list of uniforms that were declared as globals at the top-level instead of in an
+ // interface block.
+ UniformBuffer fUniformBuffer;
+ std::vector<const VarDeclaration*> fTopLevelUniforms;
+ SkTHashMap<const Variable*, int> fTopLevelUniformMap; // <var, UniformBuffer field index>
+ SkTHashSet<const Variable*> fSPIRVBonusVariables;
+ SpvId fUniformBufferId = NA;
+
+ friend class PointerLValue;
+ friend class SwizzleLValue;
+
+ using INHERITED = CodeGenerator;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLSPIRVtoHLSL.cpp b/gfx/skia/skia/src/sksl/codegen/SkSLSPIRVtoHLSL.cpp
new file mode 100644
index 0000000000..98bb969c5b
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/codegen/SkSLSPIRVtoHLSL.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/codegen/SkSLSPIRVtoHLSL.h"
+
+#if defined(SK_ENABLE_SPIRV_CROSS)
+
+#include <spirv_hlsl.hpp>
+
+/*
+ * This translation unit serves as a bridge between Skia/SkSL and SPIRV-Cross.
+ * Each library is built with a separate copy of spirv.h (or spirv.hpp), so we
+ * avoid conflicts by never including both in the same cpp.
+ */
+
+namespace SkSL {
+
+bool SPIRVtoHLSL(const std::string& spirv, std::string* hlsl) {
+ spirv_cross::CompilerHLSL hlslCompiler((const uint32_t*)spirv.c_str(),
+ spirv.size() / sizeof(uint32_t));
+
+ spirv_cross::CompilerGLSL::Options optionsGLSL;
+ // Force all uninitialized variables to be 0, otherwise they will fail to compile
+ // by FXC.
+ optionsGLSL.force_zero_initialized_variables = true;
+
+ spirv_cross::CompilerHLSL::Options optionsHLSL;
+ optionsHLSL.shader_model = 51;
+ // PointCoord and PointSize are not supported in HLSL
+ optionsHLSL.point_coord_compat = true;
+ optionsHLSL.point_size_compat = true;
+
+ hlslCompiler.set_common_options(optionsGLSL);
+ hlslCompiler.set_hlsl_options(optionsHLSL);
+ hlsl->assign(hlslCompiler.compile());
+ return true;
+}
+
+}
+
+#else
+
+namespace SkSL { bool SPIRVtoHLSL(const std::string&, std::string*) { return false; } }
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLSPIRVtoHLSL.h b/gfx/skia/skia/src/sksl/codegen/SkSLSPIRVtoHLSL.h
new file mode 100644
index 0000000000..5207546a67
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/codegen/SkSLSPIRVtoHLSL.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_SPIRVTOHLSL
+#define SKSL_SPIRVTOHLSL
+
+#include <string>
+
+namespace SkSL {
+
+bool SPIRVtoHLSL(const std::string& spirv, std::string* hlsl);
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLVMCodeGenerator.cpp b/gfx/skia/skia/src/sksl/codegen/SkSLVMCodeGenerator.cpp
new file mode 100644
index 0000000000..33eab93b9a
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/codegen/SkSLVMCodeGenerator.cpp
@@ -0,0 +1,2302 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/codegen/SkSLVMCodeGenerator.h"
+
+#include "include/core/SkBlendMode.h"
+#include "include/core/SkColor.h"
+#include "include/core/SkColorType.h"
+#include "include/core/SkPoint.h"
+#include "include/core/SkSpan.h"
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLLayout.h"
+#include "include/private/SkSLModifiers.h"
+#include "include/private/SkSLProgramElement.h"
+#include "include/private/SkSLStatement.h"
+#include "include/private/base/SkFloatingPoint.h"
+#include "include/private/base/SkTArray.h"
+#include "include/private/base/SkTPin.h"
+#include "include/sksl/SkSLOperator.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/base/SkStringView.h"
+#include "src/core/SkTHash.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLIntrinsicList.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/ir/SkSLBinaryExpression.h"
+#include "src/sksl/ir/SkSLBlock.h"
+#include "src/sksl/ir/SkSLChildCall.h"
+#include "src/sksl/ir/SkSLConstructor.h"
+#include "src/sksl/ir/SkSLConstructorArrayCast.h"
+#include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h"
+#include "src/sksl/ir/SkSLConstructorMatrixResize.h"
+#include "src/sksl/ir/SkSLConstructorSplat.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLExpressionStatement.h"
+#include "src/sksl/ir/SkSLFieldAccess.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/SkSLLiteral.h"
+#include "src/sksl/ir/SkSLPostfixExpression.h"
+#include "src/sksl/ir/SkSLPrefixExpression.h"
+#include "src/sksl/ir/SkSLProgram.h"
+#include "src/sksl/ir/SkSLReturnStatement.h"
+#include "src/sksl/ir/SkSLSwitchCase.h"
+#include "src/sksl/ir/SkSLSwitchStatement.h"
+#include "src/sksl/ir/SkSLSwizzle.h"
+#include "src/sksl/ir/SkSLTernaryExpression.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/SkSLDebugInfo.h"
+#include "src/sksl/tracing/SkSLTraceHook.h"
+#include "src/sksl/tracing/SkVMDebugTrace.h"
+
+#include <algorithm>
+#include <functional>
+#include <iterator>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace {
+ // sksl allows the optimizations of fast_mul(), so we want to use that most of the time.
+ // This little sneaky snippet of code lets us use ** as a fast multiply infix operator.
+ struct FastF32 { skvm::F32 val; };
+ static FastF32 operator*(skvm::F32 y) { return {y}; }
+ static skvm::F32 operator*(skvm::F32 x, FastF32 y) { return fast_mul(x, y.val); }
+ static skvm::F32 operator*(float x, FastF32 y) { return fast_mul(x, y.val); }
+}
+
+namespace SkSL {
+
+namespace {
+
+// Holds scalars, vectors, or matrices
+struct Value {
+ Value() = default;
+ explicit Value(size_t slots) {
+ fVals.resize(slots);
+ }
+ Value(skvm::F32 x) : fVals({ x.id }) {}
+ Value(skvm::I32 x) : fVals({ x.id }) {}
+
+ explicit operator bool() const { return !fVals.empty(); }
+
+ size_t slots() const { return fVals.size(); }
+
+ struct ValRef {
+ ValRef(skvm::Val& val) : fVal(val) {}
+
+ ValRef& operator=(ValRef v) { fVal = v.fVal; return *this; }
+ ValRef& operator=(skvm::Val v) { fVal = v; return *this; }
+ ValRef& operator=(skvm::F32 v) { fVal = v.id; return *this; }
+ ValRef& operator=(skvm::I32 v) { fVal = v.id; return *this; }
+
+ operator skvm::Val() { return fVal; }
+
+ skvm::Val& fVal;
+ };
+
+ ValRef operator[](int i) {
+ // These redundant asserts work around what we think is a codegen bug in GCC 8.x for
+ // 32-bit x86 Debug builds.
+ SkASSERT(i < fVals.size());
+ return fVals[i];
+ }
+ skvm::Val operator[](int i) const {
+ // These redundant asserts work around what we think is a codegen bug in GCC 8.x for
+ // 32-bit x86 Debug builds.
+ SkASSERT(i < fVals.size());
+ return fVals[i];
+ }
+
+ SkSpan<skvm::Val> asSpan() { return SkSpan(fVals); }
+
+private:
+ SkSTArray<4, skvm::Val, true> fVals;
+};
+
+} // namespace
+
+class SkVMGenerator {
+public:
+ SkVMGenerator(const Program& program,
+ skvm::Builder* builder,
+ SkVMDebugTrace* debugTrace,
+ SkVMCallbacks* callbacks);
+
+ void writeProgram(SkSpan<skvm::Val> uniforms,
+ skvm::Coord device,
+ const FunctionDefinition& function,
+ SkSpan<skvm::Val> arguments,
+ SkSpan<skvm::Val> outReturn);
+
+private:
+ /**
+ * In SkSL, a Variable represents a named, typed value (along with qualifiers, etc).
+ * Every Variable is mapped to one (or several, contiguous) indices into our vector of
+ * skvm::Val. Those skvm::Val entries hold the current actual value of that variable.
+ *
+ * NOTE: Conceptually, each Variable is just mapped to a Value. We could implement it that way,
+ * (and eliminate the indirection), but it would add overhead for each Variable,
+ * and add additional (different) bookkeeping for things like lvalue-swizzles.
+ *
+ * Any time a variable appears in an expression, that's a VariableReference, which is a kind of
+ * Expression. Evaluating that VariableReference (or any other Expression) produces a Value,
+ * which is a set of skvm::Val. (This allows an Expression to produce a vector or matrix, in
+ * addition to a scalar).
+ *
+ * For a VariableReference, producing a Value is straightforward - we get the slot of the
+ * Variable (from fSlotMap), use that to look up the current skvm::Vals holding the variable's
+ * contents, and construct a Value with those ids.
+ */
+
+ /** Creates a Value from a collection of adjacent slots. */
+ Value getSlotValue(size_t slot, size_t nslots);
+
+ /**
+ * Returns the slot index of this function inside the FunctionDebugInfo array in SkVMDebugTrace.
+ * The FunctionDebugInfo slot will be created if it doesn't already exist.
+ */
+ int getDebugFunctionInfo(const FunctionDeclaration& decl);
+
+ /** Used by `createSlot` to add this variable to SlotDebugInfo inside SkVMDebugTrace. */
+ void addDebugSlotInfo(const std::string& varName, const Type& type, int line,
+ int fnReturnValue);
+
+ void addDebugSlotInfoForGroup(const std::string& varName, const Type& type, int line,
+ int* groupIndex, int fnReturnValue);
+
+ /** Used by `getSlot` to create a new slot on its first access. */
+ size_t createSlot(const std::string& name, const Type& type, int line, int fnReturnValue);
+
+ /**
+ * Returns the slot holding v's Val(s). Allocates storage if this is first time 'v' is
+ * referenced. Compound variables (e.g. vectors) will consume more than one slot, with
+ * getSlot returning the start of the contiguous chunk of slots.
+ */
+ size_t getSlot(const Variable& v);
+
+ /**
+ * Returns the slot holding fn's return value. Each call site is given a distinct slot, since
+ * multiple calls to the same function can occur in a single statement. This is generally the
+ * FunctionCall or ChildCall node, but main() doesn't have one of these so it uses the
+ * FunctionDefinition. Allocates storage if this is first time accessing the slot.
+ */
+ size_t getFunctionSlot(const IRNode& callSite, const FunctionDefinition& fn);
+
+ /**
+ * Writes a value to a slot previously created by getSlot.
+ */
+ void writeToSlot(int slot, skvm::Val value);
+
+ /**
+ * Returns the line number corresponding to a position.
+ */
+ int getLine(Position pos);
+
+ /**
+ * Emits an trace_line opcode. writeStatement does this, and statements that alter control flow
+ * may need to explicitly add additional traces.
+ */
+ void emitTraceLine(int line);
+
+ /** Emits an trace_scope opcode, which alters the SkSL variable-scope depth. */
+ void emitTraceScope(skvm::I32 executionMask, int delta);
+
+ /** Initializes uniforms and global variables at the start of main(). */
+ void setupGlobals(SkSpan<skvm::Val> uniforms, skvm::Coord device);
+
+ /** Emits an SkSL function. Returns the slot index of the SkSL function's return value. */
+ size_t writeFunction(const IRNode& caller,
+ const FunctionDefinition& function,
+ SkSpan<skvm::Val> arguments);
+
+ skvm::F32 f32(skvm::Val id) { SkASSERT(id != skvm::NA); return {fBuilder, id}; }
+ skvm::I32 i32(skvm::Val id) { SkASSERT(id != skvm::NA); return {fBuilder, id}; }
+
+ // Shorthand for scalars
+ skvm::F32 f32(const Value& v) { SkASSERT(v.slots() == 1); return f32(v[0]); }
+ skvm::I32 i32(const Value& v) { SkASSERT(v.slots() == 1); return i32(v[0]); }
+
+ template <typename Fn>
+ Value unary(const Value& v, Fn&& fn) {
+ Value result(v.slots());
+ for (size_t i = 0; i < v.slots(); ++i) {
+ result[i] = fn({fBuilder, v[i]});
+ }
+ return result;
+ }
+
+ skvm::I32 mask() {
+ // Mask off execution if we have encountered `break` or `continue` on this path.
+ skvm::I32 result = fConditionMask & fLoopMask;
+ if (!fFunctionStack.empty()) {
+ // As we encounter (possibly conditional) return statements, fReturned is updated to
+ // store the lanes that have already returned. For the remainder of the current
+ // function, those lanes should be disabled.
+ result = result & ~currentFunction().fReturned;
+ }
+ return result;
+ }
+
+ size_t indexSlotOffset(const IndexExpression& expr);
+
+ Value writeExpression(const Expression& expr);
+ Value writeBinaryExpression(const BinaryExpression& b);
+ Value writeAggregationConstructor(const AnyConstructor& c);
+ Value writeChildCall(const ChildCall& c);
+ Value writeConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c);
+ Value writeConstructorMatrixResize(const ConstructorMatrixResize& c);
+ Value writeConstructorCast(const AnyConstructor& c);
+ Value writeConstructorSplat(const ConstructorSplat& c);
+ Value writeFunctionCall(const FunctionCall& c);
+ Value writeFieldAccess(const FieldAccess& expr);
+ Value writeLiteral(const Literal& l);
+ Value writeIndexExpression(const IndexExpression& expr);
+ Value writeIntrinsicCall(const FunctionCall& c);
+ Value writePostfixExpression(const PostfixExpression& p);
+ Value writePrefixExpression(const PrefixExpression& p);
+ Value writeSwizzle(const Swizzle& swizzle);
+ Value writeTernaryExpression(const TernaryExpression& t);
+ Value writeVariableExpression(const VariableReference& expr);
+
+ Value writeTypeConversion(const Value& src, Type::NumberKind srcKind, Type::NumberKind dstKind);
+
+ void writeStatement(const Statement& s);
+ void writeBlock(const Block& b);
+ void writeBreakStatement();
+ void writeContinueStatement();
+ void writeForStatement(const ForStatement& f);
+ void writeIfStatement(const IfStatement& stmt);
+ void writeReturnStatement(const ReturnStatement& r);
+ void writeSwitchStatement(const SwitchStatement& s);
+ void writeVarDeclaration(const VarDeclaration& decl);
+
+ Value writeStore(const Expression& lhs, const Value& rhs);
+ skvm::Val writeConditionalStore(skvm::Val lhs, skvm::Val rhs, skvm::I32 mask);
+
+ Value writeMatrixInverse2x2(const Value& m);
+ Value writeMatrixInverse3x3(const Value& m);
+ Value writeMatrixInverse4x4(const Value& m);
+
+ void recursiveBinaryCompare(const Value& lVal, const Type& lType,
+ const Value& rVal, const Type& rType,
+ size_t* slotOffset, Value* result,
+ const std::function <Value(skvm::F32 x, skvm::F32 y)>& float_comp,
+ const std::function <Value(skvm::I32 x, skvm::I32 y)>& int_comp);
+
+ void determineLineOffsets();
+
+ //
+ // Global state for the lifetime of the generator:
+ //
+ const Program& fProgram;
+ skvm::Builder* fBuilder;
+ SkVMDebugTrace* fDebugTrace;
+ int fTraceHookID = -1;
+ SkVMCallbacks* fCallbacks;
+ // contains the position of each newline in the source, plus a zero at the beginning and the
+ // total source length at the end as sentinels
+ std::vector<int> fLineOffsets;
+
+ struct Slot {
+ skvm::Val val;
+ bool writtenTo = false;
+ };
+ std::vector<Slot> fSlots;
+
+ // [Variable/Function, first slot in fSlots]
+ SkTHashMap<const IRNode*, size_t> fSlotMap;
+
+ // Debug trace mask (set to true when fTraceCoord matches device coordinates)
+ skvm::I32 fTraceMask;
+
+ // Conditional execution mask (managed by ScopedCondition, and tied to control-flow scopes)
+ skvm::I32 fConditionMask;
+
+ // Similar: loop execution masks. Each loop starts with all lanes active (fLoopMask).
+ // 'break' disables a lane in fLoopMask until the loop finishes
+ // 'continue' disables a lane in fLoopMask, and sets fContinueMask to be re-enabled on the next
+ // iteration
+ skvm::I32 fLoopMask;
+ skvm::I32 fContinueMask;
+
+ // `fInsideCompoundStatement` will be nonzero if we are currently writing statements inside of a
+ // compound-statement Block. (Conceptually those statements should all count as one.)
+ int fInsideCompoundStatement = 0;
+
+ //
+ // State that's local to the generation of a single function:
+ //
+ struct Function {
+ size_t fReturnSlot;
+ skvm::I32 fReturned;
+ };
+ std::vector<Function> fFunctionStack;
+ Function& currentFunction() { return fFunctionStack.back(); }
+
+ class ScopedCondition {
+ public:
+ ScopedCondition(SkVMGenerator* generator, skvm::I32 mask)
+ : fGenerator(generator), fOldConditionMask(fGenerator->fConditionMask) {
+ fGenerator->fConditionMask &= mask;
+ }
+
+ ~ScopedCondition() { fGenerator->fConditionMask = fOldConditionMask; }
+
+ private:
+ SkVMGenerator* fGenerator;
+ skvm::I32 fOldConditionMask;
+ };
+};
+
+static Type::NumberKind base_number_kind(const Type& type) {
+ if (type.typeKind() == Type::TypeKind::kMatrix || type.typeKind() == Type::TypeKind::kVector) {
+ return base_number_kind(type.componentType());
+ }
+ return type.numberKind();
+}
+
+static inline bool is_uniform(const SkSL::Variable& var) {
+ return var.modifiers().fFlags & Modifiers::kUniform_Flag;
+}
+
+SkVMGenerator::SkVMGenerator(const Program& program,
+ skvm::Builder* builder,
+ SkVMDebugTrace* debugTrace,
+ SkVMCallbacks* callbacks)
+ : fProgram(program)
+ , fBuilder(builder)
+ , fDebugTrace(debugTrace)
+ , fCallbacks(callbacks) {}
+
+void SkVMGenerator::writeProgram(SkSpan<skvm::Val> uniforms,
+ skvm::Coord device,
+ const FunctionDefinition& function,
+ SkSpan<skvm::Val> arguments,
+ SkSpan<skvm::Val> outReturn) {
+ this->determineLineOffsets();
+ fConditionMask = fLoopMask = fBuilder->splat(0xffff'ffff);
+
+ this->setupGlobals(uniforms, device);
+ size_t returnSlot = this->writeFunction(function, function, arguments);
+
+ // Copy the value from the return slot into outReturn.
+ SkASSERT(function.declaration().returnType().slotCount() == outReturn.size());
+ for (size_t i = 0; i < outReturn.size(); ++i) {
+ outReturn[i] = fSlots[returnSlot + i].val;
+ }
+}
+
+void SkVMGenerator::determineLineOffsets() {
+ SkASSERT(fLineOffsets.empty());
+ fLineOffsets.push_back(0);
+ for (size_t i = 0; i < fProgram.fSource->length(); ++i) {
+ if ((*fProgram.fSource)[i] == '\n') {
+ fLineOffsets.push_back(i);
+ }
+ }
+ fLineOffsets.push_back(fProgram.fSource->length());
+}
+
+void SkVMGenerator::setupGlobals(SkSpan<skvm::Val> uniforms, skvm::Coord device) {
+ if (fDebugTrace) {
+ // Copy the program source into the debug info so that it will be written in the trace file.
+ fDebugTrace->setSource(*fProgram.fSource);
+
+ // Create a trace hook and attach it to the builder.
+ fDebugTrace->fTraceHook = SkSL::Tracer::Make(&fDebugTrace->fTraceInfo);
+ fTraceHookID = fBuilder->attachTraceHook(fDebugTrace->fTraceHook.get());
+
+ // The SkVM blitter generates centered pixel coordinates. (0.5, 1.5, 2.5, 3.5, etc.)
+ // Add 0.5 to the requested trace coordinate to match this.
+ skvm::Coord traceCoord = {to_F32(fBuilder->splat(fDebugTrace->fTraceCoord.fX)) + 0.5f,
+ to_F32(fBuilder->splat(fDebugTrace->fTraceCoord.fY)) + 0.5f};
+
+ // If we are debugging, we need to create a trace mask. This will be true when the current
+ // device coordinates match the requested trace coordinates. We calculate each mask
+ // individually to guarantee consistent order-of-evaluation.
+ skvm::I32 xMask = (device.x == traceCoord.x),
+ yMask = (device.y == traceCoord.y);
+ fTraceMask = xMask & yMask;
+ }
+
+ // Add storage for each global variable (including uniforms) to fSlots, and entries in
+ // fSlotMap to remember where every variable is stored.
+ const skvm::Val* uniformIter = uniforms.begin();
+ size_t fpCount = 0;
+ for (const ProgramElement* e : fProgram.elements()) {
+ if (e->is<GlobalVarDeclaration>()) {
+ const GlobalVarDeclaration& gvd = e->as<GlobalVarDeclaration>();
+ const VarDeclaration& decl = gvd.varDeclaration();
+ const Variable* var = decl.var();
+ SkASSERT(!fSlotMap.find(var));
+
+ // For most variables, fSlotMap stores an index into fSlots, but for children,
+ // fSlotMap stores the index to pass to fSample(Shader|ColorFilter|Blender)
+ if (var->type().isEffectChild()) {
+ fSlotMap.set(var, fpCount++);
+ continue;
+ }
+
+ // Opaque types include child processors and GL objects (samplers, textures, etc).
+ // Of those, only child processors are legal variables.
+ SkASSERT(!var->type().isVoid());
+ SkASSERT(!var->type().isOpaque());
+
+ // getSlot() allocates space for the variable's value in fSlots, initializes it to zero,
+ // and populates fSlotMap.
+ size_t slot = this->getSlot(*var),
+ nslots = var->type().slotCount();
+
+ // builtin variables are system-defined, with special semantics. The only builtin
+ // variable exposed to runtime effects is sk_FragCoord.
+ if (int builtin = var->modifiers().fLayout.fBuiltin; builtin >= 0) {
+ switch (builtin) {
+ case SK_FRAGCOORD_BUILTIN:
+ SkASSERT(nslots == 4);
+ this->writeToSlot(slot + 0, device.x.id);
+ this->writeToSlot(slot + 1, device.y.id);
+ this->writeToSlot(slot + 2, fBuilder->splat(0.0f).id);
+ this->writeToSlot(slot + 3, fBuilder->splat(1.0f).id);
+ break;
+ default:
+ SkDEBUGFAILF("Unsupported builtin %d", builtin);
+ }
+ continue;
+ }
+
+ // For uniforms, copy the supplied IDs over
+ if (is_uniform(*var)) {
+ SkASSERT(uniformIter + nslots <= uniforms.end());
+ for (size_t i = 0; i < nslots; ++i) {
+ this->writeToSlot(slot + i, uniformIter[i]);
+ }
+ uniformIter += nslots;
+ continue;
+ }
+
+ // For other globals, populate with the initializer expression (if there is one)
+ if (decl.value()) {
+ Value val = this->writeExpression(*decl.value());
+ for (size_t i = 0; i < nslots; ++i) {
+ this->writeToSlot(slot + i, val[i]);
+ }
+ }
+ }
+ }
+ SkASSERT(uniformIter == uniforms.end());
+}
+
+Value SkVMGenerator::getSlotValue(size_t slot, size_t nslots) {
+ Value val(nslots);
+ for (size_t i = 0; i < nslots; ++i) {
+ val[i] = fSlots[slot + i].val;
+ }
+ return val;
+}
+
+int SkVMGenerator::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;
+}
+
+size_t SkVMGenerator::writeFunction(const IRNode& caller,
+ const FunctionDefinition& function,
+ SkSpan<skvm::Val> arguments) {
+ const FunctionDeclaration& decl = function.declaration();
+
+ int funcIndex = -1;
+ if (fDebugTrace) {
+ funcIndex = this->getDebugFunctionInfo(decl);
+ fBuilder->trace_enter(fTraceHookID, this->mask(), fTraceMask, funcIndex);
+ }
+
+ size_t returnSlot = this->getFunctionSlot(caller, function);
+ fFunctionStack.push_back({/*fReturnSlot=*/returnSlot, /*fReturned=*/fBuilder->splat(0)});
+
+ // For all parameters, copy incoming argument IDs to our vector of (all) variable IDs
+ size_t argIdx = 0;
+ for (const Variable* p : decl.parameters()) {
+ size_t paramSlot = this->getSlot(*p),
+ nslots = p->type().slotCount();
+
+ for (size_t i = 0; i < nslots; ++i) {
+ fSlots[paramSlot + i].writtenTo = false;
+ this->writeToSlot(paramSlot + i, arguments[argIdx + i]);
+ }
+ argIdx += nslots;
+ }
+ SkASSERT(argIdx == arguments.size());
+
+ this->writeBlock(function.body()->as<Block>());
+
+ // Copy 'out' and 'inout' parameters back to their caller-supplied argument storage
+ argIdx = 0;
+ for (const Variable* p : decl.parameters()) {
+ size_t nslots = p->type().slotCount();
+
+ if (p->modifiers().fFlags & Modifiers::kOut_Flag) {
+ size_t paramSlot = this->getSlot(*p);
+ for (size_t i = 0; i < nslots; ++i) {
+ arguments[argIdx + i] = fSlots[paramSlot + i].val;
+ }
+ }
+ argIdx += nslots;
+ }
+ SkASSERT(argIdx == arguments.size());
+
+ fFunctionStack.pop_back();
+
+ if (fDebugTrace) {
+ fBuilder->trace_exit(fTraceHookID, this->mask(), fTraceMask, funcIndex);
+ }
+
+ return returnSlot;
+}
+
+void SkVMGenerator::writeToSlot(int slot, skvm::Val value) {
+ if (fDebugTrace && (!fSlots[slot].writtenTo || fSlots[slot].val != value)) {
+ if (fProgram.fConfig->fSettings.fAllowTraceVarInSkVMDebugTrace) {
+ fBuilder->trace_var(fTraceHookID, this->mask(), fTraceMask, slot, i32(value));
+ }
+ fSlots[slot].writtenTo = true;
+ }
+
+ fSlots[slot].val = value;
+}
+
+void SkVMGenerator::addDebugSlotInfoForGroup(const std::string& varName, const Type& type, int line,
+ int* groupIndex, int fnReturnValue) {
+ 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,
+ line, groupIndex, fnReturnValue);
+ }
+ break;
+ }
+ case Type::TypeKind::kStruct: {
+ for (const Type::Field& field : type.fields()) {
+ this->addDebugSlotInfoForGroup(varName + "." + std::string(field.fName),
+ *field.fType, line, groupIndex, fnReturnValue);
+ }
+ 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.line = line;
+ slotInfo.fnReturnValue = fnReturnValue;
+ fDebugTrace->fSlotInfo.push_back(std::move(slotInfo));
+ }
+ break;
+ }
+ }
+}
+
+void SkVMGenerator::addDebugSlotInfo(const std::string& varName, const Type& type, int line,
+ int fnReturnValue) {
+ int groupIndex = 0;
+ this->addDebugSlotInfoForGroup(varName, type, line, &groupIndex, fnReturnValue);
+ SkASSERT((size_t)groupIndex == type.slotCount());
+}
+
+size_t SkVMGenerator::createSlot(const std::string& name,
+ const Type& type,
+ int line,
+ int fnReturnValue) {
+ size_t slot = fSlots.size(),
+ nslots = type.slotCount();
+
+ if (nslots > 0) {
+ if (fDebugTrace) {
+ // Our debug slot-info table should have the same length as the actual slot table.
+ SkASSERT(fDebugTrace->fSlotInfo.size() == slot);
+
+ // Append slot names and types to our debug slot-info table.
+ fDebugTrace->fSlotInfo.reserve(slot + nslots);
+ this->addDebugSlotInfo(name, type, line, fnReturnValue);
+
+ // Confirm that we added the expected number of slots.
+ SkASSERT(fDebugTrace->fSlotInfo.size() == (slot + nslots));
+ }
+
+ // Create brand new slots initialized to zero.
+ skvm::Val initialValue = fBuilder->splat(0.0f).id;
+ fSlots.insert(fSlots.end(), nslots, Slot{initialValue});
+ }
+ return slot;
+}
+
+// TODO(skia:13058): remove this and track positions directly
+int SkVMGenerator::getLine(Position pos) {
+ if (pos.valid()) {
+ // Binary search within fLineOffets to find the line.
+ SkASSERT(fLineOffsets.size() >= 2);
+ SkASSERT(fLineOffsets[0] == 0);
+ SkASSERT(fLineOffsets.back() == (int)fProgram.fSource->length());
+ return std::distance(fLineOffsets.begin(), std::upper_bound(fLineOffsets.begin(),
+ fLineOffsets.end(), pos.startOffset()));
+ } else {
+ return -1;
+ }
+}
+
+size_t SkVMGenerator::getSlot(const Variable& v) {
+ size_t* entry = fSlotMap.find(&v);
+ if (entry != nullptr) {
+ return *entry;
+ }
+
+ size_t slot = this->createSlot(std::string(v.name()), v.type(), this->getLine(v.fPosition),
+ /*fnReturnValue=*/-1);
+ fSlotMap.set(&v, slot);
+ return slot;
+}
+
+size_t SkVMGenerator::getFunctionSlot(const IRNode& callSite, const FunctionDefinition& fn) {
+ size_t* entry = fSlotMap.find(&callSite);
+ if (entry != nullptr) {
+ return *entry;
+ }
+
+ const FunctionDeclaration& decl = fn.declaration();
+ size_t slot = this->createSlot("[" + std::string(decl.name()) + "].result",
+ decl.returnType(),
+ this->getLine(fn.fPosition),
+ /*fnReturnValue=*/1);
+ fSlotMap.set(&callSite, slot);
+ return slot;
+}
+
+void SkVMGenerator::recursiveBinaryCompare(
+ const Value& lVal,
+ const Type& lType,
+ const Value& rVal,
+ const Type& rType,
+ size_t* slotOffset,
+ Value* result,
+ const std::function<Value(skvm::F32 x, skvm::F32 y)>& float_comp,
+ const std::function<Value(skvm::I32 x, skvm::I32 y)>& int_comp) {
+ switch (lType.typeKind()) {
+ case Type::TypeKind::kStruct:
+ SkASSERT(rType.typeKind() == Type::TypeKind::kStruct);
+ // Go through all the fields
+ for (size_t f = 0; f < lType.fields().size(); ++f) {
+ const Type::Field& lField = lType.fields()[f];
+ const Type::Field& rField = rType.fields()[f];
+ this->recursiveBinaryCompare(lVal,
+ *lField.fType,
+ rVal,
+ *rField.fType,
+ slotOffset,
+ result,
+ float_comp,
+ int_comp);
+ }
+ break;
+
+ case Type::TypeKind::kArray:
+ case Type::TypeKind::kVector:
+ case Type::TypeKind::kMatrix:
+ SkASSERT(lType.typeKind() == rType.typeKind());
+ // Go through all the elements
+ for (int c = 0; c < lType.columns(); ++c) {
+ this->recursiveBinaryCompare(lVal,
+ lType.componentType(),
+ rVal,
+ rType.componentType(),
+ slotOffset,
+ result,
+ float_comp,
+ int_comp);
+ }
+ break;
+ default:
+ SkASSERT(lType.typeKind() == rType.typeKind() &&
+ lType.slotCount() == rType.slotCount());
+ Type::NumberKind nk = base_number_kind(lType);
+ auto L = lVal[*slotOffset];
+ auto R = rVal[*slotOffset];
+ (*result)[*slotOffset] =
+ i32(nk == Type::NumberKind::kFloat
+ ? float_comp(f32(L), f32(R))
+ : int_comp(i32(L), i32(R))).id;
+ *slotOffset += lType.slotCount();
+ break;
+ }
+}
+
+Value SkVMGenerator::writeBinaryExpression(const BinaryExpression& b) {
+ const Expression& left = *b.left();
+ const Expression& right = *b.right();
+ Operator op = b.getOperator();
+ if (op.kind() == Operator::Kind::EQ) {
+ return this->writeStore(left, this->writeExpression(right));
+ }
+
+ const Type& lType = left.type();
+ const Type& rType = right.type();
+ bool lVecOrMtx = (lType.isVector() || lType.isMatrix());
+ bool rVecOrMtx = (rType.isVector() || rType.isMatrix());
+ bool isAssignment = op.isAssignment();
+ if (isAssignment) {
+ op = op.removeAssignment();
+ }
+ Type::NumberKind nk = base_number_kind(lType);
+
+ // A few ops require special treatment:
+ switch (op.kind()) {
+ case Operator::Kind::LOGICALAND: {
+ SkASSERT(!isAssignment);
+ SkASSERT(nk == Type::NumberKind::kBoolean);
+ skvm::I32 lVal = i32(this->writeExpression(left));
+ ScopedCondition shortCircuit(this, lVal);
+ skvm::I32 rVal = i32(this->writeExpression(right));
+ return lVal & rVal;
+ }
+ case Operator::Kind::LOGICALOR: {
+ SkASSERT(!isAssignment);
+ SkASSERT(nk == Type::NumberKind::kBoolean);
+ skvm::I32 lVal = i32(this->writeExpression(left));
+ ScopedCondition shortCircuit(this, ~lVal);
+ skvm::I32 rVal = i32(this->writeExpression(right));
+ return lVal | rVal;
+ }
+ case Operator::Kind::COMMA:
+ // We write the left side of the expression to preserve its side effects, even though we
+ // immediately discard the result.
+ this->writeExpression(left);
+ return this->writeExpression(right);
+ default:
+ break;
+ }
+
+ // All of the other ops always evaluate both sides of the expression
+ Value lVal = this->writeExpression(left),
+ rVal = this->writeExpression(right);
+
+ // Special case for M*V, V*M, M*M (but not V*V!)
+ if (op.kind() == Operator::Kind::STAR
+ && lVecOrMtx && rVecOrMtx && !(lType.isVector() && rType.isVector())) {
+ int rCols = rType.columns(),
+ rRows = rType.rows(),
+ lCols = lType.columns(),
+ lRows = lType.rows();
+ // M*V treats the vector as a column
+ if (rType.isVector()) {
+ std::swap(rCols, rRows);
+ }
+ SkASSERT(lCols == rRows);
+ SkASSERT(b.type().slotCount() == static_cast<size_t>(lRows * rCols));
+ Value result(lRows * rCols);
+ size_t resultIdx = 0;
+ const skvm::F32 zero = fBuilder->splat(0.0f);
+ for (int c = 0; c < rCols; ++c)
+ for (int r = 0; r < lRows; ++r) {
+ skvm::F32 sum = zero;
+ for (int j = 0; j < lCols; ++j) {
+ sum += f32(lVal[j*lRows + r]) * f32(rVal[c*rRows + j]);
+ }
+ result[resultIdx++] = sum;
+ }
+ SkASSERT(resultIdx == result.slots());
+ return isAssignment ? this->writeStore(left, result) : result;
+ }
+
+ size_t nslots = std::max(lVal.slots(), rVal.slots());
+
+ auto binary = [&](const std::function <Value(skvm::F32 x, skvm::F32 y)>& f_fn,
+ const std::function <Value(skvm::I32 x, skvm::I32 y)>& i_fn,
+ bool foldResults = false) -> Value {
+
+ Value result(nslots);
+ if (op.isEquality() && (lType.isStruct() || lType.isArray())) {
+ // Shifting over lVal and rVal
+ size_t slotOffset = 0;
+ this->recursiveBinaryCompare(
+ lVal, lType, rVal, rType, &slotOffset, &result, f_fn, i_fn);
+ SkASSERT(slotOffset == nslots);
+ } else {
+ for (size_t slot = 0; slot < nslots; ++slot) {
+ // If one side is scalar, replicate it to all channels
+ skvm::Val L = lVal.slots() == 1 ? lVal[0] : lVal[slot],
+ R = rVal.slots() == 1 ? rVal[0] : rVal[slot];
+
+ if (nk == Type::NumberKind::kFloat) {
+ result[slot] = i32(f_fn(f32(L), f32(R)));
+ } else {
+ result[slot] = i32(i_fn(i32(L), i32(R)));
+ }
+ }
+ }
+
+ if (foldResults && nslots > 1) {
+ SkASSERT(op.isEquality());
+ skvm::I32 folded = i32(result[0]);
+ for (size_t i = 1; i < nslots; ++i) {
+ if (op.kind() == Operator::Kind::NEQ) {
+ folded |= i32(result[i]);
+ } else {
+ folded &= i32(result[i]);
+ }
+ }
+ return folded;
+ }
+
+ return isAssignment ? this->writeStore(left, result) : result;
+ };
+
+ auto unsupported_f = [&](skvm::F32, skvm::F32) {
+ SkDEBUGFAIL("Unsupported operator");
+ return skvm::F32{};
+ };
+
+ switch (op.kind()) {
+ case Operator::Kind::EQEQ:
+ SkASSERT(!isAssignment);
+ return binary([](skvm::F32 x, skvm::F32 y) { return x == y; },
+ [](skvm::I32 x, skvm::I32 y) { return x == y; }, /*foldResults=*/ true);
+ case Operator::Kind::NEQ:
+ SkASSERT(!isAssignment);
+ return binary([](skvm::F32 x, skvm::F32 y) { return x != y; },
+ [](skvm::I32 x, skvm::I32 y) { return x != y; }, /*foldResults=*/ true);
+ case Operator::Kind::GT:
+ return binary([](skvm::F32 x, skvm::F32 y) { return x > y; },
+ [](skvm::I32 x, skvm::I32 y) { return x > y; });
+ case Operator::Kind::GTEQ:
+ return binary([](skvm::F32 x, skvm::F32 y) { return x >= y; },
+ [](skvm::I32 x, skvm::I32 y) { return x >= y; });
+ case Operator::Kind::LT:
+ return binary([](skvm::F32 x, skvm::F32 y) { return x < y; },
+ [](skvm::I32 x, skvm::I32 y) { return x < y; });
+ case Operator::Kind::LTEQ:
+ return binary([](skvm::F32 x, skvm::F32 y) { return x <= y; },
+ [](skvm::I32 x, skvm::I32 y) { return x <= y; });
+
+ case Operator::Kind::PLUS:
+ return binary([](skvm::F32 x, skvm::F32 y) { return x + y; },
+ [](skvm::I32 x, skvm::I32 y) { return x + y; });
+ case Operator::Kind::MINUS:
+ return binary([](skvm::F32 x, skvm::F32 y) { return x - y; },
+ [](skvm::I32 x, skvm::I32 y) { return x - y; });
+ case Operator::Kind::STAR:
+ return binary([](skvm::F32 x, skvm::F32 y) { return x ** y; },
+ [](skvm::I32 x, skvm::I32 y) { return x * y; });
+ case Operator::Kind::SLASH:
+ // Minimum spec (GLSL ES 1.0) has very loose requirements for integer operations.
+ // (Low-end GPUs may not have integer ALUs). Given that, we are allowed to do floating
+ // point division plus rounding. Section 10.28 of the spec even clarifies that the
+ // rounding mode is undefined (but round-towards-zero is the obvious/common choice).
+ return binary([](skvm::F32 x, skvm::F32 y) { return x / y; },
+ [](skvm::I32 x, skvm::I32 y) {
+ return skvm::trunc(skvm::to_F32(x) / skvm::to_F32(y));
+ });
+
+ case Operator::Kind::BITWISEXOR:
+ case Operator::Kind::LOGICALXOR:
+ return binary(unsupported_f, [](skvm::I32 x, skvm::I32 y) { return x ^ y; });
+ case Operator::Kind::BITWISEAND:
+ return binary(unsupported_f, [](skvm::I32 x, skvm::I32 y) { return x & y; });
+ case Operator::Kind::BITWISEOR:
+ return binary(unsupported_f, [](skvm::I32 x, skvm::I32 y) { return x | y; });
+
+ // These three operators are all 'reserved' (illegal) in our minimum spec, but will require
+ // implementation in the future.
+ case Operator::Kind::PERCENT:
+ case Operator::Kind::SHL:
+ case Operator::Kind::SHR:
+ default:
+ SkDEBUGFAIL("Unsupported operator");
+ return {};
+ }
+}
+
+Value SkVMGenerator::writeAggregationConstructor(const AnyConstructor& c) {
+ Value result(c.type().slotCount());
+ size_t resultIdx = 0;
+ for (const auto &arg : c.argumentSpan()) {
+ Value tmp = this->writeExpression(*arg);
+ for (size_t tmpSlot = 0; tmpSlot < tmp.slots(); ++tmpSlot) {
+ result[resultIdx++] = tmp[tmpSlot];
+ }
+ }
+ return result;
+}
+
+Value SkVMGenerator::writeTypeConversion(const Value& src,
+ Type::NumberKind srcKind,
+ Type::NumberKind dstKind) {
+ // Conversion among "similar" types (floatN <-> halfN), (shortN <-> intN), etc. is a no-op.
+ if (srcKind == dstKind) {
+ return src;
+ }
+
+ // TODO: Handle signed vs. unsigned. GLSL ES 1.0 only has 'int', so no problem yet.
+ Value dst(src.slots());
+ switch (dstKind) {
+ case Type::NumberKind::kFloat:
+ if (srcKind == Type::NumberKind::kSigned) {
+ // int -> float
+ for (size_t i = 0; i < src.slots(); ++i) {
+ dst[i] = skvm::to_F32(i32(src[i]));
+ }
+ return dst;
+ }
+ if (srcKind == Type::NumberKind::kBoolean) {
+ // bool -> float
+ for (size_t i = 0; i < src.slots(); ++i) {
+ dst[i] = skvm::select(i32(src[i]), 1.0f, 0.0f);
+ }
+ return dst;
+ }
+ break;
+
+ case Type::NumberKind::kSigned:
+ if (srcKind == Type::NumberKind::kFloat) {
+ // float -> int
+ for (size_t i = 0; i < src.slots(); ++i) {
+ dst[i] = skvm::trunc(f32(src[i]));
+ }
+ return dst;
+ }
+ if (srcKind == Type::NumberKind::kBoolean) {
+ // bool -> int
+ for (size_t i = 0; i < src.slots(); ++i) {
+ dst[i] = skvm::select(i32(src[i]), 1, 0);
+ }
+ return dst;
+ }
+ break;
+
+ case Type::NumberKind::kBoolean:
+ if (srcKind == Type::NumberKind::kSigned) {
+ // int -> bool
+ for (size_t i = 0; i < src.slots(); ++i) {
+ dst[i] = i32(src[i]) != 0;
+ }
+ return dst;
+ }
+ if (srcKind == Type::NumberKind::kFloat) {
+ // float -> bool
+ for (size_t i = 0; i < src.slots(); ++i) {
+ dst[i] = f32(src[i]) != 0.0;
+ }
+ return dst;
+ }
+ break;
+
+ default:
+ break;
+ }
+ SkDEBUGFAILF("Unsupported type conversion: %d -> %d", (int)srcKind, (int)dstKind);
+ return {};
+}
+
+Value SkVMGenerator::writeConstructorCast(const AnyConstructor& c) {
+ auto arguments = c.argumentSpan();
+ SkASSERT(arguments.size() == 1);
+ const Expression& argument = *arguments.front();
+
+ const Type& srcType = argument.type();
+ const Type& dstType = c.type();
+ Type::NumberKind srcKind = base_number_kind(srcType);
+ Type::NumberKind dstKind = base_number_kind(dstType);
+ Value src = this->writeExpression(argument);
+ return this->writeTypeConversion(src, srcKind, dstKind);
+}
+
+Value SkVMGenerator::writeConstructorSplat(const ConstructorSplat& c) {
+ SkASSERT(c.type().isVector());
+ SkASSERT(c.argument()->type().isScalar());
+ int columns = c.type().columns();
+
+ // Splat the argument across all components of a vector.
+ Value src = this->writeExpression(*c.argument());
+ Value dst(columns);
+ for (int i = 0; i < columns; ++i) {
+ dst[i] = src[0];
+ }
+ return dst;
+}
+
+Value SkVMGenerator::writeConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& ctor) {
+ const Type& dstType = ctor.type();
+ SkASSERT(dstType.isMatrix());
+ SkASSERT(ctor.argument()->type().matches(dstType.componentType()));
+
+ Value src = this->writeExpression(*ctor.argument());
+ Value dst(dstType.rows() * dstType.columns());
+ size_t dstIndex = 0;
+
+ // Matrix-from-scalar builds a diagonal scale matrix
+ const skvm::F32 zero = fBuilder->splat(0.0f);
+ for (int c = 0; c < dstType.columns(); ++c) {
+ for (int r = 0; r < dstType.rows(); ++r) {
+ dst[dstIndex++] = (c == r ? f32(src) : zero);
+ }
+ }
+
+ SkASSERT(dstIndex == dst.slots());
+ return dst;
+}
+
+Value SkVMGenerator::writeConstructorMatrixResize(const ConstructorMatrixResize& ctor) {
+ const Type& srcType = ctor.argument()->type();
+ const Type& dstType = ctor.type();
+ Value src = this->writeExpression(*ctor.argument());
+ Value dst(dstType.rows() * dstType.columns());
+
+ // Matrix-from-matrix uses src where it overlaps, and fills in missing fields with identity.
+ size_t dstIndex = 0;
+ for (int c = 0; c < dstType.columns(); ++c) {
+ for (int r = 0; r < dstType.rows(); ++r) {
+ if (c < srcType.columns() && r < srcType.rows()) {
+ dst[dstIndex++] = src[c * srcType.rows() + r];
+ } else {
+ dst[dstIndex++] = fBuilder->splat(c == r ? 1.0f : 0.0f);
+ }
+ }
+ }
+
+ SkASSERT(dstIndex == dst.slots());
+ return dst;
+}
+
+Value SkVMGenerator::writeFieldAccess(const FieldAccess& expr) {
+ Value base = this->writeExpression(*expr.base());
+ Value field(expr.type().slotCount());
+ size_t offset = expr.initialSlot();
+ for (size_t i = 0; i < field.slots(); ++i) {
+ field[i] = base[offset + i];
+ }
+ return field;
+}
+
+size_t SkVMGenerator::indexSlotOffset(const IndexExpression& expr) {
+ Value index = this->writeExpression(*expr.index());
+ int indexValue = -1;
+ SkAssertResult(fBuilder->allImm(index[0], &indexValue));
+
+ // When indexing by a literal, the front-end guarantees that we don't go out of bounds.
+ // But when indexing by a loop variable, it's possible to generate out-of-bounds access.
+ // The GLSL spec leaves that behavior undefined - we'll just clamp everything here.
+ indexValue = SkTPin(indexValue, 0, expr.base()->type().columns() - 1);
+
+ size_t stride = expr.type().slotCount();
+ return indexValue * stride;
+}
+
+Value SkVMGenerator::writeIndexExpression(const IndexExpression& expr) {
+ Value base = this->writeExpression(*expr.base());
+ Value element(expr.type().slotCount());
+ size_t offset = this->indexSlotOffset(expr);
+ for (size_t i = 0; i < element.slots(); ++i) {
+ element[i] = base[offset + i];
+ }
+ return element;
+}
+
+Value SkVMGenerator::writeVariableExpression(const VariableReference& expr) {
+ size_t slot = this->getSlot(*expr.variable());
+ return this->getSlotValue(slot, expr.type().slotCount());
+}
+
+Value SkVMGenerator::writeMatrixInverse2x2(const Value& m) {
+ SkASSERT(m.slots() == 4);
+ skvm::F32 a = f32(m[0]),
+ b = f32(m[1]),
+ c = f32(m[2]),
+ d = f32(m[3]);
+ skvm::F32 idet = 1.0f / (a*d - b*c);
+
+ Value result(m.slots());
+ result[0] = ( d ** idet);
+ result[1] = (-b ** idet);
+ result[2] = (-c ** idet);
+ result[3] = ( a ** idet);
+ return result;
+}
+
+Value SkVMGenerator::writeMatrixInverse3x3(const Value& m) {
+ SkASSERT(m.slots() == 9);
+ skvm::F32 a11 = f32(m[0]), a12 = f32(m[3]), a13 = f32(m[6]),
+ a21 = f32(m[1]), a22 = f32(m[4]), a23 = f32(m[7]),
+ a31 = f32(m[2]), a32 = f32(m[5]), a33 = f32(m[8]);
+ skvm::F32 idet = 1.0f / (a11*a22*a33 + a12*a23*a31 + a13*a21*a32 -
+ a11*a23*a32 - a12*a21*a33 - a13*a22*a31);
+
+ Value result(m.slots());
+ result[0] = ((a22**a33 - a23**a32) ** idet);
+ result[1] = ((a23**a31 - a21**a33) ** idet);
+ result[2] = ((a21**a32 - a22**a31) ** idet);
+ result[3] = ((a13**a32 - a12**a33) ** idet);
+ result[4] = ((a11**a33 - a13**a31) ** idet);
+ result[5] = ((a12**a31 - a11**a32) ** idet);
+ result[6] = ((a12**a23 - a13**a22) ** idet);
+ result[7] = ((a13**a21 - a11**a23) ** idet);
+ result[8] = ((a11**a22 - a12**a21) ** idet);
+ return result;
+}
+
+Value SkVMGenerator::writeMatrixInverse4x4(const Value& m) {
+ SkASSERT(m.slots() == 16);
+ skvm::F32 a00 = f32(m[0]), a10 = f32(m[4]), a20 = f32(m[ 8]), a30 = f32(m[12]),
+ a01 = f32(m[1]), a11 = f32(m[5]), a21 = f32(m[ 9]), a31 = f32(m[13]),
+ a02 = f32(m[2]), a12 = f32(m[6]), a22 = f32(m[10]), a32 = f32(m[14]),
+ a03 = f32(m[3]), a13 = f32(m[7]), a23 = f32(m[11]), a33 = f32(m[15]);
+
+ skvm::F32 b00 = a00**a11 - a01**a10,
+ b01 = a00**a12 - a02**a10,
+ b02 = a00**a13 - a03**a10,
+ b03 = a01**a12 - a02**a11,
+ b04 = a01**a13 - a03**a11,
+ b05 = a02**a13 - a03**a12,
+ b06 = a20**a31 - a21**a30,
+ b07 = a20**a32 - a22**a30,
+ b08 = a20**a33 - a23**a30,
+ b09 = a21**a32 - a22**a31,
+ b10 = a21**a33 - a23**a31,
+ b11 = a22**a33 - a23**a32;
+
+ skvm::F32 idet = 1.0f / (b00**b11 - b01**b10 + b02**b09 + b03**b08 - b04**b07 + b05**b06);
+
+ b00 *= idet;
+ b01 *= idet;
+ b02 *= idet;
+ b03 *= idet;
+ b04 *= idet;
+ b05 *= idet;
+ b06 *= idet;
+ b07 *= idet;
+ b08 *= idet;
+ b09 *= idet;
+ b10 *= idet;
+ b11 *= idet;
+
+ Value result(m.slots());
+ result[ 0] = (a11*b11 - a12*b10 + a13*b09);
+ result[ 1] = (a02*b10 - a01*b11 - a03*b09);
+ result[ 2] = (a31*b05 - a32*b04 + a33*b03);
+ result[ 3] = (a22*b04 - a21*b05 - a23*b03);
+ result[ 4] = (a12*b08 - a10*b11 - a13*b07);
+ result[ 5] = (a00*b11 - a02*b08 + a03*b07);
+ result[ 6] = (a32*b02 - a30*b05 - a33*b01);
+ result[ 7] = (a20*b05 - a22*b02 + a23*b01);
+ result[ 8] = (a10*b10 - a11*b08 + a13*b06);
+ result[ 9] = (a01*b08 - a00*b10 - a03*b06);
+ result[10] = (a30*b04 - a31*b02 + a33*b00);
+ result[11] = (a21*b02 - a20*b04 - a23*b00);
+ result[12] = (a11*b07 - a10*b09 - a12*b06);
+ result[13] = (a00*b09 - a01*b07 + a02*b06);
+ result[14] = (a31*b01 - a30*b03 - a32*b00);
+ result[15] = (a20*b03 - a21*b01 + a22*b00);
+ return result;
+}
+
+Value SkVMGenerator::writeChildCall(const ChildCall& c) {
+ size_t* childPtr = fSlotMap.find(&c.child());
+ SkASSERT(childPtr != nullptr);
+
+ const Expression* arg = c.arguments()[0].get();
+ Value argVal = this->writeExpression(*arg);
+ skvm::Color color;
+
+ switch (c.child().type().typeKind()) {
+ case Type::TypeKind::kShader: {
+ SkASSERT(c.arguments().size() == 1);
+ SkASSERT(arg->type().matches(*fProgram.fContext->fTypes.fFloat2));
+ skvm::Coord coord = {f32(argVal[0]), f32(argVal[1])};
+ color = fCallbacks->sampleShader(*childPtr, coord);
+ break;
+ }
+ case Type::TypeKind::kColorFilter: {
+ SkASSERT(c.arguments().size() == 1);
+ SkASSERT(arg->type().matches(*fProgram.fContext->fTypes.fHalf4) ||
+ arg->type().matches(*fProgram.fContext->fTypes.fFloat4));
+ skvm::Color inColor = {f32(argVal[0]), f32(argVal[1]), f32(argVal[2]), f32(argVal[3])};
+ color = fCallbacks->sampleColorFilter(*childPtr, inColor);
+ break;
+ }
+ case Type::TypeKind::kBlender: {
+ SkASSERT(c.arguments().size() == 2);
+ SkASSERT(arg->type().matches(*fProgram.fContext->fTypes.fHalf4) ||
+ arg->type().matches(*fProgram.fContext->fTypes.fFloat4));
+ skvm::Color srcColor = {f32(argVal[0]), f32(argVal[1]), f32(argVal[2]), f32(argVal[3])};
+
+ arg = c.arguments()[1].get();
+ argVal = this->writeExpression(*arg);
+ SkASSERT(arg->type().matches(*fProgram.fContext->fTypes.fHalf4) ||
+ arg->type().matches(*fProgram.fContext->fTypes.fFloat4));
+ skvm::Color dstColor = {f32(argVal[0]), f32(argVal[1]), f32(argVal[2]), f32(argVal[3])};
+
+ color = fCallbacks->sampleBlender(*childPtr, srcColor, dstColor);
+ break;
+ }
+ default: {
+ SkDEBUGFAILF("cannot sample from type '%s'", c.child().type().description().c_str());
+ }
+ }
+
+ Value result(4);
+ result[0] = color.r;
+ result[1] = color.g;
+ result[2] = color.b;
+ result[3] = color.a;
+ return result;
+}
+
+Value SkVMGenerator::writeIntrinsicCall(const FunctionCall& c) {
+ IntrinsicKind intrinsicKind = c.function().intrinsicKind();
+ SkASSERT(intrinsicKind != kNotIntrinsic);
+
+ const size_t nargs = c.arguments().size();
+ const size_t kMaxArgs = 3; // eg: clamp, mix, smoothstep
+ Value args[kMaxArgs];
+ SkASSERT(nargs >= 1 && nargs <= std::size(args));
+
+ // All other intrinsics have at most three args, and those can all be evaluated up front:
+ for (size_t i = 0; i < nargs; ++i) {
+ args[i] = this->writeExpression(*c.arguments()[i]);
+ }
+ Type::NumberKind nk = base_number_kind(c.arguments()[0]->type());
+
+ auto binary = [&](auto&& fn) {
+ // Binary intrinsics are (vecN, vecN), (vecN, float), or (float, vecN)
+ size_t nslots = std::max(args[0].slots(), args[1].slots());
+ Value result(nslots);
+ SkASSERT(args[0].slots() == nslots || args[0].slots() == 1);
+ SkASSERT(args[1].slots() == nslots || args[1].slots() == 1);
+
+ for (size_t i = 0; i < nslots; ++i) {
+ result[i] = fn({fBuilder, args[0][args[0].slots() == 1 ? 0 : i]},
+ {fBuilder, args[1][args[1].slots() == 1 ? 0 : i]});
+ }
+ return result;
+ };
+
+ auto ternary = [&](auto&& fn) {
+ // Ternary intrinsics are some combination of vecN and float
+ size_t nslots = std::max({args[0].slots(), args[1].slots(), args[2].slots()});
+ Value result(nslots);
+ SkASSERT(args[0].slots() == nslots || args[0].slots() == 1);
+ SkASSERT(args[1].slots() == nslots || args[1].slots() == 1);
+ SkASSERT(args[2].slots() == nslots || args[2].slots() == 1);
+
+ for (size_t i = 0; i < nslots; ++i) {
+ result[i] = fn({fBuilder, args[0][args[0].slots() == 1 ? 0 : i]},
+ {fBuilder, args[1][args[1].slots() == 1 ? 0 : i]},
+ {fBuilder, args[2][args[2].slots() == 1 ? 0 : i]});
+ }
+ return result;
+ };
+
+ auto dot = [&](const Value& x, const Value& y) {
+ SkASSERT(x.slots() == y.slots());
+ skvm::F32 result = f32(x[0]) * f32(y[0]);
+ for (size_t i = 1; i < x.slots(); ++i) {
+ result += f32(x[i]) * f32(y[i]);
+ }
+ return result;
+ };
+
+ switch (intrinsicKind) {
+ case k_radians_IntrinsicKind:
+ return unary(args[0], [](skvm::F32 deg) { return deg * (SK_FloatPI / 180); });
+ case k_degrees_IntrinsicKind:
+ return unary(args[0], [](skvm::F32 rad) { return rad * (180 / SK_FloatPI); });
+
+ case k_sin_IntrinsicKind: return unary(args[0], skvm::approx_sin);
+ case k_cos_IntrinsicKind: return unary(args[0], skvm::approx_cos);
+ case k_tan_IntrinsicKind: return unary(args[0], skvm::approx_tan);
+
+ case k_asin_IntrinsicKind: return unary(args[0], skvm::approx_asin);
+ case k_acos_IntrinsicKind: return unary(args[0], skvm::approx_acos);
+
+ case k_atan_IntrinsicKind: return nargs == 1 ? unary(args[0], skvm::approx_atan)
+ : binary(skvm::approx_atan2);
+
+ case k_pow_IntrinsicKind:
+ return binary([](skvm::F32 x, skvm::F32 y) { return skvm::approx_powf(x, y); });
+ case k_exp_IntrinsicKind: return unary(args[0], skvm::approx_exp);
+ case k_log_IntrinsicKind: return unary(args[0], skvm::approx_log);
+ case k_exp2_IntrinsicKind: return unary(args[0], skvm::approx_pow2);
+ case k_log2_IntrinsicKind: return unary(args[0], skvm::approx_log2);
+
+ case k_sqrt_IntrinsicKind: return unary(args[0], skvm::sqrt);
+ case k_inversesqrt_IntrinsicKind:
+ return unary(args[0], [](skvm::F32 x) { return 1.0f / skvm::sqrt(x); });
+
+ case k_abs_IntrinsicKind: return unary(args[0], skvm::abs);
+ case k_sign_IntrinsicKind:
+ return unary(args[0], [](skvm::F32 x) { return select(x < 0, -1.0f,
+ select(x > 0, +1.0f, 0.0f)); });
+ case k_floor_IntrinsicKind: return unary(args[0], skvm::floor);
+ case k_ceil_IntrinsicKind: return unary(args[0], skvm::ceil);
+ case k_fract_IntrinsicKind: return unary(args[0], skvm::fract);
+ case k_mod_IntrinsicKind:
+ return binary([](skvm::F32 x, skvm::F32 y) { return x - y*skvm::floor(x / y); });
+
+ case k_min_IntrinsicKind:
+ return binary([](skvm::F32 x, skvm::F32 y) { return skvm::min(x, y); });
+ case k_max_IntrinsicKind:
+ return binary([](skvm::F32 x, skvm::F32 y) { return skvm::max(x, y); });
+ case k_clamp_IntrinsicKind:
+ return ternary(
+ [](skvm::F32 x, skvm::F32 lo, skvm::F32 hi) { return skvm::clamp(x, lo, hi); });
+ case k_saturate_IntrinsicKind:
+ return unary(args[0], [](skvm::F32 x) { return skvm::clamp01(x); });
+ case k_mix_IntrinsicKind:
+ return ternary(
+ [](skvm::F32 x, skvm::F32 y, skvm::F32 t) { return skvm::lerp(x, y, t); });
+ case k_step_IntrinsicKind:
+ return binary([](skvm::F32 edge, skvm::F32 x) { return select(x < edge, 0.0f, 1.0f); });
+ case k_smoothstep_IntrinsicKind:
+ return ternary([](skvm::F32 edge0, skvm::F32 edge1, skvm::F32 x) {
+ skvm::F32 t = skvm::clamp01((x - edge0) / (edge1 - edge0));
+ return t ** t ** (3 - 2 ** t);
+ });
+
+ case k_length_IntrinsicKind: return skvm::sqrt(dot(args[0], args[0]));
+ case k_distance_IntrinsicKind: {
+ Value vec = binary([](skvm::F32 x, skvm::F32 y) { return x - y; });
+ return skvm::sqrt(dot(vec, vec));
+ }
+ case k_dot_IntrinsicKind: return dot(args[0], args[1]);
+ case k_cross_IntrinsicKind: {
+ skvm::F32 ax = f32(args[0][0]), ay = f32(args[0][1]), az = f32(args[0][2]),
+ bx = f32(args[1][0]), by = f32(args[1][1]), bz = f32(args[1][2]);
+ Value result(3);
+ result[0] = ay**bz - az**by;
+ result[1] = az**bx - ax**bz;
+ result[2] = ax**by - ay**bx;
+ return result;
+ }
+ case k_normalize_IntrinsicKind: {
+ skvm::F32 invLen = 1.0f / skvm::sqrt(dot(args[0], args[0]));
+ return unary(args[0], [&](skvm::F32 x) { return x ** invLen; });
+ }
+ case k_faceforward_IntrinsicKind: {
+ const Value &N = args[0],
+ &I = args[1],
+ &Nref = args[2];
+
+ skvm::F32 dotNrefI = dot(Nref, I);
+ return unary(N, [&](skvm::F32 n) { return select(dotNrefI<0, n, -n); });
+ }
+ case k_reflect_IntrinsicKind: {
+ const Value &I = args[0],
+ &N = args[1];
+
+ skvm::F32 dotNI = dot(N, I);
+ return binary([&](skvm::F32 i, skvm::F32 n) {
+ return i - 2**dotNI**n;
+ });
+ }
+ case k_refract_IntrinsicKind: {
+ const Value &I = args[0],
+ &N = args[1];
+ skvm::F32 eta = f32(args[2]);
+
+ skvm::F32 dotNI = dot(N, I),
+ k = 1 - eta**eta**(1 - dotNI**dotNI);
+ return binary([&](skvm::F32 i, skvm::F32 n) {
+ return select(k<0, 0.0f, eta**i - (eta**dotNI + sqrt(k))**n);
+ });
+ }
+
+ case k_matrixCompMult_IntrinsicKind:
+ return binary([](skvm::F32 x, skvm::F32 y) { return x ** y; });
+ case k_inverse_IntrinsicKind: {
+ switch (args[0].slots()) {
+ case 4: return this->writeMatrixInverse2x2(args[0]);
+ case 9: return this->writeMatrixInverse3x3(args[0]);
+ case 16: return this->writeMatrixInverse4x4(args[0]);
+ default:
+ SkDEBUGFAIL("Invalid call to inverse");
+ return {};
+ }
+ }
+
+ case k_lessThan_IntrinsicKind:
+ return nk == Type::NumberKind::kFloat
+ ? binary([](skvm::F32 x, skvm::F32 y) { return x < y; })
+ : binary([](skvm::I32 x, skvm::I32 y) { return x < y; });
+ case k_lessThanEqual_IntrinsicKind:
+ return nk == Type::NumberKind::kFloat
+ ? binary([](skvm::F32 x, skvm::F32 y) { return x <= y; })
+ : binary([](skvm::I32 x, skvm::I32 y) { return x <= y; });
+ case k_greaterThan_IntrinsicKind:
+ return nk == Type::NumberKind::kFloat
+ ? binary([](skvm::F32 x, skvm::F32 y) { return x > y; })
+ : binary([](skvm::I32 x, skvm::I32 y) { return x > y; });
+ case k_greaterThanEqual_IntrinsicKind:
+ return nk == Type::NumberKind::kFloat
+ ? binary([](skvm::F32 x, skvm::F32 y) { return x >= y; })
+ : binary([](skvm::I32 x, skvm::I32 y) { return x >= y; });
+
+ case k_equal_IntrinsicKind:
+ return nk == Type::NumberKind::kFloat
+ ? binary([](skvm::F32 x, skvm::F32 y) { return x == y; })
+ : binary([](skvm::I32 x, skvm::I32 y) { return x == y; });
+ case k_notEqual_IntrinsicKind:
+ return nk == Type::NumberKind::kFloat
+ ? binary([](skvm::F32 x, skvm::F32 y) { return x != y; })
+ : binary([](skvm::I32 x, skvm::I32 y) { return x != y; });
+
+ case k_any_IntrinsicKind: {
+ skvm::I32 result = i32(args[0][0]);
+ for (size_t i = 1; i < args[0].slots(); ++i) {
+ result |= i32(args[0][i]);
+ }
+ return result;
+ }
+ case k_all_IntrinsicKind: {
+ skvm::I32 result = i32(args[0][0]);
+ for (size_t i = 1; i < args[0].slots(); ++i) {
+ result &= i32(args[0][i]);
+ }
+ return result;
+ }
+ case k_not_IntrinsicKind: return unary(args[0], [](skvm::I32 x) { return ~x; });
+
+ case k_toLinearSrgb_IntrinsicKind: {
+ skvm::Color color = {
+ f32(args[0][0]), f32(args[0][1]), f32(args[0][2]), fBuilder->splat(1.0f)};
+ color = fCallbacks->toLinearSrgb(color);
+ Value result(3);
+ result[0] = color.r;
+ result[1] = color.g;
+ result[2] = color.b;
+ return result;
+ }
+ case k_fromLinearSrgb_IntrinsicKind: {
+ skvm::Color color = {
+ f32(args[0][0]), f32(args[0][1]), f32(args[0][2]), fBuilder->splat(1.0f)};
+ color = fCallbacks->fromLinearSrgb(color);
+ Value result(3);
+ result[0] = color.r;
+ result[1] = color.g;
+ result[2] = color.b;
+ return result;
+ }
+
+ default:
+ SkDEBUGFAILF("unsupported intrinsic %s", c.function().description().c_str());
+ return {};
+ }
+ SkUNREACHABLE;
+}
+
+Value SkVMGenerator::writeFunctionCall(const FunctionCall& call) {
+ if (call.function().isIntrinsic() && !call.function().definition()) {
+ return this->writeIntrinsicCall(call);
+ }
+
+ const FunctionDeclaration& decl = call.function();
+ SkASSERTF(decl.definition(), "no definition for function '%s'", decl.description().c_str());
+ const FunctionDefinition& funcDef = *decl.definition();
+
+ // Evaluate all arguments, gather the results into a contiguous list of IDs
+ std::vector<skvm::Val> argVals;
+ for (const auto& arg : call.arguments()) {
+ Value v = this->writeExpression(*arg);
+ for (size_t i = 0; i < v.slots(); ++i) {
+ argVals.push_back(v[i]);
+ }
+ }
+
+ size_t returnSlot;
+ {
+ // This merges currentFunction().fReturned into fConditionMask. Lanes that conditionally
+ // returned in the current function would otherwise resume execution within the child.
+ ScopedCondition m(this, ~currentFunction().fReturned);
+ returnSlot = this->writeFunction(call, funcDef, SkSpan(argVals));
+ }
+
+ // Propagate new values of any 'out' params back to the original arguments
+ const std::unique_ptr<Expression>* argIter = call.arguments().begin();
+ size_t valIdx = 0;
+ for (const Variable* p : decl.parameters()) {
+ size_t nslots = p->type().slotCount();
+ if (p->modifiers().fFlags & Modifiers::kOut_Flag) {
+ Value v(nslots);
+ for (size_t i = 0; i < nslots; ++i) {
+ v[i] = argVals[valIdx + i];
+ }
+ const std::unique_ptr<Expression>& arg = *argIter;
+ this->writeStore(*arg, v);
+ }
+ valIdx += nslots;
+ argIter++;
+ }
+
+ // Create a result Value from the return slot
+ return this->getSlotValue(returnSlot, call.type().slotCount());
+}
+
+Value SkVMGenerator::writeLiteral(const Literal& l) {
+ if (l.type().isFloat()) {
+ return fBuilder->splat(l.as<Literal>().floatValue());
+ }
+ if (l.type().isInteger()) {
+ return fBuilder->splat(static_cast<int>(l.as<Literal>().intValue()));
+ }
+ SkASSERT(l.type().isBoolean());
+ return fBuilder->splat(l.as<Literal>().boolValue() ? ~0 : 0);
+}
+
+Value SkVMGenerator::writePrefixExpression(const PrefixExpression& p) {
+ Value val = this->writeExpression(*p.operand());
+
+ switch (p.getOperator().kind()) {
+ case Operator::Kind::PLUSPLUS:
+ case Operator::Kind::MINUSMINUS: {
+ bool incr = p.getOperator().kind() == Operator::Kind::PLUSPLUS;
+
+ switch (base_number_kind(p.type())) {
+ case Type::NumberKind::kFloat:
+ val = f32(val) + fBuilder->splat(incr ? 1.0f : -1.0f);
+ break;
+ case Type::NumberKind::kSigned:
+ val = i32(val) + fBuilder->splat(incr ? 1 : -1);
+ break;
+ default:
+ SkASSERT(false);
+ return {};
+ }
+ return this->writeStore(*p.operand(), val);
+ }
+ case Operator::Kind::MINUS: {
+ switch (base_number_kind(p.type())) {
+ case Type::NumberKind::kFloat:
+ return this->unary(val, [](skvm::F32 x) { return -x; });
+ case Type::NumberKind::kSigned:
+ return this->unary(val, [](skvm::I32 x) { return -x; });
+ default:
+ SkASSERT(false);
+ return {};
+ }
+ }
+ case Operator::Kind::LOGICALNOT:
+ case Operator::Kind::BITWISENOT:
+ return this->unary(val, [](skvm::I32 x) { return ~x; });
+ default:
+ SkASSERT(false);
+ return {};
+ }
+}
+
+Value SkVMGenerator::writePostfixExpression(const PostfixExpression& p) {
+ switch (p.getOperator().kind()) {
+ case Operator::Kind::PLUSPLUS:
+ case Operator::Kind::MINUSMINUS: {
+ Value old = this->writeExpression(*p.operand()),
+ val = old;
+ SkASSERT(val.slots() == 1);
+ bool incr = p.getOperator().kind() == Operator::Kind::PLUSPLUS;
+
+ switch (base_number_kind(p.type())) {
+ case Type::NumberKind::kFloat:
+ val = f32(val) + fBuilder->splat(incr ? 1.0f : -1.0f);
+ break;
+ case Type::NumberKind::kSigned:
+ val = i32(val) + fBuilder->splat(incr ? 1 : -1);
+ break;
+ default:
+ SkASSERT(false);
+ return {};
+ }
+ this->writeStore(*p.operand(), val);
+ return old;
+ }
+ default:
+ SkASSERT(false);
+ return {};
+ }
+}
+
+Value SkVMGenerator::writeSwizzle(const Swizzle& s) {
+ Value base = this->writeExpression(*s.base());
+ Value swizzled(s.components().size());
+ for (int i = 0; i < s.components().size(); ++i) {
+ swizzled[i] = base[s.components()[i]];
+ }
+ return swizzled;
+}
+
+Value SkVMGenerator::writeTernaryExpression(const TernaryExpression& t) {
+ skvm::I32 test = i32(this->writeExpression(*t.test()));
+ Value ifTrue, ifFalse;
+
+ {
+ ScopedCondition m(this, test);
+ ifTrue = this->writeExpression(*t.ifTrue());
+ }
+ {
+ ScopedCondition m(this, ~test);
+ ifFalse = this->writeExpression(*t.ifFalse());
+ }
+
+ size_t nslots = ifTrue.slots();
+ SkASSERT(nslots == ifFalse.slots());
+
+ Value result(nslots);
+ for (size_t i = 0; i < nslots; ++i) {
+ result[i] = skvm::select(test, i32(ifTrue[i]), i32(ifFalse[i]));
+ }
+ return result;
+}
+
+Value SkVMGenerator::writeExpression(const Expression& e) {
+ switch (e.kind()) {
+ case Expression::Kind::kBinary:
+ return this->writeBinaryExpression(e.as<BinaryExpression>());
+ case Expression::Kind::kChildCall:
+ return this->writeChildCall(e.as<ChildCall>());
+ case Expression::Kind::kConstructorArray:
+ case Expression::Kind::kConstructorCompound:
+ case Expression::Kind::kConstructorStruct:
+ return this->writeAggregationConstructor(e.asAnyConstructor());
+ case Expression::Kind::kConstructorArrayCast:
+ return this->writeExpression(*e.as<ConstructorArrayCast>().argument());
+ case Expression::Kind::kConstructorDiagonalMatrix:
+ return this->writeConstructorDiagonalMatrix(e.as<ConstructorDiagonalMatrix>());
+ case Expression::Kind::kConstructorMatrixResize:
+ return this->writeConstructorMatrixResize(e.as<ConstructorMatrixResize>());
+ case Expression::Kind::kConstructorScalarCast:
+ case Expression::Kind::kConstructorCompoundCast:
+ return this->writeConstructorCast(e.asAnyConstructor());
+ case Expression::Kind::kConstructorSplat:
+ return this->writeConstructorSplat(e.as<ConstructorSplat>());
+ case Expression::Kind::kFieldAccess:
+ return this->writeFieldAccess(e.as<FieldAccess>());
+ case Expression::Kind::kIndex:
+ return this->writeIndexExpression(e.as<IndexExpression>());
+ case Expression::Kind::kVariableReference:
+ return this->writeVariableExpression(e.as<VariableReference>());
+ case Expression::Kind::kLiteral:
+ return this->writeLiteral(e.as<Literal>());
+ case Expression::Kind::kFunctionCall:
+ return this->writeFunctionCall(e.as<FunctionCall>());
+ case Expression::Kind::kPrefix:
+ return this->writePrefixExpression(e.as<PrefixExpression>());
+ case Expression::Kind::kPostfix:
+ return this->writePostfixExpression(e.as<PostfixExpression>());
+ case Expression::Kind::kSwizzle:
+ return this->writeSwizzle(e.as<Swizzle>());
+ case Expression::Kind::kTernary:
+ return this->writeTernaryExpression(e.as<TernaryExpression>());
+ default:
+ SkDEBUGFAIL("Unsupported expression");
+ return {};
+ }
+}
+
+Value SkVMGenerator::writeStore(const Expression& lhs, const Value& rhs) {
+ SkASSERTF(rhs.slots() == lhs.type().slotCount(),
+ "lhs=%s (%s)\nrhs=%zu slot",
+ lhs.type().description().c_str(), lhs.description().c_str(), rhs.slots());
+
+ // We need to figure out the collection of slots that we're storing into. The l-value (lhs)
+ // is always a VariableReference, possibly wrapped by one or more Swizzle, FieldAccess, or
+ // IndexExpressions. The underlying VariableReference has a range of slots for its storage,
+ // and each expression wrapped around that selects a sub-set of those slots (Field/Index),
+ // or rearranges them (Swizzle).
+ SkSTArray<4, size_t, true> slots;
+ slots.resize(rhs.slots());
+
+ // Start with the identity slot map - this basically says that the values from rhs belong in
+ // slots [0, 1, 2 ... N] of the lhs.
+ for (int i = 0; i < slots.size(); ++i) {
+ slots[i] = i;
+ }
+
+ // Now, as we peel off each outer expression, adjust 'slots' to be the locations relative to
+ // the next (inner) expression:
+ const Expression* expr = &lhs;
+ while (!expr->is<VariableReference>()) {
+ switch (expr->kind()) {
+ case Expression::Kind::kFieldAccess: {
+ const FieldAccess& fld = expr->as<FieldAccess>();
+ size_t offset = fld.initialSlot();
+ for (size_t& s : slots) {
+ s += offset;
+ }
+ expr = fld.base().get();
+ } break;
+ case Expression::Kind::kIndex: {
+ const IndexExpression& idx = expr->as<IndexExpression>();
+ size_t offset = this->indexSlotOffset(idx);
+ for (size_t& s : slots) {
+ s += offset;
+ }
+ expr = idx.base().get();
+ } break;
+ case Expression::Kind::kSwizzle: {
+ const Swizzle& swz = expr->as<Swizzle>();
+ for (size_t& s : slots) {
+ s = swz.components()[s];
+ }
+ expr = swz.base().get();
+ } break;
+ default:
+ // No other kinds of expressions are valid in lvalues. (see Analysis::IsAssignable)
+ SkDEBUGFAIL("Invalid expression type");
+ return {};
+ }
+ }
+
+ // When we get here, 'slots' are all relative to the first slot holding 'var's storage
+ const Variable& var = *expr->as<VariableReference>().variable();
+ size_t varSlot = this->getSlot(var);
+ for (size_t& slot : slots) {
+ SkASSERT(slot < var.type().slotCount());
+ slot += varSlot;
+ }
+
+ // `slots` are now absolute indices into `fSlots`.
+ skvm::I32 mask = this->mask();
+ for (size_t i = 0; i < rhs.slots(); ++i) {
+ int slotNum = slots[i];
+ skvm::Val conditionalStore = this->writeConditionalStore(fSlots[slotNum].val, rhs[i], mask);
+ this->writeToSlot(slotNum, conditionalStore);
+ }
+
+ return rhs;
+}
+
+skvm::Val SkVMGenerator::writeConditionalStore(skvm::Val lhs, skvm::Val rhs, skvm::I32 mask) {
+ return select(mask, f32(rhs), f32(lhs)).id;
+}
+
+void SkVMGenerator::writeBlock(const Block& b) {
+ skvm::I32 mask = this->mask();
+ if (b.blockKind() == Block::Kind::kCompoundStatement) {
+ this->emitTraceLine(this->getLine(b.fPosition));
+ ++fInsideCompoundStatement;
+ } else {
+ this->emitTraceScope(mask, +1);
+ }
+
+ for (const std::unique_ptr<Statement>& stmt : b.children()) {
+ this->writeStatement(*stmt);
+ }
+
+ if (b.blockKind() == Block::Kind::kCompoundStatement) {
+ --fInsideCompoundStatement;
+ } else {
+ this->emitTraceScope(mask, -1);
+ }
+}
+
+void SkVMGenerator::writeBreakStatement() {
+ // Any active lanes stop executing for the duration of the current loop
+ fLoopMask &= ~this->mask();
+}
+
+void SkVMGenerator::writeContinueStatement() {
+ // Any active lanes stop executing for the current iteration.
+ // Remember them in fContinueMask, to be re-enabled later.
+ skvm::I32 mask = this->mask();
+ fLoopMask &= ~mask;
+ fContinueMask |= mask;
+}
+
+void SkVMGenerator::writeForStatement(const ForStatement& f) {
+ // We require that all loops be ES2-compliant (unrollable), and actually unroll them here
+ SkASSERT(f.unrollInfo());
+ const LoopUnrollInfo& loop = *f.unrollInfo();
+ SkASSERT(loop.fIndex->type().slotCount() == 1);
+
+ size_t indexSlot = this->getSlot(*loop.fIndex);
+ double val = loop.fStart;
+
+ const skvm::I32 zero = fBuilder->splat(0);
+ skvm::I32 oldLoopMask = fLoopMask,
+ oldContinueMask = fContinueMask;
+
+ const Type::NumberKind indexKind = base_number_kind(loop.fIndex->type());
+
+ // We want the loop index to disappear at the end of the loop, so wrap the for statement in a
+ // trace scope.
+ if (loop.fCount > 0) {
+ int line = this->getLine(f.test() ? f.test()->fPosition : f.fPosition);
+ skvm::I32 mask = this->mask();
+ this->emitTraceScope(mask, +1);
+
+ for (int i = 0; i < loop.fCount; ++i) {
+ this->writeToSlot(indexSlot, (indexKind == Type::NumberKind::kFloat)
+ ? fBuilder->splat(static_cast<float>(val)).id
+ : fBuilder->splat(static_cast<int>(val)).id);
+
+ fContinueMask = zero;
+ this->writeStatement(*f.statement());
+ fLoopMask |= fContinueMask;
+
+ this->emitTraceLine(line);
+ val += loop.fDelta;
+ }
+
+ this->emitTraceScope(mask, -1);
+ }
+
+ fLoopMask = oldLoopMask;
+ fContinueMask = oldContinueMask;
+}
+
+void SkVMGenerator::writeIfStatement(const IfStatement& i) {
+ Value test = this->writeExpression(*i.test());
+ {
+ ScopedCondition ifTrue(this, i32(test));
+ this->writeStatement(*i.ifTrue());
+ }
+ if (i.ifFalse()) {
+ ScopedCondition ifFalse(this, ~i32(test));
+ this->writeStatement(*i.ifFalse());
+ }
+}
+
+void SkVMGenerator::writeReturnStatement(const ReturnStatement& r) {
+ skvm::I32 returnsHere = this->mask();
+
+ if (r.expression()) {
+ Value val = this->writeExpression(*r.expression());
+
+ size_t slot = currentFunction().fReturnSlot;
+ size_t nslots = r.expression()->type().slotCount();
+ for (size_t i = 0; i < nslots; ++i) {
+ fSlots[slot + i].writtenTo = false;
+ skvm::Val conditionalStore = this->writeConditionalStore(fSlots[slot + i].val, val[i],
+ returnsHere);
+ this->writeToSlot(slot + i, conditionalStore);
+ }
+ }
+
+ currentFunction().fReturned |= returnsHere;
+}
+
+void SkVMGenerator::writeSwitchStatement(const SwitchStatement& s) {
+ skvm::I32 falseValue = fBuilder->splat( 0);
+ skvm::I32 trueValue = fBuilder->splat(~0);
+
+ // Create a "switchFallthough" scratch variable, initialized to false.
+ skvm::I32 switchFallthrough = falseValue;
+
+ // Loop masks behave just like for statements. When a break is encountered, it masks off all
+ // lanes for the rest of the body of the switch.
+ skvm::I32 oldLoopMask = fLoopMask;
+ Value switchValue = this->writeExpression(*s.value());
+
+ for (const std::unique_ptr<Statement>& stmt : s.cases()) {
+ const SwitchCase& c = stmt->as<SwitchCase>();
+ if (!c.isDefault()) {
+ Value caseValue = fBuilder->splat((int) c.value());
+
+ // We want to execute this switch case if we're falling through from a previous case, or
+ // if the case value matches.
+ ScopedCondition conditionalCaseBlock(
+ this,
+ switchFallthrough | (i32(caseValue) == i32(switchValue)));
+ this->writeStatement(*c.statement());
+
+ // If we are inside the case block, we set the fallthrough flag to true (`break` still
+ // works to stop the flow of execution regardless, since it zeroes out the loop-mask).
+ switchFallthrough.id = this->writeConditionalStore(switchFallthrough.id, trueValue.id,
+ this->mask());
+ } else {
+ // This is the default case. Since it's always last, we can just dump in the code.
+ this->writeStatement(*c.statement());
+ }
+ }
+
+ // Restore state.
+ fLoopMask = oldLoopMask;
+}
+
+void SkVMGenerator::writeVarDeclaration(const VarDeclaration& decl) {
+ size_t slot = this->getSlot(*decl.var()),
+ nslots = decl.var()->type().slotCount();
+
+ Value val = decl.value() ? this->writeExpression(*decl.value()) : Value{};
+ for (size_t i = 0; i < nslots; ++i) {
+ fSlots[slot + i].writtenTo = false;
+ this->writeToSlot(slot + i, val ? val[i] : fBuilder->splat(0.0f).id);
+ }
+}
+
+void SkVMGenerator::emitTraceLine(int line) {
+ if (fDebugTrace && line > 0 && fInsideCompoundStatement == 0) {
+ fBuilder->trace_line(fTraceHookID, this->mask(), fTraceMask, line);
+ }
+}
+
+void SkVMGenerator::emitTraceScope(skvm::I32 executionMask, int delta) {
+ if (fDebugTrace) {
+ fBuilder->trace_scope(fTraceHookID, executionMask, fTraceMask, delta);
+ }
+}
+
+void SkVMGenerator::writeStatement(const Statement& s) {
+ // The debugger should stop on all types of statements, except for Blocks.
+ if (!s.is<Block>()) {
+ this->emitTraceLine(this->getLine(s.fPosition));
+ }
+
+ switch (s.kind()) {
+ case Statement::Kind::kBlock:
+ this->writeBlock(s.as<Block>());
+ break;
+ case Statement::Kind::kBreak:
+ this->writeBreakStatement();
+ break;
+ case Statement::Kind::kContinue:
+ this->writeContinueStatement();
+ break;
+ case Statement::Kind::kExpression:
+ this->writeExpression(*s.as<ExpressionStatement>().expression());
+ break;
+ case Statement::Kind::kFor:
+ this->writeForStatement(s.as<ForStatement>());
+ break;
+ case Statement::Kind::kIf:
+ this->writeIfStatement(s.as<IfStatement>());
+ break;
+ case Statement::Kind::kReturn:
+ this->writeReturnStatement(s.as<ReturnStatement>());
+ break;
+ case Statement::Kind::kSwitch:
+ this->writeSwitchStatement(s.as<SwitchStatement>());
+ break;
+ case Statement::Kind::kVarDeclaration:
+ this->writeVarDeclaration(s.as<VarDeclaration>());
+ break;
+ case Statement::Kind::kDiscard:
+ case Statement::Kind::kDo:
+ SkDEBUGFAIL("Unsupported control flow");
+ break;
+ case Statement::Kind::kNop:
+ break;
+ default:
+ SkDEBUGFAIL("Unrecognized statement");
+ break;
+ }
+}
+
+skvm::Color ProgramToSkVM(const Program& program,
+ const FunctionDefinition& function,
+ skvm::Builder* builder,
+ SkVMDebugTrace* debugTrace,
+ SkSpan<skvm::Val> uniforms,
+ skvm::Coord device,
+ skvm::Coord local,
+ skvm::Color inputColor,
+ skvm::Color destColor,
+ SkVMCallbacks* callbacks) {
+ skvm::Val zero = builder->splat(0.0f).id;
+ skvm::Val result[4] = {zero,zero,zero,zero};
+
+ skvm::Val args[8]; // At most 8 arguments (half4 srcColor, half4 dstColor)
+ size_t argSlots = 0;
+ for (const SkSL::Variable* param : function.declaration().parameters()) {
+ switch (param->modifiers().fLayout.fBuiltin) {
+ case SK_MAIN_COORDS_BUILTIN:
+ SkASSERT(param->type().slotCount() == 2);
+ SkASSERT((argSlots + 2) <= std::size(args));
+ args[argSlots++] = local.x.id;
+ args[argSlots++] = local.y.id;
+ break;
+ case SK_INPUT_COLOR_BUILTIN:
+ SkASSERT(param->type().slotCount() == 4);
+ SkASSERT((argSlots + 4) <= std::size(args));
+ args[argSlots++] = inputColor.r.id;
+ args[argSlots++] = inputColor.g.id;
+ args[argSlots++] = inputColor.b.id;
+ args[argSlots++] = inputColor.a.id;
+ break;
+ case SK_DEST_COLOR_BUILTIN:
+ SkASSERT(param->type().slotCount() == 4);
+ SkASSERT((argSlots + 4) <= std::size(args));
+ args[argSlots++] = destColor.r.id;
+ args[argSlots++] = destColor.g.id;
+ args[argSlots++] = destColor.b.id;
+ args[argSlots++] = destColor.a.id;
+ break;
+ default:
+ SkDEBUGFAIL("Invalid parameter to main()");
+ return {};
+ }
+ }
+ SkASSERT(argSlots <= std::size(args));
+
+ // Make sure that the SkVMDebugTrace starts from a clean slate.
+ if (debugTrace) {
+ debugTrace->fSlotInfo.clear();
+ debugTrace->fFuncInfo.clear();
+ debugTrace->fTraceInfo.clear();
+ }
+
+ SkVMGenerator generator(program, builder, debugTrace, callbacks);
+ generator.writeProgram(uniforms, device, function, {args, argSlots}, SkSpan(result));
+
+ return skvm::Color{{builder, result[0]},
+ {builder, result[1]},
+ {builder, result[2]},
+ {builder, result[3]}};
+}
+
+bool ProgramToSkVM(const Program& program,
+ const FunctionDefinition& function,
+ skvm::Builder* b,
+ SkVMDebugTrace* debugTrace,
+ SkSpan<skvm::Val> uniforms,
+ SkVMSignature* outSignature) {
+ SkVMSignature ignored,
+ *signature = outSignature ? outSignature : &ignored;
+
+ std::vector<skvm::Ptr> argPtrs;
+ std::vector<skvm::Val> argVals;
+
+ for (const Variable* p : function.declaration().parameters()) {
+ size_t slots = p->type().slotCount();
+ signature->fParameterSlots += slots;
+ for (size_t i = 0; i < slots; ++i) {
+ argPtrs.push_back(b->varying<float>());
+ argVals.push_back(b->loadF(argPtrs.back()).id);
+ }
+ }
+
+ std::vector<skvm::Ptr> returnPtrs;
+ std::vector<skvm::Val> returnVals;
+
+ signature->fReturnSlots = function.declaration().returnType().slotCount();
+ for (size_t i = 0; i < signature->fReturnSlots; ++i) {
+ returnPtrs.push_back(b->varying<float>());
+ returnVals.push_back(b->splat(0.0f).id);
+ }
+
+ class Callbacks : public SkVMCallbacks {
+ public:
+ Callbacks(skvm::Color color) : fColor(color) {}
+
+ skvm::Color sampleShader(int, skvm::Coord) override {
+ fUsedUnsupportedFeatures = true;
+ return fColor;
+ }
+ skvm::Color sampleColorFilter(int, skvm::Color) override {
+ fUsedUnsupportedFeatures = true;
+ return fColor;
+ }
+ skvm::Color sampleBlender(int, skvm::Color, skvm::Color) override {
+ fUsedUnsupportedFeatures = true;
+ return fColor;
+ }
+
+ skvm::Color toLinearSrgb(skvm::Color) override {
+ fUsedUnsupportedFeatures = true;
+ return fColor;
+ }
+ skvm::Color fromLinearSrgb(skvm::Color) override {
+ fUsedUnsupportedFeatures = true;
+ return fColor;
+ }
+
+ bool fUsedUnsupportedFeatures = false;
+ const skvm::Color fColor;
+ };
+
+ // Set up device coordinates so that the rightmost evaluated pixel will be centered on (0, 0).
+ // (If the coordinates aren't used, dead-code elimination will optimize this away.)
+ skvm::F32 pixelCenter = b->splat(0.5f);
+ skvm::Coord device = {pixelCenter, pixelCenter};
+ device.x += to_F32(b->splat(1) - b->index());
+
+ skvm::F32 zero = b->splat(0.0f);
+ skvm::Color sampledColor{zero, zero, zero, zero};
+ Callbacks callbacks(sampledColor);
+
+ SkVMGenerator generator(program, b, debugTrace, &callbacks);
+ generator.writeProgram(uniforms, device, function, SkSpan(argVals), SkSpan(returnVals));
+
+ // If the SkSL tried to use any shader, colorFilter, or blender objects - we don't have a
+ // mechanism (yet) for binding to those.
+ if (callbacks.fUsedUnsupportedFeatures) {
+ return false;
+ }
+
+ // generateCode has updated the contents of 'argVals' for any 'out' or 'inout' parameters.
+ // Propagate those changes back to our varying buffers:
+ size_t argIdx = 0;
+ for (const Variable* p : function.declaration().parameters()) {
+ size_t nslots = p->type().slotCount();
+ if (p->modifiers().fFlags & Modifiers::kOut_Flag) {
+ for (size_t i = 0; i < nslots; ++i) {
+ b->storeF(argPtrs[argIdx + i], skvm::F32{b, argVals[argIdx + i]});
+ }
+ }
+ argIdx += nslots;
+ }
+
+ // It's also updated the contents of 'returnVals' with the return value of the entry point.
+ // Store that as well:
+ for (size_t i = 0; i < signature->fReturnSlots; ++i) {
+ b->storeF(returnPtrs[i], skvm::F32{b, returnVals[i]});
+ }
+
+ return true;
+}
+
+/*
+ * Testing utility function that emits program's "main" with a minimal harness. Used to create
+ * representative skvm op sequences for SkSL tests.
+ */
+bool testingOnly_ProgramToSkVMShader(const Program& program,
+ skvm::Builder* builder,
+ SkVMDebugTrace* debugTrace) {
+ const SkSL::FunctionDeclaration* main = program.getFunction("main");
+ if (!main) {
+ return false;
+ }
+
+ size_t uniformSlots = 0;
+ int childSlots = 0;
+ for (const SkSL::ProgramElement* e : program.elements()) {
+ if (e->is<GlobalVarDeclaration>()) {
+ const GlobalVarDeclaration& decl = e->as<GlobalVarDeclaration>();
+ const Variable& var = *decl.varDeclaration().var();
+ if (var.type().isEffectChild()) {
+ childSlots++;
+ } else if (is_uniform(var)) {
+ uniformSlots += var.type().slotCount();
+ }
+ }
+ }
+
+ skvm::Uniforms uniforms(builder->uniform(), 0);
+
+ auto new_uni = [&]() { return builder->uniformF(uniforms.pushF(0.0f)); };
+
+ // Assume identity CTM
+ skvm::Coord device = {pun_to_F32(builder->index()), new_uni()};
+ // Position device coords at pixel centers, so debug traces will trigger
+ device.x += 0.5f;
+ device.y += 0.5f;
+ skvm::Coord local = device;
+
+ class Callbacks : public SkVMCallbacks {
+ public:
+ Callbacks(skvm::Builder* builder, skvm::Uniforms* uniforms, int numChildren) {
+ for (int i = 0; i < numChildren; ++i) {
+ fChildren.push_back(
+ {uniforms->pushPtr(nullptr), builder->uniform32(uniforms->push(0))});
+ }
+ }
+
+ skvm::Color sampleShader(int i, skvm::Coord coord) override {
+ skvm::PixelFormat pixelFormat = skvm::SkColorType_to_PixelFormat(kRGBA_F32_SkColorType);
+ skvm::I32 index = trunc(coord.x);
+ index += trunc(coord.y) * fChildren[i].rowBytesAsPixels;
+ return gather(pixelFormat, fChildren[i].addr, index);
+ }
+
+ skvm::Color sampleColorFilter(int i, skvm::Color color) override {
+ return color;
+ }
+
+ skvm::Color sampleBlender(int i, skvm::Color src, skvm::Color dst) override {
+ return blend(SkBlendMode::kSrcOver, src, dst);
+ }
+
+ // TODO(skia:10479): Make these actually convert to/from something like sRGB, for use in
+ // test files.
+ skvm::Color toLinearSrgb(skvm::Color color) override {
+ return color;
+ }
+ skvm::Color fromLinearSrgb(skvm::Color color) override {
+ return color;
+ }
+
+ struct Child {
+ skvm::Uniform addr;
+ skvm::I32 rowBytesAsPixels;
+ };
+ std::vector<Child> fChildren;
+ };
+ Callbacks callbacks(builder, &uniforms, childSlots);
+
+ std::vector<skvm::Val> uniformVals;
+ for (size_t i = 0; i < uniformSlots; ++i) {
+ uniformVals.push_back(new_uni().id);
+ }
+
+ skvm::Color inColor = builder->uniformColor(SkColors::kWhite, &uniforms);
+ skvm::Color destColor = builder->uniformColor(SkColors::kBlack, &uniforms);
+
+ skvm::Color result = SkSL::ProgramToSkVM(program, *main->definition(), builder, debugTrace,
+ SkSpan(uniformVals), device, local, inColor,
+ destColor, &callbacks);
+
+ storeF(builder->varying<float>(), result.r);
+ storeF(builder->varying<float>(), result.g);
+ storeF(builder->varying<float>(), result.b);
+ storeF(builder->varying<float>(), result.a);
+
+ return true;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLVMCodeGenerator.h b/gfx/skia/skia/src/sksl/codegen/SkSLVMCodeGenerator.h
new file mode 100644
index 0000000000..cfff7477bf
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/codegen/SkSLVMCodeGenerator.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_VMGENERATOR
+#define SKSL_VMGENERATOR
+
+#include "src/core/SkVM.h"
+
+#include <cstddef>
+
+template <typename T> class SkSpan;
+
+namespace SkSL {
+
+class FunctionDefinition;
+struct Program;
+class SkVMDebugTrace;
+
+class SkVMCallbacks {
+public:
+ virtual ~SkVMCallbacks() = default;
+
+ virtual skvm::Color sampleShader(int index, skvm::Coord coord) = 0;
+ virtual skvm::Color sampleColorFilter(int index, skvm::Color color) = 0;
+ virtual skvm::Color sampleBlender(int index, skvm::Color src, skvm::Color dst) = 0;
+
+ virtual skvm::Color toLinearSrgb(skvm::Color color) = 0;
+ virtual skvm::Color fromLinearSrgb(skvm::Color color) = 0;
+};
+
+// Convert 'function' to skvm instructions in 'builder', for use by blends, shaders, & color filters
+skvm::Color ProgramToSkVM(const Program& program,
+ const FunctionDefinition& function,
+ skvm::Builder* builder,
+ SkVMDebugTrace* debugTrace,
+ SkSpan<skvm::Val> uniforms,
+ skvm::Coord device,
+ skvm::Coord local,
+ skvm::Color inputColor,
+ skvm::Color destColor,
+ SkVMCallbacks* callbacks);
+
+struct SkVMSignature {
+ size_t fParameterSlots = 0;
+ size_t fReturnSlots = 0;
+};
+
+/*
+ * Converts 'function' to skvm instructions in 'builder'. Always adds one arg per value in the
+ * parameter list, then one per value in the return type. For example:
+ *
+ * float2 fn(float2 a, float b) { ... }
+ *
+ * ... is mapped so that it can be called as:
+ *
+ * p.eval(N, &a.x, &a.y, &b, &return.x, &return.y);
+ *
+ * The number of parameter and return slots (pointers) is placed in 'outSignature', if provided.
+ * If the program declares any uniforms, 'uniforms' should contain the IDs of each individual value
+ * (eg, one ID per component of a vector).
+ */
+bool ProgramToSkVM(const Program& program,
+ const FunctionDefinition& function,
+ skvm::Builder* b,
+ SkVMDebugTrace* debugTrace,
+ SkSpan<skvm::Val> uniforms,
+ SkVMSignature* outSignature = nullptr);
+
+bool testingOnly_ProgramToSkVMShader(const Program& program,
+ skvm::Builder* builder,
+ SkVMDebugTrace* debugTrace);
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLWGSLCodeGenerator.cpp b/gfx/skia/skia/src/sksl/codegen/SkSLWGSLCodeGenerator.cpp
new file mode 100644
index 0000000000..f5f593b33c
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/codegen/SkSLWGSLCodeGenerator.cpp
@@ -0,0 +1,1939 @@
+/*
+ * Copyright 2022 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/codegen/SkSLWGSLCodeGenerator.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "include/core/SkSpan.h"
+#include "include/core/SkTypes.h"
+#include "include/private/SkBitmaskEnum.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLLayout.h"
+#include "include/private/SkSLModifiers.h"
+#include "include/private/SkSLProgramElement.h"
+#include "include/private/SkSLStatement.h"
+#include "include/private/SkSLString.h"
+#include "include/private/SkSLSymbol.h"
+#include "include/private/base/SkTArray.h"
+#include "include/private/base/SkTo.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "include/sksl/SkSLOperator.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLOutputStream.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/SkSLStringStream.h"
+#include "src/sksl/SkSLUtil.h"
+#include "src/sksl/analysis/SkSLProgramVisitor.h"
+#include "src/sksl/ir/SkSLBinaryExpression.h"
+#include "src/sksl/ir/SkSLBlock.h"
+#include "src/sksl/ir/SkSLConstructor.h"
+#include "src/sksl/ir/SkSLConstructorCompound.h"
+#include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h"
+#include "src/sksl/ir/SkSLConstructorMatrixResize.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLExpressionStatement.h"
+#include "src/sksl/ir/SkSLFieldAccess.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/SkSLInterfaceBlock.h"
+#include "src/sksl/ir/SkSLLiteral.h"
+#include "src/sksl/ir/SkSLProgram.h"
+#include "src/sksl/ir/SkSLReturnStatement.h"
+#include "src/sksl/ir/SkSLStructDefinition.h"
+#include "src/sksl/ir/SkSLSwizzle.h"
+#include "src/sksl/ir/SkSLSymbolTable.h"
+#include "src/sksl/ir/SkSLTernaryExpression.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"
+
+// TODO(skia:13092): This is a temporary debug feature. Remove when the implementation is
+// complete and this is no longer needed.
+#define DUMP_SRC_IR 0
+
+namespace SkSL {
+
+enum class ProgramKind : int8_t;
+
+namespace {
+
+// See https://www.w3.org/TR/WGSL/#memory-view-types
+enum class PtrAddressSpace {
+ kFunction,
+ kPrivate,
+ kStorage,
+};
+
+std::string_view pipeline_struct_prefix(ProgramKind kind) {
+ if (ProgramConfig::IsVertex(kind)) {
+ return "VS";
+ }
+ if (ProgramConfig::IsFragment(kind)) {
+ return "FS";
+ }
+ return "";
+}
+
+std::string_view address_space_to_str(PtrAddressSpace addressSpace) {
+ switch (addressSpace) {
+ case PtrAddressSpace::kFunction:
+ return "function";
+ case PtrAddressSpace::kPrivate:
+ return "private";
+ case PtrAddressSpace::kStorage:
+ return "storage";
+ }
+ SkDEBUGFAIL("unsupported ptr address space");
+ return "unsupported";
+}
+
+std::string_view to_scalar_type(const Type& type) {
+ SkASSERT(type.typeKind() == Type::TypeKind::kScalar);
+ switch (type.numberKind()) {
+ // Floating-point numbers in WebGPU currently always have 32-bit footprint and
+ // relaxed-precision is not supported without extensions. f32 is the only floating-point
+ // number type in WGSL (see the discussion on https://github.com/gpuweb/gpuweb/issues/658).
+ case Type::NumberKind::kFloat:
+ return "f32";
+ case Type::NumberKind::kSigned:
+ return "i32";
+ case Type::NumberKind::kUnsigned:
+ return "u32";
+ case Type::NumberKind::kBoolean:
+ return "bool";
+ case Type::NumberKind::kNonnumeric:
+ [[fallthrough]];
+ default:
+ break;
+ }
+ return type.name();
+}
+
+// Convert a SkSL type to a WGSL type. Handles all plain types except structure types
+// (see https://www.w3.org/TR/WGSL/#plain-types-section).
+std::string to_wgsl_type(const Type& type) {
+ switch (type.typeKind()) {
+ case Type::TypeKind::kScalar:
+ return std::string(to_scalar_type(type));
+ case Type::TypeKind::kVector: {
+ std::string_view ct = to_scalar_type(type.componentType());
+ return String::printf("vec%d<%.*s>", type.columns(), (int)ct.length(), ct.data());
+ }
+ case Type::TypeKind::kMatrix: {
+ std::string_view ct = to_scalar_type(type.componentType());
+ return String::printf(
+ "mat%dx%d<%.*s>", type.columns(), type.rows(), (int)ct.length(), ct.data());
+ }
+ case Type::TypeKind::kArray: {
+ std::string elementType = to_wgsl_type(type.componentType());
+ if (type.isUnsizedArray()) {
+ return String::printf("array<%s>", elementType.c_str());
+ }
+ return String::printf("array<%s, %d>", elementType.c_str(), type.columns());
+ }
+ default:
+ break;
+ }
+ return std::string(type.name());
+}
+
+// Create a mangled WGSL type name that can be used in function and variable declarations (regular
+// type names cannot be used in this manner since they may contain tokens that are not allowed in
+// symbol names).
+std::string to_mangled_wgsl_type_name(const Type& type) {
+ switch (type.typeKind()) {
+ case Type::TypeKind::kScalar:
+ return std::string(to_scalar_type(type));
+ case Type::TypeKind::kVector: {
+ std::string_view ct = to_scalar_type(type.componentType());
+ return String::printf("vec%d%.*s", type.columns(), (int)ct.length(), ct.data());
+ }
+ case Type::TypeKind::kMatrix: {
+ std::string_view ct = to_scalar_type(type.componentType());
+ return String::printf(
+ "mat%dx%d%.*s", type.columns(), type.rows(), (int)ct.length(), ct.data());
+ }
+ case Type::TypeKind::kArray: {
+ std::string elementType = to_wgsl_type(type.componentType());
+ if (type.isUnsizedArray()) {
+ return String::printf("arrayof%s", elementType.c_str());
+ }
+ return String::printf("array%dof%s", type.columns(), elementType.c_str());
+ }
+ default:
+ break;
+ }
+ return std::string(type.name());
+}
+
+std::string to_ptr_type(const Type& type,
+ PtrAddressSpace addressSpace = PtrAddressSpace::kFunction) {
+ return "ptr<" + std::string(address_space_to_str(addressSpace)) + ", " + to_wgsl_type(type) +
+ ">";
+}
+
+std::string_view wgsl_builtin_name(WGSLCodeGenerator::Builtin builtin) {
+ using Builtin = WGSLCodeGenerator::Builtin;
+ switch (builtin) {
+ case Builtin::kVertexIndex:
+ return "vertex_index";
+ case Builtin::kInstanceIndex:
+ return "instance_index";
+ case Builtin::kPosition:
+ return "position";
+ case Builtin::kFrontFacing:
+ return "front_facing";
+ case Builtin::kSampleIndex:
+ return "sample_index";
+ case Builtin::kFragDepth:
+ return "frag_depth";
+ case Builtin::kSampleMask:
+ return "sample_mask";
+ case Builtin::kLocalInvocationId:
+ return "local_invocation_id";
+ case Builtin::kLocalInvocationIndex:
+ return "local_invocation_index";
+ case Builtin::kGlobalInvocationId:
+ return "global_invocation_id";
+ case Builtin::kWorkgroupId:
+ return "workgroup_id";
+ case Builtin::kNumWorkgroups:
+ return "num_workgroups";
+ default:
+ break;
+ }
+
+ SkDEBUGFAIL("unsupported builtin");
+ return "unsupported";
+}
+
+std::string_view wgsl_builtin_type(WGSLCodeGenerator::Builtin builtin) {
+ using Builtin = WGSLCodeGenerator::Builtin;
+ switch (builtin) {
+ case Builtin::kVertexIndex:
+ return "u32";
+ case Builtin::kInstanceIndex:
+ return "u32";
+ case Builtin::kPosition:
+ return "vec4<f32>";
+ case Builtin::kFrontFacing:
+ return "bool";
+ case Builtin::kSampleIndex:
+ return "u32";
+ case Builtin::kFragDepth:
+ return "f32";
+ case Builtin::kSampleMask:
+ return "u32";
+ case Builtin::kLocalInvocationId:
+ return "vec3<u32>";
+ case Builtin::kLocalInvocationIndex:
+ return "u32";
+ case Builtin::kGlobalInvocationId:
+ return "vec3<u32>";
+ case Builtin::kWorkgroupId:
+ return "vec3<u32>";
+ case Builtin::kNumWorkgroups:
+ return "vec3<u32>";
+ default:
+ break;
+ }
+
+ SkDEBUGFAIL("unsupported builtin");
+ return "unsupported";
+}
+
+// Some built-in variables have a type that differs from their SkSL counterpart (e.g. signed vs
+// unsigned integer). We handle these cases with an explicit type conversion during a variable
+// reference. Returns the WGSL type of the conversion target if conversion is needed, otherwise
+// returns std::nullopt.
+std::optional<std::string_view> needs_builtin_type_conversion(const Variable& v) {
+ switch (v.modifiers().fLayout.fBuiltin) {
+ case SK_VERTEXID_BUILTIN:
+ case SK_INSTANCEID_BUILTIN:
+ return {"i32"};
+ default:
+ break;
+ }
+ return std::nullopt;
+}
+
+// Map a SkSL builtin flag to a WGSL builtin kind. Returns std::nullopt if `builtin` is not
+// not supported for WGSL.
+//
+// Also see //src/sksl/sksl_vert.sksl and //src/sksl/sksl_frag.sksl for supported built-ins.
+std::optional<WGSLCodeGenerator::Builtin> builtin_from_sksl_name(int builtin) {
+ using Builtin = WGSLCodeGenerator::Builtin;
+ switch (builtin) {
+ case SK_POSITION_BUILTIN:
+ [[fallthrough]];
+ case SK_FRAGCOORD_BUILTIN:
+ return {Builtin::kPosition};
+ case SK_VERTEXID_BUILTIN:
+ return {Builtin::kVertexIndex};
+ case SK_INSTANCEID_BUILTIN:
+ return {Builtin::kInstanceIndex};
+ case SK_CLOCKWISE_BUILTIN:
+ // TODO(skia:13092): While `front_facing` is the corresponding built-in, it does not
+ // imply a particular winding order. We correctly compute the face orientation based
+ // on how Skia configured the render pipeline for all references to this built-in
+ // variable (see `SkSL::Program::Inputs::fUseFlipRTUniform`).
+ return {Builtin::kFrontFacing};
+ default:
+ break;
+ }
+ return std::nullopt;
+}
+
+const SymbolTable* top_level_symbol_table(const FunctionDefinition& f) {
+ return f.body()->as<Block>().symbolTable()->fParent.get();
+}
+
+const char* delimiter_to_str(WGSLCodeGenerator::Delimiter delimiter) {
+ using Delim = WGSLCodeGenerator::Delimiter;
+ switch (delimiter) {
+ case Delim::kComma:
+ return ",";
+ case Delim::kSemicolon:
+ return ";";
+ case Delim::kNone:
+ default:
+ break;
+ }
+ return "";
+}
+
+// FunctionDependencyResolver visits the IR tree rooted at a particular function definition and
+// computes that function's dependencies on pipeline stage IO parameters. These are later used to
+// synthesize arguments when writing out function definitions.
+class FunctionDependencyResolver : public ProgramVisitor {
+public:
+ using Deps = WGSLCodeGenerator::FunctionDependencies;
+ using DepsMap = WGSLCodeGenerator::ProgramRequirements::DepsMap;
+
+ FunctionDependencyResolver(const Program* p,
+ const FunctionDeclaration* f,
+ DepsMap* programDependencyMap)
+ : fProgram(p), fFunction(f), fDependencyMap(programDependencyMap) {}
+
+ Deps resolve() {
+ fDeps = Deps::kNone;
+ this->visit(*fProgram);
+ return fDeps;
+ }
+
+private:
+ bool visitProgramElement(const ProgramElement& p) override {
+ // Only visit the program that matches the requested function.
+ if (p.is<FunctionDefinition>() && &p.as<FunctionDefinition>().declaration() == fFunction) {
+ return INHERITED::visitProgramElement(p);
+ }
+ // Continue visiting other program elements.
+ return false;
+ }
+
+ bool visitExpression(const Expression& e) override {
+ if (e.is<VariableReference>()) {
+ const VariableReference& v = e.as<VariableReference>();
+ const Modifiers& modifiers = v.variable()->modifiers();
+ if (v.variable()->storage() == Variable::Storage::kGlobal) {
+ if (modifiers.fFlags & Modifiers::kIn_Flag) {
+ fDeps |= Deps::kPipelineInputs;
+ }
+ if (modifiers.fFlags & Modifiers::kOut_Flag) {
+ fDeps |= Deps::kPipelineOutputs;
+ }
+ }
+ } else if (e.is<FunctionCall>()) {
+ // The current function that we're processing (`fFunction`) inherits the dependencies of
+ // functions that it makes calls to, because the pipeline stage IO parameters need to be
+ // passed down as an argument.
+ const FunctionCall& callee = e.as<FunctionCall>();
+
+ // Don't process a function again if we have already resolved it.
+ Deps* found = fDependencyMap->find(&callee.function());
+ if (found) {
+ fDeps |= *found;
+ } else {
+ // Store the dependencies that have been discovered for the current function so far.
+ // If `callee` directly or indirectly calls the current function, then this value
+ // will prevent an infinite recursion.
+ fDependencyMap->set(fFunction, fDeps);
+
+ // Separately traverse the called function's definition and determine its
+ // dependencies.
+ FunctionDependencyResolver resolver(fProgram, &callee.function(), fDependencyMap);
+ Deps calleeDeps = resolver.resolve();
+
+ // Store the callee's dependencies in the global map to avoid processing
+ // the function again for future calls.
+ fDependencyMap->set(&callee.function(), calleeDeps);
+
+ // Add to the current function's dependencies.
+ fDeps |= calleeDeps;
+ }
+ }
+ return INHERITED::visitExpression(e);
+ }
+
+ const Program* const fProgram;
+ const FunctionDeclaration* const fFunction;
+ DepsMap* const fDependencyMap;
+ Deps fDeps = Deps::kNone;
+
+ using INHERITED = ProgramVisitor;
+};
+
+WGSLCodeGenerator::ProgramRequirements resolve_program_requirements(const Program* program) {
+ bool mainNeedsCoordsArgument = false;
+ WGSLCodeGenerator::ProgramRequirements::DepsMap dependencies;
+
+ for (const ProgramElement* e : program->elements()) {
+ if (!e->is<FunctionDefinition>()) {
+ continue;
+ }
+
+ const FunctionDeclaration& decl = e->as<FunctionDefinition>().declaration();
+ if (decl.isMain()) {
+ for (const Variable* v : decl.parameters()) {
+ if (v->modifiers().fLayout.fBuiltin == SK_MAIN_COORDS_BUILTIN) {
+ mainNeedsCoordsArgument = true;
+ break;
+ }
+ }
+ }
+
+ FunctionDependencyResolver resolver(program, &decl, &dependencies);
+ dependencies.set(&decl, resolver.resolve());
+ }
+
+ return WGSLCodeGenerator::ProgramRequirements(std::move(dependencies), mainNeedsCoordsArgument);
+}
+
+int count_pipeline_inputs(const Program* program) {
+ int inputCount = 0;
+ for (const ProgramElement* e : program->elements()) {
+ if (e->is<GlobalVarDeclaration>()) {
+ const Variable* v = e->as<GlobalVarDeclaration>().varDeclaration().var();
+ if (v->modifiers().fFlags & Modifiers::kIn_Flag) {
+ inputCount++;
+ }
+ } else if (e->is<InterfaceBlock>()) {
+ const Variable* v = e->as<InterfaceBlock>().var();
+ if (v->modifiers().fFlags & Modifiers::kIn_Flag) {
+ inputCount++;
+ }
+ }
+ }
+ return inputCount;
+}
+
+static bool is_in_global_uniforms(const Variable& var) {
+ SkASSERT(var.storage() == VariableStorage::kGlobal);
+ return var.modifiers().fFlags & Modifiers::kUniform_Flag && !var.type().isOpaque();
+}
+
+} // namespace
+
+bool WGSLCodeGenerator::generateCode() {
+ // The resources of a WGSL program are structured in the following way:
+ // - Vertex and fragment stage attribute inputs and outputs are bundled
+ // inside synthetic structs called VSIn/VSOut/FSIn/FSOut.
+ // - All uniform and storage type resources are declared in global scope.
+ this->preprocessProgram();
+
+ StringStream header;
+ {
+ AutoOutputStream outputToHeader(this, &header, &fIndentation);
+ // TODO(skia:13092): Implement the following:
+ // - global uniform/storage resource declarations, including interface blocks.
+ this->writeStageInputStruct();
+ this->writeStageOutputStruct();
+ this->writeNonBlockUniformsForTests();
+ }
+ StringStream body;
+ {
+ AutoOutputStream outputToBody(this, &body, &fIndentation);
+ for (const ProgramElement* e : fProgram.elements()) {
+ this->writeProgramElement(*e);
+ }
+
+// TODO(skia:13092): This is a temporary debug feature. Remove when the implementation is
+// complete and this is no longer needed.
+#if DUMP_SRC_IR
+ this->writeLine("\n----------");
+ this->writeLine("Source IR:\n");
+ for (const ProgramElement* e : fProgram.elements()) {
+ this->writeLine(e->description().c_str());
+ }
+#endif
+ }
+
+ write_stringstream(header, *fOut);
+ write_stringstream(fExtraFunctions, *fOut);
+ write_stringstream(body, *fOut);
+ return fContext.fErrors->errorCount() == 0;
+}
+
+void WGSLCodeGenerator::preprocessProgram() {
+ fRequirements = resolve_program_requirements(&fProgram);
+ fPipelineInputCount = count_pipeline_inputs(&fProgram);
+}
+
+void WGSLCodeGenerator::write(std::string_view s) {
+ if (s.empty()) {
+ return;
+ }
+ if (fAtLineStart) {
+ for (int i = 0; i < fIndentation; i++) {
+ fOut->writeText(" ");
+ }
+ }
+ fOut->writeText(std::string(s).c_str());
+ fAtLineStart = false;
+}
+
+void WGSLCodeGenerator::writeLine(std::string_view s) {
+ this->write(s);
+ fOut->writeText("\n");
+ fAtLineStart = true;
+}
+
+void WGSLCodeGenerator::finishLine() {
+ if (!fAtLineStart) {
+ this->writeLine();
+ }
+}
+
+void WGSLCodeGenerator::writeName(std::string_view name) {
+ // Add underscore before name to avoid conflict with reserved words.
+ if (fReservedWords.contains(name)) {
+ this->write("_");
+ }
+ this->write(name);
+}
+
+void WGSLCodeGenerator::writeVariableDecl(const Type& type,
+ std::string_view name,
+ Delimiter delimiter) {
+ this->writeName(name);
+ this->write(": " + to_wgsl_type(type));
+ this->writeLine(delimiter_to_str(delimiter));
+}
+
+void WGSLCodeGenerator::writePipelineIODeclaration(Modifiers modifiers,
+ const Type& type,
+ std::string_view name,
+ Delimiter delimiter) {
+ // In WGSL, an entry-point IO parameter is "one of either a built-in value or
+ // assigned a location". However, some SkSL declarations, specifically sk_FragColor, can
+ // contain both a location and a builtin modifier. In addition, WGSL doesn't have a built-in
+ // equivalent for sk_FragColor as it relies on the user-defined location for a render
+ // target.
+ //
+ // Instead of special-casing sk_FragColor, we just give higher precedence to a location
+ // modifier if a declaration happens to both have a location and it's a built-in.
+ //
+ // Also see:
+ // https://www.w3.org/TR/WGSL/#input-output-locations
+ // https://www.w3.org/TR/WGSL/#attribute-location
+ // https://www.w3.org/TR/WGSL/#builtin-inputs-outputs
+ int location = modifiers.fLayout.fLocation;
+ if (location >= 0) {
+ this->writeUserDefinedIODecl(type, name, location, delimiter);
+ } else if (modifiers.fLayout.fBuiltin >= 0) {
+ auto builtin = builtin_from_sksl_name(modifiers.fLayout.fBuiltin);
+ if (builtin.has_value()) {
+ this->writeBuiltinIODecl(type, name, *builtin, delimiter);
+ }
+ }
+}
+
+void WGSLCodeGenerator::writeUserDefinedIODecl(const Type& type,
+ std::string_view name,
+ int location,
+ Delimiter delimiter) {
+ this->write("@location(" + std::to_string(location) + ") ");
+
+ // "User-defined IO of scalar or vector integer type must always be specified as
+ // @interpolate(flat)" (see https://www.w3.org/TR/WGSL/#interpolation)
+ if (type.isInteger() || (type.isVector() && type.componentType().isInteger())) {
+ this->write("@interpolate(flat) ");
+ }
+
+ this->writeVariableDecl(type, name, delimiter);
+}
+
+void WGSLCodeGenerator::writeBuiltinIODecl(const Type& type,
+ std::string_view name,
+ Builtin builtin,
+ Delimiter delimiter) {
+ this->write("@builtin(");
+ this->write(wgsl_builtin_name(builtin));
+ this->write(") ");
+
+ this->writeName(name);
+ this->write(": ");
+ this->write(wgsl_builtin_type(builtin));
+ this->writeLine(delimiter_to_str(delimiter));
+}
+
+void WGSLCodeGenerator::writeFunction(const FunctionDefinition& f) {
+ this->writeFunctionDeclaration(f.declaration());
+ this->write(" ");
+ this->writeBlock(f.body()->as<Block>());
+
+ if (f.declaration().isMain()) {
+ // We just emitted the user-defined main function. Next, we generate a program entry point
+ // that calls the user-defined main.
+ this->writeEntryPoint(f);
+ }
+}
+
+void WGSLCodeGenerator::writeFunctionDeclaration(const FunctionDeclaration& f) {
+ this->write("fn ");
+ this->write(f.mangledName());
+ this->write("(");
+ auto separator = SkSL::String::Separator();
+ if (this->writeFunctionDependencyParams(f)) {
+ separator(); // update the separator as parameters have been written
+ }
+ for (const Variable* param : f.parameters()) {
+ this->write(separator());
+ this->writeName(param->mangledName());
+ this->write(": ");
+
+ // Declare an "out" function parameter as a pointer.
+ if (param->modifiers().fFlags & Modifiers::kOut_Flag) {
+ this->write(to_ptr_type(param->type()));
+ } else {
+ this->write(to_wgsl_type(param->type()));
+ }
+ }
+ this->write(")");
+ if (!f.returnType().isVoid()) {
+ this->write(" -> ");
+ this->write(to_wgsl_type(f.returnType()));
+ }
+}
+
+void WGSLCodeGenerator::writeEntryPoint(const FunctionDefinition& main) {
+ SkASSERT(main.declaration().isMain());
+
+ // The input and output parameters for a vertex/fragment stage entry point function have the
+ // FSIn/FSOut/VSIn/VSOut struct types that have been synthesized in generateCode(). An entry
+ // point always has the same signature and acts as a trampoline to the user-defined main
+ // function.
+ std::string outputType;
+ if (ProgramConfig::IsVertex(fProgram.fConfig->fKind)) {
+ this->write("@vertex fn vertexMain(");
+ if (fPipelineInputCount > 0) {
+ this->write("_stageIn: VSIn");
+ }
+ this->writeLine(") -> VSOut {");
+ outputType = "VSOut";
+ } else if (ProgramConfig::IsFragment(fProgram.fConfig->fKind)) {
+ this->write("@fragment fn fragmentMain(");
+ if (fPipelineInputCount > 0) {
+ this->write("_stageIn: FSIn");
+ }
+ this->writeLine(") -> FSOut {");
+ outputType = "FSOut";
+ } else {
+ fContext.fErrors->error(Position(), "program kind not supported");
+ return;
+ }
+
+ // Declare the stage output struct.
+ fIndentation++;
+ this->write("var _stageOut: ");
+ this->write(outputType);
+ this->writeLine(";");
+
+ // Generate assignment to sk_FragColor built-in if the user-defined main returns a color.
+ if (ProgramConfig::IsFragment(fProgram.fConfig->fKind)) {
+ const SymbolTable* symbolTable = top_level_symbol_table(main);
+ const Symbol* symbol = symbolTable->find("sk_FragColor");
+ SkASSERT(symbol);
+ if (main.declaration().returnType().matches(symbol->type())) {
+ this->write("_stageOut.sk_FragColor = ");
+ }
+ }
+
+ // Generate the function call to the user-defined main:
+ this->write(main.declaration().mangledName());
+ this->write("(");
+ auto separator = SkSL::String::Separator();
+ FunctionDependencies* deps = fRequirements.dependencies.find(&main.declaration());
+ if (deps) {
+ if ((*deps & FunctionDependencies::kPipelineInputs) != FunctionDependencies::kNone) {
+ this->write(separator());
+ this->write("_stageIn");
+ }
+ if ((*deps & FunctionDependencies::kPipelineOutputs) != FunctionDependencies::kNone) {
+ this->write(separator());
+ this->write("&_stageOut");
+ }
+ }
+ // TODO(armansito): Handle arbitrary parameters.
+ if (main.declaration().parameters().size() != 0) {
+ const Variable* v = main.declaration().parameters()[0];
+ const Type& type = v->type();
+ if (v->modifiers().fLayout.fBuiltin == SK_MAIN_COORDS_BUILTIN) {
+ if (!type.matches(*fContext.fTypes.fFloat2)) {
+ fContext.fErrors->error(
+ main.fPosition,
+ "main function has unsupported parameter: " + type.description());
+ return;
+ }
+
+ this->write(separator());
+ this->write("_stageIn.sk_FragCoord.xy");
+ }
+ }
+ this->writeLine(");");
+ this->writeLine("return _stageOut;");
+
+ fIndentation--;
+ this->writeLine("}");
+}
+
+void WGSLCodeGenerator::writeStatement(const Statement& s) {
+ switch (s.kind()) {
+ case Statement::Kind::kBlock:
+ this->writeBlock(s.as<Block>());
+ break;
+ case Statement::Kind::kExpression:
+ this->writeExpressionStatement(s.as<ExpressionStatement>());
+ break;
+ case Statement::Kind::kIf:
+ this->writeIfStatement(s.as<IfStatement>());
+ break;
+ case Statement::Kind::kReturn:
+ this->writeReturnStatement(s.as<ReturnStatement>());
+ break;
+ case Statement::Kind::kVarDeclaration:
+ this->writeVarDeclaration(s.as<VarDeclaration>());
+ break;
+ default:
+ SkDEBUGFAILF("unsupported statement (kind: %d) %s",
+ static_cast<int>(s.kind()), s.description().c_str());
+ break;
+ }
+}
+
+void WGSLCodeGenerator::writeStatements(const StatementArray& statements) {
+ for (const auto& s : statements) {
+ if (!s->isEmpty()) {
+ this->writeStatement(*s);
+ this->finishLine();
+ }
+ }
+}
+
+void WGSLCodeGenerator::writeBlock(const Block& b) {
+ // Write scope markers if this block is a scope, or if the block is empty (since we need to emit
+ // something here to make the code valid).
+ bool isScope = b.isScope() || b.isEmpty();
+ if (isScope) {
+ this->writeLine("{");
+ fIndentation++;
+ }
+ this->writeStatements(b.children());
+ if (isScope) {
+ fIndentation--;
+ this->writeLine("}");
+ }
+}
+
+void WGSLCodeGenerator::writeExpressionStatement(const ExpressionStatement& s) {
+ if (Analysis::HasSideEffects(*s.expression())) {
+ this->writeExpression(*s.expression(), Precedence::kTopLevel);
+ this->write(";");
+ }
+}
+
+void WGSLCodeGenerator::writeIfStatement(const IfStatement& s) {
+ this->write("if (");
+ this->writeExpression(*s.test(), Precedence::kTopLevel);
+ this->write(") ");
+ this->writeStatement(*s.ifTrue());
+ if (s.ifFalse()) {
+ this->write("else ");
+ this->writeStatement(*s.ifFalse());
+ }
+}
+
+void WGSLCodeGenerator::writeReturnStatement(const ReturnStatement& s) {
+ this->write("return");
+ if (s.expression()) {
+ this->write(" ");
+ this->writeExpression(*s.expression(), Precedence::kTopLevel);
+ }
+ this->write(";");
+}
+
+void WGSLCodeGenerator::writeVarDeclaration(const VarDeclaration& varDecl) {
+ bool isConst = varDecl.var()->modifiers().fFlags & Modifiers::kConst_Flag;
+ if (isConst) {
+ this->write("let ");
+ } else {
+ this->write("var ");
+ }
+ this->writeName(varDecl.var()->mangledName());
+ this->write(": ");
+ this->write(to_wgsl_type(varDecl.var()->type()));
+
+ if (varDecl.value()) {
+ this->write(" = ");
+ this->writeExpression(*varDecl.value(), Precedence::kTopLevel);
+ } else if (isConst) {
+ SkDEBUGFAILF("A let-declared constant must specify a value");
+ }
+
+ this->write(";");
+}
+
+void WGSLCodeGenerator::writeExpression(const Expression& e, Precedence parentPrecedence) {
+ switch (e.kind()) {
+ case Expression::Kind::kBinary:
+ this->writeBinaryExpression(e.as<BinaryExpression>(), parentPrecedence);
+ break;
+ case Expression::Kind::kConstructorCompound:
+ this->writeConstructorCompound(e.as<ConstructorCompound>(), parentPrecedence);
+ break;
+ case Expression::Kind::kConstructorCompoundCast:
+ case Expression::Kind::kConstructorScalarCast:
+ case Expression::Kind::kConstructorSplat:
+ this->writeAnyConstructor(e.asAnyConstructor(), parentPrecedence);
+ break;
+ case Expression::Kind::kConstructorDiagonalMatrix:
+ this->writeConstructorDiagonalMatrix(e.as<ConstructorDiagonalMatrix>(),
+ parentPrecedence);
+ break;
+ case Expression::Kind::kConstructorMatrixResize:
+ this->writeConstructorMatrixResize(e.as<ConstructorMatrixResize>(), parentPrecedence);
+ break;
+ case Expression::Kind::kFieldAccess:
+ this->writeFieldAccess(e.as<FieldAccess>());
+ break;
+ case Expression::Kind::kFunctionCall:
+ this->writeFunctionCall(e.as<FunctionCall>());
+ break;
+ case Expression::Kind::kIndex:
+ this->writeIndexExpression(e.as<IndexExpression>());
+ break;
+ case Expression::Kind::kLiteral:
+ this->writeLiteral(e.as<Literal>());
+ break;
+ case Expression::Kind::kSwizzle:
+ this->writeSwizzle(e.as<Swizzle>());
+ break;
+ case Expression::Kind::kTernary:
+ this->writeTernaryExpression(e.as<TernaryExpression>(), parentPrecedence);
+ break;
+ case Expression::Kind::kVariableReference:
+ this->writeVariableReference(e.as<VariableReference>());
+ break;
+ default:
+ SkDEBUGFAILF("unsupported expression (kind: %d) %s",
+ static_cast<int>(e.kind()),
+ e.description().c_str());
+ break;
+ }
+}
+
+void WGSLCodeGenerator::writeBinaryExpression(const BinaryExpression& b,
+ Precedence parentPrecedence) {
+ const Expression& left = *b.left();
+ const Expression& right = *b.right();
+ Operator op = b.getOperator();
+
+ // The equality and comparison operators are only supported for scalar and vector types.
+ if (op.isEquality() && !left.type().isScalar() && !left.type().isVector()) {
+ if (left.type().isMatrix()) {
+ if (op.kind() == OperatorKind::NEQ) {
+ this->write("!");
+ }
+ this->writeMatrixEquality(left, right);
+ return;
+ }
+
+ // TODO(skia:13092): Synthesize helper functions for structs and arrays.
+ return;
+ }
+
+ Precedence precedence = op.getBinaryPrecedence();
+ bool needParens = precedence >= parentPrecedence;
+
+ // The equality operators ('=='/'!=') in WGSL apply component-wise to vectors and result in a
+ // vector. We need to reduce the value to a boolean.
+ if (left.type().isVector()) {
+ if (op.kind() == Operator::Kind::EQEQ) {
+ this->write("all");
+ needParens = true;
+ } else if (op.kind() == Operator::Kind::NEQ) {
+ this->write("any");
+ needParens = true;
+ }
+ }
+
+ if (needParens) {
+ this->write("(");
+ }
+
+ // TODO(skia:13092): Correctly handle the case when lhs is a pointer.
+
+ this->writeExpression(left, precedence);
+ this->write(op.operatorName());
+ this->writeExpression(right, precedence);
+
+ if (needParens) {
+ this->write(")");
+ }
+}
+
+void WGSLCodeGenerator::writeFieldAccess(const FieldAccess& f) {
+ const Type::Field* field = &f.base()->type().fields()[f.fieldIndex()];
+ if (FieldAccess::OwnerKind::kDefault == f.ownerKind()) {
+ this->writeExpression(*f.base(), Precedence::kPostfix);
+ this->write(".");
+ } else {
+ // We are accessing a field in an anonymous interface block. If the field refers to a
+ // pipeline IO parameter, then we access it via the synthesized IO structs. We make an
+ // explicit exception for `sk_PointSize` which we declare as a placeholder variable in
+ // global scope as it is not supported by WebGPU as a pipeline IO parameter (see comments
+ // in `writeStageOutputStruct`).
+ const Variable& v = *f.base()->as<VariableReference>().variable();
+ if (v.modifiers().fFlags & Modifiers::kIn_Flag) {
+ this->write("_stageIn.");
+ } else if (v.modifiers().fFlags & Modifiers::kOut_Flag &&
+ field->fModifiers.fLayout.fBuiltin != SK_POINTSIZE_BUILTIN) {
+ this->write("(*_stageOut).");
+ } else {
+ // TODO(skia:13092): Reference the variable using the base name used for its
+ // uniform/storage block global declaration.
+ }
+ }
+ this->writeName(field->fName);
+}
+
+void WGSLCodeGenerator::writeFunctionCall(const FunctionCall& c) {
+ const FunctionDeclaration& func = c.function();
+
+ // TODO(skia:13092): Handle intrinsic call as many of them need to be rewritten.
+
+ // We implement function out-parameters by declaring them as pointers. SkSL follows GLSL's
+ // out-parameter semantics, in which out-parameters are only written back to the original
+ // variable after the function's execution is complete (see
+ // https://www.khronos.org/opengl/wiki/Core_Language_(GLSL)#Parameters).
+ //
+ // In addition, SkSL supports swizzles and array index expressions to be passed into
+ // out-parameters however WGSL does not allow taking their address into a pointer.
+ //
+ // We support these by wrapping each function call in a special helper, which internally stores
+ // all out parameters in temporaries.
+
+ // First detect which arguments are passed to out-parameters.
+ const ExpressionArray& args = c.arguments();
+ const std::vector<Variable*>& params = func.parameters();
+ SkASSERT(SkToSizeT(args.size()) == params.size());
+
+ bool foundOutParam = false;
+ SkSTArray<16, VariableReference*> outVars;
+ outVars.push_back_n(args.size(), static_cast<VariableReference*>(nullptr));
+
+ for (int i = 0; i < args.size(); ++i) {
+ if (params[i]->modifiers().fFlags & Modifiers::kOut_Flag) {
+ // Find the expression's inner variable being written to. Assignability was verified at
+ // IR generation time, so this should always succeed.
+ Analysis::AssignmentInfo info;
+ SkAssertResult(Analysis::IsAssignable(*args[i], &info));
+ outVars[i] = info.fAssignedVar;
+ foundOutParam = true;
+ }
+ }
+
+ if (foundOutParam) {
+ this->writeName(this->writeOutParamHelper(c, args, outVars));
+ } else {
+ this->writeName(func.mangledName());
+ }
+
+ this->write("(");
+ auto separator = SkSL::String::Separator();
+ if (this->writeFunctionDependencyArgs(func)) {
+ separator();
+ }
+ for (int i = 0; i < args.size(); ++i) {
+ this->write(separator());
+ if (outVars[i]) {
+ // We need to take the address of the variable and pass it down as a pointer.
+ this->write("&");
+ this->writeExpression(*outVars[i], Precedence::kSequence);
+ } else {
+ this->writeExpression(*args[i], Precedence::kSequence);
+ }
+ }
+ this->write(")");
+}
+
+void WGSLCodeGenerator::writeIndexExpression(const IndexExpression& i) {
+ this->writeExpression(*i.base(), Precedence::kPostfix);
+ this->write("[");
+ this->writeExpression(*i.index(), Precedence::kTopLevel);
+ this->write("]");
+}
+
+void WGSLCodeGenerator::writeLiteral(const Literal& l) {
+ const Type& type = l.type();
+ if (type.isFloat() || type.isBoolean()) {
+ this->write(l.description(OperatorPrecedence::kTopLevel));
+ return;
+ }
+ SkASSERT(type.isInteger());
+ if (type.matches(*fContext.fTypes.fUInt)) {
+ this->write(std::to_string(l.intValue() & 0xffffffff));
+ this->write("u");
+ } else if (type.matches(*fContext.fTypes.fUShort)) {
+ this->write(std::to_string(l.intValue() & 0xffff));
+ this->write("u");
+ } else {
+ this->write(std::to_string(l.intValue()));
+ }
+}
+
+void WGSLCodeGenerator::writeSwizzle(const Swizzle& swizzle) {
+ this->writeExpression(*swizzle.base(), Precedence::kPostfix);
+ this->write(".");
+ for (int c : swizzle.components()) {
+ SkASSERT(c >= 0 && c <= 3);
+ this->write(&("x\0y\0z\0w\0"[c * 2]));
+ }
+}
+
+void WGSLCodeGenerator::writeTernaryExpression(const TernaryExpression& t,
+ Precedence parentPrecedence) {
+ bool needParens = Precedence::kTernary >= parentPrecedence;
+ if (needParens) {
+ this->write("(");
+ }
+
+ // The trivial case is when neither branch has side effects and evaluate to a scalar or vector
+ // type. This can be represented with a call to the WGSL `select` intrinsic although it doesn't
+ // support short-circuiting.
+ if ((t.type().isScalar() || t.type().isVector()) && !Analysis::HasSideEffects(*t.ifTrue()) &&
+ !Analysis::HasSideEffects(*t.ifFalse())) {
+ this->write("select(");
+ this->writeExpression(*t.ifFalse(), Precedence::kTernary);
+ this->write(", ");
+ this->writeExpression(*t.ifTrue(), Precedence::kTernary);
+ this->write(", ");
+
+ bool isVector = t.type().isVector();
+ if (isVector) {
+ // Splat the condition expression into a vector.
+ this->write(String::printf("vec%d<bool>(", t.type().columns()));
+ }
+ this->writeExpression(*t.test(), Precedence::kTernary);
+ if (isVector) {
+ this->write(")");
+ }
+ this->write(")");
+ if (needParens) {
+ this->write(")");
+ }
+ return;
+ }
+
+ // TODO(skia:13092): WGSL does not support ternary expressions. To replicate the required
+ // short-circuting behavior we need to hoist the expression out into the surrounding block,
+ // convert it into an if statement that writes the result to a synthesized variable, and replace
+ // the original expression with a reference to that variable.
+ //
+ // Once hoisting is supported, we may want to use that for vector type expressions as well,
+ // since select above does a component-wise select
+}
+
+void WGSLCodeGenerator::writeVariableReference(const VariableReference& r) {
+ // TODO(skia:13092): Correctly handle RTflip for built-ins.
+ const Variable& v = *r.variable();
+
+ // Insert a conversion expression if this is a built-in variable whose type differs from the
+ // SkSL.
+ std::optional<std::string_view> conversion = needs_builtin_type_conversion(v);
+ if (conversion.has_value()) {
+ this->write(*conversion);
+ this->write("(");
+ }
+
+ bool needsDeref = false;
+ bool isSynthesizedOutParamArg = fOutParamArgVars.contains(&v);
+
+ // When a variable is referenced in the context of a synthesized out-parameter helper argument,
+ // two special rules apply:
+ // 1. If it's accessed via a pipeline I/O or global uniforms struct, it should instead
+ // be referenced by name (since it's actually referring to a function parameter).
+ // 2. Its type should be treated as a pointer and should be dereferenced as such.
+ if (v.storage() == Variable::Storage::kGlobal && !isSynthesizedOutParamArg) {
+ if (v.modifiers().fFlags & Modifiers::kIn_Flag) {
+ this->write("_stageIn.");
+ } else if (v.modifiers().fFlags & Modifiers::kOut_Flag) {
+ this->write("(*_stageOut).");
+ } else if (is_in_global_uniforms(v)) {
+ this->write("_globalUniforms.");
+ }
+ } else if ((v.storage() == Variable::Storage::kParameter &&
+ v.modifiers().fFlags & Modifiers::kOut_Flag) ||
+ isSynthesizedOutParamArg) {
+ // This is an out-parameter and its type is a pointer, which we need to dereference.
+ // We wrap the dereference in parentheses in case the value is used in an access expression
+ // later.
+ needsDeref = true;
+ this->write("(*");
+ }
+
+ this->writeName(v.mangledName());
+ if (needsDeref) {
+ this->write(")");
+ }
+ if (conversion.has_value()) {
+ this->write(")");
+ }
+}
+
+void WGSLCodeGenerator::writeAnyConstructor(const AnyConstructor& c, Precedence parentPrecedence) {
+ this->write(to_wgsl_type(c.type()));
+ this->write("(");
+ auto separator = SkSL::String::Separator();
+ for (const auto& e : c.argumentSpan()) {
+ this->write(separator());
+ this->writeExpression(*e, Precedence::kSequence);
+ }
+ this->write(")");
+}
+
+void WGSLCodeGenerator::writeConstructorCompound(const ConstructorCompound& c,
+ Precedence parentPrecedence) {
+ if (c.type().isVector()) {
+ this->writeConstructorCompoundVector(c, parentPrecedence);
+ } else if (c.type().isMatrix()) {
+ this->writeConstructorCompoundMatrix(c, parentPrecedence);
+ } else {
+ fContext.fErrors->error(c.fPosition, "unsupported compound constructor");
+ }
+}
+
+void WGSLCodeGenerator::writeConstructorCompoundVector(const ConstructorCompound& c,
+ Precedence parentPrecedence) {
+ // WGSL supports constructing vectors from a mix of scalars and vectors but
+ // not matrices (see https://www.w3.org/TR/WGSL/#type-constructor-expr).
+ //
+ // SkSL supports vec4(mat2x2) which we handle specially.
+ if (c.type().columns() == 4 && c.argumentSpan().size() == 1) {
+ const Expression& arg = *c.argumentSpan().front();
+ if (arg.type().isMatrix()) {
+ // This is the vec4(mat2x2) case.
+ SkASSERT(arg.type().columns() == 2);
+ SkASSERT(arg.type().rows() == 2);
+
+ // Generate a helper so that the argument expression gets evaluated once.
+ std::string name = String::printf("%s_from_%s",
+ to_mangled_wgsl_type_name(c.type()).c_str(),
+ to_mangled_wgsl_type_name(arg.type()).c_str());
+ if (!fHelpers.contains(name)) {
+ fHelpers.add(name);
+ std::string returnType = to_wgsl_type(c.type());
+ std::string argType = to_wgsl_type(arg.type());
+ fExtraFunctions.printf(
+ "fn %s(x: %s) -> %s {\n return %s(x[0].xy, x[1].xy);\n}\n",
+ name.c_str(),
+ argType.c_str(),
+ returnType.c_str(),
+ returnType.c_str());
+ }
+ this->write(name);
+ this->write("(");
+ this->writeExpression(arg, Precedence::kSequence);
+ this->write(")");
+ return;
+ }
+ }
+ this->writeAnyConstructor(c, parentPrecedence);
+}
+
+void WGSLCodeGenerator::writeConstructorCompoundMatrix(const ConstructorCompound& c,
+ Precedence parentPrecedence) {
+ SkASSERT(c.type().isMatrix());
+
+ // Emit and invoke a matrix-constructor helper method if one is necessary.
+ if (this->isMatrixConstructorHelperNeeded(c)) {
+ this->write(this->getMatrixConstructorHelper(c));
+ this->write("(");
+ auto separator = String::Separator();
+ for (const std::unique_ptr<Expression>& expr : c.arguments()) {
+ this->write(separator());
+ this->writeExpression(*expr, Precedence::kSequence);
+ }
+ this->write(")");
+ return;
+ }
+
+ // WGSL doesn't allow creating matrices by passing in scalars and vectors in a jumble; it
+ // requires your scalars to be grouped up into columns. As `isMatrixConstructorHelperNeeded`
+ // returned false, we know that none of our scalars/vectors "wrap" across across a column, so we
+ // can group our inputs up and synthesize a constructor for each column.
+ const Type& matrixType = c.type();
+ const Type& columnType = matrixType.componentType().toCompound(
+ fContext, /*columns=*/matrixType.rows(), /*rows=*/1);
+
+ this->write(to_wgsl_type(matrixType));
+ this->write("(");
+ auto separator = String::Separator();
+ int scalarCount = 0;
+ for (const std::unique_ptr<Expression>& arg : c.arguments()) {
+ this->write(separator());
+ if (arg->type().columns() < matrixType.rows()) {
+ // Write a `floatN(` constructor to group scalars and smaller vectors together.
+ if (!scalarCount) {
+ this->write(to_wgsl_type(columnType));
+ this->write("(");
+ }
+ scalarCount += arg->type().columns();
+ }
+ this->writeExpression(*arg, Precedence::kSequence);
+ if (scalarCount && scalarCount == matrixType.rows()) {
+ // Close our `floatN(...` constructor block from above.
+ this->write(")");
+ scalarCount = 0;
+ }
+ }
+ this->write(")");
+}
+
+void WGSLCodeGenerator::writeConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c,
+ Precedence parentPrecedence) {
+ const Type& type = c.type();
+ SkASSERT(type.isMatrix());
+ SkASSERT(c.argument()->type().isScalar());
+
+ // Generate a helper so that the argument expression gets evaluated once.
+ std::string name = String::printf("%s_diagonal", to_mangled_wgsl_type_name(type).c_str());
+ if (!fHelpers.contains(name)) {
+ fHelpers.add(name);
+
+ std::string typeName = to_wgsl_type(type);
+ fExtraFunctions.printf("fn %s(x: %s) -> %s {\n",
+ name.c_str(),
+ to_wgsl_type(c.argument()->type()).c_str(),
+ typeName.c_str());
+ fExtraFunctions.printf(" return %s(", typeName.c_str());
+ auto separator = String::Separator();
+ for (int col = 0; col < type.columns(); ++col) {
+ for (int row = 0; row < type.rows(); ++row) {
+ fExtraFunctions.printf("%s%s", separator().c_str(), (col == row) ? "x" : "0.0");
+ }
+ }
+ fExtraFunctions.printf(");\n}\n");
+ }
+ this->write(name);
+ this->write("(");
+ this->writeExpression(*c.argument(), Precedence::kSequence);
+ this->write(")");
+}
+
+void WGSLCodeGenerator::writeConstructorMatrixResize(const ConstructorMatrixResize& c,
+ Precedence parentPrecedence) {
+ this->write(this->getMatrixConstructorHelper(c));
+ this->write("(");
+ this->writeExpression(*c.argument(), Precedence::kSequence);
+ this->write(")");
+}
+
+bool WGSLCodeGenerator::isMatrixConstructorHelperNeeded(const ConstructorCompound& c) {
+ // WGSL supports 3 categories of matrix constructors:
+ // 1. Identity construction from a matrix of identical dimensions (handled as
+ // ConstructorCompoundCast);
+ // 2. Column-major construction by elements (scalars);
+ // 3. Column-by-column construction from vectors.
+ //
+ // WGSL does not have a diagonal constructor. In addition, SkSL (like GLSL) supports free-form
+ // inputs that combine vectors, matrices, and scalars.
+ //
+ // Some cases are simple to translate and so we handle those inline--e.g. a list of scalars can
+ // be constructed trivially. In more complex cases, we generate a helper function that converts
+ // our inputs into a properly-shaped matrix.
+ //
+ // A matrix constructor helper method is always used if any input argument is a matrix.
+ // Helper methods are also necessary when any argument would span multiple rows. For instance:
+ //
+ // float2 x = (1, 2);
+ // float3x2(x, 3, 4, 5, 6) = | 1 3 5 | = no helper needed; conversion can be done inline
+ // | 2 4 6 |
+ //
+ // float2 x = (2, 3);
+ // float3x2(1, x, 4, 5, 6) = | 1 3 5 | = x spans multiple rows; a helper method will be used
+ // | 2 4 6 |
+ //
+ // float4 x = (1, 2, 3, 4);
+ // float2x2(x) = | 1 3 | = x spans multiple rows; a helper method will be used
+ // | 2 4 |
+ //
+ int position = 0;
+ for (const std::unique_ptr<Expression>& expr : c.arguments()) {
+ if (expr->type().isMatrix()) {
+ return true;
+ }
+ position += expr->type().columns();
+ if (position > c.type().rows()) {
+ // An input argument would span multiple rows; a helper function is required.
+ return true;
+ }
+ if (position == c.type().rows()) {
+ // We've advanced to the end of a row. Wrap to the start of the next row.
+ position = 0;
+ }
+ }
+ return false;
+}
+
+std::string WGSLCodeGenerator::getMatrixConstructorHelper(const AnyConstructor& c) {
+ const Type& type = c.type();
+ int columns = type.columns();
+ int rows = type.rows();
+ auto args = c.argumentSpan();
+ std::string typeName = to_wgsl_type(type);
+
+ // Create the helper-method name and use it as our lookup key.
+ std::string name = String::printf("%s_from", to_mangled_wgsl_type_name(type).c_str());
+ for (const std::unique_ptr<Expression>& expr : args) {
+ String::appendf(&name, "_%s", to_mangled_wgsl_type_name(expr->type()).c_str());
+ }
+
+ // If a helper-method has not been synthesized yet, create it now.
+ if (!fHelpers.contains(name)) {
+ fHelpers.add(name);
+
+ fExtraFunctions.printf("fn %s(", name.c_str());
+
+ auto separator = String::Separator();
+ for (size_t i = 0; i < args.size(); ++i) {
+ fExtraFunctions.printf(
+ "%sx%zu: %s", separator().c_str(), i, to_wgsl_type(args[i]->type()).c_str());
+ }
+
+ fExtraFunctions.printf(") -> %s {\n return %s(", typeName.c_str(), typeName.c_str());
+
+ if (args.size() == 1 && args.front()->type().isMatrix()) {
+ this->writeMatrixFromMatrixArgs(args.front()->type(), columns, rows);
+ } else {
+ this->writeMatrixFromScalarAndVectorArgs(c, columns, rows);
+ }
+
+ fExtraFunctions.writeText(");\n}\n");
+ }
+ return name;
+}
+
+// Assembles a matrix by resizing another matrix named `x0`.
+// Cells that don't exist in the source matrix will be populated with identity-matrix values.
+void WGSLCodeGenerator::writeMatrixFromMatrixArgs(const Type& sourceMatrix, int columns, int rows) {
+ SkASSERT(rows <= 4);
+ SkASSERT(columns <= 4);
+
+ const char* separator = "";
+ std::string matrixType = to_wgsl_type(sourceMatrix.componentType());
+ for (int c = 0; c < columns; ++c) {
+ fExtraFunctions.printf("%svec%d<%s>(", separator, rows, matrixType.c_str());
+ separator = "), ";
+
+ // Determine how many values to take from the source matrix for this row.
+ int swizzleLength = 0;
+ if (c < sourceMatrix.columns()) {
+ swizzleLength = std::min<>(rows, sourceMatrix.rows());
+ }
+
+ // Emit all the values from the source matrix row.
+ bool firstItem;
+ switch (swizzleLength) {
+ case 0:
+ firstItem = true;
+ break;
+ case 1:
+ firstItem = false;
+ fExtraFunctions.printf("x0[%d].x", c);
+ break;
+ case 2:
+ firstItem = false;
+ fExtraFunctions.printf("x0[%d].xy", c);
+ break;
+ case 3:
+ firstItem = false;
+ fExtraFunctions.printf("x0[%d].xyz", c);
+ break;
+ case 4:
+ firstItem = false;
+ fExtraFunctions.printf("x0[%d].xyzw", c);
+ break;
+ default:
+ SkUNREACHABLE;
+ }
+
+ // Emit the placeholder identity-matrix cells.
+ for (int r = swizzleLength; r < rows; ++r) {
+ fExtraFunctions.printf("%s%s", firstItem ? "" : ", ", (r == c) ? "1.0" : "0.0");
+ firstItem = false;
+ }
+ }
+
+ fExtraFunctions.writeText(")");
+}
+
+// Assembles a matrix of type by concatenating an arbitrary mix of scalar and vector values, named
+// `x0`, `x1`, etc. An error is written if the expression list don't contain exactly C*R scalars.
+void WGSLCodeGenerator::writeMatrixFromScalarAndVectorArgs(const AnyConstructor& ctor,
+ int columns,
+ int rows) {
+ SkASSERT(rows <= 4);
+ SkASSERT(columns <= 4);
+
+ std::string matrixType = to_wgsl_type(ctor.type().componentType());
+ size_t argIndex = 0;
+ int argPosition = 0;
+ auto args = ctor.argumentSpan();
+
+ static constexpr char kSwizzle[] = "xyzw";
+ const char* separator = "";
+ for (int c = 0; c < columns; ++c) {
+ fExtraFunctions.printf("%svec%d<%s>(", separator, rows, matrixType.c_str());
+ separator = "), ";
+
+ auto columnSeparator = String::Separator();
+ for (int r = 0; r < rows;) {
+ fExtraFunctions.writeText(columnSeparator().c_str());
+ if (argIndex < args.size()) {
+ const Type& argType = args[argIndex]->type();
+ switch (argType.typeKind()) {
+ case Type::TypeKind::kScalar: {
+ fExtraFunctions.printf("x%zu", argIndex);
+ ++r;
+ ++argPosition;
+ break;
+ }
+ case Type::TypeKind::kVector: {
+ fExtraFunctions.printf("x%zu.", argIndex);
+ do {
+ fExtraFunctions.write8(kSwizzle[argPosition]);
+ ++r;
+ ++argPosition;
+ } while (r < rows && argPosition < argType.columns());
+ break;
+ }
+ case Type::TypeKind::kMatrix: {
+ fExtraFunctions.printf("x%zu[%d].", argIndex, argPosition / argType.rows());
+ do {
+ fExtraFunctions.write8(kSwizzle[argPosition]);
+ ++r;
+ ++argPosition;
+ } while (r < rows && (argPosition % argType.rows()) != 0);
+ break;
+ }
+ default: {
+ SkDEBUGFAIL("incorrect type of argument for matrix constructor");
+ fExtraFunctions.writeText("<error>");
+ break;
+ }
+ }
+
+ if (argPosition >= argType.columns() * argType.rows()) {
+ ++argIndex;
+ argPosition = 0;
+ }
+ } else {
+ SkDEBUGFAIL("not enough arguments for matrix constructor");
+ fExtraFunctions.writeText("<error>");
+ }
+ }
+ }
+
+ if (argPosition != 0 || argIndex != args.size()) {
+ SkDEBUGFAIL("incorrect number of arguments for matrix constructor");
+ fExtraFunctions.writeText(", <error>");
+ }
+
+ fExtraFunctions.writeText(")");
+}
+
+void WGSLCodeGenerator::writeMatrixEquality(const Expression& left, const Expression& right) {
+ const Type& leftType = left.type();
+ const Type& rightType = right.type();
+ SkASSERT(leftType.isMatrix());
+ SkASSERT(rightType.isMatrix());
+ SkASSERT(leftType.rows() == rightType.rows());
+ SkASSERT(leftType.columns() == rightType.columns());
+
+ std::string name = String::printf("%s_eq_%s",
+ to_mangled_wgsl_type_name(leftType).c_str(),
+ to_mangled_wgsl_type_name(rightType).c_str());
+ if (!fHelpers.contains(name)) {
+ fHelpers.add(name);
+ fExtraFunctions.printf("fn %s(left: %s, right: %s) -> bool {\n return ",
+ name.c_str(),
+ to_wgsl_type(leftType).c_str(),
+ to_wgsl_type(rightType).c_str());
+ const char* separator = "";
+ for (int i = 0; i < leftType.columns(); ++i) {
+ fExtraFunctions.printf("%sall(left[%d] == right[%d])", separator, i, i);
+ separator = " &&\n ";
+ }
+ fExtraFunctions.printf(";\n}\n");
+ }
+ this->write(name);
+ this->write("(");
+ this->writeExpression(left, Precedence::kSequence);
+ this->write(", ");
+ this->writeExpression(right, Precedence::kSequence);
+ this->write(")");
+}
+
+void WGSLCodeGenerator::writeProgramElement(const ProgramElement& e) {
+ switch (e.kind()) {
+ case ProgramElement::Kind::kExtension:
+ // TODO(skia:13092): WGSL supports extensions via the "enable" directive
+ // (https://www.w3.org/TR/WGSL/#language-extensions). While we could easily emit this
+ // directive, we should first ensure that all possible SkSL extension names are
+ // converted to their appropriate WGSL extension. Currently there are no known supported
+ // WGSL extensions aside from the hypotheticals listed in the spec.
+ break;
+ case ProgramElement::Kind::kGlobalVar:
+ this->writeGlobalVarDeclaration(e.as<GlobalVarDeclaration>());
+ break;
+ case ProgramElement::Kind::kInterfaceBlock:
+ // All interface block declarations are handled explicitly as the "program header" in
+ // generateCode().
+ break;
+ case ProgramElement::Kind::kStructDefinition:
+ this->writeStructDefinition(e.as<StructDefinition>());
+ break;
+ case ProgramElement::Kind::kFunctionPrototype:
+ // A WGSL function declaration must contain its body and the function name is in scope
+ // for the entire program (see https://www.w3.org/TR/WGSL/#function-declaration and
+ // https://www.w3.org/TR/WGSL/#declaration-and-scope).
+ //
+ // As such, we don't emit function prototypes.
+ break;
+ case ProgramElement::Kind::kFunction:
+ this->writeFunction(e.as<FunctionDefinition>());
+ break;
+ default:
+ SkDEBUGFAILF("unsupported program element: %s\n", e.description().c_str());
+ break;
+ }
+}
+
+void WGSLCodeGenerator::writeGlobalVarDeclaration(const GlobalVarDeclaration& d) {
+ const Variable& var = *d.declaration()->as<VarDeclaration>().var();
+ if ((var.modifiers().fFlags & (Modifiers::kIn_Flag | Modifiers::kOut_Flag)) ||
+ is_in_global_uniforms(var)) {
+ // Pipeline stage I/O parameters and top-level (non-block) uniforms are handled specially
+ // in generateCode().
+ return;
+ }
+
+ // TODO(skia:13092): Implement workgroup variable decoration
+ this->write("var<private> ");
+ this->writeVariableDecl(var.type(), var.name(), Delimiter::kSemicolon);
+}
+
+void WGSLCodeGenerator::writeStructDefinition(const StructDefinition& s) {
+ const Type& type = s.type();
+ this->writeLine("struct " + type.displayName() + " {");
+ fIndentation++;
+ this->writeFields(SkSpan(type.fields()), type.fPosition);
+ fIndentation--;
+ this->writeLine("};");
+}
+
+void WGSLCodeGenerator::writeFields(SkSpan<const Type::Field> fields,
+ Position parentPos,
+ const MemoryLayout*) {
+ // TODO(skia:13092): Check alignment against `layout` constraints, if present. A layout
+ // constraint will be specified for interface blocks and for structs that appear in a block.
+ for (const Type::Field& field : fields) {
+ const Type* fieldType = field.fType;
+ this->writeVariableDecl(*fieldType, field.fName, Delimiter::kComma);
+ }
+}
+
+void WGSLCodeGenerator::writeStageInputStruct() {
+ std::string_view structNamePrefix = pipeline_struct_prefix(fProgram.fConfig->fKind);
+ if (structNamePrefix.empty()) {
+ // There's no need to declare pipeline stage outputs.
+ return;
+ }
+
+ // It is illegal to declare a struct with no members.
+ if (fPipelineInputCount < 1) {
+ return;
+ }
+
+ this->write("struct ");
+ this->write(structNamePrefix);
+ this->writeLine("In {");
+ fIndentation++;
+
+ bool declaredFragCoordsBuiltin = false;
+ for (const ProgramElement* e : fProgram.elements()) {
+ if (e->is<GlobalVarDeclaration>()) {
+ const Variable* v = e->as<GlobalVarDeclaration>().declaration()
+ ->as<VarDeclaration>().var();
+ if (v->modifiers().fFlags & Modifiers::kIn_Flag) {
+ this->writePipelineIODeclaration(v->modifiers(), v->type(), v->mangledName(),
+ Delimiter::kComma);
+ if (v->modifiers().fLayout.fBuiltin == SK_FRAGCOORD_BUILTIN) {
+ declaredFragCoordsBuiltin = true;
+ }
+ }
+ } else if (e->is<InterfaceBlock>()) {
+ const Variable* v = e->as<InterfaceBlock>().var();
+ // Merge all the members of `in` interface blocks to the input struct, which are
+ // specified as either "builtin" or with a "layout(location=".
+ //
+ // TODO(armansito): Is it legal to have an interface block without a storage qualifier
+ // but with members that have individual storage qualifiers?
+ if (v->modifiers().fFlags & Modifiers::kIn_Flag) {
+ for (const auto& f : v->type().fields()) {
+ this->writePipelineIODeclaration(f.fModifiers, *f.fType, f.fName,
+ Delimiter::kComma);
+ if (f.fModifiers.fLayout.fBuiltin == SK_FRAGCOORD_BUILTIN) {
+ declaredFragCoordsBuiltin = true;
+ }
+ }
+ }
+ }
+ }
+
+ if (ProgramConfig::IsFragment(fProgram.fConfig->fKind) &&
+ fRequirements.mainNeedsCoordsArgument && !declaredFragCoordsBuiltin) {
+ this->writeLine("@builtin(position) sk_FragCoord: vec4<f32>,");
+ }
+
+ fIndentation--;
+ this->writeLine("};");
+}
+
+void WGSLCodeGenerator::writeStageOutputStruct() {
+ std::string_view structNamePrefix = pipeline_struct_prefix(fProgram.fConfig->fKind);
+ if (structNamePrefix.empty()) {
+ // There's no need to declare pipeline stage outputs.
+ return;
+ }
+
+ this->write("struct ");
+ this->write(structNamePrefix);
+ this->writeLine("Out {");
+ fIndentation++;
+
+ // TODO(skia:13092): Remember all variables that are added to the output struct here so they
+ // can be referenced correctly when handling variable references.
+ bool declaredPositionBuiltin = false;
+ bool requiresPointSizeBuiltin = false;
+ for (const ProgramElement* e : fProgram.elements()) {
+ if (e->is<GlobalVarDeclaration>()) {
+ const Variable* v = e->as<GlobalVarDeclaration>().declaration()
+ ->as<VarDeclaration>().var();
+ if (v->modifiers().fFlags & Modifiers::kOut_Flag) {
+ this->writePipelineIODeclaration(v->modifiers(), v->type(), v->mangledName(),
+ Delimiter::kComma);
+ }
+ } else if (e->is<InterfaceBlock>()) {
+ const Variable* v = e->as<InterfaceBlock>().var();
+ // Merge all the members of `out` interface blocks to the output struct, which are
+ // specified as either "builtin" or with a "layout(location=".
+ //
+ // TODO(armansito): Is it legal to have an interface block without a storage qualifier
+ // but with members that have individual storage qualifiers?
+ if (v->modifiers().fFlags & Modifiers::kOut_Flag) {
+ for (const auto& f : v->type().fields()) {
+ this->writePipelineIODeclaration(f.fModifiers, *f.fType, f.fName,
+ Delimiter::kComma);
+ if (f.fModifiers.fLayout.fBuiltin == SK_POSITION_BUILTIN) {
+ declaredPositionBuiltin = true;
+ } else if (f.fModifiers.fLayout.fBuiltin == SK_POINTSIZE_BUILTIN) {
+ // sk_PointSize is explicitly not supported by `builtin_from_sksl_name` so
+ // writePipelineIODeclaration will never write it. We mark it here if the
+ // declaration is needed so we can synthesize it below.
+ requiresPointSizeBuiltin = true;
+ }
+ }
+ }
+ }
+ }
+
+ // A vertex program must include the `position` builtin in its entry point return type.
+ if (ProgramConfig::IsVertex(fProgram.fConfig->fKind) && !declaredPositionBuiltin) {
+ this->writeLine("@builtin(position) sk_Position: vec4<f32>,");
+ }
+
+ fIndentation--;
+ this->writeLine("};");
+
+ // In WebGPU/WGSL, the vertex stage does not support a point-size output and the size
+ // of a point primitive is always 1 pixel (see https://github.com/gpuweb/gpuweb/issues/332).
+ //
+ // There isn't anything we can do to emulate this correctly at this stage so we
+ // synthesize a placeholder variable that has no effect. Programs should not rely on
+ // sk_PointSize when using the Dawn backend.
+ if (ProgramConfig::IsVertex(fProgram.fConfig->fKind) && requiresPointSizeBuiltin) {
+ this->writeLine("/* unsupported */ var<private> sk_PointSize: f32;");
+ }
+}
+
+void WGSLCodeGenerator::writeNonBlockUniformsForTests() {
+ for (const ProgramElement* e : fProgram.elements()) {
+ if (e->is<GlobalVarDeclaration>()) {
+ const GlobalVarDeclaration& decls = e->as<GlobalVarDeclaration>();
+ const Variable& var = *decls.varDeclaration().var();
+ if (is_in_global_uniforms(var)) {
+ if (!fDeclaredUniformsStruct) {
+ this->write("struct _GlobalUniforms {\n");
+ fDeclaredUniformsStruct = true;
+ }
+ this->write(" ");
+ this->writeVariableDecl(var.type(), var.mangledName(), Delimiter::kComma);
+ }
+ }
+ }
+ if (fDeclaredUniformsStruct) {
+ int binding = fProgram.fConfig->fSettings.fDefaultUniformBinding;
+ int set = fProgram.fConfig->fSettings.fDefaultUniformSet;
+ this->write("};\n");
+ this->write("@binding(" + std::to_string(binding) + ") ");
+ this->write("@group(" + std::to_string(set) + ") ");
+ this->writeLine("var<uniform> _globalUniforms: _GlobalUniforms;");
+ }
+}
+
+bool WGSLCodeGenerator::writeFunctionDependencyArgs(const FunctionDeclaration& f) {
+ FunctionDependencies* deps = fRequirements.dependencies.find(&f);
+ if (!deps || *deps == FunctionDependencies::kNone) {
+ return false;
+ }
+
+ const char* separator = "";
+ if ((*deps & FunctionDependencies::kPipelineInputs) != FunctionDependencies::kNone) {
+ this->write("_stageIn");
+ separator = ", ";
+ }
+ if ((*deps & FunctionDependencies::kPipelineOutputs) != FunctionDependencies::kNone) {
+ this->write(separator);
+ this->write("_stageOut");
+ }
+ return true;
+}
+
+bool WGSLCodeGenerator::writeFunctionDependencyParams(const FunctionDeclaration& f) {
+ FunctionDependencies* deps = fRequirements.dependencies.find(&f);
+ if (!deps || *deps == FunctionDependencies::kNone) {
+ return false;
+ }
+
+ std::string_view structNamePrefix = pipeline_struct_prefix(fProgram.fConfig->fKind);
+ if (structNamePrefix.empty()) {
+ return false;
+ }
+ const char* separator = "";
+ if ((*deps & FunctionDependencies::kPipelineInputs) != FunctionDependencies::kNone) {
+ this->write("_stageIn: ");
+ separator = ", ";
+ this->write(structNamePrefix);
+ this->write("In");
+ }
+ if ((*deps & FunctionDependencies::kPipelineOutputs) != FunctionDependencies::kNone) {
+ this->write(separator);
+ this->write("_stageOut: ptr<function, ");
+ this->write(structNamePrefix);
+ this->write("Out>");
+ }
+ return true;
+}
+
+std::string WGSLCodeGenerator::writeOutParamHelper(const FunctionCall& c,
+ const ExpressionArray& args,
+ const SkTArray<VariableReference*>& outVars) {
+ // It's possible for out-param function arguments to contain an out-param function call
+ // expression. Emit the function into a temporary stream to prevent the nested helper from
+ // clobbering the current helper as we recursively evaluate argument expressions.
+ StringStream tmpStream;
+ AutoOutputStream outputToExtraFunctions(this, &tmpStream, &fIndentation);
+
+ // Reset the line start state while the AutoOutputStream is active. We restore it later before
+ // the function returns.
+ bool atLineStart = fAtLineStart;
+ fAtLineStart = false;
+ const FunctionDeclaration& func = c.function();
+
+ // Synthesize a helper function that takes the same inputs as `function`, except in places where
+ // `outVars` is non-null; in those places, we take the type of the VariableReference.
+ //
+ // float _outParamHelper_0_originalFuncName(float _var0, float _var1, float& outParam) {
+ std::string name =
+ "_outParamHelper_" + std::to_string(fSwizzleHelperCount++) + "_" + func.mangledName();
+ auto separator = SkSL::String::Separator();
+ this->write("fn ");
+ this->write(name);
+ this->write("(");
+ if (this->writeFunctionDependencyParams(func)) {
+ separator();
+ }
+
+ SkASSERT(outVars.size() == args.size());
+ SkASSERT(SkToSizeT(outVars.size()) == func.parameters().size());
+
+ // We need to detect cases where the caller passes the same variable as an out-param more than
+ // once and avoid redeclaring the variable name. This is also a situation that is not permitted
+ // by WGSL aliasing rules (see https://www.w3.org/TR/WGSL/#aliasing). Because the parameter is
+ // redundant and we don't actually ever reference it, we give it a placeholder name.
+ auto parentOutParamArgVars = std::move(fOutParamArgVars);
+ SkASSERT(fOutParamArgVars.empty());
+
+ for (int i = 0; i < args.size(); ++i) {
+ this->write(separator());
+
+ if (outVars[i]) {
+ const Variable* var = outVars[i]->variable();
+ if (!fOutParamArgVars.contains(var)) {
+ fOutParamArgVars.add(var);
+ this->writeName(var->mangledName());
+ } else {
+ this->write("_unused");
+ this->write(std::to_string(i));
+ }
+ } else {
+ this->write("_var");
+ this->write(std::to_string(i));
+ }
+
+ this->write(": ");
+
+ // Declare the parameter using the type of argument variable. If the complete argument is an
+ // access or swizzle expression, the target assignment will be resolved below when we copy
+ // the value to the out-parameter.
+ const Type& type = outVars[i] ? outVars[i]->type() : args[i]->type();
+
+ // Declare an out-parameter as a pointer.
+ if (func.parameters()[i]->modifiers().fFlags & Modifiers::kOut_Flag) {
+ this->write(to_ptr_type(type));
+ } else {
+ this->write(to_wgsl_type(type));
+ }
+ }
+
+ this->write(")");
+ if (!func.returnType().isVoid()) {
+ this->write(" -> ");
+ this->write(to_wgsl_type(func.returnType()));
+ }
+ this->writeLine(" {");
+ ++fIndentation;
+
+ // Declare a temporary variable for each out-parameter.
+ for (int i = 0; i < outVars.size(); ++i) {
+ if (!outVars[i]) {
+ continue;
+ }
+ this->write("var ");
+ this->write("_var");
+ this->write(std::to_string(i));
+ this->write(": ");
+ this->write(to_wgsl_type(args[i]->type()));
+
+ // If this is an inout parameter then we need to copy the input argument into the parameter
+ // per https://www.khronos.org/opengl/wiki/Core_Language_(GLSL)#Parameters.
+ if (func.parameters()[i]->modifiers().fFlags & Modifiers::kIn_Flag) {
+ this->write(" = ");
+ this->writeExpression(*args[i], Precedence::kAssignment);
+ }
+
+ this->writeLine(";");
+ }
+
+ // Call the function we're wrapping. If it has a return type, then store it so it can be
+ // returned later.
+ bool hasReturn = !c.type().isVoid();
+ if (hasReturn) {
+ this->write("var _return: ");
+ this->write(to_wgsl_type(c.type()));
+ this->write(" = ");
+ }
+
+ // Write the function call.
+ this->writeName(func.mangledName());
+ this->write("(");
+ auto newSeparator = SkSL::String::Separator();
+ if (this->writeFunctionDependencyArgs(func)) {
+ newSeparator();
+ }
+ for (int i = 0; i < args.size(); ++i) {
+ this->write(newSeparator());
+ // All forwarded arguments now have a name that looks like "_var[i]" (e.g. _var0, var1,
+ // etc.). All such variables should be of value type and those that have been passed in as
+ // inout should have been dereferenced when they were stored in a local temporary. We need
+ // to take their address again when forwarding to a pointer.
+ if (outVars[i]) {
+ this->write("&");
+ }
+ this->write("_var");
+ this->write(std::to_string(i));
+ }
+ this->writeLine(");");
+
+ // Copy the temporary variables back into the original out-parameters.
+ for (int i = 0; i < outVars.size(); ++i) {
+ if (!outVars[i]) {
+ continue;
+ }
+ // TODO(skia:13092): WGSL does not support assigning to a swizzle
+ // (see https://github.com/gpuweb/gpuweb/issues/737). These will require special treatment
+ // when they appear on the lhs of an assignment.
+ this->writeExpression(*args[i], Precedence::kAssignment);
+ this->write(" = _var");
+ this->write(std::to_string(i));
+ this->writeLine(";");
+ }
+
+ // Return
+ if (hasReturn) {
+ this->writeLine("return _return;");
+ }
+
+ --fIndentation;
+ this->writeLine("}");
+
+ // Write the function out to `fExtraFunctions`.
+ write_stringstream(tmpStream, fExtraFunctions);
+
+ // Restore any global state
+ fOutParamArgVars = std::move(parentOutParamArgVars);
+ fAtLineStart = atLineStart;
+ return name;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLWGSLCodeGenerator.h b/gfx/skia/skia/src/sksl/codegen/SkSLWGSLCodeGenerator.h
new file mode 100644
index 0000000000..9ec57ba17a
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/codegen/SkSLWGSLCodeGenerator.h
@@ -0,0 +1,289 @@
+/*
+ * Copyright 2022 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_WGSLCODEGENERATOR
+#define SKSL_WGSLCODEGENERATOR
+
+#include "include/core/SkSpan.h"
+#include "include/private/SkSLDefines.h"
+#include "include/private/base/SkTArray.h"
+#include "src/core/SkTHash.h"
+#include "src/sksl/SkSLStringStream.h"
+#include "src/sksl/codegen/SkSLCodeGenerator.h"
+#include "src/sksl/ir/SkSLType.h"
+
+#include <cstdint>
+#include <initializer_list>
+#include <string>
+#include <string_view>
+#include <utility>
+
+namespace sknonstd {
+template <typename T> struct is_bitmask_enum;
+} // namespace sknonstd
+
+namespace SkSL {
+
+class AnyConstructor;
+class BinaryExpression;
+class Block;
+class Context;
+class ConstructorCompound;
+class ConstructorDiagonalMatrix;
+class ConstructorMatrixResize;
+class Expression;
+class ExpressionStatement;
+class FieldAccess;
+class FunctionCall;
+class FunctionDeclaration;
+class FunctionDefinition;
+class GlobalVarDeclaration;
+class IfStatement;
+class IndexExpression;
+class Literal;
+class MemoryLayout;
+class OutputStream;
+class Position;
+class ProgramElement;
+class ReturnStatement;
+class Statement;
+class StructDefinition;
+class Swizzle;
+class TernaryExpression;
+class VarDeclaration;
+class Variable;
+class VariableReference;
+enum class OperatorPrecedence : uint8_t;
+struct Modifiers;
+struct Program;
+
+/**
+ * Convert a Program into WGSL code.
+ */
+class WGSLCodeGenerator : public CodeGenerator {
+public:
+ // See https://www.w3.org/TR/WGSL/#builtin-values
+ enum class Builtin {
+ // Vertex stage:
+ kVertexIndex, // input
+ kInstanceIndex, // input
+ kPosition, // output, fragment stage input
+
+ // Fragment stage:
+ kFrontFacing, // input
+ kSampleIndex, // input
+ kFragDepth, // output
+ kSampleMask, // input, output
+
+ // Compute stage:
+ kLocalInvocationId, // input
+ kLocalInvocationIndex, // input
+ kGlobalInvocationId, // input
+ kWorkgroupId, // input
+ kNumWorkgroups, // input
+ };
+
+ // Represents a function's dependencies that are not accessible in global scope. For instance,
+ // pipeline stage input and output parameters must be passed in as an argument.
+ //
+ // This is a bitmask enum.
+ enum class FunctionDependencies : uint8_t {
+ kNone = 0,
+ kPipelineInputs = 1,
+ kPipelineOutputs = 2,
+ };
+
+ // Variable declarations can be terminated by:
+ // - comma (","), e.g. in struct member declarations or function parameters
+ // - semicolon (";"), e.g. in function scope variables
+ // A "none" option is provided to skip the delimiter when not needed, e.g. at the end of a list
+ // of declarations.
+ enum class Delimiter {
+ kComma,
+ kSemicolon,
+ kNone,
+ };
+
+ struct ProgramRequirements {
+ using DepsMap = SkTHashMap<const FunctionDeclaration*, FunctionDependencies>;
+
+ ProgramRequirements() = default;
+ ProgramRequirements(DepsMap dependencies, bool mainNeedsCoordsArgument)
+ : dependencies(std::move(dependencies))
+ , mainNeedsCoordsArgument(mainNeedsCoordsArgument) {}
+
+ // Mappings used to synthesize function parameters according to dependencies on pipeline
+ // input/output variables.
+ DepsMap dependencies;
+
+ // True, if the main function takes a coordinate parameter. This is used to ensure that
+ // sk_FragCoord is declared as part of pipeline inputs.
+ bool mainNeedsCoordsArgument;
+ };
+
+ WGSLCodeGenerator(const Context* context, const Program* program, OutputStream* out)
+ : INHERITED(context, program, out)
+ , fReservedWords({"array",
+ "FSIn",
+ "FSOut",
+ "_globalUniforms",
+ "_GlobalUniforms",
+ "_return",
+ "_stageIn",
+ "_stageOut",
+ "VSIn",
+ "VSOut"}) {}
+
+ bool generateCode() override;
+
+private:
+ using INHERITED = CodeGenerator;
+ using Precedence = OperatorPrecedence;
+
+ // Called by generateCode() as the first step.
+ void preprocessProgram();
+
+ // Write output content while correctly handling indentation.
+ void write(std::string_view s);
+ void writeLine(std::string_view s = std::string_view());
+ void finishLine();
+ void writeName(std::string_view name);
+ void writeVariableDecl(const Type& type, std::string_view name, Delimiter delimiter);
+
+ // Helpers to declare a pipeline stage IO parameter declaration.
+ void writePipelineIODeclaration(Modifiers modifiers,
+ const Type& type,
+ std::string_view name,
+ Delimiter delimiter);
+ void writeUserDefinedIODecl(const Type& type,
+ std::string_view name,
+ int location,
+ Delimiter delimiter);
+ void writeBuiltinIODecl(const Type& type,
+ std::string_view name,
+ Builtin builtin,
+ Delimiter delimiter);
+
+ // Write a function definition.
+ void writeFunction(const FunctionDefinition& f);
+ void writeFunctionDeclaration(const FunctionDeclaration& f);
+
+ // Write the program entry point.
+ void writeEntryPoint(const FunctionDefinition& f);
+
+ // Writers for supported statement types.
+ void writeStatement(const Statement& s);
+ void writeStatements(const StatementArray& statements);
+ void writeBlock(const Block& b);
+ void writeExpressionStatement(const ExpressionStatement& s);
+ void writeIfStatement(const IfStatement& s);
+ void writeReturnStatement(const ReturnStatement& s);
+ void writeVarDeclaration(const VarDeclaration& varDecl);
+
+ // Writers for expressions.
+ void writeExpression(const Expression& e, Precedence parentPrecedence);
+ void writeBinaryExpression(const BinaryExpression& b, Precedence parentPrecedence);
+ void writeFieldAccess(const FieldAccess& f);
+ void writeFunctionCall(const FunctionCall&);
+ void writeIndexExpression(const IndexExpression& i);
+ void writeLiteral(const Literal& l);
+ void writeSwizzle(const Swizzle& swizzle);
+ void writeTernaryExpression(const TernaryExpression& t, Precedence parentPrecedence);
+ void writeVariableReference(const VariableReference& r);
+
+ // Constructor expressions
+ void writeAnyConstructor(const AnyConstructor& c, Precedence parentPrecedence);
+ void writeConstructorCompound(const ConstructorCompound& c, Precedence parentPrecedence);
+ void writeConstructorCompoundVector(const ConstructorCompound& c, Precedence parentPrecedence);
+ void writeConstructorCompoundMatrix(const ConstructorCompound& c, Precedence parentPrecedence);
+ void writeConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c,
+ Precedence parentPrecedence);
+ void writeConstructorMatrixResize(const ConstructorMatrixResize& c,
+ Precedence parentPrecedence);
+
+ // Matrix constructor helpers.
+ bool isMatrixConstructorHelperNeeded(const ConstructorCompound& c);
+ std::string getMatrixConstructorHelper(const AnyConstructor& c);
+ void writeMatrixFromMatrixArgs(const Type& sourceMatrix, int columns, int rows);
+ void writeMatrixFromScalarAndVectorArgs(const AnyConstructor& ctor, int columns, int rows);
+
+ // Synthesized helper functions for comparison operators that are not supported by WGSL.
+ void writeMatrixEquality(const Expression& left, const Expression& right);
+
+ // Generic recursive ProgramElement visitor.
+ void writeProgramElement(const ProgramElement& e);
+ void writeGlobalVarDeclaration(const GlobalVarDeclaration& d);
+ void writeStructDefinition(const StructDefinition& s);
+
+ // Writes the WGSL struct fields for SkSL structs and interface blocks. Enforces WGSL address
+ // space layout constraints
+ // (https://www.w3.org/TR/WGSL/#address-space-layout-constraints) if a `layout` is
+ // provided. A struct that does not need to be host-shareable does not require a `layout`.
+ void writeFields(SkSpan<const Type::Field> fields,
+ Position parentPos,
+ const MemoryLayout* layout = nullptr);
+
+ // We bundle all varying pipeline stage inputs and outputs in a struct.
+ void writeStageInputStruct();
+ void writeStageOutputStruct();
+
+ // Writes all top-level non-opaque global uniform declarations (i.e. not part of an interface
+ // block) into a single uniform block binding.
+ //
+ // In complete fragment/vertex/compute programs, uniforms will be declared only as interface
+ // blocks and global opaque types (like textures and samplers) which we expect to be declared
+ // with a unique binding and descriptor set index. However, test files that are declared as RTE
+ // programs may contain OpenGL-style global uniform declarations with no clear binding index to
+ // use for the containing synthesized block.
+ //
+ // Since we are handling these variables only to generate gold files from RTEs and never run
+ // them, we always declare them at the default bind group and binding index.
+ void writeNonBlockUniformsForTests();
+
+ // For a given function declaration, writes out any implicitly required pipeline stage arguments
+ // based on the function's pre-determined dependencies. These are expected to be written out as
+ // the first parameters for a function that requires them. Returns true if any arguments were
+ // written.
+ bool writeFunctionDependencyArgs(const FunctionDeclaration&);
+ bool writeFunctionDependencyParams(const FunctionDeclaration&);
+
+ // Generate an out-parameter helper function for the given call and return its name.
+ std::string writeOutParamHelper(const FunctionCall&,
+ const ExpressionArray& args,
+ const SkTArray<VariableReference*>& outVars);
+
+ // Stores the disallowed identifier names.
+ SkTHashSet<std::string_view> fReservedWords;
+ ProgramRequirements fRequirements;
+ int fPipelineInputCount = 0;
+ bool fDeclaredUniformsStruct = false;
+
+ // Out-parameters to functions are declared as pointers. While we process the arguments to a
+ // out-parameter helper function, we need to temporarily track that they are re-declared as
+ // pointer-parameters in the helper, so that expression-tree processing can know to correctly
+ // dereference them when the variable is referenced. The contents of this set are expected to
+ // be uniquely scoped for each out-param helper and will be cleared every time a new out-param
+ // helper function has been emitted.
+ SkTHashSet<const Variable*> fOutParamArgVars;
+
+ // Output processing state.
+ int fIndentation = 0;
+ bool fAtLineStart = false;
+
+ int fSwizzleHelperCount = 0;
+ StringStream fExtraFunctions; // all internally synthesized helpers are written here
+ SkTHashSet<std::string> fHelpers; // all synthesized helper functions, by name
+};
+
+} // namespace SkSL
+
+namespace sknonstd {
+template <>
+struct is_bitmask_enum<SkSL::WGSLCodeGenerator::FunctionDependencies> : std::true_type {};
+} // namespace sknonstd
+
+#endif // SKSL_WGSLCODEGENERATOR
diff --git a/gfx/skia/skia/src/sksl/dsl/DSLBlock.cpp b/gfx/skia/skia/src/sksl/dsl/DSLBlock.cpp
new file mode 100644
index 0000000000..9a4a478e74
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/dsl/DSLBlock.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2021 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/sksl/DSLBlock.h"
+
+#include "include/sksl/DSLStatement.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLBlock.h"
+
+#include <utility>
+
+namespace SkSL {
+
+namespace dsl {
+
+DSLBlock::DSLBlock(SkSL::StatementArray statements,
+ std::shared_ptr<SymbolTable> symbols,
+ Position pos)
+ : fStatements(std::move(statements))
+ , fSymbols(std::move(symbols))
+ , fPosition(pos) {}
+
+DSLBlock::DSLBlock(SkTArray<DSLStatement> statements,
+ std::shared_ptr<SymbolTable> symbols,
+ Position pos)
+ : fSymbols(std::move(symbols))
+ , fPosition(pos) {
+ fStatements.reserve_back(statements.size());
+ for (DSLStatement& s : statements) {
+ fStatements.push_back(s.release());
+ }
+}
+
+std::unique_ptr<SkSL::Block> DSLBlock::release() {
+ return std::make_unique<SkSL::Block>(fPosition, std::move(fStatements),
+ Block::Kind::kBracedScope, std::move(fSymbols));
+}
+
+void DSLBlock::append(DSLStatement stmt) {
+ fStatements.push_back(stmt.release());
+}
+
+} // namespace dsl
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/dsl/DSLCase.cpp b/gfx/skia/skia/src/sksl/dsl/DSLCase.cpp
new file mode 100644
index 0000000000..4730894824
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/dsl/DSLCase.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2021 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/sksl/DSLCase.h"
+
+namespace SkSL {
+
+namespace dsl {
+
+DSLCase::DSLCase(DSLExpression value, SkSL::StatementArray statements, Position pos)
+ : fValue(std::move(value))
+ , fStatements(std::move(statements))
+ , fPosition(pos) {}
+
+DSLCase::DSLCase(DSLExpression value, SkTArray<DSLStatement> statements, Position pos)
+ : fValue(std::move(value))
+ , fPosition(pos) {
+ fStatements.reserve_back(statements.size());
+ for (DSLStatement& stmt : statements) {
+ fStatements.push_back(stmt.release());
+ }
+}
+
+DSLCase::DSLCase(DSLCase&& other)
+ : fValue(std::move(other.fValue))
+ , fStatements(std::move(other.fStatements)) {}
+
+DSLCase::~DSLCase() {}
+
+DSLCase& DSLCase::operator=(DSLCase&& other) {
+ fValue.assign(std::move(other.fValue));
+ fStatements = std::move(other.fStatements);
+ return *this;
+}
+
+void DSLCase::append(DSLStatement stmt) {
+ fStatements.push_back(stmt.release());
+}
+
+} // namespace dsl
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/dsl/DSLCore.cpp b/gfx/skia/skia/src/sksl/dsl/DSLCore.cpp
new file mode 100644
index 0000000000..21dfca49cf
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/dsl/DSLCore.cpp
@@ -0,0 +1,615 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/sksl/DSLCore.h"
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLProgramElement.h"
+#include "include/private/SkSLStatement.h"
+#include "include/sksl/DSLModifiers.h"
+#include "include/sksl/DSLType.h"
+#include "include/sksl/DSLVar.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLModifiersPool.h" // IWYU pragma: keep
+#include "src/sksl/SkSLPool.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/SkSLThreadContext.h"
+#include "src/sksl/dsl/priv/DSLWriter.h"
+#include "src/sksl/ir/SkSLBlock.h"
+#include "src/sksl/ir/SkSLBreakStatement.h"
+#include "src/sksl/ir/SkSLContinueStatement.h"
+#include "src/sksl/ir/SkSLDiscardStatement.h"
+#include "src/sksl/ir/SkSLDoStatement.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLExtension.h"
+#include "src/sksl/ir/SkSLForStatement.h"
+#include "src/sksl/ir/SkSLFunctionCall.h"
+#include "src/sksl/ir/SkSLIfStatement.h"
+#include "src/sksl/ir/SkSLInterfaceBlock.h"
+#include "src/sksl/ir/SkSLModifiersDeclaration.h"
+#include "src/sksl/ir/SkSLProgram.h"
+#include "src/sksl/ir/SkSLReturnStatement.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 <vector>
+
+namespace SkSL {
+
+class Variable;
+
+namespace dsl {
+
+void Start(SkSL::Compiler* compiler, ProgramKind kind) {
+ Start(compiler, kind, ProgramSettings());
+}
+
+void Start(SkSL::Compiler* compiler, ProgramKind kind, const ProgramSettings& settings) {
+ ThreadContext::SetInstance(std::make_unique<ThreadContext>(compiler, kind, settings,
+ compiler->moduleForProgramKind(kind),
+ /*isModule=*/false));
+}
+
+void StartModule(SkSL::Compiler* compiler,
+ ProgramKind kind,
+ const ProgramSettings& settings,
+ const SkSL::Module* parent) {
+ ThreadContext::SetInstance(std::make_unique<ThreadContext>(compiler, kind, settings,
+ parent, /*isModule=*/true));
+}
+
+void End() {
+ ThreadContext::SetInstance(nullptr);
+}
+
+ErrorReporter& GetErrorReporter() {
+ return ThreadContext::GetErrorReporter();
+}
+
+void SetErrorReporter(ErrorReporter* errorReporter) {
+ SkASSERT(errorReporter);
+ ThreadContext::SetErrorReporter(errorReporter);
+}
+
+class DSLCore {
+public:
+ static std::unique_ptr<SkSL::Program> ReleaseProgram(std::unique_ptr<std::string> source) {
+ ThreadContext& instance = ThreadContext::Instance();
+ SkSL::Compiler& compiler = *instance.fCompiler;
+ Pool* pool = instance.fPool.get();
+ auto result = std::make_unique<SkSL::Program>(std::move(source),
+ std::move(instance.fConfig),
+ compiler.fContext,
+ std::move(instance.fProgramElements),
+ std::move(instance.fSharedElements),
+ std::move(instance.fModifiersPool),
+ std::move(compiler.fSymbolTable),
+ std::move(instance.fPool),
+ instance.fInputs);
+ bool success = false;
+ if (!compiler.finalize(*result)) {
+ // Do not return programs that failed to compile.
+ } else if (!compiler.optimize(*result)) {
+ // Do not return programs that failed to optimize.
+ } else {
+ // We have a successful program!
+ success = true;
+ }
+ if (pool) {
+ pool->detachFromThread();
+ }
+ SkASSERT(instance.fProgramElements.empty());
+ SkASSERT(!ThreadContext::SymbolTable());
+ return success ? std::move(result) : nullptr;
+ }
+
+ template <typename... Args>
+ static DSLExpression Call(const char* name, Position pos, Args... args) {
+ SkSL::ExpressionArray argArray;
+ argArray.reserve_back(sizeof...(args));
+ ((void)argArray.push_back(args.release()), ...);
+
+ return DSLExpression(SkSL::FunctionCall::Convert(ThreadContext::Context(), pos,
+ ThreadContext::Compiler().convertIdentifier(Position(), name),
+ std::move(argArray)));
+ }
+
+ static DSLStatement Break(Position pos) {
+ return SkSL::BreakStatement::Make(pos);
+ }
+
+ static DSLStatement Continue(Position pos) {
+ return SkSL::ContinueStatement::Make(pos);
+ }
+
+ static void Declare(const DSLModifiers& modifiers) {
+ ThreadContext::ProgramElements().push_back(std::make_unique<SkSL::ModifiersDeclaration>(
+ ThreadContext::Modifiers(modifiers.fModifiers)));
+ }
+
+ static DSLStatement Declare(DSLVar& var, Position pos) {
+ return DSLWriter::Declaration(var);
+ }
+
+ static DSLStatement Declare(SkTArray<DSLVar>& vars, Position pos) {
+ StatementArray statements;
+ for (DSLVar& v : vars) {
+ statements.push_back(Declare(v, pos).release());
+ }
+ return SkSL::Block::Make(pos, std::move(statements), Block::Kind::kCompoundStatement);
+ }
+
+ static void Declare(DSLGlobalVar& var, Position pos) {
+ std::unique_ptr<SkSL::Statement> stmt = DSLWriter::Declaration(var);
+ if (stmt && !stmt->isEmpty()) {
+ ThreadContext::ProgramElements().push_back(
+ std::make_unique<SkSL::GlobalVarDeclaration>(std::move(stmt)));
+ }
+ }
+
+ static void Declare(SkTArray<DSLGlobalVar>& vars, Position pos) {
+ for (DSLGlobalVar& v : vars) {
+ Declare(v, pos);
+ }
+ }
+
+ static DSLStatement Discard(Position pos) {
+ return DSLStatement(SkSL::DiscardStatement::Convert(ThreadContext::Context(), pos), pos);
+ }
+
+ static DSLStatement Do(DSLStatement stmt, DSLExpression test, Position pos) {
+ return DSLStatement(DoStatement::Convert(ThreadContext::Context(), pos, stmt.release(),
+ test.release()), pos);
+ }
+
+ static DSLStatement For(DSLStatement initializer, DSLExpression test,
+ DSLExpression next, DSLStatement stmt, Position pos,
+ const ForLoopPositions& forLoopPositions) {
+ return DSLStatement(ForStatement::Convert(ThreadContext::Context(), pos, forLoopPositions,
+ initializer.releaseIfPossible(),
+ test.releaseIfPossible(),
+ next.releaseIfPossible(),
+ stmt.release(),
+ ThreadContext::SymbolTable()), pos);
+ }
+
+ static DSLStatement If(DSLExpression test, DSLStatement ifTrue, DSLStatement ifFalse,
+ Position pos) {
+ return DSLStatement(IfStatement::Convert(ThreadContext::Context(),
+ pos,
+ test.release(),
+ ifTrue.release(),
+ ifFalse.releaseIfPossible()), pos);
+ }
+
+ static DSLExpression InterfaceBlock(const DSLModifiers& modifiers, std::string_view typeName,
+ SkTArray<DSLField> fields, std::string_view varName,
+ int arraySize, Position pos) {
+ // Build a struct type corresponding to the passed-in fields and array size.
+ DSLType varType = StructType(typeName, fields, /*interfaceBlock=*/true, pos);
+ if (arraySize > 0) {
+ varType = Array(varType, arraySize);
+ }
+
+ // Create a global variable to attach our interface block to. (The variable doesn't actually
+ // get a program element, though; the interface block does instead.)
+ DSLGlobalVar var(modifiers, varType, varName, DSLExpression(), pos);
+ if (SkSL::Variable* skslVar = DSLWriter::Var(var)) {
+ // Add an InterfaceBlock program element to the program.
+ if (std::unique_ptr<SkSL::InterfaceBlock> intf = SkSL::InterfaceBlock::Convert(
+ ThreadContext::Context(), pos, skslVar, ThreadContext::SymbolTable())) {
+ ThreadContext::ProgramElements().push_back(std::move(intf));
+ // Return a VariableReference to the global variable tied to the interface block.
+ return DSLExpression(var);
+ }
+ }
+
+ // The InterfaceBlock couldn't be created; return poison.
+ return DSLExpression(nullptr);
+ }
+
+ static DSLStatement Return(DSLExpression value, Position pos) {
+ // Note that because Return is called before the function in which it resides exists, at
+ // this point we do not know the function's return type. We therefore do not check for
+ // errors, or coerce the value to the correct type, until the return statement is actually
+ // added to a function. (This is done in FunctionDefinition::Convert.)
+ return SkSL::ReturnStatement::Make(pos, value.releaseIfPossible());
+ }
+
+ static DSLExpression Swizzle(DSLExpression base, SkSL::SwizzleComponent::Type a,
+ Position pos, Position maskPos) {
+ return DSLExpression(Swizzle::Convert(ThreadContext::Context(), pos, maskPos,
+ base.release(), ComponentArray{a}),
+ pos);
+ }
+
+ static DSLExpression Swizzle(DSLExpression base,
+ SkSL::SwizzleComponent::Type a,
+ SkSL::SwizzleComponent::Type b,
+ Position pos,
+ Position maskPos) {
+ return DSLExpression(Swizzle::Convert(ThreadContext::Context(), pos, maskPos,
+ base.release(), ComponentArray{a, b}),
+ pos);
+ }
+
+ static DSLExpression Swizzle(DSLExpression base,
+ SkSL::SwizzleComponent::Type a,
+ SkSL::SwizzleComponent::Type b,
+ SkSL::SwizzleComponent::Type c,
+ Position pos,
+ Position maskPos) {
+ return DSLExpression(Swizzle::Convert(ThreadContext::Context(), pos, maskPos,
+ base.release(), ComponentArray{a, b, c}),
+ pos);
+ }
+
+ static DSLExpression Swizzle(DSLExpression base,
+ SkSL::SwizzleComponent::Type a,
+ SkSL::SwizzleComponent::Type b,
+ SkSL::SwizzleComponent::Type c,
+ SkSL::SwizzleComponent::Type d,
+ Position pos,
+ Position maskPos) {
+ return DSLExpression(Swizzle::Convert(ThreadContext::Context(), pos, maskPos,
+ base.release(), ComponentArray{a, b, c, d}),
+ pos);
+ }
+
+ static DSLExpression Select(DSLExpression test, DSLExpression ifTrue, DSLExpression ifFalse,
+ Position pos) {
+ auto result = TernaryExpression::Convert(ThreadContext::Context(), pos, test.release(),
+ ifTrue.release(), ifFalse.release());
+ SkASSERT(!result || result->fPosition == pos);
+ return DSLExpression(std::move(result), pos);
+ }
+
+ static DSLStatement Switch(DSLExpression value, SkTArray<DSLCase> cases, Position pos) {
+ ExpressionArray values;
+ values.reserve_back(cases.size());
+ StatementArray caseBlocks;
+ caseBlocks.reserve_back(cases.size());
+ for (DSLCase& c : cases) {
+ values.push_back(c.fValue.releaseIfPossible());
+ caseBlocks.push_back(SkSL::Block::Make(Position(), std::move(c.fStatements),
+ Block::Kind::kUnbracedBlock));
+ }
+ return DSLStatement(SwitchStatement::Convert(ThreadContext::Context(), pos,
+ value.release(),
+ std::move(values),
+ std::move(caseBlocks),
+ ThreadContext::SymbolTable()), pos);
+ }
+
+ static DSLStatement While(DSLExpression test, DSLStatement stmt, Position pos) {
+ return DSLStatement(ForStatement::ConvertWhile(ThreadContext::Context(), pos,
+ test.release(),
+ stmt.release(),
+ ThreadContext::SymbolTable()), pos);
+ }
+};
+
+std::unique_ptr<SkSL::Program> ReleaseProgram(std::unique_ptr<std::string> source) {
+ return DSLCore::ReleaseProgram(std::move(source));
+}
+
+void AddExtension(std::string_view name, Position pos) {
+ ThreadContext::ProgramElements().push_back(std::make_unique<SkSL::Extension>(pos, name));
+}
+
+DSLStatement Break(Position pos) {
+ return DSLCore::Break(pos);
+}
+
+DSLStatement Continue(Position pos) {
+ return DSLCore::Continue(pos);
+}
+
+void Declare(const DSLModifiers& modifiers, Position pos) {
+ SkSL::ProgramKind kind = ThreadContext::GetProgramConfig()->fKind;
+ if (!ProgramConfig::IsFragment(kind) &&
+ !ProgramConfig::IsVertex(kind)) {
+ ThreadContext::ReportError("layout qualifiers are not allowed in this kind of program",
+ pos);
+ return;
+ }
+ DSLCore::Declare(modifiers);
+}
+
+// Logically, we'd want the variable's initial value to appear on here in Declare, since that
+// matches how we actually write code (and in fact that was what our first attempt looked like).
+// Unfortunately, C++ doesn't guarantee execution order between arguments, and Declare() can appear
+// as a function argument in constructs like Block(Declare(x, 0), foo(x)). If these are executed out
+// of order, we will evaluate the reference to x before we evaluate Declare(x, 0), and thus the
+// variable's initial value is unknown at the point of reference. There are probably some other
+// issues with this as well, but it is particularly dangerous when x is const, since SkSL will
+// expect its value to be known when it is referenced and will end up asserting, dereferencing a
+// null pointer, or possibly doing something else awful.
+//
+// So, we put the initial value onto the Var itself instead of the Declare to guarantee that it is
+// always executed in the correct order.
+DSLStatement Declare(DSLVar& var, Position pos) {
+ return DSLCore::Declare(var, pos);
+}
+
+DSLStatement Declare(SkTArray<DSLVar>& vars, Position pos) {
+ return DSLCore::Declare(vars, pos);
+}
+
+void Declare(DSLGlobalVar& var, Position pos) {
+ DSLCore::Declare(var, pos);
+}
+
+void Declare(SkTArray<DSLGlobalVar>& vars, Position pos) {
+ DSLCore::Declare(vars, pos);
+}
+
+DSLStatement Discard(Position pos) {
+ return DSLCore::Discard(pos);
+}
+
+DSLStatement Do(DSLStatement stmt, DSLExpression test, Position pos) {
+ return DSLCore::Do(std::move(stmt), std::move(test), pos);
+}
+
+DSLStatement For(DSLStatement initializer, DSLExpression test, DSLExpression next,
+ DSLStatement stmt, Position pos, ForLoopPositions forLoopPositions) {
+ return DSLCore::For(std::move(initializer), std::move(test), std::move(next),
+ std::move(stmt), pos, forLoopPositions);
+}
+
+DSLStatement If(DSLExpression test, DSLStatement ifTrue, DSLStatement ifFalse, Position pos) {
+ return DSLCore::If(std::move(test), std::move(ifTrue), std::move(ifFalse), pos);
+}
+
+DSLExpression InterfaceBlock(const DSLModifiers& modifiers, std::string_view typeName,
+ SkTArray<DSLField> fields, std::string_view varName, int arraySize,
+ Position pos) {
+ return DSLCore::InterfaceBlock(modifiers, typeName, std::move(fields), varName, arraySize, pos);
+}
+
+DSLStatement Return(DSLExpression expr, Position pos) {
+ return DSLCore::Return(std::move(expr), pos);
+}
+
+DSLExpression Select(DSLExpression test, DSLExpression ifTrue, DSLExpression ifFalse,
+ Position pos) {
+ return DSLCore::Select(std::move(test), std::move(ifTrue), std::move(ifFalse), pos);
+}
+
+DSLStatement Switch(DSLExpression value, SkTArray<DSLCase> cases, Position pos) {
+ return DSLCore::Switch(std::move(value), std::move(cases), pos);
+}
+
+DSLStatement While(DSLExpression test, DSLStatement stmt, Position pos) {
+ return DSLCore::While(std::move(test), std::move(stmt), pos);
+}
+
+DSLExpression Abs(DSLExpression x, Position pos) {
+ return DSLCore::Call("abs", pos, std::move(x));
+}
+
+DSLExpression All(DSLExpression x, Position pos) {
+ return DSLCore::Call("all", pos, std::move(x));
+}
+
+DSLExpression Any(DSLExpression x, Position pos) {
+ return DSLCore::Call("any", pos, std::move(x));
+}
+
+DSLExpression Atan(DSLExpression y_over_x, Position pos) {
+ return DSLCore::Call("atan", pos, std::move(y_over_x));
+}
+
+DSLExpression Atan(DSLExpression y, DSLExpression x, Position pos) {
+ return DSLCore::Call("atan", pos, std::move(y), std::move(x));
+}
+
+DSLExpression Ceil(DSLExpression x, Position pos) {
+ return DSLCore::Call("ceil", pos, std::move(x));
+}
+
+DSLExpression Clamp(DSLExpression x, DSLExpression min, DSLExpression max, Position pos) {
+ return DSLCore::Call("clamp", pos, std::move(x), std::move(min), std::move(max));
+}
+
+DSLExpression Cos(DSLExpression x, Position pos) {
+ return DSLCore::Call("cos", pos, std::move(x));
+}
+
+DSLExpression Cross(DSLExpression x, DSLExpression y, Position pos) {
+ return DSLCore::Call("cross", pos, std::move(x), std::move(y));
+}
+
+DSLExpression Degrees(DSLExpression x, Position pos) {
+ return DSLCore::Call("degrees", pos, std::move(x));
+}
+
+DSLExpression Distance(DSLExpression x, DSLExpression y, Position pos) {
+ return DSLCore::Call("distance", pos, std::move(x), std::move(y));
+}
+
+DSLExpression Dot(DSLExpression x, DSLExpression y, Position pos) {
+ return DSLCore::Call("dot", pos, std::move(x), std::move(y));
+}
+
+DSLExpression Equal(DSLExpression x, DSLExpression y, Position pos) {
+ return DSLCore::Call("equal", pos, std::move(x), std::move(y));
+}
+
+DSLExpression Exp(DSLExpression x, Position pos) {
+ return DSLCore::Call("exp", pos, std::move(x));
+}
+
+DSLExpression Exp2(DSLExpression x, Position pos) {
+ return DSLCore::Call("exp2", pos, std::move(x));
+}
+
+DSLExpression Faceforward(DSLExpression n, DSLExpression i, DSLExpression nref, Position pos) {
+ return DSLCore::Call("faceforward", pos, std::move(n), std::move(i), std::move(nref));
+}
+
+DSLExpression Fract(DSLExpression x, Position pos) {
+ return DSLCore::Call("fract", pos, std::move(x));
+}
+
+DSLExpression Floor(DSLExpression x, Position pos) {
+ return DSLCore::Call("floor", pos, std::move(x));
+}
+
+DSLExpression GreaterThan(DSLExpression x, DSLExpression y, Position pos) {
+ return DSLCore::Call("greaterThan", pos, std::move(x), std::move(y));
+}
+
+DSLExpression GreaterThanEqual(DSLExpression x, DSLExpression y, Position pos) {
+ return DSLCore::Call("greaterThanEqual", pos, std::move(x), std::move(y));
+}
+
+DSLExpression Inverse(DSLExpression x, Position pos) {
+ return DSLCore::Call("inverse", pos, std::move(x));
+}
+
+DSLExpression Inversesqrt(DSLExpression x, Position pos) {
+ return DSLCore::Call("inversesqrt", pos, std::move(x));
+}
+
+DSLExpression Length(DSLExpression x, Position pos) {
+ return DSLCore::Call("length", pos, std::move(x));
+}
+
+DSLExpression LessThan(DSLExpression x, DSLExpression y, Position pos) {
+ return DSLCore::Call("lessThan", pos, std::move(x), std::move(y));
+}
+
+DSLExpression LessThanEqual(DSLExpression x, DSLExpression y, Position pos) {
+ return DSLCore::Call("lessThanEqual", pos, std::move(x), std::move(y));
+}
+
+DSLExpression Log(DSLExpression x, Position pos) {
+ return DSLCore::Call("log", pos, std::move(x));
+}
+
+DSLExpression Log2(DSLExpression x, Position pos) {
+ return DSLCore::Call("log2", pos, std::move(x));
+}
+
+DSLExpression Max(DSLExpression x, DSLExpression y, Position pos) {
+ return DSLCore::Call("max", pos, std::move(x), std::move(y));
+}
+
+DSLExpression Min(DSLExpression x, DSLExpression y, Position pos) {
+ return DSLCore::Call("min", pos, std::move(x), std::move(y));
+}
+
+DSLExpression Mix(DSLExpression x, DSLExpression y, DSLExpression a, Position pos) {
+ return DSLCore::Call("mix", pos, std::move(x), std::move(y), std::move(a));
+}
+
+DSLExpression Mod(DSLExpression x, DSLExpression y, Position pos) {
+ return DSLCore::Call("mod", pos, std::move(x), std::move(y));
+}
+
+DSLExpression Normalize(DSLExpression x, Position pos) {
+ return DSLCore::Call("normalize", pos, std::move(x));
+}
+
+DSLExpression NotEqual(DSLExpression x, DSLExpression y, Position pos) {
+ return DSLCore::Call("notEqual", pos, std::move(x), std::move(y));
+}
+
+DSLExpression Pow(DSLExpression x, DSLExpression y, Position pos) {
+ return DSLCore::Call("pow", pos, std::move(x), std::move(y));
+}
+
+DSLExpression Radians(DSLExpression x, Position pos) {
+ return DSLCore::Call("radians", pos, std::move(x));
+}
+
+DSLExpression Reflect(DSLExpression i, DSLExpression n, Position pos) {
+ return DSLCore::Call("reflect", pos, std::move(i), std::move(n));
+}
+
+DSLExpression Refract(DSLExpression i, DSLExpression n, DSLExpression eta, Position pos) {
+ return DSLCore::Call("refract", pos, std::move(i), std::move(n), std::move(eta));
+}
+
+DSLExpression Round(DSLExpression x, Position pos) {
+ return DSLCore::Call("round", pos, std::move(x));
+}
+
+DSLExpression Saturate(DSLExpression x, Position pos) {
+ return DSLCore::Call("saturate", pos, std::move(x));
+}
+
+DSLExpression Sign(DSLExpression x, Position pos) {
+ return DSLCore::Call("sign", pos, std::move(x));
+}
+
+DSLExpression Sin(DSLExpression x, Position pos) {
+ return DSLCore::Call("sin", pos, std::move(x));
+}
+
+DSLExpression Smoothstep(DSLExpression edge1, DSLExpression edge2, DSLExpression x,
+ Position pos) {
+ return DSLCore::Call("smoothstep", pos, std::move(edge1), std::move(edge2), std::move(x));
+}
+
+DSLExpression Sqrt(DSLExpression x, Position pos) {
+ return DSLCore::Call("sqrt", pos, std::move(x));
+}
+
+DSLExpression Step(DSLExpression edge, DSLExpression x, Position pos) {
+ return DSLCore::Call("step", pos, std::move(edge), std::move(x));
+}
+
+DSLExpression Swizzle(DSLExpression base, SkSL::SwizzleComponent::Type a,
+ Position pos, Position maskPos) {
+ return DSLCore::Swizzle(std::move(base), a, pos, maskPos);
+}
+
+DSLExpression Swizzle(DSLExpression base,
+ SkSL::SwizzleComponent::Type a,
+ SkSL::SwizzleComponent::Type b,
+ Position pos,
+ Position maskPos) {
+ return DSLCore::Swizzle(std::move(base), a, b, pos, maskPos);
+}
+
+DSLExpression Swizzle(DSLExpression base,
+ SkSL::SwizzleComponent::Type a,
+ SkSL::SwizzleComponent::Type b,
+ SkSL::SwizzleComponent::Type c,
+ Position pos,
+ Position maskPos) {
+ return DSLCore::Swizzle(std::move(base), a, b, c, pos, maskPos);
+}
+
+DSLExpression Swizzle(DSLExpression base,
+ SkSL::SwizzleComponent::Type a,
+ SkSL::SwizzleComponent::Type b,
+ SkSL::SwizzleComponent::Type c,
+ SkSL::SwizzleComponent::Type d,
+ Position pos,
+ Position maskPos) {
+ return DSLCore::Swizzle(std::move(base), a, b, c, d, pos, maskPos);
+}
+
+DSLExpression Tan(DSLExpression x, Position pos) {
+ return DSLCore::Call("tan", pos, std::move(x));
+}
+
+DSLExpression Unpremul(DSLExpression x, Position pos) {
+ return DSLCore::Call("unpremul", pos, std::move(x));
+}
+
+} // namespace dsl
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/dsl/DSLExpression.cpp b/gfx/skia/skia/src/sksl/dsl/DSLExpression.cpp
new file mode 100644
index 0000000000..937f58432b
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/dsl/DSLExpression.cpp
@@ -0,0 +1,295 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/sksl/DSLExpression.h"
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLDefines.h"
+#include "include/sksl/DSLCore.h"
+#include "include/sksl/DSLType.h"
+#include "include/sksl/DSLVar.h"
+#include "include/sksl/SkSLOperator.h"
+#include "src/sksl/SkSLThreadContext.h"
+#include "src/sksl/dsl/priv/DSLWriter.h"
+#include "src/sksl/ir/SkSLBinaryExpression.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLFieldAccess.h"
+#include "src/sksl/ir/SkSLFunctionCall.h"
+#include "src/sksl/ir/SkSLIndexExpression.h"
+#include "src/sksl/ir/SkSLLiteral.h"
+#include "src/sksl/ir/SkSLPoison.h"
+#include "src/sksl/ir/SkSLPostfixExpression.h"
+#include "src/sksl/ir/SkSLPrefixExpression.h"
+#include "src/sksl/ir/SkSLVariableReference.h"
+
+#include <utility>
+
+namespace SkSL {
+
+namespace dsl {
+
+DSLExpression::DSLExpression() {}
+
+DSLExpression::DSLExpression(DSLExpression&& other)
+ : fExpression(std::move(other.fExpression)) {}
+
+DSLExpression::DSLExpression(std::unique_ptr<SkSL::Expression> expression, Position pos)
+ : fExpression(expression ? std::move(expression)
+ : SkSL::Poison::Make(pos, ThreadContext::Context())) {
+ // If a position was passed in, it must match the expression's position.
+ SkASSERTF(!pos.valid() || this->position() == pos,
+ "expected expression position (%d-%d), but received (%d-%d)",
+ pos.startOffset(), pos.endOffset(),
+ this->position().startOffset(), this->position().endOffset());
+}
+
+DSLExpression::DSLExpression(float value, Position pos)
+ : fExpression(SkSL::Literal::MakeFloat(ThreadContext::Context(),
+ pos,
+ value)) {}
+
+DSLExpression::DSLExpression(int value, Position pos)
+ : fExpression(SkSL::Literal::MakeInt(ThreadContext::Context(),
+ pos,
+ value)) {}
+
+DSLExpression::DSLExpression(int64_t value, Position pos)
+ : fExpression(SkSL::Literal::MakeInt(ThreadContext::Context(),
+ pos,
+ value)) {}
+
+DSLExpression::DSLExpression(unsigned int value, Position pos)
+ : fExpression(SkSL::Literal::MakeInt(ThreadContext::Context(),
+ pos,
+ value)) {}
+
+DSLExpression::DSLExpression(bool value, Position pos)
+ : fExpression(SkSL::Literal::MakeBool(ThreadContext::Context(),
+ pos,
+ value)) {}
+
+DSLExpression::DSLExpression(DSLVarBase& var, Position pos)
+ : fExpression(std::make_unique<SkSL::VariableReference>(
+ pos, DSLWriter::Var(var), SkSL::VariableReference::RefKind::kRead)) {}
+
+DSLExpression::DSLExpression(DSLVarBase&& var, Position pos)
+ : DSLExpression(var) {}
+
+DSLExpression::~DSLExpression() {}
+
+DSLExpression DSLExpression::Poison(Position pos) {
+ return DSLExpression(SkSL::Poison::Make(pos, ThreadContext::Context()));
+}
+
+bool DSLExpression::isValid() const {
+ return this->hasValue() && !fExpression->is<SkSL::Poison>();
+}
+
+void DSLExpression::swap(DSLExpression& other) {
+ std::swap(fExpression, other.fExpression);
+}
+
+std::unique_ptr<SkSL::Expression> DSLExpression::release() {
+ SkASSERT(this->hasValue());
+ return std::move(fExpression);
+}
+
+std::unique_ptr<SkSL::Expression> DSLExpression::releaseIfPossible() {
+ return std::move(fExpression);
+}
+
+DSLType DSLExpression::type() const {
+ if (!this->hasValue()) {
+ return kVoid_Type;
+ }
+ return &fExpression->type();
+}
+
+std::string DSLExpression::description() const {
+ SkASSERT(this->hasValue());
+ return fExpression->description();
+}
+
+Position DSLExpression::position() const {
+ SkASSERT(this->hasValue());
+ return fExpression->fPosition;
+}
+
+void DSLExpression::setPosition(Position pos) {
+ SkASSERT(this->hasValue());
+ fExpression->fPosition = pos;
+}
+
+DSLExpression DSLExpression::x(Position pos) {
+ return Swizzle(std::move(*this), X, pos);
+}
+
+DSLExpression DSLExpression::y(Position pos) {
+ return Swizzle(std::move(*this), Y, pos);
+}
+
+DSLExpression DSLExpression::z(Position pos) {
+ return Swizzle(std::move(*this), Z, pos);
+}
+
+DSLExpression DSLExpression::w(Position pos) {
+ return Swizzle(std::move(*this), W, pos);
+}
+
+DSLExpression DSLExpression::r(Position pos) {
+ return Swizzle(std::move(*this), R, pos);
+}
+
+DSLExpression DSLExpression::g(Position pos) {
+ return Swizzle(std::move(*this), G, pos);
+}
+
+DSLExpression DSLExpression::b(Position pos) {
+ return Swizzle(std::move(*this), B, pos);
+}
+
+DSLExpression DSLExpression::a(Position pos) {
+ return Swizzle(std::move(*this), A, pos);
+}
+
+DSLExpression DSLExpression::field(std::string_view name, Position pos) {
+ return DSLExpression(FieldAccess::Convert(ThreadContext::Context(), pos,
+ *ThreadContext::SymbolTable(), this->release(), name), pos);
+}
+
+DSLExpression DSLExpression::assign(DSLExpression right) {
+ Position pos = this->position().rangeThrough(right.position());
+ return DSLExpression(BinaryExpression::Convert(ThreadContext::Context(), pos, this->release(),
+ SkSL::Operator::Kind::EQ, right.release()));
+}
+
+DSLExpression DSLExpression::operator[](DSLExpression right) {
+ Position pos = this->position().rangeThrough(right.position());
+ return DSLExpression(IndexExpression::Convert(ThreadContext::Context(),
+ *ThreadContext::SymbolTable(), pos,
+ this->release(), right.release()));
+}
+
+DSLExpression DSLExpression::index(DSLExpression index, Position pos) {
+ std::unique_ptr<SkSL::Expression> result = IndexExpression::Convert(ThreadContext::Context(),
+ *ThreadContext::SymbolTable(), pos, this->release(), index.release());
+ return DSLExpression(std::move(result), pos);
+}
+
+DSLExpression DSLExpression::operator()(SkTArray<DSLExpression> args, Position pos) {
+ ExpressionArray converted;
+ converted.reserve_back(args.size());
+ for (DSLExpression& arg : args) {
+ converted.push_back(arg.release());
+ }
+ return (*this)(std::move(converted), pos);
+}
+
+DSLExpression DSLExpression::operator()(ExpressionArray args, Position pos) {
+ return DSLExpression(SkSL::FunctionCall::Convert(ThreadContext::Context(), pos, this->release(),
+ std::move(args)), pos);
+}
+
+DSLExpression DSLExpression::prefix(Operator::Kind op, Position pos) {
+ std::unique_ptr<SkSL::Expression> result = PrefixExpression::Convert(ThreadContext::Context(),
+ pos, op, this->release());
+ return DSLExpression(std::move(result), pos);
+}
+
+DSLExpression DSLExpression::postfix(Operator::Kind op, Position pos) {
+ std::unique_ptr<SkSL::Expression> result = PostfixExpression::Convert(ThreadContext::Context(),
+ pos, this->release(), op);
+ return DSLExpression(std::move(result), pos);
+}
+
+DSLExpression DSLExpression::binary(Operator::Kind op, DSLExpression right, Position pos) {
+ std::unique_ptr<SkSL::Expression> result = BinaryExpression::Convert(ThreadContext::Context(),
+ pos, this->release(), op, right.release());
+ return DSLExpression(std::move(result), pos);
+}
+
+#define OP(op, token) \
+DSLExpression operator op(DSLExpression left, DSLExpression right) { \
+ return DSLExpression(BinaryExpression::Convert(ThreadContext::Context(), \
+ Position(), \
+ left.release(), \
+ Operator::Kind::token, \
+ right.release())); \
+}
+
+#define PREFIXOP(op, token) \
+DSLExpression operator op(DSLExpression expr) { \
+ return DSLExpression(PrefixExpression::Convert(ThreadContext::Context(), \
+ Position(), \
+ Operator::Kind::token, \
+ expr.release())); \
+}
+
+#define POSTFIXOP(op, token) \
+DSLExpression operator op(DSLExpression expr, int) { \
+ return DSLExpression(PostfixExpression::Convert(ThreadContext::Context(), \
+ Position(), \
+ expr.release(), \
+ Operator::Kind::token)); \
+}
+
+OP(+, PLUS)
+OP(+=, PLUSEQ)
+OP(-, MINUS)
+OP(-=, MINUSEQ)
+OP(*, STAR)
+OP(*=, STAREQ)
+OP(/, SLASH)
+OP(/=, SLASHEQ)
+OP(%, PERCENT)
+OP(%=, PERCENTEQ)
+OP(<<, SHL)
+OP(<<=, SHLEQ)
+OP(>>, SHR)
+OP(>>=, SHREQ)
+OP(&&, LOGICALAND)
+OP(||, LOGICALOR)
+OP(&, BITWISEAND)
+OP(&=, BITWISEANDEQ)
+OP(|, BITWISEOR)
+OP(|=, BITWISEOREQ)
+OP(^, BITWISEXOR)
+OP(^=, BITWISEXOREQ)
+DSLExpression LogicalXor(DSLExpression left, DSLExpression right) {
+ return DSLExpression(BinaryExpression::Convert(ThreadContext::Context(),
+ Position(),
+ left.release(),
+ SkSL::Operator::Kind::LOGICALXOR,
+ right.release()));
+}
+OP(==, EQEQ)
+OP(!=, NEQ)
+OP(>, GT)
+OP(<, LT)
+OP(>=, GTEQ)
+OP(<=, LTEQ)
+
+PREFIXOP(+, PLUS)
+PREFIXOP(-, MINUS)
+PREFIXOP(!, LOGICALNOT)
+PREFIXOP(~, BITWISENOT)
+PREFIXOP(++, PLUSPLUS)
+POSTFIXOP(++, PLUSPLUS)
+PREFIXOP(--, MINUSMINUS)
+POSTFIXOP(--, MINUSMINUS)
+
+DSLExpression operator,(DSLExpression left, DSLExpression right) {
+ return DSLExpression(BinaryExpression::Convert(ThreadContext::Context(),
+ Position(),
+ left.release(),
+ SkSL::Operator::Kind::COMMA,
+ right.release()));
+}
+
+} // namespace dsl
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/dsl/DSLFunction.cpp b/gfx/skia/skia/src/sksl/dsl/DSLFunction.cpp
new file mode 100644
index 0000000000..1a3836ce5a
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/dsl/DSLFunction.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2021 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/sksl/DSLFunction.h"
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLModifiers.h"
+#include "include/private/SkSLProgramElement.h"
+#include "include/private/SkSLStatement.h"
+#include "include/private/SkSLString.h"
+#include "include/sksl/DSLType.h"
+#include "include/sksl/DSLVar.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLIntrinsicList.h"
+#include "src/sksl/SkSLModifiersPool.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/SkSLThreadContext.h"
+#include "src/sksl/dsl/priv/DSLWriter.h"
+#include "src/sksl/ir/SkSLBlock.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLFunctionCall.h"
+#include "src/sksl/ir/SkSLFunctionDeclaration.h"
+#include "src/sksl/ir/SkSLFunctionDefinition.h"
+#include "src/sksl/ir/SkSLFunctionPrototype.h"
+#include "src/sksl/ir/SkSLVariable.h"
+
+#include <cstddef>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace SkSL {
+
+namespace dsl {
+
+static bool is_intrinsic_in_module(const Context& context, std::string_view name) {
+ return context.fConfig->fIsBuiltinCode && SkSL::FindIntrinsicKind(name) != kNotIntrinsic;
+}
+
+void DSLFunction::init(DSLModifiers modifiers, const DSLType& returnType, std::string_view name,
+ SkSpan<DSLParameter*> params, Position pos) {
+ fPosition = pos;
+
+ const Context& context = ThreadContext::Context();
+ if (context.fConfig->fSettings.fForceNoInline) {
+ // Apply the `noinline` modifier to every function. This allows us to test Runtime
+ // Effects without any inlining, even when the code is later added to a paint.
+ modifiers.fModifiers.fFlags &= ~Modifiers::kInline_Flag;
+ modifiers.fModifiers.fFlags |= Modifiers::kNoInline_Flag;
+ }
+
+ std::vector<std::unique_ptr<Variable>> paramVars;
+ paramVars.reserve(params.size());
+ for (DSLParameter* param : params) {
+ SkASSERT(!param->fInitialValue.hasValue());
+ SkASSERT(!param->fDeclaration);
+ std::unique_ptr<SkSL::Variable> paramVar = DSLWriter::CreateParameterVar(*param);
+ if (!paramVar) {
+ return;
+ }
+ paramVars.push_back(std::move(paramVar));
+ }
+ SkASSERT(paramVars.size() == params.size());
+ fDecl = SkSL::FunctionDeclaration::Convert(context,
+ *ThreadContext::SymbolTable(),
+ pos,
+ modifiers.fPosition,
+ context.fModifiersPool->add(modifiers.fModifiers),
+ name,
+ std::move(paramVars),
+ pos,
+ &returnType.skslType());
+ if (fDecl) {
+ for (size_t i = 0; i < params.size(); ++i) {
+ params[i]->fVar = fDecl->parameters()[i];
+ params[i]->fInitialized = true;
+ }
+ }
+}
+
+void DSLFunction::prototype() {
+ if (!fDecl) {
+ // We failed to create the declaration; error should already have been reported.
+ return;
+ }
+ ThreadContext::ProgramElements().push_back(std::make_unique<SkSL::FunctionPrototype>(
+ fDecl->fPosition, fDecl, ThreadContext::IsModule()));
+}
+
+void DSLFunction::define(DSLBlock block, Position pos) {
+ std::unique_ptr<SkSL::Block> body = block.release();
+ body->fPosition = pos;
+ if (!fDecl) {
+ // We failed to create the declaration; error should already have been reported.
+ return;
+ }
+ // We don't allow modules to define actual functions with intrinsic names. (Those should be
+ // reserved for actual intrinsics.)
+ const Context& context = ThreadContext::Context();
+ if (is_intrinsic_in_module(context, fDecl->name())) {
+ ThreadContext::ReportError(
+ SkSL::String::printf("Intrinsic function '%.*s' should not have a definition",
+ (int)fDecl->name().size(),
+ fDecl->name().data()),
+ fDecl->fPosition);
+ return;
+ }
+
+ if (fDecl->definition()) {
+ ThreadContext::ReportError(SkSL::String::printf("function '%s' was already defined",
+ fDecl->description().c_str()),
+ fDecl->fPosition);
+ return;
+ }
+ std::unique_ptr<FunctionDefinition> function = FunctionDefinition::Convert(
+ ThreadContext::Context(),
+ pos,
+ *fDecl,
+ std::move(body),
+ /*builtin=*/false);
+ fDecl->setDefinition(function.get());
+ ThreadContext::ProgramElements().push_back(std::move(function));
+}
+
+DSLExpression DSLFunction::call(SkSpan<DSLExpression> args, Position pos) {
+ ExpressionArray released;
+ released.reserve_back(args.size());
+ for (DSLExpression& arg : args) {
+ released.push_back(arg.release());
+ }
+ return this->call(std::move(released));
+}
+
+DSLExpression DSLFunction::call(ExpressionArray args, Position pos) {
+ std::unique_ptr<SkSL::Expression> result =
+ SkSL::FunctionCall::Convert(ThreadContext::Context(), pos, *fDecl, std::move(args));
+ return DSLExpression(std::move(result), pos);
+}
+
+} // namespace dsl
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/dsl/DSLLayout.cpp b/gfx/skia/skia/src/sksl/dsl/DSLLayout.cpp
new file mode 100644
index 0000000000..4ef840f230
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/dsl/DSLLayout.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2021 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/sksl/DSLLayout.h"
+
+#include "src/sksl/SkSLThreadContext.h"
+
+#include <string>
+
+namespace SkSL {
+
+namespace dsl {
+
+DSLLayout& DSLLayout::flag(SkSL::Layout::Flag mask, const char* name, Position pos) {
+ if (fSkSLLayout.fFlags & mask) {
+ ThreadContext::ReportError("layout qualifier '" + std::string(name) +
+ "' appears more than once", pos);
+ }
+ fSkSLLayout.fFlags |= mask;
+ return *this;
+}
+
+DSLLayout& DSLLayout::intValue(int* target, int value, SkSL::Layout::Flag flag, const char* name,
+ Position pos) {
+ this->flag(flag, name, pos);
+ *target = value;
+ return *this;
+}
+
+} // namespace dsl
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/dsl/DSLStatement.cpp b/gfx/skia/skia/src/sksl/dsl/DSLStatement.cpp
new file mode 100644
index 0000000000..ed11acac80
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/dsl/DSLStatement.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2021 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/sksl/DSLStatement.h"
+
+#include "include/private/SkSLDefines.h"
+#include "include/sksl/DSLBlock.h"
+#include "include/sksl/DSLExpression.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/SkSLThreadContext.h"
+#include "src/sksl/ir/SkSLBlock.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLExpressionStatement.h"
+#include "src/sksl/ir/SkSLNop.h"
+
+namespace SkSL {
+
+namespace dsl {
+
+DSLStatement::DSLStatement() {}
+
+DSLStatement::DSLStatement(DSLBlock block)
+ : fStatement(block.release()) {}
+
+DSLStatement::DSLStatement(DSLExpression expr) {
+ std::unique_ptr<SkSL::Expression> skslExpr = expr.release();
+ if (skslExpr) {
+ fStatement = SkSL::ExpressionStatement::Convert(ThreadContext::Context(),
+ std::move(skslExpr));
+ }
+}
+
+DSLStatement::DSLStatement(std::unique_ptr<SkSL::Expression> expr)
+ : fStatement(SkSL::ExpressionStatement::Convert(ThreadContext::Context(), std::move(expr))) {
+ SkASSERT(this->hasValue());
+}
+
+DSLStatement::DSLStatement(std::unique_ptr<SkSL::Statement> stmt)
+ : fStatement(std::move(stmt)) {
+ SkASSERT(this->hasValue());
+}
+
+DSLStatement::DSLStatement(std::unique_ptr<SkSL::Statement> stmt, Position pos)
+ : fStatement(stmt ? std::move(stmt) : SkSL::Nop::Make()) {
+ if (pos.valid() && !fStatement->fPosition.valid()) {
+ fStatement->fPosition = pos;
+ }
+}
+
+DSLStatement::~DSLStatement() {}
+
+DSLStatement operator,(DSLStatement left, DSLStatement right) {
+ Position pos = left.fStatement->fPosition;
+ StatementArray stmts;
+ stmts.reserve_back(2);
+ stmts.push_back(left.release());
+ stmts.push_back(right.release());
+ return DSLStatement(SkSL::Block::Make(pos, std::move(stmts), Block::Kind::kCompoundStatement));
+}
+
+} // namespace dsl
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/dsl/DSLType.cpp b/gfx/skia/skia/src/sksl/dsl/DSLType.cpp
new file mode 100644
index 0000000000..82c000e5ab
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/dsl/DSLType.cpp
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/sksl/DSLType.h"
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLProgramElement.h"
+#include "include/private/SkSLString.h"
+#include "include/private/SkSLSymbol.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/SkSLThreadContext.h"
+#include "src/sksl/ir/SkSLConstructor.h"
+#include "src/sksl/ir/SkSLStructDefinition.h"
+#include "src/sksl/ir/SkSLSymbolTable.h"
+#include "src/sksl/ir/SkSLType.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace SkSL {
+
+struct Modifiers;
+
+namespace dsl {
+
+static const SkSL::Type* verify_type(const Context& context,
+ const SkSL::Type* type,
+ bool allowGenericTypes,
+ Position pos) {
+ if (!context.fConfig->fIsBuiltinCode && type) {
+ if (!allowGenericTypes && (type->isGeneric() || type->isLiteral())) {
+ context.fErrors->error(pos, "type '" + std::string(type->name()) + "' is generic");
+ return context.fTypes.fPoison.get();
+ }
+ if (!type->isAllowedInES2(context)) {
+ context.fErrors->error(pos, "type '" + std::string(type->name()) +"' is not supported");
+ return context.fTypes.fPoison.get();
+ }
+ }
+ return type;
+}
+
+static const SkSL::Type* find_type(const Context& context,
+ Position pos,
+ std::string_view name) {
+ const Symbol* symbol = ThreadContext::SymbolTable()->find(name);
+ if (!symbol) {
+ context.fErrors->error(pos, String::printf("no symbol named '%.*s'",
+ (int)name.length(), name.data()));
+ return context.fTypes.fPoison.get();
+ }
+ if (!symbol->is<SkSL::Type>()) {
+ context.fErrors->error(pos, String::printf("symbol '%.*s' is not a type",
+ (int)name.length(), name.data()));
+ return context.fTypes.fPoison.get();
+ }
+ const SkSL::Type* type = &symbol->as<SkSL::Type>();
+ return verify_type(context, type, /*allowGenericTypes=*/false, pos);
+}
+
+static const SkSL::Type* find_type(const Context& context,
+ Position overallPos,
+ std::string_view name,
+ Position modifiersPos,
+ Modifiers* modifiers) {
+ const auto* type = find_type(context, overallPos, name);
+ return type->applyQualifiers(context, modifiers, ThreadContext::SymbolTable().get(),
+ modifiersPos);
+}
+
+static const SkSL::Type* get_type_from_type_constant(TypeConstant tc) {
+ const Context& context = ThreadContext::Context();
+ switch (tc) {
+ case kBool_Type:
+ return context.fTypes.fBool.get();
+ case kBool2_Type:
+ return context.fTypes.fBool2.get();
+ case kBool3_Type:
+ return context.fTypes.fBool3.get();
+ case kBool4_Type:
+ return context.fTypes.fBool4.get();
+ case kHalf_Type:
+ return context.fTypes.fHalf.get();
+ case kHalf2_Type:
+ return context.fTypes.fHalf2.get();
+ case kHalf3_Type:
+ return context.fTypes.fHalf3.get();
+ case kHalf4_Type:
+ return context.fTypes.fHalf4.get();
+ case kHalf2x2_Type:
+ return context.fTypes.fHalf2x2.get();
+ case kHalf3x2_Type:
+ return context.fTypes.fHalf3x2.get();
+ case kHalf4x2_Type:
+ return context.fTypes.fHalf4x2.get();
+ case kHalf2x3_Type:
+ return context.fTypes.fHalf2x3.get();
+ case kHalf3x3_Type:
+ return context.fTypes.fHalf3x3.get();
+ case kHalf4x3_Type:
+ return context.fTypes.fHalf4x3.get();
+ case kHalf2x4_Type:
+ return context.fTypes.fHalf2x4.get();
+ case kHalf3x4_Type:
+ return context.fTypes.fHalf3x4.get();
+ case kHalf4x4_Type:
+ return context.fTypes.fHalf4x4.get();
+ case kFloat_Type:
+ return context.fTypes.fFloat.get();
+ case kFloat2_Type:
+ return context.fTypes.fFloat2.get();
+ case kFloat3_Type:
+ return context.fTypes.fFloat3.get();
+ case kFloat4_Type:
+ return context.fTypes.fFloat4.get();
+ case kFloat2x2_Type:
+ return context.fTypes.fFloat2x2.get();
+ case kFloat3x2_Type:
+ return context.fTypes.fFloat3x2.get();
+ case kFloat4x2_Type:
+ return context.fTypes.fFloat4x2.get();
+ case kFloat2x3_Type:
+ return context.fTypes.fFloat2x3.get();
+ case kFloat3x3_Type:
+ return context.fTypes.fFloat3x3.get();
+ case kFloat4x3_Type:
+ return context.fTypes.fFloat4x3.get();
+ case kFloat2x4_Type:
+ return context.fTypes.fFloat2x4.get();
+ case kFloat3x4_Type:
+ return context.fTypes.fFloat3x4.get();
+ case kFloat4x4_Type:
+ return context.fTypes.fFloat4x4.get();
+ case kInt_Type:
+ return context.fTypes.fInt.get();
+ case kInt2_Type:
+ return context.fTypes.fInt2.get();
+ case kInt3_Type:
+ return context.fTypes.fInt3.get();
+ case kInt4_Type:
+ return context.fTypes.fInt4.get();
+ case kShader_Type:
+ return context.fTypes.fShader.get();
+ case kShort_Type:
+ return context.fTypes.fShort.get();
+ case kShort2_Type:
+ return context.fTypes.fShort2.get();
+ case kShort3_Type:
+ return context.fTypes.fShort3.get();
+ case kShort4_Type:
+ return context.fTypes.fShort4.get();
+ case kUInt_Type:
+ return context.fTypes.fUInt.get();
+ case kUInt2_Type:
+ return context.fTypes.fUInt2.get();
+ case kUInt3_Type:
+ return context.fTypes.fUInt3.get();
+ case kUInt4_Type:
+ return context.fTypes.fUInt4.get();
+ case kUShort_Type:
+ return context.fTypes.fUShort.get();
+ case kUShort2_Type:
+ return context.fTypes.fUShort2.get();
+ case kUShort3_Type:
+ return context.fTypes.fUShort3.get();
+ case kUShort4_Type:
+ return context.fTypes.fUShort4.get();
+ case kVoid_Type:
+ return context.fTypes.fVoid.get();
+ case kPoison_Type:
+ return context.fTypes.fPoison.get();
+ default:
+ SkUNREACHABLE;
+ }
+}
+
+DSLType::DSLType(TypeConstant tc, Position pos)
+ : fSkSLType(verify_type(ThreadContext::Context(),
+ get_type_from_type_constant(tc),
+ /*allowGenericTypes=*/false,
+ pos)) {}
+
+DSLType::DSLType(std::string_view name, Position pos)
+ : fSkSLType(find_type(ThreadContext::Context(), pos, name)) {}
+
+DSLType::DSLType(std::string_view name, DSLModifiers* modifiers, Position pos)
+ : fSkSLType(find_type(ThreadContext::Context(),
+ pos,
+ name,
+ modifiers->fPosition,
+ &modifiers->fModifiers)) {}
+
+DSLType::DSLType(const SkSL::Type* type, Position pos)
+ : fSkSLType(verify_type(ThreadContext::Context(), type, /*allowGenericTypes=*/true, pos)) {}
+
+DSLType DSLType::Invalid() {
+ return DSLType(ThreadContext::Context().fTypes.fInvalid.get(), Position());
+}
+
+bool DSLType::isBoolean() const {
+ return this->skslType().isBoolean();
+}
+
+bool DSLType::isNumber() const {
+ return this->skslType().isNumber();
+}
+
+bool DSLType::isFloat() const {
+ return this->skslType().isFloat();
+}
+
+bool DSLType::isSigned() const {
+ return this->skslType().isSigned();
+}
+
+bool DSLType::isUnsigned() const {
+ return this->skslType().isUnsigned();
+}
+
+bool DSLType::isInteger() const {
+ return this->skslType().isInteger();
+}
+
+bool DSLType::isScalar() const {
+ return this->skslType().isScalar();
+}
+
+bool DSLType::isVector() const {
+ return this->skslType().isVector();
+}
+
+bool DSLType::isMatrix() const {
+ return this->skslType().isMatrix();
+}
+
+bool DSLType::isArray() const {
+ return this->skslType().isArray();
+}
+
+bool DSLType::isStruct() const {
+ return this->skslType().isStruct();
+}
+
+bool DSLType::isInterfaceBlock() const {
+ return this->skslType().isInterfaceBlock();
+}
+
+bool DSLType::isEffectChild() const {
+ return this->skslType().isEffectChild();
+}
+
+DSLExpression DSLType::Construct(DSLType type, SkSpan<DSLExpression> argArray) {
+ SkSL::ExpressionArray skslArgs;
+ skslArgs.reserve_back(argArray.size());
+
+ for (DSLExpression& arg : argArray) {
+ if (!arg.hasValue()) {
+ return DSLExpression();
+ }
+ skslArgs.push_back(arg.release());
+ }
+ return DSLExpression(SkSL::Constructor::Convert(ThreadContext::Context(), Position(),
+ type.skslType(), std::move(skslArgs)));
+}
+
+DSLType Array(const DSLType& base, int count, Position pos) {
+ count = base.skslType().convertArraySize(ThreadContext::Context(), pos,
+ DSLExpression(count, pos).release());
+ if (!count) {
+ return DSLType(kPoison_Type);
+ }
+ return DSLType(ThreadContext::SymbolTable()->addArrayDimension(&base.skslType(), count), pos);
+}
+
+DSLType UnsizedArray(const DSLType& base, Position pos) {
+ if (!base.skslType().checkIfUsableInArray(ThreadContext::Context(), pos)) {
+ return DSLType(kPoison_Type);
+ }
+ return ThreadContext::SymbolTable()->addArrayDimension(&base.skslType(),
+ SkSL::Type::kUnsizedArray);
+}
+
+DSLType StructType(std::string_view name,
+ SkSpan<DSLField> fields,
+ bool interfaceBlock,
+ Position pos) {
+ std::vector<SkSL::Type::Field> skslFields;
+ skslFields.reserve(fields.size());
+ for (const DSLField& field : fields) {
+ skslFields.emplace_back(field.fPosition, field.fModifiers.fModifiers, field.fName,
+ &field.fType.skslType());
+ }
+ auto newType = SkSL::Type::MakeStructType(ThreadContext::Context(), pos, name,
+ std::move(skslFields), interfaceBlock);
+ return DSLType(ThreadContext::SymbolTable()->add(std::move(newType)), pos);
+}
+
+DSLType Struct(std::string_view name, SkSpan<DSLField> fields, Position pos) {
+ DSLType result = StructType(name, fields, /*interfaceBlock=*/false, pos);
+ ThreadContext::ProgramElements().push_back(
+ std::make_unique<SkSL::StructDefinition>(pos, result.skslType()));
+ return result;
+}
+
+} // namespace dsl
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/dsl/DSLVar.cpp b/gfx/skia/skia/src/sksl/dsl/DSLVar.cpp
new file mode 100644
index 0000000000..299aa27a11
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/dsl/DSLVar.cpp
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/sksl/DSLVar.h"
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLStatement.h"
+#include "include/private/SkSLSymbol.h"
+#include "include/sksl/DSLModifiers.h"
+#include "include/sksl/DSLType.h"
+#include "include/sksl/SkSLOperator.h"
+#include "src/sksl/SkSLThreadContext.h"
+#include "src/sksl/ir/SkSLBinaryExpression.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLFieldAccess.h"
+#include "src/sksl/ir/SkSLFunctionCall.h"
+#include "src/sksl/ir/SkSLSymbolTable.h"
+#include "src/sksl/ir/SkSLVariable.h"
+
+#include <utility>
+
+namespace SkSL {
+
+namespace dsl {
+
+/**
+ * DSLVarBase
+ */
+
+DSLVarBase::DSLVarBase(VariableStorage storage, DSLType type, std::string_view name,
+ DSLExpression initialValue, Position pos, Position namePos)
+ : DSLVarBase(storage, DSLModifiers(), std::move(type), name, std::move(initialValue),
+ pos, namePos) {}
+
+DSLVarBase::DSLVarBase(VariableStorage storage, const DSLModifiers& modifiers, DSLType type,
+ std::string_view name, DSLExpression initialValue, Position pos,
+ Position namePos)
+ : fModifiers(std::move(modifiers))
+ , fType(std::move(type))
+ , fNamePosition(namePos)
+ , fName(name)
+ , fInitialValue(std::move(initialValue))
+ , fPosition(pos)
+ , fStorage(storage) {}
+
+void DSLVarBase::swap(DSLVarBase& other) {
+ SkASSERT(this->storage() == other.storage());
+ std::swap(fModifiers, other.fModifiers);
+ std::swap(fType, other.fType);
+ std::swap(fDeclaration, other.fDeclaration);
+ std::swap(fVar, other.fVar);
+ std::swap(fNamePosition, other.fNamePosition);
+ std::swap(fName, other.fName);
+ std::swap(fInitialValue.fExpression, other.fInitialValue.fExpression);
+ std::swap(fInitialized, other.fInitialized);
+ std::swap(fPosition, other.fPosition);
+}
+
+DSLExpression DSLVarBase::operator[](DSLExpression&& index) {
+ return DSLExpression(*this)[std::move(index)];
+}
+
+DSLExpression DSLVarBase::assignExpression(DSLExpression expr) {
+ return DSLExpression(BinaryExpression::Convert(ThreadContext::Context(), Position(),
+ DSLExpression(*this, Position()).release(), SkSL::Operator::Kind::EQ,
+ expr.release()));
+}
+
+/**
+ * DSLVar
+ */
+
+DSLVar::DSLVar() : DSLVarBase(SkSL::VariableStorage::kLocal) {}
+
+DSLVar::DSLVar(DSLType type, std::string_view name, DSLExpression initialValue,
+ Position pos, Position namePos)
+ : INHERITED(SkSL::VariableStorage::kLocal, type, name, std::move(initialValue),
+ pos, namePos) {}
+
+DSLVar::DSLVar(const DSLModifiers& modifiers, DSLType type, std::string_view name,
+ DSLExpression initialValue, Position pos, Position namePos)
+ : INHERITED(SkSL::VariableStorage::kLocal, modifiers, type, name, std::move(initialValue),
+ pos, namePos) {}
+
+void DSLVar::swap(DSLVar& other) {
+ INHERITED::swap(other);
+}
+
+/**
+ * DSLGlobalVar
+ */
+
+DSLGlobalVar::DSLGlobalVar() : DSLVarBase(SkSL::VariableStorage::kGlobal) {}
+
+DSLGlobalVar::DSLGlobalVar(DSLType type, std::string_view name, DSLExpression initialValue,
+ Position pos, Position namePos)
+ : INHERITED(SkSL::VariableStorage::kGlobal, type, name, std::move(initialValue),
+ pos, namePos) {}
+
+DSLGlobalVar::DSLGlobalVar(const DSLModifiers& modifiers, DSLType type, std::string_view name,
+ DSLExpression initialValue, Position pos, Position namePos)
+ : INHERITED(SkSL::VariableStorage::kGlobal, modifiers, type, name, std::move(initialValue),
+ pos, namePos) {}
+
+DSLGlobalVar::DSLGlobalVar(const char* name)
+ : INHERITED(SkSL::VariableStorage::kGlobal, kVoid_Type, name, DSLExpression(),
+ Position(), Position()) {
+ fName = name;
+ SkSL::SymbolTable* symbolTable = ThreadContext::SymbolTable().get();
+ SkSL::Symbol* result = symbolTable->findMutable(fName);
+ SkASSERTF(result, "could not find '%.*s' in symbol table", (int)fName.length(), fName.data());
+ fVar = &result->as<SkSL::Variable>();
+ fInitialized = true;
+}
+
+void DSLGlobalVar::swap(DSLGlobalVar& other) {
+ INHERITED::swap(other);
+}
+
+std::unique_ptr<SkSL::Expression> DSLGlobalVar::methodCall(std::string_view methodName,
+ Position pos) {
+ if (!this->fType.isEffectChild()) {
+ ThreadContext::ReportError("type does not support method calls", pos);
+ return nullptr;
+ }
+ return FieldAccess::Convert(ThreadContext::Context(), pos, *ThreadContext::SymbolTable(),
+ DSLExpression(*this, pos).release(), methodName);
+}
+
+DSLExpression DSLGlobalVar::eval(ExpressionArray args, Position pos) {
+ auto method = this->methodCall("eval", pos);
+ return DSLExpression(
+ method ? SkSL::FunctionCall::Convert(ThreadContext::Context(), pos, std::move(method),
+ std::move(args))
+ : nullptr,
+ pos);
+}
+
+DSLExpression DSLGlobalVar::eval(DSLExpression x, Position pos) {
+ ExpressionArray converted;
+ converted.push_back(x.release());
+ return this->eval(std::move(converted), pos);
+}
+
+DSLExpression DSLGlobalVar::eval(DSLExpression x, DSLExpression y, Position pos) {
+ ExpressionArray converted;
+ converted.push_back(x.release());
+ converted.push_back(y.release());
+ return this->eval(std::move(converted), pos);
+}
+
+/**
+ * DSLParameter
+ */
+
+DSLParameter::DSLParameter() : DSLVarBase(SkSL::VariableStorage::kParameter) {}
+
+DSLParameter::DSLParameter(DSLType type, std::string_view name, Position pos, Position namePos)
+ : INHERITED(SkSL::VariableStorage::kParameter, type, name, DSLExpression(), pos, namePos) {}
+
+DSLParameter::DSLParameter(const DSLModifiers& modifiers, DSLType type, std::string_view name,
+ Position pos, Position namePos)
+ : INHERITED(SkSL::VariableStorage::kParameter, modifiers, type, name, DSLExpression(),
+ pos, namePos) {}
+
+void DSLParameter::swap(DSLParameter& other) {
+ INHERITED::swap(other);
+}
+
+} // namespace dsl
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/dsl/priv/DSLWriter.cpp b/gfx/skia/skia/src/sksl/dsl/priv/DSLWriter.cpp
new file mode 100644
index 0000000000..9885db21f8
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/dsl/priv/DSLWriter.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/dsl/priv/DSLWriter.h"
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLProgramElement.h"
+#include "include/private/SkSLStatement.h"
+#include "include/sksl/DSLCore.h"
+#include "include/sksl/DSLExpression.h"
+#include "include/sksl/DSLModifiers.h"
+#include "include/sksl/DSLStatement.h"
+#include "include/sksl/DSLType.h"
+#include "include/sksl/DSLVar.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/SkSLModifiersPool.h"
+#include "src/sksl/SkSLThreadContext.h"
+#include "src/sksl/ir/SkSLBlock.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLNop.h"
+#include "src/sksl/ir/SkSLSymbolTable.h"
+#include "src/sksl/ir/SkSLType.h"
+#include "src/sksl/ir/SkSLVarDeclarations.h"
+#include "src/sksl/ir/SkSLVariable.h"
+
+#include <utility>
+#include <vector>
+
+namespace SkSL {
+
+namespace dsl {
+
+SkSL::Variable* DSLWriter::Var(DSLVarBase& var) {
+ // fInitialized is true if we have attempted to create a var, whether or not we actually
+ // succeeded. If it's true, we don't want to try again, to avoid reporting the same error
+ // multiple times.
+ if (!var.fInitialized) {
+ // We haven't even attempted to create a var yet, so fVar ought to be null
+ SkASSERT(!var.fVar);
+ var.fInitialized = true;
+ if (var.storage() != SkSL::VariableStorage::kParameter) {
+ const SkSL::Type* baseType = &var.fType.skslType();
+ if (baseType->isArray()) {
+ baseType = &baseType->componentType();
+ }
+ }
+ std::unique_ptr<SkSL::Variable> skslvar = SkSL::Variable::Convert(ThreadContext::Context(),
+ var.fPosition,
+ var.fModifiers.fPosition,
+ var.fModifiers.fModifiers,
+ &var.fType.skslType(),
+ var.fNamePosition,
+ var.fName,
+ /*isArray=*/false,
+ /*arraySize=*/nullptr,
+ var.storage());
+ SkSL::Variable* varPtr = skslvar.get();
+ if (var.storage() != SkSL::VariableStorage::kParameter) {
+ var.fDeclaration = VarDeclaration::Convert(ThreadContext::Context(),
+ std::move(skslvar),
+ var.fInitialValue.releaseIfPossible(),
+ /*addToSymbolTable=*/false);
+ if (var.fDeclaration) {
+ var.fVar = varPtr;
+ var.fInitialized = true;
+ }
+ }
+ }
+ return var.fVar;
+}
+
+std::unique_ptr<SkSL::Variable> DSLWriter::CreateParameterVar(DSLParameter& var) {
+ // This should only be called on undeclared parameter variables, but we allow the creation to go
+ // ahead regardless so we don't have to worry about null pointers potentially sneaking in and
+ // breaking things. DSLFunction is responsible for reporting errors for invalid parameters.
+ return SkSL::Variable::Convert(ThreadContext::Context(),
+ var.fPosition,
+ var.fModifiers.fPosition,
+ var.fModifiers.fModifiers,
+ &var.fType.skslType(),
+ var.fNamePosition,
+ var.fName,
+ /*isArray=*/false,
+ /*arraySize=*/nullptr,
+ var.storage());
+}
+
+std::unique_ptr<SkSL::Statement> DSLWriter::Declaration(DSLVarBase& var) {
+ Var(var);
+ if (!var.fDeclaration) {
+ // We should have already reported an error before ending up here, just clean up the
+ // initial value so it doesn't assert and return a nop.
+ var.fInitialValue.releaseIfPossible();
+ return SkSL::Nop::Make();
+ }
+ return std::move(var.fDeclaration);
+}
+
+void DSLWriter::AddVarDeclaration(DSLStatement& existing, DSLVar& additional) {
+ if (existing.fStatement->is<Block>()) {
+ SkSL::Block& block = existing.fStatement->as<Block>();
+ SkASSERT(!block.isScope());
+ block.children().push_back(Declare(additional).release());
+ } else if (existing.fStatement->is<VarDeclaration>()) {
+ Position pos = existing.fStatement->fPosition;
+ StatementArray stmts;
+ stmts.reserve_back(2);
+ stmts.push_back(std::move(existing.fStatement));
+ stmts.push_back(Declare(additional).release());
+ existing.fStatement = SkSL::Block::Make(pos, std::move(stmts),
+ Block::Kind::kCompoundStatement);
+ } else if (existing.fStatement->isEmpty()) {
+ // If the variable declaration generated an error, we can end up with a Nop statement here.
+ existing.fStatement = Declare(additional).release();
+ }
+}
+
+void DSLWriter::Reset() {
+ SymbolTable::Pop(&ThreadContext::SymbolTable());
+ SymbolTable::Push(&ThreadContext::SymbolTable());
+ ThreadContext::ProgramElements().clear();
+ ThreadContext::GetModifiersPool()->clear();
+}
+
+} // namespace dsl
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/dsl/priv/DSLWriter.h b/gfx/skia/skia/src/sksl/dsl/priv/DSLWriter.h
new file mode 100644
index 0000000000..798c642eab
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/dsl/priv/DSLWriter.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_DSLWRITER
+#define SKSL_DSLWRITER
+
+#include "include/core/SkTypes.h"
+
+#include <memory>
+
+namespace SkSL {
+
+class Variable;
+class Statement;
+
+namespace dsl {
+
+class DSLParameter;
+class DSLStatement;
+class DSLVarBase;
+class DSLVar;
+
+/**
+ * Various utility methods needed by DSL code.
+ */
+class DSLWriter {
+public:
+ /**
+ * Returns the SkSL variable corresponding to a DSL var.
+ */
+ static SkSL::Variable* Var(DSLVarBase& var);
+
+ /**
+ * Creates an SkSL variable corresponding to a DSLParameter.
+ */
+ static std::unique_ptr<SkSL::Variable> CreateParameterVar(DSLParameter& var);
+
+ /**
+ * Returns the SkSL declaration corresponding to a DSLVar.
+ */
+ static std::unique_ptr<SkSL::Statement> Declaration(DSLVarBase& var);
+
+ /**
+ * Adds a new declaration into an existing declaration statement. This either turns the original
+ * declaration into an unscoped block or, if it already was, appends a new statement to the end
+ * of it.
+ */
+ static void AddVarDeclaration(DSLStatement& existing, DSLVar& additional);
+
+ /**
+ * Clears any elements or symbols which have been output.
+ */
+ static void Reset();
+};
+
+} // namespace dsl
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/dsl/priv/DSL_priv.h b/gfx/skia/skia/src/sksl/dsl/priv/DSL_priv.h
new file mode 100644
index 0000000000..4967291e7e
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/dsl/priv/DSL_priv.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2021 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_DSL_PRIV
+#define SKSL_DSL_PRIV
+
+#include "include/private/SkSLProgramKind.h"
+
+namespace SkSL {
+
+class Compiler;
+struct ProgramSettings;
+
+namespace dsl {
+
+/**
+ * Initializes the DSL for compiling modules (SkSL include files).
+ */
+void StartModule(SkSL::Compiler* compiler,
+ SkSL::ProgramKind kind,
+ const SkSL::ProgramSettings& settings,
+ const SkSL::Module* parent);
+
+} // namespace dsl
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/generated/sksl_compute.minified.sksl b/gfx/skia/skia/src/sksl/generated/sksl_compute.minified.sksl
new file mode 100644
index 0000000000..f0322f9ae2
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/generated/sksl_compute.minified.sksl
@@ -0,0 +1,7 @@
+static constexpr char SKSL_MINIFIED_sksl_compute[] =
+"layout(builtin=24)in uint3 sk_NumWorkgroups;layout(builtin=26)in uint3 sk_WorkgroupID"
+";layout(builtin=27)in uint3 sk_LocalInvocationID;layout(builtin=28)in uint3"
+" sk_GlobalInvocationID;layout(builtin=29)in uint sk_LocalInvocationIndex;$pure"
+" half4 read($readableTexture2D,uint2);void write($writableTexture2D,uint2,half4"
+");$pure uint width($genTexture2D);$pure uint height($genTexture2D);void workgroupBarrier"
+"();void storageBarrier();";
diff --git a/gfx/skia/skia/src/sksl/generated/sksl_compute.unoptimized.sksl b/gfx/skia/skia/src/sksl/generated/sksl_compute.unoptimized.sksl
new file mode 100644
index 0000000000..7f7d211176
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/generated/sksl_compute.unoptimized.sksl
@@ -0,0 +1,7 @@
+static constexpr char SKSL_MINIFIED_sksl_compute[] =
+"layout(builtin=24)in uint3 sk_NumWorkgroups;layout(builtin=26)in uint3 sk_WorkgroupID"
+";layout(builtin=27)in uint3 sk_LocalInvocationID;layout(builtin=28)in uint3"
+" sk_GlobalInvocationID;layout(builtin=29)in uint sk_LocalInvocationIndex;$pure"
+" half4 read($readableTexture2D t,uint2 pos);void write($writableTexture2D t"
+",uint2 pos,half4 color);$pure uint width($genTexture2D t);$pure uint height"
+"($genTexture2D t);void workgroupBarrier();void storageBarrier();";
diff --git a/gfx/skia/skia/src/sksl/generated/sksl_frag.minified.sksl b/gfx/skia/skia/src/sksl/generated/sksl_frag.minified.sksl
new file mode 100644
index 0000000000..f0d4a792d0
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/generated/sksl_frag.minified.sksl
@@ -0,0 +1,5 @@
+static constexpr char SKSL_MINIFIED_sksl_frag[] =
+"layout(builtin=15)in float4 sk_FragCoord;layout(builtin=17)in bool sk_Clockwise"
+";layout(location=0,index=0,builtin=10001)out half4 sk_FragColor;layout(builtin"
+"=10008)half4 sk_LastFragColor;layout(builtin=10012)out half4 sk_SecondaryFragColor"
+";";
diff --git a/gfx/skia/skia/src/sksl/generated/sksl_frag.unoptimized.sksl b/gfx/skia/skia/src/sksl/generated/sksl_frag.unoptimized.sksl
new file mode 100644
index 0000000000..f0d4a792d0
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/generated/sksl_frag.unoptimized.sksl
@@ -0,0 +1,5 @@
+static constexpr char SKSL_MINIFIED_sksl_frag[] =
+"layout(builtin=15)in float4 sk_FragCoord;layout(builtin=17)in bool sk_Clockwise"
+";layout(location=0,index=0,builtin=10001)out half4 sk_FragColor;layout(builtin"
+"=10008)half4 sk_LastFragColor;layout(builtin=10012)out half4 sk_SecondaryFragColor"
+";";
diff --git a/gfx/skia/skia/src/sksl/generated/sksl_gpu.minified.sksl b/gfx/skia/skia/src/sksl/generated/sksl_gpu.minified.sksl
new file mode 100644
index 0000000000..beb7f44c00
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/generated/sksl_gpu.minified.sksl
@@ -0,0 +1,85 @@
+static constexpr char SKSL_MINIFIED_sksl_gpu[] =
+"$pure $genIType mix($genIType,$genIType,$genBType);$pure $genBType mix($genBType"
+",$genBType,$genBType);$pure $genType fma($genType,$genType,$genType);$pure $genHType"
+" fma($genHType,$genHType,$genHType);$genType frexp($genType,out $genIType);"
+"$genHType frexp($genHType,out $genIType);$pure $genType ldexp($genType,$genIType"
+");$pure $genHType ldexp($genHType,$genIType);$pure uint packSnorm2x16(float2"
+");$pure uint packUnorm4x8(float4);$pure uint packSnorm4x8(float4);$pure float2"
+" unpackSnorm2x16(uint);$pure float4 unpackUnorm4x8(uint);$pure float4 unpackSnorm4x8"
+"(uint);$pure uint packHalf2x16(float2);$pure float2 unpackHalf2x16(uint);$pure"
+" $genIType bitCount($genIType);$pure $genIType bitCount($genUType);$pure $genIType"
+" findLSB($genIType);$pure $genIType findLSB($genUType);$pure $genIType findMSB"
+"($genIType);$pure $genIType findMSB($genUType);$pure sampler2D makeSampler2D"
+"(texture2D,sampler);$pure half4 sample(sampler2D,float2);$pure half4 sample"
+"(sampler2D,float3);$pure half4 sample(sampler2D,float3,float);$pure half4 sample"
+"(samplerExternalOES,float2);$pure half4 sample(samplerExternalOES,float2,float"
+");$pure half4 sample(sampler2DRect,float2);$pure half4 sample(sampler2DRect"
+",float3);$pure half4 sampleLod(sampler2D,float2,float);$pure half4 sampleLod"
+"(sampler2D,float3,float);$pure half4 sampleGrad(sampler2D,float2,float2,float2"
+");$pure half4 subpassLoad(subpassInput);$pure half4 subpassLoad(subpassInputMS"
+",int);$pure uint atomicLoad(atomicUint);void atomicStore(atomicUint,uint);uint"
+" atomicAdd(atomicUint,uint);$pure half4 blend_clear(half4 a,half4 b){return"
+" half4(0.);}$pure half4 blend_src(half4 a,half4 b){return a;}$pure half4 blend_dst"
+"(half4 a,half4 b){return b;}$pure half4 blend_src_over(half4 a,half4 b){return"
+" a+(1.-a.w)*b;}$pure half4 blend_dst_over(half4 a,half4 b){return(1.-b.w)*a"
+"+b;}$pure half4 blend_src_in(half4 a,half4 b){return a*b.w;}$pure half4 blend_dst_in"
+"(half4 a,half4 b){return b*a.w;}$pure half4 blend_src_out(half4 a,half4 b){"
+"return(1.-b.w)*a;}$pure half4 blend_dst_out(half4 a,half4 b){return(1.-a.w)"
+"*b;}$pure half4 blend_src_atop(half4 a,half4 b){return b.w*a+(1.-a.w)*b;}$pure"
+" half4 blend_dst_atop(half4 a,half4 b){return(1.-b.w)*a+a.w*b;}$pure half4 blend_xor"
+"(half4 a,half4 b){return(1.-b.w)*a+(1.-a.w)*b;}$pure half4 blend_plus(half4"
+" a,half4 b){return min(a+b,1.);}$pure half4 blend_porter_duff(half4 a,half4"
+" b,half4 c){half2 d=a.xy+a.zw*(half2(c.w,b.w)+min(a.zw,0.));return min(half4"
+"(1.),b*d.x+c*d.y);}$pure half4 blend_modulate(half4 a,half4 b){return a*b;}"
+"$pure half4 blend_screen(half4 a,half4 b){return a+(1.-a)*b;}$pure half $b("
+"half2 a,half2 b){return 2.*b.x<=b.y?(2.*a.x)*b.x:a.y*b.y-(2.*(b.y-b.x))*(a."
+"y-a.x);}$pure half4 blend_overlay(half4 a,half4 b){half4 c=half4($b(a.xw,b."
+"xw),$b(a.yw,b.yw),$b(a.zw,b.zw),a.w+(1.-a.w)*b.w);c.xyz+=b.xyz*(1.-a.w)+a.xyz"
+"*(1.-b.w);return c;}$pure half4 blend_overlay(half c,half4 d,half4 e){return"
+" blend_overlay(bool(c)?e:d,bool(c)?d:e);}$pure half4 blend_lighten(half4 a,"
+"half4 b){half4 c=blend_src_over(a,b);c.xyz=max(c.xyz,(1.-b.w)*a.xyz+b.xyz);"
+"return c;}$pure half4 blend_darken(half c,half4 d,half4 e){half4 f=blend_src_over"
+"(d,e);half3 g=(1.-e.w)*d.xyz+e.xyz;f.xyz=c*min(f.xyz*c,g*c);return f;}$pure"
+" half4 blend_darken(half4 a,half4 b){return blend_darken(1.,a,b);}const half"
+" $kGuardedDivideEpsilon=half(sk_Caps.mustGuardDivisionEvenAfterExplicitZeroCheck"
+"?1e-08:0.);$pure inline half $c(half a,half b){return a/(b+$kGuardedDivideEpsilon"
+");}$pure inline half3 $c(half3 a,half b){return a/(b+$kGuardedDivideEpsilon"
+");}$pure half $d(half2 a,half2 b){if(b.x==0.){return a.x*(1.-b.y);}else{half"
+" c=a.y-a.x;if(c==0.){return(a.y*b.y+a.x*(1.-b.y))+b.x*(1.-a.y);}else{c=min("
+"b.y,$c(b.x*a.y,c));return(c*a.y+a.x*(1.-b.y))+b.x*(1.-a.y);}}}$pure half4 blend_color_dodge"
+"(half4 a,half4 b){return half4($d(a.xw,b.xw),$d(a.yw,b.yw),$d(a.zw,b.zw),a."
+"w+(1.-a.w)*b.w);}$pure half $e(half2 a,half2 b){if(b.y==b.x){return(a.y*b.y"
+"+a.x*(1.-b.y))+b.x*(1.-a.y);}else if(a.x==0.){return b.x*(1.-a.y);}else{half"
+" c=max(0.,b.y-$c((b.y-b.x)*a.y,a.x));return(c*a.y+a.x*(1.-b.y))+b.x*(1.-a.y"
+");}}$pure half4 blend_color_burn(half4 a,half4 b){return half4($e(a.xw,b.xw"
+"),$e(a.yw,b.yw),$e(a.zw,b.zw),a.w+(1.-a.w)*b.w);}$pure half4 blend_hard_light"
+"(half4 a,half4 b){return blend_overlay(b,a);}$pure half $f(half2 a,half2 b)"
+"{if(2.*a.x<=a.y){return($c((b.x*b.x)*(a.y-2.*a.x),b.y)+(1.-b.y)*a.x)+b.x*(("
+"-a.y+2.*a.x)+1.);}else if(4.*b.x<=b.y){half c=b.x*b.x;half e=c*b.x;half f=b"
+".y*b.y;half g=f*b.y;return $c(((f*(a.x-b.x*((3.*a.y-6.*a.x)-1.))+((12.*b.y)"
+"*c)*(a.y-2.*a.x))-(16.*e)*(a.y-2.*a.x))-g*a.x,f);}else{return((b.x*((a.y-2."
+"*a.x)+1.)+a.x)-sqrt(b.y*b.x)*(a.y-2.*a.x))-b.y*a.x;}}$pure half4 blend_soft_light"
+"(half4 a,half4 b){return b.w==0.?a:half4($f(a.xw,b.xw),$f(a.yw,b.yw),$f(a.zw"
+",b.zw),a.w+(1.-a.w)*b.w);}$pure half4 blend_difference(half4 a,half4 b){return"
+" half4((a.xyz+b.xyz)-2.*min(a.xyz*b.w,b.xyz*a.w),a.w+(1.-a.w)*b.w);}$pure half4"
+" blend_exclusion(half4 a,half4 b){return half4((b.xyz+a.xyz)-(2.*b.xyz)*a.xyz"
+",a.w+(1.-a.w)*b.w);}$pure half4 blend_multiply(half4 a,half4 b){return half4"
+"(((1.-a.w)*b.xyz+(1.-b.w)*a.xyz)+a.xyz*b.xyz,a.w+(1.-a.w)*b.w);}$pure half $g"
+"(half3 a){return dot(half3(.3,.59,.11),a);}$pure half3 $h(half3 a,half b,half3"
+" c){half d=$g(c);half3 e=(d-$g(a))+a;half f=min(min(e.x,e.y),e.z);half g=max"
+"(max(e.x,e.y),e.z);if(f<0.&&d!=f){e=d+(e-d)*$c(d,d-f);}if(g>b&&g!=d){e=d+$c"
+"((e-d)*(b-d),g-d);}return e;}$pure half $i(half3 a){return max(max(a.x,a.y)"
+",a.z)-min(min(a.x,a.y),a.z);}$pure half3 $j(half3 a,half3 b){half c=min(min"
+"(a.x,a.y),a.z);half d=max(max(a.x,a.y),a.z);return d>c?((a-c)*$i(b))/(d-c):"
+"half3(0.);}$pure half4 blend_hslc(half2 a,half4 b,half4 c){half d=c.w*b.w;half3"
+" e=b.xyz*c.w;half3 f=c.xyz*b.w;half3 g=bool(a.x)?f:e;half3 h=bool(a.x)?e:f;"
+"if(bool(a.y)){g=$j(g,h);h=f;}return half4(((($h(g,d,h)+c.xyz)-f)+b.xyz)-e,("
+"b.w+c.w)-d);}$pure half4 blend_hue(half4 a,half4 b){return blend_hslc(half2"
+"(0.,1.),a,b);}$pure half4 blend_saturation(half4 a,half4 b){return blend_hslc"
+"(half2(1.),a,b);}$pure half4 blend_color(half4 a,half4 b){return blend_hslc"
+"(half2(0.),a,b);}$pure half4 blend_luminosity(half4 a,half4 b){return blend_hslc"
+"(half2(1.,0.),a,b);}$pure float2 proj(float3 a){return a.xy/a.z;}$pure float"
+" cross_length_2d(float2 c,float2 d){return determinant(float2x2(c,d));}$pure"
+" half cross_length_2d(half2 c,half2 d){return determinant(half2x2(c,d));}$pure"
+" float2 perp(float2 a){return float2(-a.y,a.x);}$pure half2 perp(half2 a){return"
+" half2(-a.y,a.x);}$pure float coverage_bias(float a){return 1.-.5*a;}";
diff --git a/gfx/skia/skia/src/sksl/generated/sksl_gpu.unoptimized.sksl b/gfx/skia/skia/src/sksl/generated/sksl_gpu.unoptimized.sksl
new file mode 100644
index 0000000000..0b09c7d461
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/generated/sksl_gpu.unoptimized.sksl
@@ -0,0 +1,107 @@
+static constexpr char SKSL_MINIFIED_sksl_gpu[] =
+"$pure $genIType mix($genIType x,$genIType y,$genBType a);$pure $genBType mix"
+"($genBType x,$genBType y,$genBType a);$pure $genType fma($genType a,$genType"
+" b,$genType c);$pure $genHType fma($genHType a,$genHType b,$genHType c);$genType"
+" frexp($genType x,out $genIType exp);$genHType frexp($genHType x,out $genIType"
+" exp);$pure $genType ldexp($genType x,$genIType exp);$pure $genHType ldexp("
+"$genHType x,$genIType exp);$pure uint packSnorm2x16(float2 v);$pure uint packUnorm4x8"
+"(float4 v);$pure uint packSnorm4x8(float4 v);$pure float2 unpackSnorm2x16(uint"
+" p);$pure float4 unpackUnorm4x8(uint p);$pure float4 unpackSnorm4x8(uint p)"
+";$pure uint packHalf2x16(float2 v);$pure float2 unpackHalf2x16(uint v);$pure"
+" $genIType bitCount($genIType value);$pure $genIType bitCount($genUType value"
+");$pure $genIType findLSB($genIType value);$pure $genIType findLSB($genUType"
+" value);$pure $genIType findMSB($genIType value);$pure $genIType findMSB($genUType"
+" value);$pure sampler2D makeSampler2D(texture2D texture,sampler s);$pure half4"
+" sample(sampler2D s,float2 P);$pure half4 sample(sampler2D s,float3 P);$pure"
+" half4 sample(sampler2D s,float3 P,float bias);$pure half4 sample(samplerExternalOES"
+" s,float2 P);$pure half4 sample(samplerExternalOES s,float2 P,float bias);$pure"
+" half4 sample(sampler2DRect s,float2 P);$pure half4 sample(sampler2DRect s,"
+"float3 P);$pure half4 sampleLod(sampler2D s,float2 P,float lod);$pure half4"
+" sampleLod(sampler2D s,float3 P,float lod);$pure half4 sampleGrad(sampler2D"
+" s,float2,float2 dPdx,float2 dPdy);$pure half4 subpassLoad(subpassInput subpass"
+");$pure half4 subpassLoad(subpassInputMS subpass,int sample);$pure uint atomicLoad"
+"(atomicUint a);void atomicStore(atomicUint a,uint value);uint atomicAdd(atomicUint"
+" a,uint value);$pure half4 blend_clear(half4 src,half4 dst){return half4(0."
+");}$pure half4 blend_src(half4 src,half4 dst){return src;}$pure half4 blend_dst"
+"(half4 src,half4 dst){return dst;}$pure half4 blend_src_over(half4 src,half4"
+" dst){return src+(1.-src.w)*dst;}$pure half4 blend_dst_over(half4 src,half4"
+" dst){return(1.-dst.w)*src+dst;}$pure half4 blend_src_in(half4 src,half4 dst"
+"){return src*dst.w;}$pure half4 blend_dst_in(half4 src,half4 dst){return dst"
+"*src.w;}$pure half4 blend_src_out(half4 src,half4 dst){return(1.-dst.w)*src"
+";}$pure half4 blend_dst_out(half4 src,half4 dst){return(1.-src.w)*dst;}$pure"
+" half4 blend_src_atop(half4 src,half4 dst){return dst.w*src+(1.-src.w)*dst;"
+"}$pure half4 blend_dst_atop(half4 src,half4 dst){return(1.-dst.w)*src+src.w"
+"*dst;}$pure half4 blend_xor(half4 src,half4 dst){return(1.-dst.w)*src+(1.-src"
+".w)*dst;}$pure half4 blend_plus(half4 src,half4 dst){return min(src+dst,1.)"
+";}$pure half4 blend_porter_duff(half4 blendOp,half4 src,half4 dst){half2 coeff"
+"=blendOp.xy+blendOp.zw*(half2(dst.w,src.w)+min(blendOp.zw,0.));return min(half4"
+"(1.),src*coeff.x+dst*coeff.y);}$pure half4 blend_modulate(half4 src,half4 dst"
+"){return src*dst;}$pure half4 blend_screen(half4 src,half4 dst){return src+"
+"(1.-src)*dst;}$pure half $blend_overlay_component(half2 s,half2 d){return 2."
+"*d.x<=d.y?(2.*s.x)*d.x:s.y*d.y-(2.*(d.y-d.x))*(s.y-s.x);}$pure half4 blend_overlay"
+"(half4 src,half4 dst){half4 result=half4($blend_overlay_component(src.xw,dst"
+".xw),$blend_overlay_component(src.yw,dst.yw),$blend_overlay_component(src.zw"
+",dst.zw),src.w+(1.-src.w)*dst.w);result.xyz+=dst.xyz*(1.-src.w)+src.xyz*(1."
+"-dst.w);return result;}$pure half4 blend_overlay(half flip,half4 a,half4 b)"
+"{return blend_overlay(bool(flip)?b:a,bool(flip)?a:b);}$pure half4 blend_lighten"
+"(half4 src,half4 dst){half4 result=blend_src_over(src,dst);result.xyz=max(result"
+".xyz,(1.-dst.w)*src.xyz+dst.xyz);return result;}$pure half4 blend_darken(half"
+" mode,half4 src,half4 dst){half4 a=blend_src_over(src,dst);half3 b=(1.-dst."
+"w)*src.xyz+dst.xyz;a.xyz=mode*min(a.xyz*mode,b*mode);return a;}$pure half4 blend_darken"
+"(half4 src,half4 dst){return blend_darken(1.,src,dst);}const half $kGuardedDivideEpsilon"
+"=half(sk_Caps.mustGuardDivisionEvenAfterExplicitZeroCheck?1e-08:0.);$pure inline"
+" half $guarded_divide(half n,half d){return n/(d+$kGuardedDivideEpsilon);}$pure"
+" inline half3 $guarded_divide(half3 n,half d){return n/(d+$kGuardedDivideEpsilon"
+");}$pure half $color_dodge_component(half2 s,half2 d){if(d.x==0.){return s."
+"x*(1.-d.y);}else{half delta=s.y-s.x;if(delta==0.){return(s.y*d.y+s.x*(1.-d."
+"y))+d.x*(1.-s.y);}else{delta=min(d.y,$guarded_divide(d.x*s.y,delta));return"
+"(delta*s.y+s.x*(1.-d.y))+d.x*(1.-s.y);}}}$pure half4 blend_color_dodge(half4"
+" src,half4 dst){return half4($color_dodge_component(src.xw,dst.xw),$color_dodge_component"
+"(src.yw,dst.yw),$color_dodge_component(src.zw,dst.zw),src.w+(1.-src.w)*dst."
+"w);}$pure half $color_burn_component(half2 s,half2 d){if(d.y==d.x){return(s"
+".y*d.y+s.x*(1.-d.y))+d.x*(1.-s.y);}else if(s.x==0.){return d.x*(1.-s.y);}else"
+"{half delta=max(0.,d.y-$guarded_divide((d.y-d.x)*s.y,s.x));return(delta*s.y"
+"+s.x*(1.-d.y))+d.x*(1.-s.y);}}$pure half4 blend_color_burn(half4 src,half4 dst"
+"){return half4($color_burn_component(src.xw,dst.xw),$color_burn_component(src"
+".yw,dst.yw),$color_burn_component(src.zw,dst.zw),src.w+(1.-src.w)*dst.w);}$pure"
+" half4 blend_hard_light(half4 src,half4 dst){return blend_overlay(dst,src);"
+"}$pure half $soft_light_component(half2 s,half2 d){if(2.*s.x<=s.y){return($guarded_divide"
+"((d.x*d.x)*(s.y-2.*s.x),d.y)+(1.-d.y)*s.x)+d.x*((-s.y+2.*s.x)+1.);}else if("
+"4.*d.x<=d.y){half DSqd=d.x*d.x;half DCub=DSqd*d.x;half DaSqd=d.y*d.y;half DaCub"
+"=DaSqd*d.y;return $guarded_divide(((DaSqd*(s.x-d.x*((3.*s.y-6.*s.x)-1.))+(("
+"12.*d.y)*DSqd)*(s.y-2.*s.x))-(16.*DCub)*(s.y-2.*s.x))-DaCub*s.x,DaSqd);}else"
+"{return((d.x*((s.y-2.*s.x)+1.)+s.x)-sqrt(d.y*d.x)*(s.y-2.*s.x))-d.y*s.x;}}$pure"
+" half4 blend_soft_light(half4 src,half4 dst){return dst.w==0.?src:half4($soft_light_component"
+"(src.xw,dst.xw),$soft_light_component(src.yw,dst.yw),$soft_light_component("
+"src.zw,dst.zw),src.w+(1.-src.w)*dst.w);}$pure half4 blend_difference(half4 src"
+",half4 dst){return half4((src.xyz+dst.xyz)-2.*min(src.xyz*dst.w,dst.xyz*src"
+".w),src.w+(1.-src.w)*dst.w);}$pure half4 blend_exclusion(half4 src,half4 dst"
+"){return half4((dst.xyz+src.xyz)-(2.*dst.xyz)*src.xyz,src.w+(1.-src.w)*dst."
+"w);}$pure half4 blend_multiply(half4 src,half4 dst){return half4(((1.-src.w"
+")*dst.xyz+(1.-dst.w)*src.xyz)+src.xyz*dst.xyz,src.w+(1.-src.w)*dst.w);}$pure"
+" half $blend_color_luminance(half3 color){return dot(half3(.3,.59,.11),color"
+");}$pure half3 $blend_set_color_luminance(half3 hueSatColor,half alpha,half3"
+" lumColor){half lum=$blend_color_luminance(lumColor);half3 result=(lum-$blend_color_luminance"
+"(hueSatColor))+hueSatColor;half minComp=min(min(result.x,result.y),result.z"
+");half maxComp=max(max(result.x,result.y),result.z);if(minComp<0.&&lum!=minComp"
+"){result=lum+(result-lum)*$guarded_divide(lum,lum-minComp);}if(maxComp>alpha"
+"&&maxComp!=lum){result=lum+$guarded_divide((result-lum)*(alpha-lum),maxComp"
+"-lum);}return result;}$pure half $blend_color_saturation(half3 color){return"
+" max(max(color.x,color.y),color.z)-min(min(color.x,color.y),color.z);}$pure"
+" half3 $blend_set_color_saturation(half3 color,half3 satColor){half mn=min("
+"min(color.x,color.y),color.z);half mx=max(max(color.x,color.y),color.z);return"
+" mx>mn?((color-mn)*$blend_color_saturation(satColor))/(mx-mn):half3(0.);}$pure"
+" half4 blend_hslc(half2 flipSat,half4 src,half4 dst){half alpha=dst.w*src.w"
+";half3 sda=src.xyz*dst.w;half3 dsa=dst.xyz*src.w;half3 l=bool(flipSat.x)?dsa"
+":sda;half3 r=bool(flipSat.x)?sda:dsa;if(bool(flipSat.y)){l=$blend_set_color_saturation"
+"(l,r);r=dsa;}return half4(((($blend_set_color_luminance(l,alpha,r)+dst.xyz)"
+"-dsa)+src.xyz)-sda,(src.w+dst.w)-alpha);}$pure half4 blend_hue(half4 src,half4"
+" dst){return blend_hslc(half2(0.,1.),src,dst);}$pure half4 blend_saturation"
+"(half4 src,half4 dst){return blend_hslc(half2(1.),src,dst);}$pure half4 blend_color"
+"(half4 src,half4 dst){return blend_hslc(half2(0.),src,dst);}$pure half4 blend_luminosity"
+"(half4 src,half4 dst){return blend_hslc(half2(1.,0.),src,dst);}$pure float2"
+" proj(float3 p){return p.xy/p.z;}$pure float cross_length_2d(float2 a,float2"
+" b){return determinant(float2x2(a,b));}$pure half cross_length_2d(half2 a,half2"
+" b){return determinant(half2x2(a,b));}$pure float2 perp(float2 v){return float2"
+"(-v.y,v.x);}$pure half2 perp(half2 v){return half2(-v.y,v.x);}$pure float coverage_bias"
+"(float scale){return 1.-.5*scale;}";
diff --git a/gfx/skia/skia/src/sksl/generated/sksl_graphite_frag.dehydrated.sksl b/gfx/skia/skia/src/sksl/generated/sksl_graphite_frag.dehydrated.sksl
new file mode 100644
index 0000000000..93d4e47b8a
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/generated/sksl_graphite_frag.dehydrated.sksl
@@ -0,0 +1,3119 @@
+static constexpr uint8_t SKSL_INCLUDE_sksl_graphite_frag[] = {14,0,27,6,
+8,115,107,95,101,114,114,111,114,
+5,104,97,108,102,52,
+5,99,111,108,111,114,
+14,115,107,95,112,97,115,115,116,104,114,111,117,103,104,
+10,99,111,108,111,114,80,97,114,97,109,
+6,102,108,111,97,116,52,
+15,115,107,95,115,111,108,105,100,95,115,104,97,100,101,114,
+2,116,109,
+3,105,110,116,
+1,102,
+5,102,108,111,97,116,
+3,108,111,119,
+4,104,105,103,104,
+5,36,116,105,108,101,
+6,99,111,111,114,100,115,
+6,102,108,111,97,116,50,
+6,115,117,98,115,101,116,
+3,116,109,88,
+3,116,109,89,
+8,105,109,103,87,105,100,116,104,
+9,105,109,103,72,101,105,103,104,116,
+1,115,
+9,115,97,109,112,108,101,114,50,68,
+15,115,107,95,105,109,97,103,101,95,115,104,97,100,101,114,
+8,116,105,108,101,77,111,100,101,
+1,116,
+10,36,116,105,108,101,95,103,114,97,100,
+11,99,111,108,111,114,115,80,97,114,97,109,
+12,111,102,102,115,101,116,115,80,97,114,97,109,
+16,36,99,111,108,111,114,105,122,101,95,103,114,97,100,95,52,
+16,36,99,111,108,111,114,105,122,101,95,103,114,97,100,95,56,
+11,112,111,105,110,116,48,80,97,114,97,109,
+11,112,111,105,110,116,49,80,97,114,97,109,
+3,112,111,115,
+19,36,108,105,110,101,97,114,95,103,114,97,100,95,108,97,121,111,117,116,
+11,99,101,110,116,101,114,80,97,114,97,109,
+11,114,97,100,105,117,115,80,97,114,97,109,
+19,36,114,97,100,105,97,108,95,103,114,97,100,95,108,97,121,111,117,116,
+9,98,105,97,115,80,97,114,97,109,
+10,115,99,97,108,101,80,97,114,97,109,
+18,36,115,119,101,101,112,95,103,114,97,100,95,108,97,121,111,117,116,
+2,112,48,
+2,112,49,
+14,36,109,97,112,95,116,111,95,117,110,105,116,95,120,
+8,102,108,111,97,116,51,120,51,
+12,114,97,100,105,117,115,48,80,97,114,97,109,
+12,114,97,100,105,117,115,49,80,97,114,97,109,
+20,36,99,111,110,105,99,97,108,95,103,114,97,100,95,108,97,121,111,117,116,
+23,115,107,95,108,105,110,101,97,114,95,103,114,97,100,95,52,95,115,104,97,100,101,114,
+23,115,107,95,108,105,110,101,97,114,95,103,114,97,100,95,56,95,115,104,97,100,101,114,
+23,115,107,95,114,97,100,105,97,108,95,103,114,97,100,95,52,95,115,104,97,100,101,114,
+23,115,107,95,114,97,100,105,97,108,95,103,114,97,100,95,56,95,115,104,97,100,101,114,
+22,115,107,95,115,119,101,101,112,95,103,114,97,100,95,52,95,115,104,97,100,101,114,
+22,115,107,95,115,119,101,101,112,95,103,114,97,100,95,56,95,115,104,97,100,101,114,
+24,115,107,95,99,111,110,105,99,97,108,95,103,114,97,100,95,52,95,115,104,97,100,101,114,
+24,115,107,95,99,111,110,105,99,97,108,95,103,114,97,100,95,56,95,115,104,97,100,101,114,
+7,99,111,108,111,114,73,110,
+1,109,
+8,102,108,111,97,116,52,120,52,
+1,118,
+6,105,110,72,83,76,65,
+21,115,107,95,109,97,116,114,105,120,95,99,111,108,111,114,102,105,108,116,101,114,
+9,98,108,101,110,100,77,111,100,101,
+3,115,114,99,
+3,100,115,116,
+8,115,107,95,98,108,101,110,100,
+15,115,107,95,98,108,101,110,100,95,115,104,97,100,101,114,
+8,100,115,116,67,111,108,111,114,
+8,115,114,99,67,111,108,111,114,
+20,115,107,95,98,108,101,110,100,95,99,111,108,111,114,102,105,108,116,101,114,
+7,105,110,67,111,108,111,114,
+20,115,107,95,116,97,98,108,101,95,99,111,108,111,114,102,105,108,116,101,114,
+23,115,107,95,103,97,117,115,115,105,97,110,95,99,111,108,111,114,102,105,108,116,101,114,
+4,104,97,108,102,
+6,107,67,108,97,109,112,
+7,107,82,101,112,101,97,116,
+13,107,77,105,114,114,111,114,82,101,112,101,97,116,
+5,99,108,97,109,112,
+6,108,101,110,103,116,104,
+3,109,111,100,
+7,108,101,110,103,116,104,50,
+3,116,109,112,
+3,109,105,120,
+4,115,116,101,112,
+10,105,110,115,101,116,67,108,97,109,112,
+10,99,108,97,109,112,101,100,80,111,115,
+5,102,108,111,111,114,
+4,99,101,105,108,
+6,115,97,109,112,108,101,
+5,102,114,97,99,116,
+3,116,95,49,
+26,109,117,115,116,68,111,79,112,66,101,116,119,101,101,110,70,108,111,111,114,65,110,100,65,98,115,
+3,97,98,115,
+11,36,105,110,116,76,105,116,101,114,97,108,
+5,100,101,108,116,97,
+3,100,111,116,
+8,100,105,115,116,97,110,99,101,
+5,97,110,103,108,101,
+28,97,116,97,110,50,73,109,112,108,101,109,101,110,116,101,100,65,115,65,116,97,110,89,79,118,101,114,88,
+4,97,116,97,110,
+7,105,110,118,101,114,115,101,
+19,83,75,95,83,99,97,108,97,114,78,101,97,114,108,121,90,101,114,111,
+7,100,67,101,110,116,101,114,
+7,100,82,97,100,105,117,115,
+6,114,97,100,105,97,108,
+4,98,111,111,108,
+5,115,116,114,105,112,
+5,115,99,97,108,101,
+9,115,99,97,108,101,83,105,103,110,
+4,98,105,97,115,
+2,112,116,
+4,115,105,103,110,
+9,116,114,97,110,115,102,111,114,109,
+1,114,
+3,114,95,50,
+6,102,108,111,97,116,51,
+4,115,113,114,116,
+9,105,115,83,119,97,112,112,101,100,
+2,67,102,
+6,115,99,97,108,101,88,
+6,115,99,97,108,101,89,
+2,114,49,
+15,105,115,70,111,99,97,108,79,110,67,105,114,99,108,101,
+5,105,110,118,82,49,
+11,100,82,97,100,105,117,115,83,105,103,110,
+13,105,115,87,101,108,108,66,101,104,97,118,101,100,
+3,120,95,116,
+5,116,109,112,80,116,
+4,116,101,109,112,
+8,99,111,108,111,114,79,117,116,
+11,36,114,103,98,95,116,111,95,104,115,108,
+8,117,110,112,114,101,109,117,108,
+11,36,104,115,108,95,116,111,95,114,103,98,
+8,115,97,116,117,114,97,116,101,
+11,98,108,101,110,100,95,99,108,101,97,114,
+9,98,108,101,110,100,95,115,114,99,
+9,98,108,101,110,100,95,100,115,116,
+17,98,108,101,110,100,95,112,111,114,116,101,114,95,100,117,102,102,
+14,98,108,101,110,100,95,109,111,100,117,108,97,116,101,
+12,98,108,101,110,100,95,115,99,114,101,101,110,
+13,98,108,101,110,100,95,111,118,101,114,108,97,121,
+12,98,108,101,110,100,95,100,97,114,107,101,110,
+17,98,108,101,110,100,95,99,111,108,111,114,95,100,111,100,103,101,
+16,98,108,101,110,100,95,99,111,108,111,114,95,98,117,114,110,
+16,98,108,101,110,100,95,115,111,102,116,95,108,105,103,104,116,
+16,98,108,101,110,100,95,100,105,102,102,101,114,101,110,99,101,
+15,98,108,101,110,100,95,101,120,99,108,117,115,105,111,110,
+14,98,108,101,110,100,95,109,117,108,116,105,112,108,121,
+10,98,108,101,110,100,95,104,115,108,99,
+5,104,97,108,102,50,
+6,102,97,99,116,111,114,
+3,101,120,112,
+52,1,139,0,
+28,1,0,
+39,
+16,0,64,0,0,2,0,0,
+51,255,255,11,0,
+54,2,0,
+17,17,0,
+51,255,255,11,0,3,
+28,3,0,
+39,
+16,0,64,0,0,23,0,1,
+51,2,0,
+51,255,255,11,0,
+54,4,0,
+17,38,0,
+51,255,255,49,0,3,
+28,5,0,
+39,
+16,0,64,0,0,56,0,1,
+51,4,0,
+51,255,255,11,0,
+54,6,0,
+17,72,0,
+51,255,255,75,0,3,
+54,7,0,
+17,79,0,
+51,255,255,81,0,3,
+54,8,0,
+17,87,0,
+51,255,255,81,0,3,
+54,9,0,
+17,91,0,
+51,255,255,81,0,3,
+28,10,0,
+39,
+16,0,64,0,0,96,0,4,
+51,6,0,
+51,7,0,
+51,8,0,
+51,9,0,
+51,255,255,81,0,
+54,11,0,
+17,102,0,
+51,255,255,109,0,3,
+54,12,0,
+17,116,0,
+51,255,255,49,0,3,
+54,13,0,
+17,123,0,
+51,255,255,75,0,3,
+54,14,0,
+17,127,0,
+51,255,255,75,0,3,
+54,15,0,
+17,131,0,
+51,255,255,75,0,3,
+54,16,0,
+17,140,0,
+51,255,255,75,0,3,
+54,17,0,
+17,150,0,
+51,255,255,152,0,3,
+28,18,0,
+39,
+16,0,64,0,0,162,0,7,
+51,11,0,
+51,12,0,
+51,13,0,
+51,14,0,
+51,15,0,
+51,16,0,
+51,17,0,
+51,255,255,11,0,
+54,19,0,
+17,178,0,
+51,255,255,75,0,3,
+54,20,0,
+17,187,0,
+51,255,255,109,0,3,
+28,21,0,
+39,
+16,0,64,0,0,189,0,2,
+51,19,0,
+51,20,0,
+51,255,255,109,0,
+0,22,0,
+51,255,255,49,0,4,
+0,23,0,
+51,255,255,81,0,4,
+54,24,0,
+17,200,0,
+51,22,0,3,
+54,25,0,
+17,212,0,
+51,23,0,3,
+54,26,0,
+17,187,0,
+51,255,255,109,0,3,
+28,27,0,
+39,
+16,0,64,0,0,225,0,3,
+51,24,0,
+51,25,0,
+51,26,0,
+51,255,255,11,0,
+0,28,0,
+51,255,255,49,0,8,
+0,29,0,
+51,255,255,81,0,8,
+54,30,0,
+17,200,0,
+51,28,0,3,
+54,31,0,
+17,212,0,
+51,29,0,3,
+54,32,0,
+17,187,0,
+51,255,255,109,0,3,
+28,33,0,
+39,
+16,0,64,0,0,242,0,3,
+51,30,0,
+51,31,0,
+51,32,0,
+51,255,255,11,0,
+54,34,0,
+17,3,1,
+51,255,255,109,0,3,
+54,35,0,
+17,15,1,
+51,255,255,109,0,3,
+54,36,0,
+17,27,1,
+51,255,255,109,0,3,
+28,37,0,
+39,
+16,0,64,0,0,31,1,3,
+51,34,0,
+51,35,0,
+51,36,0,
+51,255,255,109,0,
+54,38,0,
+17,51,1,
+51,255,255,109,0,3,
+54,39,0,
+17,63,1,
+51,255,255,81,0,3,
+54,40,0,
+17,27,1,
+51,255,255,109,0,3,
+28,41,0,
+39,
+16,0,64,0,0,75,1,3,
+51,38,0,
+51,39,0,
+51,40,0,
+51,255,255,109,0,
+54,42,0,
+17,51,1,
+51,255,255,109,0,3,
+54,43,0,
+17,95,1,
+51,255,255,81,0,3,
+54,44,0,
+17,105,1,
+51,255,255,81,0,3,
+54,45,0,
+17,27,1,
+51,255,255,109,0,3,
+28,46,0,
+39,
+16,0,64,0,0,116,1,4,
+51,42,0,
+51,43,0,
+51,44,0,
+51,45,0,
+51,255,255,109,0,
+54,47,0,
+17,135,1,
+51,255,255,109,0,3,
+54,48,0,
+17,138,1,
+51,255,255,109,0,3,
+28,49,0,
+39,
+16,0,64,0,0,141,1,2,
+51,47,0,
+51,48,0,
+51,255,255,156,1,
+54,50,0,
+17,3,1,
+51,255,255,109,0,3,
+54,51,0,
+17,15,1,
+51,255,255,109,0,3,
+54,52,0,
+17,165,1,
+51,255,255,81,0,3,
+54,53,0,
+17,178,1,
+51,255,255,81,0,3,
+54,54,0,
+17,27,1,
+51,255,255,109,0,3,
+28,55,0,
+39,
+16,0,64,0,0,191,1,5,
+51,50,0,
+51,51,0,
+51,52,0,
+51,53,0,
+51,54,0,
+51,255,255,109,0,
+54,56,0,
+17,102,0,
+51,255,255,109,0,3,
+54,57,0,
+17,200,0,
+51,22,0,3,
+54,58,0,
+17,212,0,
+51,23,0,3,
+54,59,0,
+17,3,1,
+51,255,255,109,0,3,
+54,60,0,
+17,15,1,
+51,255,255,109,0,3,
+54,61,0,
+17,178,0,
+51,255,255,75,0,3,
+28,62,0,
+39,
+16,0,64,0,0,212,1,6,
+51,56,0,
+51,57,0,
+51,58,0,
+51,59,0,
+51,60,0,
+51,61,0,
+51,255,255,11,0,
+54,63,0,
+17,102,0,
+51,255,255,109,0,3,
+54,64,0,
+17,200,0,
+51,28,0,3,
+54,65,0,
+17,212,0,
+51,29,0,3,
+54,66,0,
+17,3,1,
+51,255,255,109,0,3,
+54,67,0,
+17,15,1,
+51,255,255,109,0,3,
+54,68,0,
+17,178,0,
+51,255,255,75,0,3,
+28,69,0,
+39,
+16,0,64,0,0,236,1,6,
+51,63,0,
+51,64,0,
+51,65,0,
+51,66,0,
+51,67,0,
+51,68,0,
+51,255,255,11,0,
+54,70,0,
+17,102,0,
+51,255,255,109,0,3,
+54,71,0,
+17,200,0,
+51,22,0,3,
+54,72,0,
+17,212,0,
+51,23,0,3,
+54,73,0,
+17,51,1,
+51,255,255,109,0,3,
+54,74,0,
+17,63,1,
+51,255,255,81,0,3,
+54,75,0,
+17,178,0,
+51,255,255,75,0,3,
+28,76,0,
+39,
+16,0,64,0,0,4,2,6,
+51,70,0,
+51,71,0,
+51,72,0,
+51,73,0,
+51,74,0,
+51,75,0,
+51,255,255,11,0,
+54,77,0,
+17,102,0,
+51,255,255,109,0,3,
+54,78,0,
+17,200,0,
+51,28,0,3,
+54,79,0,
+17,212,0,
+51,29,0,3,
+54,80,0,
+17,51,1,
+51,255,255,109,0,3,
+54,81,0,
+17,63,1,
+51,255,255,81,0,3,
+54,82,0,
+17,178,0,
+51,255,255,75,0,3,
+28,83,0,
+39,
+16,0,64,0,0,28,2,6,
+51,77,0,
+51,78,0,
+51,79,0,
+51,80,0,
+51,81,0,
+51,82,0,
+51,255,255,11,0,
+54,84,0,
+17,102,0,
+51,255,255,109,0,3,
+54,85,0,
+17,200,0,
+51,22,0,3,
+54,86,0,
+17,212,0,
+51,23,0,3,
+54,87,0,
+17,51,1,
+51,255,255,109,0,3,
+54,88,0,
+17,95,1,
+51,255,255,81,0,3,
+54,89,0,
+17,105,1,
+51,255,255,81,0,3,
+54,90,0,
+17,178,0,
+51,255,255,75,0,3,
+28,91,0,
+39,
+16,0,64,0,0,52,2,7,
+51,84,0,
+51,85,0,
+51,86,0,
+51,87,0,
+51,88,0,
+51,89,0,
+51,90,0,
+51,255,255,11,0,
+54,92,0,
+17,102,0,
+51,255,255,109,0,3,
+54,93,0,
+17,200,0,
+51,28,0,3,
+54,94,0,
+17,212,0,
+51,29,0,3,
+54,95,0,
+17,51,1,
+51,255,255,109,0,3,
+54,96,0,
+17,95,1,
+51,255,255,81,0,3,
+54,97,0,
+17,105,1,
+51,255,255,81,0,3,
+54,98,0,
+17,178,0,
+51,255,255,75,0,3,
+28,99,0,
+39,
+16,0,64,0,0,75,2,7,
+51,92,0,
+51,93,0,
+51,94,0,
+51,95,0,
+51,96,0,
+51,97,0,
+51,98,0,
+51,255,255,11,0,
+54,100,0,
+17,102,0,
+51,255,255,109,0,3,
+54,101,0,
+17,200,0,
+51,22,0,3,
+54,102,0,
+17,212,0,
+51,23,0,3,
+54,103,0,
+17,3,1,
+51,255,255,109,0,3,
+54,104,0,
+17,15,1,
+51,255,255,109,0,3,
+54,105,0,
+17,165,1,
+51,255,255,81,0,3,
+54,106,0,
+17,178,1,
+51,255,255,81,0,3,
+54,107,0,
+17,178,0,
+51,255,255,75,0,3,
+28,108,0,
+39,
+16,0,64,0,0,98,2,8,
+51,100,0,
+51,101,0,
+51,102,0,
+51,103,0,
+51,104,0,
+51,105,0,
+51,106,0,
+51,107,0,
+51,255,255,11,0,
+54,109,0,
+17,102,0,
+51,255,255,109,0,3,
+54,110,0,
+17,200,0,
+51,28,0,3,
+54,111,0,
+17,212,0,
+51,29,0,3,
+54,112,0,
+17,3,1,
+51,255,255,109,0,3,
+54,113,0,
+17,15,1,
+51,255,255,109,0,3,
+54,114,0,
+17,165,1,
+51,255,255,81,0,3,
+54,115,0,
+17,178,1,
+51,255,255,81,0,3,
+54,116,0,
+17,178,0,
+51,255,255,75,0,3,
+28,117,0,
+39,
+16,0,64,0,0,123,2,8,
+51,109,0,
+51,110,0,
+51,111,0,
+51,112,0,
+51,113,0,
+51,114,0,
+51,115,0,
+51,116,0,
+51,255,255,11,0,
+54,118,0,
+17,148,2,
+51,255,255,11,0,3,
+54,119,0,
+17,156,2,
+51,255,255,158,2,3,
+54,120,0,
+17,167,2,
+51,255,255,49,0,3,
+54,121,0,
+17,169,2,
+51,255,255,75,0,3,
+28,122,0,
+39,
+16,0,64,0,0,176,2,4,
+51,118,0,
+51,119,0,
+51,120,0,
+51,121,0,
+51,255,255,11,0,
+54,123,0,
+17,198,2,
+51,255,255,75,0,3,
+54,124,0,
+17,208,2,
+51,255,255,11,0,3,
+54,125,0,
+17,212,2,
+51,255,255,11,0,3,
+28,126,0,
+39,
+16,0,64,0,0,216,2,3,
+51,123,0,
+51,124,0,
+51,125,0,
+51,255,255,11,0,
+54,127,0,
+17,198,2,
+51,255,255,75,0,3,
+54,128,0,
+17,208,2,
+51,255,255,11,0,3,
+54,129,0,
+17,212,2,
+51,255,255,11,0,3,
+28,130,0,
+39,
+16,0,64,0,0,225,2,3,
+51,127,0,
+51,128,0,
+51,129,0,
+51,255,255,11,0,
+54,131,0,
+17,241,2,
+51,255,255,11,0,3,
+54,132,0,
+17,198,2,
+51,255,255,75,0,3,
+54,133,0,
+17,250,2,
+51,255,255,49,0,3,
+28,134,0,
+39,
+16,0,64,0,0,3,3,3,
+51,131,0,
+51,132,0,
+51,133,0,
+51,255,255,11,0,
+54,135,0,
+17,24,3,
+51,255,255,11,0,3,
+54,136,0,
+17,150,0,
+51,255,255,152,0,3,
+28,137,0,
+39,
+16,0,64,0,0,32,3,2,
+51,135,0,
+51,136,0,
+51,255,255,11,0,
+54,138,0,
+17,24,3,
+51,255,255,11,0,3,
+28,139,0,
+39,
+16,0,64,0,0,53,3,1,
+51,138,0,
+51,255,255,11,0,31,0,
+26,0,
+32,0,
+54,0,
+36,0,
+48,0,
+40,0,
+45,0,
+9,0,
+20,0,
+21,0,
+27,0,
+22,0,
+28,0,
+125,0,
+133,0,
+129,0,
+107,0,
+116,0,
+0,0,
+138,0,
+17,0,
+61,0,
+68,0,
+121,0,
+2,0,
+75,0,
+82,0,
+4,0,
+90,0,
+98,0,
+136,0,
+20,
+29,1,0,
+2,
+52,1,0,0,0,0,1,
+44,
+8,
+51,255,255,11,0,4,
+25,
+51,255,255,77,3,0,0,128,63,
+25,
+51,255,255,77,3,0,0,0,0,
+25,
+51,255,255,77,3,0,0,128,63,
+25,
+51,255,255,77,3,0,0,128,63,1,
+29,3,0,
+2,
+52,1,0,0,0,0,1,
+44,
+56,2,0,0,1,
+29,5,0,
+2,
+52,1,0,0,0,0,1,
+44,
+9,
+51,255,255,11,0,1,
+56,4,0,0,1,
+29,10,0,
+2,
+52,1,3,0,
+54,140,0,
+38,
+16,4,82,3,
+51,255,255,75,0,2,
+54,141,0,
+38,
+16,4,89,3,
+51,255,255,75,0,2,
+54,142,0,
+38,
+16,4,97,3,
+51,255,255,75,0,2,3,0,
+0,0,
+2,0,
+1,0,4,
+55,140,0,
+51,255,255,75,0,0,
+36,
+51,255,255,75,0,0,0,0,0,
+55,141,0,
+51,255,255,75,0,0,
+36,
+51,255,255,75,0,1,0,0,0,
+55,142,0,
+51,255,255,75,0,0,
+36,
+51,255,255,75,0,2,0,0,0,
+32,0,
+1,
+56,6,0,0,16,
+56,140,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,81,0,255,255,111,3,3,
+56,7,0,0,
+56,8,0,0,
+56,9,0,0,1,
+32,0,
+1,
+56,6,0,0,16,
+56,141,0,0,
+2,
+52,1,1,0,
+54,143,0,
+17,117,3,
+51,255,255,81,0,2,1,0,
+0,0,2,
+55,143,0,
+51,255,255,81,0,0,
+1,
+56,9,0,0,1,
+56,8,0,0,
+44,
+1,
+27,
+51,255,255,81,0,255,255,124,3,2,
+1,
+56,7,0,0,1,
+56,8,0,0,
+56,143,0,0,0,
+56,8,0,0,1,
+32,0,
+1,
+56,6,0,0,16,
+56,142,0,0,
+2,
+52,1,3,0,
+54,144,0,
+17,117,3,
+51,255,255,81,0,2,
+54,145,0,
+17,128,3,
+51,255,255,81,0,2,
+54,146,0,
+17,136,3,
+51,255,255,81,0,2,3,0,
+0,0,
+1,0,
+2,0,4,
+55,144,0,
+51,255,255,81,0,0,
+1,
+56,9,0,0,1,
+56,8,0,0,
+55,145,0,
+51,255,255,81,0,0,
+1,
+25,
+51,255,255,81,0,0,0,0,64,2,
+56,144,0,0,
+55,146,0,
+51,255,255,81,0,0,
+27,
+51,255,255,81,0,255,255,124,3,2,
+1,
+56,7,0,0,1,
+56,8,0,0,
+56,145,0,0,
+44,
+1,
+27,
+51,255,255,81,0,255,255,140,3,3,
+56,146,0,0,
+1,
+56,145,0,0,1,
+56,146,0,0,
+27,
+51,255,255,81,0,255,255,144,3,2,
+56,144,0,0,
+56,146,0,0,0,
+56,8,0,0,1,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,81,0,255,255,111,3,3,
+56,7,0,0,
+56,8,0,0,
+56,9,0,0,1,1,
+29,18,0,
+2,
+52,1,2,0,
+54,147,0,
+17,149,3,
+51,255,255,49,0,2,
+54,148,0,
+17,160,3,
+51,255,255,109,0,2,2,0,
+1,0,
+0,0,5,
+22,
+1,
+50,
+56,11,0,1,1,0,15,
+27,
+51,255,255,81,0,10,0,4,
+56,13,0,0,
+50,
+56,11,0,0,1,0,
+50,
+56,12,0,0,1,0,
+50,
+56,12,0,0,1,2,
+22,
+1,
+50,
+56,11,0,1,1,1,15,
+27,
+51,255,255,81,0,10,0,4,
+56,14,0,0,
+50,
+56,11,0,0,1,1,
+50,
+56,12,0,0,1,1,
+50,
+56,12,0,0,1,3,
+55,147,0,
+51,255,255,49,0,0,
+8,
+51,255,255,49,0,2,
+1,
+27,
+51,255,255,109,0,255,255,171,3,1,
+50,
+56,12,0,0,2,0,1,0,
+25,
+51,255,255,81,0,0,0,0,63,
+1,
+27,
+51,255,255,109,0,255,255,177,3,1,
+50,
+56,12,0,0,2,2,3,1,
+25,
+51,255,255,81,0,0,0,0,63,
+55,148,0,
+51,255,255,109,0,0,
+27,
+51,255,255,109,0,255,255,111,3,3,
+56,11,0,0,
+50,
+56,147,0,0,2,0,1,
+50,
+56,147,0,0,2,2,3,
+44,
+27,
+51,255,255,11,0,255,255,182,3,2,
+56,17,0,0,
+1,
+56,148,0,0,3,
+8,
+51,255,255,109,0,2,
+12,
+51,255,255,81,0,1,
+56,15,0,0,
+12,
+51,255,255,81,0,1,
+56,16,0,0,1,
+29,21,0,
+2,
+52,1,0,0,0,0,2,
+49,0,
+52,1,0,0,0,0,
+56,19,0,0,4,0,0,0,0,0,
+2,
+57,2,
+22,
+1,
+50,
+56,20,0,1,1,0,15,
+27,
+51,255,255,81,0,255,255,111,3,3,
+50,
+56,20,0,0,1,0,
+25,
+51,255,255,81,0,0,0,0,0,
+25,
+51,255,255,81,0,0,0,128,63,
+4,0,0,1,0,0,0,
+2,
+57,2,
+22,
+1,
+50,
+56,20,0,1,1,0,15,
+27,
+51,255,255,81,0,255,255,189,3,1,
+50,
+56,20,0,0,1,0,
+4,0,0,2,0,0,0,
+2,
+52,1,1,0,
+54,149,0,
+17,195,3,
+51,255,255,81,0,2,1,0,
+0,0,5,
+55,149,0,
+51,255,255,81,0,0,
+1,
+50,
+56,20,0,0,1,0,1,
+25,
+51,255,255,81,0,0,0,128,63,
+22,
+1,
+50,
+56,20,0,1,1,0,15,
+1,
+1,
+56,149,0,0,1,
+1,
+25,
+51,255,255,81,0,0,0,0,64,2,
+27,
+51,255,255,81,0,255,255,171,3,1,
+1,
+56,149,0,0,2,
+25,
+51,255,255,81,0,0,0,0,63,1,
+25,
+51,255,255,81,0,0,0,128,63,
+32,0,
+45,199,3,
+2,
+52,1,0,0,0,0,1,
+22,
+1,
+50,
+56,20,0,1,1,0,15,
+27,
+51,255,255,81,0,255,255,111,3,3,
+50,
+56,20,0,0,1,0,
+25,
+51,255,255,81,0,0,0,128,191,
+25,
+51,255,255,81,0,0,0,128,63,1,
+57,
+22,
+1,
+50,
+56,20,0,1,1,0,15,
+27,
+51,255,255,81,0,255,255,226,3,1,
+50,
+56,20,0,0,1,0,
+4,1,0,3,0,0,0,
+2,
+57,2,
+32,0,
+1,
+1,
+50,
+56,20,0,0,1,0,18,
+25,
+51,255,255,81,0,0,0,0,0,9,
+1,
+50,
+56,20,0,0,1,0,19,
+25,
+51,255,255,81,0,0,0,128,63,
+2,
+52,1,0,0,0,0,1,
+44,
+8,
+51,255,255,109,0,2,
+25,
+51,255,255,81,0,0,0,0,0,
+25,
+51,255,255,81,0,0,0,128,191,1,
+57,
+4,0,
+44,
+56,20,0,0,1,
+29,27,0,
+2,
+52,1,0,0,0,0,1,
+32,0,
+1,
+50,
+56,26,0,0,1,1,18,
+25,
+51,255,255,81,0,0,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+13,
+51,255,255,11,0,1,
+25,
+51,255,255,77,3,0,0,0,0,1,
+32,0,
+1,
+50,
+56,26,0,0,1,0,20,
+33,
+56,25,0,0,
+36,
+51,255,255,230,3,0,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+9,
+51,255,255,11,0,1,
+33,
+56,24,0,0,
+36,
+51,255,255,230,3,0,0,0,0,1,
+32,0,
+1,
+50,
+56,26,0,0,1,0,18,
+33,
+56,25,0,0,
+36,
+51,255,255,230,3,1,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+9,
+51,255,255,11,0,1,
+27,
+51,255,255,49,0,255,255,140,3,3,
+33,
+56,24,0,0,
+36,
+51,255,255,230,3,0,0,0,0,
+33,
+56,24,0,0,
+36,
+51,255,255,230,3,1,0,0,0,
+1,
+1,
+50,
+56,26,0,0,1,0,1,
+33,
+56,25,0,0,
+36,
+51,255,255,230,3,0,0,0,0,3,
+1,
+33,
+56,25,0,0,
+36,
+51,255,255,230,3,1,0,0,0,1,
+33,
+56,25,0,0,
+36,
+51,255,255,230,3,0,0,0,0,1,
+32,0,
+1,
+50,
+56,26,0,0,1,0,18,
+33,
+56,25,0,0,
+36,
+51,255,255,230,3,2,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+9,
+51,255,255,11,0,1,
+27,
+51,255,255,49,0,255,255,140,3,3,
+33,
+56,24,0,0,
+36,
+51,255,255,230,3,1,0,0,0,
+33,
+56,24,0,0,
+36,
+51,255,255,230,3,2,0,0,0,
+1,
+1,
+50,
+56,26,0,0,1,0,1,
+33,
+56,25,0,0,
+36,
+51,255,255,230,3,1,0,0,0,3,
+1,
+33,
+56,25,0,0,
+36,
+51,255,255,230,3,2,0,0,0,1,
+33,
+56,25,0,0,
+36,
+51,255,255,230,3,1,0,0,0,1,
+32,0,
+1,
+50,
+56,26,0,0,1,0,18,
+33,
+56,25,0,0,
+36,
+51,255,255,230,3,3,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+9,
+51,255,255,11,0,1,
+27,
+51,255,255,49,0,255,255,140,3,3,
+33,
+56,24,0,0,
+36,
+51,255,255,230,3,2,0,0,0,
+33,
+56,24,0,0,
+36,
+51,255,255,230,3,3,0,0,0,
+1,
+1,
+50,
+56,26,0,0,1,0,1,
+33,
+56,25,0,0,
+36,
+51,255,255,230,3,2,0,0,0,3,
+1,
+33,
+56,25,0,0,
+36,
+51,255,255,230,3,3,0,0,0,1,
+33,
+56,25,0,0,
+36,
+51,255,255,230,3,2,0,0,0,1,
+2,
+52,1,0,0,0,0,1,
+44,
+9,
+51,255,255,11,0,1,
+33,
+56,24,0,0,
+36,
+51,255,255,230,3,3,0,0,0,1,1,
+29,33,0,
+2,
+52,1,0,0,0,0,1,
+32,0,
+1,
+50,
+56,32,0,0,1,1,18,
+25,
+51,255,255,81,0,0,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+13,
+51,255,255,11,0,1,
+25,
+51,255,255,77,3,0,0,0,0,1,
+32,0,
+1,
+50,
+56,32,0,0,1,0,18,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,4,0,0,0,
+2,
+52,1,0,0,0,0,1,
+32,0,
+1,
+50,
+56,32,0,0,1,0,18,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,2,0,0,0,
+2,
+52,1,0,0,0,0,1,
+32,0,
+1,
+50,
+56,32,0,0,1,0,20,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,0,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+9,
+51,255,255,11,0,1,
+33,
+56,30,0,0,
+36,
+51,255,255,230,3,0,0,0,0,1,
+32,0,
+1,
+50,
+56,32,0,0,1,0,18,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,1,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+9,
+51,255,255,11,0,1,
+27,
+51,255,255,49,0,255,255,140,3,3,
+33,
+56,30,0,0,
+36,
+51,255,255,230,3,0,0,0,0,
+33,
+56,30,0,0,
+36,
+51,255,255,230,3,1,0,0,0,
+1,
+1,
+50,
+56,32,0,0,1,0,1,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,0,0,0,0,3,
+1,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,1,0,0,0,1,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,0,0,0,0,1,
+2,
+52,1,0,0,0,0,1,
+44,
+9,
+51,255,255,11,0,1,
+27,
+51,255,255,49,0,255,255,140,3,3,
+33,
+56,30,0,0,
+36,
+51,255,255,230,3,1,0,0,0,
+33,
+56,30,0,0,
+36,
+51,255,255,230,3,2,0,0,0,
+1,
+1,
+50,
+56,32,0,0,1,0,1,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,1,0,0,0,3,
+1,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,2,0,0,0,1,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,1,0,0,0,1,1,
+2,
+52,1,0,0,0,0,1,
+32,0,
+1,
+50,
+56,32,0,0,1,0,18,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,3,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+9,
+51,255,255,11,0,1,
+27,
+51,255,255,49,0,255,255,140,3,3,
+33,
+56,30,0,0,
+36,
+51,255,255,230,3,2,0,0,0,
+33,
+56,30,0,0,
+36,
+51,255,255,230,3,3,0,0,0,
+1,
+1,
+50,
+56,32,0,0,1,0,1,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,2,0,0,0,3,
+1,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,3,0,0,0,1,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,2,0,0,0,1,
+2,
+52,1,0,0,0,0,1,
+44,
+9,
+51,255,255,11,0,1,
+27,
+51,255,255,49,0,255,255,140,3,3,
+33,
+56,30,0,0,
+36,
+51,255,255,230,3,3,0,0,0,
+33,
+56,30,0,0,
+36,
+51,255,255,230,3,4,0,0,0,
+1,
+1,
+50,
+56,32,0,0,1,0,1,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,3,0,0,0,3,
+1,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,4,0,0,0,1,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,3,0,0,0,1,1,1,
+2,
+52,1,0,0,0,0,1,
+32,0,
+1,
+50,
+56,32,0,0,1,0,18,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,6,0,0,0,
+2,
+52,1,0,0,0,0,1,
+32,0,
+1,
+50,
+56,32,0,0,1,0,18,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,5,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+9,
+51,255,255,11,0,1,
+27,
+51,255,255,49,0,255,255,140,3,3,
+33,
+56,30,0,0,
+36,
+51,255,255,230,3,4,0,0,0,
+33,
+56,30,0,0,
+36,
+51,255,255,230,3,5,0,0,0,
+1,
+1,
+50,
+56,32,0,0,1,0,1,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,4,0,0,0,3,
+1,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,5,0,0,0,1,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,4,0,0,0,1,
+2,
+52,1,0,0,0,0,1,
+44,
+9,
+51,255,255,11,0,1,
+27,
+51,255,255,49,0,255,255,140,3,3,
+33,
+56,30,0,0,
+36,
+51,255,255,230,3,5,0,0,0,
+33,
+56,30,0,0,
+36,
+51,255,255,230,3,6,0,0,0,
+1,
+1,
+50,
+56,32,0,0,1,0,1,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,5,0,0,0,3,
+1,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,6,0,0,0,1,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,5,0,0,0,1,1,
+2,
+52,1,0,0,0,0,1,
+32,0,
+1,
+50,
+56,32,0,0,1,0,18,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,7,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+9,
+51,255,255,11,0,1,
+27,
+51,255,255,49,0,255,255,140,3,3,
+33,
+56,30,0,0,
+36,
+51,255,255,230,3,6,0,0,0,
+33,
+56,30,0,0,
+36,
+51,255,255,230,3,7,0,0,0,
+1,
+1,
+50,
+56,32,0,0,1,0,1,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,6,0,0,0,3,
+1,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,7,0,0,0,1,
+33,
+56,31,0,0,
+36,
+51,255,255,230,3,6,0,0,0,1,
+2,
+52,1,0,0,0,0,1,
+44,
+9,
+51,255,255,11,0,1,
+33,
+56,30,0,0,
+36,
+51,255,255,230,3,7,0,0,0,1,1,1,1,
+29,37,0,
+2,
+52,1,2,0,
+54,150,0,
+17,242,3,
+51,255,255,109,0,2,
+54,151,0,
+17,187,0,
+51,255,255,81,0,2,2,0,
+0,0,
+1,0,4,
+22,
+1,
+56,36,0,2,23,
+56,34,0,0,
+55,150,0,
+51,255,255,109,0,0,
+1,
+56,35,0,0,1,
+56,34,0,0,
+55,151,0,
+51,255,255,81,0,0,
+1,
+27,
+51,255,255,81,0,255,255,248,3,2,
+56,36,0,0,
+56,150,0,0,3,
+27,
+51,255,255,81,0,255,255,248,3,2,
+56,150,0,0,
+56,150,0,0,
+44,
+8,
+51,255,255,109,0,2,
+56,151,0,0,
+25,
+51,255,255,81,0,0,0,128,63,1,
+29,41,0,
+2,
+52,1,1,0,
+54,152,0,
+17,187,0,
+51,255,255,81,0,2,1,0,
+0,0,2,
+55,152,0,
+51,255,255,81,0,0,
+1,
+27,
+51,255,255,81,0,255,255,252,3,2,
+56,40,0,0,
+56,38,0,0,3,
+56,39,0,0,
+44,
+8,
+51,255,255,109,0,2,
+56,152,0,0,
+25,
+51,255,255,81,0,0,0,128,63,1,
+29,46,0,
+2,
+52,1,2,0,
+54,153,0,
+17,5,4,
+51,255,255,81,0,2,
+54,154,0,
+17,187,0,
+51,255,255,81,0,2,2,0,
+0,0,
+1,0,4,
+22,
+1,
+56,45,0,2,23,
+56,42,0,0,
+55,153,0,
+51,255,255,81,0,0,
+53,
+45,11,4,
+1,
+25,
+51,255,255,81,0,0,0,0,64,2,
+27,
+51,255,255,81,0,255,255,40,4,2,
+42,1,
+50,
+56,45,0,0,1,1,
+1,
+27,
+51,255,255,81,0,255,255,117,3,1,
+56,45,0,0,1,
+50,
+56,45,0,0,1,0,
+27,
+51,255,255,81,0,255,255,40,4,2,
+42,1,
+50,
+56,45,0,0,1,1,
+42,1,
+50,
+56,45,0,0,1,0,
+55,154,0,
+51,255,255,81,0,0,
+1,
+1,
+1,
+1,
+56,153,0,0,2,
+25,
+51,255,255,81,0,131,249,34,62,0,
+25,
+51,255,255,81,0,0,0,0,63,0,
+56,43,0,0,2,
+56,44,0,0,
+44,
+8,
+51,255,255,109,0,2,
+56,154,0,0,
+25,
+51,255,255,81,0,0,0,128,63,1,
+29,49,0,
+2,
+52,1,0,0,0,0,1,
+44,
+1,
+8,
+51,255,255,156,1,9,
+25,
+51,255,255,81,0,0,0,0,0,
+25,
+51,255,255,81,0,0,0,128,191,
+25,
+51,255,255,81,0,0,0,0,0,
+25,
+51,255,255,81,0,0,0,128,63,
+25,
+51,255,255,81,0,0,0,0,0,
+25,
+51,255,255,81,0,0,0,0,0,
+25,
+51,255,255,81,0,0,0,0,0,
+25,
+51,255,255,81,0,0,0,0,0,
+25,
+51,255,255,81,0,0,0,128,63,2,
+27,
+51,255,255,156,1,255,255,45,4,1,
+8,
+51,255,255,156,1,9,
+1,
+50,
+56,48,0,0,1,1,1,
+50,
+56,47,0,0,1,1,
+1,
+50,
+56,47,0,0,1,0,1,
+50,
+56,48,0,0,1,0,
+25,
+51,255,255,81,0,0,0,0,0,
+1,
+50,
+56,48,0,0,1,0,1,
+50,
+56,47,0,0,1,0,
+1,
+50,
+56,48,0,0,1,1,1,
+50,
+56,47,0,0,1,1,
+25,
+51,255,255,81,0,0,0,0,0,
+50,
+56,47,0,0,1,0,
+50,
+56,47,0,0,1,1,
+25,
+51,255,255,81,0,0,0,128,63,1,
+29,55,0,
+2,
+52,1,5,0,
+54,155,0,
+38,
+16,4,53,4,
+51,255,255,81,0,2,
+54,156,0,
+17,73,4,
+51,255,255,81,0,2,
+54,157,0,
+17,81,4,
+51,255,255,81,0,2,
+54,158,0,
+17,89,4,
+51,255,255,96,4,2,
+54,159,0,
+17,101,4,
+51,255,255,96,4,2,5,0,
+0,0,
+1,0,
+2,0,
+3,0,
+4,0,6,
+55,155,0,
+51,255,255,81,0,0,
+25,
+51,255,255,81,0,0,0,128,57,
+55,156,0,
+51,255,255,81,0,0,
+27,
+51,255,255,81,0,255,255,252,3,2,
+56,50,0,0,
+56,51,0,0,
+55,157,0,
+51,255,255,81,0,0,
+1,
+56,53,0,0,1,
+56,52,0,0,
+55,158,0,
+51,255,255,96,4,0,
+1,
+56,156,0,0,18,
+56,155,0,0,
+55,159,0,
+51,255,255,96,4,0,
+1,
+27,
+51,255,255,81,0,255,255,226,3,1,
+56,157,0,0,18,
+56,155,0,0,
+32,0,
+56,158,0,0,
+2,
+52,1,5,0,
+54,160,0,
+17,107,4,
+51,255,255,81,0,2,
+54,161,0,
+17,113,4,
+51,255,255,81,0,2,
+54,162,0,
+17,123,4,
+51,255,255,81,0,2,
+54,163,0,
+17,128,4,
+51,255,255,109,0,2,
+54,164,0,
+17,187,0,
+51,255,255,81,0,2,5,0,
+2,0,
+3,0,
+0,0,
+1,0,
+4,0,7,
+32,0,
+56,159,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+8,
+51,255,255,109,0,2,
+25,
+51,255,255,81,0,0,0,0,0,
+25,
+51,255,255,81,0,0,0,128,191,1,
+57,
+55,160,0,
+51,255,255,81,0,0,
+1,
+25,
+51,255,255,81,0,0,0,128,63,3,
+56,157,0,0,
+55,161,0,
+51,255,255,81,0,0,
+27,
+51,255,255,81,0,255,255,131,4,1,
+56,157,0,0,
+55,162,0,
+51,255,255,81,0,0,
+1,
+56,52,0,0,3,
+56,157,0,0,
+55,163,0,
+51,255,255,109,0,0,
+1,
+1,
+56,54,0,0,1,
+56,50,0,0,2,
+56,160,0,0,
+55,164,0,
+51,255,255,81,0,0,
+1,
+1,
+27,
+51,255,255,81,0,255,255,117,3,1,
+56,163,0,0,2,
+56,161,0,0,1,
+56,162,0,0,
+44,
+8,
+51,255,255,109,0,2,
+56,164,0,0,
+25,
+51,255,255,81,0,0,0,128,63,1,
+32,0,
+56,159,0,0,
+2,
+52,1,5,0,
+54,165,0,
+17,136,4,
+51,255,255,156,1,2,
+54,166,0,
+17,146,4,
+51,255,255,81,0,2,
+54,167,0,
+17,148,4,
+51,255,255,81,0,2,
+54,168,0,
+17,128,4,
+51,255,255,109,0,2,
+54,169,0,
+17,187,0,
+51,255,255,81,0,2,5,0,
+3,0,
+1,0,
+2,0,
+4,0,
+0,0,8,
+55,165,0,
+51,255,255,156,1,0,
+27,
+51,255,255,156,1,49,0,2,
+56,50,0,0,
+56,51,0,0,
+55,166,0,
+51,255,255,81,0,0,
+1,
+56,52,0,0,3,
+56,156,0,0,
+55,167,0,
+51,255,255,81,0,0,
+1,
+56,166,0,0,2,
+56,166,0,0,
+55,168,0,
+51,255,255,109,0,0,
+50,
+1,
+56,165,0,0,2,
+8,
+51,255,255,152,4,2,
+56,54,0,0,
+25,
+51,255,255,81,0,0,0,128,63,2,0,1,
+55,169,0,
+51,255,255,81,0,0,
+1,
+56,167,0,0,1,
+1,
+50,
+56,168,0,0,1,1,2,
+50,
+56,168,0,0,1,1,
+32,0,
+1,
+56,169,0,0,18,
+25,
+51,255,255,81,0,0,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+8,
+51,255,255,109,0,2,
+25,
+51,255,255,81,0,0,0,0,0,
+25,
+51,255,255,81,0,0,0,128,191,1,
+57,
+22,
+1,
+56,169,0,1,15,
+1,
+50,
+56,168,0,0,1,0,0,
+27,
+51,255,255,81,0,255,255,159,4,1,
+56,169,0,0,
+44,
+8,
+51,255,255,109,0,2,
+56,169,0,0,
+25,
+51,255,255,81,0,0,0,128,63,1,
+2,
+52,1,14,0,
+54,170,0,
+17,79,0,
+51,255,255,81,0,2,
+54,171,0,
+17,164,4,
+51,255,255,96,4,2,
+54,172,0,
+17,174,4,
+51,255,255,109,0,2,
+54,173,0,
+17,136,4,
+51,255,255,156,1,2,
+54,174,0,
+17,177,4,
+51,255,255,81,0,2,
+54,175,0,
+17,184,4,
+51,255,255,81,0,2,
+54,176,0,
+17,191,4,
+51,255,255,81,0,2,
+54,177,0,
+17,194,4,
+51,255,255,96,4,2,
+54,178,0,
+17,128,4,
+51,255,255,109,0,2,
+54,179,0,
+17,210,4,
+51,255,255,81,0,2,
+54,180,0,
+17,216,4,
+51,255,255,81,0,2,
+54,181,0,
+17,228,4,
+51,255,255,96,4,2,
+54,182,0,
+17,242,4,
+51,255,255,81,0,2,
+54,183,0,
+17,187,0,
+51,255,255,81,0,2,14,0,
+2,0,
+10,0,
+0,0,
+9,0,
+7,0,
+1,0,
+11,0,
+8,0,
+6,0,
+4,0,
+5,0,
+13,0,
+3,0,
+12,0,21,
+55,170,0,
+51,255,255,81,0,0,
+1,
+56,52,0,0,3,
+1,
+56,52,0,0,1,
+56,53,0,0,
+55,171,0,
+51,255,255,96,4,0,
+1,
+27,
+51,255,255,81,0,255,255,226,3,1,
+1,
+56,170,0,0,1,
+25,
+51,255,255,81,0,0,0,128,63,18,
+56,155,0,0,
+32,0,
+56,171,0,0,
+2,
+52,1,1,0,
+54,184,0,
+17,246,4,
+51,255,255,109,0,2,1,0,
+0,0,4,
+55,184,0,
+51,255,255,109,0,0,
+56,50,0,0,
+22,
+1,
+56,50,0,1,15,
+56,51,0,0,
+22,
+1,
+56,51,0,1,15,
+56,184,0,0,
+22,
+1,
+56,170,0,1,15,
+25,
+51,255,255,81,0,0,0,0,0,1,
+57,
+55,172,0,
+51,255,255,109,0,0,
+1,
+1,
+56,50,0,0,2,
+1,
+25,
+51,255,255,81,0,0,0,128,63,1,
+56,170,0,0,0,
+1,
+56,51,0,0,2,
+56,170,0,0,
+55,173,0,
+51,255,255,156,1,0,
+27,
+51,255,255,156,1,49,0,2,
+56,172,0,0,
+56,51,0,0,
+55,174,0,
+51,255,255,81,0,0,
+27,
+51,255,255,81,0,255,255,226,3,1,
+1,
+25,
+51,255,255,81,0,0,0,128,63,1,
+56,170,0,0,
+55,175,0,
+51,255,255,81,0,0,
+56,174,0,0,
+55,176,0,
+51,255,255,81,0,0,
+1,
+27,
+51,255,255,81,0,255,255,226,3,1,
+1,
+56,53,0,0,1,
+56,52,0,0,3,
+56,156,0,0,
+55,177,0,
+51,255,255,96,4,0,
+1,
+27,
+51,255,255,81,0,255,255,226,3,1,
+1,
+56,176,0,0,1,
+25,
+51,255,255,81,0,0,0,128,63,18,
+56,155,0,0,
+32,0,
+56,177,0,0,
+2,
+52,1,0,0,0,0,2,
+22,
+1,
+56,174,0,2,24,
+25,
+51,255,255,81,0,0,0,0,63,
+22,
+1,
+56,175,0,2,24,
+25,
+51,255,255,81,0,0,0,0,63,1,
+2,
+52,1,0,0,0,0,2,
+22,
+1,
+56,174,0,2,24,
+1,
+56,176,0,0,3,
+1,
+1,
+56,176,0,0,2,
+56,176,0,0,1,
+25,
+51,255,255,81,0,0,0,128,63,
+22,
+1,
+56,175,0,2,25,
+27,
+51,255,255,81,0,255,255,159,4,1,
+27,
+51,255,255,81,0,255,255,226,3,1,
+1,
+1,
+56,176,0,0,2,
+56,176,0,0,1,
+25,
+51,255,255,81,0,0,0,128,63,1,
+22,
+1,
+56,173,0,1,15,
+1,
+8,
+51,255,255,156,1,9,
+56,174,0,0,
+25,
+51,255,255,81,0,0,0,0,0,
+25,
+51,255,255,81,0,0,0,0,0,
+25,
+51,255,255,81,0,0,0,0,0,
+56,175,0,0,
+25,
+51,255,255,81,0,0,0,0,0,
+25,
+51,255,255,81,0,0,0,0,0,
+25,
+51,255,255,81,0,0,0,0,0,
+25,
+51,255,255,81,0,0,0,128,63,2,
+56,173,0,0,
+55,178,0,
+51,255,255,109,0,0,
+50,
+1,
+56,173,0,0,2,
+8,
+51,255,255,152,4,2,
+56,54,0,0,
+25,
+51,255,255,81,0,0,0,128,63,2,0,1,
+55,179,0,
+51,255,255,81,0,0,
+1,
+25,
+51,255,255,81,0,0,0,128,63,3,
+56,176,0,0,
+55,180,0,
+51,255,255,81,0,0,
+27,
+51,255,255,81,0,255,255,131,4,1,
+1,
+25,
+51,255,255,81,0,0,0,128,63,1,
+56,170,0,0,
+55,181,0,
+51,255,255,96,4,0,
+1,
+42,7,
+56,177,0,0,8,
+1,
+56,176,0,0,19,
+25,
+51,255,255,81,0,0,0,128,63,
+55,182,0,
+51,255,255,81,0,0,
+25,
+51,255,255,81,0,0,0,128,191,
+32,0,
+56,177,0,0,
+2,
+52,1,0,0,0,0,1,
+22,
+1,
+56,182,0,1,15,
+1,
+27,
+51,255,255,81,0,255,255,248,3,2,
+56,178,0,0,
+56,178,0,0,3,
+50,
+56,178,0,0,1,0,1,
+32,0,
+56,181,0,0,
+2,
+52,1,0,0,0,0,1,
+22,
+1,
+56,182,0,1,15,
+1,
+27,
+51,255,255,81,0,255,255,117,3,1,
+56,178,0,0,1,
+1,
+50,
+56,178,0,0,1,0,2,
+56,179,0,0,1,
+2,
+52,1,1,0,
+54,185,0,
+17,252,4,
+51,255,255,81,0,2,1,0,
+0,0,2,
+55,185,0,
+51,255,255,81,0,0,
+1,
+1,
+50,
+56,178,0,0,1,0,2,
+50,
+56,178,0,0,1,0,1,
+1,
+50,
+56,178,0,0,1,1,2,
+50,
+56,178,0,0,1,1,
+32,0,
+1,
+56,185,0,0,21,
+25,
+51,255,255,81,0,0,0,0,0,
+2,
+52,1,0,0,0,0,1,
+32,0,
+1,
+56,171,0,0,9,
+1,
+56,180,0,0,18,
+25,
+51,255,255,81,0,0,0,0,0,
+2,
+52,1,0,0,0,0,1,
+22,
+1,
+56,182,0,1,15,
+1,
+42,1,
+27,
+51,255,255,81,0,255,255,159,4,1,
+56,185,0,0,1,
+1,
+50,
+56,178,0,0,1,0,2,
+56,179,0,0,1,
+2,
+52,1,0,0,0,0,1,
+22,
+1,
+56,182,0,1,15,
+1,
+27,
+51,255,255,81,0,255,255,159,4,1,
+56,185,0,0,1,
+1,
+50,
+56,178,0,0,1,0,2,
+56,179,0,0,1,1,
+57,1,
+32,0,
+1,
+42,7,
+56,181,0,0,8,
+1,
+56,182,0,0,18,
+25,
+51,255,255,81,0,0,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+8,
+51,255,255,109,0,2,
+25,
+51,255,255,81,0,0,0,0,0,
+25,
+51,255,255,81,0,0,0,128,191,1,
+57,
+55,183,0,
+51,255,255,81,0,0,
+1,
+56,170,0,0,0,
+1,
+56,180,0,0,2,
+56,182,0,0,
+32,0,
+56,171,0,0,
+2,
+52,1,0,0,0,0,1,
+22,
+1,
+56,183,0,1,15,
+1,
+25,
+51,255,255,81,0,0,0,128,63,1,
+56,183,0,0,1,
+57,
+44,
+8,
+51,255,255,109,0,2,
+56,183,0,0,
+25,
+51,255,255,81,0,0,0,128,63,1,1,
+29,62,0,
+2,
+52,1,1,0,
+54,186,0,
+17,187,0,
+51,255,255,109,0,2,1,0,
+0,0,3,
+55,186,0,
+51,255,255,109,0,0,
+27,
+51,255,255,109,0,37,0,3,
+56,59,0,0,
+56,60,0,0,
+56,56,0,0,
+22,
+1,
+56,186,0,1,15,
+27,
+51,255,255,109,0,21,0,2,
+56,61,0,0,
+56,186,0,0,
+44,
+27,
+51,255,255,11,0,27,0,3,
+56,57,0,0,
+56,58,0,0,
+56,186,0,0,1,
+29,69,0,
+2,
+52,1,1,0,
+54,187,0,
+17,187,0,
+51,255,255,109,0,2,1,0,
+0,0,3,
+55,187,0,
+51,255,255,109,0,0,
+27,
+51,255,255,109,0,37,0,3,
+56,66,0,0,
+56,67,0,0,
+56,63,0,0,
+22,
+1,
+56,187,0,1,15,
+27,
+51,255,255,109,0,21,0,2,
+56,68,0,0,
+56,187,0,0,
+44,
+27,
+51,255,255,11,0,33,0,3,
+56,64,0,0,
+56,65,0,0,
+56,187,0,0,1,
+29,76,0,
+2,
+52,1,1,0,
+54,188,0,
+17,187,0,
+51,255,255,109,0,2,1,0,
+0,0,3,
+55,188,0,
+51,255,255,109,0,0,
+27,
+51,255,255,109,0,41,0,3,
+56,73,0,0,
+56,74,0,0,
+56,70,0,0,
+22,
+1,
+56,188,0,1,15,
+27,
+51,255,255,109,0,21,0,2,
+56,75,0,0,
+56,188,0,0,
+44,
+27,
+51,255,255,11,0,27,0,3,
+56,71,0,0,
+56,72,0,0,
+56,188,0,0,1,
+29,83,0,
+2,
+52,1,1,0,
+54,189,0,
+17,187,0,
+51,255,255,109,0,2,1,0,
+0,0,3,
+55,189,0,
+51,255,255,109,0,0,
+27,
+51,255,255,109,0,41,0,3,
+56,80,0,0,
+56,81,0,0,
+56,77,0,0,
+22,
+1,
+56,189,0,1,15,
+27,
+51,255,255,109,0,21,0,2,
+56,82,0,0,
+56,189,0,0,
+44,
+27,
+51,255,255,11,0,33,0,3,
+56,78,0,0,
+56,79,0,0,
+56,189,0,0,1,
+29,91,0,
+2,
+52,1,1,0,
+54,190,0,
+17,187,0,
+51,255,255,109,0,2,1,0,
+0,0,3,
+55,190,0,
+51,255,255,109,0,0,
+27,
+51,255,255,109,0,46,0,4,
+56,87,0,0,
+56,88,0,0,
+56,89,0,0,
+56,84,0,0,
+22,
+1,
+56,190,0,1,15,
+27,
+51,255,255,109,0,21,0,2,
+56,90,0,0,
+56,190,0,0,
+44,
+27,
+51,255,255,11,0,27,0,3,
+56,85,0,0,
+56,86,0,0,
+56,190,0,0,1,
+29,99,0,
+2,
+52,1,1,0,
+54,191,0,
+17,187,0,
+51,255,255,109,0,2,1,0,
+0,0,3,
+55,191,0,
+51,255,255,109,0,0,
+27,
+51,255,255,109,0,46,0,4,
+56,95,0,0,
+56,96,0,0,
+56,97,0,0,
+56,92,0,0,
+22,
+1,
+56,191,0,1,15,
+27,
+51,255,255,109,0,21,0,2,
+56,98,0,0,
+56,191,0,0,
+44,
+27,
+51,255,255,11,0,33,0,3,
+56,93,0,0,
+56,94,0,0,
+56,191,0,0,1,
+29,108,0,
+2,
+52,1,1,0,
+54,192,0,
+17,187,0,
+51,255,255,109,0,2,1,0,
+0,0,3,
+55,192,0,
+51,255,255,109,0,0,
+27,
+51,255,255,109,0,55,0,5,
+56,103,0,0,
+56,104,0,0,
+56,105,0,0,
+56,106,0,0,
+56,100,0,0,
+22,
+1,
+56,192,0,1,15,
+27,
+51,255,255,109,0,21,0,2,
+56,107,0,0,
+56,192,0,0,
+44,
+27,
+51,255,255,11,0,27,0,3,
+56,101,0,0,
+56,102,0,0,
+56,192,0,0,1,
+29,117,0,
+2,
+52,1,1,0,
+54,193,0,
+17,187,0,
+51,255,255,109,0,2,1,0,
+0,0,3,
+55,193,0,
+51,255,255,109,0,0,
+27,
+51,255,255,109,0,55,0,5,
+56,112,0,0,
+56,113,0,0,
+56,114,0,0,
+56,115,0,0,
+56,109,0,0,
+22,
+1,
+56,193,0,1,15,
+27,
+51,255,255,109,0,21,0,2,
+56,116,0,0,
+56,193,0,0,
+44,
+27,
+51,255,255,11,0,33,0,3,
+56,110,0,0,
+56,111,0,0,
+56,193,0,0,1,
+29,122,0,
+2,
+52,1,1,0,
+54,194,0,
+17,1,5,
+51,255,255,11,0,2,1,0,
+0,0,4,
+32,0,
+12,
+51,255,255,96,4,1,
+56,121,0,0,
+2,
+52,1,0,0,0,0,1,
+22,
+1,
+56,118,0,1,15,
+27,
+51,255,255,11,0,255,255,10,5,2,
+50,
+56,118,0,0,3,0,1,2,
+50,
+56,118,0,0,1,3,1,
+2,
+52,1,0,0,0,0,1,
+22,
+1,
+56,118,0,1,15,
+27,
+51,255,255,11,0,255,255,22,5,1,
+56,118,0,0,1,
+55,194,0,
+51,255,255,11,0,0,
+9,
+51,255,255,11,0,1,
+1,
+1,
+56,119,0,0,2,
+9,
+51,255,255,49,0,1,
+56,118,0,0,0,
+56,120,0,0,
+32,0,
+12,
+51,255,255,96,4,1,
+56,121,0,0,
+2,
+52,1,0,0,0,0,1,
+22,
+1,
+56,194,0,1,15,
+27,
+51,255,255,11,0,255,255,31,5,2,
+50,
+56,194,0,0,3,0,1,2,
+50,
+56,194,0,0,1,3,1,
+2,
+52,1,0,0,0,0,2,
+22,
+1,
+56,194,0,1,15,
+27,
+51,255,255,11,0,255,255,43,5,1,
+56,194,0,0,
+22,
+1,
+50,
+56,194,0,2,3,0,1,2,24,
+50,
+56,194,0,0,1,3,1,
+44,
+56,194,0,0,1,
+29,126,0,
+2,
+52,1,0,0,0,0,1,
+49,0,
+52,1,0,0,0,0,
+56,123,0,0,30,0,0,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,52,5,2,
+56,124,0,0,
+56,125,0,0,1,0,1,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,64,5,2,
+56,124,0,0,
+56,125,0,0,1,0,2,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,74,5,2,
+56,124,0,0,
+56,125,0,0,1,0,3,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,84,5,3,
+8,
+51,255,255,11,0,4,
+25,
+51,255,255,77,3,0,0,128,63,
+25,
+51,255,255,77,3,0,0,0,0,
+25,
+51,255,255,77,3,0,0,0,0,
+25,
+51,255,255,77,3,0,0,128,191,
+56,124,0,0,
+56,125,0,0,1,0,4,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,84,5,3,
+8,
+51,255,255,11,0,4,
+25,
+51,255,255,77,3,0,0,0,0,
+25,
+51,255,255,77,3,0,0,128,63,
+25,
+51,255,255,77,3,0,0,128,191,
+25,
+51,255,255,77,3,0,0,0,0,
+56,124,0,0,
+56,125,0,0,1,0,5,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,84,5,3,
+8,
+51,255,255,11,0,4,
+25,
+51,255,255,77,3,0,0,0,0,
+25,
+51,255,255,77,3,0,0,0,0,
+25,
+51,255,255,77,3,0,0,128,63,
+25,
+51,255,255,77,3,0,0,0,0,
+56,124,0,0,
+56,125,0,0,1,0,6,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,84,5,3,
+8,
+51,255,255,11,0,4,
+25,
+51,255,255,77,3,0,0,0,0,
+25,
+51,255,255,77,3,0,0,0,0,
+25,
+51,255,255,77,3,0,0,0,0,
+25,
+51,255,255,77,3,0,0,128,63,
+56,124,0,0,
+56,125,0,0,1,0,7,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,84,5,3,
+8,
+51,255,255,11,0,4,
+25,
+51,255,255,77,3,0,0,0,0,
+25,
+51,255,255,77,3,0,0,0,0,
+25,
+51,255,255,77,3,0,0,128,191,
+25,
+51,255,255,77,3,0,0,0,0,
+56,124,0,0,
+56,125,0,0,1,0,8,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,84,5,3,
+8,
+51,255,255,11,0,4,
+25,
+51,255,255,77,3,0,0,0,0,
+25,
+51,255,255,77,3,0,0,0,0,
+25,
+51,255,255,77,3,0,0,0,0,
+25,
+51,255,255,77,3,0,0,128,191,
+56,124,0,0,
+56,125,0,0,1,0,9,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,84,5,3,
+8,
+51,255,255,11,0,4,
+25,
+51,255,255,77,3,0,0,0,0,
+25,
+51,255,255,77,3,0,0,0,0,
+25,
+51,255,255,77,3,0,0,128,63,
+25,
+51,255,255,77,3,0,0,128,191,
+56,124,0,0,
+56,125,0,0,1,0,10,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,84,5,3,
+8,
+51,255,255,11,0,4,
+25,
+51,255,255,77,3,0,0,0,0,
+25,
+51,255,255,77,3,0,0,0,0,
+25,
+51,255,255,77,3,0,0,128,191,
+25,
+51,255,255,77,3,0,0,128,63,
+56,124,0,0,
+56,125,0,0,1,0,11,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,84,5,3,
+8,
+51,255,255,11,0,4,
+25,
+51,255,255,77,3,0,0,0,0,
+25,
+51,255,255,77,3,0,0,0,0,
+25,
+51,255,255,77,3,0,0,128,191,
+25,
+51,255,255,77,3,0,0,128,191,
+56,124,0,0,
+56,125,0,0,1,0,12,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,84,5,3,
+8,
+51,255,255,11,0,4,
+25,
+51,255,255,77,3,0,0,128,63,
+25,
+51,255,255,77,3,0,0,128,63,
+25,
+51,255,255,77,3,0,0,0,0,
+25,
+51,255,255,77,3,0,0,0,0,
+56,124,0,0,
+56,125,0,0,1,0,13,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,102,5,2,
+56,124,0,0,
+56,125,0,0,1,0,14,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,117,5,2,
+56,124,0,0,
+56,125,0,0,1,0,15,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,130,5,3,
+25,
+51,255,255,77,3,0,0,0,0,
+56,124,0,0,
+56,125,0,0,1,0,16,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,144,5,3,
+25,
+51,255,255,77,3,0,0,128,63,
+56,124,0,0,
+56,125,0,0,1,0,17,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,144,5,3,
+25,
+51,255,255,77,3,0,0,128,191,
+56,124,0,0,
+56,125,0,0,1,0,18,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,157,5,2,
+56,124,0,0,
+56,125,0,0,1,0,19,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,175,5,2,
+56,124,0,0,
+56,125,0,0,1,0,20,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,130,5,3,
+25,
+51,255,255,77,3,0,0,128,63,
+56,124,0,0,
+56,125,0,0,1,0,21,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,192,5,2,
+56,124,0,0,
+56,125,0,0,1,0,22,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,209,5,2,
+56,124,0,0,
+56,125,0,0,1,0,23,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,226,5,2,
+56,124,0,0,
+56,125,0,0,1,0,24,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,242,5,2,
+56,124,0,0,
+56,125,0,0,1,0,25,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,1,6,3,
+8,
+51,255,255,12,6,2,
+25,
+51,255,255,77,3,0,0,0,0,
+25,
+51,255,255,77,3,0,0,128,63,
+56,124,0,0,
+56,125,0,0,1,0,26,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,1,6,3,
+13,
+51,255,255,12,6,1,
+25,
+51,255,255,77,3,0,0,128,63,
+56,124,0,0,
+56,125,0,0,1,0,27,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,1,6,3,
+13,
+51,255,255,12,6,1,
+25,
+51,255,255,77,3,0,0,0,0,
+56,124,0,0,
+56,125,0,0,1,0,28,0,0,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,255,255,1,6,3,
+8,
+51,255,255,12,6,2,
+25,
+51,255,255,77,3,0,0,128,63,
+25,
+51,255,255,77,3,0,0,0,0,
+56,124,0,0,
+56,125,0,0,1,1,
+44,
+13,
+51,255,255,11,0,1,
+25,
+51,255,255,77,3,0,0,0,0,1,
+29,130,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,126,0,3,
+56,127,0,0,
+56,128,0,0,
+56,129,0,0,1,
+29,134,0,
+2,
+52,1,0,0,0,0,1,
+44,
+27,
+51,255,255,11,0,126,0,3,
+56,132,0,0,
+9,
+51,255,255,11,0,1,
+56,133,0,0,
+56,131,0,0,1,
+29,137,0,
+2,
+52,1,2,0,
+54,195,0,
+17,102,0,
+51,255,255,11,0,2,
+54,196,0,
+17,17,0,
+51,255,255,11,0,2,2,0,
+1,0,
+0,0,3,
+55,195,0,
+51,255,255,11,0,0,
+1,
+1,
+1,
+27,
+51,255,255,11,0,255,255,22,5,1,
+56,135,0,0,2,
+25,
+51,255,255,77,3,0,0,127,67,3,
+25,
+51,255,255,77,3,0,0,128,67,0,
+25,
+51,255,255,77,3,0,0,0,59,
+55,196,0,
+51,255,255,11,0,0,
+8,
+51,255,255,11,0,4,
+50,
+27,
+51,255,255,11,0,255,255,182,3,2,
+56,136,0,0,
+9,
+51,255,255,109,0,1,
+8,
+51,255,255,12,6,2,
+50,
+56,195,0,0,1,0,
+25,
+51,255,255,77,3,0,0,192,62,1,0,
+50,
+27,
+51,255,255,11,0,255,255,182,3,2,
+56,136,0,0,
+9,
+51,255,255,109,0,1,
+8,
+51,255,255,12,6,2,
+50,
+56,195,0,0,1,1,
+25,
+51,255,255,77,3,0,0,32,63,1,0,
+50,
+27,
+51,255,255,11,0,255,255,182,3,2,
+56,136,0,0,
+9,
+51,255,255,109,0,1,
+8,
+51,255,255,12,6,2,
+50,
+56,195,0,0,1,2,
+25,
+51,255,255,77,3,0,0,96,63,1,0,
+25,
+51,255,255,77,3,0,0,128,63,
+44,
+1,
+56,196,0,0,2,
+50,
+27,
+51,255,255,11,0,255,255,182,3,2,
+56,136,0,0,
+9,
+51,255,255,109,0,1,
+8,
+51,255,255,12,6,2,
+50,
+56,195,0,0,1,3,
+25,
+51,255,255,77,3,0,0,0,62,1,0,1,
+29,139,0,
+2,
+52,1,1,0,
+54,197,0,
+17,18,6,
+51,255,255,77,3,2,1,0,
+0,0,3,
+55,197,0,
+51,255,255,77,3,0,
+1,
+25,
+51,255,255,77,3,0,0,128,63,1,
+50,
+56,138,0,0,1,3,
+22,
+1,
+56,197,0,1,15,
+1,
+27,
+51,255,255,77,3,255,255,25,6,1,
+1,
+1,
+42,1,
+56,197,0,0,2,
+56,197,0,0,2,
+25,
+51,255,255,77,3,0,0,128,64,1,
+25,
+51,255,255,77,3,188,116,147,60,
+44,
+13,
+51,255,255,11,0,1,
+56,197,0,0,1,
+21,};
+static constexpr size_t SKSL_INCLUDE_sksl_graphite_frag_LENGTH = sizeof(SKSL_INCLUDE_sksl_graphite_frag);
diff --git a/gfx/skia/skia/src/sksl/generated/sksl_graphite_frag.minified.sksl b/gfx/skia/skia/src/sksl/generated/sksl_graphite_frag.minified.sksl
new file mode 100644
index 0000000000..464e53c517
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/generated/sksl_graphite_frag.minified.sksl
@@ -0,0 +1,179 @@
+static constexpr char SKSL_MINIFIED_sksl_graphite_frag[] =
+"$pure half4 sk_error(){return half4(1.,0.,1.,1.);}$pure half4 sk_passthrough"
+"(half4 a){return a;}$pure half4 sk_solid_shader(float4 a){return half4(a);}"
+"$pure half4 $k(int a,half4 b){half4 c=b;switch(a){case 0:break;case 1:c=half4"
+"(b.xyz,1.);break;case 2:c=b.xxxx;break;case 3:c=half4(b.xxx,1.);break;case 4"
+":c=b.zyxw;break;}return c;}$pure half $l(int a,half b,half[7]c){half d=c[0]"
+";half e=c[1];half f=c[2];half g=c[3];half h=c[4];half i=c[5];half j=c[6];half"
+" k=sign(b);b=abs(b);switch(a){case 1:b=b<h?g*b+j:pow(e*b+f,d)+i;break;case 2"
+":b=pow(max(e+f*pow(b,g),0.)/(h+i*pow(b,g)),j);break;case 3:b=b*e<=1.?pow(b*"
+"e,f):exp((b-i)*g)+h;b*=j+1.;break;case 4:b/=j+1.;b=b<=1.?e*pow(b,f):g*log(b"
+"-h)+i;break;}return k*b;}$pure half4 sk_color_space_transform(half4 a,int b"
+",int c,half[7]d,half3x3 e,int f,half[7]g){if(bool(b&1)){a=unpremul(a);}if(bool"
+"(b&2)){a.x=$l(c,a.x,d);a.y=$l(c,a.y,d);a.z=$l(c,a.z,d);}if(bool(b&4)){a.xyz"
+"=e*a.xyz;}if(bool(b&8)){a.x=$l(f,a.x,g);a.y=$l(f,a.y,g);a.z=$l(f,a.z,g);}if"
+"(bool(b&16)){a.xyz*=a.w;}return a;}$pure float $m(int a,float b,float c,float"
+" d){switch(a){case 0:return clamp(b,c,d);case 1:{float e=d-c;return mod(b-c"
+",e)+c;}case 2:{float e=d-c;float g=2.*e;float h=mod(b-c,g);return mix(h,g-h"
+",step(e,h))+c;}default:return b;}}$pure half4 $n(float2 a,float2 b,float4 c"
+",int d,int e,int f,int g,sampler2D h){if(d==3&&f==0){float i=floor(a.x)+.5;"
+"if(i<c.x||i>c.z){return half4(0.);}}if(e==3&&f==0){float i=floor(a.y)+.5;if"
+"(i<c.y||i>c.w){return half4(0.);}}a.x=$m(d,a.x,c.x,c.z);a.y=$m(e,a.y,c.y,c."
+"w);float4 i;if(f==0){i=float4(floor(c.xy)+.5,ceil(c.zw)-.5);}else{i=float4("
+"c.xy+.5,c.zw-.5);}float2 j=clamp(a,i.xy,i.zw);half4 k=sample(h,j/b);k=$k(g,"
+"k);if(f==1){half2 l=half2(a-j);half2 m=abs(l);bool n=d==1;bool o=e==1;if(n||"
+"o){float p;float q;half4 r;half4 t;if(n){p=l.x>0.?i.x:i.z;r=sample(h,float2"
+"(p,j.y)/b);r=$k(g,r);}if(o){q=l.y>0.?i.y:i.w;t=sample(h,float2(j.x,q)/b);t="
+"$k(g,t);}if(n&&o){half4 u=sample(h,float2(p,q)/b);u=$k(g,u);k=mix(mix(k,r,m"
+".x),mix(t,u,m.x),m.y);}else if(n){k=mix(k,r,m.x);}else if(o){k=mix(k,t,m.y)"
+";}}if(d==3){k*=max(1.-m.x,0.);}if(e==3){k*=max(1.-m.y,0.);}}return k;}$pure"
+" half4 $o(float2 a,float2 b,float4 c,int d,int e,float4x4 g,int h,sampler2D"
+" i){float2 j=fract(a-.5);a-=1.5;a=floor(a)+.5;float4 k=g*float4(1.,j.x,j.x*"
+"j.x,(j.x*j.x)*j.x);float4 l=g*float4(1.,j.y,j.y*j.y,(j.y*j.y)*j.y);float4 m"
+"=float4(0.);for(int n=0;n<4;++n){float4 o=float4(0.);for(int p=0;p<4;++p){o"
+"+=k[p]*float4($n(a+float2(float(p),float(n)),b,c,d,e,0,h,i));}m+=l[n]*o;}return"
+" half4(m);}$pure half4 sk_image_shader(float2 a,float2 b,float4 c,int d,int"
+" e,int f,int g,float4x4 h,int i,int j,int k,half[7]l,half3x3 m,int n,half[7"
+"]o,sampler2D p){half4 q=g!=0?$o(a,b,c,d,e,h,i,p):$n(a,b,c,d,e,f,i,p);return"
+" sk_color_space_transform(q,j,k,l,m,n,o);}$pure half4 sk_dither_shader(half4"
+" a,float2 b,float c,sampler2D d){half2 f=half2(half(b.x*.125),half(b.y*.125"
+"));half g=sample(d,float2(f)).x-.5;return half4(half3(clamp(float3(a.xyz)+float"
+"(g)*c,0.,float(a.w))),a.w);}$pure float2 $p(int a,float2 b){switch(a){case 0"
+":b.x=clamp(b.x,0.,1.);break;case 1:b.x=fract(b.x);break;case 2:{float c=b.x"
+"-1.;b.x=(c-2.*floor(c*.5))-1.;if(sk_Caps.mustDoOpBetweenFloorAndAbs){b.x=clamp"
+"(b.x,-1.,1.);}b.x=abs(b.x);break;}case 3:if(b.x<0.||b.x>1.){return float2(0."
+",-1.);}break;}return b;}$pure half4 $q(float4[4]a,float[4]b,float2 c){if(c."
+"y<0.){return half4(0.);}else if(c.x<=b[0]){return half4(a[0]);}else if(c.x<"
+"b[1]){return half4(mix(a[0],a[1],(c.x-b[0])/(b[1]-b[0])));}else if(c.x<b[2]"
+"){return half4(mix(a[1],a[2],(c.x-b[1])/(b[2]-b[1])));}else if(c.x<b[3]){return"
+" half4(mix(a[2],a[3],(c.x-b[2])/(b[3]-b[2])));}else{return half4(a[3]);}}$pure"
+" half4 $r(float4[8]a,float[8]b,float2 c){if(c.y<0.){return half4(0.);}else if"
+"(c.x<b[4]){if(c.x<b[2]){if(c.x<=b[0]){return half4(a[0]);}else if(c.x<b[1])"
+"{return half4(mix(a[0],a[1],(c.x-b[0])/(b[1]-b[0])));}else{return half4(mix"
+"(a[1],a[2],(c.x-b[1])/(b[2]-b[1])));}}else{if(c.x<b[3]){return half4(mix(a["
+"2],a[3],(c.x-b[2])/(b[3]-b[2])));}else{return half4(mix(a[3],a[4],(c.x-b[3]"
+")/(b[4]-b[3])));}}}else{if(c.x<b[6]){if(c.x<b[5]){return half4(mix(a[4],a[5"
+"],(c.x-b[4])/(b[5]-b[4])));}else{return half4(mix(a[5],a[6],(c.x-b[5])/(b[6"
+"]-b[5])));}}else{if(c.x<b[7]){return half4(mix(a[6],a[7],(c.x-b[6])/(b[7]-b"
+"[6])));}else{return half4(a[7]);}}}}half4 $s(sampler2D a,int b,float2 c){if"
+"(c.y<0.){return half4(0.);}else if(c.x==0.){return sampleLod(a,float2(0.,.25"
+"),0.);}else if(c.x==1.){return sampleLod(a,float2(1.,.25),0.);}else{int f=0"
+";int g=b;for(int h=1;h<b;h<<=1){int i=(f+g)/2;float j=(float(i)+.5)/float(b"
+");float2 k=float2(sampleLod(a,float2(j,.75),0.).xy);float l=ldexp(k.x,int(k"
+".y));if(c.x<l){g=i;}else{f=i;}}float h=(float(f)+.5)/float(b);float i=(float"
+"(f+1)+.5)/float(b);half4 j=sampleLod(a,float2(h,.25),0.);half4 k=sampleLod("
+"a,float2(i,.25),0.);float2 l=float2(sampleLod(a,float2(h,.75),0.).xy);float"
+" m=ldexp(l.x,int(l.y));l=float2(sampleLod(a,float2(i,.75),0.).xy);float n=ldexp"
+"(l.x,int(l.y));return half4(mix(float4(j),float4(k),(c.x-m)/(n-m)));}}$pure"
+" float2 $t(float2 a,float2 b,float2 c){c-=a;float2 d=b-a;float e=dot(c,d)/dot"
+"(d,d);return float2(e,1.);}$pure float2 $u(float2 a,float b,float2 c){float"
+" d=distance(c,a)/b;return float2(d,1.);}$pure float2 $v(float2 a,float b,float"
+" c,float2 d){d-=a;float e=sk_Caps.atan2ImplementedAsAtanYOverX?2.*atan(-d.y"
+",length(d)-d.x):atan(-d.y,-d.x);float f=((e*.159154937+.5)+b)*c;return float2"
+"(f,1.);}$pure float3x3 $w(float2 a,float2 b){return float3x3(0.,-1.,0.,1.,0."
+",0.,0.,0.,1.)*inverse(float3x3(b.y-a.y,a.x-b.x,0.,b.x-a.x,b.y-a.y,0.,a.x,a."
+"y,1.));}$pure float2 $x(float2 a,float2 b,float c,float d,float2 e){const float"
+" f=.000244140625;float g=distance(a,b);float h=d-c;bool i=g<f;bool j=abs(h)"
+"<f;if(i){if(j){return float2(0.,-1.);}float k=1./h;float l=sign(h);float m="
+"c/h;float2 n=(e-a)*k;float o=length(n)*l-m;return float2(o,1.);}else if(j){"
+"float3x3 k=$w(a,b);float l=c/g;float m=l*l;float2 n=(k*float3(e,1.)).xy;float"
+" o=m-n.y*n.y;if(o<0.){return float2(0.,-1.);}o=n.x+sqrt(o);return float2(o,"
+"1.);}else{float k=c/(c-d);bool l=abs(k-1.)<f;if(l){float2 m=a;a=b;b=m;k=0.;"
+"}float2 m=a*(1.-k)+b*k;float3x3 n=$w(m,b);float o=abs(1.-k);float p=o;float"
+" q=abs(d-c)/g;bool r=abs(q-1.)<f;if(r){o*=.5;p*=.5;}else{o*=q/(q*q-1.);p/=sqrt"
+"(abs(q*q-1.));}n=float3x3(o,0.,0.,0.,p,0.,0.,0.,1.)*n;float2 s=(n*float3(e,"
+"1.)).xy;float u=1./q;float v=sign(1.-k);bool w=!r&&q>1.;float x=-1.;if(r){x"
+"=dot(s,s)/s.x;}else if(w){x=length(s)-s.x*u;}else{float y=s.x*s.x-s.y*s.y;if"
+"(y>=0.){if(l||v<0.){x=-sqrt(y)-s.x*u;}else{x=sqrt(y)-s.x*u;}}}if(!w&&x<0.){"
+"return float2(0.,-1.);}float y=k+v*x;if(l){y=1.-y;}return float2(y,1.);}}$pure"
+" half4 sk_linear_grad_4_shader(float2 a,float4[4]b,float[4]c,float2 d,float2"
+" e,int f,int g,int h){float2 i=$t(d,e,a);i=$p(f,i);half4 j=$q(b,c,i);return"
+" $interpolated_to_rgb_unpremul(j,g,h);}$pure half4 sk_linear_grad_8_shader("
+"float2 a,float4[8]b,float[8]c,float2 d,float2 e,int f,int g,int h){float2 i"
+"=$t(d,e,a);i=$p(f,i);half4 j=$r(b,c,i);return $interpolated_to_rgb_unpremul"
+"(j,g,h);}$pure half4 sk_linear_grad_tex_shader(float2 a,float2 b,float2 c,int"
+" d,int e,int f,int g,sampler2D h){float2 i=$t(b,c,a);i=$p(e,i);half4 j=$s(h"
+",d,i);return $interpolated_to_rgb_unpremul(j,f,g);}$pure half4 sk_radial_grad_4_shader"
+"(float2 a,float4[4]b,float[4]c,float2 d,float e,int f,int g,int h){float2 i"
+"=$u(d,e,a);i=$p(f,i);half4 j=$q(b,c,i);return $interpolated_to_rgb_unpremul"
+"(j,g,h);}$pure half4 sk_radial_grad_8_shader(float2 a,float4[8]b,float[8]c,"
+"float2 d,float e,int f,int g,int h){float2 i=$u(d,e,a);i=$p(f,i);half4 j=$r"
+"(b,c,i);return $interpolated_to_rgb_unpremul(j,g,h);}$pure half4 sk_radial_grad_tex_shader"
+"(float2 a,float2 b,float c,int d,int e,int f,int g,sampler2D h){float2 i=$u"
+"(b,c,a);i=$p(e,i);half4 j=$s(h,d,i);return $interpolated_to_rgb_unpremul(j,"
+"f,g);}$pure half4 sk_sweep_grad_4_shader(float2 a,float4[4]b,float[4]c,float2"
+" d,float e,float f,int g,int h,int i){float2 j=$v(d,e,f,a);j=$p(g,j);half4 k"
+"=$q(b,c,j);return $interpolated_to_rgb_unpremul(k,h,i);}$pure half4 sk_sweep_grad_8_shader"
+"(float2 a,float4[8]b,float[8]c,float2 d,float e,float f,int g,int h,int i){"
+"float2 j=$v(d,e,f,a);j=$p(g,j);half4 k=$r(b,c,j);return $interpolated_to_rgb_unpremul"
+"(k,h,i);}$pure half4 sk_sweep_grad_tex_shader(float2 a,float2 b,float c,float"
+" d,int e,int f,int g,int h,sampler2D i){float2 j=$v(b,c,d,a);j=$p(f,j);half4"
+" k=$s(i,e,j);return $interpolated_to_rgb_unpremul(k,g,h);}$pure half4 sk_conical_grad_4_shader"
+"(float2 a,float4[4]b,float[4]c,float2 d,float2 e,float f,float g,int h,int i"
+",int j){float2 k=$x(d,e,f,g,a);k=$p(h,k);half4 l=$q(b,c,k);return $interpolated_to_rgb_unpremul"
+"(l,i,j);}$pure half4 sk_conical_grad_8_shader(float2 a,float4[8]b,float[8]c"
+",float2 d,float2 e,float f,float g,int h,int i,int j){float2 k=$x(d,e,f,g,a"
+");k=$p(h,k);half4 l=$r(b,c,k);return $interpolated_to_rgb_unpremul(l,i,j);}"
+"$pure half4 sk_conical_grad_tex_shader(float2 a,float2 b,float2 c,float d,float"
+" e,int f,int g,int h,int i,sampler2D j){float2 k=$x(b,c,d,e,a);k=$p(g,k);half4"
+" l=$s(j,f,k);return $interpolated_to_rgb_unpremul(l,h,i);}$pure half4 sk_matrix_colorfilter"
+"(half4 a,float4x4 b,float4 c,int d){if(bool(d)){a=$rgb_to_hsl(a.xyz,a.w);}else"
+"{a=unpremul(a);}half4 e=half4(b*float4(a)+c);if(bool(d)){e=$hsl_to_rgb(e.xyz"
+",e.w);}else{e=saturate(e);e.xyz*=e.w;}return e;}$pure half4 noise_helper(half2"
+" a,half2 b,int c,sampler2D d){half4 f;f.xy=floor(a);f.zw=f.xy+half2(1.);if("
+"bool(c)){if(f.x>=b.x){f.x-=b.x;}if(f.y>=b.y){f.y-=b.y;}if(f.z>=b.x){f.z-=b."
+"x;}if(f.w>=b.y){f.w-=b.y;}}half g=sample(d,float2(half2(f.x*.00390625,.5)))"
+".x;half h=sample(d,float2(half2(f.z*.00390625,.5))).x;half2 i=half2(g,h);i="
+"floor(i*half2(255.)+half2(.5))*half2(.003921569);half4 k=256.*i.xyxy+f.yyww"
+";k*=half4(.00390625);return k;}$pure half4 noise_function(half2 a,half4 b,sampler2D"
+" c){half2 d=fract(a);half2 e=(d*d)*(half2(3.)-2.*d);const half f=.00390625;"
+"half4 g;for(int h=0;h<4;h++){half i=(half(h)+.5)*.25;half4 j=sample(c,float2"
+"(half2(b.x,i)));half4 k=sample(c,float2(half2(b.y,i)));half4 l=sample(c,float2"
+"(half2(b.w,i)));half4 m=sample(c,float2(half2(b.z,i)));half2 n;half2 o=d;n."
+"x=dot((j.yw+j.xz*f)*2.-half2(1.),o);o.x-=1.;n.y=dot((k.yw+k.xz*f)*2.-half2("
+"1.),o);half2 p;p.x=mix(n.x,n.y,e.x);o.y-=1.;n.y=dot((l.yw+l.xz*f)*2.-half2("
+"1.),o);o.x+=1.;n.x=dot((m.yw+m.xz*f)*2.-half2(1.),o);p.y=mix(n.x,n.y,e.x);g"
+"[h]=mix(p.x,p.y,e.y);}return g;}$pure half4 perlin_noise_shader(float2 a,float2"
+" b,float2 c,int d,int e,int f,sampler2D g,sampler2D h){half2 k=half2(floor("
+"a)*b);half4 l=half4(0.);half2 m=half2(c);half n=1.;for(int o=0;o<e;++o){half4"
+" p=noise_helper(k,m,f,g);half4 q=noise_function(k,p,h);if(d!=0){q=abs(q);}q"
+"*=n;l+=q;k*=half2(2.);n*=.5;m*=half2(2.);}if(d==0){l=l*half4(.5)+half4(.5);"
+"}l=saturate(l);return half4(l.xyz*l.www,l.w);}$pure half4 sk_blend(int a,half4"
+" b,half4 c){switch(a){case 0:{return blend_clear(b,c);}case 1:{return blend_src"
+"(b,c);}case 2:{return blend_dst(b,c);}case 3:{return blend_porter_duff(half4"
+"(1.,0.,0.,-1.),b,c);}case 4:{return blend_porter_duff(half4(0.,1.,-1.,0.),b"
+",c);}case 5:{return blend_porter_duff(half4(0.,0.,1.,0.),b,c);}case 6:{return"
+" blend_porter_duff(half4(0.,0.,0.,1.),b,c);}case 7:{return blend_porter_duff"
+"(half4(0.,0.,-1.,0.),b,c);}case 8:{return blend_porter_duff(half4(0.,0.,0.,"
+"-1.),b,c);}case 9:{return blend_porter_duff(half4(0.,0.,1.,-1.),b,c);}case 10"
+":{return blend_porter_duff(half4(0.,0.,-1.,1.),b,c);}case 11:{return blend_porter_duff"
+"(half4(0.,0.,-1.,-1.),b,c);}case 12:{return blend_porter_duff(half4(1.,1.,0."
+",0.),b,c);}case 13:{return blend_modulate(b,c);}case 14:{return blend_screen"
+"(b,c);}case 15:{return blend_overlay(0.,b,c);}case 16:{return blend_darken("
+"1.,b,c);}case 17:{return blend_darken(-1.,b,c);}case 18:{return blend_color_dodge"
+"(b,c);}case 19:{return blend_color_burn(b,c);}case 20:{return blend_overlay"
+"(1.,b,c);}case 21:{return blend_soft_light(b,c);}case 22:{return blend_difference"
+"(b,c);}case 23:{return blend_exclusion(b,c);}case 24:{return blend_multiply"
+"(b,c);}case 25:{return blend_hslc(half2(0.,1.),b,c);}case 26:{return blend_hslc"
+"(half2(1.),b,c);}case 27:{return blend_hslc(half2(0.),b,c);}case 28:{return"
+" blend_hslc(half2(1.,0.),b,c);}default:return half4(0.);}}$pure half4 sk_blend_shader"
+"(int a,half4 b,half4 c){return sk_blend(a,c,b);}$pure half4 porter_duff_blend_shader"
+"(half4 a,half4 b,half4 c){return blend_porter_duff(a,c,b);}$pure half4 sk_blend_colorfilter"
+"(half4 a,int b,float4 c){return sk_blend(b,half4(c),a);}$pure half4 sk_table_colorfilter"
+"(half4 a,sampler2D b){half4 c=(unpremul(a)*255.)*.00390625+.001953125;half4"
+" d=half4(sample(b,float2(half2(c.x,.375))).x,sample(b,float2(half2(c.y,.625"
+"))).x,sample(b,float2(half2(c.z,.875))).x,1.);return d*sample(b,float2(half2"
+"(c.w,.125))).x;}$pure half4 sk_gaussian_colorfilter(half4 a){half b=1.-a.w;"
+"b=exp((-b*b)*4.)-.018;return half4(b);}$pure float inverse_grad_len(float2 a"
+",float2x2 b){float2 c=a*b;return inversesqrt(dot(c,c));}$pure float2 elliptical_distance"
+"(float2 a,float2 b,float c,float2x2 d){float2 e=1./(b*b+c*c);float2 g=e*a;float"
+" h=inverse_grad_len(g,d);float i=(.5*h)*(dot(a,g)-1.);float j=((b.x*c)*e.x)"
+"*h;return float2(j-i,j+i);}void corner_distance(inout float2 a,float2x2 b,float2"
+" c,float2 d,float2 e,float2 f){float2 g=f-d;if(g.x>0.&&g.y>0.){if(f.x>0.&&f"
+".y>0.||c.x>0.&&c.y<0.){float2 h=elliptical_distance(g*e,f,c.x,b);if(f.x-c.x"
+"<=0.){h.y=1.;}else{h.y*=-1.;}a=min(a,h);}else if(c.y==0.){float h=((c.x-g.x"
+")-g.y)*inverse_grad_len(e,b);a.x=min(a.x,h);}}}void corner_distances(inout float2"
+" a,float2x2 b,float2 c,float4 e,float4 f,float4 g){corner_distance(a,b,c,e."
+"xy,float2(-1.),float2(f.x,g.x));corner_distance(a,b,c,e.zy,float2(1.,-1.),float2"
+"(f.y,g.y));corner_distance(a,b,c,e.zw,float2(1.),float2(f.z,g.z));corner_distance"
+"(a,b,c,e.xw,float2(-1.,1.),float2(f.w,g.w));}";
diff --git a/gfx/skia/skia/src/sksl/generated/sksl_graphite_frag.unoptimized.sksl b/gfx/skia/skia/src/sksl/generated/sksl_graphite_frag.unoptimized.sksl
new file mode 100644
index 0000000000..3be44de02c
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/generated/sksl_graphite_frag.unoptimized.sksl
@@ -0,0 +1,314 @@
+static constexpr char SKSL_MINIFIED_sksl_graphite_frag[] =
+"const int $kTileModeClamp=0;const int $kTileModeRepeat=1;const int $kTileModeMirror"
+"=2;const int $kTileModeDecal=3;const int $kReadSwizzleNormalRGBA=0;const int"
+" $kReadSwizzleRGB1=1;const int $kReadSwizzleRRRR=2;const int $kReadSwizzleRRR1"
+"=3;const int $kReadSwizzleBGRA=4;const int $kFilterModeNearest=0;const int $kFilterModeLinear"
+"=1;const int $kTFTypeSRGB=1;const int $kTFTypePQ=2;const int $kTFTypeHLG=3;"
+"const int $kTFTypeHLGinv=4;const int $kColorSpaceXformFlagUnpremul=1;const int"
+" $kColorSpaceXformFlagLinearize=2;const int $kColorSpaceXformFlagGamutTransform"
+"=4;const int $kColorSpaceXformFlagEncode=8;const int $kColorSpaceXformFlagPremul"
+"=16;$pure half4 sk_error(){return half4(1.,0.,1.,1.);}$pure half4 sk_passthrough"
+"(half4 color){return color;}$pure half4 sk_solid_shader(float4 colorParam){"
+"return half4(colorParam);}$pure half4 $apply_swizzle(int swizzleType,half4 color"
+"){half4 resultantColor=color;switch(swizzleType){case 0:break;case 1:resultantColor"
+"=half4(color.xyz,1.);break;case 2:resultantColor=color.xxxx;break;case 3:resultantColor"
+"=half4(color.xxx,1.);break;case 4:resultantColor=color.zyxw;break;}return resultantColor"
+";}$pure half $apply_xfer_fn(int kind,half x,half[7]cs){half G=cs[0];half A="
+"cs[1];half B=cs[2];half C=cs[3];half D=cs[4];half E=cs[5];half F=cs[6];half"
+" s=sign(x);x=abs(x);switch(kind){case 1:x=x<D?C*x+F:pow(A*x+B,G)+E;break;case"
+" 2:x=pow(max(A+B*pow(x,C),0.)/(D+E*pow(x,C)),F);break;case 3:x=x*A<=1.?pow("
+"x*A,B):exp((x-E)*C)+D;x*=F+1.;break;case 4:x/=F+1.;x=x<=1.?A*pow(x,B):C*log"
+"(x-D)+E;break;}return s*x;}$pure half4 sk_color_space_transform(half4 color"
+",int flags,int srcKind,half[7]srcCoeffs,half3x3 gamutTransform,int dstKind,"
+"half[7]dstCoeffs){if(bool(flags&$kColorSpaceXformFlagUnpremul)){color=unpremul"
+"(color);}if(bool(flags&$kColorSpaceXformFlagLinearize)){color.x=$apply_xfer_fn"
+"(srcKind,color.x,srcCoeffs);color.y=$apply_xfer_fn(srcKind,color.y,srcCoeffs"
+");color.z=$apply_xfer_fn(srcKind,color.z,srcCoeffs);}if(bool(flags&$kColorSpaceXformFlagGamutTransform"
+")){color.xyz=gamutTransform*color.xyz;}if(bool(flags&$kColorSpaceXformFlagEncode"
+")){color.x=$apply_xfer_fn(dstKind,color.x,dstCoeffs);color.y=$apply_xfer_fn"
+"(dstKind,color.y,dstCoeffs);color.z=$apply_xfer_fn(dstKind,color.z,dstCoeffs"
+");}if(bool(flags&$kColorSpaceXformFlagPremul)){color.xyz*=color.w;}return color"
+";}$pure float $tile(int tileMode,float f,float low,float high){switch(tileMode"
+"){case 0:return clamp(f,low,high);case 1:{float length=high-low;return mod("
+"f-low,length)+low;}case 2:{float length=high-low;float length2=2.*length;float"
+" tmp=mod(f-low,length2);return mix(tmp,length2-tmp,step(length,tmp))+low;}default"
+":return f;}}$pure half4 $sample_image(float2 pos,float2 imgSize,float4 subset"
+",int tileModeX,int tileModeY,int filterMode,int readSwizzle,sampler2D s){if"
+"(tileModeX==$kTileModeDecal&&filterMode==$kFilterModeNearest){float snappedX"
+"=floor(pos.x)+.5;if(snappedX<subset.x||snappedX>subset.z){return half4(0.);"
+"}}if(tileModeY==$kTileModeDecal&&filterMode==$kFilterModeNearest){float snappedY"
+"=floor(pos.y)+.5;if(snappedY<subset.y||snappedY>subset.w){return half4(0.);"
+"}}pos.x=$tile(tileModeX,pos.x,subset.x,subset.z);pos.y=$tile(tileModeY,pos."
+"y,subset.y,subset.w);float4 insetClamp;if(filterMode==$kFilterModeNearest){"
+"insetClamp=float4(floor(subset.xy)+.5,ceil(subset.zw)-.5);}else{insetClamp="
+"float4(subset.xy+.5,subset.zw-.5);}float2 clampedPos=clamp(pos,insetClamp.xy"
+",insetClamp.zw);half4 color=sample(s,clampedPos/imgSize);color=$apply_swizzle"
+"(readSwizzle,color);if(filterMode==$kFilterModeLinear){half2 error=half2(pos"
+"-clampedPos);half2 absError=abs(error);bool sampleExtraX=tileModeX==$kTileModeRepeat"
+";bool sampleExtraY=tileModeY==$kTileModeRepeat;if(sampleExtraX||sampleExtraY"
+"){float extraCoordX;float extraCoordY;half4 extraColorX;half4 extraColorY;if"
+"(sampleExtraX){extraCoordX=error.x>0.?insetClamp.x:insetClamp.z;extraColorX"
+"=sample(s,float2(extraCoordX,clampedPos.y)/imgSize);extraColorX=$apply_swizzle"
+"(readSwizzle,extraColorX);}if(sampleExtraY){extraCoordY=error.y>0.?insetClamp"
+".y:insetClamp.w;extraColorY=sample(s,float2(clampedPos.x,extraCoordY)/imgSize"
+");extraColorY=$apply_swizzle(readSwizzle,extraColorY);}if(sampleExtraX&&sampleExtraY"
+"){half4 extraColorXY=sample(s,float2(extraCoordX,extraCoordY)/imgSize);extraColorXY"
+"=$apply_swizzle(readSwizzle,extraColorXY);color=mix(mix(color,extraColorX,absError"
+".x),mix(extraColorY,extraColorXY,absError.x),absError.y);}else if(sampleExtraX"
+"){color=mix(color,extraColorX,absError.x);}else if(sampleExtraY){color=mix("
+"color,extraColorY,absError.y);}}if(tileModeX==$kTileModeDecal){color*=max(1."
+"-absError.x,0.);}if(tileModeY==$kTileModeDecal){color*=max(1.-absError.y,0."
+");}}return color;}$pure half4 $cubic_filter_image(float2 pos,float2 imgSize"
+",float4 subset,int tileModeX,int tileModeY,float4x4 coeffs,int readSwizzle,"
+"sampler2D s){float2 f=fract(pos-.5);pos-=1.5;pos=floor(pos)+.5;float4 wx=coeffs"
+"*float4(1.,f.x,f.x*f.x,(f.x*f.x)*f.x);float4 wy=coeffs*float4(1.,f.y,f.y*f."
+"y,(f.y*f.y)*f.y);float4 color=float4(0.);for(int y=0;y<4;++y){float4 rowColor"
+"=float4(0.);for(int x=0;x<4;++x){rowColor+=wx[x]*float4($sample_image(pos+float2"
+"(float(x),float(y)),imgSize,subset,tileModeX,tileModeY,$kFilterModeNearest,"
+"readSwizzle,s));}color+=wy[y]*rowColor;}return half4(color);}$pure half4 sk_image_shader"
+"(float2 coords,float2 imgSize,float4 subset,int tileModeX,int tileModeY,int"
+" filterMode,int useCubic,float4x4 cubicCoeffs,int readSwizzle,int csXformFlags"
+",int csXformSrcKind,half[7]csXformSrcCoeffs,half3x3 csXformGamutTransform,int"
+" csXformDstKind,half[7]csXformDstCoeffs,sampler2D s){half4 sampleColor=useCubic"
+"!=0?$cubic_filter_image(coords,imgSize,subset,tileModeX,tileModeY,cubicCoeffs"
+",readSwizzle,s):$sample_image(coords,imgSize,subset,tileModeX,tileModeY,filterMode"
+",readSwizzle,s);return sk_color_space_transform(sampleColor,csXformFlags,csXformSrcKind"
+",csXformSrcCoeffs,csXformGamutTransform,csXformDstKind,csXformDstCoeffs);}$pure"
+" half4 sk_dither_shader(half4 colorIn,float2 coords,float range,sampler2D lut"
+"){const float kImgSize=8.;half2 lutCoords=half2(half(coords.x*.125),half(coords"
+".y*.125));half value=sample(lut,float2(lutCoords)).x-.5;return half4(half3("
+"clamp(float3(colorIn.xyz)+float(value)*range,0.,float(colorIn.w))),colorIn."
+"w);}$pure float2 $tile_grad(int tileMode,float2 t){switch(tileMode){case 0:"
+"t.x=clamp(t.x,0.,1.);break;case 1:t.x=fract(t.x);break;case 2:{float t_1=t."
+"x-1.;t.x=(t_1-2.*floor(t_1*.5))-1.;if(sk_Caps.mustDoOpBetweenFloorAndAbs){t"
+".x=clamp(t.x,-1.,1.);}t.x=abs(t.x);break;}case 3:if(t.x<0.||t.x>1.){return float2"
+"(0.,-1.);}break;}return t;}$pure half4 $colorize_grad_4(float4[4]colorsParam"
+",float[4]offsetsParam,float2 t){if(t.y<0.){return half4(0.);}else if(t.x<=offsetsParam"
+"[0]){return half4(colorsParam[0]);}else if(t.x<offsetsParam[1]){return half4"
+"(mix(colorsParam[0],colorsParam[1],(t.x-offsetsParam[0])/(offsetsParam[1]-offsetsParam"
+"[0])));}else if(t.x<offsetsParam[2]){return half4(mix(colorsParam[1],colorsParam"
+"[2],(t.x-offsetsParam[1])/(offsetsParam[2]-offsetsParam[1])));}else if(t.x<"
+"offsetsParam[3]){return half4(mix(colorsParam[2],colorsParam[3],(t.x-offsetsParam"
+"[2])/(offsetsParam[3]-offsetsParam[2])));}else{return half4(colorsParam[3])"
+";}}$pure half4 $colorize_grad_8(float4[8]colorsParam,float[8]offsetsParam,float2"
+" t){if(t.y<0.){return half4(0.);}else if(t.x<offsetsParam[4]){if(t.x<offsetsParam"
+"[2]){if(t.x<=offsetsParam[0]){return half4(colorsParam[0]);}else if(t.x<offsetsParam"
+"[1]){return half4(mix(colorsParam[0],colorsParam[1],(t.x-offsetsParam[0])/("
+"offsetsParam[1]-offsetsParam[0])));}else{return half4(mix(colorsParam[1],colorsParam"
+"[2],(t.x-offsetsParam[1])/(offsetsParam[2]-offsetsParam[1])));}}else{if(t.x"
+"<offsetsParam[3]){return half4(mix(colorsParam[2],colorsParam[3],(t.x-offsetsParam"
+"[2])/(offsetsParam[3]-offsetsParam[2])));}else{return half4(mix(colorsParam"
+"[3],colorsParam[4],(t.x-offsetsParam[3])/(offsetsParam[4]-offsetsParam[3]))"
+");}}}else{if(t.x<offsetsParam[6]){if(t.x<offsetsParam[5]){return half4(mix("
+"colorsParam[4],colorsParam[5],(t.x-offsetsParam[4])/(offsetsParam[5]-offsetsParam"
+"[4])));}else{return half4(mix(colorsParam[5],colorsParam[6],(t.x-offsetsParam"
+"[5])/(offsetsParam[6]-offsetsParam[5])));}}else{if(t.x<offsetsParam[7]){return"
+" half4(mix(colorsParam[6],colorsParam[7],(t.x-offsetsParam[6])/(offsetsParam"
+"[7]-offsetsParam[6])));}else{return half4(colorsParam[7]);}}}}half4 $colorize_grad_tex"
+"(sampler2D colorsAndOffsetsSampler,int numStops,float2 t){const float kColorCoord"
+"=.25;const float kOffsetCoord=.75;if(t.y<0.){return half4(0.);}else if(t.x=="
+"0.){return sampleLod(colorsAndOffsetsSampler,float2(0.,.25),0.);}else if(t."
+"x==1.){return sampleLod(colorsAndOffsetsSampler,float2(1.,.25),0.);}else{int"
+" low=0;int high=numStops;for(int loop=1;loop<numStops;loop<<=1){int mid=(low"
+"+high)/2;float midFlt=(float(mid)+.5)/float(numStops);float2 tmp=float2(sampleLod"
+"(colorsAndOffsetsSampler,float2(midFlt,.75),0.).xy);float offset=ldexp(tmp."
+"x,int(tmp.y));if(t.x<offset){high=mid;}else{low=mid;}}float lowFlt=(float(low"
+")+.5)/float(numStops);float highFlt=(float(low+1)+.5)/float(numStops);half4"
+" color0=sampleLod(colorsAndOffsetsSampler,float2(lowFlt,.25),0.);half4 color1"
+"=sampleLod(colorsAndOffsetsSampler,float2(highFlt,.25),0.);float2 tmp=float2"
+"(sampleLod(colorsAndOffsetsSampler,float2(lowFlt,.75),0.).xy);float offset0"
+"=ldexp(tmp.x,int(tmp.y));tmp=float2(sampleLod(colorsAndOffsetsSampler,float2"
+"(highFlt,.75),0.).xy);float offset1=ldexp(tmp.x,int(tmp.y));return half4(mix"
+"(float4(color0),float4(color1),(t.x-offset0)/(offset1-offset0)));}}$pure float2"
+" $linear_grad_layout(float2 point0Param,float2 point1Param,float2 pos){pos-="
+"point0Param;float2 delta=point1Param-point0Param;float t=dot(pos,delta)/dot"
+"(delta,delta);return float2(t,1.);}$pure float2 $radial_grad_layout(float2 centerParam"
+",float radiusParam,float2 pos){float t=distance(pos,centerParam)/radiusParam"
+";return float2(t,1.);}$pure float2 $sweep_grad_layout(float2 centerParam,float"
+" biasParam,float scaleParam,float2 pos){pos-=centerParam;float angle=sk_Caps"
+".atan2ImplementedAsAtanYOverX?2.*atan(-pos.y,length(pos)-pos.x):atan(-pos.y"
+",-pos.x);float t=((angle*.159154937+.5)+biasParam)*scaleParam;return float2"
+"(t,1.);}$pure float3x3 $map_to_unit_x(float2 p0,float2 p1){return float3x3("
+"0.,-1.,0.,1.,0.,0.,0.,0.,1.)*inverse(float3x3(p1.y-p0.y,p0.x-p1.x,0.,p1.x-p0"
+".x,p1.y-p0.y,0.,p0.x,p0.y,1.));}$pure float2 $conical_grad_layout(float2 point0Param"
+",float2 point1Param,float radius0Param,float radius1Param,float2 pos){const"
+" float SK_ScalarNearlyZero=.000244140625;float dCenter=distance(point0Param"
+",point1Param);float dRadius=radius1Param-radius0Param;bool radial=dCenter<SK_ScalarNearlyZero"
+";bool strip=abs(dRadius)<SK_ScalarNearlyZero;if(radial){if(strip){return float2"
+"(0.,-1.);}float scale=1./dRadius;float scaleSign=sign(dRadius);float bias=radius0Param"
+"/dRadius;float2 pt=(pos-point0Param)*scale;float t=length(pt)*scaleSign-bias"
+";return float2(t,1.);}else if(strip){float3x3 transform=$map_to_unit_x(point0Param"
+",point1Param);float r=radius0Param/dCenter;float r_2=r*r;float2 pt=(transform"
+"*float3(pos,1.)).xy;float t=r_2-pt.y*pt.y;if(t<0.){return float2(0.,-1.);}t"
+"=pt.x+sqrt(t);return float2(t,1.);}else{float f=radius0Param/(radius0Param-"
+"radius1Param);bool isSwapped=abs(f-1.)<SK_ScalarNearlyZero;if(isSwapped){float2"
+" tmpPt=point0Param;point0Param=point1Param;point1Param=tmpPt;f=0.;}float2 Cf"
+"=point0Param*(1.-f)+point1Param*f;float3x3 transform=$map_to_unit_x(Cf,point1Param"
+");float scaleX=abs(1.-f);float scaleY=scaleX;float r1=abs(radius1Param-radius0Param"
+")/dCenter;bool isFocalOnCircle=abs(r1-1.)<SK_ScalarNearlyZero;if(isFocalOnCircle"
+"){scaleX*=.5;scaleY*=.5;}else{scaleX*=r1/(r1*r1-1.);scaleY/=sqrt(abs(r1*r1-"
+"1.));}transform=float3x3(scaleX,0.,0.,0.,scaleY,0.,0.,0.,1.)*transform;float2"
+" pt=(transform*float3(pos,1.)).xy;float invR1=1./r1;float dRadiusSign=sign("
+"1.-f);bool isWellBehaved=!isFocalOnCircle&&r1>1.;float x_t=-1.;if(isFocalOnCircle"
+"){x_t=dot(pt,pt)/pt.x;}else if(isWellBehaved){x_t=length(pt)-pt.x*invR1;}else"
+"{float temp=pt.x*pt.x-pt.y*pt.y;if(temp>=0.){if(isSwapped||dRadiusSign<0.){"
+"x_t=-sqrt(temp)-pt.x*invR1;}else{x_t=sqrt(temp)-pt.x*invR1;}}}if(!isWellBehaved"
+"&&x_t<0.){return float2(0.,-1.);}float t=f+dRadiusSign*x_t;if(isSwapped){t="
+"1.-t;}return float2(t,1.);}}$pure half4 sk_linear_grad_4_shader(float2 coords"
+",float4[4]colorsParam,float[4]offsetsParam,float2 point0Param,float2 point1Param"
+",int tileMode,int colorSpace,int doUnpremul){float2 t=$linear_grad_layout(point0Param"
+",point1Param,coords);t=$tile_grad(tileMode,t);half4 color=$colorize_grad_4("
+"colorsParam,offsetsParam,t);return $interpolated_to_rgb_unpremul(color,colorSpace"
+",doUnpremul);}$pure half4 sk_linear_grad_8_shader(float2 coords,float4[8]colorsParam"
+",float[8]offsetsParam,float2 point0Param,float2 point1Param,int tileMode,int"
+" colorSpace,int doUnpremul){float2 t=$linear_grad_layout(point0Param,point1Param"
+",coords);t=$tile_grad(tileMode,t);half4 color=$colorize_grad_8(colorsParam,"
+"offsetsParam,t);return $interpolated_to_rgb_unpremul(color,colorSpace,doUnpremul"
+");}$pure half4 sk_linear_grad_tex_shader(float2 coords,float2 point0Param,float2"
+" point1Param,int numStops,int tileMode,int colorSpace,int doUnpremul,sampler2D"
+" colorAndOffsetSampler){float2 t=$linear_grad_layout(point0Param,point1Param"
+",coords);t=$tile_grad(tileMode,t);half4 color=$colorize_grad_tex(colorAndOffsetSampler"
+",numStops,t);return $interpolated_to_rgb_unpremul(color,colorSpace,doUnpremul"
+");}$pure half4 sk_radial_grad_4_shader(float2 coords,float4[4]colorsParam,float"
+"[4]offsetsParam,float2 centerParam,float radiusParam,int tileMode,int colorSpace"
+",int doUnpremul){float2 t=$radial_grad_layout(centerParam,radiusParam,coords"
+");t=$tile_grad(tileMode,t);half4 color=$colorize_grad_4(colorsParam,offsetsParam"
+",t);return $interpolated_to_rgb_unpremul(color,colorSpace,doUnpremul);}$pure"
+" half4 sk_radial_grad_8_shader(float2 coords,float4[8]colorsParam,float[8]offsetsParam"
+",float2 centerParam,float radiusParam,int tileMode,int colorSpace,int doUnpremul"
+"){float2 t=$radial_grad_layout(centerParam,radiusParam,coords);t=$tile_grad"
+"(tileMode,t);half4 color=$colorize_grad_8(colorsParam,offsetsParam,t);return"
+" $interpolated_to_rgb_unpremul(color,colorSpace,doUnpremul);}$pure half4 sk_radial_grad_tex_shader"
+"(float2 coords,float2 centerParam,float radiusParam,int numStops,int tileMode"
+",int colorSpace,int doUnpremul,sampler2D colorAndOffsetSampler){float2 t=$radial_grad_layout"
+"(centerParam,radiusParam,coords);t=$tile_grad(tileMode,t);half4 color=$colorize_grad_tex"
+"(colorAndOffsetSampler,numStops,t);return $interpolated_to_rgb_unpremul(color"
+",colorSpace,doUnpremul);}$pure half4 sk_sweep_grad_4_shader(float2 coords,float4"
+"[4]colorsParam,float[4]offsetsParam,float2 centerParam,float biasParam,float"
+" scaleParam,int tileMode,int colorSpace,int doUnpremul){float2 t=$sweep_grad_layout"
+"(centerParam,biasParam,scaleParam,coords);t=$tile_grad(tileMode,t);half4 color"
+"=$colorize_grad_4(colorsParam,offsetsParam,t);return $interpolated_to_rgb_unpremul"
+"(color,colorSpace,doUnpremul);}$pure half4 sk_sweep_grad_8_shader(float2 coords"
+",float4[8]colorsParam,float[8]offsetsParam,float2 centerParam,float biasParam"
+",float scaleParam,int tileMode,int colorSpace,int doUnpremul){float2 t=$sweep_grad_layout"
+"(centerParam,biasParam,scaleParam,coords);t=$tile_grad(tileMode,t);half4 color"
+"=$colorize_grad_8(colorsParam,offsetsParam,t);return $interpolated_to_rgb_unpremul"
+"(color,colorSpace,doUnpremul);}$pure half4 sk_sweep_grad_tex_shader(float2 coords"
+",float2 centerParam,float biasParam,float scaleParam,int numStops,int tileMode"
+",int colorSpace,int doUnpremul,sampler2D colorAndOffsetSampler){float2 t=$sweep_grad_layout"
+"(centerParam,biasParam,scaleParam,coords);t=$tile_grad(tileMode,t);half4 color"
+"=$colorize_grad_tex(colorAndOffsetSampler,numStops,t);return $interpolated_to_rgb_unpremul"
+"(color,colorSpace,doUnpremul);}$pure half4 sk_conical_grad_4_shader(float2 coords"
+",float4[4]colorsParam,float[4]offsetsParam,float2 point0Param,float2 point1Param"
+",float radius0Param,float radius1Param,int tileMode,int colorSpace,int doUnpremul"
+"){float2 t=$conical_grad_layout(point0Param,point1Param,radius0Param,radius1Param"
+",coords);t=$tile_grad(tileMode,t);half4 color=$colorize_grad_4(colorsParam,"
+"offsetsParam,t);return $interpolated_to_rgb_unpremul(color,colorSpace,doUnpremul"
+");}$pure half4 sk_conical_grad_8_shader(float2 coords,float4[8]colorsParam,"
+"float[8]offsetsParam,float2 point0Param,float2 point1Param,float radius0Param"
+",float radius1Param,int tileMode,int colorSpace,int doUnpremul){float2 t=$conical_grad_layout"
+"(point0Param,point1Param,radius0Param,radius1Param,coords);t=$tile_grad(tileMode"
+",t);half4 color=$colorize_grad_8(colorsParam,offsetsParam,t);return $interpolated_to_rgb_unpremul"
+"(color,colorSpace,doUnpremul);}$pure half4 sk_conical_grad_tex_shader(float2"
+" coords,float2 point0Param,float2 point1Param,float radius0Param,float radius1Param"
+",int numStops,int tileMode,int colorSpace,int doUnpremul,sampler2D colorAndOffsetSampler"
+"){float2 t=$conical_grad_layout(point0Param,point1Param,radius0Param,radius1Param"
+",coords);t=$tile_grad(tileMode,t);half4 color=$colorize_grad_tex(colorAndOffsetSampler"
+",numStops,t);return $interpolated_to_rgb_unpremul(color,colorSpace,doUnpremul"
+");}$pure half4 sk_matrix_colorfilter(half4 colorIn,float4x4 m,float4 v,int inHSLA"
+"){if(bool(inHSLA)){colorIn=$rgb_to_hsl(colorIn.xyz,colorIn.w);}else{colorIn"
+"=unpremul(colorIn);}half4 colorOut=half4(m*float4(colorIn)+v);if(bool(inHSLA"
+")){colorOut=$hsl_to_rgb(colorOut.xyz,colorOut.w);}else{colorOut=saturate(colorOut"
+");colorOut.xyz*=colorOut.w;}return colorOut;}$pure half4 noise_helper(half2"
+" noiseVec,half2 stitchData,int stitching,sampler2D permutationSampler){const"
+" half kBlockSize=256.;half4 floorVal;floorVal.xy=floor(noiseVec);floorVal.zw"
+"=floorVal.xy+half2(1.);if(bool(stitching)){if(floorVal.x>=stitchData.x){floorVal"
+".x-=stitchData.x;}{}if(floorVal.y>=stitchData.y){floorVal.y-=stitchData.y;}"
+"{}if(floorVal.z>=stitchData.x){floorVal.z-=stitchData.x;}{}if(floorVal.w>=stitchData"
+".y){floorVal.w-=stitchData.y;}{}}half sampleX=sample(permutationSampler,float2"
+"(half2(floorVal.x*.00390625,.5))).x;half sampleY=sample(permutationSampler,"
+"float2(half2(floorVal.z*.00390625,.5))).x;half2 latticeIdx=half2(sampleX,sampleY"
+");const half kInv255=.003921569;latticeIdx=floor(latticeIdx*half2(255.)+half2"
+"(.5))*half2(.003921569);half4 noiseXCoords=kBlockSize*latticeIdx.xyxy+floorVal"
+".yyww;noiseXCoords*=half4(.00390625);return noiseXCoords;}$pure half4 noise_function"
+"(half2 noiseVec,half4 noiseXCoords,sampler2D noiseSampler){half2 fractVal=fract"
+"(noiseVec);half2 noiseSmooth=(fractVal*fractVal)*(half2(3.)-2.*fractVal);const"
+" half kInv256=.00390625;half4 result;for(int channel=0;channel<4;channel++)"
+"{half chanCoord=(half(channel)+.5)*.25;half4 sampleA=sample(noiseSampler,float2"
+"(half2(noiseXCoords.x,chanCoord)));half4 sampleB=sample(noiseSampler,float2"
+"(half2(noiseXCoords.y,chanCoord)));half4 sampleC=sample(noiseSampler,float2"
+"(half2(noiseXCoords.w,chanCoord)));half4 sampleD=sample(noiseSampler,float2"
+"(half2(noiseXCoords.z,chanCoord)));half2 uv;half2 tmpFractVal=fractVal;uv.x"
+"=dot((sampleA.yw+sampleA.xz*kInv256)*2.-half2(1.),tmpFractVal);tmpFractVal."
+"x-=1.;uv.y=dot((sampleB.yw+sampleB.xz*kInv256)*2.-half2(1.),tmpFractVal);half2"
+" ab;ab.x=mix(uv.x,uv.y,noiseSmooth.x);tmpFractVal.y-=1.;uv.y=dot((sampleC.yw"
+"+sampleC.xz*kInv256)*2.-half2(1.),tmpFractVal);tmpFractVal.x+=1.;uv.x=dot(("
+"sampleD.yw+sampleD.xz*kInv256)*2.-half2(1.),tmpFractVal);ab.y=mix(uv.x,uv.y"
+",noiseSmooth.x);result[channel]=mix(ab.x,ab.y,noiseSmooth.y);}return result"
+";}$pure half4 perlin_noise_shader(float2 coords,float2 baseFrequency,float2"
+" stitchDataIn,int noiseType,int numOctaves,int stitching,sampler2D permutationSampler"
+",sampler2D noiseSampler){const int kFractalNoise_Type=0;const int kTurbulence_Type"
+"=1;half2 noiseVec=half2(floor(coords)*baseFrequency);half4 color=half4(0.);"
+"half2 stitchData=half2(stitchDataIn);half ratio=1.;for(int octave=0;octave<"
+"numOctaves;++octave){half4 noiseXCoords=noise_helper(noiseVec,stitchData,stitching"
+",permutationSampler);half4 tmp=noise_function(noiseVec,noiseXCoords,noiseSampler"
+");if(noiseType!=kFractalNoise_Type){tmp=abs(tmp);}tmp*=ratio;color+=tmp;noiseVec"
+"*=half2(2.);ratio*=.5;stitchData*=half2(2.);}if(noiseType==kFractalNoise_Type"
+"){color=color*half4(.5)+half4(.5);}color=saturate(color);return half4(color"
+".xyz*color.www,color.w);}$pure half4 sk_blend(int blendMode,half4 src,half4"
+" dst){const int kClear=0;const int kSrc=1;const int kDst=2;const int kSrcOver"
+"=3;const int kDstOver=4;const int kSrcIn=5;const int kDstIn=6;const int kSrcOut"
+"=7;const int kDstOut=8;const int kSrcATop=9;const int kDstATop=10;const int"
+" kXor=11;const int kPlus=12;const int kModulate=13;const int kScreen=14;const"
+" int kOverlay=15;const int kDarken=16;const int kLighten=17;const int kColorDodge"
+"=18;const int kColorBurn=19;const int kHardLight=20;const int kSoftLight=21"
+";const int kDifference=22;const int kExclusion=23;const int kMultiply=24;const"
+" int kHue=25;const int kSaturation=26;const int kColor=27;const int kLuminosity"
+"=28;switch(blendMode){case 0:{return blend_clear(src,dst);}case 1:{return blend_src"
+"(src,dst);}case 2:{return blend_dst(src,dst);}case 3:{return blend_porter_duff"
+"(half4(1.,0.,0.,-1.),src,dst);}case 4:{return blend_porter_duff(half4(0.,1."
+",-1.,0.),src,dst);}case 5:{return blend_porter_duff(half4(0.,0.,1.,0.),src,"
+"dst);}case 6:{return blend_porter_duff(half4(0.,0.,0.,1.),src,dst);}case 7:"
+"{return blend_porter_duff(half4(0.,0.,-1.,0.),src,dst);}case 8:{return blend_porter_duff"
+"(half4(0.,0.,0.,-1.),src,dst);}case 9:{return blend_porter_duff(half4(0.,0."
+",1.,-1.),src,dst);}case 10:{return blend_porter_duff(half4(0.,0.,-1.,1.),src"
+",dst);}case 11:{return blend_porter_duff(half4(0.,0.,-1.,-1.),src,dst);}case"
+" 12:{return blend_porter_duff(half4(1.,1.,0.,0.),src,dst);}case 13:{return blend_modulate"
+"(src,dst);}case 14:{return blend_screen(src,dst);}case 15:{return blend_overlay"
+"(0.,src,dst);}case 16:{return blend_darken(1.,src,dst);}case 17:{return blend_darken"
+"(-1.,src,dst);}case 18:{return blend_color_dodge(src,dst);}case 19:{return blend_color_burn"
+"(src,dst);}case 20:{return blend_overlay(1.,src,dst);}case 21:{return blend_soft_light"
+"(src,dst);}case 22:{return blend_difference(src,dst);}case 23:{return blend_exclusion"
+"(src,dst);}case 24:{return blend_multiply(src,dst);}case 25:{return blend_hslc"
+"(half2(0.,1.),src,dst);}case 26:{return blend_hslc(half2(1.),src,dst);}case"
+" 27:{return blend_hslc(half2(0.),src,dst);}case 28:{return blend_hslc(half2"
+"(1.,0.),src,dst);}default:return half4(0.);}}$pure half4 sk_blend_shader(int"
+" blendMode,half4 dst,half4 src){return sk_blend(blendMode,src,dst);}$pure half4"
+" porter_duff_blend_shader(half4 blendOp,half4 dst,half4 src){return blend_porter_duff"
+"(blendOp,src,dst);}$pure half4 sk_blend_colorfilter(half4 dstColor,int blendMode"
+",float4 srcColor){return sk_blend(blendMode,half4(srcColor),dstColor);}$pure"
+" half4 sk_table_colorfilter(half4 inColor,sampler2D s){half4 coords=(unpremul"
+"(inColor)*255.)*.00390625+.001953125;half4 color=half4(sample(s,float2(half2"
+"(coords.x,.375))).x,sample(s,float2(half2(coords.y,.625))).x,sample(s,float2"
+"(half2(coords.z,.875))).x,1.);return color*sample(s,float2(half2(coords.w,.125"
+"))).x;}$pure half4 sk_gaussian_colorfilter(half4 inColor){half factor=1.-inColor"
+".w;factor=exp((-factor*factor)*4.)-.018;return half4(factor);}$pure float inverse_grad_len"
+"(float2 localGrad,float2x2 jacobian){float2 devGrad=localGrad*jacobian;return"
+" inversesqrt(dot(devGrad,devGrad));}$pure float2 elliptical_distance(float2"
+" uv,float2 radii,float strokeRadius,float2x2 jacobian){float2 invR2=1./(radii"
+"*radii+strokeRadius*strokeRadius);float2 normUV=invR2*uv;float invGradLength"
+"=inverse_grad_len(normUV,jacobian);float f=(.5*invGradLength)*(dot(uv,normUV"
+")-1.);float width=((radii.x*strokeRadius)*invR2.x)*invGradLength;return float2"
+"(width-f,width+f);}void corner_distance(inout float2 dist,float2x2 jacobian"
+",float2 strokeParams,float2 cornerEdgeDist,float2 xyFlip,float2 radii){float2"
+" uv=radii-cornerEdgeDist;if(uv.x>0.&&uv.y>0.){if(radii.x>0.&&radii.y>0.||strokeParams"
+".x>0.&&strokeParams.y<0.){float2 d=elliptical_distance(uv*xyFlip,radii,strokeParams"
+".x,jacobian);if(radii.x-strokeParams.x<=0.){d.y=1.;}else{d.y*=-1.;}dist=min"
+"(dist,d);}else if(strokeParams.y==0.){float bevelDist=((strokeParams.x-uv.x"
+")-uv.y)*inverse_grad_len(xyFlip,jacobian);dist.x=min(dist.x,bevelDist);}}}void"
+" corner_distances(inout float2 d,float2x2 J,float2 stroke,float4 edgeDists,"
+"float4 xRadii,float4 yRadii){corner_distance(d,J,stroke,edgeDists.xy,float2"
+"(-1.),float2(xRadii.x,yRadii.x));corner_distance(d,J,stroke,edgeDists.zy,float2"
+"(1.,-1.),float2(xRadii.y,yRadii.y));corner_distance(d,J,stroke,edgeDists.zw"
+",float2(1.),float2(xRadii.z,yRadii.z));corner_distance(d,J,stroke,edgeDists"
+".xw,float2(-1.,1.),float2(xRadii.w,yRadii.w));}";
diff --git a/gfx/skia/skia/src/sksl/generated/sksl_graphite_vert.minified.sksl b/gfx/skia/skia/src/sksl/generated/sksl_graphite_vert.minified.sksl
new file mode 100644
index 0000000000..99bded4db6
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/generated/sksl_graphite_vert.minified.sksl
@@ -0,0 +1,64 @@
+static constexpr char SKSL_MINIFIED_sksl_graphite_vert[] =
+"$pure float curve_type_using_inf_support(float4 a){if(isinf(a.z)){return 2."
+";}if(isinf(a.w)){return 1.;}return 0.;}$pure bool $k(float a){return a!=0.;"
+"}$pure bool $l(float a){return a==2.;}$pure float $m(float2 a,float2 b,float2"
+" c,float2 d,float2x2 e){float2 f=e*(fma(float2(-2.),b,c)+a);float2 g=e*(fma"
+"(float2(-2.),c,d)+b);return max(dot(f,f),dot(g,g));}$pure float $n(float2 a"
+",float2 b,float2 c,float2 d,float2x2 e){float f=$m(a,b,c,d,e);return max(ceil"
+"(sqrt(3.*sqrt(f))),1.);}$pure float $o(float2 a,float2 b,float2 c,float2 d,"
+"float2x2 e){float f=$m(a,b,c,d,e);return ceil(log2(max(9.*f,1.))*.25);}$pure"
+" float $p(float2 a,float2 b,float2 c,float d){float2 e=(min(min(a,b),c)+max"
+"(max(a,b),c))*.5;a-=e;b-=e;c-=e;float f=sqrt(max(max(dot(a,a),dot(b,b)),dot"
+"(c,c)));float2 g=fma(float2(-2.*d),b,a)+c;float h=abs(fma(-2.,d,2.));float i"
+"=max(0.,fma(f,4.,-1.));float j=length(g)*4.+i*h;float k=4.*min(d,1.);return"
+" j/k;}$pure float $q(float2 a,float2 b,float2 c,float d){float e=$p(a,b,c,d"
+");return max(ceil(sqrt(e)),1.);}$pure float $r(float2 a,float2 b,float2 c,float"
+" d){float e=$p(a,b,c,d);return ceil(log2(max(e,1.))*.5);}$pure float2 $s(float2"
+" c,float2 d){float2 e=c-d;if(e==float2(0.)){return float2(0.);}else{float f"
+"=1./max(abs(e.x),abs(e.y));return normalize(f*e);}}$pure float $t(float2 c,"
+"float2 d){return clamp(dot(c,d),-1.,1.);}$pure float $u(float a,float b){float"
+" c=fma(a,.5,.5);return(c*b)*b>=1.?inversesqrt(c):sqrt(c);}$pure float $v(float"
+" a){return.5/acos(max(1.-.25/a,-1.));}$pure float $w(float c,float d,float e"
+"){return fma(d-c,e,c);}$pure float2 $w(float2 c,float2 d,float e){return fma"
+"(d-c,float2(e),c);}$pure float4 $w(float4 c,float4 d,float4 e){return fma(d"
+"-c,e,c);}$pure float2 tessellate_filled_curve(float2x2 a,float b,float c,float4"
+" d,float4 e,float f){float2 g;if($l(f)){g=b!=0.?d.zw:(c!=0.?e.xy:d.xy);}else"
+"{float2 h=d.xy;float2 i=d.zw;float2 j=e.xy;float2 k=e.zw;float l=-1.;float m"
+";if($k(f)){l=k.x;m=$r(a*h,a*i,a*j,l);i*=l;k=j;}else{m=$o(h,i,j,k,a);}if(b>m"
+"){c=floor(ldexp(c,int(m-b)));b=m;}float n=floor(.5+ldexp(c,int(5.-b)));if(0."
+"<n&&n<32.){float o=n*.03125;float2 p=mix(h,i,o);float2 q=mix(i,j,o);float2 r"
+"=mix(j,k,o);float2 s=mix(p,q,o);float2 t=mix(q,r,o);float2 x=mix(s,t,o);float"
+" y=mix(1.,l,o);float z=(l+1.)-y;float A=mix(y,z,o);g=l<0.?x:s/A;}else{g=n=="
+"0.?h:k;}}return g;}$pure float4 tessellate_stroked_curve(float a,float b,float2x2"
+" c,float2 d,float e,float4 f,float4 g,float2 h,float2 i,float j){float2 k=f"
+".xy;float2 l=f.zw;float2 m=g.xy;float2 n=g.zw;float o=-1.;if($k(j)){o=n.x;n"
+"=m;}float p;if(o<0.){if(k==l&&m==n){p=1.;}else{p=$n(k,l,m,n,c);}}else{p=$q("
+"c*k,c*l,c*m,o);}float q=i.x;float r=i.y;bool s=i.x==0.;float t;if(s){t=$v(1."
+");q=.5;}else{t=$v(e*i.x);}if(s){k=c*k;l=c*l;m=c*m;n=c*n;h=c*h;}float2 u=$s("
+"k==l?(l==m?n:m):l,k);float2 v=$s(n,n==m?(m==l?k:l):m);if(u==float2(0.)){u=float2"
+"(1.,0.);v=float2(-1.,0.);}float x;if(r>=0.){x=(sign(r)+1.)+2.;}else{float2 y"
+"=$s(k,h);float z=acos($t(y,u));float A=max(ceil(z*t),1.);x=A+2.;x=min(x,b-2."
+");}float y=cross_length_2d(m-k,n-l);float z=abs(a)-x;if(z<0.){v=u;if(h!=k){"
+"u=$s(k,h);}y=cross_length_2d(u,v);}float A=$t(u,v);float B=acos(A);if(y<0.)"
+"{B=-B;}float C;float D=sign(a);if(z<0.){C=x-2.;p=1.;n=(m=(l=k));z+=C+1.;float"
+" E=.01;bool F=abs(y)*inversesqrt(dot(u,u)*dot(v,v))<E;if(!F||dot(u,v)<0.){if"
+"(z>=0.){D=y<0.?min(D,0.):max(D,0.);}}z=max(z,0.);}else{float E=(b-x)-1.;C=max"
+"(ceil(abs(B)*t),1.);C=min(C,E);p=min(p,(E-C)+1.);}float E=B/C;float F=(p+C)"
+"-1.;bool G=z>=F;if(z>F){D=0.;}if(abs(a)==2.&&r>0.){D*=$u(A,r);}float2 H;float2"
+" I;if(z!=0.&&!G){float2 J;float2 K;float2 L=l-k;float2 M=n-k;if(o>=0.){L*=o"
+";K=.5*M-L;J=(o-1.)*M;l*=o;}else{float2 N=m-l;K=N-L;J=fma(float2(-3.),N,M);}"
+"float2 N=K*(p*2.);float2 O=L*(p*p);float P=0.;float Q=min(p-1.,z);float R=-"
+"abs(E);float S=(1.+z)*abs(E);for(int U=4;U>=0;--U){float V=P+exp2(float(U))"
+";if(V<=Q){float2 W=fma(float2(V),J,N);W=fma(float2(V),W,O);float X=dot(normalize"
+"(W),u);float Y=fma(V,R,S);Y=min(Y,3.14159274);if(X>=cos(Y)){P=V;}}}float U="
+"P/p;float V=z-P;float W=acos(clamp(u.x,-1.,1.));W=u.y>=0.?W:-W;float X=fma("
+"V,E,W);H=float2(cos(X),sin(X));float2 Y=float2(-H.y,H.x);float Z=dot(Y,J);float"
+" aa=dot(Y,K);float ac=dot(Y,L);float ad=max(aa*aa-Z*ac,0.);float ae=sqrt(ad"
+");if(aa>0.){ae=-ae;}ae-=aa;float af=(-.5*ae)*Z;float2 ag=abs(fma(ae,ae,af))"
+"<abs(fma(Z,ac,af))?float2(ae,Z):float2(ac,ae);float ah=ag.y!=0.?ag.x/ag.y:0."
+";ah=clamp(ah,0.,1.);if(V==0.){ah=0.;}float ai=max(U,ah);float2 aj=$w(k,l,ai"
+");float2 ak=$w(l,m,ai);float2 al=$w(m,n,ai);float2 am=$w(aj,ak,ai);float2 an"
+"=$w(ak,al,ai);float2 ao=$w(am,an,ai);float ap=$w(1.,o,ai);float aq=(o+1.)-ap"
+";float ar=$w(ap,aq,ai);if(ai!=ah){H=o>=0.?$s(ak*ap,aj*aq):$s(an,am);}I=o>=0."
+"?am/ar:ao;}else{H=z==0.?u:v;I=z==0.?k:n;}float2 J=float2(H.y,-H.x);I+=J*(q*"
+"D);if(s){return float4(I+d,inverse(c)*I);}else{return float4(c*I+d,I);}}";
diff --git a/gfx/skia/skia/src/sksl/generated/sksl_graphite_vert.unoptimized.sksl b/gfx/skia/skia/src/sksl/generated/sksl_graphite_vert.unoptimized.sksl
new file mode 100644
index 0000000000..e2b4f80fc5
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/generated/sksl_graphite_vert.unoptimized.sksl
@@ -0,0 +1,121 @@
+static constexpr char SKSL_MINIFIED_sksl_graphite_vert[] =
+"const float $PI=3.14159274;const float $kCubicCurveType=0.;const float $kConicCurveType"
+"=1.;const float $kTriangularConicCurveType=2.;$pure float curve_type_using_inf_support"
+"(float4 p23){if(isinf(p23.z)){return $kTriangularConicCurveType;}if(isinf(p23"
+".w)){return $kConicCurveType;}return $kCubicCurveType;}$pure bool $is_conic_curve"
+"(float curveType){return curveType!=$kCubicCurveType;}$pure bool $is_triangular_conic_curve"
+"(float curveType){return curveType==$kTriangularConicCurveType;}const float"
+" $kDegree=3.;const float $kPrecision=4.;const float $kLengthTerm=3.;const float"
+" $kLengthTermPow2=9.;$pure float $wangs_formula_max_fdiff_p2(float2 p0,float2"
+" p1,float2 p2,float2 p3,float2x2 matrix){float2 d0=matrix*(fma(float2(-2.),"
+"p1,p2)+p0);float2 d1=matrix*(fma(float2(-2.),p2,p3)+p1);return max(dot(d0,d0"
+"),dot(d1,d1));}$pure float $wangs_formula_cubic(float2 p0,float2 p1,float2 p2"
+",float2 p3,float2x2 matrix){float m=$wangs_formula_max_fdiff_p2(p0,p1,p2,p3"
+",matrix);return max(ceil(sqrt($kLengthTerm*sqrt(m))),1.);}$pure float $wangs_formula_cubic_log2"
+"(float2 p0,float2 p1,float2 p2,float2 p3,float2x2 matrix){float m=$wangs_formula_max_fdiff_p2"
+"(p0,p1,p2,p3,matrix);return ceil(log2(max($kLengthTermPow2*m,1.))*.25);}$pure"
+" float $wangs_formula_conic_p2(float2 p0,float2 p1,float2 p2,float w){float2"
+" C=(min(min(p0,p1),p2)+max(max(p0,p1),p2))*.5;p0-=C;p1-=C;p2-=C;float m=sqrt"
+"(max(max(dot(p0,p0),dot(p1,p1)),dot(p2,p2)));float2 dp=fma(float2(-2.*w),p1"
+",p0)+p2;float dw=abs(fma(-2.,w,2.));float rp_minus_1=max(0.,fma(m,$kPrecision"
+",-1.));float numer=length(dp)*$kPrecision+rp_minus_1*dw;float denom=4.*min("
+"w,1.);return numer/denom;}$pure float $wangs_formula_conic(float2 p0,float2"
+" p1,float2 p2,float w){float n2=$wangs_formula_conic_p2(p0,p1,p2,w);return max"
+"(ceil(sqrt(n2)),1.);}$pure float $wangs_formula_conic_log2(float2 p0,float2"
+" p1,float2 p2,float w){float n2=$wangs_formula_conic_p2(p0,p1,p2,w);return ceil"
+"(log2(max(n2,1.))*.5);}$pure float2 $robust_normalize_diff(float2 a,float2 b"
+"){float2 diff=a-b;if(diff==float2(0.)){return float2(0.);}else{float invMag"
+"=1./max(abs(diff.x),abs(diff.y));return normalize(invMag*diff);}}$pure float"
+" $cosine_between_unit_vectors(float2 a,float2 b){return clamp(dot(a,b),-1.,"
+"1.);}$pure float $miter_extent(float cosTheta,float miterLimit){float x=fma"
+"(cosTheta,.5,.5);return(x*miterLimit)*miterLimit>=1.?inversesqrt(x):sqrt(x)"
+";}$pure float $num_radial_segments_per_radian(float approxDevStrokeRadius){"
+"return.5/acos(max(1.-.25/approxDevStrokeRadius,-1.));}$pure float $unchecked_mix"
+"(float a,float b,float T){return fma(b-a,T,a);}$pure float2 $unchecked_mix("
+"float2 a,float2 b,float T){return fma(b-a,float2(T),a);}$pure float4 $unchecked_mix"
+"(float4 a,float4 b,float4 T){return fma(b-a,T,a);}$pure float2 tessellate_filled_curve"
+"(float2x2 vectorXform,float resolveLevel,float idxInResolveLevel,float4 p01"
+",float4 p23,float curveType){float2 localcoord;if($is_triangular_conic_curve"
+"(curveType)){localcoord=resolveLevel!=0.?p01.zw:(idxInResolveLevel!=0.?p23."
+"xy:p01.xy);}else{float2 p0=p01.xy;float2 p1=p01.zw;float2 p2=p23.xy;float2 p3"
+"=p23.zw;float w=-1.;float maxResolveLevel;if($is_conic_curve(curveType)){w="
+"p3.x;maxResolveLevel=$wangs_formula_conic_log2(vectorXform*p0,vectorXform*p1"
+",vectorXform*p2,w);p1*=w;p3=p2;}else{maxResolveLevel=$wangs_formula_cubic_log2"
+"(p0,p1,p2,p3,vectorXform);}if(resolveLevel>maxResolveLevel){idxInResolveLevel"
+"=floor(ldexp(idxInResolveLevel,int(maxResolveLevel-resolveLevel)));resolveLevel"
+"=maxResolveLevel;}float fixedVertexID=floor(.5+ldexp(idxInResolveLevel,int("
+"5.-resolveLevel)));if(0.<fixedVertexID&&fixedVertexID<32.){float T=fixedVertexID"
+"*.03125;float2 ab=mix(p0,p1,T);float2 bc=mix(p1,p2,T);float2 cd=mix(p2,p3,T"
+");float2 abc=mix(ab,bc,T);float2 bcd=mix(bc,cd,T);float2 abcd=mix(abc,bcd,T"
+");float u=mix(1.,w,T);float v=(w+1.)-u;float uv=mix(u,v,T);localcoord=w<0.?"
+"abcd:abc/uv;}else{localcoord=fixedVertexID==0.?p0:p3;}}return localcoord;}$pure"
+" float4 tessellate_stroked_curve(float edgeID,float maxEdges,float2x2 affineMatrix"
+",float2 translate,float maxScale,float4 p01,float4 p23,float2 lastControlPoint"
+",float2 strokeParams,float curveType){float2 p0=p01.xy;float2 p1=p01.zw;float2"
+" p2=p23.xy;float2 p3=p23.zw;float w=-1.;if($is_conic_curve(curveType)){w=p3"
+".x;p3=p2;}float numParametricSegments;if(w<0.){if(p0==p1&&p2==p3){numParametricSegments"
+"=1.;}else{numParametricSegments=$wangs_formula_cubic(p0,p1,p2,p3,affineMatrix"
+");}}else{numParametricSegments=$wangs_formula_conic(affineMatrix*p0,affineMatrix"
+"*p1,affineMatrix*p2,w);}float strokeRadius=strokeParams.x;float joinType=strokeParams"
+".y;bool isHairline=strokeParams.x==0.;float numRadialSegmentsPerRadian;if(isHairline"
+"){numRadialSegmentsPerRadian=$num_radial_segments_per_radian(1.);strokeRadius"
+"=.5;}else{numRadialSegmentsPerRadian=$num_radial_segments_per_radian(maxScale"
+"*strokeParams.x);}if(isHairline){p0=affineMatrix*p0;p1=affineMatrix*p1;p2=affineMatrix"
+"*p2;p3=affineMatrix*p3;lastControlPoint=affineMatrix*lastControlPoint;}float2"
+" tan0=$robust_normalize_diff(p0==p1?(p1==p2?p3:p2):p1,p0);float2 tan1=$robust_normalize_diff"
+"(p3,p3==p2?(p2==p1?p0:p1):p2);if(tan0==float2(0.)){tan0=float2(1.,0.);tan1="
+"float2(-1.,0.);}float numEdgesInJoin;if(joinType>=0.){numEdgesInJoin=(sign("
+"joinType)+1.)+2.;}else{float2 prevTan=$robust_normalize_diff(p0,lastControlPoint"
+");float joinRads=acos($cosine_between_unit_vectors(prevTan,tan0));float numRadialSegmentsInJoin"
+"=max(ceil(joinRads*numRadialSegmentsPerRadian),1.);numEdgesInJoin=numRadialSegmentsInJoin"
+"+2.;numEdgesInJoin=min(numEdgesInJoin,maxEdges-2.);}float turn=cross_length_2d"
+"(p2-p0,p3-p1);float combinedEdgeID=abs(edgeID)-numEdgesInJoin;if(combinedEdgeID"
+"<0.){tan1=tan0;if(lastControlPoint!=p0){tan0=$robust_normalize_diff(p0,lastControlPoint"
+");}turn=cross_length_2d(tan0,tan1);}float cosTheta=$cosine_between_unit_vectors"
+"(tan0,tan1);float rotation=acos(cosTheta);if(turn<0.){rotation=-rotation;}float"
+" numRadialSegments;float strokeOutset=sign(edgeID);if(combinedEdgeID<0.){numRadialSegments"
+"=numEdgesInJoin-2.;numParametricSegments=1.;p3=(p2=(p1=p0));combinedEdgeID+="
+"numRadialSegments+1.;float sinEpsilon=.01;bool tangentsNearlyParallel=abs(turn"
+")*inversesqrt(dot(tan0,tan0)*dot(tan1,tan1))<sinEpsilon;if(!tangentsNearlyParallel"
+"||dot(tan0,tan1)<0.){if(combinedEdgeID>=0.){strokeOutset=turn<0.?min(strokeOutset"
+",0.):max(strokeOutset,0.);}}combinedEdgeID=max(combinedEdgeID,0.);}else{float"
+" maxCombinedSegments=(maxEdges-numEdgesInJoin)-1.;numRadialSegments=max(ceil"
+"(abs(rotation)*numRadialSegmentsPerRadian),1.);numRadialSegments=min(numRadialSegments"
+",maxCombinedSegments);numParametricSegments=min(numParametricSegments,(maxCombinedSegments"
+"-numRadialSegments)+1.);}float radsPerSegment=rotation/numRadialSegments;float"
+" numCombinedSegments=(numParametricSegments+numRadialSegments)-1.;bool isFinalEdge"
+"=combinedEdgeID>=numCombinedSegments;if(combinedEdgeID>numCombinedSegments)"
+"{strokeOutset=0.;}if(abs(edgeID)==2.&&joinType>0.){strokeOutset*=$miter_extent"
+"(cosTheta,joinType);}float2 tangent;float2 strokeCoord;if(combinedEdgeID!=0."
+"&&!isFinalEdge){float2 A;float2 B;float2 C=p1-p0;float2 D=p3-p0;if(w>=0.){C"
+"*=w;B=.5*D-C;A=(w-1.)*D;p1*=w;}else{float2 E=p2-p1;B=E-C;A=fma(float2(-3.),"
+"E,D);}float2 B_=B*(numParametricSegments*2.);float2 C_=C*(numParametricSegments"
+"*numParametricSegments);float lastParametricEdgeID=0.;float maxParametricEdgeID"
+"=min(numParametricSegments-1.,combinedEdgeID);float negAbsRadsPerSegment=-abs"
+"(radsPerSegment);float maxRotation0=(1.+combinedEdgeID)*abs(radsPerSegment)"
+";for(int exp=4;exp>=0;--exp){float testParametricID=lastParametricEdgeID+exp2"
+"(float(exp));if(testParametricID<=maxParametricEdgeID){float2 testTan=fma(float2"
+"(testParametricID),A,B_);testTan=fma(float2(testParametricID),testTan,C_);float"
+" cosRotation=dot(normalize(testTan),tan0);float maxRotation=fma(testParametricID"
+",negAbsRadsPerSegment,maxRotation0);maxRotation=min(maxRotation,$PI);if(cosRotation"
+">=cos(maxRotation)){lastParametricEdgeID=testParametricID;}}}float parametricT"
+"=lastParametricEdgeID/numParametricSegments;float lastRadialEdgeID=combinedEdgeID"
+"-lastParametricEdgeID;float angle0=acos(clamp(tan0.x,-1.,1.));angle0=tan0.y"
+">=0.?angle0:-angle0;float radialAngle=fma(lastRadialEdgeID,radsPerSegment,angle0"
+");tangent=float2(cos(radialAngle),sin(radialAngle));float2 norm=float2(-tangent"
+".y,tangent.x);float a=dot(norm,A);float b_over_2=dot(norm,B);float c=dot(norm"
+",C);float discr_over_4=max(b_over_2*b_over_2-a*c,0.);float q=sqrt(discr_over_4"
+");if(b_over_2>0.){q=-q;}q-=b_over_2;float _5qa=(-.5*q)*a;float2 root=abs(fma"
+"(q,q,_5qa))<abs(fma(a,c,_5qa))?float2(q,a):float2(c,q);float radialT=root.y"
+"!=0.?root.x/root.y:0.;radialT=clamp(radialT,0.,1.);if(lastRadialEdgeID==0.)"
+"{radialT=0.;}float T=max(parametricT,radialT);float2 ab=$unchecked_mix(p0,p1"
+",T);float2 bc=$unchecked_mix(p1,p2,T);float2 cd=$unchecked_mix(p2,p3,T);float2"
+" abc=$unchecked_mix(ab,bc,T);float2 bcd=$unchecked_mix(bc,cd,T);float2 abcd"
+"=$unchecked_mix(abc,bcd,T);float u=$unchecked_mix(1.,w,T);float v=(w+1.)-u;"
+"float uv=$unchecked_mix(u,v,T);if(T!=radialT){tangent=w>=0.?$robust_normalize_diff"
+"(bc*u,ab*v):$robust_normalize_diff(bcd,abc);}strokeCoord=w>=0.?abc/uv:abcd;"
+"}else{tangent=combinedEdgeID==0.?tan0:tan1;strokeCoord=combinedEdgeID==0.?p0"
+":p3;}float2 ortho=float2(tangent.y,-tangent.x);strokeCoord+=ortho*(strokeRadius"
+"*strokeOutset);if(isHairline){return float4(strokeCoord+translate,inverse(affineMatrix"
+")*strokeCoord);}else{return float4(affineMatrix*strokeCoord+translate,strokeCoord"
+");}}";
diff --git a/gfx/skia/skia/src/sksl/generated/sksl_public.minified.sksl b/gfx/skia/skia/src/sksl/generated/sksl_public.minified.sksl
new file mode 100644
index 0000000000..9e07ced8b6
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/generated/sksl_public.minified.sksl
@@ -0,0 +1,4 @@
+static constexpr char SKSL_MINIFIED_sksl_public[] =
+"$pure half3 toLinearSrgb(half3);$pure half3 fromLinearSrgb(half3);half4 $eval"
+"(float2,shader);half4 $eval(half4,colorFilter);half4 $eval(half4,half4,blender"
+");";
diff --git a/gfx/skia/skia/src/sksl/generated/sksl_public.unoptimized.sksl b/gfx/skia/skia/src/sksl/generated/sksl_public.unoptimized.sksl
new file mode 100644
index 0000000000..d71770d441
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/generated/sksl_public.unoptimized.sksl
@@ -0,0 +1,4 @@
+static constexpr char SKSL_MINIFIED_sksl_public[] =
+"$pure half3 toLinearSrgb(half3 color);$pure half3 fromLinearSrgb(half3 color"
+");half4 $eval(float2 coords,shader s);half4 $eval(half4 color,colorFilter f"
+");half4 $eval(half4 src,half4 dst,blender b);";
diff --git a/gfx/skia/skia/src/sksl/generated/sksl_rt_shader.minified.sksl b/gfx/skia/skia/src/sksl/generated/sksl_rt_shader.minified.sksl
new file mode 100644
index 0000000000..70ca8dacc6
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/generated/sksl_rt_shader.minified.sksl
@@ -0,0 +1,2 @@
+static constexpr char SKSL_MINIFIED_sksl_rt_shader[] =
+"layout(builtin=15)float4 sk_FragCoord;";
diff --git a/gfx/skia/skia/src/sksl/generated/sksl_rt_shader.unoptimized.sksl b/gfx/skia/skia/src/sksl/generated/sksl_rt_shader.unoptimized.sksl
new file mode 100644
index 0000000000..70ca8dacc6
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/generated/sksl_rt_shader.unoptimized.sksl
@@ -0,0 +1,2 @@
+static constexpr char SKSL_MINIFIED_sksl_rt_shader[] =
+"layout(builtin=15)float4 sk_FragCoord;";
diff --git a/gfx/skia/skia/src/sksl/generated/sksl_shared.minified.sksl b/gfx/skia/skia/src/sksl/generated/sksl_shared.minified.sksl
new file mode 100644
index 0000000000..7f2b17c64c
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/generated/sksl_shared.minified.sksl
@@ -0,0 +1,143 @@
+static constexpr char SKSL_MINIFIED_sksl_shared[] =
+"$pure $genType radians($genType);$pure $genHType radians($genHType);$pure $genType"
+" degrees($genType);$pure $genHType degrees($genHType);$pure $genType sin($genType"
+");$pure $genHType sin($genHType);$pure $genType cos($genType);$pure $genHType"
+" cos($genHType);$pure $genType tan($genType);$pure $genHType tan($genHType)"
+";$pure $genType asin($genType);$pure $genHType asin($genHType);$pure $genType"
+" acos($genType);$pure $genHType acos($genHType);$pure $genType atan($genType"
+",$genType);$pure $genHType atan($genHType,$genHType);$pure $genType atan($genType"
+");$pure $genHType atan($genHType);$es3 $pure $genType sinh($genType);$es3 $pure"
+" $genHType sinh($genHType);$es3 $pure $genType cosh($genType);$es3 $pure $genHType"
+" cosh($genHType);$es3 $pure $genType tanh($genType);$es3 $pure $genHType tanh"
+"($genHType);$es3 $pure $genType asinh($genType);$es3 $pure $genHType asinh("
+"$genHType);$es3 $pure $genType acosh($genType);$es3 $pure $genHType acosh($genHType"
+");$es3 $pure $genType atanh($genType);$es3 $pure $genHType atanh($genHType)"
+";$pure $genType pow($genType,$genType);$pure $genHType pow($genHType,$genHType"
+");$pure $genType exp($genType);$pure $genHType exp($genHType);$pure $genType"
+" log($genType);$pure $genHType log($genHType);$pure $genType exp2($genType)"
+";$pure $genHType exp2($genHType);$pure $genType log2($genType);$pure $genHType"
+" log2($genHType);$pure $genType sqrt($genType);$pure $genHType sqrt($genHType"
+");$pure $genType inversesqrt($genType);$pure $genHType inversesqrt($genHType"
+");$pure $genType abs($genType);$pure $genHType abs($genHType);$pure $genType"
+" sign($genType);$pure $genHType sign($genHType);$pure $genType floor($genType"
+");$pure $genHType floor($genHType);$pure $genType ceil($genType);$pure $genHType"
+" ceil($genHType);$pure $genType fract($genType);$pure $genHType fract($genHType"
+");$pure $genType mod($genType,float);$pure $genType mod($genType,$genType);"
+"$pure $genHType mod($genHType,half);$pure $genHType mod($genHType,$genHType"
+");$pure $genType min($genType,$genType);$pure $genType min($genType,float);"
+"$pure $genHType min($genHType,$genHType);$pure $genHType min($genHType,half"
+");$pure $genType max($genType,$genType);$pure $genType max($genType,float);"
+"$pure $genHType max($genHType,$genHType);$pure $genHType max($genHType,half"
+");$pure $genType clamp($genType,$genType,$genType);$pure $genType clamp($genType"
+",float,float);$pure $genHType clamp($genHType,$genHType,$genHType);$pure $genHType"
+" clamp($genHType,half,half);$pure $genType saturate($genType);$pure $genHType"
+" saturate($genHType);$pure $genType mix($genType,$genType,$genType);$pure $genType"
+" mix($genType,$genType,float);$pure $genHType mix($genHType,$genHType,$genHType"
+");$pure $genHType mix($genHType,$genHType,half);$pure $genType step($genType"
+",$genType);$pure $genType step(float,$genType);$pure $genHType step($genHType"
+",$genHType);$pure $genHType step(half,$genHType);$pure $genType smoothstep("
+"$genType,$genType,$genType);$pure $genType smoothstep(float,float,$genType)"
+";$pure $genHType smoothstep($genHType,$genHType,$genHType);$pure $genHType smoothstep"
+"(half,half,$genHType);$es3 $pure $genIType abs($genIType);$es3 $pure $genIType"
+" sign($genIType);$es3 $pure $genIType floatBitsToInt($genType);$es3 $pure $genUType"
+" floatBitsToUint($genType);$es3 $pure $genType intBitsToFloat($genIType);$es3"
+" $pure $genType uintBitsToFloat($genUType);$es3 $pure $genType trunc($genType"
+");$es3 $pure $genHType trunc($genHType);$es3 $pure $genType round($genType)"
+";$es3 $pure $genHType round($genHType);$es3 $pure $genType roundEven($genType"
+");$es3 $pure $genHType roundEven($genHType);$es3 $pure $genIType min($genIType"
+",$genIType);$es3 $pure $genIType min($genIType,int);$es3 $pure $genUType min"
+"($genUType,$genUType);$es3 $pure $genUType min($genUType,uint);$es3 $pure $genIType"
+" max($genIType,$genIType);$es3 $pure $genIType max($genIType,int);$es3 $pure"
+" $genUType max($genUType,$genUType);$es3 $pure $genUType max($genUType,uint"
+");$es3 $pure $genIType clamp($genIType,$genIType,$genIType);$es3 $pure $genIType"
+" clamp($genIType,int,int);$es3 $pure $genUType clamp($genUType,$genUType,$genUType"
+");$es3 $pure $genUType clamp($genUType,uint,uint);$es3 $pure $genType mix($genType"
+",$genType,$genBType);$es3 $pure $genHType mix($genHType,$genHType,$genBType"
+");$es3 $pure $genBType isnan($genType);$es3 $pure $genBType isnan($genHType"
+");$es3 $pure $genBType isinf($genType);$es3 $pure $genBType isinf($genHType"
+");$es3 $pure $genType modf($genType,out $genType);$es3 $pure $genHType modf"
+"($genHType,out $genHType);$es3 $pure uint packUnorm2x16(float2);$es3 $pure float2"
+" unpackUnorm2x16(uint);$pure float length($genType);$pure half length($genHType"
+");$pure float distance($genType,$genType);$pure half distance($genHType,$genHType"
+");$pure float dot($genType,$genType);$pure half dot($genHType,$genHType);$pure"
+" float3 cross(float3,float3);$pure half3 cross(half3,half3);$pure $genType normalize"
+"($genType);$pure $genHType normalize($genHType);$pure $genType faceforward("
+"$genType,$genType,$genType);$pure $genHType faceforward($genHType,$genHType"
+",$genHType);$pure $genType reflect($genType,$genType);$pure $genHType reflect"
+"($genHType,$genHType);$pure $genType refract($genType,$genType,float);$pure"
+" $genHType refract($genHType,$genHType,half);$pure $squareMat matrixCompMult"
+"($squareMat,$squareMat);$pure $squareHMat matrixCompMult($squareHMat,$squareHMat"
+");$es3 $pure $mat matrixCompMult($mat,$mat);$es3 $pure $hmat matrixCompMult"
+"($hmat,$hmat);$pure $squareMat inverse($squareMat);$pure $squareHMat inverse"
+"($squareHMat);$es3 $pure float determinant($squareMat);$es3 $pure half determinant"
+"($squareHMat);$es3 $pure $squareMat transpose($squareMat);$es3 $pure $squareHMat"
+" transpose($squareHMat);$es3 $pure float2x3 transpose(float3x2);$es3 $pure half2x3"
+" transpose(half3x2);$es3 $pure float2x4 transpose(float4x2);$es3 $pure half2x4"
+" transpose(half4x2);$es3 $pure float3x2 transpose(float2x3);$es3 $pure half3x2"
+" transpose(half2x3);$es3 $pure float3x4 transpose(float4x3);$es3 $pure half3x4"
+" transpose(half4x3);$es3 $pure float4x2 transpose(float2x4);$es3 $pure half4x2"
+" transpose(half2x4);$es3 $pure float4x3 transpose(float3x4);$es3 $pure half4x3"
+" transpose(half3x4);$es3 $pure $squareMat outerProduct($vec,$vec);$es3 $pure"
+" $squareHMat outerProduct($hvec,$hvec);$es3 $pure float2x3 outerProduct(float3"
+",float2);$es3 $pure half2x3 outerProduct(half3,half2);$es3 $pure float3x2 outerProduct"
+"(float2,float3);$es3 $pure half3x2 outerProduct(half2,half3);$es3 $pure float2x4"
+" outerProduct(float4,float2);$es3 $pure half2x4 outerProduct(half4,half2);$es3"
+" $pure float4x2 outerProduct(float2,float4);$es3 $pure half4x2 outerProduct"
+"(half2,half4);$es3 $pure float3x4 outerProduct(float4,float3);$es3 $pure half3x4"
+" outerProduct(half4,half3);$es3 $pure float4x3 outerProduct(float3,float4);"
+"$es3 $pure half4x3 outerProduct(half3,half4);$pure $bvec lessThan($vec,$vec"
+");$pure $bvec lessThan($hvec,$hvec);$pure $bvec lessThan($ivec,$ivec);$pure"
+" $bvec lessThan($svec,$svec);$pure $bvec lessThanEqual($vec,$vec);$pure $bvec"
+" lessThanEqual($hvec,$hvec);$pure $bvec lessThanEqual($ivec,$ivec);$pure $bvec"
+" lessThanEqual($svec,$svec);$pure $bvec greaterThan($vec,$vec);$pure $bvec greaterThan"
+"($hvec,$hvec);$pure $bvec greaterThan($ivec,$ivec);$pure $bvec greaterThan("
+"$svec,$svec);$pure $bvec greaterThanEqual($vec,$vec);$pure $bvec greaterThanEqual"
+"($hvec,$hvec);$pure $bvec greaterThanEqual($ivec,$ivec);$pure $bvec greaterThanEqual"
+"($svec,$svec);$pure $bvec equal($vec,$vec);$pure $bvec equal($hvec,$hvec);$pure"
+" $bvec equal($ivec,$ivec);$pure $bvec equal($svec,$svec);$pure $bvec equal("
+"$bvec,$bvec);$pure $bvec notEqual($vec,$vec);$pure $bvec notEqual($hvec,$hvec"
+");$pure $bvec notEqual($ivec,$ivec);$pure $bvec notEqual($svec,$svec);$pure"
+" $bvec notEqual($bvec,$bvec);$es3 $pure $bvec lessThan($usvec,$usvec);$es3 $pure"
+" $bvec lessThan($uvec,$uvec);$es3 $pure $bvec lessThanEqual($uvec,$uvec);$es3"
+" $pure $bvec lessThanEqual($usvec,$usvec);$es3 $pure $bvec greaterThan($uvec"
+",$uvec);$es3 $pure $bvec greaterThan($usvec,$usvec);$es3 $pure $bvec greaterThanEqual"
+"($uvec,$uvec);$es3 $pure $bvec greaterThanEqual($usvec,$usvec);$es3 $pure $bvec"
+" equal($uvec,$uvec);$es3 $pure $bvec equal($usvec,$usvec);$es3 $pure $bvec notEqual"
+"($uvec,$uvec);$es3 $pure $bvec notEqual($usvec,$usvec);$pure bool any($bvec"
+");$pure bool all($bvec);$pure $bvec not($bvec);$es3 $pure $genType dFdx($genType"
+");$es3 $pure $genType dFdy($genType);$es3 $pure $genHType dFdx($genHType);$es3"
+" $pure $genHType dFdy($genHType);$es3 $pure $genType fwidth($genType);$es3 $pure"
+" $genHType fwidth($genHType);$pure half4 unpremul(half4 a){return half4(a.xyz"
+"/max(a.w,.0001),a.w);}$pure float4 unpremul(float4 a){return float4(a.xyz/max"
+"(a.w,.0001),a.w);}$pure half4 $unpremul_polar(half4 a){return half4(a.x,a.yz"
+"/max(a.w,.0001),a.w);}$pure half4 $rgb_to_hsl(half3 b,half d){half4 e=b.y<b"
+".z?half4(b.zy,-1.,.6666667):half4(b.yz,0.,-.333333343);half4 f=b.x<e.x?half4"
+"(e.x,b.x,e.yw):half4(b.x,e.x,e.yz);half h=f.x;half i=h-min(f.y,f.z);half j="
+"h-i*.5;half k=abs(f.w+(f.y-f.z)/(i*6.+.0001));half l=i/((d+.0001)-abs(j*2.-"
+"d));half m=j/(d+.0001);return half4(k,l,m,d);}$pure half3 $hsl_to_rgb(half3"
+" a){half b=(1.-abs(2.*a.z-1.))*a.y;half3 c=a.xxx+half3(0.,.6666667,.333333343"
+");half3 d=saturate(abs(fract(c)*6.-3.)-1.);return(d-.5)*b+a.z;}$pure half4 $hsl_to_rgb"
+"(half3 b,half c){return saturate(half4($hsl_to_rgb(b)*c,c));}$pure half3 $css_lab_to_xyz"
+"(half3 a){half3 d;d.y=(a.x+16.)*.00862069;d.x=a.y*.002+d.y;d.z=d.y-a.z*.005"
+";half3 g=pow(d,half3(3.));half3 h=half3(g.x>.008856452?g.x:(116.*d.x-16.)*.00110705639"
+",a.x>8.000001?g.y:a.x*.00110705639,g.z>.008856452?g.z:(116.*d.z-16.)*.00110705639"
+");return h*half3(.9642956,1.,.825104535);}$pure half3 $a(half3 a){return half3"
+"(a.z,a.y*cos(radians(a.x)),a.y*sin(radians(a.x)));}$pure half3 $css_hcl_to_xyz"
+"(half3 a){return $css_lab_to_xyz($a(a));}$pure half3 $css_oklab_to_linear_srgb"
+"(half3 a){half b=(a.x+.396337777*a.y)+.215803757*a.z;half c=(a.x-.105561346"
+"*a.y)-.06385417*a.z;half d=(a.x-.08948418*a.y)-1.29148555*a.z;half e=(b*b)*"
+"b;half f=(c*c)*c;half g=(d*d)*d;return half3((4.0767417*e-3.3077116*f)+.230969936"
+"*g,(-1.268438*e+2.60975742*f)-.341319382*g,(-.00419608643*e-.7034186*f)+1.70761466"
+"*g);}$pure half3 $css_okhcl_to_linear_srgb(half3 a){return $css_oklab_to_linear_srgb"
+"($a(a));}$pure half3 $css_hsl_to_srgb(half3 b){b.x=mod(b.x,360.);if(b.x<0.)"
+"{b.x+=360.;}b.yz*=.01;half3 c=mod(half3(0.,8.,4.)+b.x*.0333333351,12.);half"
+" d=b.y*min(b.z,1.-b.z);return b.z-d*clamp(min(c-3.,9.-c),-1.,1.);}$pure half3"
+" $css_hwb_to_srgb(half3 a){a.yz*=.01;if(a.y+a.z>=1.){half b=a.y/(a.y+a.z);return"
+" half3(b);}half3 b=$css_hsl_to_srgb(half3(a.x,100.,50.));b*=(1.-a.y)-a.z;b+="
+"a.y;return b;}$pure half4 $interpolated_to_rgb_unpremul(half4 a,int b,int c"
+"){if(bool(c)){switch(b){case 2:;case 3:a=unpremul(a);break;case 4:;case 5:;"
+"case 7:;case 8:a=$unpremul_polar(a);break;}}switch(b){case 2:{a.xyz=$css_lab_to_xyz"
+"(a.xyz);break;}case 3:{a.xyz=$css_oklab_to_linear_srgb(a.xyz);break;}case 4"
+":{a.xyz=$css_hcl_to_xyz(a.xyz);break;}case 5:{a.xyz=$css_okhcl_to_linear_srgb"
+"(a.xyz);break;}case 7:{a.xyz=$css_hsl_to_srgb(a.xyz);break;}case 8:{a.xyz=$css_hwb_to_srgb"
+"(a.xyz);break;}}return a;}";
diff --git a/gfx/skia/skia/src/sksl/generated/sksl_shared.unoptimized.sksl b/gfx/skia/skia/src/sksl/generated/sksl_shared.unoptimized.sksl
new file mode 100644
index 0000000000..050060ed4b
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/generated/sksl_shared.unoptimized.sksl
@@ -0,0 +1,163 @@
+static constexpr char SKSL_MINIFIED_sksl_shared[] =
+"$pure $genType radians($genType degrees);$pure $genHType radians($genHType degrees"
+");$pure $genType degrees($genType radians);$pure $genHType degrees($genHType"
+" radians);$pure $genType sin($genType angle);$pure $genHType sin($genHType angle"
+");$pure $genType cos($genType angle);$pure $genHType cos($genHType angle);$pure"
+" $genType tan($genType angle);$pure $genHType tan($genHType angle);$pure $genType"
+" asin($genType x);$pure $genHType asin($genHType x);$pure $genType acos($genType"
+" x);$pure $genHType acos($genHType x);$pure $genType atan($genType y,$genType"
+" x);$pure $genHType atan($genHType y,$genHType x);$pure $genType atan($genType"
+" y_over_x);$pure $genHType atan($genHType y_over_x);$es3 $pure $genType sinh"
+"($genType x);$es3 $pure $genHType sinh($genHType x);$es3 $pure $genType cosh"
+"($genType x);$es3 $pure $genHType cosh($genHType x);$es3 $pure $genType tanh"
+"($genType x);$es3 $pure $genHType tanh($genHType x);$es3 $pure $genType asinh"
+"($genType x);$es3 $pure $genHType asinh($genHType x);$es3 $pure $genType acosh"
+"($genType x);$es3 $pure $genHType acosh($genHType x);$es3 $pure $genType atanh"
+"($genType x);$es3 $pure $genHType atanh($genHType x);$pure $genType pow($genType"
+" x,$genType y);$pure $genHType pow($genHType x,$genHType y);$pure $genType exp"
+"($genType x);$pure $genHType exp($genHType x);$pure $genType log($genType x"
+");$pure $genHType log($genHType x);$pure $genType exp2($genType x);$pure $genHType"
+" exp2($genHType x);$pure $genType log2($genType x);$pure $genHType log2($genHType"
+" x);$pure $genType sqrt($genType x);$pure $genHType sqrt($genHType x);$pure"
+" $genType inversesqrt($genType x);$pure $genHType inversesqrt($genHType x);"
+"$pure $genType abs($genType x);$pure $genHType abs($genHType x);$pure $genType"
+" sign($genType x);$pure $genHType sign($genHType x);$pure $genType floor($genType"
+" x);$pure $genHType floor($genHType x);$pure $genType ceil($genType x);$pure"
+" $genHType ceil($genHType x);$pure $genType fract($genType x);$pure $genHType"
+" fract($genHType x);$pure $genType mod($genType x,float y);$pure $genType mod"
+"($genType x,$genType y);$pure $genHType mod($genHType x,half y);$pure $genHType"
+" mod($genHType x,$genHType y);$pure $genType min($genType x,$genType y);$pure"
+" $genType min($genType x,float y);$pure $genHType min($genHType x,$genHType"
+" y);$pure $genHType min($genHType x,half y);$pure $genType max($genType x,$genType"
+" y);$pure $genType max($genType x,float y);$pure $genHType max($genHType x,"
+"$genHType y);$pure $genHType max($genHType x,half y);$pure $genType clamp($genType"
+" x,$genType minVal,$genType maxVal);$pure $genType clamp($genType x,float minVal"
+",float maxVal);$pure $genHType clamp($genHType x,$genHType minVal,$genHType"
+" maxVal);$pure $genHType clamp($genHType x,half minVal,half maxVal);$pure $genType"
+" saturate($genType x);$pure $genHType saturate($genHType x);$pure $genType mix"
+"($genType x,$genType y,$genType a);$pure $genType mix($genType x,$genType y"
+",float a);$pure $genHType mix($genHType x,$genHType y,$genHType a);$pure $genHType"
+" mix($genHType x,$genHType y,half a);$pure $genType step($genType edge,$genType"
+" x);$pure $genType step(float edge,$genType x);$pure $genHType step($genHType"
+" edge,$genHType x);$pure $genHType step(half edge,$genHType x);$pure $genType"
+" smoothstep($genType edge0,$genType edge1,$genType x);$pure $genType smoothstep"
+"(float edge0,float edge1,$genType x);$pure $genHType smoothstep($genHType edge0"
+",$genHType edge1,$genHType x);$pure $genHType smoothstep(half edge0,half edge1"
+",$genHType x);$es3 $pure $genIType abs($genIType x);$es3 $pure $genIType sign"
+"($genIType x);$es3 $pure $genIType floatBitsToInt($genType value);$es3 $pure"
+" $genUType floatBitsToUint($genType value);$es3 $pure $genType intBitsToFloat"
+"($genIType value);$es3 $pure $genType uintBitsToFloat($genUType value);$es3"
+" $pure $genType trunc($genType x);$es3 $pure $genHType trunc($genHType x);$es3"
+" $pure $genType round($genType x);$es3 $pure $genHType round($genHType x);$es3"
+" $pure $genType roundEven($genType x);$es3 $pure $genHType roundEven($genHType"
+" x);$es3 $pure $genIType min($genIType x,$genIType y);$es3 $pure $genIType min"
+"($genIType x,int y);$es3 $pure $genUType min($genUType x,$genUType y);$es3 $pure"
+" $genUType min($genUType x,uint y);$es3 $pure $genIType max($genIType x,$genIType"
+" y);$es3 $pure $genIType max($genIType x,int y);$es3 $pure $genUType max($genUType"
+" x,$genUType y);$es3 $pure $genUType max($genUType x,uint y);$es3 $pure $genIType"
+" clamp($genIType x,$genIType minVal,$genIType maxVal);$es3 $pure $genIType clamp"
+"($genIType x,int minVal,int maxVal);$es3 $pure $genUType clamp($genUType x,"
+"$genUType minVal,$genUType maxVal);$es3 $pure $genUType clamp($genUType x,uint"
+" minVal,uint maxVal);$es3 $pure $genType mix($genType x,$genType y,$genBType"
+" a);$es3 $pure $genHType mix($genHType x,$genHType y,$genBType a);$es3 $pure"
+" $genBType isnan($genType x);$es3 $pure $genBType isnan($genHType x);$es3 $pure"
+" $genBType isinf($genType x);$es3 $pure $genBType isinf($genHType x);$es3 $pure"
+" $genType modf($genType x,out $genType i);$es3 $pure $genHType modf($genHType"
+" x,out $genHType i);$es3 $pure uint packUnorm2x16(float2 v);$es3 $pure float2"
+" unpackUnorm2x16(uint p);$pure float length($genType x);$pure half length($genHType"
+" x);$pure float distance($genType p0,$genType p1);$pure half distance($genHType"
+" p0,$genHType p1);$pure float dot($genType x,$genType y);$pure half dot($genHType"
+" x,$genHType y);$pure float3 cross(float3 x,float3 y);$pure half3 cross(half3"
+" x,half3 y);$pure $genType normalize($genType x);$pure $genHType normalize("
+"$genHType x);$pure $genType faceforward($genType N,$genType I,$genType Nref"
+");$pure $genHType faceforward($genHType N,$genHType I,$genHType Nref);$pure"
+" $genType reflect($genType I,$genType N);$pure $genHType reflect($genHType I"
+",$genHType N);$pure $genType refract($genType I,$genType N,float eta);$pure"
+" $genHType refract($genHType I,$genHType N,half eta);$pure $squareMat matrixCompMult"
+"($squareMat x,$squareMat y);$pure $squareHMat matrixCompMult($squareHMat x,"
+"$squareHMat y);$es3 $pure $mat matrixCompMult($mat x,$mat y);$es3 $pure $hmat"
+" matrixCompMult($hmat x,$hmat y);$pure $squareMat inverse($squareMat m);$pure"
+" $squareHMat inverse($squareHMat m);$es3 $pure float determinant($squareMat"
+" m);$es3 $pure half determinant($squareHMat m);$es3 $pure $squareMat transpose"
+"($squareMat m);$es3 $pure $squareHMat transpose($squareHMat m);$es3 $pure float2x3"
+" transpose(float3x2 m);$es3 $pure half2x3 transpose(half3x2 m);$es3 $pure float2x4"
+" transpose(float4x2 m);$es3 $pure half2x4 transpose(half4x2 m);$es3 $pure float3x2"
+" transpose(float2x3 m);$es3 $pure half3x2 transpose(half2x3 m);$es3 $pure float3x4"
+" transpose(float4x3 m);$es3 $pure half3x4 transpose(half4x3 m);$es3 $pure float4x2"
+" transpose(float2x4 m);$es3 $pure half4x2 transpose(half2x4 m);$es3 $pure float4x3"
+" transpose(float3x4 m);$es3 $pure half4x3 transpose(half3x4 m);$es3 $pure $squareMat"
+" outerProduct($vec c,$vec r);$es3 $pure $squareHMat outerProduct($hvec c,$hvec"
+" r);$es3 $pure float2x3 outerProduct(float3 c,float2 r);$es3 $pure half2x3 outerProduct"
+"(half3 c,half2 r);$es3 $pure float3x2 outerProduct(float2 c,float3 r);$es3 $pure"
+" half3x2 outerProduct(half2 c,half3 r);$es3 $pure float2x4 outerProduct(float4"
+" c,float2 r);$es3 $pure half2x4 outerProduct(half4 c,half2 r);$es3 $pure float4x2"
+" outerProduct(float2 c,float4 r);$es3 $pure half4x2 outerProduct(half2 c,half4"
+" r);$es3 $pure float3x4 outerProduct(float4 c,float3 r);$es3 $pure half3x4 outerProduct"
+"(half4 c,half3 r);$es3 $pure float4x3 outerProduct(float3 c,float4 r);$es3 $pure"
+" half4x3 outerProduct(half3 c,half4 r);$pure $bvec lessThan($vec x,$vec y);"
+"$pure $bvec lessThan($hvec x,$hvec y);$pure $bvec lessThan($ivec x,$ivec y)"
+";$pure $bvec lessThan($svec x,$svec y);$pure $bvec lessThanEqual($vec x,$vec"
+" y);$pure $bvec lessThanEqual($hvec x,$hvec y);$pure $bvec lessThanEqual($ivec"
+" x,$ivec y);$pure $bvec lessThanEqual($svec x,$svec y);$pure $bvec greaterThan"
+"($vec x,$vec y);$pure $bvec greaterThan($hvec x,$hvec y);$pure $bvec greaterThan"
+"($ivec x,$ivec y);$pure $bvec greaterThan($svec x,$svec y);$pure $bvec greaterThanEqual"
+"($vec x,$vec y);$pure $bvec greaterThanEqual($hvec x,$hvec y);$pure $bvec greaterThanEqual"
+"($ivec x,$ivec y);$pure $bvec greaterThanEqual($svec x,$svec y);$pure $bvec"
+" equal($vec x,$vec y);$pure $bvec equal($hvec x,$hvec y);$pure $bvec equal("
+"$ivec x,$ivec y);$pure $bvec equal($svec x,$svec y);$pure $bvec equal($bvec"
+" x,$bvec y);$pure $bvec notEqual($vec x,$vec y);$pure $bvec notEqual($hvec x"
+",$hvec y);$pure $bvec notEqual($ivec x,$ivec y);$pure $bvec notEqual($svec x"
+",$svec y);$pure $bvec notEqual($bvec x,$bvec y);$es3 $pure $bvec lessThan($usvec"
+" x,$usvec y);$es3 $pure $bvec lessThan($uvec x,$uvec y);$es3 $pure $bvec lessThanEqual"
+"($uvec x,$uvec y);$es3 $pure $bvec lessThanEqual($usvec x,$usvec y);$es3 $pure"
+" $bvec greaterThan($uvec x,$uvec y);$es3 $pure $bvec greaterThan($usvec x,$usvec"
+" y);$es3 $pure $bvec greaterThanEqual($uvec x,$uvec y);$es3 $pure $bvec greaterThanEqual"
+"($usvec x,$usvec y);$es3 $pure $bvec equal($uvec x,$uvec y);$es3 $pure $bvec"
+" equal($usvec x,$usvec y);$es3 $pure $bvec notEqual($uvec x,$uvec y);$es3 $pure"
+" $bvec notEqual($usvec x,$usvec y);$pure bool any($bvec x);$pure bool all($bvec"
+" x);$pure $bvec not($bvec x);$es3 $pure $genType dFdx($genType p);$es3 $pure"
+" $genType dFdy($genType p);$es3 $pure $genHType dFdx($genHType p);$es3 $pure"
+" $genHType dFdy($genHType p);$es3 $pure $genType fwidth($genType p);$es3 $pure"
+" $genHType fwidth($genHType p);$pure half4 unpremul(half4 color){return half4"
+"(color.xyz/max(color.w,.0001),color.w);}$pure float4 unpremul(float4 color)"
+"{return float4(color.xyz/max(color.w,.0001),color.w);}$export $pure half4 $unpremul_polar"
+"(half4 color){return half4(color.x,color.yz/max(color.w,.0001),color.w);}$export"
+" $pure half4 $rgb_to_hsl(half3 c,half a){half4 p=c.y<c.z?half4(c.zy,-1.,.6666667"
+"):half4(c.yz,0.,-.333333343);half4 q=c.x<p.x?half4(p.x,c.x,p.yw):half4(c.x,"
+"p.x,p.yz);const half kEps=.0001;half pmV=q.x;half pmC=pmV-min(q.y,q.z);half"
+" pmL=pmV-pmC*.5;half H=abs(q.w+(q.y-q.z)/(pmC*6.+kEps));half S=pmC/((a+kEps"
+")-abs(pmL*2.-a));half L=pmL/(a+kEps);return half4(H,S,L,a);}$export $pure half3"
+" $hsl_to_rgb(half3 hsl){half C=(1.-abs(2.*hsl.z-1.))*hsl.y;half3 p=hsl.xxx+"
+"half3(0.,.6666667,.333333343);half3 q=saturate(abs(fract(p)*6.-3.)-1.);return"
+"(q-.5)*C+hsl.z;}$export $pure half4 $hsl_to_rgb(half3 hsl,half a){return saturate"
+"(half4($hsl_to_rgb(hsl)*a,a));}$export $pure half3 $css_lab_to_xyz(half3 lab"
+"){const half k=903.2963;const half e=.008856452;half3 f;f.y=(lab.x+16.)*.00862069"
+";f.x=lab.y*.002+f.y;f.z=f.y-lab.z*.005;half3 f_cubed=pow(f,half3(3.));half3"
+" xyz=half3(f_cubed.x>e?f_cubed.x:(116.*f.x-16.)*.00110705639,lab.x>8.000001"
+"?f_cubed.y:lab.x*.00110705639,f_cubed.z>e?f_cubed.z:(116.*f.z-16.)*.00110705639"
+");const half3 D50=half3(.9642956,1.,.825104535);return xyz*D50;}$pure half3"
+" $css_hcl_to_lab(half3 hcl){return half3(hcl.z,hcl.y*cos(radians(hcl.x)),hcl"
+".y*sin(radians(hcl.x)));}$export $pure half3 $css_hcl_to_xyz(half3 hcl){return"
+" $css_lab_to_xyz($css_hcl_to_lab(hcl));}$export $pure half3 $css_oklab_to_linear_srgb"
+"(half3 oklab){half l_=(oklab.x+.396337777*oklab.y)+.215803757*oklab.z;half m_"
+"=(oklab.x-.105561346*oklab.y)-.06385417*oklab.z;half s_=(oklab.x-.08948418*"
+"oklab.y)-1.29148555*oklab.z;half l=(l_*l_)*l_;half m=(m_*m_)*m_;half s=(s_*"
+"s_)*s_;return half3((4.0767417*l-3.3077116*m)+.230969936*s,(-1.268438*l+2.60975742"
+"*m)-.341319382*s,(-.00419608643*l-.7034186*m)+1.70761466*s);}$export $pure half3"
+" $css_okhcl_to_linear_srgb(half3 okhcl){return $css_oklab_to_linear_srgb($css_hcl_to_lab"
+"(okhcl));}$export $pure half3 $css_hsl_to_srgb(half3 hsl){hsl.x=mod(hsl.x,360."
+");if(hsl.x<0.){hsl.x+=360.;}hsl.yz*=.01;half3 k=mod(half3(0.,8.,4.)+hsl.x*.0333333351"
+",12.);half a=hsl.y*min(hsl.z,1.-hsl.z);return hsl.z-a*clamp(min(k-3.,9.-k),"
+"-1.,1.);}$export $pure half3 $css_hwb_to_srgb(half3 hwb){hwb.yz*=.01;if(hwb"
+".y+hwb.z>=1.){half gray=hwb.y/(hwb.y+hwb.z);return half3(gray);}half3 rgb=$css_hsl_to_srgb"
+"(half3(hwb.x,100.,50.));rgb*=(1.-hwb.y)-hwb.z;rgb+=hwb.y;return rgb;}$export"
+" $pure half4 $interpolated_to_rgb_unpremul(half4 color,int colorSpace,int doUnpremul"
+"){const int kDestination=0;const int kSRGBLinear=1;const int kLab=2;const int"
+" kOKLab=3;const int kLCH=4;const int kOKLCH=5;const int kSRGB=6;const int kHSL"
+"=7;const int kHWB=8;if(bool(doUnpremul)){switch(colorSpace){case 2:;case 3:"
+"color=unpremul(color);break;case 4:;case 5:;case 7:;case 8:color=$unpremul_polar"
+"(color);break;}}switch(colorSpace){case 2:{color.xyz=$css_lab_to_xyz(color."
+"xyz);break;}case 3:{color.xyz=$css_oklab_to_linear_srgb(color.xyz);break;}case"
+" 4:{color.xyz=$css_hcl_to_xyz(color.xyz);break;}case 5:{color.xyz=$css_okhcl_to_linear_srgb"
+"(color.xyz);break;}case 7:{color.xyz=$css_hsl_to_srgb(color.xyz);break;}case"
+" 8:{color.xyz=$css_hwb_to_srgb(color.xyz);break;}}return color;}";
diff --git a/gfx/skia/skia/src/sksl/generated/sksl_vert.minified.sksl b/gfx/skia/skia/src/sksl/generated/sksl_vert.minified.sksl
new file mode 100644
index 0000000000..ce8fd5f9a0
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/generated/sksl_vert.minified.sksl
@@ -0,0 +1,4 @@
+static constexpr char SKSL_MINIFIED_sksl_vert[] =
+"out sk_PerVertex{layout(builtin=0)float4 sk_Position;layout(builtin=1)float"
+" sk_PointSize;};layout(builtin=42)in int sk_VertexID;layout(builtin=43)in int"
+" sk_InstanceID;";
diff --git a/gfx/skia/skia/src/sksl/generated/sksl_vert.unoptimized.sksl b/gfx/skia/skia/src/sksl/generated/sksl_vert.unoptimized.sksl
new file mode 100644
index 0000000000..ce8fd5f9a0
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/generated/sksl_vert.unoptimized.sksl
@@ -0,0 +1,4 @@
+static constexpr char SKSL_MINIFIED_sksl_vert[] =
+"out sk_PerVertex{layout(builtin=0)float4 sk_Position;layout(builtin=1)float"
+" sk_PointSize;};layout(builtin=42)in int sk_VertexID;layout(builtin=43)in int"
+" sk_InstanceID;";
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLBinaryExpression.cpp b/gfx/skia/skia/src/sksl/ir/SkSLBinaryExpression.cpp
new file mode 100644
index 0000000000..fb9d544a40
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLBinaryExpression.cpp
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLBinaryExpression.h"
+
+#include "include/private/SkSLDefines.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLConstantFolder.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/SkSLUtil.h"
+#include "src/sksl/ir/SkSLFieldAccess.h"
+#include "src/sksl/ir/SkSLIndexExpression.h"
+#include "src/sksl/ir/SkSLLiteral.h"
+#include "src/sksl/ir/SkSLSetting.h"
+#include "src/sksl/ir/SkSLSwizzle.h"
+#include "src/sksl/ir/SkSLTernaryExpression.h"
+#include "src/sksl/ir/SkSLType.h"
+#include "src/sksl/ir/SkSLVariableReference.h"
+
+namespace SkSL {
+
+static bool is_low_precision_matrix_vector_multiply(const Expression& left,
+ const Operator& op,
+ const Expression& right,
+ const Type& resultType) {
+ return !resultType.highPrecision() &&
+ op.kind() == Operator::Kind::STAR &&
+ left.type().isMatrix() &&
+ right.type().isVector() &&
+ left.type().rows() == right.type().columns() &&
+ Analysis::IsTrivialExpression(left) &&
+ Analysis::IsTrivialExpression(right);
+}
+
+static std::unique_ptr<Expression> rewrite_matrix_vector_multiply(const Context& context,
+ Position pos,
+ const Expression& left,
+ const Operator& op,
+ const Expression& right,
+ const Type& resultType) {
+ // Rewrite m33 * v3 as (m[0] * v[0] + m[1] * v[1] + m[2] * v[2])
+ std::unique_ptr<Expression> sum;
+ for (int n = 0; n < left.type().rows(); ++n) {
+ // Get mat[N] with an index expression.
+ std::unique_ptr<Expression> matN = IndexExpression::Make(
+ context, pos, left.clone(), Literal::MakeInt(context, left.fPosition, n));
+ // Get vec[N] with a swizzle expression.
+ std::unique_ptr<Expression> vecN = Swizzle::Make(context,
+ left.fPosition.rangeThrough(right.fPosition), right.clone(),
+ ComponentArray{(SkSL::SwizzleComponent::Type)n});
+ // Multiply them together.
+ const Type* matNType = &matN->type();
+ std::unique_ptr<Expression> product =
+ BinaryExpression::Make(context, pos, std::move(matN), op, std::move(vecN),
+ matNType);
+ // Sum all the components together.
+ if (!sum) {
+ sum = std::move(product);
+ } else {
+ sum = BinaryExpression::Make(context,
+ pos,
+ std::move(sum),
+ Operator(Operator::Kind::PLUS),
+ std::move(product),
+ matNType);
+ }
+ }
+
+ return sum;
+}
+
+std::unique_ptr<Expression> BinaryExpression::Convert(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> left,
+ Operator op,
+ std::unique_ptr<Expression> right) {
+ if (!left || !right) {
+ return nullptr;
+ }
+ const Type* rawLeftType = (left->isIntLiteral() && right->type().isInteger())
+ ? &right->type()
+ : &left->type();
+ const Type* rawRightType = (right->isIntLiteral() && left->type().isInteger())
+ ? &left->type()
+ : &right->type();
+
+ bool isAssignment = op.isAssignment();
+ if (isAssignment &&
+ !Analysis::UpdateVariableRefKind(left.get(),
+ op.kind() != Operator::Kind::EQ
+ ? VariableReference::RefKind::kReadWrite
+ : VariableReference::RefKind::kWrite,
+ context.fErrors)) {
+ return nullptr;
+ }
+
+ const Type* leftType;
+ const Type* rightType;
+ const Type* resultType;
+ if (!op.determineBinaryType(context, *rawLeftType, *rawRightType,
+ &leftType, &rightType, &resultType)) {
+ context.fErrors->error(pos, "type mismatch: '" + std::string(op.tightOperatorName()) +
+ "' cannot operate on '" + left->type().displayName() + "', '" +
+ right->type().displayName() + "'");
+ return nullptr;
+ }
+
+ if (isAssignment && (leftType->componentType().isOpaque() || leftType->isOrContainsAtomic())) {
+ context.fErrors->error(pos, "assignments to opaque type '" + left->type().displayName() +
+ "' are not permitted");
+ return nullptr;
+ }
+ if (context.fConfig->strictES2Mode()) {
+ if (!op.isAllowedInStrictES2Mode()) {
+ context.fErrors->error(pos, "operator '" + std::string(op.tightOperatorName()) +
+ "' is not allowed");
+ return nullptr;
+ }
+ if (leftType->isOrContainsArray()) {
+ // Most operators are already rejected on arrays, but GLSL ES 1.0 is very explicit that
+ // the *only* operator allowed on arrays is subscripting (and the rules against
+ // assignment, comparison, and even sequence apply to structs containing arrays as well)
+ context.fErrors->error(pos, "operator '" + std::string(op.tightOperatorName()) +
+ "' can not operate on arrays (or structs containing arrays)");
+ return nullptr;
+ }
+ }
+
+ left = leftType->coerceExpression(std::move(left), context);
+ right = rightType->coerceExpression(std::move(right), context);
+ if (!left || !right) {
+ return nullptr;
+ }
+
+ return BinaryExpression::Make(context, pos, std::move(left), op, std::move(right), resultType);
+}
+
+std::unique_ptr<Expression> BinaryExpression::Make(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> left,
+ Operator op,
+ std::unique_ptr<Expression> right) {
+ // Determine the result type of the binary expression.
+ const Type* leftType;
+ const Type* rightType;
+ const Type* resultType;
+ SkAssertResult(op.determineBinaryType(context, left->type(), right->type(),
+ &leftType, &rightType, &resultType));
+
+ return BinaryExpression::Make(context, pos, std::move(left), op, std::move(right), resultType);
+}
+
+std::unique_ptr<Expression> BinaryExpression::Make(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> left,
+ Operator op,
+ std::unique_ptr<Expression> right,
+ const Type* resultType) {
+ // We should have detected non-ES2 compliant behavior in Convert.
+ SkASSERT(!context.fConfig->strictES2Mode() || op.isAllowedInStrictES2Mode());
+ SkASSERT(!context.fConfig->strictES2Mode() || !left->type().isOrContainsArray());
+
+ // We should have detected non-assignable assignment expressions in Convert.
+ SkASSERT(!op.isAssignment() || Analysis::IsAssignable(*left));
+ SkASSERT(!op.isAssignment() || !left->type().componentType().isOpaque());
+
+ // For simple assignments, detect and report out-of-range literal values.
+ if (op.kind() == Operator::Kind::EQ) {
+ left->type().checkForOutOfRangeLiteral(context, *right);
+ }
+
+ // Perform constant-folding on the expression.
+ if (std::unique_ptr<Expression> result = ConstantFolder::Simplify(context, pos, *left,
+ op, *right, *resultType)) {
+ return result;
+ }
+
+ if (context.fConfig->fSettings.fOptimize && !context.fConfig->fIsBuiltinCode) {
+ // When sk_Caps.rewriteMatrixVectorMultiply is set, we rewrite medium-precision
+ // matrix * vector multiplication as:
+ // (sk_Caps.rewriteMatrixVectorMultiply ? (mat[0]*vec[0] + ... + mat[N]*vec[N])
+ // : mat * vec)
+ if (is_low_precision_matrix_vector_multiply(*left, op, *right, *resultType)) {
+ // Look up `sk_Caps.rewriteMatrixVectorMultiply`.
+ auto caps = Setting::Make(context, pos, &ShaderCaps::fRewriteMatrixVectorMultiply);
+
+ // There are three possible outcomes from Setting::Convert:
+ // - If the ShaderCaps aren't known (fCaps in the Context is null), we will get back a
+ // Setting IRNode. In practice, this should happen when compiling a module.
+ // In this case, we generate a ternary expression which will be optimized away when
+ // the module code is actually incorporated into a program.
+ // - If `rewriteMatrixVectorMultiply` is true in our shader caps, we will get back a
+ // Literal set to true. When this happens, we always return the rewritten expression.
+ // - If `rewriteMatrixVectorMultiply` is false in our shader caps, we will get back a
+ // Literal set to false. When this happens, we return the expression as-is.
+ bool capsBitIsTrue = caps->isBoolLiteral() && caps->as<Literal>().boolValue();
+ if (capsBitIsTrue || !caps->isBoolLiteral()) {
+ // Rewrite the multiplication as a sum of vector-scalar products.
+ std::unique_ptr<Expression> rewrite =
+ rewrite_matrix_vector_multiply(context, pos, *left, op, *right,
+ *resultType);
+
+ // If we know the caps bit is true, return the rewritten expression directly.
+ if (capsBitIsTrue) {
+ return rewrite;
+ }
+
+ // Return a ternary expression:
+ // sk_Caps.rewriteMatrixVectorMultiply ? (rewrite) : (mat * vec)
+ return TernaryExpression::Make(
+ context,
+ pos,
+ std::move(caps),
+ std::move(rewrite),
+ std::make_unique<BinaryExpression>(pos, std::move(left), op,
+ std::move(right), resultType));
+ }
+ }
+ }
+
+ return std::make_unique<BinaryExpression>(pos, std::move(left), op,
+ std::move(right), resultType);
+}
+
+bool BinaryExpression::CheckRef(const Expression& expr) {
+ switch (expr.kind()) {
+ case Expression::Kind::kFieldAccess:
+ return CheckRef(*expr.as<FieldAccess>().base());
+
+ case Expression::Kind::kIndex:
+ return CheckRef(*expr.as<IndexExpression>().base());
+
+ case Expression::Kind::kSwizzle:
+ return CheckRef(*expr.as<Swizzle>().base());
+
+ case Expression::Kind::kTernary: {
+ const TernaryExpression& t = expr.as<TernaryExpression>();
+ return CheckRef(*t.ifTrue()) && CheckRef(*t.ifFalse());
+ }
+ case Expression::Kind::kVariableReference: {
+ const VariableReference& ref = expr.as<VariableReference>();
+ return ref.refKind() == VariableRefKind::kWrite ||
+ ref.refKind() == VariableRefKind::kReadWrite;
+ }
+ default:
+ return false;
+ }
+}
+
+std::unique_ptr<Expression> BinaryExpression::clone(Position pos) const {
+ return std::make_unique<BinaryExpression>(pos,
+ this->left()->clone(),
+ this->getOperator(),
+ this->right()->clone(),
+ &this->type());
+}
+
+std::string BinaryExpression::description(OperatorPrecedence parentPrecedence) const {
+ OperatorPrecedence operatorPrecedence = this->getOperator().getBinaryPrecedence();
+ bool needsParens = (operatorPrecedence >= parentPrecedence);
+ return std::string(needsParens ? "(" : "") +
+ this->left()->description(operatorPrecedence) +
+ this->getOperator().operatorName() +
+ this->right()->description(operatorPrecedence) +
+ std::string(needsParens ? ")" : "");
+}
+
+VariableReference* BinaryExpression::isAssignmentIntoVariable() {
+ if (this->getOperator().isAssignment()) {
+ Analysis::AssignmentInfo assignmentInfo;
+ if (Analysis::IsAssignable(*this->left(), &assignmentInfo, /*errors=*/nullptr)) {
+ return assignmentInfo.fAssignedVar;
+ }
+ }
+ return nullptr;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLBinaryExpression.h b/gfx/skia/skia/src/sksl/ir/SkSLBinaryExpression.h
new file mode 100644
index 0000000000..094af55ef2
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLBinaryExpression.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_BINARYEXPRESSION
+#define SKSL_BINARYEXPRESSION
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/sksl/SkSLOperator.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace SkSL {
+
+class Context;
+class Type;
+class VariableReference;
+
+/**
+ * A binary operation.
+ */
+class BinaryExpression final : public Expression {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kBinary;
+
+ BinaryExpression(Position pos, std::unique_ptr<Expression> left, Operator op,
+ std::unique_ptr<Expression> right, const Type* type)
+ : INHERITED(pos, kIRNodeKind, type)
+ , fLeft(std::move(left))
+ , fOperator(op)
+ , fRight(std::move(right)) {
+ // If we are assigning to a VariableReference, ensure that it is set to Write or ReadWrite.
+ SkASSERT(!op.isAssignment() || CheckRef(*this->left()));
+ }
+
+ // Creates a potentially-simplified form of the expression. Determines the result type
+ // programmatically. Typechecks and coerces input expressions; reports errors via ErrorReporter.
+ static std::unique_ptr<Expression> Convert(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> left,
+ Operator op,
+ std::unique_ptr<Expression> right);
+
+ // Creates a potentially-simplified form of the expression. Determines the result type
+ // programmatically. Asserts if the expressions do not typecheck or are otherwise invalid.
+ static std::unique_ptr<Expression> Make(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> left,
+ Operator op,
+ std::unique_ptr<Expression> right);
+
+ // Creates a potentially-simplified form of the expression. Result type is passed in.
+ // Asserts if the expressions do not typecheck or are otherwise invalid.
+ static std::unique_ptr<Expression> Make(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> left,
+ Operator op,
+ std::unique_ptr<Expression> right,
+ const Type* resultType);
+
+ std::unique_ptr<Expression>& left() {
+ return fLeft;
+ }
+
+ const std::unique_ptr<Expression>& left() const {
+ return fLeft;
+ }
+
+ std::unique_ptr<Expression>& right() {
+ return fRight;
+ }
+
+ const std::unique_ptr<Expression>& right() const {
+ return fRight;
+ }
+
+ Operator getOperator() const {
+ return fOperator;
+ }
+
+ std::unique_ptr<Expression> clone(Position pos) const override;
+
+ std::string description(OperatorPrecedence parentPrecedence) const override;
+
+ /**
+ * If the expression is an assignment like `a = 1` or `a += sin(b)`, returns the
+ * VariableReference that will be written to. For other types of expressions, returns null.
+ * Complex expressions that contain inner assignments, like `(a = b) * 2`, will return null.
+ */
+ VariableReference* isAssignmentIntoVariable();
+
+private:
+ static bool CheckRef(const Expression& expr);
+
+ std::unique_ptr<Expression> fLeft;
+ Operator fOperator;
+ std::unique_ptr<Expression> fRight;
+
+ using INHERITED = Expression;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLBlock.cpp b/gfx/skia/skia/src/sksl/ir/SkSLBlock.cpp
new file mode 100644
index 0000000000..24531e2cf3
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLBlock.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLBlock.h"
+
+#include "src/sksl/ir/SkSLNop.h"
+#include "src/sksl/ir/SkSLSymbolTable.h"
+
+#include <type_traits>
+
+namespace SkSL {
+
+std::unique_ptr<Statement> Block::Make(Position pos,
+ StatementArray statements,
+ Kind kind,
+ std::shared_ptr<SymbolTable> symbols) {
+ // We can't simplify away braces or populated symbol tables.
+ if (kind == Kind::kBracedScope || (symbols && symbols->count())) {
+ return std::make_unique<Block>(pos, std::move(statements), kind, std::move(symbols));
+ }
+
+ // If the Block is completely empty, synthesize a Nop.
+ if (statements.empty()) {
+ return Nop::Make();
+ }
+
+ if (statements.size() > 1) {
+ // The statement array contains multiple statements, but some of those might be no-ops.
+ // If the statement array only contains one real statement, we can return that directly and
+ // avoid creating an additional Block node.
+ std::unique_ptr<Statement>* foundStatement = nullptr;
+ for (std::unique_ptr<Statement>& stmt : statements) {
+ if (!stmt->isEmpty()) {
+ if (!foundStatement) {
+ // We found a single non-empty statement. Remember it and keep looking.
+ foundStatement = &stmt;
+ continue;
+ }
+ // We found more than one non-empty statement. We actually do need a Block.
+ return std::make_unique<Block>(pos, std::move(statements), kind,
+ /*symbols=*/nullptr);
+ }
+ }
+
+ // The array wrapped one valid Statement. Avoid allocating a Block by returning it directly.
+ if (foundStatement) {
+ return std::move(*foundStatement);
+ }
+
+ // The statement array contained nothing but empty statements!
+ // In this case, we don't actually need to allocate a Block.
+ // We can just return one of those empty statements. Fall through to...
+ }
+
+ return std::move(statements.front());
+}
+
+std::unique_ptr<Block> Block::MakeBlock(Position pos,
+ StatementArray statements,
+ Kind kind,
+ std::shared_ptr<SymbolTable> symbols) {
+ // Nothing to optimize here--eliminating empty statements doesn't actually improve the generated
+ // code, and we promise to return a Block.
+ return std::make_unique<Block>(pos, std::move(statements), kind, std::move(symbols));
+}
+
+std::unique_ptr<Statement> Block::clone() const {
+ StatementArray cloned;
+ cloned.reserve_back(this->children().size());
+ for (const std::unique_ptr<Statement>& stmt : this->children()) {
+ cloned.push_back(stmt->clone());
+ }
+ return std::make_unique<Block>(fPosition,
+ std::move(cloned),
+ fBlockKind,
+ SymbolTable::WrapIfBuiltin(this->symbolTable()));
+}
+
+std::string Block::description() const {
+ std::string result;
+
+ // Write scope markers if this block is a scope, or if the block is empty (since we need to emit
+ // something here to make the code valid).
+ bool isScope = this->isScope() || this->isEmpty();
+ if (isScope) {
+ result += "{";
+ }
+ for (const std::unique_ptr<Statement>& stmt : this->children()) {
+ result += "\n";
+ result += stmt->description();
+ }
+ result += isScope ? "\n}\n" : "\n";
+ return result;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLBlock.h b/gfx/skia/skia/src/sksl/ir/SkSLBlock.h
new file mode 100644
index 0000000000..7bdfdf8bf7
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLBlock.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_BLOCK
+#define SKSL_BLOCK
+
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLStatement.h"
+#include "include/sksl/SkSLPosition.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace SkSL {
+
+class SymbolTable;
+
+/**
+ * A block of multiple statements functioning as a single statement.
+ */
+class Block final : public Statement {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kBlock;
+
+ // "kBracedScope" represents an actual language-level block. Other kinds of block are used to
+ // pass around multiple statements as if they were a single unit, with no semantic impact.
+ enum class Kind {
+ kUnbracedBlock, // Represents a group of statements without curly braces.
+ kBracedScope, // Represents a language-level Block, with curly braces.
+ kCompoundStatement, // A block which conceptually represents a single statement, such as
+ // `int a, b;`. (SkSL represents this internally as two statements:
+ // `int a; int b;`) Allowed to optimize away to its interior Statement.
+ // Treated as a single statement by the debugger.
+ };
+
+ Block(Position pos, StatementArray statements,
+ Kind kind = Kind::kBracedScope, const std::shared_ptr<SymbolTable> symbols = nullptr)
+ : INHERITED(pos, kIRNodeKind)
+ , fChildren(std::move(statements))
+ , fBlockKind(kind)
+ , fSymbolTable(std::move(symbols)) {}
+
+ // Make is allowed to simplify compound statements. For a single-statement unscoped Block,
+ // Make can return the Statement as-is. For an empty unscoped Block, Make can return Nop.
+ static std::unique_ptr<Statement> Make(Position pos,
+ StatementArray statements,
+ Kind kind = Kind::kBracedScope,
+ std::shared_ptr<SymbolTable> symbols = nullptr);
+
+ // MakeBlock always makes a real Block object. This is important because many callers rely on
+ // Blocks specifically; e.g. a function body must be a scoped Block, nothing else will do.
+ static std::unique_ptr<Block> MakeBlock(Position pos,
+ StatementArray statements,
+ Kind kind = Kind::kBracedScope,
+ std::shared_ptr<SymbolTable> symbols = nullptr);
+
+ const StatementArray& children() const {
+ return fChildren;
+ }
+
+ StatementArray& children() {
+ return fChildren;
+ }
+
+ bool isScope() const {
+ return fBlockKind == Kind::kBracedScope;
+ }
+
+ Kind blockKind() const {
+ return fBlockKind;
+ }
+
+ void setBlockKind(Kind kind) {
+ fBlockKind = kind;
+ }
+
+ std::shared_ptr<SymbolTable> symbolTable() const {
+ return fSymbolTable;
+ }
+
+ bool isEmpty() const override {
+ for (const std::unique_ptr<Statement>& stmt : this->children()) {
+ if (!stmt->isEmpty()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ std::unique_ptr<Statement> clone() const override;
+
+ std::string description() const override;
+
+private:
+ StatementArray fChildren;
+ Kind fBlockKind;
+ std::shared_ptr<SymbolTable> fSymbolTable;
+
+ using INHERITED = Statement;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLBreakStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLBreakStatement.h
new file mode 100644
index 0000000000..96cb700b14
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLBreakStatement.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_BREAKSTATEMENT
+#define SKSL_BREAKSTATEMENT
+
+#include "include/private/SkSLStatement.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+namespace SkSL {
+
+/**
+ * A 'break' statement.
+ */
+class BreakStatement final : public Statement {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kBreak;
+
+ BreakStatement(Position pos)
+ : INHERITED(pos, kIRNodeKind) {}
+
+ static std::unique_ptr<Statement> Make(Position pos) {
+ return std::make_unique<BreakStatement>(pos);
+ }
+
+ std::unique_ptr<Statement> clone() const override {
+ return std::make_unique<BreakStatement>(fPosition);
+ }
+
+ std::string description() const override {
+ return "break;";
+ }
+
+private:
+ using INHERITED = Statement;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLChildCall.cpp b/gfx/skia/skia/src/sksl/ir/SkSLChildCall.cpp
new file mode 100644
index 0000000000..e20a577051
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLChildCall.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLChildCall.h"
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLString.h"
+#include "include/private/base/SkTArray.h"
+#include "include/sksl/SkSLOperator.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/ir/SkSLType.h"
+#include "src/sksl/ir/SkSLVariable.h"
+
+namespace SkSL {
+
+std::unique_ptr<Expression> ChildCall::clone(Position pos) const {
+ return std::make_unique<ChildCall>(pos, &this->type(), &this->child(),
+ this->arguments().clone());
+}
+
+std::string ChildCall::description(OperatorPrecedence) const {
+ std::string result = std::string(this->child().name()) + ".eval(";
+ auto separator = SkSL::String::Separator();
+ for (const std::unique_ptr<Expression>& arg : this->arguments()) {
+ result += separator();
+ result += arg->description(OperatorPrecedence::kSequence);
+ }
+ result += ")";
+ return result;
+}
+
+[[maybe_unused]] static bool call_signature_is_valid(const Context& context,
+ const Variable& child,
+ const ExpressionArray& arguments) {
+ const Type* half4 = context.fTypes.fHalf4.get();
+ const Type* float2 = context.fTypes.fFloat2.get();
+
+ auto params = [&]() -> SkSTArray<2, const Type*> {
+ switch (child.type().typeKind()) {
+ case Type::TypeKind::kBlender: return { half4, half4 };
+ case Type::TypeKind::kColorFilter: return { half4 };
+ case Type::TypeKind::kShader: return { float2 };
+ default:
+ SkUNREACHABLE;
+ }
+ }();
+
+ if (params.size() != arguments.size()) {
+ return false;
+ }
+ for (int i = 0; i < arguments.size(); i++) {
+ if (!arguments[i]->type().matches(*params[i])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+std::unique_ptr<Expression> ChildCall::Make(const Context& context,
+ Position pos,
+ const Type* returnType,
+ const Variable& child,
+ ExpressionArray arguments) {
+ SkASSERT(call_signature_is_valid(context, child, arguments));
+ return std::make_unique<ChildCall>(pos, returnType, &child, std::move(arguments));
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLChildCall.h b/gfx/skia/skia/src/sksl/ir/SkSLChildCall.h
new file mode 100644
index 0000000000..7d48a84d58
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLChildCall.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_CHILDCALL
+#define SKSL_CHILDCALL
+
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace SkSL {
+
+class Context;
+class Type;
+class Variable;
+enum class OperatorPrecedence : uint8_t;
+
+/**
+ * A call to a child effect object (shader, color filter, or blender).
+ */
+class ChildCall final : public Expression {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kChildCall;
+
+ ChildCall(Position pos, const Type* type, const Variable* child, ExpressionArray arguments)
+ : INHERITED(pos, kIRNodeKind, type)
+ , fChild(*child)
+ , fArguments(std::move(arguments)) {}
+
+ // Creates the child call; reports errors via ASSERT.
+ static std::unique_ptr<Expression> Make(const Context& context,
+ Position pos,
+ const Type* returnType,
+ const Variable& child,
+ ExpressionArray arguments);
+
+ const Variable& child() const {
+ return fChild;
+ }
+
+ ExpressionArray& arguments() {
+ return fArguments;
+ }
+
+ const ExpressionArray& arguments() const {
+ return fArguments;
+ }
+
+ std::unique_ptr<Expression> clone(Position pos) const override;
+
+ std::string description(OperatorPrecedence) const override;
+
+private:
+ const Variable& fChild;
+ ExpressionArray fArguments;
+
+ using INHERITED = Expression;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructor.cpp b/gfx/skia/skia/src/sksl/ir/SkSLConstructor.cpp
new file mode 100644
index 0000000000..5b505a6584
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructor.cpp
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2020 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLConstructor.h"
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLString.h"
+#include "include/private/base/SkTArray.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "include/sksl/SkSLOperator.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/ir/SkSLConstructorArray.h"
+#include "src/sksl/ir/SkSLConstructorCompound.h"
+#include "src/sksl/ir/SkSLConstructorCompoundCast.h"
+#include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h"
+#include "src/sksl/ir/SkSLConstructorMatrixResize.h"
+#include "src/sksl/ir/SkSLConstructorScalarCast.h"
+#include "src/sksl/ir/SkSLConstructorSplat.h"
+#include "src/sksl/ir/SkSLConstructorStruct.h"
+#include "src/sksl/ir/SkSLType.h"
+
+#include <vector>
+
+namespace SkSL {
+
+static std::unique_ptr<Expression> convert_compound_constructor(const Context& context,
+ Position pos,
+ const Type& type,
+ ExpressionArray args) {
+ SkASSERT(type.isVector() || type.isMatrix());
+
+ // The meaning of a compound constructor containing a single argument varies significantly in
+ // GLSL/SkSL, depending on the argument type.
+ if (args.size() == 1) {
+ std::unique_ptr<Expression>& argument = args.front();
+ if (type.isVector() && argument->type().isVector() &&
+ argument->type().componentType().matches(type.componentType()) &&
+ argument->type().slotCount() > type.slotCount()) {
+ // Casting a vector-type into a smaller matching vector-type is a slice in GLSL.
+ // We don't allow those casts in SkSL; recommend a swizzle instead.
+ // Only `.xy` and `.xyz` are valid recommendations here, because `.x` would imply a
+ // scalar(vector) cast, and nothing has more slots than `.xyzw`.
+ const char* swizzleHint;
+ switch (type.slotCount()) {
+ case 2: swizzleHint = "; use '.xy' instead"; break;
+ case 3: swizzleHint = "; use '.xyz' instead"; break;
+ default: swizzleHint = ""; SkDEBUGFAIL("unexpected slicing cast"); break;
+ }
+
+ context.fErrors->error(pos, "'" + argument->type().displayName() +
+ "' is not a valid parameter to '" + type.displayName() + "' constructor" +
+ swizzleHint);
+ return nullptr;
+ }
+
+ if (argument->type().isScalar()) {
+ // A constructor containing a single scalar is a splat (for vectors) or diagonal matrix
+ // (for matrices). It's legal regardless of the scalar's type, so synthesize an explicit
+ // conversion to the proper type. (This cast is a no-op if it's unnecessary; it can fail
+ // if we're casting a literal that exceeds the limits of the type.)
+ std::unique_ptr<Expression> typecast = ConstructorScalarCast::Convert(
+ context, pos, type.componentType(), std::move(args));
+ if (!typecast) {
+ return nullptr;
+ }
+
+ // Matrix-from-scalar creates a diagonal matrix; vector-from-scalar creates a splat.
+ return type.isMatrix()
+ ? ConstructorDiagonalMatrix::Make(context, pos, type, std::move(typecast))
+ : ConstructorSplat::Make(context, pos, type, std::move(typecast));
+ } else if (argument->type().isVector()) {
+ // A vector constructor containing a single vector with the same number of columns is a
+ // cast (e.g. float3 -> int3).
+ if (type.isVector() && argument->type().columns() == type.columns()) {
+ return ConstructorCompoundCast::Make(context, pos, type, std::move(argument));
+ }
+ } else if (argument->type().isMatrix()) {
+ // A matrix constructor containing a single matrix can be a resize, typecast, or both.
+ // GLSL lumps these into one category, but internally SkSL keeps them distinct.
+ if (type.isMatrix()) {
+ // First, handle type conversion. If the component types differ, synthesize the
+ // destination type with the argument's rows/columns. (This will be a no-op if it's
+ // already the right type.)
+ const Type& typecastType = type.componentType().toCompound(
+ context,
+ argument->type().columns(),
+ argument->type().rows());
+ argument = ConstructorCompoundCast::Make(context, pos, typecastType,
+ std::move(argument));
+
+ // Casting a matrix type into another matrix type is a resize.
+ return ConstructorMatrixResize::Make(context, pos, type,
+ std::move(argument));
+ }
+
+ // A vector constructor containing a single matrix can be compound construction if the
+ // matrix is 2x2 and the vector is 4-slot.
+ if (type.isVector() && type.columns() == 4 && argument->type().slotCount() == 4) {
+ // Casting a 2x2 matrix to a vector is a form of compound construction.
+ // First, reshape the matrix into a 4-slot vector of the same type.
+ const Type& vectorType = argument->type().componentType().toCompound(context,
+ /*columns=*/4,
+ /*rows=*/1);
+ std::unique_ptr<Expression> vecCtor =
+ ConstructorCompound::Make(context, pos, vectorType, std::move(args));
+
+ // Then, add a typecast to the result expression to ensure the types match.
+ // This will be a no-op if no typecasting is needed.
+ return ConstructorCompoundCast::Make(context, pos, type, std::move(vecCtor));
+ }
+ }
+ }
+
+ // For more complex cases, we walk the argument list and fix up the arguments as needed.
+ int expected = type.rows() * type.columns();
+ int actual = 0;
+ for (std::unique_ptr<Expression>& arg : args) {
+ if (!arg->type().isScalar() && !arg->type().isVector()) {
+ context.fErrors->error(pos, "'" + arg->type().displayName() +
+ "' is not a valid parameter to '" + type.displayName() + "' constructor");
+ return nullptr;
+ }
+
+ // Rely on Constructor::Convert to force this subexpression to the proper type. If it's a
+ // literal, this will make sure it's the right type of literal. If an expression of matching
+ // type, the expression will be returned as-is. If it's an expression of mismatched type,
+ // this adds a cast.
+ const Type& ctorType = type.componentType().toCompound(context, arg->type().columns(),
+ /*rows=*/1);
+ ExpressionArray ctorArg;
+ ctorArg.push_back(std::move(arg));
+ arg = Constructor::Convert(context, pos, ctorType, std::move(ctorArg));
+ if (!arg) {
+ return nullptr;
+ }
+ actual += ctorType.columns();
+ }
+
+ if (actual != expected) {
+ context.fErrors->error(pos, "invalid arguments to '" + type.displayName() +
+ "' constructor (expected " + std::to_string(expected) +
+ " scalars, but found " + std::to_string(actual) + ")");
+ return nullptr;
+ }
+
+ return ConstructorCompound::Make(context, pos, type, std::move(args));
+}
+
+std::unique_ptr<Expression> Constructor::Convert(const Context& context,
+ Position pos,
+ const Type& type,
+ ExpressionArray args) {
+ if (args.size() == 1 && args[0]->type().matches(type) && !type.componentType().isOpaque()) {
+ // Don't generate redundant casts; if the expression is already of the correct type, just
+ // return it as-is.
+ args[0]->fPosition = pos;
+ return std::move(args[0]);
+ }
+ if (type.isScalar()) {
+ return ConstructorScalarCast::Convert(context, pos, type, std::move(args));
+ }
+ if (type.isVector() || type.isMatrix()) {
+ return convert_compound_constructor(context, pos, type, std::move(args));
+ }
+ if (type.isArray() && type.columns() > 0) {
+ return ConstructorArray::Convert(context, pos, type, std::move(args));
+ }
+ if (type.isStruct() && type.fields().size() > 0) {
+ return ConstructorStruct::Convert(context, pos, type, std::move(args));
+ }
+
+ context.fErrors->error(pos, "cannot construct '" + type.displayName() + "'");
+ return nullptr;
+}
+
+std::optional<double> AnyConstructor::getConstantValue(int n) const {
+ SkASSERT(n >= 0 && n < (int)this->type().slotCount());
+ for (const std::unique_ptr<Expression>& arg : this->argumentSpan()) {
+ int argSlots = arg->type().slotCount();
+ if (n < argSlots) {
+ return arg->getConstantValue(n);
+ }
+ n -= argSlots;
+ }
+
+ SkDEBUGFAIL("argument-list slot count doesn't match constructor-type slot count");
+ return std::nullopt;
+}
+
+Expression::ComparisonResult AnyConstructor::compareConstant(const Expression& other) const {
+ SkASSERT(this->type().slotCount() == other.type().slotCount());
+
+ if (!other.supportsConstantValues()) {
+ return ComparisonResult::kUnknown;
+ }
+
+ int exprs = this->type().slotCount();
+ for (int n = 0; n < exprs; ++n) {
+ // Get the n'th subexpression from each side. If either one is null, return "unknown."
+ std::optional<double> left = this->getConstantValue(n);
+ if (!left.has_value()) {
+ return ComparisonResult::kUnknown;
+ }
+ std::optional<double> right = other.getConstantValue(n);
+ if (!right.has_value()) {
+ return ComparisonResult::kUnknown;
+ }
+ // Both sides are known and can be compared for equality directly.
+ if (*left != *right) {
+ return ComparisonResult::kNotEqual;
+ }
+ }
+ return ComparisonResult::kEqual;
+}
+
+AnyConstructor& Expression::asAnyConstructor() {
+ SkASSERT(this->isAnyConstructor());
+ return static_cast<AnyConstructor&>(*this);
+}
+
+const AnyConstructor& Expression::asAnyConstructor() const {
+ SkASSERT(this->isAnyConstructor());
+ return static_cast<const AnyConstructor&>(*this);
+}
+
+std::string AnyConstructor::description(OperatorPrecedence) const {
+ std::string result = this->type().description() + "(";
+ auto separator = SkSL::String::Separator();
+ for (const std::unique_ptr<Expression>& arg : this->argumentSpan()) {
+ result += separator();
+ result += arg->description(OperatorPrecedence::kSequence);
+ }
+ result.push_back(')');
+ return result;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructor.h b/gfx/skia/skia/src/sksl/ir/SkSLConstructor.h
new file mode 100644
index 0000000000..7b3616e7bf
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructor.h
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_CONSTRUCTOR
+#define SKSL_CONSTRUCTOR
+
+#include "include/core/SkSpan.h"
+#include "include/private/SkSLDefines.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLType.h"
+
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+#include <utility>
+
+namespace SkSL {
+
+class Context;
+enum class OperatorPrecedence : uint8_t;
+
+/**
+ * Base class representing a constructor with unknown arguments.
+ */
+class AnyConstructor : public Expression {
+public:
+ AnyConstructor(Position pos, Kind kind, const Type* type)
+ : INHERITED(pos, kind, type) {}
+
+ virtual SkSpan<std::unique_ptr<Expression>> argumentSpan() = 0;
+ virtual SkSpan<const std::unique_ptr<Expression>> argumentSpan() const = 0;
+
+ std::string description(OperatorPrecedence) const override;
+
+ const Type& componentType() const {
+ return this->type().componentType();
+ }
+
+ bool supportsConstantValues() const override { return true; }
+ std::optional<double> getConstantValue(int n) const override;
+
+ ComparisonResult compareConstant(const Expression& other) const override;
+
+private:
+ using INHERITED = Expression;
+};
+
+/**
+ * Base class representing a constructor that takes a single argument.
+ */
+class SingleArgumentConstructor : public AnyConstructor {
+public:
+ SingleArgumentConstructor(Position pos, Kind kind, const Type* type,
+ std::unique_ptr<Expression> argument)
+ : INHERITED(pos, kind, type)
+ , fArgument(std::move(argument)) {}
+
+ std::unique_ptr<Expression>& argument() {
+ return fArgument;
+ }
+
+ const std::unique_ptr<Expression>& argument() const {
+ return fArgument;
+ }
+
+ SkSpan<std::unique_ptr<Expression>> argumentSpan() final {
+ return {&fArgument, 1};
+ }
+
+ SkSpan<const std::unique_ptr<Expression>> argumentSpan() const final {
+ return {&fArgument, 1};
+ }
+
+private:
+ std::unique_ptr<Expression> fArgument;
+
+ using INHERITED = AnyConstructor;
+};
+
+/**
+ * Base class representing a constructor that takes an array of arguments.
+ */
+class MultiArgumentConstructor : public AnyConstructor {
+public:
+ MultiArgumentConstructor(Position pos, Kind kind, const Type* type,
+ ExpressionArray arguments)
+ : INHERITED(pos, kind, type)
+ , fArguments(std::move(arguments)) {}
+
+ ExpressionArray& arguments() {
+ return fArguments;
+ }
+
+ const ExpressionArray& arguments() const {
+ return fArguments;
+ }
+
+ SkSpan<std::unique_ptr<Expression>> argumentSpan() final {
+ return {&fArguments.front(), fArguments.size()};
+ }
+
+ SkSpan<const std::unique_ptr<Expression>> argumentSpan() const final {
+ return {&fArguments.front(), fArguments.size()};
+ }
+
+private:
+ ExpressionArray fArguments;
+
+ using INHERITED = AnyConstructor;
+};
+
+/**
+ * Converts any GLSL constructor, such as `float2(x, y)` or `mat3x3(otherMat)` or `int[2](0, i)`, to
+ * an SkSL expression.
+ *
+ * Vector constructors must always consist of either exactly 1 scalar, or a collection of vectors
+ * and scalars totaling exactly the right number of scalar components.
+ *
+ * Matrix constructors must always consist of either exactly 1 scalar, exactly 1 matrix, or a
+ * collection of vectors and scalars totaling exactly the right number of scalar components.
+ *
+ * Array constructors must always contain the proper number of array elements (matching the Type).
+ */
+namespace Constructor {
+ // Creates, typechecks and simplifies constructor expressions. Reports errors via the
+ // ErrorReporter. This can return null on error, so be careful. There are several different
+ // Constructor expression types; this class chooses the proper one based on context, e.g.
+ // `ConstructorCompound`, `ConstructorScalarCast`, or `ConstructorMatrixResize`.
+ std::unique_ptr<Expression> Convert(const Context& context,
+ Position pos,
+ const Type& type,
+ ExpressionArray args);
+}
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorArray.cpp b/gfx/skia/skia/src/sksl/ir/SkSLConstructorArray.cpp
new file mode 100644
index 0000000000..f88098cf3a
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorArray.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLConstructorArray.h"
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLString.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/ir/SkSLConstructorArrayCast.h"
+#include "src/sksl/ir/SkSLType.h"
+
+#include <algorithm>
+#include <string>
+
+namespace SkSL {
+
+std::unique_ptr<Expression> ConstructorArray::Convert(const Context& context,
+ Position pos,
+ const Type& type,
+ ExpressionArray args) {
+ SkASSERTF(type.isArray() && type.columns() > 0, "%s", type.description().c_str());
+
+ // ES2 doesn't support first-class array types.
+ if (context.fConfig->strictES2Mode()) {
+ context.fErrors->error(pos, "construction of array type '" + type.displayName() +
+ "' is not supported");
+ return nullptr;
+ }
+
+ // An array of atomics cannot be constructed.
+ if (type.isOrContainsAtomic()) {
+ context.fErrors->error(
+ pos,
+ String::printf("construction of array type '%s' with atomic member is not allowed",
+ type.displayName().c_str()));
+ return nullptr;
+ }
+
+ // If there is a single argument containing an array of matching size and the types are
+ // coercible, this is actually a cast. i.e., `half[10](myFloat10Array)`. This isn't a GLSL
+ // feature, but the Pipeline stage code generator needs this functionality so that code which
+ // was originally compiled with "allow narrowing conversions" enabled can be later recompiled
+ // without narrowing conversions (we patch over these conversions with an explicit cast).
+ if (args.size() == 1) {
+ const Expression& expr = *args.front();
+ const Type& exprType = expr.type();
+
+ if (exprType.isArray() && exprType.canCoerceTo(type, /*allowNarrowing=*/true)) {
+ return ConstructorArrayCast::Make(context, pos, type, std::move(args.front()));
+ }
+ }
+
+ // Check that the number of constructor arguments matches the array size.
+ if (type.columns() != args.size()) {
+ context.fErrors->error(pos, String::printf("invalid arguments to '%s' constructor "
+ "(expected %d elements, but found %d)", type.displayName().c_str(), type.columns(),
+ args.size()));
+ return nullptr;
+ }
+
+ // Convert each constructor argument to the array's component type.
+ const Type& baseType = type.componentType();
+ for (std::unique_ptr<Expression>& argument : args) {
+ argument = baseType.coerceExpression(std::move(argument), context);
+ if (!argument) {
+ return nullptr;
+ }
+ }
+
+ return ConstructorArray::Make(context, pos, type, std::move(args));
+}
+
+std::unique_ptr<Expression> ConstructorArray::Make(const Context& context,
+ Position pos,
+ const Type& type,
+ ExpressionArray args) {
+ SkASSERT(!context.fConfig->strictES2Mode());
+ SkASSERT(type.isAllowedInES2(context));
+ SkASSERT(type.columns() == args.size());
+ SkASSERT(!type.isOrContainsAtomic());
+ SkASSERT(std::all_of(args.begin(), args.end(), [&](const std::unique_ptr<Expression>& arg) {
+ return type.componentType().matches(arg->type());
+ }));
+
+ return std::make_unique<ConstructorArray>(pos, type, std::move(args));
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorArray.h b/gfx/skia/skia/src/sksl/ir/SkSLConstructorArray.h
new file mode 100644
index 0000000000..8dfdb34db1
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorArray.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_CONSTRUCTOR_ARRAY
+#define SKSL_CONSTRUCTOR_ARRAY
+
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLConstructor.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+#include <memory>
+#include <utility>
+
+namespace SkSL {
+
+class Context;
+class Type;
+
+/**
+ * Represents the construction of an array type, such as "float[5](x, y, z, w, 1)".
+ */
+class ConstructorArray final : public MultiArgumentConstructor {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kConstructorArray;
+
+ ConstructorArray(Position pos, const Type& type, ExpressionArray arguments)
+ : INHERITED(pos, kIRNodeKind, &type, std::move(arguments)) {}
+
+ // ConstructorArray::Convert will typecheck and create array-constructor expressions.
+ // Reports errors via the ErrorReporter; returns null on error.
+ static std::unique_ptr<Expression> Convert(const Context& context,
+ Position pos,
+ const Type& type,
+ ExpressionArray args);
+
+ // ConstructorArray::Make creates array-constructor expressions; errors reported via SkASSERT.
+ static std::unique_ptr<Expression> Make(const Context& context,
+ Position pos,
+ const Type& type,
+ ExpressionArray args);
+
+ std::unique_ptr<Expression> clone(Position pos) const override {
+ return std::make_unique<ConstructorArray>(pos, this->type(), this->arguments().clone());
+ }
+
+private:
+ using INHERITED = MultiArgumentConstructor;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorArrayCast.cpp b/gfx/skia/skia/src/sksl/ir/SkSLConstructorArrayCast.cpp
new file mode 100644
index 0000000000..a5f9039eb0
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorArrayCast.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLConstructorArrayCast.h"
+
+#include "include/core/SkSpan.h"
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLDefines.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLConstantFolder.h"
+#include "src/sksl/ir/SkSLConstructorArray.h"
+#include "src/sksl/ir/SkSLConstructorCompoundCast.h"
+#include "src/sksl/ir/SkSLConstructorScalarCast.h"
+#include "src/sksl/ir/SkSLType.h"
+
+namespace SkSL {
+
+static std::unique_ptr<Expression> cast_constant_array(const Context& context,
+ Position pos,
+ const Type& destType,
+ std::unique_ptr<Expression> constCtor) {
+ const Type& scalarType = destType.componentType();
+
+ // Create a ConstructorArray(...) which typecasts each argument inside.
+ auto inputArgs = constCtor->as<ConstructorArray>().argumentSpan();
+ ExpressionArray typecastArgs;
+ typecastArgs.reserve_back(inputArgs.size());
+ for (std::unique_ptr<Expression>& arg : inputArgs) {
+ Position argPos = arg->fPosition;
+ if (arg->type().isScalar()) {
+ typecastArgs.push_back(ConstructorScalarCast::Make(context, argPos, scalarType,
+ std::move(arg)));
+ } else {
+ typecastArgs.push_back(ConstructorCompoundCast::Make(context, argPos, scalarType,
+ std::move(arg)));
+ }
+ }
+
+ return ConstructorArray::Make(context, pos, destType, std::move(typecastArgs));
+}
+
+std::unique_ptr<Expression> ConstructorArrayCast::Make(const Context& context,
+ Position pos,
+ const Type& type,
+ std::unique_ptr<Expression> arg) {
+ // Only arrays of the same size are allowed.
+ SkASSERT(type.isArray());
+ SkASSERT(type.isAllowedInES2(context));
+ SkASSERT(arg->type().isArray());
+ SkASSERT(type.columns() == arg->type().columns());
+
+ // If this is a no-op cast, return the expression as-is.
+ if (type.matches(arg->type())) {
+ arg->fPosition = pos;
+ return arg;
+ }
+
+ // Look up the value of constant variables. This allows constant-expressions like `myArray` to
+ // be replaced with the compile-time constant `int[2](0, 1)`.
+ arg = ConstantFolder::MakeConstantValueForVariable(pos, std::move(arg));
+
+ // We can cast a vector of compile-time constants at compile-time.
+ if (Analysis::IsCompileTimeConstant(*arg)) {
+ return cast_constant_array(context, pos, type, std::move(arg));
+ }
+ return std::make_unique<ConstructorArrayCast>(pos, type, std::move(arg));
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorArrayCast.h b/gfx/skia/skia/src/sksl/ir/SkSLConstructorArrayCast.h
new file mode 100644
index 0000000000..7db825142b
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorArrayCast.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_CONSTRUCTOR_ARRAY_CAST
+#define SKSL_CONSTRUCTOR_ARRAY_CAST
+
+#include "include/private/SkSLIRNode.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLConstructor.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+#include <memory>
+#include <utility>
+
+namespace SkSL {
+
+class Context;
+class Type;
+
+/**
+ * Represents the typecasting of an array. Arrays cannot be directly casted in SkSL (or GLSL), but
+ * type narrowing can cause an array to be implicitly casted. For instance, the expression
+ * `myHalf2Array == float[2](a, b)` should be allowed when narrowing conversions are enabled; this
+ * constructor allows the necessary array-type conversion to be represented in IR.
+ *
+ * These always contain exactly 1 array of matching size, and are never constant.
+ */
+class ConstructorArrayCast final : public SingleArgumentConstructor {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kConstructorArrayCast;
+
+ ConstructorArrayCast(Position pos, const Type& type, std::unique_ptr<Expression> arg)
+ : INHERITED(pos, kIRNodeKind, &type, std::move(arg)) {}
+
+ static std::unique_ptr<Expression> Make(const Context& context,
+ Position pos,
+ const Type& type,
+ std::unique_ptr<Expression> arg);
+
+ std::unique_ptr<Expression> clone(Position pos) const override {
+ return std::make_unique<ConstructorArrayCast>(pos, this->type(), argument()->clone());
+ }
+
+private:
+ using INHERITED = SingleArgumentConstructor;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorCompound.cpp b/gfx/skia/skia/src/sksl/ir/SkSLConstructorCompound.cpp
new file mode 100644
index 0000000000..06bbd8a6d8
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorCompound.cpp
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLConstructorCompound.h"
+
+#include "include/core/SkTypes.h"
+#include "include/private/base/SkTArray.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLConstantFolder.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/ir/SkSLConstructorSplat.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLType.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <numeric>
+#include <string>
+
+namespace SkSL {
+
+static bool is_safe_to_eliminate(const Type& type, const Expression& arg) {
+ if (type.isScalar()) {
+ // A scalar "compound type" with a single scalar argument is a no-op and can be eliminated.
+ // (Pedantically, this isn't a compound at all, but it's harmless to allow and simplifies
+ // call sites which need to narrow a vector and may sometimes end up with a scalar.)
+ SkASSERTF(arg.type().matches(type), "Creating type '%s' from '%s'",
+ type.description().c_str(), arg.type().description().c_str());
+ return true;
+ }
+ if (type.isVector() && arg.type().matches(type)) {
+ // A vector compound constructor containing a single argument of matching type can trivially
+ // be eliminated.
+ return true;
+ }
+ // This is a meaningful single-argument compound constructor (e.g. vector-from-matrix,
+ // matrix-from-vector).
+ return false;
+}
+
+static const Expression* make_splat_from_arguments(const Type& type, const ExpressionArray& args) {
+ // Splats cannot represent a matrix.
+ if (type.isMatrix()) {
+ return nullptr;
+ }
+ const Expression* splatExpression = nullptr;
+ for (int index = 0; index < args.size(); ++index) {
+ // Arguments must only be scalars or a splat constructors (which can only contain scalars).
+ const Expression* expr;
+ if (args[index]->type().isScalar()) {
+ expr = args[index].get();
+ } else if (args[index]->is<ConstructorSplat>()) {
+ expr = args[index]->as<ConstructorSplat>().argument().get();
+ } else {
+ return nullptr;
+ }
+ // On the first iteration, just remember the expression we encountered.
+ if (index == 0) {
+ splatExpression = expr;
+ continue;
+ }
+ // On subsequent iterations, ensure that the expression we found matches the first one.
+ // (Note that IsSameExpressionTree will always reject an Expression with side effects.)
+ if (!Analysis::IsSameExpressionTree(*expr, *splatExpression)) {
+ return nullptr;
+ }
+ }
+
+ return splatExpression;
+}
+
+std::unique_ptr<Expression> ConstructorCompound::Make(const Context& context,
+ Position pos,
+ const Type& type,
+ ExpressionArray args) {
+ SkASSERT(type.isAllowedInES2(context));
+
+ // All the arguments must have matching component type.
+ SkASSERT(std::all_of(args.begin(), args.end(), [&](const std::unique_ptr<Expression>& arg) {
+ const Type& argType = arg->type();
+ return (argType.isScalar() || argType.isVector() || argType.isMatrix()) &&
+ (argType.componentType().matches(type.componentType()));
+ }));
+
+ // The slot count of the combined argument list must match the composite type's slot count.
+ SkASSERT(type.slotCount() ==
+ std::accumulate(args.begin(), args.end(), /*initial value*/ (size_t)0,
+ [](size_t n, const std::unique_ptr<Expression>& arg) {
+ return n + arg->type().slotCount();
+ }));
+ // No-op compound constructors (containing a single argument of the same type) are eliminated.
+ // (Even though this is a "compound constructor," we let scalars pass through here; it's
+ // harmless to allow and simplifies call sites which need to narrow a vector and may sometimes
+ // end up with a scalar.)
+ if (args.size() == 1 && is_safe_to_eliminate(type, *args.front())) {
+ args.front()->fPosition = pos;
+ return std::move(args.front());
+ }
+ // Beyond this point, the type must be a vector or matrix.
+ SkASSERT(type.isVector() || type.isMatrix());
+
+ if (context.fConfig->fSettings.fOptimize) {
+ // Find ConstructorCompounds embedded inside other ConstructorCompounds and flatten them.
+ // - float4(float2(1, 2), 3, 4) --> float4(1, 2, 3, 4)
+ // - float4(w, float3(sin(x), cos(y), tan(z))) --> float4(w, sin(x), cos(y), tan(z))
+ // - mat2(float2(a, b), float2(c, d)) --> mat2(a, b, c, d)
+
+ // See how many fields we would have if composite constructors were flattened out.
+ int fields = 0;
+ for (const std::unique_ptr<Expression>& arg : args) {
+ fields += arg->is<ConstructorCompound>()
+ ? arg->as<ConstructorCompound>().arguments().size()
+ : 1;
+ }
+
+ // If we added up more fields than we're starting with, we found at least one input that can
+ // be flattened out.
+ if (fields > args.size()) {
+ ExpressionArray flattened;
+ flattened.reserve_back(fields);
+ for (std::unique_ptr<Expression>& arg : args) {
+ // For non-ConstructorCompound fields, move them over as-is.
+ if (!arg->is<ConstructorCompound>()) {
+ flattened.push_back(std::move(arg));
+ continue;
+ }
+ // For ConstructorCompound fields, move over their inner arguments individually.
+ ConstructorCompound& compositeCtor = arg->as<ConstructorCompound>();
+ for (std::unique_ptr<Expression>& innerArg : compositeCtor.arguments()) {
+ flattened.push_back(std::move(innerArg));
+ }
+ }
+ args = std::move(flattened);
+ }
+ }
+
+ // Replace constant variables with their corresponding values, so `float2(one, two)` can
+ // compile down to `float2(1.0, 2.0)` (the latter is a compile-time constant).
+ for (std::unique_ptr<Expression>& arg : args) {
+ arg = ConstantFolder::MakeConstantValueForVariable(pos, std::move(arg));
+ }
+
+ if (context.fConfig->fSettings.fOptimize) {
+ // Reduce compound constructors to splats where possible.
+ if (const Expression* splat = make_splat_from_arguments(type, args)) {
+ return ConstructorSplat::Make(context, pos, type, splat->clone());
+ }
+ }
+
+ return std::make_unique<ConstructorCompound>(pos, type, std::move(args));
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorCompound.h b/gfx/skia/skia/src/sksl/ir/SkSLConstructorCompound.h
new file mode 100644
index 0000000000..5dfd93f63c
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorCompound.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_CONSTRUCTOR_COMPOUND
+#define SKSL_CONSTRUCTOR_COMPOUND
+
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLConstructor.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+#include <memory>
+#include <utility>
+
+namespace SkSL {
+
+class Context;
+class Type;
+
+/**
+ * Represents a vector or matrix that is composed from other expressions, such as
+ * `half3(pos.xy, 1)` or `mat3(a.xyz, b.xyz, 0, 0, 1)`
+ *
+ * These can contain a mix of scalars and aggregates. The total number of scalar values inside the
+ * constructor must always match the type's slot count. (e.g. `pos.xy` consumes two slots.)
+ * The inner values must have the same component type as the vector/matrix.
+ */
+class ConstructorCompound final : public MultiArgumentConstructor {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kConstructorCompound;
+
+ ConstructorCompound(Position pos, const Type& type, ExpressionArray args)
+ : INHERITED(pos, kIRNodeKind, &type, std::move(args)) {}
+
+ static std::unique_ptr<Expression> Make(const Context& context,
+ Position pos,
+ const Type& type,
+ ExpressionArray args);
+
+ std::unique_ptr<Expression> clone(Position pos) const override {
+ return std::make_unique<ConstructorCompound>(pos, this->type(), this->arguments().clone());
+ }
+
+private:
+ using INHERITED = MultiArgumentConstructor;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorCompoundCast.cpp b/gfx/skia/skia/src/sksl/ir/SkSLConstructorCompoundCast.cpp
new file mode 100644
index 0000000000..2e9d9fca4c
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorCompoundCast.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLConstructorCompoundCast.h"
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLDefines.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLConstantFolder.h"
+#include "src/sksl/ir/SkSLConstructorCompound.h"
+#include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h"
+#include "src/sksl/ir/SkSLConstructorScalarCast.h"
+#include "src/sksl/ir/SkSLConstructorSplat.h"
+#include "src/sksl/ir/SkSLLiteral.h"
+#include "src/sksl/ir/SkSLType.h"
+
+#include <cstddef>
+#include <optional>
+
+namespace SkSL {
+
+static std::unique_ptr<Expression> cast_constant_composite(const Context& context,
+ Position pos,
+ const Type& destType,
+ std::unique_ptr<Expression> constCtor) {
+ const Type& scalarType = destType.componentType();
+
+ // We generate nicer code for splats and diagonal matrices by handling them separately instead
+ // of relying on the constant-subexpression code below. This is not truly necessary but it makes
+ // our output look a little better; human beings prefer `half4(0)` to `half4(0, 0, 0, 0)`.
+ if (constCtor->is<ConstructorSplat>()) {
+ // This is a typecast of a splat containing a constant value, e.g. `half4(7)`. We can
+ // replace it with a splat of a different type, e.g. `int4(7)`.
+ ConstructorSplat& splat = constCtor->as<ConstructorSplat>();
+ return ConstructorSplat::Make(
+ context, pos, destType,
+ ConstructorScalarCast::Make(context, pos, scalarType, std::move(splat.argument())));
+ }
+
+ if (constCtor->is<ConstructorDiagonalMatrix>() && destType.isMatrix()) {
+ // This is a typecast of a constant diagonal matrix, e.g. `float3x3(2)`. We can replace it
+ // with a diagonal matrix of a different type, e.g. `half3x3(2)`.
+ ConstructorDiagonalMatrix& matrixCtor = constCtor->as<ConstructorDiagonalMatrix>();
+ return ConstructorDiagonalMatrix::Make(
+ context, pos, destType,
+ ConstructorScalarCast::Make(context, pos, scalarType,
+ std::move(matrixCtor.argument())));
+ }
+
+ // Create a compound Constructor(literal, ...) which typecasts each scalar value inside.
+ size_t numSlots = destType.slotCount();
+ SkASSERT(numSlots == constCtor->type().slotCount());
+
+ ExpressionArray typecastArgs;
+ typecastArgs.reserve_back(numSlots);
+ for (size_t index = 0; index < numSlots; ++index) {
+ std::optional<double> slotVal = constCtor->getConstantValue(index);
+ if (scalarType.checkForOutOfRangeLiteral(context, *slotVal, constCtor->fPosition)) {
+ // We've reported an error because the literal is out of range for this type. Zero out
+ // the value to avoid a cascade of errors.
+ *slotVal = 0.0;
+ }
+ typecastArgs.push_back(Literal::Make(pos, *slotVal, &scalarType));
+ }
+
+ return ConstructorCompound::Make(context, pos, destType, std::move(typecastArgs));
+}
+
+std::unique_ptr<Expression> ConstructorCompoundCast::Make(const Context& context,
+ Position pos,
+ const Type& type,
+ std::unique_ptr<Expression> arg) {
+ // Only vectors or matrices of the same dimensions are allowed.
+ SkASSERT(type.isVector() || type.isMatrix());
+ SkASSERT(type.isAllowedInES2(context));
+ SkASSERT(arg->type().isVector() == type.isVector());
+ SkASSERT(arg->type().isMatrix() == type.isMatrix());
+ SkASSERT(type.columns() == arg->type().columns());
+ SkASSERT(type.rows() == arg->type().rows());
+
+ // If this is a no-op cast, return the expression as-is.
+ if (type.matches(arg->type())) {
+ return arg;
+ }
+ // Look up the value of constant variables. This allows constant-expressions like
+ // `int4(colorGreen)` to be replaced with the compile-time constant `int4(0, 1, 0, 1)`.
+ arg = ConstantFolder::MakeConstantValueForVariable(pos, std::move(arg));
+
+ // We can cast a vector of compile-time constants at compile-time.
+ if (Analysis::IsCompileTimeConstant(*arg)) {
+ return cast_constant_composite(context, pos, type, std::move(arg));
+ }
+ return std::make_unique<ConstructorCompoundCast>(pos, type, std::move(arg));
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorCompoundCast.h b/gfx/skia/skia/src/sksl/ir/SkSLConstructorCompoundCast.h
new file mode 100644
index 0000000000..9dc08271d7
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorCompoundCast.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_CONSTRUCTOR_COMPOUND_CAST
+#define SKSL_CONSTRUCTOR_COMPOUND_CAST
+
+#include "include/private/SkSLIRNode.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLConstructor.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+#include <memory>
+#include <utility>
+
+namespace SkSL {
+
+class Context;
+class Type;
+
+/**
+ * Represents the construction of a vector/matrix typecast, such as `half3(myInt3)` or
+ * `float4x4(myHalf4x4)`. Matrix resizes are done in ConstructorMatrixResize, not here.
+ *
+ * These always contain exactly 1 vector or matrix of matching size, and are never constant.
+ */
+class ConstructorCompoundCast final : public SingleArgumentConstructor {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kConstructorCompoundCast;
+
+ ConstructorCompoundCast(Position pos, const Type& type, std::unique_ptr<Expression> arg)
+ : INHERITED(pos, kIRNodeKind, &type, std::move(arg)) {}
+
+ static std::unique_ptr<Expression> Make(const Context& context,
+ Position pos,
+ const Type& type,
+ std::unique_ptr<Expression> arg);
+
+ std::unique_ptr<Expression> clone(Position pos) const override {
+ return std::make_unique<ConstructorCompoundCast>(pos, this->type(), argument()->clone());
+ }
+
+private:
+ using INHERITED = SingleArgumentConstructor;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorDiagonalMatrix.cpp b/gfx/skia/skia/src/sksl/ir/SkSLConstructorDiagonalMatrix.cpp
new file mode 100644
index 0000000000..e863babfa9
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorDiagonalMatrix.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h"
+
+#include "include/core/SkTypes.h"
+#include "src/sksl/SkSLConstantFolder.h"
+#include "src/sksl/ir/SkSLType.h"
+
+namespace SkSL {
+
+std::unique_ptr<Expression> ConstructorDiagonalMatrix::Make(const Context& context,
+ Position pos,
+ const Type& type,
+ std::unique_ptr<Expression> arg) {
+ SkASSERT(type.isMatrix());
+ SkASSERT(type.isAllowedInES2(context));
+ SkASSERT(arg->type().isScalar());
+ SkASSERT(arg->type().matches(type.componentType()));
+
+ // Look up the value of constant variables. This allows constant-expressions like `mat4(five)`
+ // to be replaced with `mat4(5.0)`.
+ arg = ConstantFolder::MakeConstantValueForVariable(pos, std::move(arg));
+
+ return std::make_unique<ConstructorDiagonalMatrix>(pos, type, std::move(arg));
+}
+
+std::optional<double> ConstructorDiagonalMatrix::getConstantValue(int n) const {
+ int rows = this->type().rows();
+ int row = n % rows;
+ int col = n / rows;
+
+ SkASSERT(col >= 0);
+ SkASSERT(row >= 0);
+ SkASSERT(col < this->type().columns());
+ SkASSERT(row < this->type().rows());
+
+ return (col == row) ? this->argument()->getConstantValue(0) : 0.0;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorDiagonalMatrix.h b/gfx/skia/skia/src/sksl/ir/SkSLConstructorDiagonalMatrix.h
new file mode 100644
index 0000000000..65342d750c
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorDiagonalMatrix.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_CONSTRUCTOR_DIAGONAL_MATRIX
+#define SKSL_CONSTRUCTOR_DIAGONAL_MATRIX
+
+#include "include/private/SkSLIRNode.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLConstructor.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+#include <memory>
+#include <optional>
+#include <utility>
+
+namespace SkSL {
+
+class Context;
+class Type;
+
+/**
+ * Represents the construction of a diagonal matrix, such as `half3x3(n)`.
+ *
+ * These always contain exactly 1 scalar.
+ */
+class ConstructorDiagonalMatrix final : public SingleArgumentConstructor {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kConstructorDiagonalMatrix;
+
+ ConstructorDiagonalMatrix(Position pos, const Type& type, std::unique_ptr<Expression> arg)
+ : INHERITED(pos, kIRNodeKind, &type, std::move(arg)) {}
+
+ static std::unique_ptr<Expression> Make(const Context& context,
+ Position pos,
+ const Type& type,
+ std::unique_ptr<Expression> arg);
+
+ std::unique_ptr<Expression> clone(Position pos) const override {
+ return std::make_unique<ConstructorDiagonalMatrix>(pos, this->type(), argument()->clone());
+ }
+
+ bool supportsConstantValues() const override { return true; }
+ std::optional<double> getConstantValue(int n) const override;
+
+private:
+ using INHERITED = SingleArgumentConstructor;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorMatrixResize.cpp b/gfx/skia/skia/src/sksl/ir/SkSLConstructorMatrixResize.cpp
new file mode 100644
index 0000000000..a015666e4b
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorMatrixResize.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLConstructorMatrixResize.h"
+
+#include "include/core/SkTypes.h"
+#include "src/sksl/ir/SkSLType.h"
+
+namespace SkSL {
+
+std::unique_ptr<Expression> ConstructorMatrixResize::Make(const Context& context,
+ Position pos,
+ const Type& type,
+ std::unique_ptr<Expression> arg) {
+ SkASSERT(type.isMatrix());
+ SkASSERT(type.isAllowedInES2(context));
+ SkASSERT(arg->type().componentType().matches(type.componentType()));
+
+ // If the matrix isn't actually changing size, return it as-is.
+ if (type.rows() == arg->type().rows() && type.columns() == arg->type().columns()) {
+ return arg;
+ }
+
+ return std::make_unique<ConstructorMatrixResize>(pos, type, std::move(arg));
+}
+
+std::optional<double> ConstructorMatrixResize::getConstantValue(int n) const {
+ int rows = this->type().rows();
+ int row = n % rows;
+ int col = n / rows;
+
+ SkASSERT(col >= 0);
+ SkASSERT(row >= 0);
+ SkASSERT(col < this->type().columns());
+ SkASSERT(row < this->type().rows());
+
+ // GLSL resize matrices are of the form:
+ // |m m 0|
+ // |m m 0|
+ // |0 0 1|
+ // Where `m` is the matrix being wrapped, and other cells contain the identity matrix.
+
+ // Forward `getConstantValue` to the wrapped matrix if the position is in its bounds.
+ if (col < this->argument()->type().columns() && row < this->argument()->type().rows()) {
+ // Recalculate `n` in terms of the inner matrix's dimensions.
+ n = row + (col * this->argument()->type().rows());
+ return this->argument()->getConstantValue(n);
+ }
+
+ // Synthesize an identity matrix for out-of-bounds positions.
+ return (col == row) ? 1.0 : 0.0;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorMatrixResize.h b/gfx/skia/skia/src/sksl/ir/SkSLConstructorMatrixResize.h
new file mode 100644
index 0000000000..bf1d3f5897
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorMatrixResize.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_CONSTRUCTOR_MATRIX_RESIZE
+#define SKSL_CONSTRUCTOR_MATRIX_RESIZE
+
+#include "include/private/SkSLIRNode.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLConstructor.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+#include <memory>
+#include <optional>
+#include <utility>
+
+namespace SkSL {
+
+class Context;
+class Type;
+
+/**
+ * Represents the construction of a matrix resize operation, such as `mat4x4(myMat2x2)`.
+ *
+ * These always contain exactly 1 matrix of non-matching size. Cells that aren't present in the
+ * input matrix are populated with the identity matrix.
+ */
+class ConstructorMatrixResize final : public SingleArgumentConstructor {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kConstructorMatrixResize;
+
+ ConstructorMatrixResize(Position pos, const Type& type, std::unique_ptr<Expression> arg)
+ : INHERITED(pos, kIRNodeKind, &type, std::move(arg)) {}
+
+ static std::unique_ptr<Expression> Make(const Context& context,
+ Position pos,
+ const Type& type,
+ std::unique_ptr<Expression> arg);
+
+ std::unique_ptr<Expression> clone(Position pos) const override {
+ return std::make_unique<ConstructorMatrixResize>(pos, this->type(), argument()->clone());
+ }
+
+ bool supportsConstantValues() const override { return true; }
+ std::optional<double> getConstantValue(int n) const override;
+
+private:
+ using INHERITED = SingleArgumentConstructor;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorScalarCast.cpp b/gfx/skia/skia/src/sksl/ir/SkSLConstructorScalarCast.cpp
new file mode 100644
index 0000000000..7b3074f31c
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorScalarCast.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLConstructorScalarCast.h"
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLDefines.h"
+#include "include/private/base/SkTArray.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "src/sksl/SkSLConstantFolder.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/ir/SkSLLiteral.h"
+#include "src/sksl/ir/SkSLType.h"
+
+#include <string>
+
+namespace SkSL {
+
+std::unique_ptr<Expression> ConstructorScalarCast::Convert(const Context& context,
+ Position pos,
+ const Type& rawType,
+ ExpressionArray args) {
+ // As you might expect, scalar-cast constructors should only be created with scalar types.
+ const Type& type = rawType.scalarTypeForLiteral();
+ SkASSERT(type.isScalar());
+
+ if (args.size() != 1) {
+ context.fErrors->error(pos, "invalid arguments to '" + type.displayName() +
+ "' constructor, (expected exactly 1 argument, but found " +
+ std::to_string(args.size()) + ")");
+ return nullptr;
+ }
+
+ const Type& argType = args[0]->type();
+ if (!argType.isScalar()) {
+ // Casting a vector-type into its scalar component type is treated as a slice in GLSL.
+ // We don't allow those casts in SkSL; recommend a .x swizzle instead.
+ const char* swizzleHint = "";
+ if (argType.componentType().matches(type)) {
+ if (argType.isVector()) {
+ swizzleHint = "; use '.x' instead";
+ } else if (argType.isMatrix()) {
+ swizzleHint = "; use '[0][0]' instead";
+ }
+ }
+
+ context.fErrors->error(pos,
+ "'" + argType.displayName() + "' is not a valid parameter to '" +
+ type.displayName() + "' constructor" + swizzleHint);
+ return nullptr;
+ }
+ if (type.checkForOutOfRangeLiteral(context, *args[0])) {
+ return nullptr;
+ }
+
+ return ConstructorScalarCast::Make(context, pos, type, std::move(args[0]));
+}
+
+std::unique_ptr<Expression> ConstructorScalarCast::Make(const Context& context,
+ Position pos,
+ const Type& type,
+ std::unique_ptr<Expression> arg) {
+ SkASSERT(type.isScalar());
+ SkASSERT(type.isAllowedInES2(context));
+ SkASSERT(arg->type().isScalar());
+
+ // No cast required when the types match.
+ if (arg->type().matches(type)) {
+ return arg;
+ }
+ // Look up the value of constant variables. This allows constant-expressions like `int(zero)` to
+ // be replaced with a literal zero.
+ arg = ConstantFolder::MakeConstantValueForVariable(pos, std::move(arg));
+
+ // We can cast scalar literals at compile-time when possible. (If the resulting literal would be
+ // out of range for its type, we report an error and return zero to minimize error cascading.
+ // This can occur when code is inlined, so we can't necessarily catch it during Convert. As
+ // such, it's not safe to return null or assert.)
+ if (arg->is<Literal>()) {
+ double value = arg->as<Literal>().value();
+ if (type.checkForOutOfRangeLiteral(context, value, arg->fPosition)) {
+ value = 0.0;
+ }
+ return Literal::Make(pos, value, &type);
+ }
+ return std::make_unique<ConstructorScalarCast>(pos, type, std::move(arg));
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorScalarCast.h b/gfx/skia/skia/src/sksl/ir/SkSLConstructorScalarCast.h
new file mode 100644
index 0000000000..295d19d959
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorScalarCast.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_CONSTRUCTOR_SCALAR_CAST
+#define SKSL_CONSTRUCTOR_SCALAR_CAST
+
+#include "include/private/SkSLIRNode.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLConstructor.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+#include <memory>
+#include <utility>
+
+namespace SkSL {
+
+class Context;
+class ExpressionArray;
+class Type;
+
+/**
+ * Represents the construction of a scalar cast, such as `float(intVariable)`.
+ *
+ * These always contain exactly 1 scalar of a differing type, and are never constant.
+ */
+class ConstructorScalarCast final : public SingleArgumentConstructor {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kConstructorScalarCast;
+
+ ConstructorScalarCast(Position pos, const Type& type, std::unique_ptr<Expression> arg)
+ : INHERITED(pos, kIRNodeKind, &type, std::move(arg)) {}
+
+ // ConstructorScalarCast::Convert will typecheck and create scalar-constructor expressions.
+ // Reports errors via the ErrorReporter; returns null on error.
+ static std::unique_ptr<Expression> Convert(const Context& context,
+ Position pos,
+ const Type& rawType,
+ ExpressionArray args);
+
+ // ConstructorScalarCast::Make casts a scalar expression. Casts that can be evaluated at
+ // compile-time will do so (e.g. `int(4.1)` --> `Literal(int 4)`). Errors reported via SkASSERT.
+ static std::unique_ptr<Expression> Make(const Context& context,
+ Position pos,
+ const Type& type,
+ std::unique_ptr<Expression> arg);
+
+ std::unique_ptr<Expression> clone(Position pos) const override {
+ return std::make_unique<ConstructorScalarCast>(pos, this->type(), argument()->clone());
+ }
+
+private:
+ using INHERITED = SingleArgumentConstructor;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorSplat.cpp b/gfx/skia/skia/src/sksl/ir/SkSLConstructorSplat.cpp
new file mode 100644
index 0000000000..0d5110c279
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorSplat.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLConstructorSplat.h"
+
+#include "src/sksl/SkSLConstantFolder.h"
+
+namespace SkSL {
+
+std::unique_ptr<Expression> ConstructorSplat::Make(const Context& context,
+ Position pos,
+ const Type& type,
+ std::unique_ptr<Expression> arg) {
+ SkASSERT(type.isAllowedInES2(context));
+ SkASSERT(type.isScalar() || type.isVector());
+ SkASSERT(arg->type().scalarTypeForLiteral().matches(
+ type.componentType().scalarTypeForLiteral()));
+ SkASSERT(arg->type().isScalar());
+
+ // A "splat" to a scalar type is a no-op and can be eliminated.
+ if (type.isScalar()) {
+ arg->fPosition = pos;
+ return arg;
+ }
+
+ // Replace constant variables with their corresponding values, so `float3(five)` can compile
+ // down to `float3(5.0)` (the latter is a compile-time constant).
+ arg = ConstantFolder::MakeConstantValueForVariable(pos, std::move(arg));
+
+ SkASSERT(type.isVector());
+ return std::make_unique<ConstructorSplat>(pos, type, std::move(arg));
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorSplat.h b/gfx/skia/skia/src/sksl/ir/SkSLConstructorSplat.h
new file mode 100644
index 0000000000..4b342d8d7a
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorSplat.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_CONSTRUCTOR_SPLAT
+#define SKSL_CONSTRUCTOR_SPLAT
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLConstructor.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLType.h"
+
+#include <memory>
+#include <optional>
+#include <utility>
+
+namespace SkSL {
+
+class Context;
+
+/**
+ * Represents the construction of a vector splat, such as `half3(n)`.
+ *
+ * These always contain exactly 1 scalar.
+ */
+class ConstructorSplat final : public SingleArgumentConstructor {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kConstructorSplat;
+
+ ConstructorSplat(Position pos, const Type& type, std::unique_ptr<Expression> arg)
+ : INHERITED(pos, kIRNodeKind, &type, std::move(arg)) {}
+
+ // The input argument must be scalar. A "splat" to a scalar type will be optimized into a no-op.
+ static std::unique_ptr<Expression> Make(const Context& context,
+ Position pos,
+ const Type& type,
+ std::unique_ptr<Expression> arg);
+
+ std::unique_ptr<Expression> clone(Position pos) const override {
+ return std::make_unique<ConstructorSplat>(pos, this->type(), argument()->clone());
+ }
+
+ bool supportsConstantValues() const override {
+ return true;
+ }
+
+ std::optional<double> getConstantValue(int n) const override {
+ SkASSERT(n >= 0 && n < this->type().columns());
+ return this->argument()->getConstantValue(0);
+ }
+
+private:
+ using INHERITED = SingleArgumentConstructor;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorStruct.cpp b/gfx/skia/skia/src/sksl/ir/SkSLConstructorStruct.cpp
new file mode 100644
index 0000000000..d8c42b4abc
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorStruct.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLConstructorStruct.h"
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLString.h"
+#include "include/private/base/SkTArray.h"
+#include "include/private/base/SkTo.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/ir/SkSLType.h"
+
+#include <string>
+#include <vector>
+
+namespace SkSL {
+
+std::unique_ptr<Expression> ConstructorStruct::Convert(const Context& context,
+ Position pos,
+ const Type& type,
+ ExpressionArray args) {
+ SkASSERTF(type.isStruct() && type.fields().size() > 0, "%s", type.description().c_str());
+
+ // Check that the number of constructor arguments matches the array size.
+ if (type.fields().size() != SkToSizeT(args.size())) {
+ context.fErrors->error(pos,
+ String::printf("invalid arguments to '%s' constructor "
+ "(expected %zu elements, but found %d)",
+ type.displayName().c_str(), type.fields().size(),
+ args.size()));
+ return nullptr;
+ }
+
+ // A struct with atomic members cannot be constructed.
+ if (type.isOrContainsAtomic()) {
+ context.fErrors->error(
+ pos,
+ String::printf("construction of struct type '%s' with atomic member is not allowed",
+ type.displayName().c_str()));
+ return nullptr;
+ }
+
+ // Convert each constructor argument to the struct's field type.
+ for (int index=0; index<args.size(); ++index) {
+ std::unique_ptr<Expression>& argument = args[index];
+ const Type::Field& field = type.fields()[index];
+
+ argument = field.fType->coerceExpression(std::move(argument), context);
+ if (!argument) {
+ return nullptr;
+ }
+ }
+
+ return ConstructorStruct::Make(context, pos, type, std::move(args));
+}
+
+[[maybe_unused]] static bool arguments_match_field_types(const ExpressionArray& args,
+ const Type& type) {
+ SkASSERT(type.fields().size() == SkToSizeT(args.size()));
+
+ for (int index = 0; index < args.size(); ++index) {
+ const std::unique_ptr<Expression>& argument = args[index];
+ const Type::Field& field = type.fields()[index];
+ if (!argument->type().matches(*field.fType)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+std::unique_ptr<Expression> ConstructorStruct::Make(const Context& context,
+ Position pos,
+ const Type& type,
+ ExpressionArray args) {
+ SkASSERT(type.isAllowedInES2(context));
+ SkASSERT(arguments_match_field_types(args, type));
+ SkASSERT(!type.isOrContainsAtomic());
+ return std::make_unique<ConstructorStruct>(pos, type, std::move(args));
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorStruct.h b/gfx/skia/skia/src/sksl/ir/SkSLConstructorStruct.h
new file mode 100644
index 0000000000..dab7c6c67d
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorStruct.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_CONSTRUCTOR_STRUCT
+#define SKSL_CONSTRUCTOR_STRUCT
+
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLConstructor.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+#include <memory>
+#include <utility>
+
+namespace SkSL {
+
+class Context;
+class Type;
+
+/**
+ * Represents the construction of an struct object, such as "Color(red, green, blue, 1)".
+ */
+class ConstructorStruct final : public MultiArgumentConstructor {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kConstructorStruct;
+
+ ConstructorStruct(Position pos, const Type& type, ExpressionArray arguments)
+ : INHERITED(pos, kIRNodeKind, &type, std::move(arguments)) {}
+
+ // ConstructorStruct::Convert will typecheck and create struct-constructor expressions.
+ // Reports errors via the ErrorReporter; returns null on error.
+ static std::unique_ptr<Expression> Convert(const Context& context,
+ Position pos,
+ const Type& type,
+ ExpressionArray args);
+
+ // ConstructorStruct::Make creates struct-constructor expressions; errors reported via SkASSERT.
+ static std::unique_ptr<Expression> Make(const Context& context,
+ Position pos,
+ const Type& type,
+ ExpressionArray args);
+
+ std::unique_ptr<Expression> clone(Position pos) const override {
+ return std::make_unique<ConstructorStruct>(pos, this->type(), this->arguments().clone());
+ }
+
+private:
+ using INHERITED = MultiArgumentConstructor;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLContinueStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLContinueStatement.h
new file mode 100644
index 0000000000..64eadbe53d
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLContinueStatement.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_CONTINUESTATEMENT
+#define SKSL_CONTINUESTATEMENT
+
+#include "include/private/SkSLStatement.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+namespace SkSL {
+
+/**
+ * A 'continue' statement.
+ */
+class ContinueStatement final : public Statement {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kContinue;
+
+ ContinueStatement(Position pos)
+ : INHERITED(pos, kIRNodeKind) {}
+
+ static std::unique_ptr<Statement> Make(Position pos) {
+ return std::make_unique<ContinueStatement>(pos);
+ }
+
+ std::unique_ptr<Statement> clone() const override {
+ return std::make_unique<ContinueStatement>(fPosition);
+ }
+
+ std::string description() const override {
+ return "continue;";
+ }
+
+private:
+ using INHERITED = Statement;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLDiscardStatement.cpp b/gfx/skia/skia/src/sksl/ir/SkSLDiscardStatement.cpp
new file mode 100644
index 0000000000..2f090219f2
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLDiscardStatement.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2022 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/core/SkTypes.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/ir/SkSLDiscardStatement.h"
+
+namespace SkSL {
+
+std::unique_ptr<Statement> DiscardStatement::Convert(const Context& context, Position pos) {
+ if (!ProgramConfig::IsFragment(context.fConfig->fKind)) {
+ context.fErrors->error(pos, "discard statement is only permitted in fragment shaders");
+ return nullptr;
+ }
+ return DiscardStatement::Make(context, pos);
+}
+
+std::unique_ptr<Statement> DiscardStatement::Make(const Context& context, Position pos) {
+ SkASSERT(ProgramConfig::IsFragment(context.fConfig->fKind));
+ return std::make_unique<DiscardStatement>(pos);
+}
+
+std::unique_ptr<Statement> DiscardStatement::clone() const {
+ return std::make_unique<DiscardStatement>(fPosition);
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLDiscardStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLDiscardStatement.h
new file mode 100644
index 0000000000..1e947d7d0f
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLDiscardStatement.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_DISCARDSTATEMENT
+#define SKSL_DISCARDSTATEMENT
+
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLStatement.h"
+#include "include/sksl/SkSLPosition.h"
+
+#include <memory>
+#include <string>
+
+namespace SkSL {
+
+class Context;
+
+/**
+ * A 'discard' statement.
+ */
+class DiscardStatement final : public Statement {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kDiscard;
+
+ DiscardStatement(Position pos) : INHERITED(pos, kIRNodeKind) {}
+
+ // Creates a discard-statement; reports errors via ErrorReporter.
+ static std::unique_ptr<Statement> Convert(const Context& context, Position pos);
+
+ // Creates a discard-statement; reports errors via SkASSERT.
+ static std::unique_ptr<Statement> Make(const Context& context, Position pos);
+
+ std::unique_ptr<Statement> clone() const override;
+
+ std::string description() const override {
+ return "discard;";
+ }
+
+private:
+ using INHERITED = Statement;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLDoStatement.cpp b/gfx/skia/skia/src/sksl/ir/SkSLDoStatement.cpp
new file mode 100644
index 0000000000..1a0a62e0aa
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLDoStatement.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLDoStatement.h"
+
+#include "include/core/SkTypes.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/ir/SkSLType.h"
+
+namespace SkSL {
+
+std::unique_ptr<Statement> DoStatement::Convert(const Context& context,
+ Position pos,
+ std::unique_ptr<Statement> stmt,
+ std::unique_ptr<Expression> test) {
+ if (context.fConfig->strictES2Mode()) {
+ context.fErrors->error(pos, "do-while loops are not supported");
+ return nullptr;
+ }
+ test = context.fTypes.fBool->coerceExpression(std::move(test), context);
+ if (!test) {
+ return nullptr;
+ }
+ if (Analysis::DetectVarDeclarationWithoutScope(*stmt, context.fErrors)) {
+ return nullptr;
+ }
+ return DoStatement::Make(context, pos, std::move(stmt), std::move(test));
+}
+
+std::unique_ptr<Statement> DoStatement::Make(const Context& context,
+ Position pos,
+ std::unique_ptr<Statement> stmt,
+ std::unique_ptr<Expression> test) {
+ SkASSERT(!context.fConfig->strictES2Mode());
+ SkASSERT(test->type().matches(*context.fTypes.fBool));
+ SkASSERT(!Analysis::DetectVarDeclarationWithoutScope(*stmt));
+ return std::make_unique<DoStatement>(pos, std::move(stmt), std::move(test));
+}
+
+std::unique_ptr<Statement> DoStatement::clone() const {
+ return std::make_unique<DoStatement>(fPosition, this->statement()->clone(),
+ this->test()->clone());
+}
+
+std::string DoStatement::description() const {
+ return "do " + this->statement()->description() +
+ " while (" + this->test()->description() + ");";
+}
+
+} // namespace SkSL
+
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLDoStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLDoStatement.h
new file mode 100644
index 0000000000..461252b38d
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLDoStatement.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_DOSTATEMENT
+#define SKSL_DOSTATEMENT
+
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLStatement.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace SkSL {
+
+class Context;
+
+/**
+ * A 'do' statement.
+ */
+class DoStatement final : public Statement {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kDo;
+
+ DoStatement(Position pos, std::unique_ptr<Statement> statement,
+ std::unique_ptr<Expression> test)
+ : INHERITED(pos, kIRNodeKind)
+ , fStatement(std::move(statement))
+ , fTest(std::move(test)) {}
+
+ // Creates an SkSL do-while loop; uses the ErrorReporter to report errors.
+ static std::unique_ptr<Statement> Convert(const Context& context,
+ Position pos,
+ std::unique_ptr<Statement> stmt,
+ std::unique_ptr<Expression> test);
+
+ // Creates an SkSL do-while loop; reports errors via ASSERT.
+ static std::unique_ptr<Statement> Make(const Context& context,
+ Position pos,
+ std::unique_ptr<Statement> stmt,
+ std::unique_ptr<Expression> test);
+
+ std::unique_ptr<Statement>& statement() {
+ return fStatement;
+ }
+
+ const std::unique_ptr<Statement>& statement() const {
+ return fStatement;
+ }
+
+ std::unique_ptr<Expression>& test() {
+ return fTest;
+ }
+
+ const std::unique_ptr<Expression>& test() const {
+ return fTest;
+ }
+
+ std::unique_ptr<Statement> clone() const override;
+
+ std::string description() const override;
+
+private:
+ std::unique_ptr<Statement> fStatement;
+ std::unique_ptr<Expression> fTest;
+
+ using INHERITED = Statement;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLExpression.cpp b/gfx/skia/skia/src/sksl/ir/SkSLExpression.cpp
new file mode 100644
index 0000000000..b6ebb7b0ec
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLExpression.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2021 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLExpression.h"
+
+#include "include/private/SkSLDefines.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "include/sksl/SkSLOperator.h"
+#include "src/sksl/SkSLContext.h"
+
+namespace SkSL {
+
+std::string Expression::description() const {
+ return this->description(OperatorPrecedence::kTopLevel);
+}
+
+bool Expression::isIncomplete(const Context& context) const {
+ switch (this->kind()) {
+ case Kind::kFunctionReference:
+ context.fErrors->error(fPosition.after(), "expected '(' to begin function call");
+ return true;
+
+ case Kind::kMethodReference:
+ context.fErrors->error(fPosition.after(), "expected '(' to begin method call");
+ return true;
+
+ case Kind::kTypeReference:
+ context.fErrors->error(fPosition.after(),
+ "expected '(' to begin constructor invocation");
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+ExpressionArray ExpressionArray::clone() const {
+ ExpressionArray cloned;
+ cloned.reserve_back(this->size());
+ for (const std::unique_ptr<Expression>& expr : *this) {
+ cloned.push_back(expr ? expr->clone() : nullptr);
+ }
+ return cloned;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLExpression.h b/gfx/skia/skia/src/sksl/ir/SkSLExpression.h
new file mode 100644
index 0000000000..6336b4a5ef
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLExpression.h
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_EXPRESSION
+#define SKSL_EXPRESSION
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLType.h"
+
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+
+namespace SkSL {
+
+class AnyConstructor;
+class Context;
+enum class OperatorPrecedence : uint8_t;
+
+/**
+ * Abstract supertype of all expressions.
+ */
+class Expression : public IRNode {
+public:
+ using Kind = ExpressionKind;
+
+ Expression(Position pos, Kind kind, const Type* type)
+ : INHERITED(pos, (int) kind)
+ , fType(type) {
+ SkASSERT(kind >= Kind::kFirst && kind <= Kind::kLast);
+ }
+
+ Kind kind() const {
+ return (Kind) fKind;
+ }
+
+ virtual const Type& type() const {
+ return *fType;
+ }
+
+ bool isAnyConstructor() const {
+ static_assert((int)Kind::kConstructorArray - 1 == (int)Kind::kChildCall);
+ static_assert((int)Kind::kConstructorStruct + 1 == (int)Kind::kFieldAccess);
+ return this->kind() >= Kind::kConstructorArray && this->kind() <= Kind::kConstructorStruct;
+ }
+
+ bool isIntLiteral() const {
+ return this->kind() == Kind::kLiteral && this->type().isInteger();
+ }
+
+ bool isFloatLiteral() const {
+ return this->kind() == Kind::kLiteral && this->type().isFloat();
+ }
+
+ bool isBoolLiteral() const {
+ return this->kind() == Kind::kLiteral && this->type().isBoolean();
+ }
+
+ AnyConstructor& asAnyConstructor();
+ const AnyConstructor& asAnyConstructor() const;
+
+ /**
+ * Returns true if this expression is incomplete. Specifically, dangling function/method-call
+ * references that were never invoked, or type references that were never constructed, are
+ * considered incomplete expressions and should result in an error.
+ */
+ bool isIncomplete(const Context& context) const;
+
+ /**
+ * Compares this constant expression against another constant expression. Returns kUnknown if
+ * we aren't able to deduce a result (an expression isn't actually constant, the types are
+ * mismatched, etc).
+ */
+ enum class ComparisonResult {
+ kUnknown = -1,
+ kNotEqual,
+ kEqual
+ };
+ virtual ComparisonResult compareConstant(const Expression& other) const {
+ return ComparisonResult::kUnknown;
+ }
+
+ CoercionCost coercionCost(const Type& target) const {
+ return this->type().coercionCost(target);
+ }
+
+ /**
+ * Returns true if this expression type supports `getConstantValue`. (This particular expression
+ * may or may not actually contain a constant value.) It's harmless to call `getConstantValue`
+ * on expressions which don't support constant values or don't contain any constant values, but
+ * if `supportsConstantValues` returns false, you can assume that `getConstantValue` will return
+ * nullopt for every slot of this expression. This allows for early-out opportunities in some
+ * cases. (Some expressions have tons of slots but never hold a constant value; e.g. a variable
+ * holding a very large array.)
+ */
+ virtual bool supportsConstantValues() const {
+ return false;
+ }
+
+ /**
+ * Returns the n'th compile-time constant value within a literal or constructor.
+ * Use Type::slotCount to determine the number of slots within an expression.
+ * Slots which do not contain compile-time constant values will return nullopt.
+ * `vec4(1, vec2(2), 3)` contains four compile-time constants: (1, 2, 2, 3)
+ * `mat2(f)` contains four slots, and two are constant: (nullopt, 0,
+ * 0, nullopt)
+ * All classes which override this function must also implement `supportsConstantValues`.
+ */
+ virtual std::optional<double> getConstantValue(int n) const {
+ SkASSERT(!this->supportsConstantValues());
+ return std::nullopt;
+ }
+
+ virtual std::unique_ptr<Expression> clone(Position pos) const = 0;
+
+ /**
+ * Returns a clone at the same position.
+ */
+ std::unique_ptr<Expression> clone() const { return this->clone(fPosition); }
+
+ /**
+ * Returns a description of the expression.
+ */
+ std::string description() const final;
+ virtual std::string description(OperatorPrecedence parentPrecedence) const = 0;
+
+
+private:
+ const Type* fType;
+
+ using INHERITED = IRNode;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLExpressionStatement.cpp b/gfx/skia/skia/src/sksl/ir/SkSLExpressionStatement.cpp
new file mode 100644
index 0000000000..7f2831644f
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLExpressionStatement.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLExpressionStatement.h"
+
+#include "include/core/SkTypes.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/ir/SkSLBinaryExpression.h"
+#include "src/sksl/ir/SkSLNop.h"
+#include "src/sksl/ir/SkSLVariableReference.h"
+
+namespace SkSL {
+
+std::unique_ptr<Statement> ExpressionStatement::Convert(const Context& context,
+ std::unique_ptr<Expression> expr) {
+ // Expression-statements need to represent a complete expression.
+ // Report an error on intermediate expressions, like FunctionReference or TypeReference.
+ if (expr->isIncomplete(context)) {
+ return nullptr;
+ }
+ return ExpressionStatement::Make(context, std::move(expr));
+}
+
+std::unique_ptr<Statement> ExpressionStatement::Make(const Context& context,
+ std::unique_ptr<Expression> expr) {
+ SkASSERT(!expr->isIncomplete(context));
+
+ if (context.fConfig->fSettings.fOptimize) {
+ // Expression-statements without any side effect can be replaced with a Nop.
+ if (!Analysis::HasSideEffects(*expr)) {
+ return Nop::Make();
+ }
+
+ // If this is an assignment statement like `a += b;`, the ref-kind of `a` will be set as
+ // read-write; `a` is written-to by the +=, and read-from by the consumer of the expression.
+ // We can demote the ref-kind to "write" safely, because the result of the expression is
+ // discarded; that is, `a` is never actually read-from.
+ if (expr->is<BinaryExpression>()) {
+ BinaryExpression& binary = expr->as<BinaryExpression>();
+ if (VariableReference* assignedVar = binary.isAssignmentIntoVariable()) {
+ if (assignedVar->refKind() == VariableRefKind::kReadWrite) {
+ assignedVar->setRefKind(VariableRefKind::kWrite);
+ }
+ }
+ }
+ }
+
+ return std::make_unique<ExpressionStatement>(std::move(expr));
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLExpressionStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLExpressionStatement.h
new file mode 100644
index 0000000000..d213ad230e
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLExpressionStatement.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_EXPRESSIONSTATEMENT
+#define SKSL_EXPRESSIONSTATEMENT
+
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLStatement.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace SkSL {
+
+class Context;
+
+/**
+ * A lone expression being used as a statement.
+ */
+class ExpressionStatement final : public Statement {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kExpression;
+
+ ExpressionStatement(std::unique_ptr<Expression> expression)
+ : INHERITED(expression->fPosition, kIRNodeKind)
+ , fExpression(std::move(expression)) {}
+
+ // Creates an SkSL expression-statement; reports errors via ErrorReporter.
+ static std::unique_ptr<Statement> Convert(const Context& context,
+ std::unique_ptr<Expression> expr);
+
+ // Creates an SkSL expression-statement; reports errors via assertion.
+ static std::unique_ptr<Statement> Make(const Context& context,
+ std::unique_ptr<Expression> expr);
+
+ const std::unique_ptr<Expression>& expression() const {
+ return fExpression;
+ }
+
+ std::unique_ptr<Expression>& expression() {
+ return fExpression;
+ }
+
+ std::unique_ptr<Statement> clone() const override {
+ return std::make_unique<ExpressionStatement>(this->expression()->clone());
+ }
+
+ std::string description() const override {
+ return this->expression()->description() + ";";
+ }
+
+private:
+ std::unique_ptr<Expression> fExpression;
+
+ using INHERITED = Statement;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLExtension.h b/gfx/skia/skia/src/sksl/ir/SkSLExtension.h
new file mode 100644
index 0000000000..94c2dd933e
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLExtension.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_EXTENSION
+#define SKSL_EXTENSION
+
+#include "include/private/SkSLProgramElement.h"
+
+namespace SkSL {
+
+/**
+ * An extension declaration.
+ */
+class Extension final : public ProgramElement {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kExtension;
+
+ Extension(Position pos, std::string_view name)
+ : INHERITED(pos, kIRNodeKind)
+ , fName(name) {}
+
+ std::string_view name() const {
+ return fName;
+ }
+
+ std::unique_ptr<ProgramElement> clone() const override {
+ return std::unique_ptr<ProgramElement>(new Extension(fPosition, this->name()));
+ }
+
+ std::string description() const override {
+ return "#extension " + std::string(this->name()) + " : enable";
+ }
+
+private:
+ std::string_view fName;
+
+ using INHERITED = ProgramElement;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLField.h b/gfx/skia/skia/src/sksl/ir/SkSLField.h
new file mode 100644
index 0000000000..a7fa6575cf
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLField.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_FIELD
+#define SKSL_FIELD
+
+#include "include/private/SkSLModifiers.h"
+#include "include/private/SkSLSymbol.h"
+#include "src/sksl/ir/SkSLType.h"
+#include "src/sksl/ir/SkSLVariable.h"
+
+namespace SkSL {
+
+/**
+ * A symbol which should be interpreted as a field access. Fields are added to the symboltable
+ * whenever a bare reference to an identifier should refer to a struct field; in GLSL, this is the
+ * result of declaring anonymous interface blocks.
+ */
+class Field final : public Symbol {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kField;
+
+ Field(Position pos, const Variable* owner, int fieldIndex)
+ : INHERITED(pos, kIRNodeKind, owner->type().fields()[fieldIndex].fName,
+ owner->type().fields()[fieldIndex].fType)
+ , fOwner(owner)
+ , fFieldIndex(fieldIndex) {}
+
+ int fieldIndex() const {
+ return fFieldIndex;
+ }
+
+ const Variable& owner() const {
+ return *fOwner;
+ }
+
+ std::string description() const override {
+ return this->owner().name().empty()
+ ? std::string(this->name())
+ : (this->owner().description() + "." + std::string(this->name()));
+ }
+
+private:
+ const Variable* fOwner;
+ int fFieldIndex;
+
+ using INHERITED = Symbol;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFieldAccess.cpp b/gfx/skia/skia/src/sksl/ir/SkSLFieldAccess.cpp
new file mode 100644
index 0000000000..5758280b7a
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLFieldAccess.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLFieldAccess.h"
+
+#include "include/core/SkSpan.h"
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLSymbol.h"
+#include "include/private/base/SkTArray.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "include/sksl/SkSLOperator.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLConstantFolder.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/ir/SkSLConstructorStruct.h"
+#include "src/sksl/ir/SkSLFunctionDeclaration.h"
+#include "src/sksl/ir/SkSLMethodReference.h"
+#include "src/sksl/ir/SkSLSetting.h"
+#include "src/sksl/ir/SkSLSymbolTable.h"
+
+#include <cstddef>
+
+namespace SkSL {
+
+std::unique_ptr<Expression> FieldAccess::Convert(const Context& context,
+ Position pos,
+ SymbolTable& symbolTable,
+ std::unique_ptr<Expression> base,
+ std::string_view field) {
+ const Type& baseType = base->type();
+ if (baseType.isEffectChild()) {
+ // Turn the field name into a free function name, prefixed with '$':
+ std::string methodName = "$" + std::string(field);
+ const Symbol* result = symbolTable.find(methodName);
+ if (result && result->is<FunctionDeclaration>()) {
+ return std::make_unique<MethodReference>(context, pos, std::move(base),
+ &result->as<FunctionDeclaration>());
+ }
+ context.fErrors->error(pos, "type '" + baseType.displayName() + "' has no method named '" +
+ std::string(field) + "'");
+ return nullptr;
+ }
+ if (baseType.isStruct()) {
+ const std::vector<Type::Field>& fields = baseType.fields();
+ for (size_t i = 0; i < fields.size(); i++) {
+ if (fields[i].fName == field) {
+ return FieldAccess::Make(context, pos, std::move(base), (int) i);
+ }
+ }
+ }
+ if (baseType.matches(*context.fTypes.fSkCaps)) {
+ return Setting::Convert(context, pos, field);
+ }
+
+ context.fErrors->error(pos, "type '" + baseType.displayName() +
+ "' does not have a field named '" + std::string(field) + "'");
+ return nullptr;
+}
+
+static std::unique_ptr<Expression> extract_field(Position pos,
+ const ConstructorStruct& ctor,
+ int fieldIndex) {
+ // Confirm that the fields that are being removed are side-effect free.
+ const ExpressionArray& args = ctor.arguments();
+ int numFields = args.size();
+ for (int index = 0; index < numFields; ++index) {
+ if (fieldIndex == index) {
+ continue;
+ }
+ if (Analysis::HasSideEffects(*args[index])) {
+ return nullptr;
+ }
+ }
+
+ // Return the desired field.
+ return args[fieldIndex]->clone(pos);
+}
+
+std::unique_ptr<Expression> FieldAccess::Make(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> base,
+ int fieldIndex,
+ OwnerKind ownerKind) {
+ SkASSERT(base->type().isStruct());
+ SkASSERT(fieldIndex >= 0);
+ SkASSERT(fieldIndex < (int)base->type().fields().size());
+
+ // Replace `knownStruct.field` with the field's value if there are no side-effects involved.
+ const Expression* expr = ConstantFolder::GetConstantValueForVariable(*base);
+ if (expr->is<ConstructorStruct>()) {
+ if (std::unique_ptr<Expression> field = extract_field(pos, expr->as<ConstructorStruct>(),
+ fieldIndex)) {
+ return field;
+ }
+ }
+
+ return std::make_unique<FieldAccess>(pos, std::move(base), fieldIndex, ownerKind);
+}
+
+size_t FieldAccess::initialSlot() const {
+ SkSpan<const Type::Field> fields = this->base()->type().fields();
+ const int fieldIndex = this->fieldIndex();
+
+ size_t slot = 0;
+ for (int index = 0; index < fieldIndex; ++index) {
+ slot += fields[index].fType->slotCount();
+ }
+ return slot;
+}
+
+std::string FieldAccess::description(OperatorPrecedence) const {
+ std::string f = this->base()->description(OperatorPrecedence::kPostfix);
+ if (!f.empty()) {
+ f.push_back('.');
+ }
+ return f + std::string(this->base()->type().fields()[this->fieldIndex()].fName);
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFieldAccess.h b/gfx/skia/skia/src/sksl/ir/SkSLFieldAccess.h
new file mode 100644
index 0000000000..8eca68fb38
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLFieldAccess.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_FIELDACCESS
+#define SKSL_FIELDACCESS
+
+#include "include/private/SkSLIRNode.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLType.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace SkSL {
+
+class Context;
+class SymbolTable;
+enum class OperatorPrecedence : uint8_t;
+
+enum class FieldAccessOwnerKind : int8_t {
+ kDefault,
+ // this field access is to a field of an anonymous interface block (and thus, the field name
+ // is actually in global scope, so only the field name needs to be written in GLSL)
+ kAnonymousInterfaceBlock
+};
+
+/**
+ * An expression which extracts a field from a struct, as in 'foo.bar'.
+ */
+class FieldAccess final : public Expression {
+public:
+ using OwnerKind = FieldAccessOwnerKind;
+
+ inline static constexpr Kind kIRNodeKind = Kind::kFieldAccess;
+
+ FieldAccess(Position pos, std::unique_ptr<Expression> base, int fieldIndex,
+ OwnerKind ownerKind = OwnerKind::kDefault)
+ : INHERITED(pos, kIRNodeKind, base->type().fields()[fieldIndex].fType)
+ , fFieldIndex(fieldIndex)
+ , fOwnerKind(ownerKind)
+ , fBase(std::move(base)) {}
+
+ // Returns a field-access expression; reports errors via the ErrorReporter.
+ static std::unique_ptr<Expression> Convert(const Context& context,
+ Position pos,
+ SymbolTable& symbolTable,
+ std::unique_ptr<Expression> base,
+ std::string_view field);
+
+ // Returns a field-access expression; reports errors via ASSERT.
+ static std::unique_ptr<Expression> Make(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> base,
+ int fieldIndex,
+ OwnerKind ownerKind = OwnerKind::kDefault);
+
+ std::unique_ptr<Expression>& base() {
+ return fBase;
+ }
+
+ const std::unique_ptr<Expression>& base() const {
+ return fBase;
+ }
+
+ int fieldIndex() const {
+ return fFieldIndex;
+ }
+
+ OwnerKind ownerKind() const {
+ return fOwnerKind;
+ }
+
+ std::unique_ptr<Expression> clone(Position pos) const override {
+ return std::make_unique<FieldAccess>(pos,
+ this->base()->clone(),
+ this->fieldIndex(),
+ this->ownerKind());
+ }
+
+ size_t initialSlot() const;
+
+ std::string description(OperatorPrecedence) const override;
+
+private:
+ int fFieldIndex;
+ FieldAccessOwnerKind fOwnerKind;
+ std::unique_ptr<Expression> fBase;
+
+ using INHERITED = Expression;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLForStatement.cpp b/gfx/skia/skia/src/sksl/ir/SkSLForStatement.cpp
new file mode 100644
index 0000000000..8777f6638c
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLForStatement.cpp
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLForStatement.h"
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLDefines.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/ir/SkSLBlock.h"
+#include "src/sksl/ir/SkSLExpressionStatement.h"
+#include "src/sksl/ir/SkSLNop.h"
+#include "src/sksl/ir/SkSLSymbolTable.h"
+#include "src/sksl/ir/SkSLType.h"
+#include "src/sksl/ir/SkSLVarDeclarations.h"
+
+namespace SkSL {
+
+static bool is_vardecl_block_initializer(const Statement* stmt) {
+ if (!stmt) {
+ return false;
+ }
+ if (!stmt->is<SkSL::Block>()) {
+ return false;
+ }
+ const SkSL::Block& b = stmt->as<SkSL::Block>();
+ if (b.isScope()) {
+ return false;
+ }
+ for (const auto& child : b.children()) {
+ if (!child->is<SkSL::VarDeclaration>()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool is_simple_initializer(const Statement* stmt) {
+ return !stmt || stmt->isEmpty() || stmt->is<SkSL::VarDeclaration>() ||
+ stmt->is<SkSL::ExpressionStatement>();
+}
+
+std::unique_ptr<Statement> ForStatement::clone() const {
+ std::unique_ptr<LoopUnrollInfo> unrollInfo;
+ if (fUnrollInfo) {
+ unrollInfo = std::make_unique<LoopUnrollInfo>(*fUnrollInfo);
+ }
+
+ return std::make_unique<ForStatement>(
+ fPosition,
+ fForLoopPositions,
+ this->initializer() ? this->initializer()->clone() : nullptr,
+ this->test() ? this->test()->clone() : nullptr,
+ this->next() ? this->next()->clone() : nullptr,
+ this->statement()->clone(),
+ std::move(unrollInfo),
+ SymbolTable::WrapIfBuiltin(this->symbols()));
+}
+
+std::string ForStatement::description() const {
+ std::string result("for (");
+ if (this->initializer()) {
+ result += this->initializer()->description();
+ } else {
+ result += ";";
+ }
+ result += " ";
+ if (this->test()) {
+ result += this->test()->description();
+ }
+ result += "; ";
+ if (this->next()) {
+ result += this->next()->description();
+ }
+ result += ") " + this->statement()->description();
+ return result;
+}
+
+std::unique_ptr<Statement> ForStatement::Convert(const Context& context,
+ Position pos,
+ ForLoopPositions positions,
+ std::unique_ptr<Statement> initializer,
+ std::unique_ptr<Expression> test,
+ std::unique_ptr<Expression> next,
+ std::unique_ptr<Statement> statement,
+ std::shared_ptr<SymbolTable> symbolTable) {
+ bool isSimpleInitializer = is_simple_initializer(initializer.get());
+ bool isVardeclBlockInitializer =
+ !isSimpleInitializer && is_vardecl_block_initializer(initializer.get());
+
+ if (!isSimpleInitializer && !isVardeclBlockInitializer) {
+ context.fErrors->error(initializer->fPosition, "invalid for loop initializer");
+ return nullptr;
+ }
+
+ if (test) {
+ test = context.fTypes.fBool->coerceExpression(std::move(test), context);
+ if (!test) {
+ return nullptr;
+ }
+ }
+
+ // The type of the next-expression doesn't matter, but it needs to be a complete expression.
+ // Report an error on intermediate expressions like FunctionReference or TypeReference.
+ if (next && next->isIncomplete(context)) {
+ return nullptr;
+ }
+
+ std::unique_ptr<LoopUnrollInfo> unrollInfo;
+ if (context.fConfig->strictES2Mode()) {
+ // In strict-ES2, loops must be unrollable or it's an error.
+ unrollInfo = Analysis::GetLoopUnrollInfo(pos, positions, initializer.get(), test.get(),
+ next.get(), statement.get(), context.fErrors);
+ if (!unrollInfo) {
+ return nullptr;
+ }
+ } else {
+ // In ES3, loops don't have to be unrollable, but we can use the unroll information for
+ // optimization purposes.
+ unrollInfo = Analysis::GetLoopUnrollInfo(pos, positions, initializer.get(), test.get(),
+ next.get(), statement.get(), /*errors=*/nullptr);
+ }
+
+ if (Analysis::DetectVarDeclarationWithoutScope(*statement, context.fErrors)) {
+ return nullptr;
+ }
+
+ if (isVardeclBlockInitializer) {
+ // If the initializer statement of a for loop contains multiple variables, this causes
+ // difficulties for several of our backends; e.g. Metal doesn't have a way to express arrays
+ // of different size in the same decl-stmt, because the array-size is part of the type. It's
+ // conceptually equivalent to synthesize a scope, declare the variables, and then emit a for
+ // statement with an empty init-stmt. (Note that we can't just do this transformation
+ // unilaterally for all for-statements, because the resulting for loop isn't ES2-compliant.)
+ StatementArray scope;
+ scope.push_back(std::move(initializer));
+ scope.push_back(ForStatement::Make(context, pos, positions, /*initializer=*/nullptr,
+ std::move(test), std::move(next), std::move(statement),
+ std::move(unrollInfo), /*symbolTable=*/nullptr));
+ return Block::Make(pos, std::move(scope), Block::Kind::kBracedScope,
+ std::move(symbolTable));
+ }
+
+ return ForStatement::Make(context, pos, positions, std::move(initializer), std::move(test),
+ std::move(next), std::move(statement), std::move(unrollInfo),
+ std::move(symbolTable));
+}
+
+std::unique_ptr<Statement> ForStatement::ConvertWhile(const Context& context, Position pos,
+ std::unique_ptr<Expression> test,
+ std::unique_ptr<Statement> statement,
+ std::shared_ptr<SymbolTable> symbolTable) {
+ if (context.fConfig->strictES2Mode()) {
+ context.fErrors->error(pos, "while loops are not supported");
+ return nullptr;
+ }
+ return ForStatement::Convert(context, pos, ForLoopPositions(), /*initializer=*/nullptr,
+ std::move(test), /*next=*/nullptr, std::move(statement), std::move(symbolTable));
+}
+
+std::unique_ptr<Statement> ForStatement::Make(const Context& context,
+ Position pos,
+ ForLoopPositions positions,
+ std::unique_ptr<Statement> initializer,
+ std::unique_ptr<Expression> test,
+ std::unique_ptr<Expression> next,
+ std::unique_ptr<Statement> statement,
+ std::unique_ptr<LoopUnrollInfo> unrollInfo,
+ std::shared_ptr<SymbolTable> symbolTable) {
+ SkASSERT(is_simple_initializer(initializer.get()) ||
+ is_vardecl_block_initializer(initializer.get()));
+ SkASSERT(!test || test->type().matches(*context.fTypes.fBool));
+ SkASSERT(!Analysis::DetectVarDeclarationWithoutScope(*statement));
+ SkASSERT(unrollInfo || !context.fConfig->strictES2Mode());
+
+ // Unrollable loops are easy to optimize because we know initializer, test and next don't have
+ // interesting side effects.
+ if (unrollInfo) {
+ // A zero-iteration unrollable loop can be replaced with Nop.
+ // An unrollable loop with an empty body can be replaced with Nop.
+ if (unrollInfo->fCount <= 0 || statement->isEmpty()) {
+ return Nop::Make();
+ }
+ }
+
+ return std::make_unique<ForStatement>(pos, positions, std::move(initializer), std::move(test),
+ std::move(next), std::move(statement), std::move(unrollInfo), std::move(symbolTable));
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLForStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLForStatement.h
new file mode 100644
index 0000000000..468df41cbc
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLForStatement.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_FORSTATEMENT
+#define SKSL_FORSTATEMENT
+
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLStatement.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace SkSL {
+
+class Context;
+class SymbolTable;
+class Variable;
+
+/**
+ * The unrollability information for an ES2-compatible loop.
+ */
+struct LoopUnrollInfo {
+ const Variable* fIndex;
+ double fStart;
+ double fDelta;
+ int fCount;
+};
+
+/**
+ * A 'for' statement.
+ */
+class ForStatement final : public Statement {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kFor;
+
+ ForStatement(Position pos,
+ ForLoopPositions forLoopPositions,
+ std::unique_ptr<Statement> initializer,
+ std::unique_ptr<Expression> test,
+ std::unique_ptr<Expression> next,
+ std::unique_ptr<Statement> statement,
+ std::unique_ptr<LoopUnrollInfo> unrollInfo,
+ std::shared_ptr<SymbolTable> symbols)
+ : INHERITED(pos, kIRNodeKind)
+ , fForLoopPositions(forLoopPositions)
+ , fSymbolTable(std::move(symbols))
+ , fInitializer(std::move(initializer))
+ , fTest(std::move(test))
+ , fNext(std::move(next))
+ , fStatement(std::move(statement))
+ , fUnrollInfo(std::move(unrollInfo)) {}
+
+ // Creates an SkSL for loop; handles type-coercion and uses the ErrorReporter to report errors.
+ static std::unique_ptr<Statement> Convert(const Context& context,
+ Position pos,
+ ForLoopPositions forLoopPositions,
+ std::unique_ptr<Statement> initializer,
+ std::unique_ptr<Expression> test,
+ std::unique_ptr<Expression> next,
+ std::unique_ptr<Statement> statement,
+ std::shared_ptr<SymbolTable> symbolTable);
+
+ // Creates an SkSL while loop; handles type-coercion and uses the ErrorReporter for errors.
+ static std::unique_ptr<Statement> ConvertWhile(const Context& context, Position pos,
+ std::unique_ptr<Expression> test,
+ std::unique_ptr<Statement> statement,
+ std::shared_ptr<SymbolTable> symbolTable);
+
+ // Creates an SkSL for/while loop. Assumes properly coerced types and reports errors via assert.
+ static std::unique_ptr<Statement> Make(const Context& context,
+ Position pos,
+ ForLoopPositions forLoopPositions,
+ std::unique_ptr<Statement> initializer,
+ std::unique_ptr<Expression> test,
+ std::unique_ptr<Expression> next,
+ std::unique_ptr<Statement> statement,
+ std::unique_ptr<LoopUnrollInfo> unrollInfo,
+ std::shared_ptr<SymbolTable> symbolTable);
+
+ ForLoopPositions forLoopPositions() const {
+ return fForLoopPositions;
+ }
+
+ std::unique_ptr<Statement>& initializer() {
+ return fInitializer;
+ }
+
+ const std::unique_ptr<Statement>& initializer() const {
+ return fInitializer;
+ }
+
+ std::unique_ptr<Expression>& test() {
+ return fTest;
+ }
+
+ const std::unique_ptr<Expression>& test() const {
+ return fTest;
+ }
+
+ std::unique_ptr<Expression>& next() {
+ return fNext;
+ }
+
+ const std::unique_ptr<Expression>& next() const {
+ return fNext;
+ }
+
+ std::unique_ptr<Statement>& statement() {
+ return fStatement;
+ }
+
+ const std::unique_ptr<Statement>& statement() const {
+ return fStatement;
+ }
+
+ const std::shared_ptr<SymbolTable>& symbols() const {
+ return fSymbolTable;
+ }
+
+ /** Loop-unroll information is only supported in strict-ES2 code. Null is returned in ES3+. */
+ const LoopUnrollInfo* unrollInfo() const {
+ return fUnrollInfo.get();
+ }
+
+ std::unique_ptr<Statement> clone() const override;
+
+ std::string description() const override;
+
+private:
+ ForLoopPositions fForLoopPositions;
+ std::shared_ptr<SymbolTable> fSymbolTable;
+ std::unique_ptr<Statement> fInitializer;
+ std::unique_ptr<Expression> fTest;
+ std::unique_ptr<Expression> fNext;
+ std::unique_ptr<Statement> fStatement;
+ std::unique_ptr<LoopUnrollInfo> fUnrollInfo;
+
+ using INHERITED = Statement;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFunctionCall.cpp b/gfx/skia/skia/src/sksl/ir/SkSLFunctionCall.cpp
new file mode 100644
index 0000000000..69df07db9d
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLFunctionCall.cpp
@@ -0,0 +1,1056 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLFunctionCall.h"
+
+#include "include/core/SkSpan.h"
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLModifiers.h"
+#include "include/private/SkSLString.h"
+#include "include/private/base/SkFloatingPoint.h"
+#include "include/private/base/SkTArray.h"
+#include "include/private/base/SkTo.h"
+#include "include/sksl/DSLCore.h"
+#include "include/sksl/DSLExpression.h"
+#include "include/sksl/DSLType.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "include/sksl/SkSLOperator.h"
+#include "src/base/SkHalf.h"
+#include "src/core/SkMatrixInvert.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLConstantFolder.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLIntrinsicList.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/ir/SkSLChildCall.h"
+#include "src/sksl/ir/SkSLConstructor.h"
+#include "src/sksl/ir/SkSLConstructorCompound.h"
+#include "src/sksl/ir/SkSLFunctionDeclaration.h"
+#include "src/sksl/ir/SkSLFunctionReference.h"
+#include "src/sksl/ir/SkSLLiteral.h"
+#include "src/sksl/ir/SkSLMethodReference.h"
+#include "src/sksl/ir/SkSLType.h"
+#include "src/sksl/ir/SkSLTypeReference.h"
+#include "src/sksl/ir/SkSLVariable.h"
+#include "src/sksl/ir/SkSLVariableReference.h"
+
+#include <algorithm>
+#include <array>
+#include <cmath>
+#include <cstdint>
+#include <cstring>
+#include <optional>
+#include <string_view>
+#include <vector>
+
+namespace SkSL {
+
+using IntrinsicArguments = std::array<const Expression*, 3>;
+
+static bool has_compile_time_constant_arguments(const ExpressionArray& arguments) {
+ for (const std::unique_ptr<Expression>& arg : arguments) {
+ const Expression* expr = ConstantFolder::GetConstantValueForVariable(*arg);
+ if (!Analysis::IsCompileTimeConstant(*expr)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+template <typename T>
+static void type_check_expression(const Expression& expr);
+
+template <>
+void type_check_expression<float>(const Expression& expr) {
+ SkASSERT(expr.type().componentType().isFloat());
+}
+
+template <>
+void type_check_expression<SKSL_INT>(const Expression& expr) {
+ SkASSERT(expr.type().componentType().isInteger());
+}
+
+template <>
+void type_check_expression<bool>(const Expression& expr) {
+ SkASSERT(expr.type().componentType().isBoolean());
+}
+
+static std::unique_ptr<Expression> assemble_compound(const Context& context,
+ Position pos,
+ const Type& returnType,
+ double value[]) {
+ int numSlots = returnType.slotCount();
+ ExpressionArray array;
+ array.reserve_back(numSlots);
+ for (int index = 0; index < numSlots; ++index) {
+ array.push_back(Literal::Make(pos, value[index], &returnType.componentType()));
+ }
+ return ConstructorCompound::Make(context, pos, returnType, std::move(array));
+}
+
+using CoalesceFn = double (*)(double, double, double);
+using FinalizeFn = double (*)(double);
+
+static std::unique_ptr<Expression> coalesce_n_way_vector(const Expression* arg0,
+ const Expression* arg1,
+ double startingState,
+ const Type& returnType,
+ CoalesceFn coalesce,
+ FinalizeFn finalize) {
+ // Takes up to two vector or scalar arguments and coalesces them in sequence:
+ // scalar = startingState;
+ // scalar = coalesce(scalar, arg0.x, arg1.x);
+ // scalar = coalesce(scalar, arg0.y, arg1.y);
+ // scalar = coalesce(scalar, arg0.z, arg1.z);
+ // scalar = coalesce(scalar, arg0.w, arg1.w);
+ // scalar = finalize(scalar);
+ //
+ // If an argument is null, zero is passed to the coalesce function. If the arguments are a mix
+ // of scalars and vectors, the scalars is interpreted as a vector containing the same value for
+ // every component.
+
+ Position pos = arg0->fPosition;
+ double minimumValue = returnType.componentType().minimumValue();
+ double maximumValue = returnType.componentType().maximumValue();
+
+ const Type& vecType = arg0->type().isVector() ? arg0->type() :
+ (arg1 && arg1->type().isVector()) ? arg1->type() :
+ arg0->type();
+ SkASSERT( arg0->type().componentType().matches(vecType.componentType()));
+ SkASSERT(!arg1 || arg1->type().componentType().matches(vecType.componentType()));
+
+ double value = startingState;
+ int arg0Index = 0;
+ int arg1Index = 0;
+ for (int index = 0; index < vecType.columns(); ++index) {
+ std::optional<double> arg0Value = arg0->getConstantValue(arg0Index);
+ arg0Index += arg0->type().isVector() ? 1 : 0;
+ SkASSERT(arg0Value.has_value());
+
+ std::optional<double> arg1Value = 0.0;
+ if (arg1) {
+ arg1Value = arg1->getConstantValue(arg1Index);
+ arg1Index += arg1->type().isVector() ? 1 : 0;
+ SkASSERT(arg1Value.has_value());
+ }
+
+ value = coalesce(value, *arg0Value, *arg1Value);
+
+ if (value >= minimumValue && value <= maximumValue) {
+ // This result will fit inside the return type.
+ } else {
+ // The value is outside the float range or is NaN (all if-checks fail); do not optimize.
+ return nullptr;
+ }
+ }
+
+ if (finalize) {
+ value = finalize(value);
+ }
+
+ return Literal::Make(pos, value, &returnType);
+}
+
+template <typename T>
+static std::unique_ptr<Expression> coalesce_vector(const IntrinsicArguments& arguments,
+ double startingState,
+ const Type& returnType,
+ CoalesceFn coalesce,
+ FinalizeFn finalize) {
+ SkASSERT(arguments[0]);
+ SkASSERT(!arguments[1]);
+ type_check_expression<T>(*arguments[0]);
+
+ return coalesce_n_way_vector(arguments[0], /*arg1=*/nullptr,
+ startingState, returnType, coalesce, finalize);
+}
+
+template <typename T>
+static std::unique_ptr<Expression> coalesce_pairwise_vectors(const IntrinsicArguments& arguments,
+ double startingState,
+ const Type& returnType,
+ CoalesceFn coalesce,
+ FinalizeFn finalize) {
+ SkASSERT(arguments[0]);
+ SkASSERT(arguments[1]);
+ SkASSERT(!arguments[2]);
+ type_check_expression<T>(*arguments[0]);
+ type_check_expression<T>(*arguments[1]);
+
+ return coalesce_n_way_vector(arguments[0], arguments[1],
+ startingState, returnType, coalesce, finalize);
+}
+
+using CompareFn = bool (*)(double, double);
+
+static std::unique_ptr<Expression> optimize_comparison(const Context& context,
+ const IntrinsicArguments& arguments,
+ CompareFn compare) {
+ const Expression* left = arguments[0];
+ const Expression* right = arguments[1];
+ SkASSERT(left);
+ SkASSERT(right);
+ SkASSERT(!arguments[2]);
+
+ const Type& type = left->type();
+ SkASSERT(type.isVector());
+ SkASSERT(type.componentType().isScalar());
+ SkASSERT(type.matches(right->type()));
+
+ double array[4];
+
+ for (int index = 0; index < type.columns(); ++index) {
+ std::optional<double> leftValue = left->getConstantValue(index);
+ std::optional<double> rightValue = right->getConstantValue(index);
+ SkASSERT(leftValue.has_value());
+ SkASSERT(rightValue.has_value());
+ array[index] = compare(*leftValue, *rightValue) ? 1.0 : 0.0;
+ }
+
+ const Type& bvecType = context.fTypes.fBool->toCompound(context, type.columns(), /*rows=*/1);
+ return assemble_compound(context, left->fPosition, bvecType, array);
+}
+
+using EvaluateFn = double (*)(double, double, double);
+
+static std::unique_ptr<Expression> evaluate_n_way_intrinsic(const Context& context,
+ const Expression* arg0,
+ const Expression* arg1,
+ const Expression* arg2,
+ const Type& returnType,
+ EvaluateFn eval) {
+ // Takes up to three arguments and evaluates all of them, left-to-right, in tandem.
+ // Equivalent to constructing a new compound value containing the results from:
+ // eval(arg0.x, arg1.x, arg2.x),
+ // eval(arg0.y, arg1.y, arg2.y),
+ // eval(arg0.z, arg1.z, arg2.z),
+ // eval(arg0.w, arg1.w, arg2.w)
+ //
+ // If an argument is null, zero is passed to the evaluation function. If the arguments are a mix
+ // of scalars and compounds, scalars are interpreted as a compound containing the same value for
+ // every component.
+
+ double minimumValue = returnType.componentType().minimumValue();
+ double maximumValue = returnType.componentType().maximumValue();
+ int slots = returnType.slotCount();
+ double array[16];
+
+ int arg0Index = 0;
+ int arg1Index = 0;
+ int arg2Index = 0;
+ for (int index = 0; index < slots; ++index) {
+ std::optional<double> arg0Value = arg0->getConstantValue(arg0Index);
+ arg0Index += arg0->type().isScalar() ? 0 : 1;
+ SkASSERT(arg0Value.has_value());
+
+ std::optional<double> arg1Value = 0.0;
+ if (arg1) {
+ arg1Value = arg1->getConstantValue(arg1Index);
+ arg1Index += arg1->type().isScalar() ? 0 : 1;
+ SkASSERT(arg1Value.has_value());
+ }
+
+ std::optional<double> arg2Value = 0.0;
+ if (arg2) {
+ arg2Value = arg2->getConstantValue(arg2Index);
+ arg2Index += arg2->type().isScalar() ? 0 : 1;
+ SkASSERT(arg2Value.has_value());
+ }
+
+ array[index] = eval(*arg0Value, *arg1Value, *arg2Value);
+
+ if (array[index] >= minimumValue && array[index] <= maximumValue) {
+ // This result will fit inside the return type.
+ } else {
+ // The value is outside the float range or is NaN (all if-checks fail); do not optimize.
+ return nullptr;
+ }
+ }
+
+ return assemble_compound(context, arg0->fPosition, returnType, array);
+}
+
+template <typename T>
+static std::unique_ptr<Expression> evaluate_intrinsic(const Context& context,
+ const IntrinsicArguments& arguments,
+ const Type& returnType,
+ EvaluateFn eval) {
+ SkASSERT(arguments[0]);
+ SkASSERT(!arguments[1]);
+ type_check_expression<T>(*arguments[0]);
+
+ return evaluate_n_way_intrinsic(context, arguments[0], /*arg1=*/nullptr, /*arg2=*/nullptr,
+ returnType, eval);
+}
+
+static std::unique_ptr<Expression> evaluate_intrinsic_numeric(const Context& context,
+ const IntrinsicArguments& arguments,
+ const Type& returnType,
+ EvaluateFn eval) {
+ SkASSERT(arguments[0]);
+ SkASSERT(!arguments[1]);
+ const Type& type = arguments[0]->type().componentType();
+
+ if (type.isFloat()) {
+ return evaluate_intrinsic<float>(context, arguments, returnType, eval);
+ }
+ if (type.isInteger()) {
+ return evaluate_intrinsic<SKSL_INT>(context, arguments, returnType, eval);
+ }
+
+ SkDEBUGFAILF("unsupported type %s", type.description().c_str());
+ return nullptr;
+}
+
+static std::unique_ptr<Expression> evaluate_pairwise_intrinsic(const Context& context,
+ const IntrinsicArguments& arguments,
+ const Type& returnType,
+ EvaluateFn eval) {
+ SkASSERT(arguments[0]);
+ SkASSERT(arguments[1]);
+ SkASSERT(!arguments[2]);
+ const Type& type = arguments[0]->type().componentType();
+
+ if (type.isFloat()) {
+ type_check_expression<float>(*arguments[0]);
+ type_check_expression<float>(*arguments[1]);
+ } else if (type.isInteger()) {
+ type_check_expression<SKSL_INT>(*arguments[0]);
+ type_check_expression<SKSL_INT>(*arguments[1]);
+ } else {
+ SkDEBUGFAILF("unsupported type %s", type.description().c_str());
+ return nullptr;
+ }
+
+ return evaluate_n_way_intrinsic(context, arguments[0], arguments[1], /*arg2=*/nullptr,
+ returnType, eval);
+}
+
+static std::unique_ptr<Expression> evaluate_3_way_intrinsic(const Context& context,
+ const IntrinsicArguments& arguments,
+ const Type& returnType,
+ EvaluateFn eval) {
+ SkASSERT(arguments[0]);
+ SkASSERT(arguments[1]);
+ SkASSERT(arguments[2]);
+ const Type& type = arguments[0]->type().componentType();
+
+ if (type.isFloat()) {
+ type_check_expression<float>(*arguments[0]);
+ type_check_expression<float>(*arguments[1]);
+ type_check_expression<float>(*arguments[2]);
+ } else if (type.isInteger()) {
+ type_check_expression<SKSL_INT>(*arguments[0]);
+ type_check_expression<SKSL_INT>(*arguments[1]);
+ type_check_expression<SKSL_INT>(*arguments[2]);
+ } else {
+ SkDEBUGFAILF("unsupported type %s", type.description().c_str());
+ return nullptr;
+ }
+
+ return evaluate_n_way_intrinsic(context, arguments[0], arguments[1], arguments[2],
+ returnType, eval);
+}
+
+template <typename T1, typename T2>
+static double pun_value(double val) {
+ // Interpret `val` as a value of type T1.
+ static_assert(sizeof(T1) == sizeof(T2));
+ T1 inputValue = (T1)val;
+ // Reinterpret those bits as a value of type T2.
+ T2 outputValue;
+ memcpy(&outputValue, &inputValue, sizeof(T2));
+ // Return the value-of-type-T2 as a double. (Non-finite values will prohibit optimization.)
+ return (double)outputValue;
+}
+
+// Helper functions for optimizing all of our intrinsics.
+namespace Intrinsics {
+namespace {
+
+double coalesce_length(double a, double b, double) { return a + (b * b); }
+double finalize_length(double a) { return std::sqrt(a); }
+
+double coalesce_distance(double a, double b, double c) { b -= c; return a + (b * b); }
+double finalize_distance(double a) { return std::sqrt(a); }
+
+double coalesce_dot(double a, double b, double c) { return a + (b * c); }
+double coalesce_any(double a, double b, double) { return a || b; }
+double coalesce_all(double a, double b, double) { return a && b; }
+
+bool compare_lessThan(double a, double b) { return a < b; }
+bool compare_lessThanEqual(double a, double b) { return a <= b; }
+bool compare_greaterThan(double a, double b) { return a > b; }
+bool compare_greaterThanEqual(double a, double b) { return a >= b; }
+bool compare_equal(double a, double b) { return a == b; }
+bool compare_notEqual(double a, double b) { return a != b; }
+
+double evaluate_radians(double a, double, double) { return a * 0.0174532925; }
+double evaluate_degrees(double a, double, double) { return a * 57.2957795; }
+double evaluate_sin(double a, double, double) { return std::sin(a); }
+double evaluate_cos(double a, double, double) { return std::cos(a); }
+double evaluate_tan(double a, double, double) { return std::tan(a); }
+double evaluate_asin(double a, double, double) { return std::asin(a); }
+double evaluate_acos(double a, double, double) { return std::acos(a); }
+double evaluate_atan(double a, double, double) { return std::atan(a); }
+double evaluate_atan2(double a, double b, double) { return std::atan2(a, b); }
+double evaluate_asinh(double a, double, double) { return std::asinh(a); }
+double evaluate_acosh(double a, double, double) { return std::acosh(a); }
+double evaluate_atanh(double a, double, double) { return std::atanh(a); }
+
+double evaluate_pow(double a, double b, double) { return std::pow(a, b); }
+double evaluate_exp(double a, double, double) { return std::exp(a); }
+double evaluate_log(double a, double, double) { return std::log(a); }
+double evaluate_exp2(double a, double, double) { return std::exp2(a); }
+double evaluate_log2(double a, double, double) { return std::log2(a); }
+double evaluate_sqrt(double a, double, double) { return std::sqrt(a); }
+double evaluate_inversesqrt(double a, double, double) {
+ return sk_ieee_double_divide(1.0, std::sqrt(a));
+}
+
+double evaluate_abs(double a, double, double) { return std::abs(a); }
+double evaluate_sign(double a, double, double) { return (a > 0) - (a < 0); }
+double evaluate_floor(double a, double, double) { return std::floor(a); }
+double evaluate_ceil(double a, double, double) { return std::ceil(a); }
+double evaluate_fract(double a, double, double) { return a - std::floor(a); }
+double evaluate_min(double a, double b, double) { return (a < b) ? a : b; }
+double evaluate_max(double a, double b, double) { return (a > b) ? a : b; }
+double evaluate_clamp(double x, double l, double h) { return (x < l) ? l : (x > h) ? h : x; }
+double evaluate_fma(double a, double b, double c) { return a * b + c; }
+double evaluate_saturate(double a, double, double) { return (a < 0) ? 0 : (a > 1) ? 1 : a; }
+double evaluate_mix(double x, double y, double a) { return x * (1 - a) + y * a; }
+double evaluate_step(double e, double x, double) { return (x < e) ? 0 : 1; }
+double evaluate_mod(double a, double b, double) {
+ return a - b * std::floor(sk_ieee_double_divide(a, b));
+}
+double evaluate_smoothstep(double edge0, double edge1, double x) {
+ double t = sk_ieee_double_divide(x - edge0, edge1 - edge0);
+ t = (t < 0) ? 0 : (t > 1) ? 1 : t;
+ return t * t * (3.0 - 2.0 * t);
+}
+
+double evaluate_matrixCompMult(double x, double y, double) { return x * y; }
+
+double evaluate_not(double a, double, double) { return !a; }
+double evaluate_sinh(double a, double, double) { return std::sinh(a); }
+double evaluate_cosh(double a, double, double) { return std::cosh(a); }
+double evaluate_tanh(double a, double, double) { return std::tanh(a); }
+double evaluate_trunc(double a, double, double) { return std::trunc(a); }
+double evaluate_round(double a, double, double) {
+ // The semantics of std::remainder guarantee a rounded-to-even result here, regardless of the
+ // current float-rounding mode.
+ return a - std::remainder(a, 1.0);
+}
+double evaluate_floatBitsToInt(double a, double, double) { return pun_value<float, int32_t> (a); }
+double evaluate_floatBitsToUint(double a, double, double) { return pun_value<float, uint32_t>(a); }
+double evaluate_intBitsToFloat(double a, double, double) { return pun_value<int32_t, float>(a); }
+double evaluate_uintBitsToFloat(double a, double, double) { return pun_value<uint32_t, float>(a); }
+
+} // namespace
+} // namespace Intrinsics
+
+static void extract_matrix(const Expression* expr, float mat[16]) {
+ size_t numSlots = expr->type().slotCount();
+ for (size_t index = 0; index < numSlots; ++index) {
+ mat[index] = *expr->getConstantValue(index);
+ }
+}
+
+static std::unique_ptr<Expression> optimize_intrinsic_call(const Context& context,
+ Position pos,
+ IntrinsicKind intrinsic,
+ const ExpressionArray& argArray,
+ const Type& returnType) {
+ // Replace constant variables with their literal values.
+ IntrinsicArguments arguments = {};
+ SkASSERT(SkToSizeT(argArray.size()) <= arguments.size());
+ for (int index = 0; index < argArray.size(); ++index) {
+ arguments[index] = ConstantFolder::GetConstantValueForVariable(*argArray[index]);
+ }
+
+ auto Get = [&](int idx, int col) -> float {
+ return *arguments[idx]->getConstantValue(col);
+ };
+
+ using namespace SkSL::dsl;
+ switch (intrinsic) {
+ // 8.1 : Angle and Trigonometry Functions
+ case k_radians_IntrinsicKind:
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_radians);
+ case k_degrees_IntrinsicKind:
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_degrees);
+ case k_sin_IntrinsicKind:
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_sin);
+ case k_cos_IntrinsicKind:
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_cos);
+ case k_tan_IntrinsicKind:
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_tan);
+ case k_sinh_IntrinsicKind:
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_sinh);
+ case k_cosh_IntrinsicKind:
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_cosh);
+ case k_tanh_IntrinsicKind:
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_tanh);
+ case k_asin_IntrinsicKind:
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_asin);
+ case k_acos_IntrinsicKind:
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_acos);
+ case k_atan_IntrinsicKind:
+ if (argArray.size() == 1) {
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_atan);
+ } else {
+ return evaluate_pairwise_intrinsic(context, arguments, returnType,
+ Intrinsics::evaluate_atan2);
+ }
+ case k_asinh_IntrinsicKind:
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_asinh);
+
+ case k_acosh_IntrinsicKind:
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_acosh);
+ case k_atanh_IntrinsicKind:
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_atanh);
+ // 8.2 : Exponential Functions
+ case k_pow_IntrinsicKind:
+ return evaluate_pairwise_intrinsic(context, arguments, returnType,
+ Intrinsics::evaluate_pow);
+ case k_exp_IntrinsicKind:
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_exp);
+ case k_log_IntrinsicKind:
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_log);
+ case k_exp2_IntrinsicKind:
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_exp2);
+ case k_log2_IntrinsicKind:
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_log2);
+ case k_sqrt_IntrinsicKind:
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_sqrt);
+ case k_inversesqrt_IntrinsicKind:
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_inversesqrt);
+ // 8.3 : Common Functions
+ case k_abs_IntrinsicKind:
+ return evaluate_intrinsic_numeric(context, arguments, returnType,
+ Intrinsics::evaluate_abs);
+ case k_sign_IntrinsicKind:
+ return evaluate_intrinsic_numeric(context, arguments, returnType,
+ Intrinsics::evaluate_sign);
+ case k_floor_IntrinsicKind:
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_floor);
+ case k_ceil_IntrinsicKind:
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_ceil);
+ case k_fract_IntrinsicKind:
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_fract);
+ case k_mod_IntrinsicKind:
+ return evaluate_pairwise_intrinsic(context, arguments, returnType,
+ Intrinsics::evaluate_mod);
+ case k_min_IntrinsicKind:
+ return evaluate_pairwise_intrinsic(context, arguments, returnType,
+ Intrinsics::evaluate_min);
+ case k_max_IntrinsicKind:
+ return evaluate_pairwise_intrinsic(context, arguments, returnType,
+ Intrinsics::evaluate_max);
+ case k_clamp_IntrinsicKind:
+ return evaluate_3_way_intrinsic(context, arguments, returnType,
+ Intrinsics::evaluate_clamp);
+ case k_fma_IntrinsicKind:
+ return evaluate_3_way_intrinsic(context, arguments, returnType,
+ Intrinsics::evaluate_fma);
+ case k_saturate_IntrinsicKind:
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_saturate);
+ case k_mix_IntrinsicKind:
+ if (arguments[2]->type().componentType().isBoolean()) {
+ const SkSL::Type& numericType = arguments[0]->type().componentType();
+
+ if (numericType.isFloat()) {
+ type_check_expression<float>(*arguments[0]);
+ type_check_expression<float>(*arguments[1]);
+ } else if (numericType.isInteger()) {
+ type_check_expression<SKSL_INT>(*arguments[0]);
+ type_check_expression<SKSL_INT>(*arguments[1]);
+ } else if (numericType.isBoolean()) {
+ type_check_expression<bool>(*arguments[0]);
+ type_check_expression<bool>(*arguments[1]);
+ } else {
+ SkDEBUGFAILF("unsupported type %s", numericType.description().c_str());
+ return nullptr;
+ }
+ return evaluate_n_way_intrinsic(context, arguments[0], arguments[1], arguments[2],
+ returnType, Intrinsics::evaluate_mix);
+ } else {
+ return evaluate_3_way_intrinsic(context, arguments, returnType,
+ Intrinsics::evaluate_mix);
+ }
+ case k_step_IntrinsicKind:
+ return evaluate_pairwise_intrinsic(context, arguments, returnType,
+ Intrinsics::evaluate_step);
+ case k_smoothstep_IntrinsicKind:
+ return evaluate_3_way_intrinsic(context, arguments, returnType,
+ Intrinsics::evaluate_smoothstep);
+ case k_trunc_IntrinsicKind:
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_trunc);
+ case k_round_IntrinsicKind: // GLSL `round` documents its rounding mode as unspecified
+ case k_roundEven_IntrinsicKind: // and is allowed to behave identically to `roundEven`.
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_round);
+ case k_floatBitsToInt_IntrinsicKind:
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_floatBitsToInt);
+ case k_floatBitsToUint_IntrinsicKind:
+ return evaluate_intrinsic<float>(context, arguments, returnType,
+ Intrinsics::evaluate_floatBitsToUint);
+ case k_intBitsToFloat_IntrinsicKind:
+ return evaluate_intrinsic<SKSL_INT>(context, arguments, returnType,
+ Intrinsics::evaluate_intBitsToFloat);
+ case k_uintBitsToFloat_IntrinsicKind:
+ return evaluate_intrinsic<SKSL_INT>(context, arguments, returnType,
+ Intrinsics::evaluate_uintBitsToFloat);
+ // 8.4 : Floating-Point Pack and Unpack Functions
+ case k_packUnorm2x16_IntrinsicKind: {
+ auto Pack = [&](int n) -> unsigned int {
+ float x = Get(0, n);
+ return (int)std::round(Intrinsics::evaluate_clamp(x, 0.0, 1.0) * 65535.0);
+ };
+ return UInt(((Pack(0) << 0) & 0x0000FFFF) |
+ ((Pack(1) << 16) & 0xFFFF0000)).release();
+ }
+ case k_packSnorm2x16_IntrinsicKind: {
+ auto Pack = [&](int n) -> unsigned int {
+ float x = Get(0, n);
+ return (int)std::round(Intrinsics::evaluate_clamp(x, -1.0, 1.0) * 32767.0);
+ };
+ return UInt(((Pack(0) << 0) & 0x0000FFFF) |
+ ((Pack(1) << 16) & 0xFFFF0000)).release();
+ }
+ case k_packHalf2x16_IntrinsicKind: {
+ auto Pack = [&](int n) -> unsigned int {
+ return SkFloatToHalf(Get(0, n));
+ };
+ return UInt(((Pack(0) << 0) & 0x0000FFFF) |
+ ((Pack(1) << 16) & 0xFFFF0000)).release();
+ }
+ case k_unpackUnorm2x16_IntrinsicKind: {
+ SKSL_INT x = *arguments[0]->getConstantValue(0);
+ uint16_t a = ((x >> 0) & 0x0000FFFF);
+ uint16_t b = ((x >> 16) & 0x0000FFFF);
+ return Float2(double(a) / 65535.0,
+ double(b) / 65535.0).release();
+ }
+ case k_unpackSnorm2x16_IntrinsicKind: {
+ SKSL_INT x = *arguments[0]->getConstantValue(0);
+ int16_t a = ((x >> 0) & 0x0000FFFF);
+ int16_t b = ((x >> 16) & 0x0000FFFF);
+ return Float2(Intrinsics::evaluate_clamp(double(a) / 32767.0, -1.0, 1.0),
+ Intrinsics::evaluate_clamp(double(b) / 32767.0, -1.0, 1.0)).release();
+ }
+ case k_unpackHalf2x16_IntrinsicKind: {
+ SKSL_INT x = *arguments[0]->getConstantValue(0);
+ uint16_t a = ((x >> 0) & 0x0000FFFF);
+ uint16_t b = ((x >> 16) & 0x0000FFFF);
+ return Float2(SkHalfToFloat(a),
+ SkHalfToFloat(b)).release();
+ }
+ // 8.5 : Geometric Functions
+ case k_length_IntrinsicKind:
+ return coalesce_vector<float>(arguments, /*startingState=*/0, returnType,
+ Intrinsics::coalesce_length,
+ Intrinsics::finalize_length);
+ case k_distance_IntrinsicKind:
+ return coalesce_pairwise_vectors<float>(arguments, /*startingState=*/0, returnType,
+ Intrinsics::coalesce_distance,
+ Intrinsics::finalize_distance);
+ case k_dot_IntrinsicKind:
+ return coalesce_pairwise_vectors<float>(arguments, /*startingState=*/0, returnType,
+ Intrinsics::coalesce_dot,
+ /*finalize=*/nullptr);
+ case k_cross_IntrinsicKind: {
+ auto X = [&](int n) -> float { return Get(0, n); };
+ auto Y = [&](int n) -> float { return Get(1, n); };
+ SkASSERT(arguments[0]->type().columns() == 3); // the vec2 form is not a real intrinsic
+
+ double vec[3] = {X(1) * Y(2) - Y(1) * X(2),
+ X(2) * Y(0) - Y(2) * X(0),
+ X(0) * Y(1) - Y(0) * X(1)};
+ return assemble_compound(context, arguments[0]->fPosition, returnType, vec);
+ }
+ case k_normalize_IntrinsicKind: {
+ auto Vec = [&] { return DSLExpression{arguments[0]->clone()}; };
+ return (Vec() / Length(Vec())).release();
+ }
+ case k_faceforward_IntrinsicKind: {
+ auto N = [&] { return DSLExpression{arguments[0]->clone()}; };
+ auto I = [&] { return DSLExpression{arguments[1]->clone()}; };
+ auto NRef = [&] { return DSLExpression{arguments[2]->clone()}; };
+ return (N() * Select(Dot(NRef(), I()) < 0, 1, -1)).release();
+ }
+ case k_reflect_IntrinsicKind: {
+ auto I = [&] { return DSLExpression{arguments[0]->clone()}; };
+ auto N = [&] { return DSLExpression{arguments[1]->clone()}; };
+ return (I() - 2.0 * Dot(N(), I()) * N()).release();
+ }
+ case k_refract_IntrinsicKind: {
+ // Refract uses its arguments out-of-order in such a way that we end up trying to create
+ // an invalid Position range, so we rewrite the arguments' positions to avoid that here.
+ auto clone = [&](const Expression* expr) {
+ return DSLExpression(expr->clone(pos));
+ };
+ auto I = [&] { return clone(arguments[0]); };
+ auto N = [&] { return clone(arguments[1]); };
+ auto Eta = [&] { return clone(arguments[2]); };
+
+ std::unique_ptr<Expression> k =
+ (1 - Pow(Eta(), 2) * (1 - Pow(Dot(N(), I()), 2))).release();
+ if (!k->is<Literal>()) {
+ return nullptr;
+ }
+ double kValue = k->as<Literal>().value();
+ return ((kValue < 0) ?
+ (0 * I()) :
+ (Eta() * I() - (Eta() * Dot(N(), I()) + std::sqrt(kValue)) * N())).release();
+ }
+
+ // 8.6 : Matrix Functions
+ case k_matrixCompMult_IntrinsicKind:
+ return evaluate_pairwise_intrinsic(context, arguments, returnType,
+ Intrinsics::evaluate_matrixCompMult);
+ case k_transpose_IntrinsicKind: {
+ double mat[16];
+ int index = 0;
+ for (int c = 0; c < returnType.columns(); ++c) {
+ for (int r = 0; r < returnType.rows(); ++r) {
+ mat[index++] = Get(0, (returnType.columns() * r) + c);
+ }
+ }
+ return assemble_compound(context, arguments[0]->fPosition, returnType, mat);
+ }
+ case k_outerProduct_IntrinsicKind: {
+ double mat[16];
+ int index = 0;
+ for (int c = 0; c < returnType.columns(); ++c) {
+ for (int r = 0; r < returnType.rows(); ++r) {
+ mat[index++] = Get(0, r) * Get(1, c);
+ }
+ }
+ return assemble_compound(context, arguments[0]->fPosition, returnType, mat);
+ }
+ case k_determinant_IntrinsicKind: {
+ float mat[16];
+ extract_matrix(arguments[0], mat);
+ float determinant;
+ switch (arguments[0]->type().slotCount()) {
+ case 4:
+ determinant = SkInvert2x2Matrix(mat, /*outMatrix=*/nullptr);
+ break;
+ case 9:
+ determinant = SkInvert3x3Matrix(mat, /*outMatrix=*/nullptr);
+ break;
+ case 16:
+ determinant = SkInvert4x4Matrix(mat, /*outMatrix=*/nullptr);
+ break;
+ default:
+ SkDEBUGFAILF("unsupported type %s", arguments[0]->type().description().c_str());
+ return nullptr;
+ }
+ return Literal::MakeFloat(arguments[0]->fPosition, determinant, &returnType);
+ }
+ case k_inverse_IntrinsicKind: {
+ float mat[16] = {};
+ extract_matrix(arguments[0], mat);
+ switch (arguments[0]->type().slotCount()) {
+ case 4:
+ if (SkInvert2x2Matrix(mat, mat) == 0.0f) {
+ return nullptr;
+ }
+ break;
+ case 9:
+ if (SkInvert3x3Matrix(mat, mat) == 0.0f) {
+ return nullptr;
+ }
+ break;
+ case 16:
+ if (SkInvert4x4Matrix(mat, mat) == 0.0f) {
+ return nullptr;
+ }
+ break;
+ default:
+ SkDEBUGFAILF("unsupported type %s", arguments[0]->type().description().c_str());
+ return nullptr;
+ }
+
+ double dmat[16];
+ std::copy(mat, mat + std::size(mat), dmat);
+ return assemble_compound(context, arguments[0]->fPosition, returnType, dmat);
+ }
+ // 8.7 : Vector Relational Functions
+ case k_lessThan_IntrinsicKind:
+ return optimize_comparison(context, arguments, Intrinsics::compare_lessThan);
+
+ case k_lessThanEqual_IntrinsicKind:
+ return optimize_comparison(context, arguments, Intrinsics::compare_lessThanEqual);
+
+ case k_greaterThan_IntrinsicKind:
+ return optimize_comparison(context, arguments, Intrinsics::compare_greaterThan);
+
+ case k_greaterThanEqual_IntrinsicKind:
+ return optimize_comparison(context, arguments, Intrinsics::compare_greaterThanEqual);
+
+ case k_equal_IntrinsicKind:
+ return optimize_comparison(context, arguments, Intrinsics::compare_equal);
+
+ case k_notEqual_IntrinsicKind:
+ return optimize_comparison(context, arguments, Intrinsics::compare_notEqual);
+
+ case k_any_IntrinsicKind:
+ return coalesce_vector<bool>(arguments, /*startingState=*/false, returnType,
+ Intrinsics::coalesce_any,
+ /*finalize=*/nullptr);
+ case k_all_IntrinsicKind:
+ return coalesce_vector<bool>(arguments, /*startingState=*/true, returnType,
+ Intrinsics::coalesce_all,
+ /*finalize=*/nullptr);
+ case k_not_IntrinsicKind:
+ return evaluate_intrinsic<bool>(context, arguments, returnType,
+ Intrinsics::evaluate_not);
+ default:
+ return nullptr;
+ }
+}
+
+std::unique_ptr<Expression> FunctionCall::clone(Position pos) const {
+ return std::make_unique<FunctionCall>(pos, &this->type(), &this->function(),
+ this->arguments().clone());
+}
+
+std::string FunctionCall::description(OperatorPrecedence) const {
+ std::string result = std::string(this->function().name()) + "(";
+ auto separator = SkSL::String::Separator();
+ for (const std::unique_ptr<Expression>& arg : this->arguments()) {
+ result += separator();
+ result += arg->description(OperatorPrecedence::kSequence);
+ }
+ result += ")";
+ return result;
+}
+
+/**
+ * Determines the cost of coercing the arguments of a function to the required types. Cost has no
+ * particular meaning other than "lower costs are preferred". Returns CoercionCost::Impossible() if
+ * the call is not valid.
+ */
+static CoercionCost call_cost(const Context& context,
+ const FunctionDeclaration& function,
+ const ExpressionArray& arguments) {
+ if (context.fConfig->strictES2Mode() &&
+ (function.modifiers().fFlags & Modifiers::kES3_Flag)) {
+ return CoercionCost::Impossible();
+ }
+ if (function.parameters().size() != SkToSizeT(arguments.size())) {
+ return CoercionCost::Impossible();
+ }
+ FunctionDeclaration::ParamTypes types;
+ const Type* ignored;
+ if (!function.determineFinalTypes(arguments, &types, &ignored)) {
+ return CoercionCost::Impossible();
+ }
+ CoercionCost total = CoercionCost::Free();
+ for (int i = 0; i < arguments.size(); i++) {
+ total = total + arguments[i]->coercionCost(*types[i]);
+ }
+ return total;
+}
+
+const FunctionDeclaration* FunctionCall::FindBestFunctionForCall(
+ const Context& context,
+ const FunctionDeclaration* overloadChain,
+ const ExpressionArray& arguments) {
+ if (!overloadChain->nextOverload()) {
+ return overloadChain;
+ }
+ CoercionCost bestCost = CoercionCost::Impossible();
+ const FunctionDeclaration* best = nullptr;
+ for (const FunctionDeclaration* f = overloadChain; f != nullptr; f = f->nextOverload()) {
+ CoercionCost cost = call_cost(context, *f, arguments);
+ if (cost <= bestCost) {
+ bestCost = cost;
+ best = f;
+ }
+ }
+ return bestCost.fImpossible ? nullptr : best;
+}
+
+static std::string build_argument_type_list(SkSpan<const std::unique_ptr<Expression>> arguments) {
+ std::string result = "(";
+ auto separator = SkSL::String::Separator();
+ for (const std::unique_ptr<Expression>& arg : arguments) {
+ result += separator();
+ result += arg->type().displayName();
+ }
+ return result + ")";
+}
+
+std::unique_ptr<Expression> FunctionCall::Convert(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> functionValue,
+ ExpressionArray arguments) {
+ switch (functionValue->kind()) {
+ case Expression::Kind::kTypeReference:
+ return Constructor::Convert(context,
+ pos,
+ functionValue->as<TypeReference>().value(),
+ std::move(arguments));
+ case Expression::Kind::kFunctionReference: {
+ const FunctionReference& ref = functionValue->as<FunctionReference>();
+ const FunctionDeclaration* best = FindBestFunctionForCall(context, ref.overloadChain(),
+ arguments);
+ if (best) {
+ return FunctionCall::Convert(context, pos, *best, std::move(arguments));
+ }
+ std::string msg = "no match for " + std::string(ref.overloadChain()->name()) +
+ build_argument_type_list(arguments);
+ context.fErrors->error(pos, msg);
+ return nullptr;
+ }
+ case Expression::Kind::kMethodReference: {
+ MethodReference& ref = functionValue->as<MethodReference>();
+ arguments.push_back(std::move(ref.self()));
+
+ const FunctionDeclaration* best = FindBestFunctionForCall(context, ref.overloadChain(),
+ arguments);
+ if (best) {
+ return FunctionCall::Convert(context, pos, *best, std::move(arguments));
+ }
+ std::string msg =
+ "no match for " + arguments.back()->type().displayName() +
+ "::" + std::string(ref.overloadChain()->name().substr(1)) +
+ build_argument_type_list(SkSpan(arguments).first(arguments.size() - 1));
+ context.fErrors->error(pos, msg);
+ return nullptr;
+ }
+ case Expression::Kind::kPoison:
+ functionValue->fPosition = pos;
+ return functionValue;
+ default:
+ context.fErrors->error(pos, "not a function");
+ return nullptr;
+ }
+}
+
+std::unique_ptr<Expression> FunctionCall::Convert(const Context& context,
+ Position pos,
+ const FunctionDeclaration& function,
+ ExpressionArray arguments) {
+ // Reject ES3 function calls in strict ES2 mode.
+ if (context.fConfig->strictES2Mode() && (function.modifiers().fFlags & Modifiers::kES3_Flag)) {
+ context.fErrors->error(pos, "call to '" + function.description() + "' is not supported");
+ return nullptr;
+ }
+
+ // Reject function calls with the wrong number of arguments.
+ if (function.parameters().size() != SkToSizeT(arguments.size())) {
+ std::string msg = "call to '" + std::string(function.name()) + "' expected " +
+ std::to_string(function.parameters().size()) + " argument";
+ if (function.parameters().size() != 1) {
+ msg += "s";
+ }
+ msg += ", but found " + std::to_string(arguments.size());
+ context.fErrors->error(pos, msg);
+ return nullptr;
+ }
+
+ // Resolve generic types.
+ FunctionDeclaration::ParamTypes types;
+ const Type* returnType;
+ if (!function.determineFinalTypes(arguments, &types, &returnType)) {
+ std::string msg = "no match for " + std::string(function.name()) +
+ build_argument_type_list(arguments);
+ context.fErrors->error(pos, msg);
+ return nullptr;
+ }
+
+ for (int i = 0; i < arguments.size(); i++) {
+ // Coerce each argument to the proper type.
+ arguments[i] = types[i]->coerceExpression(std::move(arguments[i]), context);
+ if (!arguments[i]) {
+ return nullptr;
+ }
+ // Update the refKind on out-parameters, and ensure that they are actually assignable.
+ const Modifiers& paramModifiers = function.parameters()[i]->modifiers();
+ if (paramModifiers.fFlags & Modifiers::kOut_Flag) {
+ const VariableRefKind refKind = paramModifiers.fFlags & Modifiers::kIn_Flag
+ ? VariableReference::RefKind::kReadWrite
+ : VariableReference::RefKind::kPointer;
+ if (!Analysis::UpdateVariableRefKind(arguments[i].get(), refKind, context.fErrors)) {
+ return nullptr;
+ }
+ }
+ // TODO(skia:13609): Make sure that we don't pass writeonly objects to readonly parameters,
+ // or vice-versa.
+ }
+
+ if (function.isMain()) {
+ context.fErrors->error(pos, "call to 'main' is not allowed");
+ return nullptr;
+ }
+
+ if (function.intrinsicKind() == k_eval_IntrinsicKind) {
+ // This is a method call on an effect child. Translate it into a ChildCall, which simplifies
+ // handling in the generators and analysis code.
+ const Variable& child = *arguments.back()->as<VariableReference>().variable();
+ arguments.pop_back();
+ return ChildCall::Make(context, pos, returnType, child, std::move(arguments));
+ }
+
+ return Make(context, pos, returnType, function, std::move(arguments));
+}
+
+std::unique_ptr<Expression> FunctionCall::Make(const Context& context,
+ Position pos,
+ const Type* returnType,
+ const FunctionDeclaration& function,
+ ExpressionArray arguments) {
+ SkASSERT(function.parameters().size() == SkToSizeT(arguments.size()));
+
+ // We might be able to optimize built-in intrinsics.
+ if (function.isIntrinsic() && has_compile_time_constant_arguments(arguments)) {
+ // The function is an intrinsic and all inputs are compile-time constants. Optimize it.
+ if (std::unique_ptr<Expression> expr = optimize_intrinsic_call(context,
+ pos,
+ function.intrinsicKind(),
+ arguments,
+ *returnType)) {
+ expr->fPosition = pos;
+ return expr;
+ }
+ }
+
+ return std::make_unique<FunctionCall>(pos, returnType, &function, std::move(arguments));
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFunctionCall.h b/gfx/skia/skia/src/sksl/ir/SkSLFunctionCall.h
new file mode 100644
index 0000000000..9f31a52772
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLFunctionCall.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_FUNCTIONCALL
+#define SKSL_FUNCTIONCALL
+
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace SkSL {
+
+class Context;
+class FunctionDeclaration;
+class Type;
+enum class OperatorPrecedence : uint8_t;
+
+/**
+ * A function invocation.
+ */
+class FunctionCall final : public Expression {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kFunctionCall;
+
+ FunctionCall(Position pos, const Type* type, const FunctionDeclaration* function,
+ ExpressionArray arguments)
+ : INHERITED(pos, kIRNodeKind, type)
+ , fFunction(*function)
+ , fArguments(std::move(arguments)) {}
+
+ // Resolves generic types, performs type conversion on arguments, determines return type, and
+ // reports errors via the ErrorReporter.
+ static std::unique_ptr<Expression> Convert(const Context& context,
+ Position pos,
+ const FunctionDeclaration& function,
+ ExpressionArray arguments);
+
+ static std::unique_ptr<Expression> Convert(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> functionValue,
+ ExpressionArray arguments);
+
+ // Creates the function call; reports errors via ASSERT.
+ static std::unique_ptr<Expression> Make(const Context& context,
+ Position pos,
+ const Type* returnType,
+ const FunctionDeclaration& function,
+ ExpressionArray arguments);
+
+ static const FunctionDeclaration* FindBestFunctionForCall(const Context& context,
+ const FunctionDeclaration* overloads,
+ const ExpressionArray& arguments);
+
+ const FunctionDeclaration& function() const {
+ return fFunction;
+ }
+
+ ExpressionArray& arguments() {
+ return fArguments;
+ }
+
+ const ExpressionArray& arguments() const {
+ return fArguments;
+ }
+
+ std::unique_ptr<Expression> clone(Position pos) const override;
+
+ std::string description(OperatorPrecedence) const override;
+
+private:
+ const FunctionDeclaration& fFunction;
+ ExpressionArray fArguments;
+
+ using INHERITED = Expression;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFunctionDeclaration.cpp b/gfx/skia/skia/src/sksl/ir/SkSLFunctionDeclaration.cpp
new file mode 100644
index 0000000000..036bfac02e
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLFunctionDeclaration.cpp
@@ -0,0 +1,598 @@
+/*
+ * Copyright 2021 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLFunctionDeclaration.h"
+
+#include "include/core/SkSpan.h"
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLLayout.h"
+#include "include/private/SkSLModifiers.h"
+#include "include/private/SkSLProgramKind.h"
+#include "include/private/SkSLString.h"
+#include "include/private/base/SkTo.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/base/SkStringView.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLModifiersPool.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLSymbolTable.h"
+#include "src/sksl/ir/SkSLType.h"
+#include "src/sksl/ir/SkSLVariable.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <utility>
+
+namespace SkSL {
+
+static bool check_modifiers(const Context& context,
+ Position pos,
+ const Modifiers& modifiers) {
+ const int permitted = Modifiers::kInline_Flag |
+ Modifiers::kNoInline_Flag |
+ (context.fConfig->fIsBuiltinCode ? (Modifiers::kES3_Flag |
+ Modifiers::kPure_Flag |
+ Modifiers::kExport_Flag) : 0);
+ modifiers.checkPermitted(context, pos, permitted, /*permittedLayoutFlags=*/0);
+ if ((modifiers.fFlags & Modifiers::kInline_Flag) &&
+ (modifiers.fFlags & Modifiers::kNoInline_Flag)) {
+ context.fErrors->error(pos, "functions cannot be both 'inline' and 'noinline'");
+ return false;
+ }
+ return true;
+}
+
+static bool check_return_type(const Context& context, Position pos, const Type& returnType) {
+ ErrorReporter& errors = *context.fErrors;
+ if (returnType.isArray()) {
+ errors.error(pos, "functions may not return type '" + returnType.displayName() + "'");
+ return false;
+ }
+ if (context.fConfig->strictES2Mode() && returnType.isOrContainsArray()) {
+ errors.error(pos, "functions may not return structs containing arrays");
+ return false;
+ }
+ if (!context.fConfig->fIsBuiltinCode && returnType.componentType().isOpaque()) {
+ errors.error(pos, "functions may not return opaque type '" + returnType.displayName() +
+ "'");
+ return false;
+ }
+ return true;
+}
+
+static bool check_parameters(const Context& context,
+ std::vector<std::unique_ptr<Variable>>& parameters,
+ bool isMain) {
+ auto typeIsValidForColor = [&](const Type& type) {
+ return type.matches(*context.fTypes.fHalf4) || type.matches(*context.fTypes.fFloat4);
+ };
+
+ // The first color parameter passed to main() is the input color; the second is the dest color.
+ static constexpr int kBuiltinColorIDs[] = {SK_INPUT_COLOR_BUILTIN, SK_DEST_COLOR_BUILTIN};
+ unsigned int builtinColorIndex = 0;
+
+ // Check modifiers on each function parameter.
+ for (auto& param : parameters) {
+ const Type& type = param->type();
+ int permittedFlags = Modifiers::kConst_Flag | Modifiers::kIn_Flag;
+ if (!type.isOpaque()) {
+ permittedFlags |= Modifiers::kOut_Flag;
+ }
+ if (type.typeKind() == Type::TypeKind::kTexture) {
+ permittedFlags |= Modifiers::kReadOnly_Flag | Modifiers::kWriteOnly_Flag;
+ }
+ param->modifiers().checkPermitted(context,
+ param->modifiersPosition(),
+ permittedFlags,
+ /*permittedLayoutFlags=*/0);
+ // Only the (builtin) declarations of 'sample' are allowed to have shader/colorFilter or FP
+ // parameters. You can pass other opaque types to functions safely; this restriction is
+ // specific to "child" objects.
+ if (type.isEffectChild() && !context.fConfig->fIsBuiltinCode) {
+ context.fErrors->error(param->fPosition, "parameters of type '" + type.displayName() +
+ "' not allowed");
+ return false;
+ }
+
+ Modifiers m = param->modifiers();
+ bool modifiersChanged = false;
+
+ // The `in` modifier on function parameters is implicit, so we can replace `in float x` with
+ // `float x`. This prevents any ambiguity when matching a function by its param types.
+ if (Modifiers::kIn_Flag == (m.fFlags & (Modifiers::kOut_Flag | Modifiers::kIn_Flag))) {
+ m.fFlags &= ~(Modifiers::kOut_Flag | Modifiers::kIn_Flag);
+ modifiersChanged = true;
+ }
+
+ if (isMain) {
+ if (ProgramConfig::IsRuntimeEffect(context.fConfig->fKind) &&
+ context.fConfig->fKind != ProgramKind::kMeshFragment &&
+ context.fConfig->fKind != ProgramKind::kMeshVertex) {
+ // We verify that the signature is fully correct later. For now, if this is a
+ // runtime effect of any flavor, a float2 param is supposed to be the coords, and a
+ // half4/float parameter is supposed to be the input or destination color:
+ if (type.matches(*context.fTypes.fFloat2)) {
+ m.fLayout.fBuiltin = SK_MAIN_COORDS_BUILTIN;
+ modifiersChanged = true;
+ } else if (typeIsValidForColor(type) &&
+ builtinColorIndex < std::size(kBuiltinColorIDs)) {
+ m.fLayout.fBuiltin = kBuiltinColorIDs[builtinColorIndex++];
+ modifiersChanged = true;
+ }
+ } else if (ProgramConfig::IsFragment(context.fConfig->fKind)) {
+ // For testing purposes, we have .sksl inputs that are treated as both runtime
+ // effects and fragment shaders. To make that work, fragment shaders are allowed to
+ // have a coords parameter.
+ if (type.matches(*context.fTypes.fFloat2)) {
+ m.fLayout.fBuiltin = SK_MAIN_COORDS_BUILTIN;
+ modifiersChanged = true;
+ }
+ }
+ }
+
+ if (modifiersChanged) {
+ param->setModifiers(context.fModifiersPool->add(m));
+ }
+ }
+ return true;
+}
+
+static bool check_main_signature(const Context& context, Position pos, const Type& returnType,
+ std::vector<std::unique_ptr<Variable>>& parameters) {
+ ErrorReporter& errors = *context.fErrors;
+ ProgramKind kind = context.fConfig->fKind;
+
+ auto typeIsValidForColor = [&](const Type& type) {
+ return type.matches(*context.fTypes.fHalf4) || type.matches(*context.fTypes.fFloat4);
+ };
+
+ auto typeIsValidForAttributes = [&](const Type& type) {
+ return type.isStruct() && type.name() == "Attributes";
+ };
+
+ auto typeIsValidForVaryings = [&](const Type& type) {
+ return type.isStruct() && type.name() == "Varyings";
+ };
+
+ auto paramIsCoords = [&](int idx) {
+ const Variable& p = *parameters[idx];
+ return p.type().matches(*context.fTypes.fFloat2) &&
+ p.modifiers().fFlags == 0 &&
+ p.modifiers().fLayout.fBuiltin == SK_MAIN_COORDS_BUILTIN;
+ };
+
+ auto paramIsBuiltinColor = [&](int idx, int builtinID) {
+ const Variable& p = *parameters[idx];
+ return typeIsValidForColor(p.type()) &&
+ p.modifiers().fFlags == 0 &&
+ p.modifiers().fLayout.fBuiltin == builtinID;
+ };
+
+ auto paramIsConstInAttributes = [&](int idx) {
+ const Variable& p = *parameters[idx];
+ return typeIsValidForAttributes(p.type()) && p.modifiers().fFlags == Modifiers::kConst_Flag;
+ };
+
+ auto paramIsConstInVaryings = [&](int idx) {
+ const Variable& p = *parameters[idx];
+ return typeIsValidForVaryings(p.type()) && p.modifiers().fFlags == Modifiers::kConst_Flag;
+ };
+
+ auto paramIsOutColor = [&](int idx) {
+ const Variable& p = *parameters[idx];
+ return typeIsValidForColor(p.type()) && p.modifiers().fFlags == Modifiers::kOut_Flag;
+ };
+
+ auto paramIsInputColor = [&](int n) { return paramIsBuiltinColor(n, SK_INPUT_COLOR_BUILTIN); };
+ auto paramIsDestColor = [&](int n) { return paramIsBuiltinColor(n, SK_DEST_COLOR_BUILTIN); };
+
+ switch (kind) {
+ case ProgramKind::kRuntimeColorFilter:
+ case ProgramKind::kPrivateRuntimeColorFilter: {
+ // (half4|float4) main(half4|float4)
+ if (!typeIsValidForColor(returnType)) {
+ errors.error(pos, "'main' must return: 'vec4', 'float4', or 'half4'");
+ return false;
+ }
+ bool validParams = (parameters.size() == 1 && paramIsInputColor(0));
+ if (!validParams) {
+ errors.error(pos, "'main' parameter must be 'vec4', 'float4', or 'half4'");
+ return false;
+ }
+ break;
+ }
+ case ProgramKind::kRuntimeShader:
+ case ProgramKind::kPrivateRuntimeShader: {
+ // (half4|float4) main(float2)
+ if (!typeIsValidForColor(returnType)) {
+ errors.error(pos, "'main' must return: 'vec4', 'float4', or 'half4'");
+ return false;
+ }
+ if (!(parameters.size() == 1 && paramIsCoords(0))) {
+ errors.error(pos, "'main' parameter must be 'float2' or 'vec2'");
+ return false;
+ }
+ break;
+ }
+ case ProgramKind::kRuntimeBlender:
+ case ProgramKind::kPrivateRuntimeBlender: {
+ // (half4|float4) main(half4|float4, half4|float4)
+ if (!typeIsValidForColor(returnType)) {
+ errors.error(pos, "'main' must return: 'vec4', 'float4', or 'half4'");
+ return false;
+ }
+ if (!(parameters.size() == 2 &&
+ paramIsInputColor(0) &&
+ paramIsDestColor(1))) {
+ errors.error(pos, "'main' parameters must be (vec4|float4|half4, "
+ "vec4|float4|half4)");
+ return false;
+ }
+ break;
+ }
+ case ProgramKind::kMeshVertex: {
+ // Varyings main(const Attributes)
+ if (!typeIsValidForVaryings(returnType)) {
+ errors.error(pos, "'main' must return 'Varyings'.");
+ return false;
+ }
+ if (!(parameters.size() == 1 && paramIsConstInAttributes(0))) {
+ errors.error(pos, "'main' parameter must be 'const Attributes'.");
+ return false;
+ }
+ break;
+ }
+ case ProgramKind::kMeshFragment: {
+ // float2 main(const Varyings) -or- float2 main(const Varyings, out half4|float4)
+ if (!returnType.matches(*context.fTypes.fFloat2)) {
+ errors.error(pos, "'main' must return: 'vec2' or 'float2'");
+ return false;
+ }
+ if (!((parameters.size() == 1 && paramIsConstInVaryings(0)) ||
+ (parameters.size() == 2 && paramIsConstInVaryings(0) && paramIsOutColor(1)))) {
+ errors.error(pos,
+ "'main' parameters must be (const Varyings, (out (half4|float4))?)");
+ return false;
+ }
+ break;
+ }
+ case ProgramKind::kFragment:
+ case ProgramKind::kGraphiteFragment: {
+ bool validParams = (parameters.size() == 0) ||
+ (parameters.size() == 1 && paramIsCoords(0));
+ if (!validParams) {
+ errors.error(pos, "shader 'main' must be main() or main(float2)");
+ return false;
+ }
+ break;
+ }
+ case ProgramKind::kVertex:
+ case ProgramKind::kGraphiteVertex:
+ case ProgramKind::kCompute:
+ if (!returnType.matches(*context.fTypes.fVoid)) {
+ errors.error(pos, "'main' must return 'void'");
+ return false;
+ }
+ if (parameters.size()) {
+ errors.error(pos, "shader 'main' must have zero parameters");
+ return false;
+ }
+ break;
+ }
+ return true;
+}
+
+/**
+ * Given a concrete type (`float3`) and a generic type (`$genType`), returns the index of the
+ * concrete type within the generic type's typelist. Returns -1 if there is no match.
+ */
+static int find_generic_index(const Type& concreteType,
+ const Type& genericType,
+ bool allowNarrowing) {
+ SkSpan<const Type* const> genericTypes = genericType.coercibleTypes();
+ for (size_t index = 0; index < genericTypes.size(); ++index) {
+ if (concreteType.canCoerceTo(*genericTypes[index], allowNarrowing)) {
+ return index;
+ }
+ }
+ return -1;
+}
+
+/** Returns true if the types match, or if `concreteType` can be found in `maybeGenericType`. */
+static bool type_generically_matches(const Type& concreteType, const Type& maybeGenericType) {
+ return maybeGenericType.isGeneric()
+ ? find_generic_index(concreteType, maybeGenericType, /*allowNarrowing=*/false) != -1
+ : concreteType.matches(maybeGenericType);
+}
+
+/**
+ * Checks a parameter list (params) against the parameters of a function that was declared earlier
+ * (otherParams). Returns true if they match, even if the parameters in `otherParams` contain
+ * generic types.
+ */
+static bool parameters_match(const std::vector<std::unique_ptr<Variable>>& params,
+ const std::vector<Variable*>& otherParams) {
+ // If the param lists are different lengths, they're definitely not a match.
+ if (params.size() != otherParams.size()) {
+ return false;
+ }
+
+ // Figure out a consistent generic index (or bail if we find a contradiction).
+ int genericIndex = -1;
+ for (size_t i = 0; i < params.size(); ++i) {
+ const Type* paramType = &params[i]->type();
+ const Type* otherParamType = &otherParams[i]->type();
+
+ if (otherParamType->isGeneric()) {
+ int genericIndexForThisParam = find_generic_index(*paramType, *otherParamType,
+ /*allowNarrowing=*/false);
+ if (genericIndexForThisParam == -1) {
+ // The type wasn't a match for this generic at all; these params can't be a match.
+ return false;
+ }
+ if (genericIndex != -1 && genericIndex != genericIndexForThisParam) {
+ // The generic index mismatches from what we determined on a previous parameter.
+ return false;
+ }
+ genericIndex = genericIndexForThisParam;
+ }
+ }
+
+ // Now that we've determined a generic index (if we needed one), do a parameter check.
+ for (size_t i = 0; i < params.size(); i++) {
+ const Type* paramType = &params[i]->type();
+ const Type* otherParamType = &otherParams[i]->type();
+
+ // Make generic types concrete.
+ if (otherParamType->isGeneric()) {
+ SkASSERT(genericIndex != -1);
+ SkASSERT(genericIndex < (int)otherParamType->coercibleTypes().size());
+ otherParamType = otherParamType->coercibleTypes()[genericIndex];
+ }
+ // Detect type mismatches.
+ if (!paramType->matches(*otherParamType)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * Checks for a previously existing declaration of this function, reporting errors if there is an
+ * incompatible symbol. Returns true and sets outExistingDecl to point to the existing declaration
+ * (or null if none) on success, returns false on error.
+ */
+static bool find_existing_declaration(const Context& context,
+ SymbolTable& symbols,
+ Position pos,
+ const Modifiers* modifiers,
+ std::string_view name,
+ std::vector<std::unique_ptr<Variable>>& parameters,
+ Position returnTypePos,
+ const Type* returnType,
+ FunctionDeclaration** outExistingDecl) {
+ auto invalidDeclDescription = [&]() -> std::string {
+ std::vector<Variable*> paramPtrs;
+ paramPtrs.reserve(parameters.size());
+ for (std::unique_ptr<Variable>& param : parameters) {
+ paramPtrs.push_back(param.get());
+ }
+ return FunctionDeclaration(pos,
+ modifiers,
+ name,
+ std::move(paramPtrs),
+ returnType,
+ context.fConfig->fIsBuiltinCode)
+ .description();
+ };
+
+ ErrorReporter& errors = *context.fErrors;
+ Symbol* entry = symbols.findMutable(name);
+ *outExistingDecl = nullptr;
+ if (entry) {
+ if (!entry->is<FunctionDeclaration>()) {
+ errors.error(pos, "symbol '" + std::string(name) + "' was already defined");
+ return false;
+ }
+ for (FunctionDeclaration* other = &entry->as<FunctionDeclaration>(); other;
+ other = other->mutableNextOverload()) {
+ SkASSERT(name == other->name());
+ if (!parameters_match(parameters, other->parameters())) {
+ continue;
+ }
+ if (!type_generically_matches(*returnType, other->returnType())) {
+ errors.error(returnTypePos,
+ "functions '" + invalidDeclDescription() + "' and '" +
+ other->description() + "' differ only in return type");
+ return false;
+ }
+ for (size_t i = 0; i < parameters.size(); i++) {
+ if (parameters[i]->modifiers() != other->parameters()[i]->modifiers()) {
+ errors.error(parameters[i]->fPosition,
+ "modifiers on parameter " + std::to_string(i + 1) +
+ " differ between declaration and definition");
+ return false;
+ }
+ }
+ if (*modifiers != other->modifiers() || other->definition() || other->isIntrinsic()) {
+ errors.error(pos, "duplicate definition of '" + invalidDeclDescription() + "'");
+ return false;
+ }
+ *outExistingDecl = other;
+ break;
+ }
+ if (!*outExistingDecl && entry->as<FunctionDeclaration>().isMain()) {
+ errors.error(pos, "duplicate definition of 'main'");
+ return false;
+ }
+ }
+ return true;
+}
+
+FunctionDeclaration::FunctionDeclaration(Position pos,
+ const Modifiers* modifiers,
+ std::string_view name,
+ std::vector<Variable*> parameters,
+ const Type* returnType,
+ bool builtin)
+ : INHERITED(pos, kIRNodeKind, name, /*type=*/nullptr)
+ , fDefinition(nullptr)
+ , fModifiers(modifiers)
+ , fParameters(std::move(parameters))
+ , fReturnType(returnType)
+ , fBuiltin(builtin)
+ , fIsMain(name == "main")
+ , fIntrinsicKind(builtin ? FindIntrinsicKind(name) : kNotIntrinsic) {
+ // None of the parameters are allowed to be be null.
+ SkASSERT(std::count(fParameters.begin(), fParameters.end(), nullptr) == 0);
+}
+
+FunctionDeclaration* FunctionDeclaration::Convert(const Context& context,
+ SymbolTable& symbols,
+ Position pos,
+ Position modifiersPosition,
+ const Modifiers* modifiers,
+ std::string_view name,
+ std::vector<std::unique_ptr<Variable>> parameters,
+ Position returnTypePos,
+ const Type* returnType) {
+ bool isMain = (name == "main");
+
+ FunctionDeclaration* decl = nullptr;
+ if (!check_modifiers(context, modifiersPosition, *modifiers) ||
+ !check_return_type(context, returnTypePos, *returnType) ||
+ !check_parameters(context, parameters, isMain) ||
+ (isMain && !check_main_signature(context, pos, *returnType, parameters)) ||
+ !find_existing_declaration(context, symbols, pos, modifiers, name, parameters,
+ returnTypePos, returnType, &decl)) {
+ return nullptr;
+ }
+ std::vector<Variable*> finalParameters;
+ finalParameters.reserve(parameters.size());
+ for (std::unique_ptr<Variable>& param : parameters) {
+ finalParameters.push_back(symbols.takeOwnershipOfSymbol(std::move(param)));
+ }
+ if (decl) {
+ return decl;
+ }
+ auto result = std::make_unique<FunctionDeclaration>(pos,
+ modifiers,
+ name,
+ std::move(finalParameters),
+ returnType,
+ context.fConfig->fIsBuiltinCode);
+ return symbols.add(std::move(result));
+}
+
+std::string FunctionDeclaration::mangledName() const {
+ if ((this->isBuiltin() && !this->definition()) || this->isMain()) {
+ // Builtins without a definition (like `sin` or `sqrt`) must use their real names.
+ return std::string(this->name());
+ }
+ // Built-in functions can have a $ prefix, which will fail to compile in GLSL. Remove the
+ // $ and add a unique mangling specifier, so user code can't conflict with the name.
+ std::string_view name = this->name();
+ const char* builtinMarker = "";
+ if (skstd::starts_with(name, '$')) {
+ name.remove_prefix(1);
+ builtinMarker = "Q"; // a unique, otherwise-unused mangle character
+ }
+ // Rename function to `funcname_returntypeparamtypes`.
+ std::string result = std::string(name) + "_" + builtinMarker +
+ this->returnType().abbreviatedName();
+ for (const Variable* p : this->parameters()) {
+ result += p->type().abbreviatedName();
+ }
+ return result;
+}
+
+std::string FunctionDeclaration::description() const {
+ int modifierFlags = this->modifiers().fFlags;
+ std::string result =
+ (modifierFlags ? Modifiers::DescribeFlags(modifierFlags) + " " : std::string()) +
+ this->returnType().displayName() + " " + std::string(this->name()) + "(";
+ auto separator = SkSL::String::Separator();
+ for (const Variable* p : this->parameters()) {
+ result += separator();
+ // We can't just say `p->description()` here, because occasionally might have added layout
+ // flags onto parameters (like `layout(builtin=10009)`) and don't want to reproduce that.
+ if (p->modifiers().fFlags) {
+ result += Modifiers::DescribeFlags(p->modifiers().fFlags) + " ";
+ }
+ result += p->type().displayName();
+ result += " ";
+ result += p->name();
+ }
+ result += ")";
+ return result;
+}
+
+bool FunctionDeclaration::matches(const FunctionDeclaration& f) const {
+ if (this->name() != f.name()) {
+ return false;
+ }
+ const std::vector<Variable*>& parameters = this->parameters();
+ const std::vector<Variable*>& otherParameters = f.parameters();
+ if (parameters.size() != otherParameters.size()) {
+ return false;
+ }
+ for (size_t i = 0; i < parameters.size(); i++) {
+ if (!parameters[i]->type().matches(otherParameters[i]->type())) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool FunctionDeclaration::determineFinalTypes(const ExpressionArray& arguments,
+ ParamTypes* outParameterTypes,
+ const Type** outReturnType) const {
+ const std::vector<Variable*>& parameters = this->parameters();
+ SkASSERT(SkToSizeT(arguments.size()) == parameters.size());
+
+ outParameterTypes->reserve_back(arguments.size());
+ int genericIndex = -1;
+ for (int i = 0; i < arguments.size(); i++) {
+ // Non-generic parameters are final as-is.
+ const Type& parameterType = parameters[i]->type();
+ if (!parameterType.isGeneric()) {
+ outParameterTypes->push_back(&parameterType);
+ continue;
+ }
+ // We use the first generic parameter we find to lock in the generic index;
+ // e.g. if we find `float3` here, all `$genType`s will be assumed to be `float3`.
+ if (genericIndex == -1) {
+ genericIndex = find_generic_index(arguments[i]->type(), parameterType,
+ /*allowNarrowing=*/true);
+ if (genericIndex == -1) {
+ // The passed-in type wasn't a match for ANY of the generic possibilities.
+ // This function isn't a match at all.
+ return false;
+ }
+ }
+ outParameterTypes->push_back(parameterType.coercibleTypes()[genericIndex]);
+ }
+ // Apply the generic index to our return type.
+ const Type& returnType = this->returnType();
+ if (returnType.isGeneric()) {
+ if (genericIndex == -1) {
+ // We don't support functions with a generic return type and no other generics.
+ return false;
+ }
+ *outReturnType = returnType.coercibleTypes()[genericIndex];
+ } else {
+ *outReturnType = &returnType;
+ }
+ return true;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFunctionDeclaration.h b/gfx/skia/skia/src/sksl/ir/SkSLFunctionDeclaration.h
new file mode 100644
index 0000000000..462456c1ea
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLFunctionDeclaration.h
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_FUNCTIONDECLARATION
+#define SKSL_FUNCTIONDECLARATION
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLSymbol.h"
+#include "include/private/base/SkTArray.h"
+#include "src/sksl/SkSLIntrinsicList.h"
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <vector>
+
+namespace SkSL {
+
+class Context;
+class ExpressionArray;
+class FunctionDefinition;
+class Position;
+class SymbolTable;
+class Type;
+class Variable;
+
+struct Modifiers;
+
+/**
+ * A function declaration (not a definition -- does not contain a body).
+ */
+class FunctionDeclaration final : public Symbol {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kFunctionDeclaration;
+
+ FunctionDeclaration(Position pos,
+ const Modifiers* modifiers,
+ std::string_view name,
+ std::vector<Variable*> parameters,
+ const Type* returnType,
+ bool builtin);
+
+ static FunctionDeclaration* Convert(const Context& context,
+ SymbolTable& symbols,
+ Position pos,
+ Position modifiersPos,
+ const Modifiers* modifiers,
+ std::string_view name,
+ std::vector<std::unique_ptr<Variable>> parameters,
+ Position returnTypePos,
+ const Type* returnType);
+
+ const Modifiers& modifiers() const {
+ return *fModifiers;
+ }
+
+ void setModifiers(const Modifiers* m) {
+ fModifiers = m;
+ }
+
+ const FunctionDefinition* definition() const {
+ return fDefinition;
+ }
+
+ void setDefinition(const FunctionDefinition* definition) {
+ fDefinition = definition;
+ fIntrinsicKind = kNotIntrinsic;
+ }
+
+ void setNextOverload(FunctionDeclaration* overload) {
+ SkASSERT(!overload || overload->name() == this->name());
+ fNextOverload = overload;
+ }
+
+ const std::vector<Variable*>& parameters() const {
+ return fParameters;
+ }
+
+ const Type& returnType() const {
+ return *fReturnType;
+ }
+
+ bool isBuiltin() const {
+ return fBuiltin;
+ }
+
+ bool isMain() const {
+ return fIsMain;
+ }
+
+ IntrinsicKind intrinsicKind() const {
+ return fIntrinsicKind;
+ }
+
+ bool isIntrinsic() const {
+ return this->intrinsicKind() != kNotIntrinsic;
+ }
+
+ const FunctionDeclaration* nextOverload() const {
+ return fNextOverload;
+ }
+
+ FunctionDeclaration* mutableNextOverload() const {
+ return fNextOverload;
+ }
+
+ std::string mangledName() const;
+
+ std::string description() const override;
+
+ bool matches(const FunctionDeclaration& f) const;
+
+ /**
+ * Determine the effective types of this function's parameters and return value when called with
+ * the given arguments. This is relevant for functions with generic parameter types, where this
+ * will collapse the generic types down into specific concrete types.
+ *
+ * Returns true if it was able to select a concrete set of types for the generic function, false
+ * if there is no possible way this can match the argument types. Note that even a true return
+ * does not guarantee that the function can be successfully called with those arguments, merely
+ * indicates that an attempt should be made. If false is returned, the state of
+ * outParameterTypes and outReturnType are undefined.
+ *
+ * This always assumes narrowing conversions are *allowed*. The calling code needs to verify
+ * that each argument can actually be coerced to the final parameter type, respecting the
+ * narrowing-conversions flag. This is handled in callCost(), or in convertCall() (via coerce).
+ */
+ using ParamTypes = SkSTArray<8, const Type*>;
+ bool determineFinalTypes(const ExpressionArray& arguments,
+ ParamTypes* outParameterTypes,
+ const Type** outReturnType) const;
+
+private:
+ const FunctionDefinition* fDefinition;
+ FunctionDeclaration* fNextOverload = nullptr;
+ const Modifiers* fModifiers;
+ std::vector<Variable*> fParameters;
+ const Type* fReturnType;
+ bool fBuiltin;
+ bool fIsMain;
+ mutable IntrinsicKind fIntrinsicKind = kNotIntrinsic;
+
+ using INHERITED = Symbol;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFunctionDefinition.cpp b/gfx/skia/skia/src/sksl/ir/SkSLFunctionDefinition.cpp
new file mode 100644
index 0000000000..b33a4352a6
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLFunctionDefinition.cpp
@@ -0,0 +1,246 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLFunctionDefinition.h"
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLSymbol.h"
+#include "include/sksl/DSLCore.h"
+#include "include/sksl/DSLExpression.h"
+#include "include/sksl/DSLStatement.h"
+#include "include/sksl/DSLType.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "src/base/SkSafeMath.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/SkSLThreadContext.h"
+#include "src/sksl/ir/SkSLBlock.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLField.h"
+#include "src/sksl/ir/SkSLFieldAccess.h"
+#include "src/sksl/ir/SkSLReturnStatement.h"
+#include "src/sksl/ir/SkSLSymbolTable.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/transform/SkSLProgramWriter.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <forward_list>
+#include <string_view>
+#include <vector>
+
+namespace SkSL {
+
+static void append_rtadjust_fixup_to_vertex_main(const Context& context,
+ const FunctionDeclaration& decl,
+ Block& body) {
+ using namespace SkSL::dsl;
+ using SkSL::dsl::Swizzle; // disambiguate from SkSL::Swizzle
+ using OwnerKind = SkSL::FieldAccess::OwnerKind;
+
+ // If this program uses RTAdjust...
+ ThreadContext::RTAdjustData& rtAdjust = ThreadContext::RTAdjustState();
+ if (rtAdjust.fVar || rtAdjust.fInterfaceBlock) {
+ // ...append a line to the end of the function body which fixes up sk_Position.
+ const SymbolTable* symbolTable = ThreadContext::SymbolTable().get();
+ const Field& skPositionField = symbolTable->find(Compiler::POSITION_NAME)->as<Field>();
+
+ auto Ref = [](const Variable* var) -> std::unique_ptr<Expression> {
+ return VariableReference::Make(Position(), var);
+ };
+ auto Field = [&](const Variable* var, int idx) -> std::unique_ptr<Expression> {
+ return FieldAccess::Make(context, Position(), Ref(var), idx,
+ OwnerKind::kAnonymousInterfaceBlock);
+ };
+ auto Pos = [&]() -> DSLExpression {
+ return DSLExpression(Field(&skPositionField.owner(), skPositionField.fieldIndex()));
+ };
+ auto Adjust = [&]() -> DSLExpression {
+ return DSLExpression(rtAdjust.fInterfaceBlock
+ ? Field(rtAdjust.fInterfaceBlock, rtAdjust.fFieldIndex)
+ : Ref(rtAdjust.fVar));
+ };
+
+ auto fixupStmt = DSLStatement(
+ Pos().assign(Float4(Swizzle(Pos(), X, Y) * Swizzle(Adjust(), X, Z) +
+ Swizzle(Pos(), W, W) * Swizzle(Adjust(), Y, W),
+ 0,
+ Pos().w()))
+ );
+
+ body.children().push_back(fixupStmt.release());
+ }
+}
+
+std::unique_ptr<FunctionDefinition> FunctionDefinition::Convert(const Context& context,
+ Position pos,
+ const FunctionDeclaration& function,
+ std::unique_ptr<Statement> body,
+ bool builtin) {
+ class Finalizer : public ProgramWriter {
+ public:
+ Finalizer(const Context& context, const FunctionDeclaration& function, Position pos)
+ : fContext(context)
+ , fFunction(function) {
+ // Function parameters count as local variables.
+ for (const Variable* var : function.parameters()) {
+ this->addLocalVariable(var, pos);
+ }
+ }
+
+ void addLocalVariable(const Variable* var, Position pos) {
+ // We count the number of slots used, but don't consider the precision of the base type.
+ // In practice, this reflects what GPUs actually do pretty well. (i.e., RelaxedPrecision
+ // math doesn't mean your variable takes less space.) We also don't attempt to reclaim
+ // slots at the end of a Block.
+ size_t prevSlotsUsed = fSlotsUsed;
+ fSlotsUsed = SkSafeMath::Add(fSlotsUsed, var->type().slotCount());
+ // To avoid overzealous error reporting, only trigger the error at the first
+ // place where the stack limit is exceeded.
+ if (prevSlotsUsed < kVariableSlotLimit && fSlotsUsed >= kVariableSlotLimit) {
+ fContext.fErrors->error(pos, "variable '" + std::string(var->name()) +
+ "' exceeds the stack size limit");
+ }
+ }
+
+ ~Finalizer() override {
+ SkASSERT(fBreakableLevel == 0);
+ SkASSERT(fContinuableLevel == std::forward_list<int>{0});
+ }
+
+ bool functionReturnsValue() const {
+ return !fFunction.returnType().isVoid();
+ }
+
+ bool visitExpression(Expression& expr) override {
+ // We don't need to scan expressions.
+ return false;
+ }
+
+ bool visitStatement(Statement& stmt) override {
+ switch (stmt.kind()) {
+ case Statement::Kind::kVarDeclaration: {
+ const Variable* var = stmt.as<VarDeclaration>().var();
+ if (var->type().isOrContainsUnsizedArray()) {
+ fContext.fErrors->error(stmt.fPosition,
+ "unsized arrays are not permitted here");
+ } else {
+ this->addLocalVariable(var, stmt.fPosition);
+ }
+ break;
+ }
+ case Statement::Kind::kReturn: {
+ // Early returns from a vertex main() function will bypass sk_Position
+ // normalization, so SkASSERT that we aren't doing that. If this becomes an
+ // issue, we can add normalization before each return statement.
+ if (ProgramConfig::IsVertex(fContext.fConfig->fKind) && fFunction.isMain()) {
+ fContext.fErrors->error(
+ stmt.fPosition,
+ "early returns from vertex programs are not supported");
+ }
+
+ // Verify that the return statement matches the function's return type.
+ ReturnStatement& returnStmt = stmt.as<ReturnStatement>();
+ if (returnStmt.expression()) {
+ if (this->functionReturnsValue()) {
+ // Coerce return expression to the function's return type.
+ returnStmt.setExpression(fFunction.returnType().coerceExpression(
+ std::move(returnStmt.expression()), fContext));
+ } else {
+ // Returning something from a function with a void return type.
+ fContext.fErrors->error(returnStmt.expression()->fPosition,
+ "may not return a value from a void function");
+ returnStmt.setExpression(nullptr);
+ }
+ } else {
+ if (this->functionReturnsValue()) {
+ // Returning nothing from a function with a non-void return type.
+ fContext.fErrors->error(returnStmt.fPosition,
+ "expected function to return '" +
+ fFunction.returnType().displayName() + "'");
+ }
+ }
+ break;
+ }
+ case Statement::Kind::kDo:
+ case Statement::Kind::kFor: {
+ ++fBreakableLevel;
+ ++fContinuableLevel.front();
+ bool result = INHERITED::visitStatement(stmt);
+ --fContinuableLevel.front();
+ --fBreakableLevel;
+ return result;
+ }
+ case Statement::Kind::kSwitch: {
+ ++fBreakableLevel;
+ fContinuableLevel.push_front(0);
+ bool result = INHERITED::visitStatement(stmt);
+ fContinuableLevel.pop_front();
+ --fBreakableLevel;
+ return result;
+ }
+ case Statement::Kind::kBreak:
+ if (fBreakableLevel == 0) {
+ fContext.fErrors->error(stmt.fPosition,
+ "break statement must be inside a loop or switch");
+ }
+ break;
+ case Statement::Kind::kContinue:
+ if (fContinuableLevel.front() == 0) {
+ if (std::any_of(fContinuableLevel.begin(),
+ fContinuableLevel.end(),
+ [](int level) { return level > 0; })) {
+ fContext.fErrors->error(stmt.fPosition,
+ "continue statement cannot be used in a switch");
+ } else {
+ fContext.fErrors->error(stmt.fPosition,
+ "continue statement must be inside a loop");
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return INHERITED::visitStatement(stmt);
+ }
+
+ private:
+ const Context& fContext;
+ const FunctionDeclaration& fFunction;
+ // how deeply nested we are in breakable constructs (for, do, switch).
+ int fBreakableLevel = 0;
+ // number of slots consumed by all variables declared in the function
+ size_t fSlotsUsed = 0;
+ // how deeply nested we are in continuable constructs (for, do).
+ // We keep a stack (via a forward_list) in order to disallow continue inside of switch.
+ std::forward_list<int> fContinuableLevel{0};
+
+ using INHERITED = ProgramWriter;
+ };
+
+ Finalizer(context, function, pos).visitStatement(*body);
+ if (function.isMain() && ProgramConfig::IsVertex(context.fConfig->fKind)) {
+ append_rtadjust_fixup_to_vertex_main(context, function, body->as<Block>());
+ }
+
+ if (Analysis::CanExitWithoutReturningValue(function, *body)) {
+ context.fErrors->error(body->fPosition, "function '" + std::string(function.name()) +
+ "' can exit without returning a value");
+ }
+
+ SkASSERTF(!function.isIntrinsic(), "Intrinsic function '%.*s' should not have a definition",
+ (int)function.name().size(), function.name().data());
+ return std::make_unique<FunctionDefinition>(pos, &function, builtin, std::move(body));
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFunctionDefinition.h b/gfx/skia/skia/src/sksl/ir/SkSLFunctionDefinition.h
new file mode 100644
index 0000000000..7b77b68a2b
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLFunctionDefinition.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_FUNCTIONDEFINITION
+#define SKSL_FUNCTIONDEFINITION
+
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLProgramElement.h"
+#include "include/private/SkSLStatement.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLFunctionDeclaration.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace SkSL {
+
+class Context;
+
+/**
+ * A function definition (a declaration plus an associated block of code).
+ */
+class FunctionDefinition final : public ProgramElement {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kFunction;
+
+ FunctionDefinition(Position pos, const FunctionDeclaration* declaration, bool builtin,
+ std::unique_ptr<Statement> body)
+ : INHERITED(pos, kIRNodeKind)
+ , fDeclaration(declaration)
+ , fBuiltin(builtin)
+ , fBody(std::move(body)) {}
+
+ /**
+ * Coerces `return` statements to the return type of the function, and reports errors in the
+ * function that can't be detected at the individual statement level:
+ * - `break` and `continue` statements must be in reasonable places.
+ * - non-void functions are required to return a value on all paths.
+ * - vertex main() functions don't allow early returns.
+ *
+ * This will return a FunctionDefinition even if an error is detected; this leads to better
+ * diagnostics overall. (Returning null here leads to spurious "function 'f()' was not defined"
+ * errors when trying to call a function with an error in it.)
+ */
+ static std::unique_ptr<FunctionDefinition> Convert(const Context& context,
+ Position pos,
+ const FunctionDeclaration& function,
+ std::unique_ptr<Statement> body,
+ bool builtin);
+
+ const FunctionDeclaration& declaration() const {
+ return *fDeclaration;
+ }
+
+ bool isBuiltin() const {
+ return fBuiltin;
+ }
+
+ std::unique_ptr<Statement>& body() {
+ return fBody;
+ }
+
+ const std::unique_ptr<Statement>& body() const {
+ return fBody;
+ }
+
+ std::unique_ptr<ProgramElement> clone() const override {
+ return std::make_unique<FunctionDefinition>(fPosition, &this->declaration(),
+ /*builtin=*/false, this->body()->clone());
+ }
+
+ std::string description() const override {
+ return this->declaration().description() + " " + this->body()->description();
+ }
+
+private:
+ const FunctionDeclaration* fDeclaration;
+ bool fBuiltin;
+ std::unique_ptr<Statement> fBody;
+
+ using INHERITED = ProgramElement;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFunctionPrototype.h b/gfx/skia/skia/src/sksl/ir/SkSLFunctionPrototype.h
new file mode 100644
index 0000000000..934cf85c27
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLFunctionPrototype.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_FUNCTIONPROTOTYPE
+#define SKSL_FUNCTIONPROTOTYPE
+
+#include "include/private/SkSLProgramElement.h"
+#include "src/sksl/ir/SkSLBlock.h"
+#include "src/sksl/ir/SkSLFunctionDeclaration.h"
+
+namespace SkSL {
+
+/**
+ * A function prototype (a function declaration as a top-level program element)
+ */
+class FunctionPrototype final : public ProgramElement {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kFunctionPrototype;
+
+ FunctionPrototype(Position pos, const FunctionDeclaration* declaration, bool builtin)
+ : INHERITED(pos, kIRNodeKind)
+ , fDeclaration(declaration)
+ , fBuiltin(builtin) {}
+
+ const FunctionDeclaration& declaration() const {
+ return *fDeclaration;
+ }
+
+ bool isBuiltin() const {
+ return fBuiltin;
+ }
+
+ std::unique_ptr<ProgramElement> clone() const override {
+ return std::make_unique<FunctionPrototype>(fPosition, &this->declaration(),
+ /*builtin=*/false);
+ }
+
+ std::string description() const override {
+ return this->declaration().description() + ";";
+ }
+
+private:
+ const FunctionDeclaration* fDeclaration;
+ bool fBuiltin;
+
+ using INHERITED = ProgramElement;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFunctionReference.h b/gfx/skia/skia/src/sksl/ir/SkSLFunctionReference.h
new file mode 100644
index 0000000000..4788dbb418
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLFunctionReference.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_FUNCTIONREFERENCE
+#define SKSL_FUNCTIONREFERENCE
+
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLFunctionDeclaration.h"
+
+namespace SkSL {
+
+/**
+ * An identifier referring to a function name. This is an intermediate value: FunctionReferences are
+ * always eventually replaced by FunctionCalls in valid programs.
+ */
+class FunctionReference final : public Expression {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kFunctionReference;
+
+ FunctionReference(const Context& context, Position pos,
+ const FunctionDeclaration* overloadChain)
+ : INHERITED(pos, kIRNodeKind, context.fTypes.fInvalid.get())
+ , fOverloadChain(overloadChain) {}
+
+ const FunctionDeclaration* overloadChain() const {
+ return fOverloadChain;
+ }
+
+ std::unique_ptr<Expression> clone(Position pos) const override {
+ return std::unique_ptr<Expression>(new FunctionReference(pos, this->overloadChain(),
+ &this->type()));
+ }
+
+ std::string description(OperatorPrecedence) const override {
+ return "<function>";
+ }
+
+private:
+ FunctionReference(Position pos, const FunctionDeclaration* overloadChain, const Type* type)
+ : INHERITED(pos, kIRNodeKind, type)
+ , fOverloadChain(overloadChain) {}
+
+ const FunctionDeclaration* fOverloadChain;
+
+ using INHERITED = Expression;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLIfStatement.cpp b/gfx/skia/skia/src/sksl/ir/SkSLIfStatement.cpp
new file mode 100644
index 0000000000..7d6918629c
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLIfStatement.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLIfStatement.h"
+
+#include "include/core/SkTypes.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLConstantFolder.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/ir/SkSLExpressionStatement.h"
+#include "src/sksl/ir/SkSLLiteral.h"
+#include "src/sksl/ir/SkSLNop.h"
+#include "src/sksl/ir/SkSLType.h"
+
+namespace SkSL {
+
+std::unique_ptr<Statement> IfStatement::clone() const {
+ return std::make_unique<IfStatement>(fPosition, this->test()->clone(), this->ifTrue()->clone(),
+ this->ifFalse() ? this->ifFalse()->clone() : nullptr);
+}
+
+std::string IfStatement::description() const {
+ std::string result;
+ result += "if (" + this->test()->description() + ") " + this->ifTrue()->description();
+ if (this->ifFalse()) {
+ result += " else " + this->ifFalse()->description();
+ }
+ return result;
+}
+
+std::unique_ptr<Statement> IfStatement::Convert(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> test,
+ std::unique_ptr<Statement> ifTrue,
+ std::unique_ptr<Statement> ifFalse) {
+ test = context.fTypes.fBool->coerceExpression(std::move(test), context);
+ if (!test) {
+ return nullptr;
+ }
+ SkASSERT(ifTrue);
+ if (Analysis::DetectVarDeclarationWithoutScope(*ifTrue, context.fErrors)) {
+ return nullptr;
+ }
+ if (ifFalse && Analysis::DetectVarDeclarationWithoutScope(*ifFalse, context.fErrors)) {
+ return nullptr;
+ }
+ return IfStatement::Make(context, pos, std::move(test), std::move(ifTrue), std::move(ifFalse));
+}
+
+static std::unique_ptr<Statement> replace_empty_with_nop(std::unique_ptr<Statement> stmt,
+ bool isEmpty) {
+ return (stmt && (!isEmpty || stmt->is<Nop>())) ? std::move(stmt)
+ : Nop::Make();
+}
+
+std::unique_ptr<Statement> IfStatement::Make(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> test,
+ std::unique_ptr<Statement> ifTrue,
+ std::unique_ptr<Statement> ifFalse) {
+ SkASSERT(test->type().matches(*context.fTypes.fBool));
+ SkASSERT(!Analysis::DetectVarDeclarationWithoutScope(*ifTrue));
+ SkASSERT(!ifFalse || !Analysis::DetectVarDeclarationWithoutScope(*ifFalse));
+
+ const bool optimize = context.fConfig->fSettings.fOptimize;
+ bool trueIsEmpty = false;
+ bool falseIsEmpty = false;
+
+ if (optimize) {
+ // If both sides are empty, the if statement can be reduced to its test expression.
+ trueIsEmpty = ifTrue->isEmpty();
+ falseIsEmpty = !ifFalse || ifFalse->isEmpty();
+ if (trueIsEmpty && falseIsEmpty) {
+ return ExpressionStatement::Make(context, std::move(test));
+ }
+ }
+
+ if (optimize) {
+ // Static Boolean values can fold down to a single branch.
+ const Expression* testValue = ConstantFolder::GetConstantValueForVariable(*test);
+ if (testValue->isBoolLiteral()) {
+ if (testValue->as<Literal>().boolValue()) {
+ return replace_empty_with_nop(std::move(ifTrue), trueIsEmpty);
+ } else {
+ return replace_empty_with_nop(std::move(ifFalse), falseIsEmpty);
+ }
+ }
+ }
+
+ if (optimize) {
+ // Replace an empty if-true branches with Nop; eliminate empty if-false branches entirely.
+ ifTrue = replace_empty_with_nop(std::move(ifTrue), trueIsEmpty);
+ if (falseIsEmpty) {
+ ifFalse = nullptr;
+ }
+ }
+
+ return std::make_unique<IfStatement>(
+ pos, std::move(test), std::move(ifTrue), std::move(ifFalse));
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLIfStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLIfStatement.h
new file mode 100644
index 0000000000..379b8ad50d
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLIfStatement.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_IFSTATEMENT
+#define SKSL_IFSTATEMENT
+
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLStatement.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace SkSL {
+
+class Context;
+
+/**
+ * An 'if' statement.
+ */
+class IfStatement final : public Statement {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kIf;
+
+ IfStatement(Position pos, std::unique_ptr<Expression> test,
+ std::unique_ptr<Statement> ifTrue, std::unique_ptr<Statement> ifFalse)
+ : INHERITED(pos, kIRNodeKind)
+ , fTest(std::move(test))
+ , fIfTrue(std::move(ifTrue))
+ , fIfFalse(std::move(ifFalse)) {}
+
+ // Creates a potentially-simplified form of the if-statement. Typechecks and coerces the test
+ // expression; reports errors via ErrorReporter.
+ static std::unique_ptr<Statement> Convert(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> test,
+ std::unique_ptr<Statement> ifTrue,
+ std::unique_ptr<Statement> ifFalse);
+
+ // Creates a potentially-simplified form of the if-statement; reports errors via ASSERT.
+ static std::unique_ptr<Statement> Make(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> test,
+ std::unique_ptr<Statement> ifTrue,
+ std::unique_ptr<Statement> ifFalse);
+
+ std::unique_ptr<Expression>& test() {
+ return fTest;
+ }
+
+ const std::unique_ptr<Expression>& test() const {
+ return fTest;
+ }
+
+ std::unique_ptr<Statement>& ifTrue() {
+ return fIfTrue;
+ }
+
+ const std::unique_ptr<Statement>& ifTrue() const {
+ return fIfTrue;
+ }
+
+ std::unique_ptr<Statement>& ifFalse() {
+ return fIfFalse;
+ }
+
+ const std::unique_ptr<Statement>& ifFalse() const {
+ return fIfFalse;
+ }
+
+ std::unique_ptr<Statement> clone() const override;
+
+ std::string description() const override;
+
+private:
+ std::unique_ptr<Expression> fTest;
+ std::unique_ptr<Statement> fIfTrue;
+ std::unique_ptr<Statement> fIfFalse;
+
+ using INHERITED = Statement;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLIndexExpression.cpp b/gfx/skia/skia/src/sksl/ir/SkSLIndexExpression.cpp
new file mode 100644
index 0000000000..b12f1b3726
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLIndexExpression.cpp
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLIndexExpression.h"
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLDefines.h"
+#include "include/private/base/SkTArray.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "include/sksl/SkSLOperator.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLConstantFolder.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/ir/SkSLConstructorArray.h"
+#include "src/sksl/ir/SkSLConstructorCompound.h"
+#include "src/sksl/ir/SkSLLiteral.h"
+#include "src/sksl/ir/SkSLSwizzle.h"
+#include "src/sksl/ir/SkSLSymbolTable.h"
+#include "src/sksl/ir/SkSLType.h"
+#include "src/sksl/ir/SkSLTypeReference.h"
+
+#include <cstdint>
+#include <optional>
+
+namespace SkSL {
+
+static bool index_out_of_range(const Context& context, Position pos, SKSL_INT index,
+ const Expression& base) {
+ if (index >= 0) {
+ if (base.type().columns() == Type::kUnsizedArray) {
+ return false;
+ } else if (index < base.type().columns()) {
+ return false;
+ }
+ }
+ context.fErrors->error(pos, "index " + std::to_string(index) + " out of range for '" +
+ base.type().displayName() + "'");
+ return true;
+}
+
+const Type& IndexExpression::IndexType(const Context& context, const Type& type) {
+ if (type.isMatrix()) {
+ if (type.componentType().matches(*context.fTypes.fFloat)) {
+ switch (type.rows()) {
+ case 2: return *context.fTypes.fFloat2;
+ case 3: return *context.fTypes.fFloat3;
+ case 4: return *context.fTypes.fFloat4;
+ default: SkASSERT(false);
+ }
+ } else if (type.componentType().matches(*context.fTypes.fHalf)) {
+ switch (type.rows()) {
+ case 2: return *context.fTypes.fHalf2;
+ case 3: return *context.fTypes.fHalf3;
+ case 4: return *context.fTypes.fHalf4;
+ default: SkASSERT(false);
+ }
+ }
+ }
+ return type.componentType();
+}
+
+std::unique_ptr<Expression> IndexExpression::Convert(const Context& context,
+ SymbolTable& symbolTable,
+ Position pos,
+ std::unique_ptr<Expression> base,
+ std::unique_ptr<Expression> index) {
+ // Convert an array type reference: `int[10]`.
+ if (base->is<TypeReference>()) {
+ const Type& baseType = base->as<TypeReference>().value();
+ SKSL_INT arraySize = baseType.convertArraySize(context, pos, std::move(index));
+ if (!arraySize) {
+ return nullptr;
+ }
+ return TypeReference::Convert(context, pos,
+ symbolTable.addArrayDimension(&baseType, arraySize));
+ }
+ // Convert an index expression with an expression inside of it: `arr[a * 3]`.
+ const Type& baseType = base->type();
+ if (!baseType.isArray() && !baseType.isMatrix() && !baseType.isVector()) {
+ context.fErrors->error(base->fPosition,
+ "expected array, but found '" + baseType.displayName() + "'");
+ return nullptr;
+ }
+ if (!index->type().isInteger()) {
+ index = context.fTypes.fInt->coerceExpression(std::move(index), context);
+ if (!index) {
+ return nullptr;
+ }
+ }
+ // Perform compile-time bounds checking on constant-expression indices.
+ const Expression* indexExpr = ConstantFolder::GetConstantValueForVariable(*index);
+ if (indexExpr->isIntLiteral()) {
+ SKSL_INT indexValue = indexExpr->as<Literal>().intValue();
+ if (index_out_of_range(context, index->fPosition, indexValue, *base)) {
+ return nullptr;
+ }
+ }
+ return IndexExpression::Make(context, pos, std::move(base), std::move(index));
+}
+
+std::unique_ptr<Expression> IndexExpression::Make(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> base,
+ std::unique_ptr<Expression> index) {
+ const Type& baseType = base->type();
+ SkASSERT(baseType.isArray() || baseType.isMatrix() || baseType.isVector());
+ SkASSERT(index->type().isInteger());
+
+ const Expression* indexExpr = ConstantFolder::GetConstantValueForVariable(*index);
+ if (indexExpr->isIntLiteral()) {
+ SKSL_INT indexValue = indexExpr->as<Literal>().intValue();
+ if (!index_out_of_range(context, index->fPosition, indexValue, *base)) {
+ if (baseType.isVector()) {
+ // Constant array indexes on vectors can be converted to swizzles: `v[2]` --> `v.z`.
+ // Swizzling is harmless and can unlock further simplifications for some base types.
+ return Swizzle::Make(context, pos, std::move(base),
+ ComponentArray{(int8_t)indexValue});
+ }
+
+ if (baseType.isArray() && !Analysis::HasSideEffects(*base)) {
+ // Indexing an constant array constructor with a constant index can just pluck out
+ // the requested value from the array.
+ const Expression* baseExpr = ConstantFolder::GetConstantValueForVariable(*base);
+ if (baseExpr->is<ConstructorArray>()) {
+ const ConstructorArray& arrayCtor = baseExpr->as<ConstructorArray>();
+ const ExpressionArray& arguments = arrayCtor.arguments();
+ SkASSERT(arguments.size() == baseType.columns());
+
+ return arguments[indexValue]->clone(pos);
+ }
+ }
+
+ if (baseType.isMatrix() && !Analysis::HasSideEffects(*base)) {
+ // Matrices can be constructed with vectors that don't line up on column boundaries,
+ // so extracting out the values from the constructor can be tricky. Fortunately, we
+ // can reconstruct an equivalent vector using `getConstantValue`. If we
+ // can't extract the data using `getConstantValue`, it wasn't constant and
+ // we're not obligated to simplify anything.
+ const Expression* baseExpr = ConstantFolder::GetConstantValueForVariable(*base);
+ int vecWidth = baseType.rows();
+ const Type& scalarType = baseType.componentType();
+ const Type& vecType = scalarType.toCompound(context, vecWidth, /*rows=*/1);
+ indexValue *= vecWidth;
+
+ ExpressionArray ctorArgs;
+ ctorArgs.reserve_back(vecWidth);
+ for (int slot = 0; slot < vecWidth; ++slot) {
+ std::optional<double> slotVal = baseExpr->getConstantValue(indexValue + slot);
+ if (slotVal.has_value()) {
+ ctorArgs.push_back(Literal::Make(baseExpr->fPosition, *slotVal,
+ &scalarType));
+ } else {
+ ctorArgs.clear();
+ break;
+ }
+ }
+
+ if (!ctorArgs.empty()) {
+ return ConstructorCompound::Make(context, pos, vecType, std::move(ctorArgs));
+ }
+ }
+ }
+ }
+
+ return std::make_unique<IndexExpression>(context, pos, std::move(base), std::move(index));
+}
+
+std::string IndexExpression::description(OperatorPrecedence) const {
+ return this->base()->description(OperatorPrecedence::kPostfix) + "[" +
+ this->index()->description(OperatorPrecedence::kTopLevel) + "]";
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLIndexExpression.h b/gfx/skia/skia/src/sksl/ir/SkSLIndexExpression.h
new file mode 100644
index 0000000000..222728e9eb
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLIndexExpression.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_INDEX
+#define SKSL_INDEX
+
+#include "include/private/SkSLIRNode.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace SkSL {
+
+class Context;
+class SymbolTable;
+class Type;
+enum class OperatorPrecedence : uint8_t;
+
+/**
+ * An expression which extracts a value from an array, vector or matrix, as in 'm[2]'.
+ */
+class IndexExpression final : public Expression {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kIndex;
+
+ IndexExpression(const Context& context, Position pos, std::unique_ptr<Expression> base,
+ std::unique_ptr<Expression> index)
+ : INHERITED(pos, kIRNodeKind, &IndexType(context, base->type()))
+ , fBase(std::move(base))
+ , fIndex(std::move(index)) {}
+
+ // Returns a simplified index-expression; reports errors via the ErrorReporter.
+ static std::unique_ptr<Expression> Convert(const Context& context,
+ SymbolTable& symbolTable,
+ Position pos,
+ std::unique_ptr<Expression> base,
+ std::unique_ptr<Expression> index);
+
+ // Returns a simplified index-expression; reports errors via ASSERT.
+ static std::unique_ptr<Expression> Make(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> base,
+ std::unique_ptr<Expression> index);
+
+ /**
+ * Given a type, returns the type that will result from extracting an array value from it.
+ */
+ static const Type& IndexType(const Context& context, const Type& type);
+
+ std::unique_ptr<Expression>& base() {
+ return fBase;
+ }
+
+ const std::unique_ptr<Expression>& base() const {
+ return fBase;
+ }
+
+ std::unique_ptr<Expression>& index() {
+ return fIndex;
+ }
+
+ const std::unique_ptr<Expression>& index() const {
+ return fIndex;
+ }
+
+ std::unique_ptr<Expression> clone(Position pos) const override {
+ return std::unique_ptr<Expression>(new IndexExpression(pos, this->base()->clone(),
+ this->index()->clone(),
+ &this->type()));
+ }
+
+ std::string description(OperatorPrecedence) const override;
+
+ using INHERITED = Expression;
+
+private:
+ IndexExpression(Position pos, std::unique_ptr<Expression> base,
+ std::unique_ptr<Expression> index, const Type* type)
+ : INHERITED(pos, Kind::kIndex, type)
+ , fBase(std::move(base))
+ , fIndex(std::move(index)) {}
+
+ std::unique_ptr<Expression> fBase;
+ std::unique_ptr<Expression> fIndex;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLInterfaceBlock.cpp b/gfx/skia/skia/src/sksl/ir/SkSLInterfaceBlock.cpp
new file mode 100644
index 0000000000..f093624eff
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLInterfaceBlock.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2022 Google Inc.
+ *
+ * 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/SkSLModifiers.h"
+#include "include/private/SkSLString.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/SkSLThreadContext.h"
+#include "src/sksl/ir/SkSLField.h"
+#include "src/sksl/ir/SkSLInterfaceBlock.h"
+#include "src/sksl/ir/SkSLSymbolTable.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+
+namespace SkSL {
+
+enum class ProgramKind : int8_t;
+
+InterfaceBlock::~InterfaceBlock() {
+ // Unhook this InterfaceBlock from its associated Variable, since we're being deleted.
+ if (fVariable) {
+ fVariable->detachDeadInterfaceBlock();
+ }
+}
+
+static std::optional<int> find_rt_adjust_index(SkSpan<const Type::Field> fields) {
+ for (size_t index = 0; index < fields.size(); ++index) {
+ const SkSL::Type::Field& f = fields[index];
+ if (f.fName == SkSL::Compiler::RTADJUST_NAME) {
+ return index;
+ }
+ }
+
+ return std::nullopt;
+}
+
+std::unique_ptr<InterfaceBlock> InterfaceBlock::Convert(const Context& context,
+ Position pos,
+ Variable* variable,
+ std::shared_ptr<SymbolTable> symbols) {
+ if (SkSL::ProgramKind kind = context.fConfig->fKind; !ProgramConfig::IsFragment(kind) &&
+ !ProgramConfig::IsVertex(kind) &&
+ !ProgramConfig::IsCompute(kind)) {
+ context.fErrors->error(pos, "interface blocks are not allowed in this kind of program");
+ return nullptr;
+ }
+
+ // Find sk_RTAdjust and error out if it's not of type `float4`.
+ SkSpan<const Type::Field> fields = variable->type().componentType().fields();
+ std::optional<int> rtAdjustIndex = find_rt_adjust_index(fields);
+ if (rtAdjustIndex.has_value()) {
+ const Type::Field& rtAdjustField = fields[*rtAdjustIndex];
+ if (!rtAdjustField.fType->matches(*context.fTypes.fFloat4)) {
+ context.fErrors->error(rtAdjustField.fPosition, "sk_RTAdjust must have type 'float4'");
+ return nullptr;
+ }
+ }
+ return InterfaceBlock::Make(context, pos, variable, rtAdjustIndex, symbols);
+}
+
+std::unique_ptr<InterfaceBlock> InterfaceBlock::Make(const Context& context,
+ Position pos,
+ Variable* variable,
+ std::optional<int> rtAdjustIndex,
+ std::shared_ptr<SymbolTable> symbols) {
+ SkASSERT(ProgramConfig::IsFragment(context.fConfig->fKind) ||
+ ProgramConfig::IsVertex(context.fConfig->fKind) ||
+ ProgramConfig::IsCompute(context.fConfig->fKind));
+
+ SkASSERT(variable->type().componentType().isInterfaceBlock());
+ SkSpan<const Type::Field> fields = variable->type().componentType().fields();
+
+ if (rtAdjustIndex.has_value()) {
+ [[maybe_unused]] const Type::Field& rtAdjustField = fields[*rtAdjustIndex];
+ SkASSERT(rtAdjustField.fName == SkSL::Compiler::RTADJUST_NAME);
+ SkASSERT(rtAdjustField.fType->matches(*context.fTypes.fFloat4));
+
+ ThreadContext::RTAdjustData& rtAdjustData = ThreadContext::RTAdjustState();
+ rtAdjustData.fInterfaceBlock = variable;
+ rtAdjustData.fFieldIndex = *rtAdjustIndex;
+ }
+
+ if (variable->name().empty()) {
+ // This interface block is anonymous. Add each field to the top-level symbol table.
+ for (size_t i = 0; i < fields.size(); ++i) {
+ symbols->add(std::make_unique<SkSL::Field>(fields[i].fPosition, variable, i));
+ }
+ } else {
+ // Add the global variable to the top-level symbol table.
+ symbols->addWithoutOwnership(variable);
+ }
+
+ return std::make_unique<SkSL::InterfaceBlock>(pos, variable, symbols);
+}
+
+std::unique_ptr<ProgramElement> InterfaceBlock::clone() const {
+ return std::make_unique<InterfaceBlock>(fPosition,
+ this->var(),
+ SymbolTable::WrapIfBuiltin(this->typeOwner()));
+}
+
+std::string InterfaceBlock::description() const {
+ std::string result = this->var()->modifiers().description() +
+ std::string(this->typeName()) + " {\n";
+ const Type* structType = &this->var()->type();
+ if (structType->isArray()) {
+ structType = &structType->componentType();
+ }
+ for (const auto& f : structType->fields()) {
+ result += f.description() + "\n";
+ }
+ result += "}";
+ if (!this->instanceName().empty()) {
+ result += " " + std::string(this->instanceName());
+ if (this->arraySize() > 0) {
+ String::appendf(&result, "[%d]", this->arraySize());
+ }
+ }
+ return result + ";";
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLInterfaceBlock.h b/gfx/skia/skia/src/sksl/ir/SkSLInterfaceBlock.h
new file mode 100644
index 0000000000..a9447dd9cb
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLInterfaceBlock.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_INTERFACEBLOCK
+#define SKSL_INTERFACEBLOCK
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLProgramElement.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLType.h"
+#include "src/sksl/ir/SkSLVariable.h"
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <utility>
+
+namespace SkSL {
+
+class Context;
+class SymbolTable;
+
+/**
+ * An interface block, as in:
+ *
+ * out sk_PerVertex {
+ * layout(builtin=0) float4 sk_Position;
+ * layout(builtin=1) float sk_PointSize;
+ * };
+ *
+ * At the IR level, this is represented by a single variable of struct type.
+ */
+class InterfaceBlock final : public ProgramElement {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kInterfaceBlock;
+
+ InterfaceBlock(Position pos,
+ Variable* var,
+ std::shared_ptr<SymbolTable> typeOwner)
+ : INHERITED(pos, kIRNodeKind)
+ , fVariable(var)
+ , fTypeOwner(std::move(typeOwner)) {
+ SkASSERT(fVariable->type().componentType().isInterfaceBlock());
+ fVariable->setInterfaceBlock(this);
+ }
+
+ ~InterfaceBlock() override;
+
+ // Returns an InterfaceBlock; errors are reported to the ErrorReporter.
+ // The caller is responsible for adding the InterfaceBlock to the program elements.
+ // The program's RTAdjustData will be updated if the InterfaceBlock contains sk_RTAdjust.
+ // The passed-in symbol table will be updated with a reference to the interface block variable
+ // (if it is named) or each of the interface block fields (if it is anonymous).
+ static std::unique_ptr<InterfaceBlock> Convert(const Context& context,
+ Position pos,
+ Variable* variable,
+ std::shared_ptr<SymbolTable> symbols);
+
+ // Returns an InterfaceBlock; errors are reported via SkASSERT.
+ // The caller is responsible for adding the InterfaceBlock to the program elements.
+ // If the InterfaceBlock contains sk_RTAdjust, the caller is responsible for passing its field
+ // index in `rtAdjustIndex`.
+ // The passed-in symbol table will be updated with a reference to the interface block variable
+ // (if it is named) or each of the interface block fields (if it is anonymous).
+ static std::unique_ptr<InterfaceBlock> Make(const Context& context,
+ Position pos,
+ Variable* variable,
+ std::optional<int> rtAdjustIndex,
+ std::shared_ptr<SymbolTable> symbols);
+
+ Variable* var() const {
+ return fVariable;
+ }
+
+ void detachDeadVariable() {
+ fVariable = nullptr;
+ }
+
+ std::string_view typeName() const {
+ return fVariable->type().componentType().name();
+ }
+
+ std::string_view instanceName() const {
+ return fVariable->name();
+ }
+
+ const std::shared_ptr<SymbolTable>& typeOwner() const {
+ return fTypeOwner;
+ }
+
+ int arraySize() const {
+ return fVariable->type().isArray() ? fVariable->type().columns() : 0;
+ }
+
+ std::unique_ptr<ProgramElement> clone() const override;
+
+ std::string description() const override;
+
+private:
+ Variable* fVariable;
+ std::shared_ptr<SymbolTable> fTypeOwner;
+
+ using INHERITED = ProgramElement;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLLayout.cpp b/gfx/skia/skia/src/sksl/ir/SkSLLayout.cpp
new file mode 100644
index 0000000000..3274ab1185
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLLayout.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2022 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/private/SkSLLayout.h"
+#include "include/private/SkSLString.h"
+
+namespace SkSL {
+
+std::string Layout::description() const {
+ std::string result;
+ auto separator = SkSL::String::Separator();
+ if (fLocation >= 0) {
+ result += separator() + "location = " + std::to_string(fLocation);
+ }
+ if (fOffset >= 0) {
+ result += separator() + "offset = " + std::to_string(fOffset);
+ }
+ if (fBinding >= 0) {
+ result += separator() + "binding = " + std::to_string(fBinding);
+ }
+ if (fTexture >= 0) {
+ result += separator() + "texture = " + std::to_string(fTexture);
+ }
+ if (fSampler >= 0) {
+ result += separator() + "sampler = " + std::to_string(fSampler);
+ }
+ if (fIndex >= 0) {
+ result += separator() + "index = " + std::to_string(fIndex);
+ }
+ if (fSet >= 0) {
+ result += separator() + "set = " + std::to_string(fSet);
+ }
+ if (fBuiltin >= 0) {
+ result += separator() + "builtin = " + std::to_string(fBuiltin);
+ }
+ if (fInputAttachmentIndex >= 0) {
+ result += separator() + "input_attachment_index = " +
+ std::to_string(fInputAttachmentIndex);
+ }
+ if (fFlags & kOriginUpperLeft_Flag) {
+ result += separator() + "origin_upper_left";
+ }
+ if (fFlags & kBlendSupportAllEquations_Flag) {
+ result += separator() + "blend_support_all_equations";
+ }
+ if (fFlags & kPushConstant_Flag) {
+ result += separator() + "push_constant";
+ }
+ if (fFlags & kColor_Flag) {
+ result += separator() + "color";
+ }
+ if (result.size() > 0) {
+ result = "layout (" + result + ")";
+ }
+ return result;
+}
+
+bool Layout::operator==(const Layout& other) const {
+ return fFlags == other.fFlags &&
+ fLocation == other.fLocation &&
+ fOffset == other.fOffset &&
+ fBinding == other.fBinding &&
+ fTexture == other.fTexture &&
+ fSampler == other.fSampler &&
+ fIndex == other.fIndex &&
+ fSet == other.fSet &&
+ fBuiltin == other.fBuiltin &&
+ fInputAttachmentIndex == other.fInputAttachmentIndex;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLLiteral.cpp b/gfx/skia/skia/src/sksl/ir/SkSLLiteral.cpp
new file mode 100644
index 0000000000..aa8c5c4440
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLLiteral.cpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2022 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/private/SkSLString.h"
+#include "src/sksl/ir/SkSLLiteral.h"
+
+namespace SkSL {
+
+std::string Literal::description(OperatorPrecedence) const {
+ if (this->type().isBoolean()) {
+ return fValue ? "true" : "false";
+ }
+ if (this->type().isInteger()) {
+ return std::to_string(this->intValue());
+ }
+ return skstd::to_string(this->floatValue());
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLLiteral.h b/gfx/skia/skia/src/sksl/ir/SkSLLiteral.h
new file mode 100644
index 0000000000..d4b0bd1be6
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLLiteral.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_FLOATLITERAL
+#define SKSL_FLOATLITERAL
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLType.h"
+
+#include <cstdint>
+#include <cinttypes>
+#include <memory>
+#include <optional>
+#include <string>
+
+namespace SkSL {
+
+enum class OperatorPrecedence : uint8_t;
+
+/**
+ * A literal value. These can contain ints, floats, or booleans.
+ */
+
+class Literal : public Expression {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kLiteral;
+
+ Literal(Position pos, double value, const Type* type)
+ : INHERITED(pos, kIRNodeKind, type)
+ , fValue(value) {}
+
+ // Makes a literal of $floatLiteral type.
+ static std::unique_ptr<Literal> MakeFloat(const Context& context, Position pos,
+ float value) {
+ return std::make_unique<Literal>(pos, value, context.fTypes.fFloatLiteral.get());
+ }
+
+ // Makes a float literal of the specified type.
+ static std::unique_ptr<Literal> MakeFloat(Position pos, float value, const Type* type) {
+ SkASSERT(type->isFloat());
+ return std::make_unique<Literal>(pos, value, type);
+ }
+
+ // Makes a literal of $intLiteral type.
+ static std::unique_ptr<Literal> MakeInt(const Context& context, Position pos,
+ SKSL_INT value) {
+ return std::make_unique<Literal>(pos, value, context.fTypes.fIntLiteral.get());
+ }
+
+ // Makes an int literal of the specified type.
+ static std::unique_ptr<Literal> MakeInt(Position pos, SKSL_INT value, const Type* type) {
+ SkASSERT(type->isInteger());
+ SkASSERTF(value >= type->minimumValue(), "Value %" PRId64 " does not fit in type %s",
+ value, type->description().c_str());
+ SkASSERTF(value <= type->maximumValue(), "Value %" PRId64 " does not fit in type %s",
+ value, type->description().c_str());
+ return std::make_unique<Literal>(pos, value, type);
+ }
+
+ // Makes a literal of boolean type.
+ static std::unique_ptr<Literal> MakeBool(const Context& context, Position pos, bool value) {
+ return std::make_unique<Literal>(pos, value, context.fTypes.fBool.get());
+ }
+
+ // Makes a literal of boolean type. (Functionally identical to the above, but useful if you
+ // don't have access to the Context.)
+ static std::unique_ptr<Literal> MakeBool(Position pos, bool value, const Type* type) {
+ SkASSERT(type->isBoolean());
+ return std::make_unique<Literal>(pos, value, type);
+ }
+
+ // Makes a literal of the specified type, rounding as needed.
+ static std::unique_ptr<Literal> Make(Position pos, double value, const Type* type) {
+ if (type->isFloat()) {
+ return MakeFloat(pos, value, type);
+ }
+ if (type->isInteger()) {
+ return MakeInt(pos, value, type);
+ }
+ SkASSERT(type->isBoolean());
+ return MakeBool(pos, value, type);
+ }
+
+ float floatValue() const {
+ SkASSERT(this->type().isFloat());
+ return (SKSL_FLOAT)fValue;
+ }
+
+ SKSL_INT intValue() const {
+ SkASSERT(this->type().isInteger());
+ return (SKSL_INT)fValue;
+ }
+
+ SKSL_INT boolValue() const {
+ SkASSERT(this->type().isBoolean());
+ return (bool)fValue;
+ }
+
+ double value() const {
+ return fValue;
+ }
+
+ std::string description(OperatorPrecedence) const override;
+
+ ComparisonResult compareConstant(const Expression& other) const override {
+ if (!other.is<Literal>() || this->type().numberKind() != other.type().numberKind()) {
+ return ComparisonResult::kUnknown;
+ }
+ return this->value() == other.as<Literal>().value()
+ ? ComparisonResult::kEqual
+ : ComparisonResult::kNotEqual;
+ }
+
+ std::unique_ptr<Expression> clone(Position pos) const override {
+ return std::make_unique<Literal>(pos, this->value(), &this->type());
+ }
+
+ bool supportsConstantValues() const override {
+ return true;
+ }
+
+ std::optional<double> getConstantValue(int n) const override {
+ SkASSERT(n == 0);
+ return fValue;
+ }
+
+private:
+ double fValue;
+
+ using INHERITED = Expression;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLMethodReference.h b/gfx/skia/skia/src/sksl/ir/SkSLMethodReference.h
new file mode 100644
index 0000000000..c077ec3f9f
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLMethodReference.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_METHODREFERENCE
+#define SKSL_METHODREFERENCE
+
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+namespace SkSL {
+
+class FunctionDeclaration;
+
+/**
+ * An identifier referring to a method name, along with an instance for the call.
+ * This is an intermediate value: MethodReferences are always eventually replaced by FunctionCalls
+ * in valid programs.
+ *
+ * Method calls are only supported on effect-child types, and they all resolve to intrinsics
+ * prefixed with '$', and taking the 'self' object as the last parameter. For example:
+ *
+ * uniform shader child;
+ * ...
+ * child.eval(xy) --> $eval(xy, child)
+ */
+class MethodReference final : public Expression {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kMethodReference;
+
+ MethodReference(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> self,
+ const FunctionDeclaration* overloadChain)
+ : INHERITED(pos, kIRNodeKind, context.fTypes.fInvalid.get())
+ , fSelf(std::move(self))
+ , fOverloadChain(overloadChain) {}
+
+ std::unique_ptr<Expression>& self() { return fSelf; }
+ const std::unique_ptr<Expression>& self() const { return fSelf; }
+
+ const FunctionDeclaration* overloadChain() const { return fOverloadChain; }
+
+ std::unique_ptr<Expression> clone(Position pos) const override {
+ return std::unique_ptr<Expression>(new MethodReference(
+ pos, this->self()->clone(), this->overloadChain(), &this->type()));
+ }
+
+ std::string description(OperatorPrecedence) const override {
+ return "<method>";
+ }
+
+private:
+ MethodReference(Position pos,
+ std::unique_ptr<Expression> self,
+ const FunctionDeclaration* overloadChain,
+ const Type* type)
+ : INHERITED(pos, kIRNodeKind, type)
+ , fSelf(std::move(self))
+ , fOverloadChain(overloadChain) {}
+
+ std::unique_ptr<Expression> fSelf;
+ const FunctionDeclaration* fOverloadChain;
+
+ using INHERITED = Expression;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLModifiers.cpp b/gfx/skia/skia/src/sksl/ir/SkSLModifiers.cpp
new file mode 100644
index 0000000000..548d38d2cd
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLModifiers.cpp
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2021 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/private/SkSLModifiers.h"
+
+#include "include/core/SkTypes.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/base/SkMathPriv.h"
+#include "src/sksl/SkSLContext.h"
+
+namespace SkSL {
+
+bool Modifiers::checkPermitted(const Context& context,
+ Position pos,
+ int permittedModifierFlags,
+ int permittedLayoutFlags) const {
+ static constexpr struct { Modifiers::Flag flag; const char* name; } kModifierFlags[] = {
+ { Modifiers::kConst_Flag, "const" },
+ { Modifiers::kIn_Flag, "in" },
+ { Modifiers::kOut_Flag, "out" },
+ { Modifiers::kUniform_Flag, "uniform" },
+ { Modifiers::kFlat_Flag, "flat" },
+ { Modifiers::kNoPerspective_Flag, "noperspective" },
+ { Modifiers::kPure_Flag, "$pure" },
+ { Modifiers::kInline_Flag, "inline" },
+ { Modifiers::kNoInline_Flag, "noinline" },
+ { Modifiers::kHighp_Flag, "highp" },
+ { Modifiers::kMediump_Flag, "mediump" },
+ { Modifiers::kLowp_Flag, "lowp" },
+ { Modifiers::kExport_Flag, "$export" },
+ { Modifiers::kES3_Flag, "$es3" },
+ { Modifiers::kWorkgroup_Flag, "workgroup" },
+ { Modifiers::kReadOnly_Flag, "readonly" },
+ { Modifiers::kWriteOnly_Flag, "writeonly" },
+ { Modifiers::kBuffer_Flag, "buffer" },
+ };
+
+ bool success = true;
+ int modifierFlags = fFlags;
+ for (const auto& f : kModifierFlags) {
+ if (modifierFlags & f.flag) {
+ if (!(permittedModifierFlags & f.flag)) {
+ context.fErrors->error(pos, "'" + std::string(f.name) + "' is not permitted here");
+ success = false;
+ }
+ modifierFlags &= ~f.flag;
+ }
+ }
+ SkASSERT(modifierFlags == 0);
+
+ int backendFlags = fLayout.fFlags & Layout::kAllBackendFlagsMask;
+ if (SkPopCount(backendFlags) > 1) {
+ context.fErrors->error(pos, "only one backend qualifier can be used");
+ success = false;
+ }
+
+ static constexpr struct { Layout::Flag flag; const char* name; } kLayoutFlags[] = {
+ { Layout::kOriginUpperLeft_Flag, "origin_upper_left"},
+ { Layout::kPushConstant_Flag, "push_constant"},
+ { Layout::kBlendSupportAllEquations_Flag, "blend_support_all_equations"},
+ { Layout::kColor_Flag, "color"},
+ { Layout::kLocation_Flag, "location"},
+ { Layout::kOffset_Flag, "offset"},
+ { Layout::kBinding_Flag, "binding"},
+ { Layout::kTexture_Flag, "texture"},
+ { Layout::kSampler_Flag, "sampler"},
+ { Layout::kIndex_Flag, "index"},
+ { Layout::kSet_Flag, "set"},
+ { Layout::kBuiltin_Flag, "builtin"},
+ { Layout::kInputAttachmentIndex_Flag, "input_attachment_index"},
+ { Layout::kSPIRV_Flag, "spirv"},
+ { Layout::kMetal_Flag, "metal"},
+ { Layout::kGL_Flag, "gl"},
+ { Layout::kWGSL_Flag, "wgsl"},
+ };
+
+ int layoutFlags = fLayout.fFlags;
+ if ((layoutFlags & (Layout::kTexture_Flag | Layout::kSampler_Flag)) &&
+ layoutFlags & Layout::kBinding_Flag) {
+ context.fErrors->error(pos, "'binding' modifier cannot coexist with 'texture'/'sampler'");
+ success = false;
+ }
+ // The `texture` and `sampler` flags are only allowed when explicitly targeting Metal and WGSL
+ if (!(layoutFlags & (Layout::kMetal_Flag | Layout::kWGSL_Flag))) {
+ permittedLayoutFlags &= ~Layout::kTexture_Flag;
+ permittedLayoutFlags &= ~Layout::kSampler_Flag;
+ }
+ // The `set` flag is not allowed when explicitly targeting Metal and GLSL. It is currently
+ // allowed when no backend flag is present.
+ // TODO(skia:14023): Further restrict the `set` flag to SPIR-V and WGSL
+ if (layoutFlags & (Layout::kMetal_Flag | Layout::kGL_Flag)) {
+ permittedLayoutFlags &= ~Layout::kSet_Flag;
+ }
+ // TODO(skia:14023): Restrict the `push_constant` flag to SPIR-V and WGSL
+
+ for (const auto& lf : kLayoutFlags) {
+ if (layoutFlags & lf.flag) {
+ if (!(permittedLayoutFlags & lf.flag)) {
+ context.fErrors->error(pos, "layout qualifier '" + std::string(lf.name) +
+ "' is not permitted here");
+ success = false;
+ }
+ layoutFlags &= ~lf.flag;
+ }
+ }
+ SkASSERT(layoutFlags == 0);
+ return success;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLModifiersDeclaration.h b/gfx/skia/skia/src/sksl/ir/SkSLModifiersDeclaration.h
new file mode 100644
index 0000000000..8d9179ce89
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLModifiersDeclaration.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_MODIFIERDECLARATION
+#define SKSL_MODIFIERDECLARATION
+
+#include "include/private/SkSLModifiers.h"
+#include "include/private/SkSLProgramElement.h"
+
+namespace SkSL {
+
+/**
+ * A declaration that consists only of modifiers, e.g.:
+ *
+ * layout(blend_support_all_equations) out;
+ */
+class ModifiersDeclaration final : public ProgramElement {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kModifiers;
+
+ ModifiersDeclaration(const Modifiers* modifiers)
+ : INHERITED(Position(), kIRNodeKind)
+ , fModifiers(modifiers) {}
+
+ const Modifiers& modifiers() const {
+ return *fModifiers;
+ }
+
+ std::unique_ptr<ProgramElement> clone() const override {
+ return std::make_unique<ModifiersDeclaration>(&this->modifiers());
+ }
+
+ std::string description() const override {
+ return this->modifiers().description() + ";";
+ }
+
+private:
+ const Modifiers* fModifiers;
+
+ using INHERITED = ProgramElement;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLNop.h b/gfx/skia/skia/src/sksl/ir/SkSLNop.h
new file mode 100644
index 0000000000..f2810ef15f
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLNop.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_NOP
+#define SKSL_NOP
+
+#include "include/private/SkSLStatement.h"
+#include "src/sksl/ir/SkSLSymbolTable.h"
+
+namespace SkSL {
+
+/**
+ * A no-op statement that does nothing.
+ */
+class Nop final : public Statement {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kNop;
+
+ Nop()
+ : INHERITED(Position(), kIRNodeKind) {}
+
+ static std::unique_ptr<Statement> Make() {
+ return std::make_unique<Nop>();
+ }
+
+ bool isEmpty() const override {
+ return true;
+ }
+
+ std::string description() const override {
+ return ";";
+ }
+
+ std::unique_ptr<Statement> clone() const override {
+ return std::make_unique<Nop>();
+ }
+
+private:
+ using INHERITED = Statement;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLPoison.h b/gfx/skia/skia/src/sksl/ir/SkSLPoison.h
new file mode 100644
index 0000000000..31bd850308
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLPoison.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2021 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLContext.h"
+
+namespace SkSL {
+
+class Poison : public Expression {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kPoison;
+
+ static std::unique_ptr<Expression> Make(Position pos, const Context& context) {
+ return std::make_unique<Poison>(pos, context.fTypes.fPoison.get());
+ }
+
+ Poison(Position pos, const Type* type)
+ : INHERITED(pos, kIRNodeKind, type) {}
+
+ std::unique_ptr<Expression> clone(Position pos) const override {
+ return std::make_unique<Poison>(pos, &this->type());
+ }
+
+ std::string description(OperatorPrecedence) const override {
+ return Compiler::POISON_TAG;
+ }
+
+private:
+ using INHERITED = Expression;
+};
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLPostfixExpression.cpp b/gfx/skia/skia/src/sksl/ir/SkSLPostfixExpression.cpp
new file mode 100644
index 0000000000..83d5856faf
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLPostfixExpression.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLPostfixExpression.h"
+
+#include "include/core/SkTypes.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "include/sksl/SkSLOperator.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/ir/SkSLType.h"
+#include "src/sksl/ir/SkSLVariableReference.h"
+
+namespace SkSL {
+
+std::unique_ptr<Expression> PostfixExpression::Convert(const Context& context, Position pos,
+ std::unique_ptr<Expression> base, Operator op) {
+ const Type& baseType = base->type();
+ if (!baseType.isNumber()) {
+ context.fErrors->error(pos, "'" + std::string(op.tightOperatorName()) +
+ "' cannot operate on '" + baseType.displayName() + "'");
+ return nullptr;
+ }
+ if (!Analysis::UpdateVariableRefKind(base.get(), VariableRefKind::kReadWrite,
+ context.fErrors)) {
+ return nullptr;
+ }
+ return PostfixExpression::Make(context, pos, std::move(base), op);
+}
+
+std::unique_ptr<Expression> PostfixExpression::Make(const Context& context, Position pos,
+ std::unique_ptr<Expression> base, Operator op) {
+ SkASSERT(base->type().isNumber());
+ SkASSERT(Analysis::IsAssignable(*base));
+ return std::make_unique<PostfixExpression>(pos, std::move(base), op);
+}
+
+std::string PostfixExpression::description(OperatorPrecedence parentPrecedence) const {
+ bool needsParens = (OperatorPrecedence::kPostfix >= parentPrecedence);
+ return std::string(needsParens ? "(" : "") +
+ this->operand()->description(OperatorPrecedence::kPostfix) +
+ std::string(this->getOperator().tightOperatorName()) +
+ std::string(needsParens ? ")" : "");
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLPostfixExpression.h b/gfx/skia/skia/src/sksl/ir/SkSLPostfixExpression.h
new file mode 100644
index 0000000000..a8d457a527
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLPostfixExpression.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_POSTFIXEXPRESSION
+#define SKSL_POSTFIXEXPRESSION
+
+#include "include/private/SkSLIRNode.h"
+#include "include/sksl/SkSLOperator.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace SkSL {
+
+class Context;
+
+/**
+ * An expression modified by a unary operator appearing after it, such as 'i++'.
+ */
+class PostfixExpression final : public Expression {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kPostfix;
+
+ PostfixExpression(Position pos, std::unique_ptr<Expression> operand, Operator op)
+ : INHERITED(pos, kIRNodeKind, &operand->type())
+ , fOperand(std::move(operand))
+ , fOperator(op) {}
+
+ // Creates an SkSL postfix expression; uses the ErrorReporter to report errors.
+ static std::unique_ptr<Expression> Convert(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> base,
+ Operator op);
+
+ // Creates an SkSL postfix expression; reports errors via ASSERT.
+ static std::unique_ptr<Expression> Make(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> base,
+ Operator op);
+
+ Operator getOperator() const {
+ return fOperator;
+ }
+
+ std::unique_ptr<Expression>& operand() {
+ return fOperand;
+ }
+
+ const std::unique_ptr<Expression>& operand() const {
+ return fOperand;
+ }
+
+ std::unique_ptr<Expression> clone(Position pos) const override {
+ return std::make_unique<PostfixExpression>(pos, this->operand()->clone(),
+ this->getOperator());
+ }
+
+ std::string description(OperatorPrecedence parentPrecedence) const override;
+
+private:
+ std::unique_ptr<Expression> fOperand;
+ Operator fOperator;
+
+ using INHERITED = Expression;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLPrefixExpression.cpp b/gfx/skia/skia/src/sksl/ir/SkSLPrefixExpression.cpp
new file mode 100644
index 0000000000..a5ea728de2
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLPrefixExpression.cpp
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLPrefixExpression.h"
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLDefines.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "include/sksl/SkSLOperator.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLConstantFolder.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/ir/SkSLConstructorArray.h"
+#include "src/sksl/ir/SkSLConstructorCompound.h"
+#include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h"
+#include "src/sksl/ir/SkSLConstructorSplat.h"
+#include "src/sksl/ir/SkSLLiteral.h"
+#include "src/sksl/ir/SkSLType.h"
+#include "src/sksl/ir/SkSLVariableReference.h"
+
+namespace SkSL {
+
+static ExpressionArray negate_operands(const Context& context,
+ Position pos,
+ const ExpressionArray& operands);
+
+static std::unique_ptr<Expression> simplify_negation(const Context& context,
+ Position pos,
+ const Expression& originalExpr) {
+ const Expression* value = ConstantFolder::GetConstantValueForVariable(originalExpr);
+ switch (value->kind()) {
+ case Expression::Kind::kLiteral: {
+ // Convert -literal(1) to literal(-1).
+ double negated = -value->as<Literal>().value();
+ // Don't simplify the expression if the type can't hold the negated value.
+ const Type& type = value->type();
+ if (type.checkForOutOfRangeLiteral(context, negated, pos)) {
+ return nullptr;
+ }
+ return Literal::Make(pos, negated, &type);
+ }
+ case Expression::Kind::kPrefix: {
+ // Convert `-(-expression)` into `expression`.
+ const PrefixExpression& prefix = value->as<PrefixExpression>();
+ if (prefix.getOperator().kind() == Operator::Kind::MINUS) {
+ return prefix.operand()->clone(pos);
+ }
+ break;
+ }
+ case Expression::Kind::kConstructorArray:
+ // Convert `-array[N](literal, ...)` into `array[N](-literal, ...)`.
+ if (Analysis::IsCompileTimeConstant(*value)) {
+ const ConstructorArray& ctor = value->as<ConstructorArray>();
+ return ConstructorArray::Make(context, pos, ctor.type(),
+ negate_operands(context, pos, ctor.arguments()));
+ }
+ break;
+
+ case Expression::Kind::kConstructorDiagonalMatrix:
+ // Convert `-matrix(literal)` into `matrix(-literal)`.
+ if (Analysis::IsCompileTimeConstant(*value)) {
+ const ConstructorDiagonalMatrix& ctor = value->as<ConstructorDiagonalMatrix>();
+ if (std::unique_ptr<Expression> simplified = simplify_negation(context,
+ pos,
+ *ctor.argument())) {
+ return ConstructorDiagonalMatrix::Make(context, pos, ctor.type(),
+ std::move(simplified));
+ }
+ }
+ break;
+
+ case Expression::Kind::kConstructorSplat:
+ // Convert `-vector(literal)` into `vector(-literal)`.
+ if (Analysis::IsCompileTimeConstant(*value)) {
+ const ConstructorSplat& ctor = value->as<ConstructorSplat>();
+ if (std::unique_ptr<Expression> simplified = simplify_negation(context,
+ pos,
+ *ctor.argument())) {
+ return ConstructorSplat::Make(context, pos, ctor.type(), std::move(simplified));
+ }
+ }
+ break;
+
+ case Expression::Kind::kConstructorCompound:
+ // Convert `-vecN(literal, ...)` into `vecN(-literal, ...)`.
+ if (Analysis::IsCompileTimeConstant(*value)) {
+ const ConstructorCompound& ctor = value->as<ConstructorCompound>();
+ return ConstructorCompound::Make(context, pos, ctor.type(),
+ negate_operands(context, pos, ctor.arguments()));
+ }
+ break;
+
+ default:
+ break;
+ }
+ return nullptr;
+}
+
+static ExpressionArray negate_operands(const Context& context,
+ Position pos,
+ const ExpressionArray& array) {
+ ExpressionArray replacement;
+ replacement.reserve_back(array.size());
+ for (const std::unique_ptr<Expression>& expr : array) {
+ // The logic below is very similar to `negate_operand`, but with different ownership rules.
+ if (std::unique_ptr<Expression> simplified = simplify_negation(context, pos, *expr)) {
+ replacement.push_back(std::move(simplified));
+ } else {
+ replacement.push_back(std::make_unique<PrefixExpression>(pos, Operator::Kind::MINUS,
+ expr->clone()));
+ }
+ }
+ return replacement;
+}
+
+static std::unique_ptr<Expression> negate_operand(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> value) {
+ // Attempt to simplify this negation (e.g. eliminate double negation, literal values)
+ if (std::unique_ptr<Expression> simplified = simplify_negation(context, pos, *value)) {
+ return simplified;
+ }
+
+ // No simplified form; convert expression to Prefix(TK_MINUS, expression).
+ return std::make_unique<PrefixExpression>(pos, Operator::Kind::MINUS, std::move(value));
+}
+
+static std::unique_ptr<Expression> logical_not_operand(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> operand) {
+ const Expression* value = ConstantFolder::GetConstantValueForVariable(*operand);
+ switch (value->kind()) {
+ case Expression::Kind::kLiteral: {
+ // Convert !boolLiteral(true) to boolLiteral(false).
+ SkASSERT(value->type().isBoolean());
+ const Literal& b = value->as<Literal>();
+ return Literal::MakeBool(pos, !b.boolValue(), &operand->type());
+ }
+ case Expression::Kind::kPrefix: {
+ // Convert `!(!expression)` into `expression`.
+ PrefixExpression& prefix = operand->as<PrefixExpression>();
+ if (prefix.getOperator().kind() == Operator::Kind::LOGICALNOT) {
+ prefix.operand()->fPosition = pos;
+ return std::move(prefix.operand());
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ // No simplified form; convert expression to Prefix(TK_LOGICALNOT, expression).
+ return std::make_unique<PrefixExpression>(pos, Operator::Kind::LOGICALNOT, std::move(operand));
+}
+
+std::unique_ptr<Expression> PrefixExpression::Convert(const Context& context, Position pos,
+ Operator op, std::unique_ptr<Expression> base) {
+ const Type& baseType = base->type();
+ switch (op.kind()) {
+ case Operator::Kind::PLUS:
+ if (baseType.isArray() || !baseType.componentType().isNumber()) {
+ context.fErrors->error(pos,
+ "'+' cannot operate on '" + baseType.displayName() + "'");
+ return nullptr;
+ }
+ break;
+
+ case Operator::Kind::MINUS:
+ if (baseType.isArray() || !baseType.componentType().isNumber()) {
+ context.fErrors->error(pos,
+ "'-' cannot operate on '" + baseType.displayName() + "'");
+ return nullptr;
+ }
+ break;
+
+ case Operator::Kind::PLUSPLUS:
+ case Operator::Kind::MINUSMINUS:
+ if (!baseType.isNumber()) {
+ context.fErrors->error(pos,
+ "'" + std::string(op.tightOperatorName()) +
+ "' cannot operate on '" + baseType.displayName() + "'");
+ return nullptr;
+ }
+ if (!Analysis::UpdateVariableRefKind(base.get(), VariableReference::RefKind::kReadWrite,
+ context.fErrors)) {
+ return nullptr;
+ }
+ break;
+
+ case Operator::Kind::LOGICALNOT:
+ if (!baseType.isBoolean()) {
+ context.fErrors->error(pos,
+ "'" + std::string(op.tightOperatorName()) +
+ "' cannot operate on '" + baseType.displayName() + "'");
+ return nullptr;
+ }
+ break;
+
+ case Operator::Kind::BITWISENOT:
+ if (context.fConfig->strictES2Mode()) {
+ // GLSL ES 1.00, Section 5.1
+ context.fErrors->error(
+ pos,
+ "operator '" + std::string(op.tightOperatorName()) + "' is not allowed");
+ return nullptr;
+ }
+ if (baseType.isArray() || !baseType.componentType().isInteger()) {
+ context.fErrors->error(pos,
+ "'" + std::string(op.tightOperatorName()) +
+ "' cannot operate on '" + baseType.displayName() + "'");
+ return nullptr;
+ }
+ if (baseType.isLiteral()) {
+ // The expression `~123` is no longer a literal; coerce to the actual type.
+ base = baseType.scalarTypeForLiteral().coerceExpression(std::move(base), context);
+ if (!base) {
+ return nullptr;
+ }
+ }
+ break;
+
+ default:
+ SK_ABORT("unsupported prefix operator");
+ }
+
+ std::unique_ptr<Expression> result = PrefixExpression::Make(context, pos, op, std::move(base));
+ SkASSERT(result->fPosition == pos);
+ return result;
+}
+
+std::unique_ptr<Expression> PrefixExpression::Make(const Context& context, Position pos,
+ Operator op, std::unique_ptr<Expression> base) {
+ switch (op.kind()) {
+ case Operator::Kind::PLUS:
+ SkASSERT(!base->type().isArray());
+ SkASSERT(base->type().componentType().isNumber());
+ base->fPosition = pos;
+ return base;
+
+ case Operator::Kind::MINUS:
+ SkASSERT(!base->type().isArray());
+ SkASSERT(base->type().componentType().isNumber());
+ return negate_operand(context, pos, std::move(base));
+
+ case Operator::Kind::LOGICALNOT:
+ SkASSERT(base->type().isBoolean());
+ return logical_not_operand(context, pos, std::move(base));
+
+ case Operator::Kind::PLUSPLUS:
+ case Operator::Kind::MINUSMINUS:
+ SkASSERT(base->type().isNumber());
+ SkASSERT(Analysis::IsAssignable(*base));
+ break;
+
+ case Operator::Kind::BITWISENOT:
+ SkASSERT(!context.fConfig->strictES2Mode());
+ SkASSERT(!base->type().isArray());
+ SkASSERT(base->type().componentType().isInteger());
+ SkASSERT(!base->type().isLiteral());
+ break;
+
+ default:
+ SkDEBUGFAILF("unsupported prefix operator: %s", op.operatorName());
+ }
+
+ return std::make_unique<PrefixExpression>(pos, op, std::move(base));
+}
+
+std::string PrefixExpression::description(OperatorPrecedence parentPrecedence) const {
+ bool needsParens = (OperatorPrecedence::kPrefix >= parentPrecedence);
+ return std::string(needsParens ? "(" : "") +
+ std::string(this->getOperator().tightOperatorName()) +
+ this->operand()->description(OperatorPrecedence::kPrefix) +
+ std::string(needsParens ? ")" : "");
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLPrefixExpression.h b/gfx/skia/skia/src/sksl/ir/SkSLPrefixExpression.h
new file mode 100644
index 0000000000..58c41d404a
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLPrefixExpression.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_PREFIXEXPRESSION
+#define SKSL_PREFIXEXPRESSION
+
+#include "include/private/SkSLIRNode.h"
+#include "include/sksl/SkSLOperator.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace SkSL {
+
+class Context;
+
+/**
+ * An expression modified by a unary operator appearing before it, such as '!flag'.
+ */
+class PrefixExpression final : public Expression {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kPrefix;
+
+ // Use PrefixExpression::Make to automatically simplify various prefix expression types.
+ PrefixExpression(Position pos, Operator op, std::unique_ptr<Expression> operand)
+ : INHERITED(pos, kIRNodeKind, &operand->type())
+ , fOperator(op)
+ , fOperand(std::move(operand)) {}
+
+ // Creates an SkSL prefix expression; uses the ErrorReporter to report errors.
+ static std::unique_ptr<Expression> Convert(const Context& context, Position pos, Operator op,
+ std::unique_ptr<Expression> base);
+
+ // Creates an SkSL prefix expression; reports errors via ASSERT.
+ static std::unique_ptr<Expression> Make(const Context& context, Position pos, Operator op,
+ std::unique_ptr<Expression> base);
+
+ Operator getOperator() const {
+ return fOperator;
+ }
+
+ std::unique_ptr<Expression>& operand() {
+ return fOperand;
+ }
+
+ const std::unique_ptr<Expression>& operand() const {
+ return fOperand;
+ }
+
+ std::unique_ptr<Expression> clone(Position pos) const override {
+ return std::make_unique<PrefixExpression>(pos, this->getOperator(),
+ this->operand()->clone());
+ }
+
+ std::string description(OperatorPrecedence parentPrecedence) const override;
+
+private:
+ Operator fOperator;
+ std::unique_ptr<Expression> fOperand;
+
+ using INHERITED = Expression;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLProgram.cpp b/gfx/skia/skia/src/sksl/ir/SkSLProgram.cpp
new file mode 100644
index 0000000000..332e4a2ac4
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLProgram.cpp
@@ -0,0 +1,116 @@
+/*
+ * 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/private/SkSLModifiers.h"
+#include "include/private/SkSLProgramElement.h"
+#include "include/private/SkSLString.h"
+#include "include/private/SkSLSymbol.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLModifiersPool.h"
+#include "src/sksl/SkSLPool.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/analysis/SkSLProgramUsage.h"
+#include "src/sksl/ir/SkSLFunctionDeclaration.h"
+#include "src/sksl/ir/SkSLProgram.h"
+#include "src/sksl/ir/SkSLSymbolTable.h" // IWYU pragma: keep
+#include "src/sksl/ir/SkSLVarDeclarations.h"
+#include "src/sksl/ir/SkSLVariable.h"
+
+#include <type_traits>
+#include <utility>
+
+namespace SkSL {
+
+Program::Program(std::unique_ptr<std::string> source,
+ std::unique_ptr<ProgramConfig> config,
+ std::shared_ptr<Context> context,
+ std::vector<std::unique_ptr<ProgramElement>> elements,
+ std::vector<const ProgramElement*> sharedElements,
+ std::unique_ptr<ModifiersPool> modifiers,
+ std::shared_ptr<SymbolTable> symbols,
+ std::unique_ptr<Pool> pool,
+ Inputs inputs)
+ : fSource(std::move(source))
+ , fConfig(std::move(config))
+ , fContext(context)
+ , fModifiers(std::move(modifiers))
+ , fSymbols(symbols)
+ , fPool(std::move(pool))
+ , fOwnedElements(std::move(elements))
+ , fSharedElements(std::move(sharedElements))
+ , fInputs(inputs) {
+ fUsage = Analysis::GetUsage(*this);
+}
+
+Program::~Program() {
+ // Some or all of the program elements are in the pool. To free them safely, we must attach
+ // the pool before destroying any program elements. (Otherwise, we may accidentally call
+ // delete on a pooled node.)
+ AutoAttachPoolToThread attach(fPool.get());
+
+ fOwnedElements.clear();
+ fContext.reset();
+ fSymbols.reset();
+ fModifiers.reset();
+}
+
+std::string Program::description() const {
+ std::string result = fConfig->versionDescription();
+ for (const ProgramElement* e : this->elements()) {
+ result += e->description();
+ }
+ return result;
+}
+
+const FunctionDeclaration* Program::getFunction(const char* functionName) const {
+ const Symbol* symbol = fSymbols->find(functionName);
+ bool valid = symbol && symbol->is<FunctionDeclaration>() &&
+ symbol->as<FunctionDeclaration>().definition();
+ return valid ? &symbol->as<FunctionDeclaration>() : nullptr;
+}
+
+static void gather_uniforms(UniformInfo* info, const Type& type, const std::string& name) {
+ switch (type.typeKind()) {
+ case Type::TypeKind::kStruct:
+ for (const auto& f : type.fields()) {
+ gather_uniforms(info, *f.fType, name + "." + std::string(f.fName));
+ }
+ break;
+ case Type::TypeKind::kArray:
+ for (int i = 0; i < type.columns(); ++i) {
+ gather_uniforms(info, type.componentType(),
+ String::printf("%s[%d]", name.c_str(), i));
+ }
+ break;
+ case Type::TypeKind::kScalar:
+ case Type::TypeKind::kVector:
+ case Type::TypeKind::kMatrix:
+ info->fUniforms.push_back({name, type.componentType().numberKind(),
+ type.rows(), type.columns(), info->fUniformSlotCount});
+ info->fUniformSlotCount += type.slotCount();
+ break;
+ default:
+ break;
+ }
+}
+
+std::unique_ptr<UniformInfo> Program::getUniformInfo() {
+ auto info = std::make_unique<UniformInfo>();
+ for (const ProgramElement* e : this->elements()) {
+ if (!e->is<GlobalVarDeclaration>()) {
+ continue;
+ }
+ const GlobalVarDeclaration& decl = e->as<GlobalVarDeclaration>();
+ const Variable& var = *decl.varDeclaration().var();
+ if (var.modifiers().fFlags & Modifiers::kUniform_Flag) {
+ gather_uniforms(info.get(), var.type(), std::string(var.name()));
+ }
+ }
+ return info;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLProgram.h b/gfx/skia/skia/src/sksl/ir/SkSLProgram.h
new file mode 100644
index 0000000000..fbdb936e49
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLProgram.h
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_PROGRAM
+#define SKSL_PROGRAM
+
+#include "src/sksl/ir/SkSLType.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+// name of the uniform used to handle features that are sensitive to whether Y is flipped.
+// TODO: find a better home for this constant
+#define SKSL_RTFLIP_NAME "u_skRTFlip"
+
+namespace SkSL {
+
+class Context;
+class FunctionDeclaration;
+class ModifiersPool;
+class Pool;
+class ProgramElement;
+class ProgramUsage;
+class SymbolTable;
+struct ProgramConfig;
+
+/** Represents a list the Uniforms contained within a Program. */
+struct UniformInfo {
+ struct Uniform {
+ std::string fName;
+ SkSL::Type::NumberKind fKind;
+ int fColumns;
+ int fRows;
+ int fSlot;
+ };
+ std::vector<Uniform> fUniforms;
+ int fUniformSlotCount = 0;
+};
+
+/**
+ * Represents a fully-digested program, ready for code generation.
+ */
+struct Program {
+ struct Inputs {
+ bool fUseFlipRTUniform = false;
+ bool operator==(const Inputs& that) const {
+ return fUseFlipRTUniform == that.fUseFlipRTUniform;
+ }
+ bool operator!=(const Inputs& that) const { return !(*this == that); }
+ };
+
+ Program(std::unique_ptr<std::string> source,
+ std::unique_ptr<ProgramConfig> config,
+ std::shared_ptr<Context> context,
+ std::vector<std::unique_ptr<ProgramElement>> elements,
+ std::vector<const ProgramElement*> sharedElements,
+ std::unique_ptr<ModifiersPool> modifiers,
+ std::shared_ptr<SymbolTable> symbols,
+ std::unique_ptr<Pool> pool,
+ Inputs inputs);
+
+ ~Program();
+
+ class ElementsCollection {
+ public:
+ class iterator {
+ public:
+ const ProgramElement* operator*() {
+ if (fShared != fSharedEnd) {
+ return *fShared;
+ } else {
+ return fOwned->get();
+ }
+ }
+
+ iterator& operator++() {
+ if (fShared != fSharedEnd) {
+ ++fShared;
+ } else {
+ ++fOwned;
+ }
+ return *this;
+ }
+
+ bool operator==(const iterator& other) const {
+ return fOwned == other.fOwned && fShared == other.fShared;
+ }
+
+ bool operator!=(const iterator& other) const {
+ return !(*this == other);
+ }
+
+ private:
+ using Owned = std::vector<std::unique_ptr<ProgramElement>>::const_iterator;
+ using Shared = std::vector<const ProgramElement*>::const_iterator;
+ friend class ElementsCollection;
+
+ iterator(Owned owned, Owned ownedEnd, Shared shared, Shared sharedEnd)
+ : fOwned(owned), fOwnedEnd(ownedEnd), fShared(shared), fSharedEnd(sharedEnd) {}
+
+ Owned fOwned;
+ Owned fOwnedEnd;
+ Shared fShared;
+ Shared fSharedEnd;
+ };
+
+ iterator begin() const {
+ return iterator(fProgram.fOwnedElements.begin(), fProgram.fOwnedElements.end(),
+ fProgram.fSharedElements.begin(), fProgram.fSharedElements.end());
+ }
+
+ iterator end() const {
+ return iterator(fProgram.fOwnedElements.end(), fProgram.fOwnedElements.end(),
+ fProgram.fSharedElements.end(), fProgram.fSharedElements.end());
+ }
+
+ private:
+ friend struct Program;
+
+ ElementsCollection(const Program& program) : fProgram(program) {}
+ const Program& fProgram;
+ };
+
+ /**
+ * Iterates over *all* elements in this Program, both owned and shared (builtin). The iterator's
+ * value type is `const ProgramElement*`, so it's clear that you *must not* modify anything (as
+ * you might be mutating shared data).
+ */
+ ElementsCollection elements() const { return ElementsCollection(*this); }
+
+ /**
+ * Returns a function declaration with the given name; null is returned if the function doesn't
+ * exist or has no definition. If the function might have overloads, you can use nextOverload()
+ * to search for the function with the expected parameter list.
+ */
+ const FunctionDeclaration* getFunction(const char* functionName) const;
+
+ /**
+ * Returns a list of uniforms used by this Program. The uniform list will exclude opaque types
+ * like textures, samplers, or child effects.
+ */
+ std::unique_ptr<UniformInfo> getUniformInfo();
+
+ std::string description() const;
+ const ProgramUsage* usage() const { return fUsage.get(); }
+
+ std::unique_ptr<std::string> fSource;
+ std::unique_ptr<ProgramConfig> fConfig;
+ std::shared_ptr<Context> fContext;
+ std::unique_ptr<ProgramUsage> fUsage;
+ std::unique_ptr<ModifiersPool> fModifiers;
+ // it's important to keep fOwnedElements defined after (and thus destroyed before) fSymbols,
+ // because destroying elements can modify reference counts in symbols
+ std::shared_ptr<SymbolTable> fSymbols;
+ std::unique_ptr<Pool> fPool;
+ // Contains *only* elements owned exclusively by this program.
+ std::vector<std::unique_ptr<ProgramElement>> fOwnedElements;
+ // Contains *only* elements owned by a built-in module that are included in this program.
+ // Use elements() to iterate over the combined set of owned + shared elements.
+ std::vector<const ProgramElement*> fSharedElements;
+ Inputs fInputs;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLReturnStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLReturnStatement.h
new file mode 100644
index 0000000000..3ee739fb79
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLReturnStatement.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_RETURNSTATEMENT
+#define SKSL_RETURNSTATEMENT
+
+#include "include/private/SkSLStatement.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+namespace SkSL {
+
+/**
+ * A 'return' statement.
+ */
+class ReturnStatement final : public Statement {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kReturn;
+
+ ReturnStatement(Position pos, std::unique_ptr<Expression> expression)
+ : INHERITED(pos, kIRNodeKind)
+ , fExpression(std::move(expression)) {}
+
+ static std::unique_ptr<Statement> Make(Position pos,
+ std::unique_ptr<Expression> expression) {
+ return std::make_unique<ReturnStatement>(pos, std::move(expression));
+ }
+
+ std::unique_ptr<Expression>& expression() {
+ return fExpression;
+ }
+
+ const std::unique_ptr<Expression>& expression() const {
+ return fExpression;
+ }
+
+ void setExpression(std::unique_ptr<Expression> expr) {
+ fExpression = std::move(expr);
+ }
+
+ std::unique_ptr<Statement> clone() const override {
+ return std::make_unique<ReturnStatement>(fPosition,
+ this->expression() ? this->expression()->clone() : nullptr);
+ }
+
+ std::string description() const override {
+ if (this->expression()) {
+ return "return " + this->expression()->description() + ";";
+ } else {
+ return "return;";
+ }
+ }
+
+private:
+ std::unique_ptr<Expression> fExpression;
+
+ using INHERITED = Statement;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSetting.cpp b/gfx/skia/skia/src/sksl/ir/SkSLSetting.cpp
new file mode 100644
index 0000000000..303549bcf7
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLSetting.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLSetting.h"
+
+#include "include/core/SkTypes.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "src/core/SkTHash.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/SkSLUtil.h"
+#include "src/sksl/ir/SkSLLiteral.h"
+
+#include <initializer_list>
+
+namespace SkSL {
+
+namespace {
+
+using CapsLookupTable = SkTHashMap<std::string_view, Setting::CapsPtr>;
+
+static const CapsLookupTable& caps_lookup_table() {
+ // Create a lookup table that converts strings into the equivalent ShaderCaps member-pointers.
+ static CapsLookupTable* sCapsLookupTable = new CapsLookupTable({
+ CapsLookupTable::Pair("mustDoOpBetweenFloorAndAbs",
+ &ShaderCaps::fMustDoOpBetweenFloorAndAbs),
+ CapsLookupTable::Pair("mustGuardDivisionEvenAfterExplicitZeroCheck",
+ &ShaderCaps::fMustGuardDivisionEvenAfterExplicitZeroCheck),
+ CapsLookupTable::Pair("atan2ImplementedAsAtanYOverX",
+ &ShaderCaps::fAtan2ImplementedAsAtanYOverX),
+ CapsLookupTable::Pair("floatIs32Bits",
+ &ShaderCaps::fFloatIs32Bits),
+ CapsLookupTable::Pair("integerSupport",
+ &ShaderCaps::fIntegerSupport),
+ CapsLookupTable::Pair("builtinDeterminantSupport",
+ &ShaderCaps::fBuiltinDeterminantSupport),
+ CapsLookupTable::Pair("rewriteMatrixVectorMultiply",
+ &ShaderCaps::fRewriteMatrixVectorMultiply),
+ });
+ return *sCapsLookupTable;
+}
+
+} // namespace
+
+std::string_view Setting::name() const {
+ for (const auto& [name, capsPtr] : caps_lookup_table()) {
+ if (capsPtr == fCapsPtr) {
+ return name;
+ }
+ }
+ SkUNREACHABLE;
+}
+
+std::unique_ptr<Expression> Setting::Convert(const Context& context,
+ Position pos,
+ const std::string_view& name) {
+ SkASSERT(context.fConfig);
+
+ if (ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
+ context.fErrors->error(pos, "name 'sk_Caps' is reserved");
+ return nullptr;
+ }
+
+ const CapsPtr* capsPtr = caps_lookup_table().find(name);
+ if (!capsPtr) {
+ context.fErrors->error(pos, "unknown capability flag '" + std::string(name) + "'");
+ return nullptr;
+ }
+
+ return Setting::Make(context, pos, *capsPtr);
+}
+
+std::unique_ptr<Expression> Setting::Make(const Context& context, Position pos, CapsPtr capsPtr) {
+ if (context.fCaps) {
+ // We know the caps values--return a boolean literal.
+ return Literal::MakeBool(context, pos, context.fCaps->*capsPtr);
+ }
+
+ // We don't know the caps values yet--generate a Setting IRNode.
+ return std::make_unique<Setting>(pos, capsPtr, context.fTypes.fBool.get());
+}
+
+std::unique_ptr<Expression> Setting::toLiteral(const Context& context) const {
+ SkASSERT(context.fCaps);
+ return Literal::MakeBool(fPosition, context.fCaps->*fCapsPtr, &this->type());
+}
+
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSetting.h b/gfx/skia/skia/src/sksl/ir/SkSLSetting.h
new file mode 100644
index 0000000000..6085744ad0
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLSetting.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_SETTING
+#define SKSL_SETTING
+
+#include "include/private/SkSLIRNode.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/SkSLUtil.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <string_view>
+
+
+namespace SkSL {
+
+class Context;
+class Type;
+enum class OperatorPrecedence : uint8_t;
+
+/**
+ * Represents a compile-time constant setting, such as sk_Caps.integerSupport. These IRNodes are
+ * used when assembling a module. These nodes are replaced with the value of the setting during
+ * compilation when ShaderCaps are available.
+ */
+class Setting final : public Expression {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kSetting;
+
+ using CapsPtr = const bool ShaderCaps::*;
+
+ Setting(Position pos, CapsPtr capsPtr, const Type* type)
+ : INHERITED(pos, kIRNodeKind, type)
+ , fCapsPtr(capsPtr) {}
+
+ // Creates the current value of the associated caps bit as a Literal if ShaderCaps are
+ // available, or a Setting IRNode when ShaderCaps are not known. Reports errors via the
+ // ErrorReporter.
+ static std::unique_ptr<Expression> Convert(const Context& context,
+ Position pos,
+ const std::string_view& name);
+
+ // Creates the current value of the passed-in caps bit as a Literal if ShaderCaps are
+ // available, or a Setting IRNode when ShaderCaps are not known.
+ static std::unique_ptr<Expression> Make(const Context& context, Position pos, CapsPtr capsPtr);
+
+ // Converts a Setting expression to its actual ShaderCaps value (boolean true/false).
+ std::unique_ptr<Expression> toLiteral(const Context& context) const;
+
+ std::unique_ptr<Expression> clone(Position pos) const override {
+ return std::make_unique<Setting>(pos, fCapsPtr, &this->type());
+ }
+
+ std::string_view name() const;
+
+ std::string description(OperatorPrecedence) const override {
+ return "sk_Caps." + std::string(this->name());
+ }
+
+private:
+ CapsPtr fCapsPtr;
+
+ using INHERITED = Expression;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLStructDefinition.h b/gfx/skia/skia/src/sksl/ir/SkSLStructDefinition.h
new file mode 100644
index 0000000000..4b6b5b81d7
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLStructDefinition.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_STRUCTDEFINITION
+#define SKSL_STRUCTDEFINITION
+
+#include <memory>
+
+#include "include/private/SkSLProgramElement.h"
+#include "src/sksl/ir/SkSLSymbolTable.h"
+#include "src/sksl/ir/SkSLType.h"
+
+namespace SkSL {
+
+/**
+ * A struct at global scope, as in:
+ *
+ * struct RenderData {
+ * float3 color;
+ * bool highQuality;
+ * };
+ */
+class StructDefinition final : public ProgramElement {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kStructDefinition;
+
+ StructDefinition(Position pos, const Type& type)
+ : INHERITED(pos, kIRNodeKind)
+ , fType(&type) {}
+
+ const Type& type() const {
+ return *fType;
+ }
+
+ std::unique_ptr<ProgramElement> clone() const override {
+ return std::make_unique<StructDefinition>(fPosition, this->type());
+ }
+
+ std::string description() const override {
+ std::string s = "struct ";
+ s += this->type().name();
+ s += " { ";
+ for (const auto& f : this->type().fields()) {
+ s += f.fModifiers.description();
+ s += f.fType->description();
+ s += " ";
+ s += f.fName;
+ s += "; ";
+ }
+ s += "};";
+ return s;
+ }
+
+private:
+ const Type* fType = nullptr;
+
+ using INHERITED = ProgramElement;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSwitchCase.h b/gfx/skia/skia/src/sksl/ir/SkSLSwitchCase.h
new file mode 100644
index 0000000000..206693ceaf
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLSwitchCase.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_SWITCHCASE
+#define SKSL_SWITCHCASE
+
+#include "include/private/SkSLStatement.h"
+#include "include/private/SkSLString.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+#include <cinttypes>
+
+namespace SkSL {
+
+/**
+ * A single case of a 'switch' statement.
+ */
+class SwitchCase final : public Statement {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kSwitchCase;
+
+ static std::unique_ptr<SwitchCase> Make(Position pos, SKSL_INT value,
+ std::unique_ptr<Statement> statement) {
+ return std::unique_ptr<SwitchCase>(new SwitchCase(pos, /*isDefault=*/false, value,
+ std::move(statement)));
+ }
+
+ static std::unique_ptr<SwitchCase> MakeDefault(Position pos,
+ std::unique_ptr<Statement> statement) {
+ return std::unique_ptr<SwitchCase>(new SwitchCase(pos, /*isDefault=*/true, -1,
+ std::move(statement)));
+ }
+
+ bool isDefault() const {
+ return fDefault;
+ }
+
+ SKSL_INT value() const {
+ SkASSERT(!this->isDefault());
+ return fValue;
+ }
+
+ std::unique_ptr<Statement>& statement() {
+ return fStatement;
+ }
+
+ const std::unique_ptr<Statement>& statement() const {
+ return fStatement;
+ }
+
+ std::unique_ptr<Statement> clone() const override {
+ return fDefault ? SwitchCase::MakeDefault(fPosition, this->statement()->clone())
+ : SwitchCase::Make(fPosition, this->value(), this->statement()->clone());
+ }
+
+ std::string description() const override {
+ if (this->isDefault()) {
+ return String::printf("default:\n%s", fStatement->description().c_str());
+ } else {
+ return String::printf("case %" PRId64 ":\n%s",
+ (int64_t) this->value(),
+ fStatement->description().c_str());
+ }
+ }
+
+private:
+ SwitchCase(Position pos, bool isDefault, SKSL_INT value, std::unique_ptr<Statement> statement)
+ : INHERITED(pos, kIRNodeKind)
+ , fDefault(isDefault)
+ , fValue(std::move(value))
+ , fStatement(std::move(statement)) {}
+
+ bool fDefault;
+ SKSL_INT fValue;
+ std::unique_ptr<Statement> fStatement;
+
+ using INHERITED = Statement;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSwitchStatement.cpp b/gfx/skia/skia/src/sksl/ir/SkSLSwitchStatement.cpp
new file mode 100644
index 0000000000..f0bb7d05ce
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLSwitchStatement.cpp
@@ -0,0 +1,275 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLSwitchStatement.h"
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLString.h"
+#include "include/private/base/SkTArray.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "src/core/SkTHash.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLConstantFolder.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/ir/SkSLBlock.h"
+#include "src/sksl/ir/SkSLNop.h"
+#include "src/sksl/ir/SkSLSwitchCase.h"
+#include "src/sksl/ir/SkSLSymbolTable.h"
+#include "src/sksl/ir/SkSLType.h"
+
+#include <algorithm>
+#include <forward_list>
+#include <iterator>
+
+namespace SkSL {
+
+std::unique_ptr<Statement> SwitchStatement::clone() const {
+ StatementArray cases;
+ cases.reserve_back(this->cases().size());
+ for (const std::unique_ptr<Statement>& stmt : this->cases()) {
+ cases.push_back(stmt->clone());
+ }
+ return std::make_unique<SwitchStatement>(fPosition,
+ this->value()->clone(),
+ std::move(cases),
+ SymbolTable::WrapIfBuiltin(this->symbols()));
+}
+
+std::string SwitchStatement::description() const {
+ std::string result;
+ result += String::printf("switch (%s) {\n", this->value()->description().c_str());
+ for (const auto& c : this->cases()) {
+ result += c->description();
+ }
+ result += "}";
+ return result;
+}
+
+static std::forward_list<const SwitchCase*> find_duplicate_case_values(
+ const StatementArray& cases) {
+ std::forward_list<const SwitchCase*> duplicateCases;
+ SkTHashSet<SKSL_INT> intValues;
+ bool foundDefault = false;
+
+ for (const std::unique_ptr<Statement>& stmt : cases) {
+ const SwitchCase* sc = &stmt->as<SwitchCase>();
+ if (sc->isDefault()) {
+ if (foundDefault) {
+ duplicateCases.push_front(sc);
+ continue;
+ }
+ foundDefault = true;
+ } else {
+ SKSL_INT value = sc->value();
+ if (intValues.contains(value)) {
+ duplicateCases.push_front(sc);
+ continue;
+ }
+ intValues.add(value);
+ }
+ }
+
+ return duplicateCases;
+}
+
+static void move_all_but_break(std::unique_ptr<Statement>& stmt, StatementArray* target) {
+ switch (stmt->kind()) {
+ case Statement::Kind::kBlock: {
+ // Recurse into the block.
+ Block& block = stmt->as<Block>();
+
+ StatementArray blockStmts;
+ blockStmts.reserve_back(block.children().size());
+ for (std::unique_ptr<Statement>& blockStmt : block.children()) {
+ move_all_but_break(blockStmt, &blockStmts);
+ }
+
+ target->push_back(Block::Make(block.fPosition, std::move(blockStmts), block.blockKind(),
+ block.symbolTable()));
+ break;
+ }
+
+ case Statement::Kind::kBreak:
+ // Do not append a break to the target.
+ break;
+
+ default:
+ // Append normal statements to the target.
+ target->push_back(std::move(stmt));
+ break;
+ }
+}
+
+std::unique_ptr<Statement> SwitchStatement::BlockForCase(StatementArray* cases,
+ SwitchCase* caseToCapture,
+ std::shared_ptr<SymbolTable> symbolTable) {
+ // We have to be careful to not move any of the pointers until after we're sure we're going to
+ // succeed, so before we make any changes at all, we check the switch-cases to decide on a plan
+ // of action. First, find the switch-case we are interested in.
+ auto iter = cases->begin();
+ for (; iter != cases->end(); ++iter) {
+ const SwitchCase& sc = (*iter)->as<SwitchCase>();
+ if (&sc == caseToCapture) {
+ break;
+ }
+ }
+
+ // Next, walk forward through the rest of the switch. If we find a conditional break, we're
+ // stuck and can't simplify at all. If we find an unconditional break, we have a range of
+ // statements that we can use for simplification.
+ auto startIter = iter;
+ Statement* stripBreakStmt = nullptr;
+ for (; iter != cases->end(); ++iter) {
+ std::unique_ptr<Statement>& stmt = (*iter)->as<SwitchCase>().statement();
+ if (Analysis::SwitchCaseContainsConditionalExit(*stmt)) {
+ // We can't reduce switch-cases to a block when they have conditional exits.
+ return nullptr;
+ }
+ if (Analysis::SwitchCaseContainsUnconditionalExit(*stmt)) {
+ // We found an unconditional exit. We can use this block, but we'll need to strip
+ // out the break statement if there is one.
+ stripBreakStmt = stmt.get();
+ break;
+ }
+ }
+
+ // We fell off the bottom of the switch or encountered a break. We know the range of statements
+ // that we need to move over, and we know it's safe to do so.
+ StatementArray caseStmts;
+ caseStmts.reserve_back(std::distance(startIter, iter) + 1);
+
+ // We can move over most of the statements as-is.
+ while (startIter != iter) {
+ caseStmts.push_back(std::move((*startIter)->as<SwitchCase>().statement()));
+ ++startIter;
+ }
+
+ // If we found an unconditional break at the end, we need to move what we can while avoiding
+ // that break.
+ if (stripBreakStmt != nullptr) {
+ SkASSERT((*startIter)->as<SwitchCase>().statement().get() == stripBreakStmt);
+ move_all_but_break((*startIter)->as<SwitchCase>().statement(), &caseStmts);
+ }
+
+ // Return our newly-synthesized block.
+ return Block::Make(caseToCapture->fPosition, std::move(caseStmts), Block::Kind::kBracedScope,
+ std::move(symbolTable));
+}
+
+std::unique_ptr<Statement> SwitchStatement::Convert(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> value,
+ ExpressionArray caseValues,
+ StatementArray caseStatements,
+ std::shared_ptr<SymbolTable> symbolTable) {
+ SkASSERT(caseValues.size() == caseStatements.size());
+
+ value = context.fTypes.fInt->coerceExpression(std::move(value), context);
+ if (!value) {
+ return nullptr;
+ }
+
+ StatementArray cases;
+ for (int i = 0; i < caseValues.size(); ++i) {
+ if (caseValues[i]) {
+ Position casePos = caseValues[i]->fPosition;
+ // Case values must be constant integers of the same type as the switch value
+ std::unique_ptr<Expression> caseValue = value->type().coerceExpression(
+ std::move(caseValues[i]), context);
+ if (!caseValue) {
+ return nullptr;
+ }
+ SKSL_INT intValue;
+ if (!ConstantFolder::GetConstantInt(*caseValue, &intValue)) {
+ context.fErrors->error(casePos, "case value must be a constant integer");
+ return nullptr;
+ }
+ cases.push_back(SwitchCase::Make(casePos, intValue, std::move(caseStatements[i])));
+ } else {
+ cases.push_back(SwitchCase::MakeDefault(pos, std::move(caseStatements[i])));
+ }
+ }
+
+ // Detect duplicate `case` labels and report an error.
+ // (Using forward_list here to optimize for the common case of no results.)
+ std::forward_list<const SwitchCase*> duplicateCases = find_duplicate_case_values(cases);
+ if (!duplicateCases.empty()) {
+ duplicateCases.reverse();
+ for (const SwitchCase* sc : duplicateCases) {
+ if (sc->isDefault()) {
+ context.fErrors->error(sc->fPosition, "duplicate default case");
+ } else {
+ context.fErrors->error(sc->fPosition, "duplicate case value '" +
+ std::to_string(sc->value()) + "'");
+ }
+ }
+ return nullptr;
+ }
+
+ return SwitchStatement::Make(
+ context, pos, std::move(value), std::move(cases), std::move(symbolTable));
+}
+
+std::unique_ptr<Statement> SwitchStatement::Make(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> value,
+ StatementArray cases,
+ std::shared_ptr<SymbolTable> symbolTable) {
+ // Confirm that every statement in `cases` is a SwitchCase.
+ SkASSERT(std::all_of(cases.begin(), cases.end(), [&](const std::unique_ptr<Statement>& stmt) {
+ return stmt->is<SwitchCase>();
+ }));
+
+ // Confirm that every switch-case value is unique.
+ SkASSERT(find_duplicate_case_values(cases).empty());
+
+ // Flatten switch statements if we're optimizing, and the value is known
+ if (context.fConfig->fSettings.fOptimize) {
+ SKSL_INT switchValue;
+ if (ConstantFolder::GetConstantInt(*value, &switchValue)) {
+ SwitchCase* defaultCase = nullptr;
+ SwitchCase* matchingCase = nullptr;
+ for (const std::unique_ptr<Statement>& stmt : cases) {
+ SwitchCase& sc = stmt->as<SwitchCase>();
+ if (sc.isDefault()) {
+ defaultCase = &sc;
+ continue;
+ }
+
+ if (sc.value() == switchValue) {
+ matchingCase = &sc;
+ break;
+ }
+ }
+
+ if (!matchingCase) {
+ // No case value matches the switch value.
+ if (!defaultCase) {
+ // No default switch-case exists; the switch had no effect.
+ // We can eliminate the entire switch!
+ return Nop::Make();
+ }
+ // We had a default case; that's what we matched with.
+ matchingCase = defaultCase;
+ }
+
+ // Convert the switch-case that we matched with into a block.
+ std::unique_ptr<Statement> newBlock = BlockForCase(&cases, matchingCase, symbolTable);
+ if (newBlock) {
+ return newBlock;
+ }
+ }
+ }
+
+ // The switch couldn't be optimized away; emit it normally.
+ return std::make_unique<SwitchStatement>(
+ pos, std::move(value), std::move(cases), std::move(symbolTable));
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSwitchStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLSwitchStatement.h
new file mode 100644
index 0000000000..71b96aa229
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLSwitchStatement.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_SWITCHSTATEMENT
+#define SKSL_SWITCHSTATEMENT
+
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLStatement.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace SkSL {
+
+class Context;
+class SwitchCase;
+class SymbolTable;
+
+/**
+ * A 'switch' statement.
+ */
+class SwitchStatement final : public Statement {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kSwitch;
+
+ SwitchStatement(Position pos, std::unique_ptr<Expression> value,
+ StatementArray cases, std::shared_ptr<SymbolTable> symbols)
+ : INHERITED(pos, kIRNodeKind)
+ , fValue(std::move(value))
+ , fCases(std::move(cases))
+ , fSymbols(std::move(symbols)) {}
+
+ // Create a `switch` statement with an array of case-values and case-statements.
+ // Coerces case values to the proper type and reports an error if cases are duplicated.
+ // Reports errors via the ErrorReporter.
+ static std::unique_ptr<Statement> Convert(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> value,
+ ExpressionArray caseValues,
+ StatementArray caseStatements,
+ std::shared_ptr<SymbolTable> symbolTable);
+
+ // Create a `switch` statement with an array of SwitchCases. The array of SwitchCases must
+ // already contain non-overlapping, correctly-typed case values. Reports errors via ASSERT.
+ static std::unique_ptr<Statement> Make(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> value,
+ StatementArray cases,
+ std::shared_ptr<SymbolTable> symbolTable);
+
+ // Returns a block containing all of the statements that will be run if the given case matches
+ // (which, owing to the statements being owned by unique_ptrs, means the switch itself will be
+ // disassembled by this call and must then be discarded).
+ // Returns null (and leaves the switch unmodified) if no such simple reduction is possible, such
+ // as when break statements appear inside conditionals.
+ static std::unique_ptr<Statement> BlockForCase(StatementArray* cases,
+ SwitchCase* caseToCapture,
+ std::shared_ptr<SymbolTable> symbolTable);
+
+ std::unique_ptr<Expression>& value() {
+ return fValue;
+ }
+
+ const std::unique_ptr<Expression>& value() const {
+ return fValue;
+ }
+
+ StatementArray& cases() {
+ return fCases;
+ }
+
+ const StatementArray& cases() const {
+ return fCases;
+ }
+
+ const std::shared_ptr<SymbolTable>& symbols() const {
+ return fSymbols;
+ }
+
+ std::unique_ptr<Statement> clone() const override;
+
+ std::string description() const override;
+
+private:
+ std::unique_ptr<Expression> fValue;
+ StatementArray fCases; // every Statement inside fCases must be a SwitchCase
+ std::shared_ptr<SymbolTable> fSymbols;
+
+ using INHERITED = Statement;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSwizzle.cpp b/gfx/skia/skia/src/sksl/ir/SkSLSwizzle.cpp
new file mode 100644
index 0000000000..a19ff0274e
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLSwizzle.cpp
@@ -0,0 +1,548 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLSwizzle.h"
+
+#include "include/core/SkSpan.h"
+#include "include/private/SkSLString.h"
+#include "include/private/base/SkTArray.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "include/sksl/SkSLOperator.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLConstantFolder.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/ir/SkSLConstructorCompound.h"
+#include "src/sksl/ir/SkSLConstructorCompoundCast.h"
+#include "src/sksl/ir/SkSLConstructorScalarCast.h"
+#include "src/sksl/ir/SkSLConstructorSplat.h"
+#include "src/sksl/ir/SkSLLiteral.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+
+namespace SkSL {
+
+static bool validate_swizzle_domain(const ComponentArray& fields) {
+ enum SwizzleDomain {
+ kCoordinate,
+ kColor,
+ kUV,
+ kRectangle,
+ };
+
+ std::optional<SwizzleDomain> domain;
+
+ for (int8_t field : fields) {
+ SwizzleDomain fieldDomain;
+ switch (field) {
+ case SwizzleComponent::X:
+ case SwizzleComponent::Y:
+ case SwizzleComponent::Z:
+ case SwizzleComponent::W:
+ fieldDomain = kCoordinate;
+ break;
+ case SwizzleComponent::R:
+ case SwizzleComponent::G:
+ case SwizzleComponent::B:
+ case SwizzleComponent::A:
+ fieldDomain = kColor;
+ break;
+ case SwizzleComponent::S:
+ case SwizzleComponent::T:
+ case SwizzleComponent::P:
+ case SwizzleComponent::Q:
+ fieldDomain = kUV;
+ break;
+ case SwizzleComponent::UL:
+ case SwizzleComponent::UT:
+ case SwizzleComponent::UR:
+ case SwizzleComponent::UB:
+ fieldDomain = kRectangle;
+ break;
+ case SwizzleComponent::ZERO:
+ case SwizzleComponent::ONE:
+ continue;
+ default:
+ return false;
+ }
+
+ if (!domain.has_value()) {
+ domain = fieldDomain;
+ } else if (domain != fieldDomain) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static char mask_char(int8_t component) {
+ switch (component) {
+ case SwizzleComponent::X: return 'x';
+ case SwizzleComponent::Y: return 'y';
+ case SwizzleComponent::Z: return 'z';
+ case SwizzleComponent::W: return 'w';
+ case SwizzleComponent::R: return 'r';
+ case SwizzleComponent::G: return 'g';
+ case SwizzleComponent::B: return 'b';
+ case SwizzleComponent::A: return 'a';
+ case SwizzleComponent::S: return 's';
+ case SwizzleComponent::T: return 't';
+ case SwizzleComponent::P: return 'p';
+ case SwizzleComponent::Q: return 'q';
+ case SwizzleComponent::UL: return 'L';
+ case SwizzleComponent::UT: return 'T';
+ case SwizzleComponent::UR: return 'R';
+ case SwizzleComponent::UB: return 'B';
+ case SwizzleComponent::ZERO: return '0';
+ case SwizzleComponent::ONE: return '1';
+ default: SkUNREACHABLE;
+ }
+}
+
+static std::string mask_string(const ComponentArray& components) {
+ std::string result;
+ for (int8_t component : components) {
+ result += mask_char(component);
+ }
+ return result;
+}
+
+static std::unique_ptr<Expression> optimize_constructor_swizzle(const Context& context,
+ Position pos,
+ const ConstructorCompound& base,
+ ComponentArray components) {
+ auto baseArguments = base.argumentSpan();
+ std::unique_ptr<Expression> replacement;
+ const Type& exprType = base.type();
+ const Type& componentType = exprType.componentType();
+ int swizzleSize = components.size();
+
+ // Swizzles can duplicate some elements and discard others, e.g.
+ // `half4(1, 2, 3, 4).xxz` --> `half3(1, 1, 3)`. However, there are constraints:
+ // - Expressions with side effects need to occur exactly once, even if they would otherwise be
+ // swizzle-eliminated
+ // - Non-trivial expressions should not be repeated, but elimination is OK.
+ //
+ // Look up the argument for the constructor at each index. This is typically simple but for
+ // weird cases like `half4(bar.yz, half2(foo))`, it can be harder than it seems. This example
+ // would result in:
+ // argMap[0] = {.fArgIndex = 0, .fComponent = 0} (bar.yz .x)
+ // argMap[1] = {.fArgIndex = 0, .fComponent = 1} (bar.yz .y)
+ // argMap[2] = {.fArgIndex = 1, .fComponent = 0} (half2(foo) .x)
+ // argMap[3] = {.fArgIndex = 1, .fComponent = 1} (half2(foo) .y)
+ struct ConstructorArgMap {
+ int8_t fArgIndex;
+ int8_t fComponent;
+ };
+
+ int numConstructorArgs = base.type().columns();
+ ConstructorArgMap argMap[4] = {};
+ int writeIdx = 0;
+ for (int argIdx = 0; argIdx < (int)baseArguments.size(); ++argIdx) {
+ const Expression& arg = *baseArguments[argIdx];
+ const Type& argType = arg.type();
+
+ if (!argType.isScalar() && !argType.isVector()) {
+ return nullptr;
+ }
+
+ int argSlots = argType.slotCount();
+ for (int componentIdx = 0; componentIdx < argSlots; ++componentIdx) {
+ argMap[writeIdx].fArgIndex = argIdx;
+ argMap[writeIdx].fComponent = componentIdx;
+ ++writeIdx;
+ }
+ }
+ SkASSERT(writeIdx == numConstructorArgs);
+
+ // Count up the number of times each constructor argument is used by the swizzle.
+ // `half4(bar.yz, half2(foo)).xwxy` -> { 3, 1 }
+ // - bar.yz is referenced 3 times, by `.x_xy`
+ // - half(foo) is referenced 1 time, by `._w__`
+ int8_t exprUsed[4] = {};
+ for (int8_t c : components) {
+ exprUsed[argMap[c].fArgIndex]++;
+ }
+
+ for (int index = 0; index < numConstructorArgs; ++index) {
+ int8_t constructorArgIndex = argMap[index].fArgIndex;
+ const Expression& baseArg = *baseArguments[constructorArgIndex];
+
+ // Check that non-trivial expressions are not swizzled in more than once.
+ if (exprUsed[constructorArgIndex] > 1 && !Analysis::IsTrivialExpression(baseArg)) {
+ return nullptr;
+ }
+ // Check that side-effect-bearing expressions are swizzled in exactly once.
+ if (exprUsed[constructorArgIndex] != 1 && Analysis::HasSideEffects(baseArg)) {
+ return nullptr;
+ }
+ }
+
+ struct ReorderedArgument {
+ int8_t fArgIndex;
+ ComponentArray fComponents;
+ };
+ SkSTArray<4, ReorderedArgument> reorderedArgs;
+ for (int8_t c : components) {
+ const ConstructorArgMap& argument = argMap[c];
+ const Expression& baseArg = *baseArguments[argument.fArgIndex];
+
+ if (baseArg.type().isScalar()) {
+ // This argument is a scalar; add it to the list as-is.
+ SkASSERT(argument.fComponent == 0);
+ reorderedArgs.push_back({argument.fArgIndex,
+ ComponentArray{}});
+ } else {
+ // This argument is a component from a vector.
+ SkASSERT(baseArg.type().isVector());
+ SkASSERT(argument.fComponent < baseArg.type().columns());
+ if (reorderedArgs.empty() ||
+ reorderedArgs.back().fArgIndex != argument.fArgIndex) {
+ // This can't be combined with the previous argument. Add a new one.
+ reorderedArgs.push_back({argument.fArgIndex,
+ ComponentArray{argument.fComponent}});
+ } else {
+ // Since we know this argument uses components, it should already have at least one
+ // component set.
+ SkASSERT(!reorderedArgs.back().fComponents.empty());
+ // Build up the current argument with one more component.
+ reorderedArgs.back().fComponents.push_back(argument.fComponent);
+ }
+ }
+ }
+
+ // Convert our reordered argument list to an actual array of expressions, with the new order and
+ // any new inner swizzles that need to be applied.
+ ExpressionArray newArgs;
+ newArgs.reserve_back(swizzleSize);
+ for (const ReorderedArgument& reorderedArg : reorderedArgs) {
+ std::unique_ptr<Expression> newArg =
+ baseArguments[reorderedArg.fArgIndex]->clone();
+
+ if (reorderedArg.fComponents.empty()) {
+ newArgs.push_back(std::move(newArg));
+ } else {
+ newArgs.push_back(Swizzle::Make(context, pos, std::move(newArg),
+ reorderedArg.fComponents));
+ }
+ }
+
+ // Wrap the new argument list in a compound constructor.
+ return ConstructorCompound::Make(context,
+ pos,
+ componentType.toCompound(context, swizzleSize, /*rows=*/1),
+ std::move(newArgs));
+}
+
+std::unique_ptr<Expression> Swizzle::Convert(const Context& context,
+ Position pos,
+ Position maskPos,
+ std::unique_ptr<Expression> base,
+ std::string_view maskString) {
+ ComponentArray components;
+ for (size_t i = 0; i < maskString.length(); ++i) {
+ char field = maskString[i];
+ switch (field) {
+ case '0': components.push_back(SwizzleComponent::ZERO); break;
+ case '1': components.push_back(SwizzleComponent::ONE); break;
+ case 'x': components.push_back(SwizzleComponent::X); break;
+ case 'r': components.push_back(SwizzleComponent::R); break;
+ case 's': components.push_back(SwizzleComponent::S); break;
+ case 'L': components.push_back(SwizzleComponent::UL); break;
+ case 'y': components.push_back(SwizzleComponent::Y); break;
+ case 'g': components.push_back(SwizzleComponent::G); break;
+ case 't': components.push_back(SwizzleComponent::T); break;
+ case 'T': components.push_back(SwizzleComponent::UT); break;
+ case 'z': components.push_back(SwizzleComponent::Z); break;
+ case 'b': components.push_back(SwizzleComponent::B); break;
+ case 'p': components.push_back(SwizzleComponent::P); break;
+ case 'R': components.push_back(SwizzleComponent::UR); break;
+ case 'w': components.push_back(SwizzleComponent::W); break;
+ case 'a': components.push_back(SwizzleComponent::A); break;
+ case 'q': components.push_back(SwizzleComponent::Q); break;
+ case 'B': components.push_back(SwizzleComponent::UB); break;
+ default:
+ context.fErrors->error(Position::Range(maskPos.startOffset() + i,
+ maskPos.startOffset() + i + 1),
+ String::printf("invalid swizzle component '%c'", field));
+ return nullptr;
+ }
+ }
+ return Convert(context, pos, maskPos, std::move(base), std::move(components));
+}
+
+// Swizzles are complicated due to constant components. The most difficult case is a mask like
+// '.x1w0'. A naive approach might turn that into 'float4(base.x, 1, base.w, 0)', but that evaluates
+// 'base' twice. We instead group the swizzle mask ('xw') and constants ('1, 0') together and use a
+// secondary swizzle to put them back into the right order, so in this case we end up with
+// 'float4(base.xw, 1, 0).xzyw'.
+std::unique_ptr<Expression> Swizzle::Convert(const Context& context,
+ Position pos,
+ Position rawMaskPos,
+ std::unique_ptr<Expression> base,
+ ComponentArray inComponents) {
+ Position maskPos = rawMaskPos.valid() ? rawMaskPos : pos;
+ if (!validate_swizzle_domain(inComponents)) {
+ context.fErrors->error(maskPos, "invalid swizzle mask '" + mask_string(inComponents) + "'");
+ return nullptr;
+ }
+
+ const Type& baseType = base->type().scalarTypeForLiteral();
+
+ if (!baseType.isVector() && !baseType.isScalar()) {
+ context.fErrors->error(
+ pos, "cannot swizzle value of type '" + baseType.displayName() + "'");
+ return nullptr;
+ }
+
+ if (inComponents.size() > 4) {
+ Position errorPos = rawMaskPos.valid() ? Position::Range(maskPos.startOffset() + 4,
+ maskPos.endOffset())
+ : pos;
+ context.fErrors->error(errorPos,
+ "too many components in swizzle mask '" + mask_string(inComponents) + "'");
+ return nullptr;
+ }
+
+ ComponentArray maskComponents;
+ bool foundXYZW = false;
+ for (int i = 0; i < inComponents.size(); ++i) {
+ switch (inComponents[i]) {
+ case SwizzleComponent::ZERO:
+ case SwizzleComponent::ONE:
+ // Skip over constant fields for now.
+ break;
+ case SwizzleComponent::X:
+ case SwizzleComponent::R:
+ case SwizzleComponent::S:
+ case SwizzleComponent::UL:
+ foundXYZW = true;
+ maskComponents.push_back(SwizzleComponent::X);
+ break;
+ case SwizzleComponent::Y:
+ case SwizzleComponent::G:
+ case SwizzleComponent::T:
+ case SwizzleComponent::UT:
+ foundXYZW = true;
+ if (baseType.columns() >= 2) {
+ maskComponents.push_back(SwizzleComponent::Y);
+ break;
+ }
+ [[fallthrough]];
+ case SwizzleComponent::Z:
+ case SwizzleComponent::B:
+ case SwizzleComponent::P:
+ case SwizzleComponent::UR:
+ foundXYZW = true;
+ if (baseType.columns() >= 3) {
+ maskComponents.push_back(SwizzleComponent::Z);
+ break;
+ }
+ [[fallthrough]];
+ case SwizzleComponent::W:
+ case SwizzleComponent::A:
+ case SwizzleComponent::Q:
+ case SwizzleComponent::UB:
+ foundXYZW = true;
+ if (baseType.columns() >= 4) {
+ maskComponents.push_back(SwizzleComponent::W);
+ break;
+ }
+ [[fallthrough]];
+ default:
+ // The swizzle component references a field that doesn't exist in the base type.
+ context.fErrors->error(
+ Position::Range(maskPos.startOffset() + i,
+ maskPos.startOffset() + i + 1),
+ String::printf("invalid swizzle component '%c'",
+ mask_char(inComponents[i])));
+ return nullptr;
+ }
+ }
+
+ if (!foundXYZW) {
+ context.fErrors->error(maskPos, "swizzle must refer to base expression");
+ return nullptr;
+ }
+
+ // Coerce literals in expressions such as `(12345).xxx` to their actual type.
+ base = baseType.coerceExpression(std::move(base), context);
+ if (!base) {
+ return nullptr;
+ }
+
+ // First, we need a vector expression that is the non-constant portion of the swizzle, packed:
+ // scalar.xxx -> type3(scalar)
+ // scalar.x0x0 -> type2(scalar)
+ // vector.zyx -> vector.zyx
+ // vector.x0y0 -> vector.xy
+ std::unique_ptr<Expression> expr = Swizzle::Make(context, pos, std::move(base), maskComponents);
+
+ // If we have processed the entire swizzle, we're done.
+ if (maskComponents.size() == inComponents.size()) {
+ return expr;
+ }
+
+ // Now we create a constructor that has the correct number of elements for the final swizzle,
+ // with all fields at the start. It's not finished yet; constants we need will be added below.
+ // scalar.x0x0 -> type4(type2(x), ...)
+ // vector.y111 -> type4(vector.y, ...)
+ // vector.z10x -> type4(vector.zx, ...)
+ //
+ // The constructor will have at most three arguments: { base expr, constant 0, constant 1 }
+ ExpressionArray constructorArgs;
+ constructorArgs.reserve_back(3);
+ constructorArgs.push_back(std::move(expr));
+
+ // Apply another swizzle to shuffle the constants into the correct place. Any constant values we
+ // need are also tacked on to the end of the constructor.
+ // scalar.x0x0 -> type4(type2(x), 0).xyxy
+ // vector.y111 -> type2(vector.y, 1).xyyy
+ // vector.z10x -> type4(vector.zx, 1, 0).xzwy
+ const Type* scalarType = &baseType.componentType();
+ ComponentArray swizzleComponents;
+ int maskFieldIdx = 0;
+ int constantFieldIdx = maskComponents.size();
+ int constantZeroIdx = -1, constantOneIdx = -1;
+
+ for (int i = 0; i < inComponents.size(); i++) {
+ switch (inComponents[i]) {
+ case SwizzleComponent::ZERO:
+ if (constantZeroIdx == -1) {
+ // Synthesize a '0' argument at the end of the constructor.
+ constructorArgs.push_back(Literal::Make(pos, /*value=*/0, scalarType));
+ constantZeroIdx = constantFieldIdx++;
+ }
+ swizzleComponents.push_back(constantZeroIdx);
+ break;
+ case SwizzleComponent::ONE:
+ if (constantOneIdx == -1) {
+ // Synthesize a '1' argument at the end of the constructor.
+ constructorArgs.push_back(Literal::Make(pos, /*value=*/1, scalarType));
+ constantOneIdx = constantFieldIdx++;
+ }
+ swizzleComponents.push_back(constantOneIdx);
+ break;
+ default:
+ // The non-constant fields are already in the expected order.
+ swizzleComponents.push_back(maskFieldIdx++);
+ break;
+ }
+ }
+
+ expr = ConstructorCompound::Make(context, pos,
+ scalarType->toCompound(context, constantFieldIdx, /*rows=*/1),
+ std::move(constructorArgs));
+
+ // Create (and potentially optimize-away) the resulting swizzle-expression.
+ return Swizzle::Make(context, pos, std::move(expr), swizzleComponents);
+}
+
+std::unique_ptr<Expression> Swizzle::Make(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> expr,
+ ComponentArray components) {
+ const Type& exprType = expr->type();
+ SkASSERTF(exprType.isVector() || exprType.isScalar(),
+ "cannot swizzle type '%s'", exprType.description().c_str());
+ SkASSERT(components.size() >= 1 && components.size() <= 4);
+
+ // Confirm that the component array only contains X/Y/Z/W. (Call MakeWith01 if you want support
+ // for ZERO and ONE. Once initial IR generation is complete, no swizzles should have zeros or
+ // ones in them.)
+ SkASSERT(std::all_of(components.begin(), components.end(), [](int8_t component) {
+ return component >= SwizzleComponent::X &&
+ component <= SwizzleComponent::W;
+ }));
+
+ // SkSL supports splatting a scalar via `scalar.xxxx`, but not all versions of GLSL allow this.
+ // Replace swizzles with equivalent splat constructors (`scalar.xxx` --> `half3(value)`).
+ if (exprType.isScalar()) {
+ return ConstructorSplat::Make(context, pos,
+ exprType.toCompound(context, components.size(), /*rows=*/1),
+ std::move(expr));
+ }
+
+ // Detect identity swizzles like `color.rgba` and optimize it away.
+ if (components.size() == exprType.columns()) {
+ bool identity = true;
+ for (int i = 0; i < components.size(); ++i) {
+ if (components[i] != i) {
+ identity = false;
+ break;
+ }
+ }
+ if (identity) {
+ expr->fPosition = pos;
+ return expr;
+ }
+ }
+
+ // Optimize swizzles of swizzles, e.g. replace `foo.argb.rggg` with `foo.arrr`.
+ if (expr->is<Swizzle>()) {
+ Swizzle& base = expr->as<Swizzle>();
+ ComponentArray combined;
+ for (int8_t c : components) {
+ combined.push_back(base.components()[c]);
+ }
+
+ // It may actually be possible to further simplify this swizzle. Go again.
+ // (e.g. `color.abgr.abgr` --> `color.rgba` --> `color`.)
+ return Swizzle::Make(context, pos, std::move(base.base()), combined);
+ }
+
+ // If we are swizzling a constant expression, we can use its value instead here (so that
+ // swizzles like `colorWhite.x` can be simplified to `1`).
+ const Expression* value = ConstantFolder::GetConstantValueForVariable(*expr);
+
+ // `half4(scalar).zyy` can be optimized to `half3(scalar)`, and `half3(scalar).y` can be
+ // optimized to just `scalar`. The swizzle components don't actually matter, as every field
+ // in a splat constructor holds the same value.
+ if (value->is<ConstructorSplat>()) {
+ const ConstructorSplat& splat = value->as<ConstructorSplat>();
+ return ConstructorSplat::Make(
+ context, pos,
+ splat.type().componentType().toCompound(context, components.size(), /*rows=*/1),
+ splat.argument()->clone());
+ }
+
+ // Swizzles on casts, like `half4(myFloat4).zyy`, can optimize to `half3(myFloat4.zyy)`.
+ if (value->is<ConstructorCompoundCast>()) {
+ const ConstructorCompoundCast& cast = value->as<ConstructorCompoundCast>();
+ const Type& castType = cast.type().componentType().toCompound(context, components.size(),
+ /*rows=*/1);
+ std::unique_ptr<Expression> swizzled = Swizzle::Make(context, pos, cast.argument()->clone(),
+ std::move(components));
+ return (castType.columns() > 1)
+ ? ConstructorCompoundCast::Make(context, pos, castType, std::move(swizzled))
+ : ConstructorScalarCast::Make(context, pos, castType, std::move(swizzled));
+ }
+
+ // Swizzles on compound constructors, like `half4(1, 2, 3, 4).yw`, can become `half2(2, 4)`.
+ if (value->is<ConstructorCompound>()) {
+ const ConstructorCompound& ctor = value->as<ConstructorCompound>();
+ if (auto replacement = optimize_constructor_swizzle(context, pos, ctor, components)) {
+ return replacement;
+ }
+ }
+
+ // The swizzle could not be simplified, so apply the requested swizzle to the base expression.
+ return std::make_unique<Swizzle>(context, pos, std::move(expr), components);
+}
+
+std::string Swizzle::description(OperatorPrecedence) const {
+ std::string result = this->base()->description(OperatorPrecedence::kPostfix) + ".";
+ for (int x : this->components()) {
+ result += "xyzw"[x];
+ }
+ return result;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSwizzle.h b/gfx/skia/skia/src/sksl/ir/SkSLSwizzle.h
new file mode 100644
index 0000000000..9911546103
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLSwizzle.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_SWIZZLE
+#define SKSL_SWIZZLE
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLType.h"
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+
+namespace SkSL {
+
+class Context;
+enum class OperatorPrecedence : uint8_t;
+
+/**
+ * Represents a vector swizzle operation such as 'float3(1, 2, 3).zyx'.
+ */
+class Swizzle final : public Expression {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kSwizzle;
+
+ Swizzle(const Context& context, Position pos, std::unique_ptr<Expression> base,
+ const ComponentArray& components)
+ : INHERITED(pos, kIRNodeKind,
+ &base->type().componentType().toCompound(context, components.size(), 1))
+ , fBase(std::move(base))
+ , fComponents(components) {
+ SkASSERT(this->components().size() >= 1 && this->components().size() <= 4);
+ }
+
+ // Swizzle::Convert permits component arrays containing ZERO or ONE, does typechecking, reports
+ // errors via ErrorReporter, and returns an expression that combines constructors and native
+ // swizzles (comprised solely of X/Y/W/Z).
+ static std::unique_ptr<Expression> Convert(const Context& context,
+ Position pos,
+ Position maskPos,
+ std::unique_ptr<Expression> base,
+ ComponentArray inComponents);
+
+ static std::unique_ptr<Expression> Convert(const Context& context,
+ Position pos,
+ Position maskPos,
+ std::unique_ptr<Expression> base,
+ std::string_view maskString);
+
+ // Swizzle::Make does not permit ZERO or ONE in the component array, just X/Y/Z/W; errors are
+ // reported via ASSERT.
+ static std::unique_ptr<Expression> Make(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> expr,
+ ComponentArray inComponents);
+
+ std::unique_ptr<Expression>& base() {
+ return fBase;
+ }
+
+ const std::unique_ptr<Expression>& base() const {
+ return fBase;
+ }
+
+ const ComponentArray& components() const {
+ return fComponents;
+ }
+
+ std::unique_ptr<Expression> clone(Position pos) const override {
+ return std::unique_ptr<Expression>(new Swizzle(pos, &this->type(), this->base()->clone(),
+ this->components()));
+ }
+
+ std::string description(OperatorPrecedence) const override;
+
+private:
+ Swizzle(Position pos, const Type* type, std::unique_ptr<Expression> base,
+ const ComponentArray& components)
+ : INHERITED(pos, kIRNodeKind, type)
+ , fBase(std::move(base))
+ , fComponents(components) {
+ SkASSERT(this->components().size() >= 1 && this->components().size() <= 4);
+ }
+
+ std::unique_ptr<Expression> fBase;
+ ComponentArray fComponents;
+
+ using INHERITED = Expression;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSymbolTable.cpp b/gfx/skia/skia/src/sksl/ir/SkSLSymbolTable.cpp
new file mode 100644
index 0000000000..e8771c9560
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLSymbolTable.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLSymbolTable.h"
+
+#include "src/sksl/SkSLThreadContext.h"
+#include "src/sksl/ir/SkSLFunctionDeclaration.h"
+#include "src/sksl/ir/SkSLType.h"
+
+namespace SkSL {
+
+bool SymbolTable::isType(std::string_view name) const {
+ const Symbol* symbol = this->find(name);
+ return symbol && symbol->is<Type>();
+}
+
+bool SymbolTable::isBuiltinType(std::string_view name) const {
+ if (!this->isBuiltin()) {
+ return fParent && fParent->isBuiltinType(name);
+ }
+ return this->isType(name);
+}
+
+const Symbol* SymbolTable::findBuiltinSymbol(std::string_view name) const {
+ if (!this->isBuiltin()) {
+ return fParent ? fParent->findBuiltinSymbol(name) : nullptr;
+ }
+ return this->find(name);
+}
+
+Symbol* SymbolTable::lookup(const SymbolKey& key) const {
+ Symbol** symbolPPtr = fSymbols.find(key);
+ if (symbolPPtr) {
+ return *symbolPPtr;
+ }
+
+ // The symbol wasn't found; recurse into the parent symbol table.
+ return fParent ? fParent->lookup(key) : nullptr;
+}
+
+void SymbolTable::renameSymbol(Symbol* symbol, std::string_view newName) {
+ if (symbol->is<FunctionDeclaration>()) {
+ // This is a function declaration, so we need to rename the entire overload set.
+ for (FunctionDeclaration* fn = &symbol->as<FunctionDeclaration>(); fn != nullptr;
+ fn = fn->mutableNextOverload()) {
+ fn->setName(newName);
+ }
+ } else {
+ // Other types of symbols don't allow multiple symbols with the same name.
+ symbol->setName(newName);
+ }
+
+ this->addWithoutOwnership(symbol);
+}
+
+const std::string* SymbolTable::takeOwnershipOfString(std::string str) {
+ fOwnedStrings.push_front(std::move(str));
+ // Because fOwnedStrings is a linked list, pointers to elements are stable.
+ return &fOwnedStrings.front();
+}
+
+void SymbolTable::addWithoutOwnership(Symbol* symbol) {
+ auto key = MakeSymbolKey(symbol->name());
+
+ // If this is a function declaration, we need to keep the overload chain in sync.
+ if (symbol->is<FunctionDeclaration>()) {
+ // If we have a function with the same name...
+ Symbol* existingSymbol = this->lookup(key);
+ if (existingSymbol && existingSymbol->is<FunctionDeclaration>()) {
+ // ... add the existing function as the next overload in the chain.
+ FunctionDeclaration* existingDecl = &existingSymbol->as<FunctionDeclaration>();
+ symbol->as<FunctionDeclaration>().setNextOverload(existingDecl);
+ fSymbols[key] = symbol;
+ return;
+ }
+ }
+
+ if (fAtModuleBoundary && fParent && fParent->lookup(key)) {
+ // We are attempting to declare a symbol at global scope that already exists in a parent
+ // module. This is a duplicate symbol and should be rejected.
+ } else {
+ Symbol*& refInSymbolTable = fSymbols[key];
+
+ if (refInSymbolTable == nullptr) {
+ refInSymbolTable = symbol;
+ return;
+ }
+ }
+
+ ThreadContext::ReportError("symbol '" + std::string(symbol->name()) + "' was already defined",
+ symbol->fPosition);
+}
+
+void SymbolTable::injectWithoutOwnership(Symbol* symbol) {
+ auto key = MakeSymbolKey(symbol->name());
+ fSymbols[key] = symbol;
+}
+
+const Type* SymbolTable::addArrayDimension(const Type* type, int arraySize) {
+ if (arraySize == 0) {
+ return type;
+ }
+ // If this is a builtin type, we add it as high as possible in the symbol table tree (at the
+ // module boundary), to enable additional reuse of the array-type.
+ if (type->isInBuiltinTypes() && fParent && !fAtModuleBoundary) {
+ return fParent->addArrayDimension(type, arraySize);
+ }
+ // Reuse an existing array type with this name if one already exists in our symbol table.
+ std::string arrayName = type->getArrayName(arraySize);
+ if (const Symbol* existingType = this->find(arrayName)) {
+ return &existingType->as<Type>();
+ }
+ // Add a new array type to the symbol table.
+ const std::string* arrayNamePtr = this->takeOwnershipOfString(std::move(arrayName));
+ return this->add(Type::MakeArrayType(*arrayNamePtr, *type, arraySize));
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSymbolTable.h b/gfx/skia/skia/src/sksl/ir/SkSLSymbolTable.h
new file mode 100644
index 0000000000..47f619c82a
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLSymbolTable.h
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_SYMBOLTABLE
+#define SKSL_SYMBOLTABLE
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkOpts_spi.h"
+#include "include/private/SkSLSymbol.h"
+#include "src/core/SkTHash.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <forward_list>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+namespace SkSL {
+
+class Type;
+
+/**
+ * Maps identifiers to symbols.
+ */
+class SymbolTable {
+public:
+ explicit SymbolTable(bool builtin)
+ : fBuiltin(builtin) {}
+
+ explicit SymbolTable(std::shared_ptr<SymbolTable> parent, bool builtin)
+ : fParent(parent)
+ , fBuiltin(builtin) {}
+
+ /** Replaces the passed-in SymbolTable with a newly-created child symbol table. */
+ static void Push(std::shared_ptr<SymbolTable>* table) {
+ Push(table, (*table)->isBuiltin());
+ }
+ static void Push(std::shared_ptr<SymbolTable>* table, bool isBuiltin) {
+ *table = std::make_shared<SymbolTable>(*table, isBuiltin);
+ }
+
+ /**
+ * Replaces the passed-in SymbolTable with its parent. If the child symbol table is otherwise
+ * unreferenced, it will be deleted.
+ */
+ static void Pop(std::shared_ptr<SymbolTable>* table) {
+ *table = (*table)->fParent;
+ }
+
+ /**
+ * If the input is a built-in symbol table, returns a new empty symbol table as a child of the
+ * input table. If the input is not a built-in symbol table, returns it as-is. Built-in symbol
+ * tables must not be mutated after creation, so they must be wrapped if mutation is necessary.
+ */
+ static std::shared_ptr<SymbolTable> WrapIfBuiltin(std::shared_ptr<SymbolTable> symbolTable) {
+ if (!symbolTable) {
+ return nullptr;
+ }
+ if (!symbolTable->isBuiltin()) {
+ return symbolTable;
+ }
+ return std::make_shared<SymbolTable>(std::move(symbolTable), /*builtin=*/false);
+ }
+
+ /**
+ * Looks up the requested symbol and returns a const pointer.
+ */
+ const Symbol* find(std::string_view name) const {
+ return this->lookup(MakeSymbolKey(name));
+ }
+
+ /**
+ * Looks up the requested symbol, only searching the built-in symbol tables. Always const.
+ */
+ const Symbol* findBuiltinSymbol(std::string_view name) const;
+
+ /**
+ * Looks up the requested symbol and returns a mutable pointer. Use caution--mutating a symbol
+ * will have program-wide impact, and built-in symbol tables must never be mutated.
+ */
+ Symbol* findMutable(std::string_view name) const {
+ return this->lookup(MakeSymbolKey(name));
+ }
+
+ /**
+ * Assigns a new name to the passed-in symbol. The old name will continue to exist in the symbol
+ * table and point to the symbol.
+ */
+ void renameSymbol(Symbol* symbol, std::string_view newName);
+
+ /**
+ * Returns true if the name refers to a type (user or built-in) in the current symbol table.
+ */
+ bool isType(std::string_view name) const;
+
+ /**
+ * Returns true if the name refers to a builtin type.
+ */
+ bool isBuiltinType(std::string_view name) const;
+
+ /**
+ * Adds a symbol to this symbol table, without conferring ownership. The caller is responsible
+ * for keeping the Symbol alive throughout the lifetime of the program/module.
+ */
+ void addWithoutOwnership(Symbol* symbol);
+
+ /**
+ * Adds a symbol to this symbol table, conferring ownership.
+ */
+ template <typename T>
+ T* add(std::unique_ptr<T> symbol) {
+ T* ptr = symbol.get();
+ this->addWithoutOwnership(this->takeOwnershipOfSymbol(std::move(symbol)));
+ return ptr;
+ }
+
+ /**
+ * Forces a symbol into this symbol table, without conferring ownership. Replaces any existing
+ * symbol with the same name, if one exists.
+ */
+ void injectWithoutOwnership(Symbol* symbol);
+
+ /**
+ * Forces a symbol into this symbol table, conferring ownership. Replaces any existing symbol
+ * with the same name, if one exists.
+ */
+ template <typename T>
+ T* inject(std::unique_ptr<T> symbol) {
+ T* ptr = symbol.get();
+ this->injectWithoutOwnership(this->takeOwnershipOfSymbol(std::move(symbol)));
+ return ptr;
+ }
+
+ /**
+ * Confers ownership of a symbol without adding its name to the lookup table.
+ */
+ template <typename T>
+ T* takeOwnershipOfSymbol(std::unique_ptr<T> symbol) {
+ T* ptr = symbol.get();
+ fOwnedSymbols.push_back(std::move(symbol));
+ return ptr;
+ }
+
+ /**
+ * Given type = `float` and arraySize = 5, creates the array type `float[5]` in the symbol
+ * table. The created array type is returned. If zero is passed, the base type is returned
+ * unchanged.
+ */
+ const Type* addArrayDimension(const Type* type, int arraySize);
+
+ // Call fn for every symbol in the table. You may not mutate anything.
+ template <typename Fn>
+ void foreach(Fn&& fn) const {
+ fSymbols.foreach(
+ [&fn](const SymbolKey& key, const Symbol* symbol) { fn(key.fName, symbol); });
+ }
+
+ size_t count() {
+ return fSymbols.count();
+ }
+
+ /** Returns true if this is a built-in SymbolTable. */
+ bool isBuiltin() const {
+ return fBuiltin;
+ }
+
+ const std::string* takeOwnershipOfString(std::string n);
+
+ /**
+ * Indicates that this symbol table's parent is in a different module than this one.
+ */
+ void markModuleBoundary() {
+ fAtModuleBoundary = true;
+ }
+
+ std::shared_ptr<SymbolTable> fParent;
+
+ std::vector<std::unique_ptr<const Symbol>> fOwnedSymbols;
+
+private:
+ struct SymbolKey {
+ std::string_view fName;
+ uint32_t fHash;
+
+ bool operator==(const SymbolKey& that) const { return fName == that.fName; }
+ bool operator!=(const SymbolKey& that) const { return fName != that.fName; }
+ struct Hash {
+ uint32_t operator()(const SymbolKey& key) const { return key.fHash; }
+ };
+ };
+
+ static SymbolKey MakeSymbolKey(std::string_view name) {
+ return SymbolKey{name, SkOpts::hash_fn(name.data(), name.size(), 0)};
+ }
+
+ Symbol* lookup(const SymbolKey& key) const;
+
+ bool fBuiltin = false;
+ bool fAtModuleBoundary = false;
+ std::forward_list<std::string> fOwnedStrings;
+ SkTHashMap<SymbolKey, Symbol*, SymbolKey::Hash> fSymbols;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLTernaryExpression.cpp b/gfx/skia/skia/src/sksl/ir/SkSLTernaryExpression.cpp
new file mode 100644
index 0000000000..2447bc043f
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLTernaryExpression.cpp
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLTernaryExpression.h"
+
+#include "include/sksl/SkSLErrorReporter.h"
+#include "include/sksl/SkSLOperator.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLConstantFolder.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/ir/SkSLBinaryExpression.h"
+#include "src/sksl/ir/SkSLLiteral.h"
+#include "src/sksl/ir/SkSLPrefixExpression.h"
+
+namespace SkSL {
+
+std::unique_ptr<Expression> TernaryExpression::Convert(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> test,
+ std::unique_ptr<Expression> ifTrue,
+ std::unique_ptr<Expression> ifFalse) {
+ test = context.fTypes.fBool->coerceExpression(std::move(test), context);
+ if (!test || !ifTrue || !ifFalse) {
+ return nullptr;
+ }
+ if (ifTrue->type().componentType().isOpaque()) {
+ context.fErrors->error(pos, "ternary expression of opaque type '" +
+ ifTrue->type().displayName() + "' not allowed");
+ return nullptr;
+ }
+ const Type* trueType;
+ const Type* falseType;
+ const Type* resultType;
+ Operator equalityOp(Operator::Kind::EQEQ);
+ if (!equalityOp.determineBinaryType(context, ifTrue->type(), ifFalse->type(),
+ &trueType, &falseType, &resultType) ||
+ !trueType->matches(*falseType)) {
+ context.fErrors->error(ifTrue->fPosition.rangeThrough(ifFalse->fPosition),
+ "ternary operator result mismatch: '" + ifTrue->type().displayName() + "', '" +
+ ifFalse->type().displayName() + "'");
+ return nullptr;
+ }
+ if (context.fConfig->strictES2Mode() && trueType->isOrContainsArray()) {
+ context.fErrors->error(pos, "ternary operator result may not be an array (or struct "
+ "containing an array)");
+ return nullptr;
+ }
+ ifTrue = trueType->coerceExpression(std::move(ifTrue), context);
+ if (!ifTrue) {
+ return nullptr;
+ }
+ ifFalse = falseType->coerceExpression(std::move(ifFalse), context);
+ if (!ifFalse) {
+ return nullptr;
+ }
+ return TernaryExpression::Make(context, pos, std::move(test), std::move(ifTrue),
+ std::move(ifFalse));
+}
+
+std::unique_ptr<Expression> TernaryExpression::Make(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> test,
+ std::unique_ptr<Expression> ifTrue,
+ std::unique_ptr<Expression> ifFalse) {
+ SkASSERT(ifTrue->type().matches(ifFalse->type()));
+ SkASSERT(!ifTrue->type().componentType().isOpaque());
+ SkASSERT(!context.fConfig->strictES2Mode() || !ifTrue->type().isOrContainsArray());
+
+ const Expression* testExpr = ConstantFolder::GetConstantValueForVariable(*test);
+ if (testExpr->isBoolLiteral()) {
+ // static boolean test, just return one of the branches
+ if (testExpr->as<Literal>().boolValue()) {
+ ifTrue->fPosition = pos;
+ return ifTrue;
+ } else {
+ ifFalse->fPosition = pos;
+ return ifFalse;
+ }
+ }
+
+ if (context.fConfig->fSettings.fOptimize) {
+ const Expression* ifTrueExpr = ConstantFolder::GetConstantValueForVariable(*ifTrue);
+ const Expression* ifFalseExpr = ConstantFolder::GetConstantValueForVariable(*ifFalse);
+
+ // A ternary with matching true- and false-cases does not need to branch.
+ if (Analysis::IsSameExpressionTree(*ifTrueExpr, *ifFalseExpr)) {
+ // If `test` has no side-effects, we can eliminate it too, and just return `ifTrue`.
+ if (!Analysis::HasSideEffects(*test)) {
+ ifTrue->fPosition = pos;
+ return ifTrue;
+ }
+ // Return a comma-expression containing `(test, ifTrue)`.
+ return BinaryExpression::Make(context, pos, std::move(test),
+ Operator::Kind::COMMA, std::move(ifTrue));
+ }
+
+ // A ternary of the form `test ? expr : false` can be simplified to `test && expr`.
+ if (ifFalseExpr->isBoolLiteral() && !ifFalseExpr->as<Literal>().boolValue()) {
+ return BinaryExpression::Make(context, pos, std::move(test),
+ Operator::Kind::LOGICALAND, std::move(ifTrue));
+ }
+
+ // A ternary of the form `test ? true : expr` can be simplified to `test || expr`.
+ if (ifTrueExpr->isBoolLiteral() && ifTrueExpr->as<Literal>().boolValue()) {
+ return BinaryExpression::Make(context, pos, std::move(test),
+ Operator::Kind::LOGICALOR, std::move(ifFalse));
+ }
+
+ // A ternary of the form `test ? false : true` can be simplified to `!test`.
+ if (ifTrueExpr->isBoolLiteral() && !ifTrueExpr->as<Literal>().boolValue() &&
+ ifFalseExpr->isBoolLiteral() && ifFalseExpr->as<Literal>().boolValue()) {
+ return PrefixExpression::Make(context, pos, Operator::Kind::LOGICALNOT,
+ std::move(test));
+ }
+ }
+
+ return std::make_unique<TernaryExpression>(pos, std::move(test), std::move(ifTrue),
+ std::move(ifFalse));
+}
+
+std::string TernaryExpression::description(OperatorPrecedence parentPrecedence) const {
+ bool needsParens = (OperatorPrecedence::kTernary >= parentPrecedence);
+ return std::string(needsParens ? "(" : "") +
+ this->test()->description(OperatorPrecedence::kTernary) + " ? " +
+ this->ifTrue()->description(OperatorPrecedence::kTernary) + " : " +
+ this->ifFalse()->description(OperatorPrecedence::kTernary) +
+ std::string(needsParens ? ")" : "");
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLTernaryExpression.h b/gfx/skia/skia/src/sksl/ir/SkSLTernaryExpression.h
new file mode 100644
index 0000000000..41f864e2c7
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLTernaryExpression.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_TERNARYEXPRESSION
+#define SKSL_TERNARYEXPRESSION
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLType.h"
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace SkSL {
+
+class Context;
+enum class OperatorPrecedence : uint8_t;
+
+/**
+ * A ternary expression (test ? ifTrue : ifFalse).
+ */
+class TernaryExpression final : public Expression {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kTernary;
+
+ TernaryExpression(Position pos, std::unique_ptr<Expression> test,
+ std::unique_ptr<Expression> ifTrue, std::unique_ptr<Expression> ifFalse)
+ : INHERITED(pos, kIRNodeKind, &ifTrue->type())
+ , fTest(std::move(test))
+ , fIfTrue(std::move(ifTrue))
+ , fIfFalse(std::move(ifFalse)) {
+ SkASSERT(this->ifTrue()->type().matches(this->ifFalse()->type()));
+ }
+
+ // Creates a potentially-simplified form of the ternary. Typechecks and coerces input
+ // expressions; reports errors via ErrorReporter.
+ static std::unique_ptr<Expression> Convert(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> test,
+ std::unique_ptr<Expression> ifTrue,
+ std::unique_ptr<Expression> ifFalse);
+
+ // Creates a potentially-simplified form of the ternary; reports errors via ASSERT.
+ static std::unique_ptr<Expression> Make(const Context& context,
+ Position pos,
+ std::unique_ptr<Expression> test,
+ std::unique_ptr<Expression> ifTrue,
+ std::unique_ptr<Expression> ifFalse);
+
+ std::unique_ptr<Expression>& test() {
+ return fTest;
+ }
+
+ const std::unique_ptr<Expression>& test() const {
+ return fTest;
+ }
+
+ std::unique_ptr<Expression>& ifTrue() {
+ return fIfTrue;
+ }
+
+ const std::unique_ptr<Expression>& ifTrue() const {
+ return fIfTrue;
+ }
+
+ std::unique_ptr<Expression>& ifFalse() {
+ return fIfFalse;
+ }
+
+ const std::unique_ptr<Expression>& ifFalse() const {
+ return fIfFalse;
+ }
+
+ std::unique_ptr<Expression> clone(Position pos) const override {
+ return std::make_unique<TernaryExpression>(pos, this->test()->clone(),
+ this->ifTrue()->clone(),
+ this->ifFalse()->clone());
+ }
+
+ std::string description(OperatorPrecedence parentPrecedence) const override;
+
+private:
+ std::unique_ptr<Expression> fTest;
+ std::unique_ptr<Expression> fIfTrue;
+ std::unique_ptr<Expression> fIfFalse;
+
+ using INHERITED = Expression;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLType.cpp b/gfx/skia/skia/src/sksl/ir/SkSLType.cpp
new file mode 100644
index 0000000000..265d49dcc8
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLType.cpp
@@ -0,0 +1,1208 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLType.h"
+
+#include "include/private/SkSLLayout.h"
+#include "include/private/SkSLString.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "src/base/SkMathPriv.h"
+#include "src/base/SkSafeMath.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLConstantFolder.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/ir/SkSLConstructorArrayCast.h"
+#include "src/sksl/ir/SkSLConstructorCompoundCast.h"
+#include "src/sksl/ir/SkSLConstructorScalarCast.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLSymbolTable.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <limits>
+#include <optional>
+#include <string_view>
+#include <utility>
+
+namespace SkSL {
+
+static constexpr int kMaxStructDepth = 8;
+
+class AliasType final : public Type {
+public:
+ AliasType(std::string_view name, const Type& targetType)
+ : INHERITED(name, targetType.abbreviatedName(), targetType.typeKind())
+ , fTargetType(targetType) {}
+
+ const Type& resolve() const override {
+ return fTargetType;
+ }
+
+ const Type& componentType() const override {
+ return fTargetType.componentType();
+ }
+
+ NumberKind numberKind() const override {
+ return fTargetType.numberKind();
+ }
+
+ int priority() const override {
+ return fTargetType.priority();
+ }
+
+ int columns() const override {
+ return fTargetType.columns();
+ }
+
+ int rows() const override {
+ return fTargetType.rows();
+ }
+
+ int bitWidth() const override {
+ return fTargetType.bitWidth();
+ }
+
+ bool isAllowedInES2() const override {
+ return fTargetType.isAllowedInES2();
+ }
+
+ size_t slotCount() const override {
+ return fTargetType.slotCount();
+ }
+
+ SpvDim_ dimensions() const override {
+ return fTargetType.dimensions();
+ }
+
+ bool isDepth() const override {
+ return fTargetType.isDepth();
+ }
+
+ bool isArrayedTexture() const override {
+ return fTargetType.isArrayedTexture();
+ }
+
+ bool isScalar() const override {
+ return fTargetType.isScalar();
+ }
+
+ bool isLiteral() const override {
+ return fTargetType.isLiteral();
+ }
+
+ bool isVector() const override {
+ return fTargetType.isVector();
+ }
+
+ bool isMatrix() const override {
+ return fTargetType.isMatrix();
+ }
+
+ bool isArray() const override {
+ return fTargetType.isArray();
+ }
+
+ bool isUnsizedArray() const override {
+ return fTargetType.isUnsizedArray();
+ }
+
+ bool isStruct() const override {
+ return fTargetType.isStruct();
+ }
+
+ bool isInterfaceBlock() const override {
+ return fTargetType.isInterfaceBlock();
+ }
+
+ bool isMultisampled() const override {
+ return fTargetType.isMultisampled();
+ }
+
+ TextureAccess textureAccess() const override {
+ return fTargetType.textureAccess();
+ }
+
+ SkSpan<const Type* const> coercibleTypes() const override {
+ return fTargetType.coercibleTypes();
+ }
+
+private:
+ using INHERITED = Type;
+
+ const Type& fTargetType;
+};
+
+class ArrayType final : public Type {
+public:
+ inline static constexpr TypeKind kTypeKind = TypeKind::kArray;
+
+ ArrayType(std::string_view name, const char* abbrev, const Type& componentType, int count)
+ : INHERITED(name, abbrev, kTypeKind)
+ , fComponentType(componentType)
+ , fCount(count) {
+ SkASSERT(count > 0 || count == kUnsizedArray);
+ // Disallow multi-dimensional arrays.
+ SkASSERT(!componentType.is<ArrayType>());
+ }
+
+ bool isArray() const override {
+ return true;
+ }
+
+ bool isUnsizedArray() const override {
+ return fCount == kUnsizedArray;
+ }
+
+ const Type& componentType() const override {
+ return fComponentType;
+ }
+
+ int columns() const override {
+ return fCount;
+ }
+
+ int bitWidth() const override {
+ return this->componentType().bitWidth();
+ }
+
+ bool isAllowedInES2() const override {
+ return fComponentType.isAllowedInES2();
+ }
+
+ size_t slotCount() const override {
+ SkASSERT(fCount != kUnsizedArray);
+ SkASSERT(fCount > 0);
+ return fCount * fComponentType.slotCount();
+ }
+
+private:
+ using INHERITED = Type;
+
+ const Type& fComponentType;
+ int fCount;
+};
+
+class GenericType final : public Type {
+public:
+ inline static constexpr TypeKind kTypeKind = TypeKind::kGeneric;
+
+ GenericType(const char* name, SkSpan<const Type* const> coercibleTypes)
+ : INHERITED(name, "G", kTypeKind) {
+ fNumTypes = coercibleTypes.size();
+ SkASSERT(fNumTypes <= std::size(fCoercibleTypes));
+ std::copy(coercibleTypes.begin(), coercibleTypes.end(), fCoercibleTypes);
+ }
+
+ SkSpan<const Type* const> coercibleTypes() const override {
+ return SkSpan(fCoercibleTypes, fNumTypes);
+ }
+
+private:
+ using INHERITED = Type;
+
+ const Type* fCoercibleTypes[9];
+ size_t fNumTypes;
+};
+
+class LiteralType : public Type {
+public:
+ inline static constexpr TypeKind kTypeKind = TypeKind::kLiteral;
+
+ LiteralType(const char* name, const Type& scalarType, int8_t priority)
+ : INHERITED(name, "L", kTypeKind)
+ , fScalarType(scalarType)
+ , fPriority(priority) {}
+
+ const Type& scalarTypeForLiteral() const override {
+ return fScalarType;
+ }
+
+ int priority() const override {
+ return fPriority;
+ }
+
+ int columns() const override {
+ return 1;
+ }
+
+ int rows() const override {
+ return 1;
+ }
+
+ NumberKind numberKind() const override {
+ return fScalarType.numberKind();
+ }
+
+ int bitWidth() const override {
+ return fScalarType.bitWidth();
+ }
+
+ double minimumValue() const override {
+ return fScalarType.minimumValue();
+ }
+
+ double maximumValue() const override {
+ return fScalarType.maximumValue();
+ }
+
+ bool isScalar() const override {
+ return true;
+ }
+
+ bool isLiteral() const override {
+ return true;
+ }
+
+ size_t slotCount() const override {
+ return 1;
+ }
+
+private:
+ using INHERITED = Type;
+
+ const Type& fScalarType;
+ int8_t fPriority;
+};
+
+
+class ScalarType final : public Type {
+public:
+ inline static constexpr TypeKind kTypeKind = TypeKind::kScalar;
+
+ ScalarType(std::string_view name, const char* abbrev, NumberKind numberKind, int8_t priority,
+ int8_t bitWidth)
+ : INHERITED(name, abbrev, kTypeKind)
+ , fNumberKind(numberKind)
+ , fPriority(priority)
+ , fBitWidth(bitWidth) {}
+
+ NumberKind numberKind() const override {
+ return fNumberKind;
+ }
+
+ int priority() const override {
+ return fPriority;
+ }
+
+ int bitWidth() const override {
+ return fBitWidth;
+ }
+
+ int columns() const override {
+ return 1;
+ }
+
+ int rows() const override {
+ return 1;
+ }
+
+ bool isScalar() const override {
+ return true;
+ }
+
+ bool isAllowedInES2() const override {
+ return fNumberKind != NumberKind::kUnsigned;
+ }
+
+ size_t slotCount() const override {
+ return 1;
+ }
+
+ using int_limits = std::numeric_limits<int32_t>;
+ using short_limits = std::numeric_limits<int16_t>;
+ using uint_limits = std::numeric_limits<uint32_t>;
+ using ushort_limits = std::numeric_limits<uint16_t>;
+ using float_limits = std::numeric_limits<float>;
+
+ /** Returns the maximum value that can fit in the type. */
+ double minimumValue() const override {
+ switch (this->numberKind()) {
+ case NumberKind::kSigned:
+ return this->highPrecision() ? int_limits::lowest()
+ : short_limits::lowest();
+
+ case NumberKind::kUnsigned:
+ return 0;
+
+ case NumberKind::kFloat:
+ default:
+ return float_limits::lowest();
+ }
+ }
+
+ /** Returns the maximum value that can fit in the type. */
+ double maximumValue() const override {
+ switch (this->numberKind()) {
+ case NumberKind::kSigned:
+ return this->highPrecision() ? int_limits::max()
+ : short_limits::max();
+
+ case NumberKind::kUnsigned:
+ return this->highPrecision() ? uint_limits::max()
+ : ushort_limits::max();
+
+ case NumberKind::kFloat:
+ default:
+ return float_limits::max();
+ }
+ }
+
+private:
+ using INHERITED = Type;
+
+ NumberKind fNumberKind;
+ int8_t fPriority;
+ int8_t fBitWidth;
+};
+
+class AtomicType final : public Type {
+public:
+ inline static constexpr TypeKind kTypeKind = TypeKind::kAtomic;
+
+ AtomicType(std::string_view name, const char* abbrev) : INHERITED(name, abbrev, kTypeKind) {}
+
+ bool isAllowedInES2() const override { return false; }
+
+private:
+ using INHERITED = Type;
+};
+
+class MatrixType final : public Type {
+public:
+ inline static constexpr TypeKind kTypeKind = TypeKind::kMatrix;
+
+ MatrixType(std::string_view name, const char* abbrev, const Type& componentType,
+ int8_t columns, int8_t rows)
+ : INHERITED(name, abbrev, kTypeKind)
+ , fComponentType(componentType.as<ScalarType>())
+ , fColumns(columns)
+ , fRows(rows) {
+ SkASSERT(columns >= 2 && columns <= 4);
+ SkASSERT(rows >= 2 && rows <= 4);
+ }
+
+ const ScalarType& componentType() const override {
+ return fComponentType;
+ }
+
+ int columns() const override {
+ return fColumns;
+ }
+
+ int rows() const override {
+ return fRows;
+ }
+
+ int bitWidth() const override {
+ return this->componentType().bitWidth();
+ }
+
+ bool isMatrix() const override {
+ return true;
+ }
+
+ bool isAllowedInES2() const override {
+ return fColumns == fRows;
+ }
+
+ size_t slotCount() const override {
+ return fColumns * fRows;
+ }
+
+private:
+ using INHERITED = Type;
+
+ const ScalarType& fComponentType;
+ int8_t fColumns;
+ int8_t fRows;
+};
+
+class TextureType final : public Type {
+public:
+ inline static constexpr TypeKind kTypeKind = TypeKind::kTexture;
+
+ TextureType(const char* name, SpvDim_ dimensions, bool isDepth, bool isArrayed,
+ bool isMultisampled, TextureAccess textureAccess)
+ : INHERITED(name, "T", kTypeKind)
+ , fDimensions(dimensions)
+ , fIsDepth(isDepth)
+ , fIsArrayed(isArrayed)
+ , fIsMultisampled(isMultisampled)
+ , fTextureAccess(textureAccess) {}
+
+ SpvDim_ dimensions() const override {
+ return fDimensions;
+ }
+
+ bool isDepth() const override {
+ return fIsDepth;
+ }
+
+ bool isArrayedTexture() const override {
+ return fIsArrayed;
+ }
+
+ bool isMultisampled() const override {
+ return fIsMultisampled;
+ }
+
+ TextureAccess textureAccess() const override {
+ return fTextureAccess;
+ }
+
+private:
+ using INHERITED = Type;
+
+ SpvDim_ fDimensions;
+ bool fIsDepth;
+ bool fIsArrayed;
+ bool fIsMultisampled;
+ TextureAccess fTextureAccess;
+};
+
+class SamplerType final : public Type {
+public:
+ inline static constexpr TypeKind kTypeKind = TypeKind::kSampler;
+
+ SamplerType(const char* name, const Type& textureType)
+ : INHERITED(name, "Z", kTypeKind)
+ , fTextureType(textureType.as<TextureType>()) {}
+
+ const TextureType& textureType() const override {
+ return fTextureType;
+ }
+
+ SpvDim_ dimensions() const override {
+ return fTextureType.dimensions();
+ }
+
+ bool isDepth() const override {
+ return fTextureType.isDepth();
+ }
+
+ bool isArrayedTexture() const override {
+ return fTextureType.isArrayedTexture();
+ }
+
+ bool isMultisampled() const override {
+ return fTextureType.isMultisampled();
+ }
+
+ TextureAccess textureAccess() const override {
+ return fTextureType.textureAccess();
+ }
+
+private:
+ using INHERITED = Type;
+
+ const TextureType& fTextureType;
+};
+
+class StructType final : public Type {
+public:
+ inline static constexpr TypeKind kTypeKind = TypeKind::kStruct;
+
+ StructType(Position pos, std::string_view name, std::vector<Field> fields, bool interfaceBlock)
+ : INHERITED(std::move(name), "S", kTypeKind, pos)
+ , fFields(std::move(fields))
+ , fInterfaceBlock(interfaceBlock) {}
+
+ const std::vector<Field>& fields() const override {
+ return fFields;
+ }
+
+ bool isStruct() const override {
+ return true;
+ }
+
+ bool isInterfaceBlock() const override {
+ return fInterfaceBlock;
+ }
+
+ bool isAllowedInES2() const override {
+ return std::all_of(fFields.begin(), fFields.end(), [](const Field& f) {
+ return f.fType->isAllowedInES2();
+ });
+ }
+
+ size_t slotCount() const override {
+ size_t slots = 0;
+ for (const Field& field : fFields) {
+ slots += field.fType->slotCount();
+ }
+ return slots;
+ }
+
+private:
+ using INHERITED = Type;
+
+ std::vector<Field> fFields;
+ bool fInterfaceBlock;
+};
+
+class VectorType final : public Type {
+public:
+ inline static constexpr TypeKind kTypeKind = TypeKind::kVector;
+
+ VectorType(std::string_view name, const char* abbrev, const Type& componentType,
+ int8_t columns)
+ : INHERITED(name, abbrev, kTypeKind)
+ , fComponentType(componentType.as<ScalarType>())
+ , fColumns(columns) {
+ SkASSERT(columns >= 2 && columns <= 4);
+ }
+
+ const ScalarType& componentType() const override {
+ return fComponentType;
+ }
+
+ int columns() const override {
+ return fColumns;
+ }
+
+ int rows() const override {
+ return 1;
+ }
+
+ int bitWidth() const override {
+ return this->componentType().bitWidth();
+ }
+
+ bool isVector() const override {
+ return true;
+ }
+
+ bool isAllowedInES2() const override {
+ return fComponentType.isAllowedInES2();
+ }
+
+ size_t slotCount() const override {
+ return fColumns;
+ }
+
+private:
+ using INHERITED = Type;
+
+ const ScalarType& fComponentType;
+ int8_t fColumns;
+};
+
+std::string Type::getArrayName(int arraySize) const {
+ std::string_view name = this->name();
+ if (arraySize == kUnsizedArray) {
+ return String::printf("%.*s[]", (int)name.size(), name.data());
+ }
+ return String::printf("%.*s[%d]", (int)name.size(), name.data(), arraySize);
+}
+
+std::unique_ptr<Type> Type::MakeAliasType(std::string_view name, const Type& targetType) {
+ return std::make_unique<AliasType>(std::move(name), targetType);
+}
+
+std::unique_ptr<Type> Type::MakeArrayType(std::string_view name, const Type& componentType,
+ int columns) {
+ return std::make_unique<ArrayType>(std::move(name), componentType.abbreviatedName(),
+ componentType, columns);
+}
+
+std::unique_ptr<Type> Type::MakeGenericType(const char* name, SkSpan<const Type* const> types) {
+ return std::make_unique<GenericType>(name, types);
+}
+
+std::unique_ptr<Type> Type::MakeLiteralType(const char* name, const Type& scalarType,
+ int8_t priority) {
+ return std::make_unique<LiteralType>(name, scalarType, priority);
+}
+
+std::unique_ptr<Type> Type::MakeMatrixType(std::string_view name, const char* abbrev,
+ const Type& componentType, int columns, int8_t rows) {
+ return std::make_unique<MatrixType>(name, abbrev, componentType, columns, rows);
+}
+
+std::unique_ptr<Type> Type::MakeSamplerType(const char* name, const Type& textureType) {
+ return std::make_unique<SamplerType>(name, textureType);
+}
+
+std::unique_ptr<Type> Type::MakeSpecialType(const char* name, const char* abbrev,
+ Type::TypeKind typeKind) {
+ return std::unique_ptr<Type>(new Type(name, abbrev, typeKind));
+}
+
+std::unique_ptr<Type> Type::MakeScalarType(std::string_view name, const char* abbrev,
+ Type::NumberKind numberKind, int8_t priority,
+ int8_t bitWidth) {
+ return std::make_unique<ScalarType>(name, abbrev, numberKind, priority, bitWidth);
+}
+
+std::unique_ptr<Type> Type::MakeAtomicType(std::string_view name, const char* abbrev) {
+ return std::make_unique<AtomicType>(name, abbrev);
+}
+
+static bool is_too_deeply_nested(const Type* t, int limit) {
+ if (limit <= 0) {
+ return true;
+ }
+
+ if (t->isStruct()) {
+ for (const Type::Field& f : t->fields()) {
+ if (is_too_deeply_nested(f.fType, limit - 1)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+std::unique_ptr<Type> Type::MakeStructType(const Context& context,
+ Position pos,
+ std::string_view name,
+ std::vector<Field> fields,
+ bool interfaceBlock) {
+ for (const Field& field : fields) {
+ if (field.fModifiers.fFlags != Modifiers::kNo_Flag) {
+ std::string desc = field.fModifiers.description();
+ desc.pop_back(); // remove trailing space
+ context.fErrors->error(field.fPosition,
+ "modifier '" + desc + "' is not permitted on a struct field");
+ }
+ if (field.fModifiers.fLayout.fFlags & Layout::kBinding_Flag) {
+ context.fErrors->error(field.fPosition,
+ "layout qualifier 'binding' is not permitted on a struct field");
+ }
+ if (field.fModifiers.fLayout.fFlags & Layout::kSet_Flag) {
+ context.fErrors->error(field.fPosition,
+ "layout qualifier 'set' is not permitted on a struct field");
+ }
+
+ if (field.fType->isVoid()) {
+ context.fErrors->error(field.fPosition, "type 'void' is not permitted in a struct");
+ }
+ if (field.fType->isOpaque() && !field.fType->isAtomic()) {
+ context.fErrors->error(field.fPosition, "opaque type '" + field.fType->displayName() +
+ "' is not permitted in a struct");
+ }
+ }
+ for (const Field& field : fields) {
+ if (is_too_deeply_nested(field.fType, kMaxStructDepth)) {
+ context.fErrors->error(pos, "struct '" + std::string(name) + "' is too deeply nested");
+ break;
+ }
+ }
+ size_t slots = 0;
+ for (const Field& field : fields) {
+ if (field.fType->isUnsizedArray()) {
+ continue;
+ }
+ slots = SkSafeMath::Add(slots, field.fType->slotCount());
+ if (slots >= kVariableSlotLimit) {
+ context.fErrors->error(pos, "struct is too large");
+ break;
+ }
+ }
+ return std::make_unique<StructType>(pos, name, std::move(fields), interfaceBlock);
+}
+
+std::unique_ptr<Type> Type::MakeTextureType(const char* name, SpvDim_ dimensions, bool isDepth,
+ bool isArrayedTexture, bool isMultisampled,
+ TextureAccess textureAccess) {
+ return std::make_unique<TextureType>(name, dimensions, isDepth, isArrayedTexture,
+ isMultisampled, textureAccess);
+}
+
+std::unique_ptr<Type> Type::MakeVectorType(std::string_view name, const char* abbrev,
+ const Type& componentType, int columns) {
+ return std::make_unique<VectorType>(name, abbrev, componentType, columns);
+}
+
+CoercionCost Type::coercionCost(const Type& other) const {
+ if (this->matches(other)) {
+ return CoercionCost::Free();
+ }
+ if (this->typeKind() == other.typeKind() &&
+ (this->isVector() || this->isMatrix() || this->isArray())) {
+ // Vectors/matrices/arrays of the same size can be coerced if their component type can be.
+ if (this->isMatrix() && (this->rows() != other.rows())) {
+ return CoercionCost::Impossible();
+ }
+ if (this->columns() != other.columns()) {
+ return CoercionCost::Impossible();
+ }
+ return this->componentType().coercionCost(other.componentType());
+ }
+ if (this->isNumber() && other.isNumber()) {
+ if (this->isLiteral() && this->isInteger()) {
+ return CoercionCost::Free();
+ } else if (this->numberKind() != other.numberKind()) {
+ return CoercionCost::Impossible();
+ } else if (other.priority() >= this->priority()) {
+ return CoercionCost::Normal(other.priority() - this->priority());
+ } else {
+ return CoercionCost::Narrowing(this->priority() - other.priority());
+ }
+ }
+ if (fTypeKind == TypeKind::kGeneric) {
+ SkSpan<const Type* const> types = this->coercibleTypes();
+ for (size_t i = 0; i < types.size(); i++) {
+ if (types[i]->matches(other)) {
+ return CoercionCost::Normal((int) i + 1);
+ }
+ }
+ }
+ return CoercionCost::Impossible();
+}
+
+const Type* Type::applyQualifiers(const Context& context,
+ Modifiers* modifiers,
+ SymbolTable* symbols,
+ Position pos) const {
+ const Type* type;
+ type = this->applyPrecisionQualifiers(context, modifiers, symbols, pos);
+ type = type->applyAccessQualifiers(context, modifiers, symbols, pos);
+ return type;
+}
+
+const Type* Type::applyPrecisionQualifiers(const Context& context,
+ Modifiers* modifiers,
+ SymbolTable* symbols,
+ Position pos) const {
+ int precisionQualifiers = modifiers->fFlags & (Modifiers::kHighp_Flag |
+ Modifiers::kMediump_Flag |
+ Modifiers::kLowp_Flag);
+ if (!precisionQualifiers) {
+ // No precision qualifiers here. Return the type as-is.
+ return this;
+ }
+
+ if (!ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
+ // We want to discourage precision modifiers internally. Instead, use the type that
+ // corresponds to the precision you need. (e.g. half vs float, short vs int)
+ context.fErrors->error(pos, "precision qualifiers are not allowed");
+ return context.fTypes.fPoison.get();
+ }
+
+ if (SkPopCount(precisionQualifiers) > 1) {
+ context.fErrors->error(pos, "only one precision qualifier can be used");
+ return context.fTypes.fPoison.get();
+ }
+
+ // We're going to return a whole new type, so the modifier bits can be cleared out.
+ modifiers->fFlags &= ~(Modifiers::kHighp_Flag |
+ Modifiers::kMediump_Flag |
+ Modifiers::kLowp_Flag);
+
+ const Type& component = this->componentType();
+ if (component.highPrecision()) {
+ if (precisionQualifiers & Modifiers::kHighp_Flag) {
+ // Type is already high precision, and we are requesting high precision. Return as-is.
+ return this;
+ }
+
+ // SkSL doesn't support low precision, so `lowp` is interpreted as medium precision.
+ // Ascertain the mediump equivalent type for this type, if any.
+ const Type* mediumpType;
+ switch (component.numberKind()) {
+ case Type::NumberKind::kFloat:
+ mediumpType = context.fTypes.fHalf.get();
+ break;
+
+ case Type::NumberKind::kSigned:
+ mediumpType = context.fTypes.fShort.get();
+ break;
+
+ case Type::NumberKind::kUnsigned:
+ mediumpType = context.fTypes.fUShort.get();
+ break;
+
+ default:
+ mediumpType = context.fTypes.fPoison.get();
+ break;
+ }
+
+ if (mediumpType) {
+ // Convert the mediump component type into the final vector/matrix/array type as needed.
+ return this->isArray()
+ ? symbols->addArrayDimension(mediumpType, this->columns())
+ : &mediumpType->toCompound(context, this->columns(), this->rows());
+ }
+ }
+
+ context.fErrors->error(pos, "type '" + this->displayName() +
+ "' does not support precision qualifiers");
+ return context.fTypes.fPoison.get();
+}
+
+const Type* Type::applyAccessQualifiers(const Context& context,
+ Modifiers* modifiers,
+ SymbolTable* symbols,
+ Position pos) const {
+ int accessQualifiers = modifiers->fFlags & (Modifiers::kReadOnly_Flag |
+ Modifiers::kWriteOnly_Flag);
+ if (!accessQualifiers) {
+ // No access qualifiers here. Return the type as-is.
+ return this;
+ }
+
+ // We're going to return a whole new type, so the modifier bits can be cleared out.
+ modifiers->fFlags &= ~(Modifiers::kReadOnly_Flag |
+ Modifiers::kWriteOnly_Flag);
+
+ if (this->matches(*context.fTypes.fReadWriteTexture2D)) {
+ switch (accessQualifiers) {
+ case Modifiers::kReadOnly_Flag:
+ return context.fTypes.fReadOnlyTexture2D.get();
+
+ case Modifiers::kWriteOnly_Flag:
+ return context.fTypes.fWriteOnlyTexture2D.get();
+
+ default:
+ context.fErrors->error(pos, "'readonly' and 'writeonly' qualifiers "
+ "cannot be combined");
+ return this;
+ }
+ }
+
+ context.fErrors->error(pos, "type '" + this->displayName() + "' does not support qualifier '" +
+ Modifiers::DescribeFlags(accessQualifiers) + "'");
+ return this;
+}
+
+const Type& Type::toCompound(const Context& context, int columns, int rows) const {
+ SkASSERT(this->isScalar());
+ if (columns == 1 && rows == 1) {
+ return *this;
+ }
+ if (this->matches(*context.fTypes.fFloat) || this->matches(*context.fTypes.fFloatLiteral)) {
+ switch (rows) {
+ case 1:
+ switch (columns) {
+ case 1: return *context.fTypes.fFloat;
+ case 2: return *context.fTypes.fFloat2;
+ case 3: return *context.fTypes.fFloat3;
+ case 4: return *context.fTypes.fFloat4;
+ default: SK_ABORT("unsupported vector column count (%d)", columns);
+ }
+ case 2:
+ switch (columns) {
+ case 2: return *context.fTypes.fFloat2x2;
+ case 3: return *context.fTypes.fFloat3x2;
+ case 4: return *context.fTypes.fFloat4x2;
+ default: SK_ABORT("unsupported matrix column count (%d)", columns);
+ }
+ case 3:
+ switch (columns) {
+ case 2: return *context.fTypes.fFloat2x3;
+ case 3: return *context.fTypes.fFloat3x3;
+ case 4: return *context.fTypes.fFloat4x3;
+ default: SK_ABORT("unsupported matrix column count (%d)", columns);
+ }
+ case 4:
+ switch (columns) {
+ case 2: return *context.fTypes.fFloat2x4;
+ case 3: return *context.fTypes.fFloat3x4;
+ case 4: return *context.fTypes.fFloat4x4;
+ default: SK_ABORT("unsupported matrix column count (%d)", columns);
+ }
+ default: SK_ABORT("unsupported row count (%d)", rows);
+ }
+ } else if (this->matches(*context.fTypes.fHalf)) {
+ switch (rows) {
+ case 1:
+ switch (columns) {
+ case 1: return *context.fTypes.fHalf;
+ case 2: return *context.fTypes.fHalf2;
+ case 3: return *context.fTypes.fHalf3;
+ case 4: return *context.fTypes.fHalf4;
+ default: SK_ABORT("unsupported vector column count (%d)", columns);
+ }
+ case 2:
+ switch (columns) {
+ case 2: return *context.fTypes.fHalf2x2;
+ case 3: return *context.fTypes.fHalf3x2;
+ case 4: return *context.fTypes.fHalf4x2;
+ default: SK_ABORT("unsupported matrix column count (%d)", columns);
+ }
+ case 3:
+ switch (columns) {
+ case 2: return *context.fTypes.fHalf2x3;
+ case 3: return *context.fTypes.fHalf3x3;
+ case 4: return *context.fTypes.fHalf4x3;
+ default: SK_ABORT("unsupported matrix column count (%d)", columns);
+ }
+ case 4:
+ switch (columns) {
+ case 2: return *context.fTypes.fHalf2x4;
+ case 3: return *context.fTypes.fHalf3x4;
+ case 4: return *context.fTypes.fHalf4x4;
+ default: SK_ABORT("unsupported matrix column count (%d)", columns);
+ }
+ default: SK_ABORT("unsupported row count (%d)", rows);
+ }
+ } else if (this->matches(*context.fTypes.fInt) || this->matches(*context.fTypes.fIntLiteral)) {
+ switch (rows) {
+ case 1:
+ switch (columns) {
+ case 1: return *context.fTypes.fInt;
+ case 2: return *context.fTypes.fInt2;
+ case 3: return *context.fTypes.fInt3;
+ case 4: return *context.fTypes.fInt4;
+ default: SK_ABORT("unsupported vector column count (%d)", columns);
+ }
+ default: SK_ABORT("unsupported row count (%d)", rows);
+ }
+ } else if (this->matches(*context.fTypes.fShort)) {
+ switch (rows) {
+ case 1:
+ switch (columns) {
+ case 1: return *context.fTypes.fShort;
+ case 2: return *context.fTypes.fShort2;
+ case 3: return *context.fTypes.fShort3;
+ case 4: return *context.fTypes.fShort4;
+ default: SK_ABORT("unsupported vector column count (%d)", columns);
+ }
+ default: SK_ABORT("unsupported row count (%d)", rows);
+ }
+ } else if (this->matches(*context.fTypes.fUInt)) {
+ switch (rows) {
+ case 1:
+ switch (columns) {
+ case 1: return *context.fTypes.fUInt;
+ case 2: return *context.fTypes.fUInt2;
+ case 3: return *context.fTypes.fUInt3;
+ case 4: return *context.fTypes.fUInt4;
+ default: SK_ABORT("unsupported vector column count (%d)", columns);
+ }
+ default: SK_ABORT("unsupported row count (%d)", rows);
+ }
+ } else if (this->matches(*context.fTypes.fUShort)) {
+ switch (rows) {
+ case 1:
+ switch (columns) {
+ case 1: return *context.fTypes.fUShort;
+ case 2: return *context.fTypes.fUShort2;
+ case 3: return *context.fTypes.fUShort3;
+ case 4: return *context.fTypes.fUShort4;
+ default: SK_ABORT("unsupported vector column count (%d)", columns);
+ }
+ default: SK_ABORT("unsupported row count (%d)", rows);
+ }
+ } else if (this->matches(*context.fTypes.fBool)) {
+ switch (rows) {
+ case 1:
+ switch (columns) {
+ case 1: return *context.fTypes.fBool;
+ case 2: return *context.fTypes.fBool2;
+ case 3: return *context.fTypes.fBool3;
+ case 4: return *context.fTypes.fBool4;
+ default: SK_ABORT("unsupported vector column count (%d)", columns);
+ }
+ default: SK_ABORT("unsupported row count (%d)", rows);
+ }
+ }
+ SkDEBUGFAILF("unsupported toCompound type %s", this->description().c_str());
+ return *context.fTypes.fVoid;
+}
+
+const Type* Type::clone(SymbolTable* symbolTable) const {
+ // Many types are built-ins, and exist in every SymbolTable by default.
+ if (this->isInBuiltinTypes()) {
+ return this;
+ }
+ // Even if the type isn't a built-in, it might already exist in the SymbolTable.
+ const Symbol* clonedSymbol = symbolTable->find(this->name());
+ if (clonedSymbol != nullptr) {
+ const Type& clonedType = clonedSymbol->as<Type>();
+ SkASSERT(clonedType.typeKind() == this->typeKind());
+ return &clonedType;
+ }
+ // This type actually needs to be cloned into the destination SymbolTable.
+ switch (this->typeKind()) {
+ case TypeKind::kArray: {
+ return symbolTable->addArrayDimension(&this->componentType(), this->columns());
+ }
+ case TypeKind::kStruct: {
+ // We are cloning an existing struct, so there's no need to call MakeStructType and
+ // fully error-check it again.
+ const std::string* name = symbolTable->takeOwnershipOfString(std::string(this->name()));
+ return symbolTable->add(std::make_unique<StructType>(
+ this->fPosition, *name, this->fields(), this->isInterfaceBlock()));
+ }
+ default:
+ SkDEBUGFAILF("don't know how to clone type '%s'", this->description().c_str());
+ return nullptr;
+ }
+}
+
+std::unique_ptr<Expression> Type::coerceExpression(std::unique_ptr<Expression> expr,
+ const Context& context) const {
+ if (!expr || expr->isIncomplete(context)) {
+ return nullptr;
+ }
+ if (expr->type().matches(*this)) {
+ return expr;
+ }
+
+ const Position pos = expr->fPosition;
+ const ProgramSettings& settings = context.fConfig->fSettings;
+ if (!expr->coercionCost(*this).isPossible(settings.fAllowNarrowingConversions)) {
+ context.fErrors->error(pos, "expected '" + this->displayName() + "', but found '" +
+ expr->type().displayName() + "'");
+ return nullptr;
+ }
+
+ if (this->isScalar()) {
+ return ConstructorScalarCast::Make(context, pos, *this, std::move(expr));
+ }
+ if (this->isVector() || this->isMatrix()) {
+ return ConstructorCompoundCast::Make(context, pos, *this, std::move(expr));
+ }
+ if (this->isArray()) {
+ return ConstructorArrayCast::Make(context, pos, *this, std::move(expr));
+ }
+ context.fErrors->error(pos, "cannot construct '" + this->displayName() + "'");
+ return nullptr;
+}
+
+static bool is_or_contains_array(const Type* type, bool onlyMatchUnsizedArrays) {
+ if (type->isStruct()) {
+ for (const Type::Field& f : type->fields()) {
+ if (is_or_contains_array(f.fType, onlyMatchUnsizedArrays)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ if (type->isArray()) {
+ return onlyMatchUnsizedArrays
+ ? (type->isUnsizedArray() || is_or_contains_array(&type->componentType(), true))
+ : true;
+ }
+
+ return false;
+}
+
+bool Type::isOrContainsArray() const {
+ return is_or_contains_array(this, /*onlyMatchUnsizedArrays=*/false);
+}
+
+bool Type::isOrContainsUnsizedArray() const {
+ return is_or_contains_array(this, /*onlyMatchUnsizedArrays=*/true);
+}
+
+bool Type::isOrContainsAtomic() const {
+ if (this->isAtomic()) {
+ return true;
+ }
+
+ if (this->isArray() && this->componentType().isOrContainsAtomic()) {
+ return true;
+ }
+
+ if (this->isStruct()) {
+ for (const Field& f : this->fields()) {
+ if (f.fType->isOrContainsAtomic()) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool Type::isAllowedInES2(const Context& context) const {
+ return !context.fConfig->strictES2Mode() || this->isAllowedInES2();
+}
+
+bool Type::checkForOutOfRangeLiteral(const Context& context, const Expression& expr) const {
+ bool foundError = false;
+ const Type& baseType = this->componentType();
+ if (baseType.isNumber()) {
+ // Replace constant expressions with their corresponding values.
+ const Expression* valueExpr = ConstantFolder::GetConstantValueForVariable(expr);
+ if (valueExpr->supportsConstantValues()) {
+ // Iterate over every constant subexpression in the value.
+ int numSlots = valueExpr->type().slotCount();
+ for (int slot = 0; slot < numSlots; ++slot) {
+ std::optional<double> slotVal = valueExpr->getConstantValue(slot);
+ // Check for Literal values that are out of range for the base type.
+ if (slotVal.has_value() &&
+ baseType.checkForOutOfRangeLiteral(context, *slotVal, valueExpr->fPosition)) {
+ foundError = true;
+ }
+ }
+ }
+ }
+
+ // We don't need range checks for floats or booleans; any matched-type value is acceptable.
+ return foundError;
+}
+
+bool Type::checkForOutOfRangeLiteral(const Context& context, double value, Position pos) const {
+ SkASSERT(this->isScalar());
+ if (!this->isNumber()) {
+ return false;
+ }
+ if (value >= this->minimumValue() && value <= this->maximumValue()) {
+ return false;
+ }
+ // We found a value that can't fit in our type. Flag it as an error.
+ context.fErrors->error(pos, SkSL::String::printf("value is out of range for type '%s': %.0f",
+ this->displayName().c_str(),
+ value));
+ return true;
+}
+
+bool Type::checkIfUsableInArray(const Context& context, Position arrayPos) const {
+ if (this->isArray()) {
+ context.fErrors->error(arrayPos, "multi-dimensional arrays are not supported");
+ return false;
+ }
+ if (this->isVoid()) {
+ context.fErrors->error(arrayPos, "type 'void' may not be used in an array");
+ return false;
+ }
+ if (this->isOpaque() && !this->isAtomic()) {
+ context.fErrors->error(arrayPos, "opaque type '" + std::string(this->name()) +
+ "' may not be used in an array");
+ return false;
+ }
+ return true;
+}
+
+SKSL_INT Type::convertArraySize(const Context& context,
+ Position arrayPos,
+ std::unique_ptr<Expression> size) const {
+ size = context.fTypes.fInt->coerceExpression(std::move(size), context);
+ if (!size) {
+ return 0;
+ }
+ if (!this->checkIfUsableInArray(context, arrayPos)) {
+ return 0;
+ }
+ SKSL_INT count;
+ if (!ConstantFolder::GetConstantInt(*size, &count)) {
+ context.fErrors->error(size->fPosition, "array size must be an integer");
+ return 0;
+ }
+ if (count <= 0) {
+ context.fErrors->error(size->fPosition, "array size must be positive");
+ return 0;
+ }
+ if (SkSafeMath::Mul(this->slotCount(), count) > kVariableSlotLimit) {
+ context.fErrors->error(size->fPosition, "array size is too large");
+ return 0;
+ }
+ return static_cast<int>(count);
+}
+
+std::string Type::Field::description() const {
+ return fModifiers.description() + fType->displayName() + " " + std::string(fName) + ";";
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLType.h b/gfx/skia/skia/src/sksl/ir/SkSLType.h
new file mode 100644
index 0000000000..955381f8c9
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLType.h
@@ -0,0 +1,600 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_TYPE
+#define SKSL_TYPE
+
+#include "include/core/SkSpan.h"
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLModifiers.h"
+#include "include/private/SkSLSymbol.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/spirv.h"
+
+#include <cmath>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <tuple>
+#include <vector>
+
+namespace SkSL {
+
+class Context;
+class Expression;
+class SymbolTable;
+
+struct CoercionCost {
+ static CoercionCost Free() { return { 0, 0, false }; }
+ static CoercionCost Normal(int cost) { return { cost, 0, false }; }
+ static CoercionCost Narrowing(int cost) { return { 0, cost, false }; }
+ static CoercionCost Impossible() { return { 0, 0, true }; }
+
+ bool isPossible(bool allowNarrowing) const {
+ return !fImpossible && (fNarrowingCost == 0 || allowNarrowing);
+ }
+
+ // Addition of two costs. Saturates at Impossible().
+ CoercionCost operator+(CoercionCost rhs) const {
+ if (fImpossible || rhs.fImpossible) {
+ return Impossible();
+ }
+ return { fNormalCost + rhs.fNormalCost, fNarrowingCost + rhs.fNarrowingCost, false };
+ }
+
+ bool operator<(CoercionCost rhs) const {
+ return std::tie( fImpossible, fNarrowingCost, fNormalCost) <
+ std::tie(rhs.fImpossible, rhs.fNarrowingCost, rhs.fNormalCost);
+ }
+
+ bool operator<=(CoercionCost rhs) const {
+ return std::tie( fImpossible, fNarrowingCost, fNormalCost) <=
+ std::tie(rhs.fImpossible, rhs.fNarrowingCost, rhs.fNormalCost);
+ }
+
+ int fNormalCost;
+ int fNarrowingCost;
+ bool fImpossible;
+};
+
+/**
+ * Represents a type, such as int or float4.
+ */
+class Type : public Symbol {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kType;
+ inline static constexpr int kMaxAbbrevLength = 3;
+ // Represents unspecified array dimensions, as in `int[]`.
+ inline static constexpr int kUnsizedArray = -1;
+ struct Field {
+ Field(Position pos, Modifiers modifiers, std::string_view name, const Type* type)
+ : fPosition(pos)
+ , fModifiers(modifiers)
+ , fName(name)
+ , fType(type) {}
+
+ std::string description() const;
+
+ Position fPosition;
+ Modifiers fModifiers;
+ std::string_view fName;
+ const Type* fType;
+ };
+
+ enum class TypeKind : int8_t {
+ kArray,
+ kAtomic,
+ kGeneric,
+ kLiteral,
+ kMatrix,
+ kOther,
+ kSampler,
+ kSeparateSampler,
+ kScalar,
+ kStruct,
+ kTexture,
+ kVector,
+ kVoid,
+
+ // Types that represent stages in the Skia pipeline
+ kColorFilter,
+ kShader,
+ kBlender,
+ };
+
+ enum class NumberKind : int8_t {
+ kFloat,
+ kSigned,
+ kUnsigned,
+ kBoolean,
+ kNonnumeric
+ };
+
+ enum class TextureAccess : int8_t {
+ kSample, // `kSample` access level allows both sampling and reading
+ kRead,
+ kWrite,
+ kReadWrite,
+ };
+
+ Type(const Type& other) = delete;
+
+ /** Creates an array type. `columns` may be kUnsizedArray. */
+ static std::unique_ptr<Type> MakeArrayType(std::string_view name, const Type& componentType,
+ int columns);
+
+ /** Converts a component type and a size (float, 10) into an array name ("float[10]"). */
+ std::string getArrayName(int arraySize) const;
+
+ /**
+ * Creates an alias which maps to another type.
+ */
+ static std::unique_ptr<Type> MakeAliasType(std::string_view name, const Type& targetType);
+
+ /**
+ * Create a generic type which maps to the listed types--e.g. $genType is a generic type which
+ * can match float, float2, float3 or float4.
+ */
+ static std::unique_ptr<Type> MakeGenericType(const char* name, SkSpan<const Type* const> types);
+
+ /** Create a type for literal scalars. */
+ static std::unique_ptr<Type> MakeLiteralType(const char* name, const Type& scalarType,
+ int8_t priority);
+
+ /** Create a matrix type. */
+ static std::unique_ptr<Type> MakeMatrixType(std::string_view name, const char* abbrev,
+ const Type& componentType, int columns,
+ int8_t rows);
+
+ /** Create a sampler type. */
+ static std::unique_ptr<Type> MakeSamplerType(const char* name, const Type& textureType);
+
+ /** Create a scalar type. */
+ static std::unique_ptr<Type> MakeScalarType(std::string_view name, const char* abbrev,
+ Type::NumberKind numberKind, int8_t priority,
+ int8_t bitWidth);
+
+ /**
+ * Create a "special" type with the given name, abbreviation, and TypeKind.
+ */
+ static std::unique_ptr<Type> MakeSpecialType(const char* name, const char* abbrev,
+ Type::TypeKind typeKind);
+
+ /**
+ * Creates a struct type with the given fields. Reports an error if the struct is not
+ * well-formed.
+ */
+ static std::unique_ptr<Type> MakeStructType(const Context& context,
+ Position pos,
+ std::string_view name,
+ std::vector<Field> fields,
+ bool interfaceBlock = false);
+
+ /** Create a texture type. */
+ static std::unique_ptr<Type> MakeTextureType(const char* name, SpvDim_ dimensions, bool isDepth,
+ bool isArrayedTexture, bool isMultisampled,
+ TextureAccess textureAccess);
+
+ /** Create a vector type. */
+ static std::unique_ptr<Type> MakeVectorType(std::string_view name, const char* abbrev,
+ const Type& componentType, int columns);
+
+ /** Create an atomic type. */
+ static std::unique_ptr<Type> MakeAtomicType(std::string_view name, const char* abbrev);
+
+ template <typename T>
+ bool is() const {
+ return this->typeKind() == T::kTypeKind;
+ }
+
+ template <typename T>
+ const T& as() const {
+ SkASSERT(this->is<T>());
+ return static_cast<const T&>(*this);
+ }
+
+ template <typename T>
+ T& as() {
+ SkASSERT(this->is<T>());
+ return static_cast<T&>(*this);
+ }
+
+ /** Creates a clone of this Type, if needed, and inserts it into a different symbol table. */
+ const Type* clone(SymbolTable* symbolTable) const;
+
+ /**
+ * Returns true if this type is known to come from BuiltinTypes. If this returns true, the Type
+ * will always be available in the root SymbolTable and never needs to be copied to migrate an
+ * Expression from one location to another. If it returns false, the Type might not exist in a
+ * separate SymbolTable and you'll need to consider copying it.
+ */
+ bool isInBuiltinTypes() const {
+ return !(this->isArray() || this->isStruct());
+ }
+
+ std::string displayName() const {
+ return std::string(this->scalarTypeForLiteral().name());
+ }
+
+ std::string description() const override {
+ return this->displayName();
+ }
+
+ /** Returns true if the program supports this type. Strict ES2 programs can't use ES3 types. */
+ bool isAllowedInES2(const Context& context) const;
+
+ /** Returns true if this type is legal to use in a strict-ES2 program. */
+ virtual bool isAllowedInES2() const {
+ return true;
+ }
+
+ /** If this is an alias, returns the underlying type, otherwise returns this. */
+ virtual const Type& resolve() const {
+ return *this;
+ }
+
+ /** Returns true if these types are equal after alias resolution. */
+ bool matches(const Type& other) const {
+ return this->resolve().name() == other.resolve().name();
+ }
+
+ /**
+ * Returns an abbreviated name of the type, meant for name-mangling. (e.g. float4x4 -> f44)
+ */
+ const char* abbreviatedName() const {
+ return fAbbreviatedName;
+ }
+
+ /**
+ * Returns the category (scalar, vector, matrix, etc.) of this type.
+ */
+ TypeKind typeKind() const {
+ return fTypeKind;
+ }
+
+ /**
+ * Returns the NumberKind of this type (always kNonnumeric for non-scalar values).
+ */
+ virtual NumberKind numberKind() const {
+ return NumberKind::kNonnumeric;
+ }
+
+ /**
+ * Returns true if this type is a bool.
+ */
+ bool isBoolean() const {
+ return this->numberKind() == NumberKind::kBoolean;
+ }
+
+ /**
+ * Returns true if this is a numeric scalar type.
+ */
+ bool isNumber() const {
+ switch (this->numberKind()) {
+ case NumberKind::kFloat:
+ case NumberKind::kSigned:
+ case NumberKind::kUnsigned:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Returns true if this is a floating-point scalar type (float or half).
+ */
+ bool isFloat() const {
+ return this->numberKind() == NumberKind::kFloat;
+ }
+
+ /**
+ * Returns true if this is a signed scalar type (int or short).
+ */
+ bool isSigned() const {
+ return this->numberKind() == NumberKind::kSigned;
+ }
+
+ /**
+ * Returns true if this is an unsigned scalar type (uint or ushort).
+ */
+ bool isUnsigned() const {
+ return this->numberKind() == NumberKind::kUnsigned;
+ }
+
+ /**
+ * Returns true if this is a signed or unsigned integer.
+ */
+ bool isInteger() const {
+ switch (this->numberKind()) {
+ case NumberKind::kSigned:
+ case NumberKind::kUnsigned:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Returns true if this is an "opaque type" (an external object which the shader references in
+ * some fashion). https://www.khronos.org/opengl/wiki/Data_Type_(GLSL)#Opaque_types
+ */
+ bool isOpaque() const {
+ switch (fTypeKind) {
+ case TypeKind::kAtomic:
+ case TypeKind::kBlender:
+ case TypeKind::kColorFilter:
+ case TypeKind::kSampler:
+ case TypeKind::kSeparateSampler:
+ case TypeKind::kShader:
+ case TypeKind::kTexture:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Returns the "priority" of a number type, in order of float > half > int > short.
+ * When operating on two number types, the result is the higher-priority type.
+ */
+ virtual int priority() const {
+ SkDEBUGFAIL("not a number type");
+ return -1;
+ }
+
+ /**
+ * Returns true if an instance of this type can be freely coerced (implicitly converted) to
+ * another type.
+ */
+ bool canCoerceTo(const Type& other, bool allowNarrowing) const {
+ return this->coercionCost(other).isPossible(allowNarrowing);
+ }
+
+ /**
+ * Determines the "cost" of coercing (implicitly converting) this type to another type. The cost
+ * is a number with no particular meaning other than that lower costs are preferable to higher
+ * costs. Returns INT_MAX if the coercion is not possible.
+ */
+ CoercionCost coercionCost(const Type& other) const;
+
+ /**
+ * For matrices and vectors, returns the type of individual cells (e.g. mat2 has a component
+ * type of Float). For arrays, returns the base type. For all other types, returns the type
+ * itself.
+ */
+ virtual const Type& componentType() const {
+ return *this;
+ }
+
+ /**
+ * For texture samplers, returns the type of texture it samples (e.g., sampler2D has
+ * a texture type of texture2D).
+ */
+ virtual const Type& textureType() const {
+ SkDEBUGFAIL("not a sampler type");
+ return *this;
+ }
+
+ /**
+ * For matrices and vectors, returns the number of columns (e.g. both mat3 and float3 return 3).
+ * For scalars, returns 1. For arrays, returns either the size of the array (if known) or -1.
+ * For all other types, causes an assertion failure.
+ */
+ virtual int columns() const {
+ SkDEBUGFAIL("type does not have columns");
+ return -1;
+ }
+
+ /**
+ * For matrices, returns the number of rows (e.g. mat2x4 returns 4). For vectors and scalars,
+ * returns 1. For all other types, causes an assertion failure.
+ */
+ virtual int rows() const {
+ SkDEBUGFAIL("type does not have rows");
+ return -1;
+ }
+
+ /** Returns the minimum value that can fit in the type. */
+ virtual double minimumValue() const {
+ SkDEBUGFAIL("type does not have a minimum value");
+ return -INFINITY;
+ }
+
+ virtual double maximumValue() const {
+ SkDEBUGFAIL("type does not have a maximum value");
+ return +INFINITY;
+ }
+
+ /**
+ * Returns the number of scalars needed to hold this type.
+ */
+ virtual size_t slotCount() const {
+ return 0;
+ }
+
+ virtual const std::vector<Field>& fields() const {
+ SK_ABORT("Internal error: not a struct");
+ }
+
+ /**
+ * For generic types, returns the types that this generic type can substitute for.
+ */
+ virtual SkSpan<const Type* const> coercibleTypes() const {
+ SkDEBUGFAIL("Internal error: not a generic type");
+ return {};
+ }
+
+ virtual SpvDim_ dimensions() const {
+ SkASSERT(false);
+ return SpvDim1D;
+ }
+
+ virtual bool isDepth() const {
+ SkASSERT(false);
+ return false;
+ }
+
+ virtual bool isArrayedTexture() const {
+ SkASSERT(false);
+ return false;
+ }
+
+ bool isVoid() const {
+ return fTypeKind == TypeKind::kVoid;
+ }
+
+ bool isGeneric() const {
+ return fTypeKind == TypeKind::kGeneric;
+ }
+
+ bool isAtomic() const { return this->typeKind() == TypeKind::kAtomic; }
+
+ virtual bool isScalar() const {
+ return false;
+ }
+
+ virtual bool isLiteral() const {
+ return false;
+ }
+
+ virtual const Type& scalarTypeForLiteral() const {
+ return *this;
+ }
+
+ virtual bool isVector() const {
+ return false;
+ }
+
+ virtual bool isMatrix() const {
+ return false;
+ }
+
+ virtual bool isArray() const {
+ return false;
+ }
+
+ virtual bool isUnsizedArray() const {
+ return false;
+ }
+
+ virtual bool isStruct() const {
+ return false;
+ }
+
+ virtual bool isInterfaceBlock() const {
+ return false;
+ }
+
+ // Is this type something that can be bound & sampled from an SkRuntimeEffect?
+ // Includes types that represent stages of the Skia pipeline (colorFilter, shader, blender).
+ bool isEffectChild() const {
+ return fTypeKind == TypeKind::kColorFilter ||
+ fTypeKind == TypeKind::kShader ||
+ fTypeKind == TypeKind::kBlender;
+ }
+
+ virtual bool isMultisampled() const {
+ SkDEBUGFAIL("not a texture type");
+ return false;
+ }
+
+ virtual TextureAccess textureAccess() const {
+ SkDEBUGFAIL("not a texture type");
+ return TextureAccess::kSample;
+ }
+
+ bool hasPrecision() const {
+ return this->componentType().isNumber() || fTypeKind == TypeKind::kSampler;
+ }
+
+ bool highPrecision() const {
+ return this->bitWidth() >= 32;
+ }
+
+ virtual int bitWidth() const {
+ return 0;
+ }
+
+ bool isOrContainsArray() const;
+ bool isOrContainsUnsizedArray() const;
+ bool isOrContainsAtomic() const;
+
+ /**
+ * Returns the corresponding vector or matrix type with the specified number of columns and
+ * rows.
+ */
+ const Type& toCompound(const Context& context, int columns, int rows) const;
+
+ /**
+ * Returns a type which honors the precision and access-level qualifiers set in Modifiers. e.g.:
+ * - Modifier `mediump` + Type `float2`: Type `half2`
+ * - Modifier `readonly` + Type `texture2D`: Type `readonlyTexture2D`
+ * Generates an error if the qualifiers don't make sense (`highp bool`, `writeonly MyStruct`)
+ */
+ const Type* applyQualifiers(const Context& context,
+ Modifiers* modifiers,
+ SymbolTable* symbols,
+ Position pos) const;
+
+ /**
+ * Coerces the passed-in expression to this type. If the types are incompatible, reports an
+ * error and returns null.
+ */
+ std::unique_ptr<Expression> coerceExpression(std::unique_ptr<Expression> expr,
+ const Context& context) const;
+
+ /** Detects any IntLiterals in the expression which can't fit in this type. */
+ bool checkForOutOfRangeLiteral(const Context& context, const Expression& expr) const;
+
+ /** Checks if `value` can fit in this type. The type must be scalar. */
+ bool checkForOutOfRangeLiteral(const Context& context, double value, Position pos) const;
+
+ /**
+ * Reports errors and returns false if this type cannot be used as the base type for an array.
+ */
+ bool checkIfUsableInArray(const Context& context, Position arrayPos) const;
+
+ /**
+ * Verifies that the expression is a valid constant array size for this type. Returns the array
+ * size, or reports errors and returns zero if the expression isn't a valid literal value.
+ */
+ SKSL_INT convertArraySize(const Context& context, Position arrayPos,
+ std::unique_ptr<Expression> size) const;
+
+protected:
+ Type(std::string_view name, const char* abbrev, TypeKind kind,
+ Position pos = Position())
+ : INHERITED(pos, kIRNodeKind, name)
+ , fTypeKind(kind) {
+ SkASSERT(strlen(abbrev) <= kMaxAbbrevLength);
+ strcpy(fAbbreviatedName, abbrev);
+ }
+
+ const Type* applyPrecisionQualifiers(const Context& context,
+ Modifiers* modifiers,
+ SymbolTable* symbols,
+ Position pos) const;
+
+ const Type* applyAccessQualifiers(const Context& context,
+ Modifiers* modifiers,
+ SymbolTable* symbols,
+ Position pos) const;
+
+private:
+ using INHERITED = Symbol;
+
+ char fAbbreviatedName[kMaxAbbrevLength + 1] = {};
+ TypeKind fTypeKind;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLTypeReference.cpp b/gfx/skia/skia/src/sksl/ir/SkSLTypeReference.cpp
new file mode 100644
index 0000000000..c725b38bb6
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLTypeReference.cpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLTypeReference.h"
+
+#include "include/core/SkTypes.h"
+#include "include/sksl/SkSLErrorReporter.h"
+
+namespace SkSL {
+
+std::unique_ptr<TypeReference> TypeReference::Convert(const Context& context,
+ Position pos,
+ const Type* type) {
+ if (!type->isAllowedInES2(context)) {
+ context.fErrors->error(pos, "type '" + type->displayName() + "' is not supported");
+ return nullptr;
+ }
+ return TypeReference::Make(context, pos, type);
+}
+
+std::unique_ptr<TypeReference> TypeReference::Make(const Context& context,
+ Position pos,
+ const Type* type) {
+ SkASSERT(type->isAllowedInES2(context));
+ return std::make_unique<TypeReference>(context, pos, type);
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLTypeReference.h b/gfx/skia/skia/src/sksl/ir/SkSLTypeReference.h
new file mode 100644
index 0000000000..aaca8a8f64
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLTypeReference.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_TYPEREFERENCE
+#define SKSL_TYPEREFERENCE
+
+#include "include/private/SkSLIRNode.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLType.h"
+
+#include <cstdint>
+#include <memory>
+#include <string>
+
+namespace SkSL {
+
+enum class OperatorPrecedence : uint8_t;
+
+/**
+ * Represents an identifier referring to a type. This is an intermediate value: TypeReferences are
+ * always eventually replaced by Constructors in valid programs.
+ */
+class TypeReference final : public Expression {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kTypeReference;
+
+ TypeReference(const Context& context, Position pos, const Type* value)
+ : TypeReference(pos, value, context.fTypes.fInvalid.get()) {}
+
+ // Creates a reference to an SkSL type; uses the ErrorReporter to report errors.
+ static std::unique_ptr<TypeReference> Convert(const Context& context,
+ Position pos,
+ const Type* type);
+
+ // Creates a reference to an SkSL type; reports errors via ASSERT.
+ static std::unique_ptr<TypeReference> Make(const Context& context, Position pos,
+ const Type* type);
+
+ const Type& value() const {
+ return fValue;
+ }
+
+ std::string description(OperatorPrecedence) const override {
+ return std::string(this->value().name());
+ }
+
+ std::unique_ptr<Expression> clone(Position pos) const override {
+ return std::unique_ptr<Expression>(new TypeReference(pos, &this->value(), &this->type()));
+ }
+
+private:
+ TypeReference(Position pos, const Type* value, const Type* type)
+ : INHERITED(pos, kIRNodeKind, type)
+ , fValue(*value) {}
+
+ const Type& fValue;
+
+ using INHERITED = Expression;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLVarDeclarations.cpp b/gfx/skia/skia/src/sksl/ir/SkSLVarDeclarations.cpp
new file mode 100644
index 0000000000..8d698687bf
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLVarDeclarations.cpp
@@ -0,0 +1,468 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLVarDeclarations.h"
+
+#include "include/private/SkSLLayout.h"
+#include "include/private/SkSLModifiers.h"
+#include "include/private/SkSLProgramKind.h"
+#include "include/private/SkSLString.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/SkSLThreadContext.h"
+#include "src/sksl/ir/SkSLSymbolTable.h"
+#include "src/sksl/ir/SkSLType.h"
+
+#include <cstddef>
+#include <string_view>
+#include <vector>
+
+namespace SkSL {
+namespace {
+
+static bool check_valid_uniform_type(Position pos,
+ const Type* t,
+ const Context& context,
+ bool topLevel = true) {
+ const Type& ct = t->componentType();
+
+ // In RuntimeEffects we only allow a restricted set of types, namely shader/blender/colorFilter,
+ // 32-bit signed integers, 16-bit and 32-bit floats, and their composites.
+ {
+ bool error = false;
+ if (ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
+ // `shader`, `blender`, `colorFilter`
+ if (t->isEffectChild()) {
+ return true;
+ }
+
+ // `int`, `int2`, `int3`, `int4`
+ if (ct.isSigned() && ct.bitWidth() == 32 && (t->isScalar() || t->isVector())) {
+ return true;
+ }
+
+ // `float`, `float2`, `float3`, `float4`, `float2x2`, `float3x3`, `float4x4`
+ // `half`, `half2`, `half3`, `half4`, `half2x2`, `half3x3`, `half4x4`
+ if (ct.isFloat() &&
+ (t->isScalar() || t->isVector() || (t->isMatrix() && t->rows() == t->columns()))) {
+ return true;
+ }
+
+ // Everything else is an error.
+ error = true;
+ }
+
+ // We disallow boolean uniforms in SkSL since they are not well supported by backend
+ // platforms and drivers. We disallow atomic variables in uniforms as that doesn't map
+ // cleanly to all backends.
+ if (error || (ct.isBoolean() && (t->isScalar() || t->isVector())) || ct.isAtomic()) {
+ context.fErrors->error(
+ pos, "variables of type '" + t->displayName() + "' may not be uniform");
+ return false;
+ }
+ }
+
+ // In non-RTE SkSL we allow structs and interface blocks to be uniforms but we must make sure
+ // their fields are allowed.
+ if (t->isStruct()) {
+ for (const Type::Field& field : t->fields()) {
+ if (!check_valid_uniform_type(
+ field.fPosition, field.fType, context, /*topLevel=*/false)) {
+ // Emit a "caused by" line only for the top-level uniform type and not for any
+ // nested structs.
+ if (topLevel) {
+ context.fErrors->error(pos, "caused by:");
+ }
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+} // namespace
+
+std::unique_ptr<Statement> VarDeclaration::clone() const {
+ // Cloning a VarDeclaration is inherently problematic, as we normally expect a one-to-one
+ // mapping between Variables and VarDeclarations and a straightforward clone would violate this
+ // assumption. We could of course theoretically clone the Variable as well, but that would
+ // require additional context and tracking, since for the whole process to work we would also
+ // have to fixup any subsequent VariableReference clones to point to the newly cloned Variables
+ // instead of the originals.
+ //
+ // Since the only reason we ever clone VarDeclarations is to support tests of clone() and we do
+ // not expect to ever need to do so otherwise, a full solution to this issue is unnecessary at
+ // the moment. We instead just keep track of whether a VarDeclaration is a clone so we can
+ // handle its cleanup properly. This allows clone() to work in the simple case that a
+ // VarDeclaration's clone does not outlive the original, which is adequate for testing. Since
+ // this leaves a sharp edge in place - destroying the original could cause a use-after-free in
+ // some circumstances - we also disable cloning altogether unless the
+ // fAllowVarDeclarationCloneForTesting ProgramSetting is enabled.
+ if (ThreadContext::Settings().fAllowVarDeclarationCloneForTesting) {
+ return std::make_unique<VarDeclaration>(this->var(),
+ &this->baseType(),
+ fArraySize,
+ this->value() ? this->value()->clone() : nullptr,
+ /*isClone=*/true);
+ } else {
+ SkDEBUGFAIL("VarDeclaration::clone() is unsupported");
+ return nullptr;
+ }
+}
+
+std::string VarDeclaration::description() const {
+ std::string result = this->var()->modifiers().description() + this->baseType().description() +
+ " " + std::string(this->var()->name());
+ if (this->arraySize() > 0) {
+ String::appendf(&result, "[%d]", this->arraySize());
+ }
+ if (this->value()) {
+ result += " = " + this->value()->description();
+ }
+ result += ";";
+ return result;
+}
+
+void VarDeclaration::ErrorCheck(const Context& context,
+ Position pos,
+ Position modifiersPosition,
+ const Modifiers& modifiers,
+ const Type* type,
+ Variable::Storage storage) {
+ const Type* baseType = type;
+ if (baseType->isArray()) {
+ baseType = &baseType->componentType();
+ }
+ SkASSERT(!baseType->isArray());
+
+ if (baseType->matches(*context.fTypes.fInvalid)) {
+ context.fErrors->error(pos, "invalid type");
+ return;
+ }
+ if (baseType->isVoid()) {
+ context.fErrors->error(pos, "variables of type 'void' are not allowed");
+ return;
+ }
+
+ if (baseType->componentType().isOpaque() && !baseType->componentType().isAtomic() &&
+ storage != Variable::Storage::kGlobal) {
+ context.fErrors->error(pos,
+ "variables of type '" + baseType->displayName() + "' must be global");
+ }
+ if ((modifiers.fFlags & Modifiers::kIn_Flag) && baseType->isMatrix()) {
+ context.fErrors->error(pos, "'in' variables may not have matrix type");
+ }
+ if ((modifiers.fFlags & Modifiers::kIn_Flag) && type->isUnsizedArray()) {
+ context.fErrors->error(pos, "'in' variables may not have unsized array type");
+ }
+ if ((modifiers.fFlags & Modifiers::kOut_Flag) && type->isUnsizedArray()) {
+ context.fErrors->error(pos, "'out' variables may not have unsized array type");
+ }
+ if ((modifiers.fFlags & Modifiers::kIn_Flag) && (modifiers.fFlags & Modifiers::kUniform_Flag)) {
+ context.fErrors->error(pos, "'in uniform' variables not permitted");
+ }
+ if ((modifiers.fFlags & Modifiers::kReadOnly_Flag) &&
+ (modifiers.fFlags & Modifiers::kWriteOnly_Flag)) {
+ context.fErrors->error(pos, "'readonly' and 'writeonly' qualifiers cannot be combined");
+ }
+ if ((modifiers.fFlags & Modifiers::kUniform_Flag) &&
+ (modifiers.fFlags & Modifiers::kBuffer_Flag)) {
+ context.fErrors->error(pos, "'uniform buffer' variables not permitted");
+ }
+ if ((modifiers.fFlags & Modifiers::kWorkgroup_Flag) &&
+ (modifiers.fFlags & (Modifiers::kIn_Flag | Modifiers::kOut_Flag))) {
+ context.fErrors->error(pos, "in / out variables may not be declared workgroup");
+ }
+ if ((modifiers.fFlags & Modifiers::kUniform_Flag)) {
+ check_valid_uniform_type(pos, baseType, context);
+ }
+ if (baseType->isEffectChild() && !(modifiers.fFlags & Modifiers::kUniform_Flag)) {
+ context.fErrors->error(pos,
+ "variables of type '" + baseType->displayName() + "' must be uniform");
+ }
+ if (baseType->isEffectChild() && (context.fConfig->fKind == ProgramKind::kMeshVertex ||
+ context.fConfig->fKind == ProgramKind::kMeshFragment)) {
+ context.fErrors->error(pos, "effects are not permitted in custom mesh shaders");
+ }
+ if (baseType->isOrContainsAtomic()) {
+ // An atomic variable (or a struct or an array that contains an atomic member) must be
+ // either:
+ // a. Declared as a workgroup-shared variable, OR
+ // b. Declared as the member of writable storage buffer block (i.e. has no readonly
+ // restriction).
+ //
+ // The checks below will enforce these two rules on all declarations. If the variable is not
+ // declared with the workgroup modifier, then it must be declared in the interface block
+ // storage. If this is the declaration for an interface block that contains an atomic
+ // member, then it must have the `buffer` modifier and no `readonly` modifier.
+ bool isWorkgroup = modifiers.fFlags & Modifiers::kWorkgroup_Flag;
+ bool isBlockMember = (storage == Variable::Storage::kInterfaceBlock);
+ bool isWritableStorageBuffer = modifiers.fFlags & Modifiers::kBuffer_Flag &&
+ !(modifiers.fFlags & Modifiers::kReadOnly_Flag);
+
+ if (!isWorkgroup &&
+ !(baseType->isInterfaceBlock() ? isWritableStorageBuffer : isBlockMember)) {
+ context.fErrors->error(pos,
+ "atomics are only permitted in workgroup variables and writable "
+ "storage blocks");
+ }
+ }
+ if (modifiers.fLayout.fFlags & Layout::kColor_Flag) {
+ if (!ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
+ context.fErrors->error(pos, "'layout(color)' is only permitted in runtime effects");
+ }
+ if (!(modifiers.fFlags & Modifiers::kUniform_Flag)) {
+ context.fErrors->error(pos,
+ "'layout(color)' is only permitted on 'uniform' variables");
+ }
+ auto validColorXformType = [](const Type& t) {
+ return t.isVector() && t.componentType().isFloat() &&
+ (t.columns() == 3 || t.columns() == 4);
+ };
+ if (!validColorXformType(*baseType)) {
+ context.fErrors->error(pos,
+ "'layout(color)' is not permitted on variables of type '" +
+ baseType->displayName() + "'");
+ }
+ }
+
+ int permitted = Modifiers::kConst_Flag | Modifiers::kHighp_Flag | Modifiers::kMediump_Flag |
+ Modifiers::kLowp_Flag;
+ if (storage == Variable::Storage::kGlobal) {
+ // Uniforms are allowed in all programs
+ permitted |= Modifiers::kUniform_Flag;
+
+ // No other modifiers are allowed in runtime effects.
+ if (!ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
+ if (baseType->isInterfaceBlock()) {
+ // Interface blocks allow `buffer`.
+ permitted |= Modifiers::kBuffer_Flag;
+
+ if (modifiers.fFlags & Modifiers::kBuffer_Flag) {
+ // Only storage blocks allow `readonly` and `writeonly`.
+ // (`readonly` and `writeonly` textures are converted to separate types via
+ // applyAccessQualifiers.)
+ permitted |= Modifiers::kReadOnly_Flag | Modifiers::kWriteOnly_Flag;
+ }
+
+ // It is an error for an unsized array to appear anywhere but the last member of a
+ // "buffer" block.
+ const auto& fields = baseType->fields();
+ const size_t illegalRangeEnd =
+ fields.size() - ((modifiers.fFlags & Modifiers::kBuffer_Flag) ? 1 : 0);
+ for (size_t i = 0; i < illegalRangeEnd; ++i) {
+ if (fields[i].fType->isUnsizedArray()) {
+ context.fErrors->error(
+ fields[i].fPosition,
+ "unsized array must be the last member of a storage block");
+ }
+ }
+ }
+
+ if (!baseType->isOpaque()) {
+ // Only non-opaque types allow `in` and `out`.
+ permitted |= Modifiers::kIn_Flag | Modifiers::kOut_Flag;
+ }
+ if (ProgramConfig::IsCompute(context.fConfig->fKind)) {
+ // Only compute shaders allow `workgroup`.
+ if (!baseType->isOpaque() || baseType->isAtomic()) {
+ permitted |= Modifiers::kWorkgroup_Flag;
+ }
+ } else {
+ // Only vertex/fragment shaders allow `flat` and `noperspective`.
+ permitted |= Modifiers::kFlat_Flag | Modifiers::kNoPerspective_Flag;
+ }
+ }
+ }
+
+ int permittedLayoutFlags = ~0;
+
+ // The `texture` and `sampler` modifiers can be present respectively on a texture and sampler or
+ // simultaneously on a combined image-sampler but they are not permitted on any other type.
+ switch (baseType->typeKind()) {
+ case Type::TypeKind::kSampler:
+ // Both texture and sampler flags are permitted
+ break;
+ case Type::TypeKind::kTexture:
+ permittedLayoutFlags &= ~Layout::kSampler_Flag;
+ break;
+ case Type::TypeKind::kSeparateSampler:
+ permittedLayoutFlags &= ~Layout::kTexture_Flag;
+ break;
+ default:
+ permittedLayoutFlags &= ~(Layout::kTexture_Flag | Layout::kSampler_Flag);
+ break;
+ }
+
+ // We don't allow 'binding' or 'set' on normal uniform variables, only on textures, samplers,
+ // and interface blocks (holding uniform variables). They're also only allowed at global scope,
+ // not on interface block fields (or locals/parameters).
+ bool permitBindingAndSet = baseType->typeKind() == Type::TypeKind::kSampler ||
+ baseType->typeKind() == Type::TypeKind::kSeparateSampler ||
+ baseType->typeKind() == Type::TypeKind::kTexture ||
+ baseType->isInterfaceBlock();
+ if (storage != Variable::Storage::kGlobal ||
+ ((modifiers.fFlags & Modifiers::kUniform_Flag) && !permitBindingAndSet)) {
+ permittedLayoutFlags &= ~Layout::kBinding_Flag;
+ permittedLayoutFlags &= ~Layout::kSet_Flag;
+ permittedLayoutFlags &= ~Layout::kSPIRV_Flag;
+ permittedLayoutFlags &= ~Layout::kMetal_Flag;
+ permittedLayoutFlags &= ~Layout::kWGSL_Flag;
+ permittedLayoutFlags &= ~Layout::kGL_Flag;
+ }
+ if (ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
+ // Disallow all layout flags except 'color' in runtime effects
+ permittedLayoutFlags &= Layout::kColor_Flag;
+ }
+ modifiers.checkPermitted(context, modifiersPosition, permitted, permittedLayoutFlags);
+}
+
+bool VarDeclaration::ErrorCheckAndCoerce(const Context& context, const Variable& var,
+ std::unique_ptr<Expression>& value) {
+ ErrorCheck(context, var.fPosition, var.modifiersPosition(), var.modifiers(), &var.type(),
+ var.storage());
+ if (value) {
+ if (var.type().isOpaque()) {
+ context.fErrors->error(value->fPosition, "opaque type '" + var.type().displayName() +
+ "' cannot use initializer expressions");
+ return false;
+ }
+ if (var.modifiers().fFlags & Modifiers::kIn_Flag) {
+ context.fErrors->error(value->fPosition,
+ "'in' variables cannot use initializer expressions");
+ return false;
+ }
+ if (var.modifiers().fFlags & Modifiers::kUniform_Flag) {
+ context.fErrors->error(value->fPosition,
+ "'uniform' variables cannot use initializer expressions");
+ return false;
+ }
+ if (var.storage() == Variable::Storage::kInterfaceBlock) {
+ context.fErrors->error(value->fPosition,
+ "initializers are not permitted on interface block fields");
+ return false;
+ }
+ value = var.type().coerceExpression(std::move(value), context);
+ if (!value) {
+ return false;
+ }
+ }
+ if (var.modifiers().fFlags & Modifiers::kConst_Flag) {
+ if (!value) {
+ context.fErrors->error(var.fPosition, "'const' variables must be initialized");
+ return false;
+ }
+ if (!Analysis::IsConstantExpression(*value)) {
+ context.fErrors->error(value->fPosition,
+ "'const' variable initializer must be a constant expression");
+ return false;
+ }
+ }
+ if (var.storage() == Variable::Storage::kInterfaceBlock) {
+ if (var.type().isOpaque()) {
+ context.fErrors->error(var.fPosition, "opaque type '" + var.type().displayName() +
+ "' is not permitted in an interface block");
+ return false;
+ }
+ }
+ if (var.storage() == Variable::Storage::kGlobal) {
+ if (value && !Analysis::IsConstantExpression(*value)) {
+ context.fErrors->error(value->fPosition,
+ "global variable initializer must be a constant expression");
+ return false;
+ }
+ }
+ return true;
+}
+
+std::unique_ptr<Statement> VarDeclaration::Convert(const Context& context,
+ std::unique_ptr<Variable> var,
+ std::unique_ptr<Expression> value,
+ bool addToSymbolTable) {
+ if (!ErrorCheckAndCoerce(context, *var, value)) {
+ return nullptr;
+ }
+ const Type* baseType = &var->type();
+ int arraySize = 0;
+ if (baseType->isArray()) {
+ arraySize = baseType->columns();
+ baseType = &baseType->componentType();
+ }
+ std::unique_ptr<Statement> varDecl = VarDeclaration::Make(context, var.get(), baseType,
+ arraySize, std::move(value));
+ if (!varDecl) {
+ return nullptr;
+ }
+
+ SymbolTable* symbols = ThreadContext::SymbolTable().get();
+ if (var->storage() == Variable::Storage::kGlobal ||
+ var->storage() == Variable::Storage::kInterfaceBlock) {
+ // Check if this globally-scoped variable name overlaps an existing symbol name.
+ if (symbols->find(var->name())) {
+ context.fErrors->error(var->fPosition,
+ "symbol '" + std::string(var->name()) + "' was already defined");
+ return nullptr;
+ }
+
+ // `sk_RTAdjust` is special, and makes the IR generator emit position-fixup expressions.
+ if (var->name() == Compiler::RTADJUST_NAME) {
+ if (ThreadContext::RTAdjustState().fVar ||
+ ThreadContext::RTAdjustState().fInterfaceBlock) {
+ context.fErrors->error(var->fPosition, "duplicate definition of 'sk_RTAdjust'");
+ return nullptr;
+ }
+ if (!var->type().matches(*context.fTypes.fFloat4)) {
+ context.fErrors->error(var->fPosition, "sk_RTAdjust must have type 'float4'");
+ return nullptr;
+ }
+ ThreadContext::RTAdjustState().fVar = var.get();
+ }
+ }
+
+ if (addToSymbolTable) {
+ symbols->add(std::move(var));
+ } else {
+ symbols->takeOwnershipOfSymbol(std::move(var));
+ }
+ return varDecl;
+}
+
+std::unique_ptr<Statement> VarDeclaration::Make(const Context& context, Variable* var,
+ const Type* baseType, int arraySize, std::unique_ptr<Expression> value) {
+ SkASSERT(!baseType->isArray());
+ // function parameters cannot have variable declarations
+ SkASSERT(var->storage() != Variable::Storage::kParameter);
+ // 'const' variables must be initialized
+ SkASSERT(!(var->modifiers().fFlags & Modifiers::kConst_Flag) || value);
+ // 'const' variable initializer must be a constant expression
+ SkASSERT(!(var->modifiers().fFlags & Modifiers::kConst_Flag) ||
+ Analysis::IsConstantExpression(*value));
+ // global variable initializer must be a constant expression
+ SkASSERT(!(value && var->storage() == Variable::Storage::kGlobal &&
+ !Analysis::IsConstantExpression(*value)));
+ // opaque type not permitted on an interface block
+ SkASSERT(!(var->storage() == Variable::Storage::kInterfaceBlock && var->type().isOpaque()));
+ // initializers are not permitted on interface block fields
+ SkASSERT(!(var->storage() == Variable::Storage::kInterfaceBlock && value));
+ // opaque type cannot use initializer expressions
+ SkASSERT(!(value && var->type().isOpaque()));
+ // 'in' variables cannot use initializer expressions
+ SkASSERT(!(value && (var->modifiers().fFlags & Modifiers::kIn_Flag)));
+ // 'uniform' variables cannot use initializer expressions
+ SkASSERT(!(value && (var->modifiers().fFlags & Modifiers::kUniform_Flag)));
+
+ auto result = std::make_unique<VarDeclaration>(var, baseType, arraySize, std::move(value));
+ var->setVarDeclaration(result.get());
+ return std::move(result);
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLVarDeclarations.h b/gfx/skia/skia/src/sksl/ir/SkSLVarDeclarations.h
new file mode 100644
index 0000000000..b90528732e
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLVarDeclarations.h
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_VARDECLARATIONS
+#define SKSL_VARDECLARATIONS
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLProgramElement.h"
+#include "include/private/SkSLStatement.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLVariable.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace SkSL {
+
+class Context;
+class Position;
+class Type;
+
+struct Modifiers;
+
+/**
+ * A single variable declaration statement. Multiple variables declared together are expanded to
+ * separate (sequential) statements. For instance, the SkSL 'int x = 2, y[3];' produces two
+ * VarDeclaration instances (wrapped in an unscoped Block).
+ */
+class VarDeclaration final : public Statement {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kVarDeclaration;
+
+ VarDeclaration(Variable* var,
+ const Type* baseType,
+ int arraySize,
+ std::unique_ptr<Expression> value,
+ bool isClone = false)
+ : INHERITED(var->fPosition, kIRNodeKind)
+ , fVar(var)
+ , fBaseType(*baseType)
+ , fArraySize(arraySize)
+ , fValue(std::move(value))
+ , fIsClone(isClone) {}
+
+ ~VarDeclaration() override {
+ // Unhook this VarDeclaration from its associated Variable, since we're being deleted.
+ if (fVar && !fIsClone) {
+ fVar->detachDeadVarDeclaration();
+ }
+ }
+
+ // Checks the modifiers, baseType, and storage for compatibility with one another and reports
+ // errors if needed. This method is implicitly called during Convert(), but is also explicitly
+ // called while processing interface block fields.
+ static void ErrorCheck(const Context& context, Position pos, Position modifiersPosition,
+ const Modifiers& modifiers, const Type* type, Variable::Storage storage);
+
+ // Does proper error checking and type coercion; reports errors via ErrorReporter.
+ static std::unique_ptr<Statement> Convert(const Context& context, std::unique_ptr<Variable> var,
+ std::unique_ptr<Expression> value, bool addToSymbolTable = true);
+
+ // Reports errors via ASSERT.
+ static std::unique_ptr<Statement> Make(const Context& context,
+ Variable* var,
+ const Type* baseType,
+ int arraySize,
+ std::unique_ptr<Expression> value);
+ const Type& baseType() const {
+ return fBaseType;
+ }
+
+ Variable* var() const {
+ return fVar;
+ }
+
+ void detachDeadVariable() {
+ fVar = nullptr;
+ }
+
+ int arraySize() const {
+ return fArraySize;
+ }
+
+ std::unique_ptr<Expression>& value() {
+ return fValue;
+ }
+
+ const std::unique_ptr<Expression>& value() const {
+ return fValue;
+ }
+
+ std::unique_ptr<Statement> clone() const override;
+
+ std::string description() const override;
+
+private:
+ static bool ErrorCheckAndCoerce(const Context& context,
+ const Variable& var,
+ std::unique_ptr<Expression>& value);
+
+ Variable* fVar;
+ const Type& fBaseType;
+ int fArraySize; // zero means "not an array"
+ std::unique_ptr<Expression> fValue;
+ // if this VarDeclaration is a clone, it doesn't actually own the associated variable
+ bool fIsClone;
+
+ using INHERITED = Statement;
+};
+
+/**
+ * A variable declaration appearing at global scope. A global declaration like 'int x, y;' produces
+ * two GlobalVarDeclaration elements, each containing the declaration of one variable.
+ */
+class GlobalVarDeclaration final : public ProgramElement {
+public:
+ inline static constexpr Kind kIRNodeKind = Kind::kGlobalVar;
+
+ GlobalVarDeclaration(std::unique_ptr<Statement> decl)
+ : INHERITED(decl->fPosition, kIRNodeKind)
+ , fDeclaration(std::move(decl)) {
+ SkASSERT(this->declaration()->is<VarDeclaration>());
+ this->varDeclaration().var()->setGlobalVarDeclaration(this);
+ }
+
+ std::unique_ptr<Statement>& declaration() {
+ return fDeclaration;
+ }
+
+ const std::unique_ptr<Statement>& declaration() const {
+ return fDeclaration;
+ }
+
+ VarDeclaration& varDeclaration() {
+ return fDeclaration->as<VarDeclaration>();
+ }
+
+ const VarDeclaration& varDeclaration() const {
+ return fDeclaration->as<VarDeclaration>();
+ }
+
+ std::unique_ptr<ProgramElement> clone() const override {
+ return std::make_unique<GlobalVarDeclaration>(this->declaration()->clone());
+ }
+
+ std::string description() const override {
+ return this->declaration()->description();
+ }
+
+private:
+ std::unique_ptr<Statement> fDeclaration;
+
+ using INHERITED = ProgramElement;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLVariable.cpp b/gfx/skia/skia/src/sksl/ir/SkSLVariable.cpp
new file mode 100644
index 0000000000..95c292a8ad
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLVariable.cpp
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2021 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLVariable.h"
+
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLLayout.h"
+#include "include/sksl/SkSLErrorReporter.h"
+#include "src/base/SkStringView.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLMangler.h"
+#include "src/sksl/SkSLModifiersPool.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/SkSLThreadContext.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLInterfaceBlock.h"
+#include "src/sksl/ir/SkSLSymbolTable.h"
+#include "src/sksl/ir/SkSLVarDeclarations.h"
+
+#include <type_traits>
+#include <utility>
+
+namespace SkSL {
+
+Variable::~Variable() {
+ // Unhook this Variable from its associated VarDeclaration, since we're being deleted.
+ if (VarDeclaration* declaration = this->varDeclaration()) {
+ declaration->detachDeadVariable();
+ }
+}
+
+InterfaceBlockVariable::~InterfaceBlockVariable() {
+ // Unhook this Variable from its associated InterfaceBlock, since we're being deleted.
+ if (fInterfaceBlockElement) {
+ fInterfaceBlockElement->detachDeadVariable();
+ }
+}
+
+const Expression* Variable::initialValue() const {
+ VarDeclaration* declaration = this->varDeclaration();
+ return declaration ? declaration->value().get() : nullptr;
+}
+
+VarDeclaration* Variable::varDeclaration() const {
+ if (!fDeclaringElement) {
+ return nullptr;
+ }
+ SkASSERT(fDeclaringElement->is<VarDeclaration>() ||
+ fDeclaringElement->is<GlobalVarDeclaration>());
+ return fDeclaringElement->is<GlobalVarDeclaration>()
+ ? &fDeclaringElement->as<GlobalVarDeclaration>().varDeclaration()
+ : &fDeclaringElement->as<VarDeclaration>();
+}
+
+GlobalVarDeclaration* Variable::globalVarDeclaration() const {
+ if (!fDeclaringElement) {
+ return nullptr;
+ }
+ SkASSERT(fDeclaringElement->is<VarDeclaration>() ||
+ fDeclaringElement->is<GlobalVarDeclaration>());
+ return fDeclaringElement->is<GlobalVarDeclaration>()
+ ? &fDeclaringElement->as<GlobalVarDeclaration>()
+ : nullptr;
+}
+
+void Variable::setVarDeclaration(VarDeclaration* declaration) {
+ SkASSERT(!fDeclaringElement || this == declaration->var());
+ if (!fDeclaringElement) {
+ fDeclaringElement = declaration;
+ }
+}
+
+void Variable::setGlobalVarDeclaration(GlobalVarDeclaration* global) {
+ SkASSERT(!fDeclaringElement || this == global->varDeclaration().var());
+ fDeclaringElement = global;
+}
+
+std::string Variable::mangledName() const {
+ // Only private variables need to use name mangling.
+ std::string_view name = this->name();
+ if (!skstd::starts_with(name, '$')) {
+ return std::string(name);
+ }
+
+ // The $ prefix will fail to compile in GLSL, so replace it with `sk_Priv`.
+ name.remove_prefix(1);
+ return "sk_Priv" + std::string(name);
+}
+
+std::unique_ptr<Variable> Variable::Convert(const Context& context,
+ Position pos,
+ Position modifiersPos,
+ const Modifiers& modifiers,
+ const Type* baseType,
+ Position namePos,
+ std::string_view name,
+ bool isArray,
+ std::unique_ptr<Expression> arraySize,
+ Variable::Storage storage) {
+ if (modifiers.fLayout.fLocation == 0 && modifiers.fLayout.fIndex == 0 &&
+ (modifiers.fFlags & Modifiers::kOut_Flag) &&
+ ProgramConfig::IsFragment(context.fConfig->fKind) && name != Compiler::FRAGCOLOR_NAME) {
+ context.fErrors->error(modifiersPos,
+ "out location=0, index=0 is reserved for sk_FragColor");
+ }
+ if (baseType->isUnsizedArray() && storage != Variable::Storage::kInterfaceBlock) {
+ context.fErrors->error(pos, "unsized arrays are not permitted here");
+ }
+ if (ProgramConfig::IsCompute(ThreadContext::Context().fConfig->fKind) &&
+ modifiers.fLayout.fBuiltin == -1) {
+ if (storage == Variable::Storage::kGlobal) {
+ if (modifiers.fFlags & Modifiers::kIn_Flag) {
+ context.fErrors->error(pos, "pipeline inputs not permitted in compute shaders");
+ } else if (modifiers.fFlags & Modifiers::kOut_Flag) {
+ context.fErrors->error(pos, "pipeline outputs not permitted in compute shaders");
+ }
+ }
+ }
+
+ return Make(context, pos, modifiersPos, modifiers, baseType, name, isArray,
+ std::move(arraySize), storage);
+}
+
+std::unique_ptr<Variable> Variable::Make(const Context& context,
+ Position pos,
+ Position modifiersPos,
+ const Modifiers& modifiers,
+ const Type* baseType,
+ std::string_view name,
+ bool isArray,
+ std::unique_ptr<Expression> arraySize,
+ Variable::Storage storage) {
+ const Type* type = baseType;
+ int arraySizeValue = 0;
+ if (isArray) {
+ SkASSERT(arraySize);
+ arraySizeValue = type->convertArraySize(context, pos, std::move(arraySize));
+ if (!arraySizeValue) {
+ return nullptr;
+ }
+ type = ThreadContext::SymbolTable()->addArrayDimension(type, arraySizeValue);
+ }
+ if (type->componentType().isInterfaceBlock()) {
+ return std::make_unique<InterfaceBlockVariable>(pos,
+ modifiersPos,
+ context.fModifiersPool->add(modifiers),
+ name,
+ type,
+ context.fConfig->fIsBuiltinCode,
+ storage);
+ } else {
+ return std::make_unique<Variable>(pos,
+ modifiersPos,
+ context.fModifiersPool->add(modifiers),
+ name,
+ type,
+ context.fConfig->fIsBuiltinCode,
+ storage);
+ }
+}
+
+Variable::ScratchVariable Variable::MakeScratchVariable(const Context& context,
+ Mangler& mangler,
+ std::string_view baseName,
+ const Type* type,
+ const Modifiers& modifiers,
+ SymbolTable* symbolTable,
+ std::unique_ptr<Expression> initialValue) {
+ // $floatLiteral or $intLiteral aren't real types that we can use for scratch variables, so
+ // replace them if they ever appear here. If this happens, we likely forgot to coerce a type
+ // somewhere during compilation.
+ if (type->isLiteral()) {
+ SkDEBUGFAIL("found a $literal type in MakeScratchVariable");
+ type = &type->scalarTypeForLiteral();
+ }
+
+ // Out-parameters aren't supported.
+ SkASSERT(!(modifiers.fFlags & Modifiers::kOut_Flag));
+
+ // Provide our new variable with a unique name, and add it to our symbol table.
+ const std::string* name =
+ symbolTable->takeOwnershipOfString(mangler.uniqueName(baseName, symbolTable));
+
+ // Create our new variable and add it to the symbol table.
+ ScratchVariable result;
+ auto var = std::make_unique<Variable>(initialValue ? initialValue->fPosition : Position(),
+ /*modifiersPosition=*/Position(),
+ context.fModifiersPool->add(Modifiers{}),
+ name->c_str(),
+ type,
+ symbolTable->isBuiltin(),
+ Variable::Storage::kLocal);
+
+ // If we are creating an array type, reduce it to base type plus array-size.
+ int arraySize = 0;
+ if (type->isArray()) {
+ arraySize = type->columns();
+ type = &type->componentType();
+ }
+ // Create our variable declaration.
+ result.fVarDecl = VarDeclaration::Make(context, var.get(), type, arraySize,
+ std::move(initialValue));
+ result.fVarSymbol = symbolTable->add(std::move(var));
+ return result;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLVariable.h b/gfx/skia/skia/src/sksl/ir/SkSLVariable.h
new file mode 100644
index 0000000000..a94292873b
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLVariable.h
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_VARIABLE
+#define SKSL_VARIABLE
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLModifiers.h"
+#include "include/private/SkSLStatement.h"
+#include "include/private/SkSLSymbol.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLType.h"
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <string_view>
+
+namespace SkSL {
+
+class Context;
+class Expression;
+class GlobalVarDeclaration;
+class InterfaceBlock;
+class Mangler;
+class SymbolTable;
+class VarDeclaration;
+
+enum class VariableStorage : int8_t {
+ kGlobal,
+ kInterfaceBlock,
+ kLocal,
+ kParameter,
+};
+
+/**
+ * Represents a variable, whether local, global, or a function parameter. This represents the
+ * variable itself (the storage location), which is shared between all VariableReferences which
+ * read or write that storage location.
+ */
+class Variable : public Symbol {
+public:
+ using Storage = VariableStorage;
+
+ inline static constexpr Kind kIRNodeKind = Kind::kVariable;
+
+ Variable(Position pos, Position modifiersPosition, const Modifiers* modifiers,
+ std::string_view name, const Type* type, bool builtin, Storage storage)
+ : INHERITED(pos, kIRNodeKind, name, type)
+ , fModifiersPosition(modifiersPosition)
+ , fModifiers(modifiers)
+ , fStorage(storage)
+ , fBuiltin(builtin) {}
+
+ ~Variable() override;
+
+ static std::unique_ptr<Variable> Convert(const Context& context, Position pos,
+ Position modifiersPos, const Modifiers& modifiers, const Type* baseType,
+ Position namePos, std::string_view name, bool isArray,
+ std::unique_ptr<Expression> arraySize, Variable::Storage storage);
+
+ static std::unique_ptr<Variable> Make(const Context& context, Position pos,
+ Position modifiersPos, const Modifiers& modifiers, const Type* baseType,
+ std::string_view name, bool isArray, std::unique_ptr<Expression> arraySize,
+ Variable::Storage storage);
+
+ /**
+ * Creates a local scratch variable and the associated VarDeclaration statement.
+ * Useful when doing IR rewrites, e.g. inlining a function call.
+ */
+ struct ScratchVariable {
+ const Variable* fVarSymbol;
+ std::unique_ptr<Statement> fVarDecl;
+ };
+ static ScratchVariable MakeScratchVariable(const Context& context,
+ Mangler& mangler,
+ std::string_view baseName,
+ const Type* type,
+ const Modifiers& modifiers,
+ SymbolTable* symbolTable,
+ std::unique_ptr<Expression> initialValue);
+ const Modifiers& modifiers() const {
+ return *fModifiers;
+ }
+
+ void setModifiers(const Modifiers* modifiers) {
+ fModifiers = modifiers;
+ }
+
+ Position modifiersPosition() const {
+ return fModifiersPosition;
+ }
+
+ bool isBuiltin() const {
+ return fBuiltin;
+ }
+
+ Storage storage() const {
+ return fStorage;
+ }
+
+ const Expression* initialValue() const;
+
+ VarDeclaration* varDeclaration() const;
+
+ void setVarDeclaration(VarDeclaration* declaration);
+
+ GlobalVarDeclaration* globalVarDeclaration() const;
+
+ void setGlobalVarDeclaration(GlobalVarDeclaration* global);
+
+ void detachDeadVarDeclaration() {
+ // The VarDeclaration is being deleted, so our reference to it has become stale.
+ fDeclaringElement = nullptr;
+ }
+
+ // The interfaceBlock methods are no-op stubs here. They have proper implementations in
+ // InterfaceBlockVariable, declared below this class, which dedicates extra space to store the
+ // pointer back to the InterfaceBlock.
+ virtual InterfaceBlock* interfaceBlock() const { return nullptr; }
+
+ virtual void setInterfaceBlock(InterfaceBlock*) { SkUNREACHABLE; }
+
+ virtual void detachDeadInterfaceBlock() {}
+
+ std::string description() const override {
+ return this->modifiers().description() + this->type().displayName() + " " +
+ std::string(this->name());
+ }
+
+ std::string mangledName() const;
+
+private:
+ IRNode* fDeclaringElement = nullptr;
+ // We don't store the position in the Modifiers object itself because they are pooled
+ Position fModifiersPosition;
+ const Modifiers* fModifiers;
+ VariableStorage fStorage;
+ bool fBuiltin;
+
+ using INHERITED = Symbol;
+};
+
+/**
+ * This represents a Variable associated with an InterfaceBlock. Mostly a normal variable, but also
+ * has an extra pointer back to the InterfaceBlock element that owns it.
+ */
+class InterfaceBlockVariable final : public Variable {
+public:
+ using Variable::Variable;
+
+ ~InterfaceBlockVariable() override;
+
+ InterfaceBlock* interfaceBlock() const override { return fInterfaceBlockElement; }
+
+ void setInterfaceBlock(InterfaceBlock* elem) override {
+ SkASSERT(!fInterfaceBlockElement);
+ fInterfaceBlockElement = elem;
+ }
+
+ void detachDeadInterfaceBlock() override {
+ // The InterfaceBlock is being deleted, so our reference to it has become stale.
+ fInterfaceBlockElement = nullptr;
+ }
+
+private:
+ InterfaceBlock* fInterfaceBlockElement = nullptr;
+
+ using INHERITED = Variable;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLVariableReference.cpp b/gfx/skia/skia/src/sksl/ir/SkSLVariableReference.cpp
new file mode 100644
index 0000000000..6eca3ddb89
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLVariableReference.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/ir/SkSLVariableReference.h"
+
+#include "src/sksl/ir/SkSLVariable.h"
+
+namespace SkSL {
+
+VariableReference::VariableReference(Position pos, const Variable* variable, RefKind refKind)
+ : INHERITED(pos, kIRNodeKind, &variable->type())
+ , fVariable(variable)
+ , fRefKind(refKind) {
+ SkASSERT(this->variable());
+}
+
+std::string VariableReference::description(OperatorPrecedence) const {
+ return std::string(this->variable()->name());
+}
+
+void VariableReference::setRefKind(RefKind refKind) {
+ fRefKind = refKind;
+}
+
+void VariableReference::setVariable(const Variable* variable) {
+ fVariable = variable;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLVariableReference.h b/gfx/skia/skia/src/sksl/ir/SkSLVariableReference.h
new file mode 100644
index 0000000000..d33c569314
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/ir/SkSLVariableReference.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_VARIABLEREFERENCE
+#define SKSL_VARIABLEREFERENCE
+
+#include "include/core/SkTypes.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/sksl/SkSLPosition.h"
+#include "src/sksl/ir/SkSLExpression.h"
+
+#include <cstdint>
+#include <memory>
+#include <string>
+
+namespace SkSL {
+
+class Variable;
+enum class OperatorPrecedence : uint8_t;
+
+enum class VariableRefKind : int8_t {
+ kRead,
+ kWrite,
+ kReadWrite,
+ // taking the address of a variable - we consider this a read & write but don't complain if
+ // the variable was not previously assigned
+ kPointer
+};
+
+/**
+ * A reference to a variable, through which it can be read or written. In the statement:
+ *
+ * x = x + 1;
+ *
+ * there is only one Variable 'x', but two VariableReferences to it.
+ */
+class VariableReference final : public Expression {
+public:
+ using RefKind = VariableRefKind;
+
+ inline static constexpr Kind kIRNodeKind = Kind::kVariableReference;
+
+ VariableReference(Position pos, const Variable* variable, RefKind refKind);
+
+ // Creates a VariableReference. There isn't much in the way of error-checking or optimization
+ // opportunities here.
+ static std::unique_ptr<Expression> Make(Position pos,
+ const Variable* variable,
+ RefKind refKind = RefKind::kRead) {
+ SkASSERT(variable);
+ return std::make_unique<VariableReference>(pos, variable, refKind);
+ }
+
+ VariableReference(const VariableReference&) = delete;
+ VariableReference& operator=(const VariableReference&) = delete;
+
+ const Variable* variable() const {
+ return fVariable;
+ }
+
+ RefKind refKind() const {
+ return fRefKind;
+ }
+
+ void setRefKind(RefKind refKind);
+ void setVariable(const Variable* variable);
+
+ std::unique_ptr<Expression> clone(Position pos) const override {
+ return std::make_unique<VariableReference>(pos, this->variable(), this->refKind());
+ }
+
+ std::string description(OperatorPrecedence) const override;
+
+private:
+ const Variable* fVariable;
+ VariableRefKind fRefKind;
+
+ using INHERITED = Expression;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/lex/DFA.h b/gfx/skia/skia/src/sksl/lex/DFA.h
new file mode 100644
index 0000000000..1fab51f921
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/lex/DFA.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_DFA
+#define SKSL_DFA
+
+#include <string>
+#include <vector>
+
+/**
+ * Tables representing a deterministic finite automaton for matching regular expressions.
+ */
+struct DFA {
+ DFA(std::vector<int> charMappings, std::vector<std::vector<int>> transitions,
+ std::vector<int> accepts)
+ : fCharMappings(charMappings)
+ , fTransitions(transitions)
+ , fAccepts(accepts) {}
+
+ // maps chars to the row index of fTransitions, as multiple characters may map to the same row.
+ // starting from state s and looking at char c, the new state is
+ // fTransitions[fCharMappings[c]][s].
+ std::vector<int> fCharMappings;
+
+ // one row per character mapping, one column per state
+ std::vector<std::vector<int>> fTransitions;
+
+ // contains, for each state, the token id we should report when matching ends in that state (-1
+ // for no match)
+ std::vector<int> fAccepts;
+};
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/lex/DFAState.h b/gfx/skia/skia/src/sksl/lex/DFAState.h
new file mode 100644
index 0000000000..a09d7ba673
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/lex/DFAState.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_DFASTATE
+#define SKSL_DFASTATE
+
+#include "src/sksl/lex/LexUtil.h"
+
+#include <vector>
+#include <string>
+
+struct DFAState {
+ struct Label {
+ std::vector<int> fStates;
+
+ Label(std::vector<int> states)
+ : fStates(std::move(states)) {}
+
+ bool operator==(const Label& other) const {
+ return fStates == other.fStates;
+ }
+
+ bool operator!=(const Label& other) const {
+ return !(*this == other);
+ }
+
+#ifdef SK_DEBUG
+ std::string description() const {
+ std::string result = "<";
+ const char* separator = "";
+ for (int s : fStates) {
+ result += separator;
+ result += std::to_string(s);
+ separator = ", ";
+ }
+ result += ">";
+ return result;
+ }
+#endif
+ };
+
+ DFAState()
+ : fId(INVALID)
+ , fLabel({}) {}
+
+ DFAState(int id, Label label)
+ : fId(id)
+ , fLabel(std::move(label)) {}
+
+ DFAState(const DFAState& other) = delete;
+
+ int fId;
+
+ Label fLabel;
+
+ bool fIsScanned = false;
+};
+
+namespace std {
+ template<> struct hash<DFAState::Label> {
+ size_t operator()(const DFAState::Label& s) const {
+ size_t result = 0;
+ for (int i : s.fStates) {
+ result = result * 101 + i;
+ }
+ return result;
+ }
+ };
+} // namespace
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/lex/LexUtil.h b/gfx/skia/skia/src/sksl/lex/LexUtil.h
new file mode 100644
index 0000000000..65692cb21b
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/lex/LexUtil.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_LEXUTIL
+#define SKSL_LEXUTIL
+
+#include <cstdlib>
+
+#define INVALID -1
+
+#define SK_ABORT(...) (fprintf(stderr, __VA_ARGS__), abort())
+#define SkASSERT(x) \
+ (void)((x) || (SK_ABORT("failed SkASSERT(%s): %s:%d\n", #x, __FILE__, __LINE__), 0))
+#define SkUNREACHABLE (SK_ABORT("unreachable"))
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/lex/Main.cpp b/gfx/skia/skia/src/sksl/lex/Main.cpp
new file mode 100644
index 0000000000..ab4e3a618b
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/lex/Main.cpp
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/lex/DFA.h"
+#include "src/sksl/lex/LexUtil.h"
+#include "src/sksl/lex/NFA.h"
+#include "src/sksl/lex/NFAtoDFA.h"
+#include "src/sksl/lex/RegexNode.h"
+#include "src/sksl/lex/RegexParser.h"
+#include "src/sksl/lex/TransitionTable.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <algorithm>
+#include <sstream>
+#include <string>
+#include <vector>
+
+/**
+ * Processes a .lex file and produces .h and .cpp files which implement a lexical analyzer. The .lex
+ * file is a text file with one token definition per line. Each line is of the form:
+ * <TOKEN_NAME> = <pattern>
+ * where <pattern> is either a regular expression (e.g [0-9]) or a double-quoted literal string.
+ */
+
+static constexpr const char HEADER[] =
+ "/*\n"
+ " * Copyright 2017 Google Inc.\n"
+ " *\n"
+ " * Use of this source code is governed by a BSD-style license that can be\n"
+ " * found in the LICENSE file.\n"
+ " */\n"
+ "/*****************************************************************************************\n"
+ " ******************** This file was generated by sksllex. Do not edit. *******************\n"
+ " *****************************************************************************************/\n";
+
+static void writeH(const DFA& dfa, const char* lexer, const char* token,
+ const std::vector<std::string>& tokens, const char* hPath) {
+ std::ofstream out(hPath);
+ SkASSERT(out.good());
+ out << HEADER;
+ out << "#ifndef SKSL_" << lexer << "\n";
+ out << "#define SKSL_" << lexer << "\n";
+ out << "#include <cstdint>\n";
+ out << "#include <string_view>\n";
+ out << "namespace SkSL {\n";
+ out << "\n";
+ out << "struct " << token << " {\n";
+ out << " enum class Kind {\n";
+ for (const std::string& t : tokens) {
+ out << " TK_" << t << ",\n";
+ }
+ out << " TK_NONE,";
+ out << R"(
+ };
+
+ )" << token << "() {}";
+
+ out << token << R"((Kind kind, int32_t offset, int32_t length)
+ : fKind(kind)
+ , fOffset(offset)
+ , fLength(length) {}
+
+ Kind fKind = Kind::TK_NONE;
+ int32_t fOffset = -1;
+ int32_t fLength = -1;
+};
+
+class )" << lexer << R"( {
+public:
+ void start(std::string_view text) {
+ fText = text;
+ fOffset = 0;
+ }
+
+ )" << token << R"( next();
+
+ struct Checkpoint {
+ int32_t fOffset;
+ };
+
+ Checkpoint getCheckpoint() const {
+ return {fOffset};
+ }
+
+ void rewindToCheckpoint(Checkpoint checkpoint) {
+ fOffset = checkpoint.fOffset;
+ }
+
+private:
+ std::string_view fText;
+ int32_t fOffset;
+};
+
+} // namespace
+#endif
+)";
+}
+
+static void writeCPP(const DFA& dfa, const char* lexer, const char* token, const char* include,
+ const char* cppPath) {
+ std::ofstream out(cppPath);
+ SkASSERT(out.good());
+ out << HEADER;
+ out << "#include \"" << include << "\"\n";
+ out << "\n";
+ out << "namespace SkSL {\n";
+ out << "\n";
+
+ size_t states = 0;
+ for (const auto& row : dfa.fTransitions) {
+ states = std::max(states, row.size());
+ }
+ out << "using State = " << (states <= 256 ? "uint8_t" : "uint16_t") << ";\n";
+
+ // Find the first character mapped in our DFA.
+ size_t startChar = 0;
+ for (; startChar < dfa.fCharMappings.size(); ++startChar) {
+ if (dfa.fCharMappings[startChar] != 0) {
+ break;
+ }
+ }
+
+ // Arbitrarily-chosen character which is greater than startChar, and should not appear in actual
+ // input.
+ SkASSERT(startChar < 18);
+ out << "static constexpr uint8_t kInvalidChar = 18;";
+ out << "static constexpr int8_t kMappings[" << dfa.fCharMappings.size() - startChar << "] = {\n"
+ " ";
+ const char* separator = "";
+ for (size_t index = startChar; index < dfa.fCharMappings.size(); ++index) {
+ out << separator << std::to_string(dfa.fCharMappings[index]);
+ separator = ", ";
+ }
+ out << "\n};\n";
+
+ WriteTransitionTable(out, dfa, states);
+
+ out << "static const int8_t kAccepts[" << states << "] = {";
+ for (size_t i = 0; i < states; ++i) {
+ if (i < dfa.fAccepts.size()) {
+ out << " " << dfa.fAccepts[i] << ",";
+ } else {
+ out << " " << INVALID << ",";
+ }
+ }
+ out << " };\n";
+ out << "\n";
+
+ out << token << " " << lexer << "::next() {";
+ out << R"(
+ // note that we cheat here: normally a lexer needs to worry about the case
+ // where a token has a prefix which is not itself a valid token - for instance,
+ // maybe we have a valid token 'while', but 'w', 'wh', etc. are not valid
+ // tokens. Our grammar doesn't have this property, so we can simplify the logic
+ // a bit.
+ int32_t startOffset = fOffset;
+ State state = 1;
+ for (;;) {
+ if (fOffset >= (int32_t)fText.length()) {
+ if (startOffset == (int32_t)fText.length() || kAccepts[state] == -1) {
+ return )" << token << "(" << token << R"(::Kind::TK_END_OF_FILE, startOffset, 0);
+ }
+ break;
+ }
+ uint8_t c = (uint8_t)(fText[fOffset] - )" << startChar << R"();
+ if (c >= )" << dfa.fCharMappings.size() - startChar << R"() {
+ c = kInvalidChar;
+ }
+ State newState = get_transition(kMappings[c], state);
+ if (!newState) {
+ break;
+ }
+ state = newState;
+ ++fOffset;
+ }
+ Token::Kind kind = ()" << token << R"(::Kind) kAccepts[state];
+ return )" << token << R"((kind, startOffset, fOffset - startOffset);
+}
+
+} // namespace
+)";
+}
+
+static void process(const char* inPath, const char* lexer, const char* token, const char* hPath,
+ const char* cppPath) {
+ NFA nfa;
+ std::vector<std::string> tokens;
+ tokens.push_back("END_OF_FILE");
+ std::string line;
+ std::ifstream in(inPath);
+ while (std::getline(in, line)) {
+ if (line.length() == 0) {
+ continue;
+ }
+ if (line.length() >= 2 && line[0] == '/' && line[1] == '/') {
+ continue;
+ }
+ std::istringstream split(line);
+ std::string name, delimiter, pattern;
+ if (split >> name >> delimiter >> pattern) {
+ SkASSERT(split.eof());
+ SkASSERT(name != "");
+ SkASSERT(delimiter == "=");
+ SkASSERT(pattern != "");
+ tokens.push_back(name);
+ if (pattern[0] == '"') {
+ SkASSERT(pattern.size() > 2 && pattern[pattern.size() - 1] == '"');
+ RegexNode node = RegexNode(RegexNode::kChar_Kind, pattern[1]);
+ for (size_t i = 2; i < pattern.size() - 1; ++i) {
+ node = RegexNode(RegexNode::kConcat_Kind, node,
+ RegexNode(RegexNode::kChar_Kind, pattern[i]));
+ }
+ nfa.addRegex(node);
+ }
+ else {
+ nfa.addRegex(RegexParser().parse(pattern));
+ }
+ }
+ }
+ NFAtoDFA converter(&nfa);
+ DFA dfa = converter.convert();
+ writeH(dfa, lexer, token, tokens, hPath);
+ writeCPP(dfa, lexer, token, (std::string("src/sksl/SkSL") + lexer + ".h").c_str(), cppPath);
+}
+
+int main(int argc, const char** argv) {
+ if (argc != 6) {
+ printf("usage: sksllex <input.lex> <lexername> <tokenname> <output.h> <output.cpp>\n");
+ exit(1);
+ }
+ process(argv[1], argv[2], argv[3], argv[4], argv[5]);
+ return 0;
+}
diff --git a/gfx/skia/skia/src/sksl/lex/NFA.cpp b/gfx/skia/skia/src/sksl/lex/NFA.cpp
new file mode 100644
index 0000000000..e73fc154d7
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/lex/NFA.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/lex/NFA.h"
+
+#include "src/sksl/lex/LexUtil.h"
+#include <string>
+
+int NFA::match(std::string s) const {
+ std::vector<int> states = fStartStates;
+ for (size_t i = 0; i < s.size(); ++i) {
+ std::vector<int> next;
+ for (int id : states) {
+ if (fStates[id].accept(s[i])) {
+ for (int nextId : fStates[id].fNext) {
+ if (fStates[nextId].fKind != NFAState::kRemapped_Kind) {
+ next.push_back(nextId);
+ } else {
+ next.insert(next.end(), fStates[nextId].fData.begin(),
+ fStates[nextId].fData.end());
+ }
+ }
+ }
+ }
+ if (!next.size()) {
+ return INVALID;
+ }
+ states = next;
+ }
+ int accept = INVALID;
+ for (int id : states) {
+ if (fStates[id].fKind == NFAState::kAccept_Kind) {
+ int result = fStates[id].fData[0];
+ if (accept == INVALID || result < accept) {
+ accept = result;
+ }
+ }
+ }
+ return accept;
+}
diff --git a/gfx/skia/skia/src/sksl/lex/NFA.h b/gfx/skia/skia/src/sksl/lex/NFA.h
new file mode 100644
index 0000000000..368fb3ec19
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/lex/NFA.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_NFA
+#define SKSL_NFA
+
+#include "src/sksl/lex/NFAState.h"
+#include "src/sksl/lex/RegexNode.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+/**
+ * A nondeterministic finite automaton for matching regular expressions. The NFA is initialized with
+ * a number of regular expressions, and then matches a string against all of them simultaneously.
+ */
+struct NFA {
+ /**
+ * Adds a new regular expression to the set of expressions matched by this automaton, returning
+ * its index.
+ */
+ int addRegex(const RegexNode& regex) {
+ std::vector<int> accept;
+ // we reserve token 0 for END_OF_FILE, so this starts at 1
+ accept.push_back(this->addState(NFAState(++fRegexCount)));
+ std::vector<int> startStates = regex.createStates(this, accept);
+ fStartStates.insert(fStartStates.end(), startStates.begin(), startStates.end());
+ return fStartStates.size() - 1;
+ }
+
+ /**
+ * Adds a new state to the NFA, returning its index.
+ */
+ int addState(NFAState s) {
+ fStates.push_back(std::move(s));
+ return fStates.size() - 1;
+ }
+
+ /**
+ * Matches a string against all of the regexes added to this NFA. Returns the index of the first
+ * (in addRegex order) matching expression, or -1 if no match. This is relatively slow and used
+ * only for debugging purposes; the NFA should be converted to a DFA before actual use.
+ */
+ int match(std::string s) const;
+
+ int fRegexCount = 0;
+
+ std::vector<NFAState> fStates;
+
+ std::vector<int> fStartStates;
+};
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/lex/NFAState.h b/gfx/skia/skia/src/sksl/lex/NFAState.h
new file mode 100644
index 0000000000..848a6f11ee
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/lex/NFAState.h
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_NFASTATE
+#define SKSL_NFASTATE
+
+#include <string>
+#include <vector>
+
+#include "src/sksl/lex/LexUtil.h"
+
+struct NFAState {
+ enum Kind {
+ // represents an accept state - if the NFA ends up in this state, we have successfully
+ // matched the token indicated by fData[0]
+ kAccept_Kind,
+ // matches the single character fChar
+ kChar_Kind,
+ // the regex '.'; matches any char but '\n'
+ kDot_Kind,
+ // a state which serves as a placeholder for the states indicated in fData. When we
+ // transition to this state, we instead transition to all of the fData states.
+ kRemapped_Kind,
+ // contains a list of true/false values in fData. fData[c] tells us whether we accept the
+ // character c.
+ kTable_Kind
+ };
+
+ NFAState(Kind kind, std::vector<int> next)
+ : fKind(kind)
+ , fNext(std::move(next)) {}
+
+ NFAState(char c, std::vector<int> next)
+ : fKind(kChar_Kind)
+ , fChar(c)
+ , fNext(std::move(next)) {}
+
+ NFAState(std::vector<int> states)
+ : fKind(kRemapped_Kind)
+ , fData(std::move(states)) {}
+
+ NFAState(bool inverse, std::vector<bool> accepts, std::vector<int> next)
+ : fKind(kTable_Kind)
+ , fInverse(inverse)
+ , fNext(std::move(next)) {
+ for (bool b : accepts) {
+ fData.push_back(b);
+ }
+ }
+
+ NFAState(int token)
+ : fKind(kAccept_Kind) {
+ fData.push_back(token);
+ }
+
+ bool accept(char c) const {
+ switch (fKind) {
+ case kAccept_Kind:
+ return false;
+ case kChar_Kind:
+ return c == fChar;
+ case kDot_Kind:
+ return c != '\n';
+ case kTable_Kind: {
+ bool value;
+ if ((size_t) c < fData.size()) {
+ value = fData[c];
+ } else {
+ value = false;
+ }
+ return value != fInverse;
+ }
+ default:
+ SkUNREACHABLE;
+ }
+ }
+
+#ifdef SK_DEBUG
+ std::string description() const {
+ switch (fKind) {
+ case kAccept_Kind:
+ return "Accept(" + std::to_string(fData[0]) + ")";
+ case kChar_Kind: {
+ std::string result = "Char('" + std::string(1, fChar) + "'";
+ for (int v : fNext) {
+ result += ", ";
+ result += std::to_string(v);
+ }
+ result += ")";
+ return result;
+ }
+ case kDot_Kind: {
+ std::string result = "Dot(";
+ const char* separator = "";
+ for (int v : fNext) {
+ result += separator;
+ result += std::to_string(v);
+ separator = ", ";
+ }
+ result += ")";
+ return result;
+ }
+ case kRemapped_Kind: {
+ std::string result = "Remapped(";
+ const char* separator = "";
+ for (int v : fData) {
+ result += separator;
+ result += std::to_string(v);
+ separator = ", ";
+ }
+ result += ")";
+ return result;
+ }
+ case kTable_Kind: {
+ std::string result = std::string("Table(") + (fInverse ? "true" : "false") + ", [";
+ const char* separator = "";
+ for (int v : fData) {
+ result += separator;
+ result += v ? "true" : "false";
+ separator = ", ";
+ }
+ result += "]";
+ for (int n : fNext) {
+ result += ", ";
+ result += std::to_string(n);
+ }
+ result += ")";
+ return result;
+ }
+ default:
+ SkUNREACHABLE;
+ }
+ }
+#endif
+
+ Kind fKind;
+
+ char fChar = 0;
+
+ bool fInverse = false;
+
+ std::vector<int> fData;
+
+ // states we transition to upon a succesful match from this state
+ std::vector<int> fNext;
+};
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/lex/NFAtoDFA.h b/gfx/skia/skia/src/sksl/lex/NFAtoDFA.h
new file mode 100644
index 0000000000..5331042985
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/lex/NFAtoDFA.h
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef NFAtoDFA_DEFINED
+#define NFAtoDFA_DEFINED
+
+#include "src/sksl/lex/DFA.h"
+#include "src/sksl/lex/DFAState.h"
+#include "src/sksl/lex/NFA.h"
+#include "src/sksl/lex/NFAState.h"
+
+#include <algorithm>
+#include <climits>
+#include <memory>
+#include <unordered_map>
+#include <set>
+#include <vector>
+
+/**
+ * Converts a nondeterministic finite automaton to a deterministic finite automaton. Since NFAs and
+ * DFAs differ only in that an NFA allows multiple states at the same time, we can find each
+ * possible combination of simultaneous NFA states and give this combination a label. These labelled
+ * nodes are our DFA nodes, since we can only be in one such unique set of NFA states at a time.
+ *
+ * As an NFA can end up in multiple accept states at the same time (for instance, the token "while"
+ * is valid for both WHILE and IDENTIFIER), we disambiguate by preferring the first matching regex
+ * (in terms of the order in which they were added to the NFA).
+ */
+class NFAtoDFA {
+public:
+ inline static constexpr char START_CHAR = 9;
+ inline static constexpr char END_CHAR = 126;
+
+ NFAtoDFA(NFA* nfa)
+ : fNFA(*nfa) {}
+
+ /**
+ * Returns a DFA created from the NFA.
+ */
+ DFA convert() {
+ // create state 0, the "reject" state
+ getState(DFAState::Label({}));
+ // create a state representing being in all of the NFA's start states at once
+ std::vector<int> startStates = fNFA.fStartStates;
+ std::sort(startStates.begin(), startStates.end());
+ // this becomes state 1, our start state
+ DFAState* start = getState(DFAState::Label(startStates));
+ this->scanState(start);
+
+ this->computeMappings();
+
+ int stateCount = 0;
+ for (const auto& row : fTransitions) {
+ stateCount = std::max(stateCount, (int) row.size());
+ }
+ return DFA(fCharMappings, fTransitions, fAccepts);
+ }
+
+private:
+ /**
+ * Returns an existing state with the given label, or creates a new one and returns it.
+ */
+ DFAState* getState(DFAState::Label label) {
+ auto found = fStates.find(label);
+ if (found == fStates.end()) {
+ int id = fStates.size();
+ fStates[label] = std::unique_ptr<DFAState>(new DFAState(id, label));
+ return fStates[label].get();
+ }
+ return found->second.get();
+ }
+
+ void add(int nfaState, std::vector<int>* states) {
+ NFAState state = fNFA.fStates[nfaState];
+ if (state.fKind == NFAState::kRemapped_Kind) {
+ for (int next : state.fData) {
+ this->add(next, states);
+ }
+ } else {
+ for (int entry : *states) {
+ if (nfaState == entry) {
+ return;
+ }
+ }
+ states->push_back(nfaState);
+ }
+ }
+
+ void addTransition(char c, int start, int next) {
+ while (fTransitions.size() <= (size_t) c) {
+ fTransitions.push_back(std::vector<int>());
+ }
+ std::vector<int>& row = fTransitions[c];
+ while (row.size() <= (size_t) start) {
+ row.push_back(INVALID);
+ }
+ row[start] = next;
+ }
+
+ void scanState(DFAState* state) {
+ state->fIsScanned = true;
+ for (char c = START_CHAR; c <= END_CHAR; ++c) {
+ std::vector<int> next;
+ int bestAccept = INT_MAX;
+ for (int idx : state->fLabel.fStates) {
+ const NFAState& nfaState = fNFA.fStates[idx];
+ if (nfaState.accept(c)) {
+ for (int nextState : nfaState.fNext) {
+ if (fNFA.fStates[nextState].fKind == NFAState::kAccept_Kind) {
+ bestAccept = std::min(bestAccept, fNFA.fStates[nextState].fData[0]);
+ }
+ this->add(nextState, &next);
+ }
+ }
+ }
+ std::sort(next.begin(), next.end());
+ DFAState* nextState = this->getState(DFAState::Label(next));
+ this->addTransition(c, state->fId, nextState->fId);
+ if (bestAccept != INT_MAX) {
+ while (fAccepts.size() <= (size_t) nextState->fId) {
+ fAccepts.push_back(INVALID);
+ }
+ fAccepts[nextState->fId] = bestAccept;
+ }
+ if (!nextState->fIsScanned) {
+ this->scanState(nextState);
+ }
+ }
+ }
+
+ // collapse rows with the same transitions to a single row. This is common, as each row
+ // represents a character and often there are many characters for which all transitions are
+ // identical (e.g. [0-9] are treated the same way by all lexer rules)
+ void computeMappings() {
+ // mappings[<input row>] = <output row>
+ std::vector<std::vector<int>*> uniques;
+ // this could be done more efficiently, but O(n^2) is plenty fast for our purposes
+ for (size_t i = 0; i < fTransitions.size(); ++i) {
+ int found = -1;
+ for (size_t j = 0; j < uniques.size(); ++j) {
+ if (*uniques[j] == fTransitions[i]) {
+ found = j;
+ break;
+ }
+ }
+ if (found == -1) {
+ found = (int) uniques.size();
+ uniques.push_back(&fTransitions[i]);
+ }
+ fCharMappings.push_back(found);
+ }
+ std::vector<std::vector<int>> newTransitions;
+ for (std::vector<int>* row : uniques) {
+ newTransitions.push_back(*row);
+ }
+ fTransitions = newTransitions;
+ }
+
+ const NFA& fNFA;
+ std::unordered_map<DFAState::Label, std::unique_ptr<DFAState>> fStates;
+ std::vector<std::vector<int>> fTransitions;
+ std::vector<int> fCharMappings;
+ std::vector<int> fAccepts;
+};
+#endif // NFAtoDFA_DEFINED
diff --git a/gfx/skia/skia/src/sksl/lex/RegexNode.cpp b/gfx/skia/skia/src/sksl/lex/RegexNode.cpp
new file mode 100644
index 0000000000..aa4e23a8b9
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/lex/RegexNode.cpp
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/lex/RegexNode.h"
+
+#include "src/sksl/lex/LexUtil.h"
+#include "src/sksl/lex/NFA.h"
+#include "src/sksl/lex/NFAState.h"
+
+#include <string>
+
+std::vector<int> RegexNode::createStates(NFA* nfa, const std::vector<int>& accept) const {
+ std::vector<int> result;
+ switch (fKind) {
+ case kChar_Kind:
+ result.push_back(nfa->addState(NFAState(fPayload.fChar, accept)));
+ break;
+ case kCharset_Kind: {
+ std::vector<bool> chars;
+ for (const RegexNode& child : fChildren) {
+ if (child.fKind == kChar_Kind) {
+ while (chars.size() <= (size_t) child.fPayload.fChar) {
+ chars.push_back(false);
+ }
+ chars[child.fPayload.fChar] = true;
+ } else {
+ SkASSERT(child.fKind == kRange_Kind);
+ while (chars.size() <= (size_t) child.fChildren[1].fPayload.fChar) {
+ chars.push_back(false);
+ }
+ for (char c = child.fChildren[0].fPayload.fChar;
+ c <= child.fChildren[1].fPayload.fChar;
+ ++c) {
+ chars[c] = true;
+ }
+ }
+ }
+ result.push_back(nfa->addState(NFAState(fPayload.fBool, chars, accept)));
+ break;
+ }
+ case kConcat_Kind: {
+ std::vector<int> right = fChildren[1].createStates(nfa, accept);
+ result = fChildren[0].createStates(nfa, right);
+ break;
+ }
+ case kDot_Kind:
+ result.push_back(nfa->addState(NFAState(NFAState::kDot_Kind, accept)));
+ break;
+ case kOr_Kind: {
+ std::vector<int> states = fChildren[0].createStates(nfa, accept);
+ result.insert(result.end(), states.begin(), states.end());
+ states = fChildren[1].createStates(nfa, accept);
+ result.insert(result.end(), states.begin(), states.end());
+ break;
+ }
+ case kPlus_Kind: {
+ std::vector<int> next = accept;
+ std::vector<int> placeholder;
+ int id = nfa->addState(NFAState(placeholder));
+ next.push_back(id);
+ result = fChildren[0].createStates(nfa, next);
+ nfa->fStates[id] = NFAState(result);
+ break;
+ }
+ case kQuestion_Kind:
+ result = fChildren[0].createStates(nfa, accept);
+ result.insert(result.end(), accept.begin(), accept.end());
+ break;
+ case kRange_Kind:
+ SkUNREACHABLE;
+ case kStar_Kind: {
+ std::vector<int> next = accept;
+ std::vector<int> placeholder;
+ int id = nfa->addState(NFAState(placeholder));
+ next.push_back(id);
+ result = fChildren[0].createStates(nfa, next);
+ result.insert(result.end(), accept.begin(), accept.end());
+ nfa->fStates[id] = NFAState(result);
+ break;
+ }
+ }
+ return result;
+}
+
+#ifdef SK_DEBUG
+std::string RegexNode::description() const {
+ switch (fKind) {
+ case kChar_Kind:
+ return std::string(1, fPayload.fChar);
+ case kCharset_Kind: {
+ std::string result("[");
+ if (fPayload.fBool) {
+ result += "^";
+ }
+ for (const RegexNode& c : fChildren) {
+ result += c.description();
+ }
+ result += "]";
+ return result;
+ }
+ case kConcat_Kind:
+ return fChildren[0].description() + fChildren[1].description();
+ case kDot_Kind:
+ return ".";
+ case kOr_Kind:
+ return "(" + fChildren[0].description() + "|" + fChildren[1].description() + ")";
+ case kPlus_Kind:
+ return fChildren[0].description() + "+";
+ case kQuestion_Kind:
+ return fChildren[0].description() + "?";
+ case kRange_Kind:
+ return fChildren[0].description() + "-" + fChildren[1].description();
+ case kStar_Kind:
+ return fChildren[0].description() + "*";
+ default:
+ return "<" + std::to_string(fKind) + ">";
+ }
+}
+#endif
diff --git a/gfx/skia/skia/src/sksl/lex/RegexNode.h b/gfx/skia/skia/src/sksl/lex/RegexNode.h
new file mode 100644
index 0000000000..20c294744e
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/lex/RegexNode.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_REGEXNODE
+#define SKSL_REGEXNODE
+
+#include <string>
+#include <utility>
+#include <vector>
+
+struct NFA;
+
+/**
+ * Represents a node in the parse tree of a regular expression.
+ */
+struct RegexNode {
+ enum Kind {
+ kChar_Kind,
+ kCharset_Kind,
+ kConcat_Kind,
+ kDot_Kind,
+ kOr_Kind,
+ kPlus_Kind,
+ kRange_Kind,
+ kQuestion_Kind,
+ kStar_Kind
+ };
+
+ RegexNode(Kind kind)
+ : fKind(kind) {}
+
+ RegexNode(Kind kind, char payload)
+ : fKind(kind) {
+ fPayload.fChar = payload;
+ }
+
+ RegexNode(Kind kind, const char* children)
+ : fKind(kind) {
+ fPayload.fBool = false;
+ while (*children != '\0') {
+ fChildren.emplace_back(kChar_Kind, *children);
+ ++children;
+ }
+ }
+
+ RegexNode(Kind kind, RegexNode child)
+ : fKind(kind) {
+ fChildren.push_back(std::move(child));
+ }
+
+ RegexNode(Kind kind, RegexNode child1, RegexNode child2)
+ : fKind(kind) {
+ fChildren.push_back(std::move(child1));
+ fChildren.push_back(std::move(child2));
+ }
+
+ /**
+ * Creates NFA states for this node, with a successful match against this node resulting in a
+ * transition to all of the states in the accept vector.
+ */
+ std::vector<int> createStates(NFA* nfa, const std::vector<int>& accept) const;
+
+ std::string description() const;
+
+ Kind fKind;
+
+ union Payload {
+ char fChar;
+ bool fBool;
+ } fPayload;
+
+ std::vector<RegexNode> fChildren;
+};
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/lex/RegexParser.cpp b/gfx/skia/skia/src/sksl/lex/RegexParser.cpp
new file mode 100644
index 0000000000..27c499e66b
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/lex/RegexParser.cpp
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/lex/RegexParser.h"
+
+#include "src/sksl/lex/LexUtil.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <utility>
+#include <vector>
+
+RegexNode RegexParser::parse(std::string source) {
+ fSource = source;
+ fIndex = 0;
+ SkASSERT(fStack.size() == 0);
+ this->regex();
+ SkASSERT(fStack.size() == 1);
+ SkASSERT(fIndex == source.size());
+ return this->pop();
+}
+
+char RegexParser::peek() {
+ if (fIndex >= fSource.size()) {
+ return END;
+ }
+ return fSource[fIndex];
+}
+
+void RegexParser::expect(char c) {
+ if (this->peek() != c) {
+ printf("expected '%c' at index %d, but found '%c'", c, (int) fIndex, this->peek());
+ exit(1);
+ }
+ ++fIndex;
+}
+
+RegexNode RegexParser::pop() {
+ RegexNode result = fStack.top();
+ fStack.pop();
+ return result;
+}
+
+void RegexParser::term() {
+ switch (this->peek()) {
+ case '(': this->group(); break;
+ case '[': this->set(); break;
+ case '.': this->dot(); break;
+ default: this->literal(); break;
+ }
+}
+
+void RegexParser::quantifiedTerm() {
+ this->term();
+ switch (this->peek()) {
+ case '*': fStack.push(RegexNode(RegexNode::kStar_Kind, this->pop())); ++fIndex; break;
+ case '+': fStack.push(RegexNode(RegexNode::kPlus_Kind, this->pop())); ++fIndex; break;
+ case '?': fStack.push(RegexNode(RegexNode::kQuestion_Kind, this->pop())); ++fIndex; break;
+ default: break;
+ }
+}
+
+void RegexParser::sequence() {
+ this->quantifiedTerm();
+ for (;;) {
+ switch (this->peek()) {
+ case END: [[fallthrough]];
+ case '|': [[fallthrough]];
+ case ')': return;
+ default:
+ this->sequence();
+ RegexNode right = this->pop();
+ RegexNode left = this->pop();
+ fStack.emplace(RegexNode::kConcat_Kind, std::move(left), std::move(right));
+ break;
+ }
+ }
+}
+
+RegexNode RegexParser::escapeSequence(char c) {
+ switch (c) {
+ case 'n': return RegexNode(RegexNode::kChar_Kind, '\n');
+ case 'r': return RegexNode(RegexNode::kChar_Kind, '\r');
+ case 't': return RegexNode(RegexNode::kChar_Kind, '\t');
+ case 's': return RegexNode(RegexNode::kCharset_Kind, " \t\n\r");
+ default: return RegexNode(RegexNode::kChar_Kind, c);
+ }
+}
+
+void RegexParser::literal() {
+ char c = this->peek();
+ if (c == '\\') {
+ ++fIndex;
+ fStack.push(this->escapeSequence(peek()));
+ ++fIndex;
+ }
+ else {
+ fStack.push(RegexNode(RegexNode::kChar_Kind, c));
+ ++fIndex;
+ }
+}
+
+void RegexParser::dot() {
+ this->expect('.');
+ fStack.push(RegexNode(RegexNode::kDot_Kind));
+}
+
+void RegexParser::group() {
+ this->expect('(');
+ this->regex();
+ this->expect(')');
+}
+
+void RegexParser::setItem() {
+ this->literal();
+ if (this->peek() == '-') {
+ ++fIndex;
+ if (peek() == ']') {
+ fStack.push(RegexNode(RegexNode::kChar_Kind, '-'));
+ }
+ else {
+ literal();
+ RegexNode end = this->pop();
+ SkASSERT(end.fKind == RegexNode::kChar_Kind);
+ RegexNode start = this->pop();
+ SkASSERT(start.fKind == RegexNode::kChar_Kind);
+ fStack.push(RegexNode(RegexNode::kRange_Kind, std::move(start), std::move(end)));
+ }
+ }
+}
+
+void RegexParser::set() {
+ expect('[');
+ size_t depth = fStack.size();
+ RegexNode set(RegexNode::kCharset_Kind);
+ if (this->peek() == '^') {
+ ++fIndex;
+ set.fPayload.fBool = true;
+ }
+ else {
+ set.fPayload.fBool = false;
+ }
+ for (;;) {
+ switch (this->peek()) {
+ case ']':
+ ++fIndex;
+ while (fStack.size() > depth) {
+ set.fChildren.push_back(this->pop());
+ }
+ fStack.push(std::move(set));
+ return;
+ case END:
+ printf("unterminated character set\n");
+ exit(1);
+ default:
+ this->setItem();
+ break;
+ }
+ }
+}
+
+void RegexParser::regex() {
+ this->sequence();
+ switch (this->peek()) {
+ case '|': {
+ ++fIndex;
+ this->regex();
+ RegexNode right = this->pop();
+ RegexNode left = this->pop();
+ fStack.push(RegexNode(RegexNode::kOr_Kind, left, right));
+ break;
+ }
+ case END: // fall through
+ case ')':
+ return;
+ default:
+ SkASSERT(false);
+ }
+}
diff --git a/gfx/skia/skia/src/sksl/lex/RegexParser.h b/gfx/skia/skia/src/sksl/lex/RegexParser.h
new file mode 100644
index 0000000000..b8f4f1ffb4
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/lex/RegexParser.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_REGEXPARSER
+#define SKSL_REGEXPARSER
+
+#include "src/sksl/lex/RegexNode.h"
+
+#include <stack>
+#include <string>
+
+/**
+ * Turns a simple regular expression into a parse tree. The regular expression syntax supports only
+ * the basic quantifiers ('*', '+', and '?'), alternation ('|'), character sets ('[a-z]'), and
+ * groups ('()').
+ */
+class RegexParser {
+public:
+ RegexNode parse(std::string source);
+
+private:
+ inline static constexpr char END = '\0';
+
+ char peek();
+
+ void expect(char c);
+
+ RegexNode pop();
+
+ /**
+ * Matches a char literal, parenthesized group, character set, or dot ('.').
+ */
+ void term();
+
+ /**
+ * Matches a term followed by an optional quantifier ('*', '+', or '?').
+ */
+ void quantifiedTerm();
+
+ /**
+ * Matches a sequence of quantifiedTerms.
+ */
+ void sequence();
+
+ /**
+ * Returns a node representing the given escape character (e.g. escapeSequence('n') returns a
+ * node which matches a newline character).
+ */
+ RegexNode escapeSequence(char c);
+
+ /**
+ * Matches a literal character or escape sequence.
+ */
+ void literal();
+
+ /**
+ * Matches a dot ('.').
+ */
+ void dot();
+
+ /**
+ * Matches a parenthesized group.
+ */
+ void group();
+
+ /**
+ * Matches a literal character, escape sequence, or character range from a character set.
+ */
+ void setItem();
+
+ /**
+ * Matches a character set.
+ */
+ void set();
+
+ void regex();
+
+ std::string fSource;
+
+ size_t fIndex;
+
+ std::stack<RegexNode> fStack;
+};
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/lex/TransitionTable.cpp b/gfx/skia/skia/src/sksl/lex/TransitionTable.cpp
new file mode 100644
index 0000000000..83720fca1c
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/lex/TransitionTable.cpp
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2021 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/lex/DFA.h"
+#include "src/sksl/lex/TransitionTable.h"
+
+#include <array>
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+namespace {
+
+// The number of bits to use per entry in our compact transition table. This is customizable:
+// - 1-bit: reasonable in theory. Doesn't actually pack many slices.
+// - 2-bit: best fit for our data. Packs extremely well.
+// - 4-bit: packs all but one slice, but doesn't save as much space overall.
+// - 8-bit: way too large (an 8-bit LUT plus an 8-bit data table is as big as a 16-bit table)
+// Other values don't divide cleanly into a byte and do not work.
+constexpr int kNumBits = 2;
+
+// These values are derived from kNumBits and shouldn't need to change.
+constexpr int kNumValues = (1 << kNumBits) - 1;
+constexpr int kDataPerByte = 8 / kNumBits;
+
+enum IndexType {
+ kCompactEntry = 0,
+ kFullEntry,
+};
+struct IndexEntry {
+ IndexType type;
+ int pos;
+};
+struct CompactEntry {
+ std::array<int, kNumValues> v = {};
+ std::vector<int> data;
+};
+struct FullEntry {
+ std::vector<int> data;
+};
+
+using TransitionSet = std::unordered_set<int>;
+
+static int add_compact_entry(const TransitionSet& transitionSet,
+ const std::vector<int>& data,
+ std::vector<CompactEntry>* entries) {
+ // Create a compact entry with the unique values from the transition set, padded out with zeros
+ // and sorted.
+ CompactEntry result{};
+ assert(transitionSet.size() <= result.v.size());
+ std::copy(transitionSet.begin(), transitionSet.end(), result.v.begin());
+ std::sort(result.v.rbegin(), result.v.rend());
+
+ // Create a mapping from real values to small values.
+ std::unordered_map<int, int> translationTable;
+ for (size_t index = 0; index < result.v.size(); ++index) {
+ translationTable[result.v[index]] = index;
+ }
+ translationTable[0] = result.v.size();
+
+ // Convert the real values into small values.
+ for (size_t index = 0; index < data.size(); ++index) {
+ int value = data[index];
+ assert(translationTable.find(value) != translationTable.end());
+ result.data.push_back(translationTable[value]);
+ }
+
+ // Look for an existing entry that exactly matches this one.
+ for (size_t index = 0; index < entries->size(); ++index) {
+ if (entries->at(index).v == result.v && entries->at(index).data == result.data) {
+ return index;
+ }
+ }
+
+ // Add this as a new entry.
+ entries->push_back(std::move(result));
+ return (int)(entries->size() - 1);
+}
+
+static int add_full_entry(const TransitionSet& transitionMap,
+ const std::vector<int>& data,
+ std::vector<FullEntry>* entries) {
+ // Create a full entry with this data.
+ FullEntry result{};
+ result.data = std::vector<int>(data.begin(), data.end());
+
+ // Look for an existing entry that exactly matches this one.
+ for (size_t index = 0; index < entries->size(); ++index) {
+ if (entries->at(index).data == result.data) {
+ return index;
+ }
+ }
+
+ // Add this as a new entry.
+ entries->push_back(std::move(result));
+ return (int)(entries->size() - 1);
+}
+
+} // namespace
+
+void WriteTransitionTable(std::ofstream& out, const DFA& dfa, size_t states) {
+ int numTransitions = dfa.fTransitions.size();
+
+ // Assemble our compact and full data tables, and an index into them.
+ std::vector<CompactEntry> compactEntries;
+ std::vector<FullEntry> fullEntries;
+ std::vector<IndexEntry> indices;
+ for (size_t s = 0; s < states; ++s) {
+ // Copy all the transitions for this state into a flat array, and into a histogram (counting
+ // the number of unique state-transition values). Most states only transition to a few
+ // possible new states.
+ TransitionSet transitionSet;
+ std::vector<int> data(numTransitions);
+ for (int t = 0; t < numTransitions; ++t) {
+ if ((size_t) t < dfa.fTransitions.size() && s < dfa.fTransitions[t].size()) {
+ int value = dfa.fTransitions[t][s];
+ assert(value >= 0 && value < (int)states);
+ data[t] = value;
+ transitionSet.insert(value);
+ }
+ }
+
+ transitionSet.erase(0);
+ if (transitionSet.size() <= kNumValues) {
+ // This table only contained a small number of unique nonzero values.
+ // Use a compact representation that squishes each value down to a few bits.
+ int index = add_compact_entry(transitionSet, data, &compactEntries);
+ indices.push_back(IndexEntry{kCompactEntry, index});
+ } else {
+ // This table contained a large number of values. We can't compact it.
+ int index = add_full_entry(transitionSet, data, &fullEntries);
+ indices.push_back(IndexEntry{kFullEntry, index});
+ }
+ }
+
+ // Find the largest value for each compact-entry slot.
+ int maxValue = 0;
+ for (const CompactEntry& entry : compactEntries) {
+ for (int index=0; index < kNumValues; ++index) {
+ maxValue = std::max(maxValue, entry.v[index]);
+ }
+ }
+
+ // Figure out how many bits we need to store our max value.
+ int bitsPerValue = std::ceil(std::log2(maxValue));
+ maxValue = (1 << bitsPerValue) - 1;
+
+ // If we exceed 10 bits per value, three values would overflow 32 bits. If this happens, we'll
+ // need to pack our values another way.
+ assert(bitsPerValue <= 10);
+
+ // Emit all the structs our transition table will use.
+ out << "using IndexEntry = int16_t;\n"
+ << "struct FullEntry {\n"
+ << " State data[" << numTransitions << "];\n"
+ << "};\n";
+
+ // Emit the compact-entry structure. We store all three values in `v`. If kNumBits were to
+ // change, we would need to adjust the packing algorithm.
+ static_assert(kNumBits == 2);
+ out << "struct CompactEntry {\n"
+ << " uint32_t values;\n"
+ << " uint8_t data[" << std::ceil(float(numTransitions) / float(kDataPerByte)) << "];\n"
+ << "};\n";
+
+ // Emit the full-table data.
+ out << "static constexpr FullEntry kFull[] = {\n";
+ for (const FullEntry& entry : fullEntries) {
+ out << " {";
+ for (int value : entry.data) {
+ out << value << ", ";
+ }
+ out << "},\n";
+ }
+ out << "};\n";
+
+ // Emit the compact-table data.
+ out << "static constexpr CompactEntry kCompact[] = {\n";
+ for (const CompactEntry& entry : compactEntries) {
+ out << " {";
+
+ // We pack all three values into `v`. If kNumBits were to change, we would need to adjust
+ // this packing algorithm.
+ static_assert(kNumBits == 2);
+ out << entry.v[0];
+ if (entry.v[1]) {
+ out << " | (" << entry.v[1] << " << " << bitsPerValue << ")";
+ }
+ if (entry.v[2]) {
+ out << " | (" << entry.v[2] << " << " << (2 * bitsPerValue) << ")";
+ }
+ out << ", {";
+
+ unsigned int shiftBits = 0, combinedBits = 0;
+ for (int index = 0; index < numTransitions; index++) {
+ combinedBits |= entry.data[index] << shiftBits;
+ shiftBits += kNumBits;
+ assert(shiftBits <= 8);
+ if (shiftBits == 8) {
+ out << combinedBits << ", ";
+ shiftBits = 0;
+ combinedBits = 0;
+ }
+ }
+ if (shiftBits > 0) {
+ // Flush any partial values.
+ out << combinedBits;
+ }
+ out << "}},\n";
+ }
+ out << "};\n"
+ << "static constexpr IndexEntry kIndices[] = {\n";
+ for (const IndexEntry& entry : indices) {
+ if (entry.type == kFullEntry) {
+ // Bit-not is used so that full entries start at -1 and go down from there.
+ out << ~entry.pos << ", ";
+ } else {
+ // Compact entries start at 0 and go up from there.
+ out << entry.pos << ", ";
+ }
+ }
+ out << "};\n"
+ << "State get_transition(int transition, int state) {\n"
+ << " IndexEntry index = kIndices[state];\n"
+ << " if (index < 0) { return kFull[~index].data[transition]; }\n"
+ << " const CompactEntry& entry = kCompact[index];\n"
+ << " int v = entry.data[transition >> " << std::log2(kDataPerByte) << "];\n"
+ << " v >>= " << kNumBits << " * (transition & " << kDataPerByte - 1 << ");\n"
+ << " v &= " << kNumValues << ";\n"
+ << " v *= " << bitsPerValue << ";\n"
+ << " return (entry.values >> v) & " << maxValue << ";\n"
+ << "}\n";
+}
diff --git a/gfx/skia/skia/src/sksl/lex/TransitionTable.h b/gfx/skia/skia/src/sksl/lex/TransitionTable.h
new file mode 100644
index 0000000000..6ac6986fae
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/lex/TransitionTable.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_TRANSITIONTABLE
+#define SKSL_TRANSITIONTABLE
+
+#include <stddef.h>
+#include <fstream>
+
+struct DFA;
+
+void WriteTransitionTable(std::ofstream& out, const DFA& dfa, size_t states);
+
+#endif // SKSL_TRANSITIONTABLE
diff --git a/gfx/skia/skia/src/sksl/lex/sksl.lex b/gfx/skia/skia/src/sksl/lex/sksl.lex
new file mode 100644
index 0000000000..02a946a350
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/lex/sksl.lex
@@ -0,0 +1,102 @@
+// *****************
+// *** IMPORTANT ***
+// *****************
+//
+// 1. This file is only used when gn arg skia_lex is set to true. It is used to regenerate the
+// SkSLLexer.h and SkSLLexer.cpp files.
+// 2. Since token IDs are used to identify operators and baked into the .dehydrated.sksl files,
+// after modifying this file it is likely everything will break until you update the dehydrated
+// binaries. If things break after updating the lexer, set REHYDRATE in SkSLCompiler.cpp to 0,
+// rebuild, and then set it back to 1.
+
+FLOAT_LITERAL = [0-9]*\.[0-9]+([eE][+-]?[0-9]+)?|[0-9]+\.[0-9]*([eE][+-]?[0-9]+)?|[0-9]+([eE][+-]?[0-9]+)
+INT_LITERAL = ([1-9][0-9]*|0[0-7]*|0[xX][0-9a-fA-F]+)[uU]?
+BAD_OCTAL = (0[0-9]+)[uU]?
+TRUE_LITERAL = "true"
+FALSE_LITERAL = "false"
+IF = "if"
+ELSE = "else"
+FOR = "for"
+WHILE = "while"
+DO = "do"
+SWITCH = "switch"
+CASE = "case"
+DEFAULT = "default"
+BREAK = "break"
+CONTINUE = "continue"
+DISCARD = "discard"
+RETURN = "return"
+IN = "in"
+OUT = "out"
+INOUT = "inout"
+UNIFORM = "uniform"
+CONST = "const"
+FLAT = "flat"
+NOPERSPECTIVE = "noperspective"
+INLINE = "inline"
+NOINLINE = "noinline"
+PURE = "$pure"
+READONLY = "readonly"
+WRITEONLY = "writeonly"
+BUFFER = "buffer"
+STRUCT = "struct"
+LAYOUT = "layout"
+HIGHP = "highp"
+MEDIUMP = "mediump"
+LOWP = "lowp"
+ES3 = "$es3"
+EXPORT = "$export"
+WORKGROUP = "workgroup"
+RESERVED = atomic|attribute|varying|precision|invariant|asm|class|union|enum|typedef|template|this|packed|goto|volatile|public|static|extern|external|interface|long|double|fixed|unsigned|superp|input|output|hvec[234]|dvec[234]|fvec[234]|sampler[13]D|sampler[12]DShadow|sampler3DRect|sampler2DRectShadow|samplerCube|sizeof|cast|namespace|using|gl_[0-9a-zA-Z_]*
+PRIVATE_IDENTIFIER = $[0-9a-zA-Z_]*
+IDENTIFIER = [a-zA-Z_][0-9a-zA-Z_]*
+DIRECTIVE = #[a-zA-Z_][0-9a-zA-Z_]*
+LPAREN = "("
+RPAREN = ")"
+LBRACE = "{"
+RBRACE = "}"
+LBRACKET = "["
+RBRACKET = "]"
+DOT = "."
+COMMA = ","
+PLUSPLUS = "++"
+MINUSMINUS = "--"
+PLUS = "+"
+MINUS = "-"
+STAR = "*"
+SLASH = "/"
+PERCENT = "%"
+SHL = "<<"
+SHR = ">>"
+BITWISEOR = "|"
+BITWISEXOR = "^"
+BITWISEAND = "&"
+BITWISENOT = "~"
+LOGICALOR = "||"
+LOGICALXOR = "^^"
+LOGICALAND = "&&"
+LOGICALNOT = "!"
+QUESTION = "?"
+COLON = ":"
+EQ = "="
+EQEQ = "=="
+NEQ = "!="
+GT = ">"
+LT = "<"
+GTEQ = ">="
+LTEQ = "<="
+PLUSEQ = "+="
+MINUSEQ = "-="
+STAREQ = "*="
+SLASHEQ = "/="
+PERCENTEQ = "%="
+SHLEQ = "<<="
+SHREQ = ">>="
+BITWISEOREQ = "|="
+BITWISEXOREQ = "^="
+BITWISEANDEQ = "&="
+SEMICOLON = ";"
+WHITESPACE = \s+
+LINE_COMMENT = //.*
+BLOCK_COMMENT = /\*([^*]|\*[^/])*\*/
+INVALID = .
diff --git a/gfx/skia/skia/src/sksl/sksl_compute.sksl b/gfx/skia/skia/src/sksl/sksl_compute.sksl
new file mode 100644
index 0000000000..b06cb7b38b
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/sksl_compute.sksl
@@ -0,0 +1,21 @@
+// defines built-in interfaces supported by SkSL compute shaders
+
+layout(builtin=24) in uint3 sk_NumWorkgroups;
+layout(builtin=26) in uint3 sk_WorkgroupID;
+layout(builtin=27) in uint3 sk_LocalInvocationID;
+layout(builtin=28) in uint3 sk_GlobalInvocationID;
+layout(builtin=29) in uint sk_LocalInvocationIndex;
+
+$pure half4 read($readableTexture2D t, uint2 pos);
+void write($writableTexture2D t, uint2 pos, half4 color);
+
+$pure uint width($genTexture2D t);
+$pure uint height($genTexture2D t);
+
+// Control-barrier with memory-ordering constraints applied to
+// workgroup shared memory only.
+void workgroupBarrier();
+
+// Control-barrier with memory-ordering constraints applied to
+// uniform and storage-buffer memory.
+void storageBarrier();
diff --git a/gfx/skia/skia/src/sksl/sksl_frag.sksl b/gfx/skia/skia/src/sksl/sksl_frag.sksl
new file mode 100644
index 0000000000..aa1e09d645
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/sksl_frag.sksl
@@ -0,0 +1,9 @@
+// defines built-in interfaces supported by SkSL fragment shaders
+
+// See "enum SpvBuiltIn_" in ./spirv.h
+layout(builtin=15) in float4 sk_FragCoord;
+layout(builtin=17) in bool sk_Clockwise; // Similar to gl_FrontFacing, but defined in device space.
+
+layout(location=0,index=0,builtin=10001) out half4 sk_FragColor;
+layout(builtin=10008) half4 sk_LastFragColor;
+layout(builtin=10012) out half4 sk_SecondaryFragColor;
diff --git a/gfx/skia/skia/src/sksl/sksl_gpu.sksl b/gfx/skia/skia/src/sksl/sksl_gpu.sksl
new file mode 100644
index 0000000000..80ac013a55
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/sksl_gpu.sksl
@@ -0,0 +1,324 @@
+// Not exposed in shared module
+
+$pure $genIType mix($genIType x, $genIType y, $genBType a);
+$pure $genBType mix($genBType x, $genBType y, $genBType a);
+$pure $genType fma($genType a, $genType b, $genType c);
+$pure $genHType fma($genHType a, $genHType b, $genHType c);
+ $genType frexp($genType x, out $genIType exp);
+ $genHType frexp($genHType x, out $genIType exp);
+$pure $genType ldexp($genType x, in $genIType exp);
+$pure $genHType ldexp($genHType x, in $genIType exp);
+
+$pure uint packSnorm2x16(float2 v);
+$pure uint packUnorm4x8(float4 v);
+$pure uint packSnorm4x8(float4 v);
+$pure float2 unpackSnorm2x16(uint p);
+$pure float4 unpackUnorm4x8(uint p);
+$pure float4 unpackSnorm4x8(uint p);
+$pure uint packHalf2x16(float2 v);
+$pure float2 unpackHalf2x16(uint v);
+
+$pure $genIType bitCount($genIType value);
+$pure $genIType bitCount($genUType value);
+$pure $genIType findLSB($genIType value);
+$pure $genIType findLSB($genUType value);
+$pure $genIType findMSB($genIType value);
+$pure $genIType findMSB($genUType value);
+
+$pure sampler2D makeSampler2D(texture2D texture, sampler s);
+
+$pure half4 sample(sampler2D s, float2 P);
+$pure half4 sample(sampler2D s, float3 P);
+$pure half4 sample(sampler2D s, float3 P, float bias);
+
+$pure half4 sample(samplerExternalOES s, float2 P);
+$pure half4 sample(samplerExternalOES s, float2 P, float bias);
+
+$pure half4 sample(sampler2DRect s, float2 P);
+$pure half4 sample(sampler2DRect s, float3 P);
+
+$pure half4 sampleLod(sampler2D s, float2 P, float lod);
+$pure half4 sampleLod(sampler2D s, float3 P, float lod);
+
+$pure half4 sampleGrad(sampler2D s, float2, float2 dPdx, float2 dPdy);
+
+// Currently we do not support the generic types of loading subpassInput so we have some explicit
+// versions that we currently use
+$pure half4 subpassLoad(subpassInput subpass);
+$pure half4 subpassLoad(subpassInputMS subpass, int sample);
+
+/** Atomically loads the value from `a` and returns it. */
+$pure uint atomicLoad(atomicUint a);
+
+/** Atomically stores the value of `value` to `a` */
+void atomicStore(atomicUint a, uint value);
+
+/**
+ * Performs an atomic addition of `value` to the contents of `a` and returns the original contents
+ * of `a` from before the addition occurred.
+ */
+uint atomicAdd(atomicUint a, uint value);
+
+// Definitions of functions implementing all of the SkBlendMode blends.
+
+$pure half4 blend_clear(half4 src, half4 dst) { return half4(0); }
+
+$pure half4 blend_src(half4 src, half4 dst) { return src; }
+
+$pure half4 blend_dst(half4 src, half4 dst) { return dst; }
+
+$pure half4 blend_src_over(half4 src, half4 dst) { return src + (1 - src.a)*dst; }
+
+$pure half4 blend_dst_over(half4 src, half4 dst) { return (1 - dst.a)*src + dst; }
+
+$pure half4 blend_src_in(half4 src, half4 dst) { return src*dst.a; }
+
+$pure half4 blend_dst_in(half4 src, half4 dst) { return dst*src.a; }
+
+$pure half4 blend_src_out(half4 src, half4 dst) { return (1 - dst.a)*src; }
+
+$pure half4 blend_dst_out(half4 src, half4 dst) { return (1 - src.a)*dst; }
+
+$pure half4 blend_src_atop(half4 src, half4 dst) { return dst.a*src + (1 - src.a)*dst; }
+
+$pure half4 blend_dst_atop(half4 src, half4 dst) { return (1 - dst.a) * src + src.a*dst; }
+
+$pure half4 blend_xor(half4 src, half4 dst) { return (1 - dst.a)*src + (1 - src.a)*dst; }
+
+$pure half4 blend_plus(half4 src, half4 dst) { return min(src + dst, 1); }
+
+// This multi-purpose Porter-Duff blend function can perform any of the thirteen blends above,
+// when passed one of the following values for BlendOp:
+// - Clear: half4(0, 0, 0, 0)
+// - Src: half4(1, 0, 0, 0)
+// - Dst: half4(0, 1, 0, 0)
+// - SrcOver: half4(1, 0, 0, -1)
+// - DstOver: half4(0, 1, -1, 0)
+// - SrcIn: half4(0, 0, 1, 0)
+// - DstIn: half4(0, 0, 0, 1)
+// - SrcOut: half4(0, 0, -1, 0)
+// - DstOut: half4(0, 0, 0, -1)
+// - SrcATop: half4(0, 0, 1, -1)
+// - DstATop: half4(0, 0, -1, 1)
+// - Xor: half4(0, 0, -1, -1)
+// - Plus: half4(1, 1, 0, 0)
+$pure half4 blend_porter_duff(half4 blendOp, half4 src, half4 dst) {
+ half2 coeff = blendOp.xy + (blendOp.zw * (half2(dst.a, src.a) + min(blendOp.zw, 0)));
+ return min(half4(1), src * coeff.x + dst * coeff.y);
+}
+
+$pure half4 blend_modulate(half4 src, half4 dst) { return src*dst; }
+
+$pure half4 blend_screen(half4 src, half4 dst) { return src + (1 - src)*dst; }
+
+$pure half $blend_overlay_component(half2 s, half2 d) {
+ return (2*d.x <= d.y) ? 2*s.x*d.x
+ : s.y*d.y - 2*(d.y - d.x)*(s.y - s.x);
+}
+
+$pure half4 blend_overlay(half4 src, half4 dst) {
+ half4 result = half4($blend_overlay_component(src.ra, dst.ra),
+ $blend_overlay_component(src.ga, dst.ga),
+ $blend_overlay_component(src.ba, dst.ba),
+ src.a + (1 - src.a)*dst.a);
+ result.rgb += dst.rgb*(1 - src.a) + src.rgb*(1 - dst.a);
+ return result;
+}
+
+$pure half4 blend_overlay(half flip, half4 a, half4 b) {
+ return blend_overlay(bool(flip) ? b : a, bool(flip) ? a : b);
+}
+
+$pure half4 blend_lighten(half4 src, half4 dst) {
+ half4 result = blend_src_over(src, dst);
+ result.rgb = max(result.rgb, (1 - dst.a)*src.rgb + dst.rgb);
+ return result;
+}
+
+$pure half4 blend_darken(half mode /* darken: 1, lighten: -1 */, half4 src, half4 dst) {
+ half4 a = blend_src_over(src, dst);
+ half3 b = (1 - dst.a) * src.rgb + dst.rgb; // DstOver.rgb
+ a.rgb = mode * min(a.rgb * mode, b.rgb * mode);
+ return a;
+}
+
+$pure half4 blend_darken(half4 src, half4 dst) {
+ return blend_darken(1, src, dst);
+}
+
+const half $kGuardedDivideEpsilon = sk_Caps.mustGuardDivisionEvenAfterExplicitZeroCheck
+ ? 0.00000001
+ : 0.0;
+
+$pure inline half $guarded_divide(half n, half d) {
+ return n / (d + $kGuardedDivideEpsilon);
+}
+
+$pure inline half3 $guarded_divide(half3 n, half d) {
+ return n / (d + $kGuardedDivideEpsilon);
+}
+
+$pure half $color_dodge_component(half2 s, half2 d) {
+ if (d.x == 0) {
+ return s.x*(1 - d.y);
+ } else {
+ half delta = s.y - s.x;
+ if (delta == 0) {
+ return s.y*d.y + s.x*(1 - d.y) + d.x*(1 - s.y);
+ } else {
+ delta = min(d.y, $guarded_divide(d.x*s.y, delta));
+ return delta*s.y + s.x*(1 - d.y) + d.x*(1 - s.y);
+ }
+ }
+}
+
+$pure half4 blend_color_dodge(half4 src, half4 dst) {
+ return half4($color_dodge_component(src.ra, dst.ra),
+ $color_dodge_component(src.ga, dst.ga),
+ $color_dodge_component(src.ba, dst.ba),
+ src.a + (1 - src.a)*dst.a);
+}
+
+$pure half $color_burn_component(half2 s, half2 d) {
+ if (d.y == d.x) {
+ return s.y*d.y + s.x*(1 - d.y) + d.x*(1 - s.y);
+ } else if (s.x == 0) {
+ return d.x*(1 - s.y);
+ } else {
+ half delta = max(0, d.y - $guarded_divide((d.y - d.x)*s.y, s.x));
+ return delta*s.y + s.x*(1 - d.y) + d.x*(1 - s.y);
+ }
+}
+
+$pure half4 blend_color_burn(half4 src, half4 dst) {
+ return half4($color_burn_component(src.ra, dst.ra),
+ $color_burn_component(src.ga, dst.ga),
+ $color_burn_component(src.ba, dst.ba),
+ src.a + (1 - src.a)*dst.a);
+}
+
+$pure half4 blend_hard_light(half4 src, half4 dst) {
+ return blend_overlay(dst, src);
+}
+
+$pure half $soft_light_component(half2 s, half2 d) {
+ if (2*s.x <= s.y) {
+ return $guarded_divide(d.x*d.x*(s.y - 2*s.x), d.y) + (1 - d.y)*s.x + d.x*(-s.y + 2*s.x + 1);
+ } else if (4.0 * d.x <= d.y) {
+ half DSqd = d.x*d.x;
+ half DCub = DSqd*d.x;
+ half DaSqd = d.y*d.y;
+ half DaCub = DaSqd*d.y;
+ return $guarded_divide(DaSqd*(s.x - d.x*(3*s.y - 6*s.x - 1)) + 12*d.y*DSqd*(s.y - 2*s.x)
+ - 16*DCub * (s.y - 2*s.x) - DaCub*s.x, DaSqd);
+ } else {
+ return d.x*(s.y - 2*s.x + 1) + s.x - sqrt(d.y*d.x)*(s.y - 2*s.x) - d.y*s.x;
+ }
+}
+
+$pure half4 blend_soft_light(half4 src, half4 dst) {
+ return (dst.a == 0) ? src : half4($soft_light_component(src.ra, dst.ra),
+ $soft_light_component(src.ga, dst.ga),
+ $soft_light_component(src.ba, dst.ba),
+ src.a + (1 - src.a)*dst.a);
+}
+
+$pure half4 blend_difference(half4 src, half4 dst) {
+ return half4(src.rgb + dst.rgb - 2*min(src.rgb*dst.a, dst.rgb*src.a),
+ src.a + (1 - src.a)*dst.a);
+}
+
+$pure half4 blend_exclusion(half4 src, half4 dst) {
+ return half4(dst.rgb + src.rgb - 2*dst.rgb*src.rgb, src.a + (1 - src.a)*dst.a);
+}
+
+$pure half4 blend_multiply(half4 src, half4 dst) {
+ return half4((1 - src.a)*dst.rgb + (1 - dst.a)*src.rgb + src.rgb*dst.rgb,
+ src.a + (1 - src.a)*dst.a);
+}
+
+$pure half $blend_color_luminance(half3 color) { return dot(half3(0.3, 0.59, 0.11), color); }
+
+$pure half3 $blend_set_color_luminance(half3 hueSatColor, half alpha, half3 lumColor) {
+ half lum = $blend_color_luminance(lumColor);
+ half3 result = lum - $blend_color_luminance(hueSatColor) + hueSatColor;
+ half minComp = min(min(result.r, result.g), result.b);
+ half maxComp = max(max(result.r, result.g), result.b);
+ if (minComp < 0 && lum != minComp) {
+ result = lum + (result - lum) * $guarded_divide(lum, (lum - minComp));
+ }
+ if (maxComp > alpha && maxComp != lum) {
+ result = lum + $guarded_divide((result - lum) * (alpha - lum), (maxComp - lum));
+ }
+ return result;
+}
+
+$pure half $blend_color_saturation(half3 color) {
+ return max(max(color.r, color.g), color.b) - min(min(color.r, color.g), color.b);
+}
+
+$pure half3 $blend_set_color_saturation(half3 color, half3 satColor) {
+ half mn = min(min(color.r, color.g), color.b);
+ half mx = max(max(color.r, color.g), color.b);
+
+ return (mx > mn) ? ((color - mn) * $blend_color_saturation(satColor)) / (mx - mn)
+ : half3(0);
+}
+
+$pure half4 blend_hslc(half2 flipSat, half4 src, half4 dst) {
+ half alpha = dst.a * src.a;
+ half3 sda = src.rgb * dst.a;
+ half3 dsa = dst.rgb * src.a;
+ half3 l = bool(flipSat.x) ? dsa : sda;
+ half3 r = bool(flipSat.x) ? sda : dsa;
+ if (bool(flipSat.y)) {
+ l = $blend_set_color_saturation(l, r);
+ r = dsa;
+ }
+ return half4($blend_set_color_luminance(l, alpha, r) + dst.rgb - dsa + src.rgb - sda,
+ src.a + dst.a - alpha);
+}
+
+$pure half4 blend_hue(half4 src, half4 dst) {
+ return blend_hslc(half2(0, 1), src, dst);
+}
+
+$pure half4 blend_saturation(half4 src, half4 dst) {
+ return blend_hslc(half2(1), src, dst);
+}
+
+$pure half4 blend_color(half4 src, half4 dst) {
+ return blend_hslc(half2(0), src, dst);
+}
+
+$pure half4 blend_luminosity(half4 src, half4 dst) {
+ return blend_hslc(half2(1, 0), src, dst);
+}
+
+$pure float2 proj(float3 p) { return p.xy / p.z; }
+
+// Implement cross() as a determinant to communicate our intent more clearly to the compiler.
+// NOTE: Due to precision issues, it might be the case that cross(a, a) != 0.
+$pure float cross_length_2d(float2 a, float2 b) {
+ return determinant(float2x2(a, b));
+}
+
+$pure half cross_length_2d(half2 a, half2 b) {
+ return determinant(half2x2(a, b));
+}
+
+$pure float2 perp(float2 v) {
+ return float2(-v.y, v.x);
+}
+
+$pure half2 perp(half2 v) {
+ return half2(-v.y, v.x);
+}
+
+// Returns a bias given a scale factor, such that 'scale * (dist + bias)' converts the distance to
+// a per-pixel coverage value, automatically widening the visible coverage ramp for subpixel
+// dimensions. The 'scale' must already be equal to the narrowest dimension of the shape and clamped
+// to [0, 1.0].
+$pure float coverage_bias(float scale) {
+ return 1.0 - 0.5 * scale;
+}
diff --git a/gfx/skia/skia/src/sksl/sksl_graphite_frag.sksl b/gfx/skia/skia/src/sksl/sksl_graphite_frag.sksl
new file mode 100644
index 0000000000..7cd6080959
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/sksl_graphite_frag.sksl
@@ -0,0 +1,1135 @@
+// Graphite-specific fragment shader code
+
+const int $kTileModeClamp = 0;
+const int $kTileModeRepeat = 1;
+const int $kTileModeMirror = 2;
+const int $kTileModeDecal = 3;
+
+const int $kReadSwizzleNormalRGBA = 0;
+const int $kReadSwizzleRGB1 = 1;
+const int $kReadSwizzleRRRR = 2;
+const int $kReadSwizzleRRR1 = 3;
+const int $kReadSwizzleBGRA = 4;
+
+const int $kFilterModeNearest = 0;
+const int $kFilterModeLinear = 1;
+
+const int $kTFTypeSRGB = 1;
+const int $kTFTypePQ = 2;
+const int $kTFTypeHLG = 3;
+const int $kTFTypeHLGinv = 4;
+
+const int $kColorSpaceXformFlagUnpremul = 0x1;
+const int $kColorSpaceXformFlagLinearize = 0x2;
+const int $kColorSpaceXformFlagGamutTransform = 0x4;
+const int $kColorSpaceXformFlagEncode = 0x8;
+const int $kColorSpaceXformFlagPremul = 0x10;
+
+$pure half4 sk_error() {
+ return half4(1.0, 0.0, 1.0, 1.0);
+}
+
+$pure half4 sk_passthrough(half4 color) {
+ return color;
+}
+
+$pure half4 sk_solid_shader(float4 colorParam) {
+ return half4(colorParam);
+}
+
+$pure half4 $apply_swizzle(int swizzleType, half4 color) {
+ half4 resultantColor = color;
+ switch (swizzleType) {
+ case $kReadSwizzleNormalRGBA:
+ break;
+ case $kReadSwizzleRGB1:
+ resultantColor = color.rgb1;
+ break;
+ case $kReadSwizzleRRRR:
+ resultantColor = color.rrrr;
+ break;
+ case $kReadSwizzleRRR1:
+ resultantColor = color.rrr1;
+ break;
+ case $kReadSwizzleBGRA:
+ resultantColor = color.bgra;
+ break;
+ }
+ return resultantColor;
+}
+
+$pure half $apply_xfer_fn(int kind, half x, half cs[7]) {
+ half G = cs[0], A = cs[1], B = cs[2], C = cs[3], D = cs[4], E = cs[5], F = cs[6];
+ half s = sign(x);
+ x = abs(x);
+ switch (kind) {
+ case $kTFTypeSRGB:
+ x = (x < D) ? (C * x) + F
+ : pow(A * x + B, G) + E;
+ break;
+ case $kTFTypePQ:
+ x = pow(max(A + B * pow(x, C), 0) / (D + E * pow(x, C)), F);
+ break;
+ case $kTFTypeHLG:
+ x = (x * A <= 1) ? pow(x * A, B)
+ : exp((x - E) * C) + D;
+ x *= (F + 1);
+ break;
+ case $kTFTypeHLGinv:
+ x /= (F + 1);
+ x = (x <= 1) ? A * pow(x, B)
+ : C * log(x - D) + E;
+ break;
+ }
+ return s * x;
+}
+
+// TODO(b/239548614) need to plumb Graphite equivalent of fColorSpaceMathNeedsFloat.
+// This would change 'color' from half4 to float4
+$pure half4 sk_color_space_transform(half4 color,
+ int flags,
+ int srcKind,
+ half srcCoeffs[7],
+ half3x3 gamutTransform,
+ int dstKind,
+ half dstCoeffs[7]) {
+ if (bool(flags & $kColorSpaceXformFlagUnpremul)) {
+ color = unpremul(color);
+ }
+
+ if (bool(flags & $kColorSpaceXformFlagLinearize)) {
+ color.r = $apply_xfer_fn(srcKind, color.r, srcCoeffs);
+ color.g = $apply_xfer_fn(srcKind, color.g, srcCoeffs);
+ color.b = $apply_xfer_fn(srcKind, color.b, srcCoeffs);
+ }
+ if (bool(flags & $kColorSpaceXformFlagGamutTransform)) {
+ color.rgb = gamutTransform * color.rgb;
+ }
+ if (bool(flags & $kColorSpaceXformFlagEncode)) {
+ color.r = $apply_xfer_fn(dstKind, color.r, dstCoeffs);
+ color.g = $apply_xfer_fn(dstKind, color.g, dstCoeffs);
+ color.b = $apply_xfer_fn(dstKind, color.b, dstCoeffs);
+ }
+
+ if (bool(flags & $kColorSpaceXformFlagPremul)) {
+ color.rgb *= color.a;
+ }
+ return color;
+}
+
+$pure float $tile(int tileMode, float f, float low, float high) {
+ switch (tileMode) {
+ case $kTileModeClamp:
+ return clamp(f, low, high);
+
+ case $kTileModeRepeat: {
+ float length = high - low;
+ return (mod(f - low, length) + low);
+ }
+ case $kTileModeMirror: {
+ float length = high - low;
+ float length2 = 2 * length;
+ float tmp = mod(f - low, length2);
+ return (mix(tmp, length2 - tmp, step(length, tmp)) + low);
+ }
+ default: // $kTileModeDecal
+ // Decal is handled later.
+ return f;
+ }
+}
+
+$pure half4 $sample_image(float2 pos,
+ float2 imgSize,
+ float4 subset,
+ int tileModeX,
+ int tileModeY,
+ int filterMode,
+ int readSwizzle,
+ sampler2D s) {
+ // Do hard-edge shader transitions to the border color for nearest-neighbor decal tiling at the
+ // subset boundaries. Snap the input coordinates to nearest neighbor before comparing to the
+ // subset rect, to avoid GPU interpolation errors. See https://crbug.com/skia/10403.
+ if (tileModeX == $kTileModeDecal && filterMode == $kFilterModeNearest) {
+ float snappedX = floor(pos.x) + 0.5;
+ if (snappedX < subset.x || snappedX > subset.z) {
+ return half4(0);
+ }
+ }
+ if (tileModeY == $kTileModeDecal && filterMode == $kFilterModeNearest) {
+ float snappedY = floor(pos.y) + 0.5;
+ if (snappedY < subset.y || snappedY > subset.w) {
+ return half4(0);
+ }
+ }
+
+ pos.x = $tile(tileModeX, pos.x, subset.x, subset.z);
+ pos.y = $tile(tileModeY, pos.y, subset.y, subset.w);
+
+ // Clamp to an inset subset to prevent sampling neighboring texels when coords fall exactly at
+ // texel boundaries.
+ float4 insetClamp;
+ if (filterMode == $kFilterModeNearest) {
+ insetClamp = float4(floor(subset.xy) + 0.5, ceil(subset.zw) - 0.5);
+ } else {
+ insetClamp = float4(subset.xy + 0.5, subset.zw - 0.5);
+ }
+ float2 clampedPos = clamp(pos, insetClamp.xy, insetClamp.zw);
+ half4 color = sample(s, clampedPos / imgSize);
+ color = $apply_swizzle(readSwizzle, color);
+
+ if (filterMode == $kFilterModeLinear) {
+ // Remember the amount the coord moved for clamping. This is used to implement shader-based
+ // filtering for repeat and decal tiling.
+ half2 error = half2(pos - clampedPos);
+ half2 absError = abs(error);
+
+ // Do 1 or 3 more texture reads depending on whether both x and y tiling modes are repeat
+ // and whether we're near a single subset edge or a corner. Then blend the multiple reads
+ // using the error values calculated above.
+ bool sampleExtraX = tileModeX == $kTileModeRepeat;
+ bool sampleExtraY = tileModeY == $kTileModeRepeat;
+ if (sampleExtraX || sampleExtraY) {
+ float extraCoordX;
+ float extraCoordY;
+ half4 extraColorX;
+ half4 extraColorY;
+ if (sampleExtraX) {
+ extraCoordX = error.x > 0 ? insetClamp.x : insetClamp.z;
+ extraColorX = sample(s, float2(extraCoordX, clampedPos.y) / imgSize);
+ extraColorX = $apply_swizzle(readSwizzle, extraColorX);
+ }
+ if (sampleExtraY) {
+ extraCoordY = error.y > 0 ? insetClamp.y : insetClamp.w;
+ extraColorY = sample(s, float2(clampedPos.x, extraCoordY) / imgSize);
+ extraColorY = $apply_swizzle(readSwizzle, extraColorY);
+ }
+ if (sampleExtraX && sampleExtraY) {
+ half4 extraColorXY = sample(s, float2(extraCoordX, extraCoordY) / imgSize);
+ extraColorXY = $apply_swizzle(readSwizzle, extraColorXY);
+ color = mix(mix(color, extraColorX, absError.x),
+ mix(extraColorY, extraColorXY, absError.x),
+ absError.y);
+ } else if (sampleExtraX) {
+ color = mix(color, extraColorX, absError.x);
+ } else if (sampleExtraY) {
+ color = mix(color, extraColorY, absError.y);
+ }
+ }
+
+ // Do soft edge shader filtering for decal tiling and linear filtering using the error
+ // values calculated above.
+ if (tileModeX == $kTileModeDecal) {
+ color *= max(1 - absError.x, 0);
+ }
+ if (tileModeY == $kTileModeDecal) {
+ color *= max(1 - absError.y, 0);
+ }
+ }
+
+ return color;
+}
+
+$pure half4 $cubic_filter_image(float2 pos,
+ float2 imgSize,
+ float4 subset,
+ int tileModeX,
+ int tileModeY,
+ float4x4 coeffs,
+ int readSwizzle,
+ sampler2D s) {
+ // Determine pos's fractional offset f between texel centers.
+ float2 f = fract(pos - 0.5);
+ // Sample 16 points at 1-pixel intervals from [p - 1.5 ... p + 1.5].
+ pos -= 1.5;
+ // Snap to texel centers to prevent sampling neighboring texels.
+ pos = floor(pos) + 0.5;
+
+ float4 wx = coeffs * float4(1.0, f.x, f.x * f.x, f.x * f.x * f.x);
+ float4 wy = coeffs * float4(1.0, f.y, f.y * f.y, f.y * f.y * f.y);
+ float4 color = float4(0);
+ for (int y = 0; y < 4; ++y) {
+ float4 rowColor = float4(0);
+ for (int x = 0; x < 4; ++x) {
+ rowColor += wx[x] * $sample_image(pos + float2(x, y), imgSize, subset,
+ tileModeX, tileModeY, $kFilterModeNearest,
+ readSwizzle, s);
+ }
+ color += wy[y] * rowColor;
+ }
+ return half4(color);
+}
+
+$pure half4 sk_image_shader(float2 coords,
+ float2 imgSize,
+ float4 subset,
+ int tileModeX,
+ int tileModeY,
+ int filterMode,
+ int useCubic,
+ float4x4 cubicCoeffs,
+ int readSwizzle,
+ int csXformFlags,
+ int csXformSrcKind,
+ half csXformSrcCoeffs[7],
+ half3x3 csXformGamutTransform,
+ int csXformDstKind,
+ half csXformDstCoeffs[7],
+ sampler2D s) {
+ half4 sampleColor = (useCubic != 0)
+ ? $cubic_filter_image(coords, imgSize, subset, tileModeX, tileModeY, cubicCoeffs,
+ readSwizzle, s)
+ : $sample_image(coords, imgSize, subset, tileModeX, tileModeY, filterMode, readSwizzle, s);
+ return sk_color_space_transform(sampleColor, csXformFlags, csXformSrcKind, csXformSrcCoeffs,
+ csXformGamutTransform, csXformDstKind, csXformDstCoeffs);
+}
+
+$pure half4 sk_dither_shader(half4 colorIn,
+ float2 coords,
+ float range,
+ sampler2D lut) {
+ const float kImgSize = 8;
+
+ half2 lutCoords = half2(coords.x/kImgSize, coords.y/kImgSize);
+ half value = sample(lut, lutCoords).r - 0.5; // undo the bias in the table
+ // For each color channel, add the random offset to the channel value and then clamp
+ // between 0 and alpha to keep the color premultiplied.
+ return half4(clamp(colorIn.rgb + value * range, 0.0, colorIn.a), colorIn.a);
+}
+
+$pure float2 $tile_grad(int tileMode, float2 t) {
+ switch (tileMode) {
+ case $kTileModeClamp:
+ t.x = clamp(t.x, 0, 1);
+ break;
+
+ case $kTileModeRepeat:
+ t.x = fract(t.x);
+ break;
+
+ case $kTileModeMirror: {
+ float t_1 = t.x - 1;
+ t.x = t_1 - 2 * floor(t_1 * 0.5) - 1;
+ if (sk_Caps.mustDoOpBetweenFloorAndAbs) {
+ // At this point the expected value of tiled_t should between -1 and 1, so this
+ // clamp has no effect other than to break up the floor and abs calls and make sure
+ // the compiler doesn't merge them back together.
+ t.x = clamp(t.x, -1, 1);
+ }
+ t.x = abs(t.x);
+ break;
+ }
+
+ case $kTileModeDecal:
+ if (t.x < 0 || t.x > 1) {
+ return float2(0, -1);
+ }
+ break;
+ }
+
+ return t;
+}
+
+$pure half4 $colorize_grad_4(float4 colorsParam[4], float offsetsParam[4], float2 t) {
+ if (t.y < 0) {
+ return half4(0);
+
+ } else if (t.x <= offsetsParam[0]) {
+ return half4(colorsParam[0]);
+ } else if (t.x < offsetsParam[1]) {
+ return half4(mix(colorsParam[0], colorsParam[1], (t.x - offsetsParam[0]) /
+ (offsetsParam[1] - offsetsParam[0])));
+ } else if (t.x < offsetsParam[2]) {
+ return half4(mix(colorsParam[1], colorsParam[2], (t.x - offsetsParam[1]) /
+ (offsetsParam[2] - offsetsParam[1])));
+ } else if (t.x < offsetsParam[3]) {
+ return half4(mix(colorsParam[2], colorsParam[3], (t.x - offsetsParam[2]) /
+ (offsetsParam[3] - offsetsParam[2])));
+ } else {
+ return half4(colorsParam[3]);
+ }
+}
+
+$pure half4 $colorize_grad_8(float4 colorsParam[8], float offsetsParam[8], float2 t) {
+ if (t.y < 0) {
+ return half4(0);
+
+ // Unrolled binary search through intervals
+ // ( .. 0), (0 .. 1), (1 .. 2), (2 .. 3), (3 .. 4), (4 .. 5), (5 .. 6), (6 .. 7), (7 .. ).
+ } else if (t.x < offsetsParam[4]) {
+ if (t.x < offsetsParam[2]) {
+ if (t.x <= offsetsParam[0]) {
+ return half4(colorsParam[0]);
+ } else if (t.x < offsetsParam[1]) {
+ return half4(mix(colorsParam[0], colorsParam[1],
+ (t.x - offsetsParam[0]) /
+ (offsetsParam[1] - offsetsParam[0])));
+ } else {
+ return half4(mix(colorsParam[1], colorsParam[2],
+ (t.x - offsetsParam[1]) /
+ (offsetsParam[2] - offsetsParam[1])));
+ }
+ } else {
+ if (t.x < offsetsParam[3]) {
+ return half4(mix(colorsParam[2], colorsParam[3],
+ (t.x - offsetsParam[2]) /
+ (offsetsParam[3] - offsetsParam[2])));
+ } else {
+ return half4(mix(colorsParam[3], colorsParam[4],
+ (t.x - offsetsParam[3]) /
+ (offsetsParam[4] - offsetsParam[3])));
+ }
+ }
+ } else {
+ if (t.x < offsetsParam[6]) {
+ if (t.x < offsetsParam[5]) {
+ return half4(mix(colorsParam[4], colorsParam[5],
+ (t.x - offsetsParam[4]) /
+ (offsetsParam[5] - offsetsParam[4])));
+ } else {
+ return half4(mix(colorsParam[5], colorsParam[6],
+ (t.x - offsetsParam[5]) /
+ (offsetsParam[6] - offsetsParam[5])));
+ }
+ } else {
+ if (t.x < offsetsParam[7]) {
+ return half4(mix(colorsParam[6], colorsParam[7],
+ (t.x - offsetsParam[6]) /
+ (offsetsParam[7] - offsetsParam[6])));
+ } else {
+ return half4(colorsParam[7]);
+ }
+ }
+ }
+}
+
+half4 $colorize_grad_tex(sampler2D colorsAndOffsetsSampler, int numStops, float2 t) {
+ const float kColorCoord = 0.25;
+ const float kOffsetCoord = 0.75;
+
+ if (t.y < 0) {
+ return half4(0);
+ } else if (t.x == 0) {
+ return sampleLod(colorsAndOffsetsSampler, float2(0, kColorCoord), 0);
+ } else if (t.x == 1) {
+ return sampleLod(colorsAndOffsetsSampler, float2(1, kColorCoord), 0);
+ } else {
+ int low = 0;
+ int high = numStops;
+ for (int loop = 1; loop < numStops; loop <<= 1) {
+ int mid = (low + high) / 2;
+ float midFlt = (float(mid) + 0.5) / float(numStops);
+
+ float2 tmp = sampleLod(colorsAndOffsetsSampler, float2(midFlt, kOffsetCoord), 0).xy;
+ float offset = ldexp(tmp.x, int(tmp.y));
+
+ if (t.x < offset) {
+ high = mid;
+ } else {
+ low = mid;
+ }
+ }
+
+ float lowFlt = (float(low) + 0.5) / float(numStops);
+ float highFlt = (float(low + 1) + 0.5) / float(numStops);
+ half4 color0 = sampleLod(colorsAndOffsetsSampler, float2(lowFlt, kColorCoord), 0);
+ half4 color1 = sampleLod(colorsAndOffsetsSampler, float2(highFlt, kColorCoord), 0);
+
+ float2 tmp = sampleLod(colorsAndOffsetsSampler, float2(lowFlt, kOffsetCoord), 0).xy;
+ float offset0 = ldexp(tmp.x, int(tmp.y));
+
+ tmp = sampleLod(colorsAndOffsetsSampler, float2(highFlt, kOffsetCoord), 0).xy;
+ float offset1 = ldexp(tmp.x, int(tmp.y));
+
+ return half4(mix(color0, color1,
+ (t.x - offset0) /
+ (offset1 - offset0)));
+ }
+}
+
+$pure float2 $linear_grad_layout(float2 point0Param, float2 point1Param, float2 pos) {
+ pos -= point0Param;
+ float2 delta = point1Param - point0Param;
+ float t = dot(pos, delta) / dot(delta, delta);
+ return float2(t, 1);
+}
+
+$pure float2 $radial_grad_layout(float2 centerParam, float radiusParam, float2 pos) {
+ float t = distance(pos, centerParam) / radiusParam;
+ return float2(t, 1);
+}
+
+$pure float2 $sweep_grad_layout(float2 centerParam, float biasParam, float scaleParam, float2 pos) {
+ pos -= centerParam;
+
+ // Some devices incorrectly implement atan2(y,x) as atan(y/x). In actuality it is
+ // atan2(y,x) = 2 * atan(y / (sqrt(x^2 + y^2) + x)). To work around this we pass in
+ // (sqrt(x^2 + y^2) + x) as the second parameter to atan2 in these cases. We let the device
+ // handle the undefined behavior if the second parameter is 0, instead of doing the divide
+ // ourselves and calling atan with the quotient.
+ float angle = sk_Caps.atan2ImplementedAsAtanYOverX ? 2 * atan(-pos.y, length(pos) - pos.x)
+ : atan(-pos.y, -pos.x);
+
+ // 0.1591549430918 is 1/(2*pi), used since atan returns values [-pi, pi]
+ float t = (angle * 0.1591549430918 + 0.5 + biasParam) * scaleParam;
+ return float2(t, 1);
+}
+
+$pure float3x3 $map_to_unit_x(float2 p0, float2 p1) {
+ // Returns a matrix that maps [p0, p1] to [(0, 0), (1, 0)]. Results are undefined if p0 = p1.
+ // From skia/src/core/SkMatrix.cpp, SkMatrix::setPolyToPoly.
+ return float3x3(
+ 0, -1, 0,
+ 1, 0, 0,
+ 0, 0, 1
+ ) * inverse(float3x3(
+ p1.y - p0.y, p0.x - p1.x, 0,
+ p1.x - p0.x, p1.y - p0.y, 0,
+ p0.x, p0.y, 1
+ ));
+}
+
+$pure float2 $conical_grad_layout(float2 point0Param,
+ float2 point1Param,
+ float radius0Param,
+ float radius1Param,
+ float2 pos) {
+ const float SK_ScalarNearlyZero = 1.0 / (1 << 12);
+ float dCenter = distance(point0Param, point1Param);
+ float dRadius = radius1Param - radius0Param;
+
+ // Degenerate case: a radial gradient (p0 = p1).
+ bool radial = dCenter < SK_ScalarNearlyZero;
+
+ // Degenerate case: a strip with bandwidth 2r (r0 = r1).
+ bool strip = abs(dRadius) < SK_ScalarNearlyZero;
+
+ if (radial) {
+ if (strip) {
+ // The start and end inputs are the same in both position and radius.
+ // We don't expect to see this input, but just in case we avoid dividing by zero.
+ return float2(0, -1);
+ }
+
+ float scale = 1 / dRadius;
+ float scaleSign = sign(dRadius);
+ float bias = radius0Param / dRadius;
+
+ float2 pt = (pos - point0Param) * scale;
+ float t = length(pt) * scaleSign - bias;
+ return float2(t, 1);
+
+ } else if (strip) {
+ float3x3 transform = $map_to_unit_x(point0Param, point1Param);
+ float r = radius0Param / dCenter;
+ float r_2 = r * r;
+
+ float2 pt = (transform * pos.xy1).xy;
+ float t = r_2 - pt.y * pt.y;
+ if (t < 0) {
+ return float2(0, -1);
+ }
+ t = pt.x + sqrt(t);
+ return float2(t, 1);
+
+ } else {
+ // See https://skia.org/docs/dev/design/conical/ for details on how this algorithm works.
+ // Calculate f and swap inputs if necessary (steps 1 and 2).
+ float f = radius0Param / (radius0Param - radius1Param);
+
+ bool isSwapped = abs(f - 1) < SK_ScalarNearlyZero;
+ if (isSwapped) {
+ float2 tmpPt = point0Param;
+ point0Param = point1Param;
+ point1Param = tmpPt;
+ f = 0;
+ }
+
+ // Apply mapping from [Cf, C1] to unit x, and apply the precalculations from steps 3 and 4,
+ // all in the same transformation.
+ float2 Cf = point0Param * (1 - f) + point1Param * f;
+ float3x3 transform = $map_to_unit_x(Cf, point1Param);
+
+ float scaleX = abs(1 - f);
+ float scaleY = scaleX;
+ float r1 = abs(radius1Param - radius0Param) / dCenter;
+ bool isFocalOnCircle = abs(r1 - 1) < SK_ScalarNearlyZero;
+ if (isFocalOnCircle) {
+ scaleX *= 0.5;
+ scaleY *= 0.5;
+ } else {
+ scaleX *= r1 / (r1 * r1 - 1);
+ scaleY /= sqrt(abs(r1 * r1 - 1));
+ }
+ transform = float3x3(
+ scaleX, 0, 0,
+ 0, scaleY, 0,
+ 0, 0, 1
+ ) * transform;
+
+ float2 pt = (transform * pos.xy1).xy;
+
+ // Continue with step 5 onward.
+ float invR1 = 1 / r1;
+ float dRadiusSign = sign(1 - f);
+ bool isWellBehaved = !isFocalOnCircle && r1 > 1;
+
+ float x_t = -1;
+ if (isFocalOnCircle) {
+ x_t = dot(pt, pt) / pt.x;
+ } else if (isWellBehaved) {
+ x_t = length(pt) - pt.x * invR1;
+ } else {
+ float temp = pt.x * pt.x - pt.y * pt.y;
+ if (temp >= 0) {
+ if (isSwapped || dRadiusSign < 0) {
+ x_t = -sqrt(temp) - pt.x * invR1;
+ } else {
+ x_t = sqrt(temp) - pt.x * invR1;
+ }
+ }
+ }
+
+ if (!isWellBehaved && x_t < 0) {
+ return float2(0, -1);
+ }
+
+ float t = f + dRadiusSign * x_t;
+ if (isSwapped) {
+ t = 1 - t;
+ }
+ return float2(t, 1);
+ }
+}
+
+$pure half4 sk_linear_grad_4_shader(float2 coords,
+ float4 colorsParam[4],
+ float offsetsParam[4],
+ float2 point0Param,
+ float2 point1Param,
+ int tileMode,
+ int colorSpace,
+ int doUnpremul) {
+ float2 t = $linear_grad_layout(point0Param, point1Param, coords);
+ t = $tile_grad(tileMode, t);
+ half4 color = $colorize_grad_4(colorsParam, offsetsParam, t);
+ return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
+}
+
+$pure half4 sk_linear_grad_8_shader(float2 coords,
+ float4 colorsParam[8],
+ float offsetsParam[8],
+ float2 point0Param,
+ float2 point1Param,
+ int tileMode,
+ int colorSpace,
+ int doUnpremul) {
+ float2 t = $linear_grad_layout(point0Param, point1Param, coords);
+ t = $tile_grad(tileMode, t);
+ half4 color = $colorize_grad_8(colorsParam, offsetsParam, t);
+ return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
+}
+
+$pure half4 sk_linear_grad_tex_shader(float2 coords,
+ float2 point0Param,
+ float2 point1Param,
+ int numStops,
+ int tileMode,
+ int colorSpace,
+ int doUnpremul,
+ sampler2D colorAndOffsetSampler) {
+ float2 t = $linear_grad_layout(point0Param, point1Param, coords);
+ t = $tile_grad(tileMode, t);
+ half4 color = $colorize_grad_tex(colorAndOffsetSampler, numStops, t);
+ return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
+}
+
+$pure half4 sk_radial_grad_4_shader(float2 coords,
+ float4 colorsParam[4],
+ float offsetsParam[4],
+ float2 centerParam,
+ float radiusParam,
+ int tileMode,
+ int colorSpace,
+ int doUnpremul) {
+ float2 t = $radial_grad_layout(centerParam, radiusParam, coords);
+ t = $tile_grad(tileMode, t);
+ half4 color = $colorize_grad_4(colorsParam, offsetsParam, t);
+ return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
+}
+
+$pure half4 sk_radial_grad_8_shader(float2 coords,
+ float4 colorsParam[8],
+ float offsetsParam[8],
+ float2 centerParam,
+ float radiusParam,
+ int tileMode,
+ int colorSpace,
+ int doUnpremul) {
+ float2 t = $radial_grad_layout(centerParam, radiusParam, coords);
+ t = $tile_grad(tileMode, t);
+ half4 color = $colorize_grad_8(colorsParam, offsetsParam, t);
+ return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
+}
+
+$pure half4 sk_radial_grad_tex_shader(float2 coords,
+ float2 centerParam,
+ float radiusParam,
+ int numStops,
+ int tileMode,
+ int colorSpace,
+ int doUnpremul,
+ sampler2D colorAndOffsetSampler) {
+ float2 t = $radial_grad_layout(centerParam, radiusParam, coords);
+ t = $tile_grad(tileMode, t);
+ half4 color = $colorize_grad_tex(colorAndOffsetSampler, numStops, t);
+ return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
+}
+
+$pure half4 sk_sweep_grad_4_shader(float2 coords,
+ float4 colorsParam[4],
+ float offsetsParam[4],
+ float2 centerParam,
+ float biasParam,
+ float scaleParam,
+ int tileMode,
+ int colorSpace,
+ int doUnpremul) {
+ float2 t = $sweep_grad_layout(centerParam, biasParam, scaleParam, coords);
+ t = $tile_grad(tileMode, t);
+ half4 color = $colorize_grad_4(colorsParam, offsetsParam, t);
+ return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
+}
+
+$pure half4 sk_sweep_grad_8_shader(float2 coords,
+ float4 colorsParam[8],
+ float offsetsParam[8],
+ float2 centerParam,
+ float biasParam,
+ float scaleParam,
+ int tileMode,
+ int colorSpace,
+ int doUnpremul) {
+ float2 t = $sweep_grad_layout(centerParam, biasParam, scaleParam, coords);
+ t = $tile_grad(tileMode, t);
+ half4 color = $colorize_grad_8(colorsParam, offsetsParam, t);
+ return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
+}
+
+$pure half4 sk_sweep_grad_tex_shader(float2 coords,
+ float2 centerParam,
+ float biasParam,
+ float scaleParam,
+ int numStops,
+ int tileMode,
+ int colorSpace,
+ int doUnpremul,
+ sampler2D colorAndOffsetSampler) {
+ float2 t = $sweep_grad_layout(centerParam, biasParam, scaleParam, coords);
+ t = $tile_grad(tileMode, t);
+ half4 color = $colorize_grad_tex(colorAndOffsetSampler, numStops, t);
+ return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
+}
+
+$pure half4 sk_conical_grad_4_shader(float2 coords,
+ float4 colorsParam[4],
+ float offsetsParam[4],
+ float2 point0Param,
+ float2 point1Param,
+ float radius0Param,
+ float radius1Param,
+ int tileMode,
+ int colorSpace,
+ int doUnpremul) {
+ float2 t = $conical_grad_layout(point0Param, point1Param, radius0Param, radius1Param, coords);
+ t = $tile_grad(tileMode, t);
+ half4 color = $colorize_grad_4(colorsParam, offsetsParam, t);
+ return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
+}
+
+$pure half4 sk_conical_grad_8_shader(float2 coords,
+ float4 colorsParam[8],
+ float offsetsParam[8],
+ float2 point0Param,
+ float2 point1Param,
+ float radius0Param,
+ float radius1Param,
+ int tileMode,
+ int colorSpace,
+ int doUnpremul) {
+ float2 t = $conical_grad_layout(point0Param, point1Param, radius0Param, radius1Param, coords);
+ t = $tile_grad(tileMode, t);
+ half4 color = $colorize_grad_8(colorsParam, offsetsParam, t);
+ return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
+}
+
+$pure half4 sk_conical_grad_tex_shader(float2 coords,
+ float2 point0Param,
+ float2 point1Param,
+ float radius0Param,
+ float radius1Param,
+ int numStops,
+ int tileMode,
+ int colorSpace,
+ int doUnpremul,
+ sampler2D colorAndOffsetSampler) {
+ float2 t = $conical_grad_layout(point0Param, point1Param, radius0Param, radius1Param, coords);
+ t = $tile_grad(tileMode, t);
+ half4 color = $colorize_grad_tex(colorAndOffsetSampler, numStops, t);
+ return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul);
+}
+
+$pure half4 sk_matrix_colorfilter(half4 colorIn, float4x4 m, float4 v, int inHSLA) {
+ if (bool(inHSLA)) {
+ colorIn = $rgb_to_hsl(colorIn.rgb, colorIn.a); // includes unpremul
+ } else {
+ colorIn = unpremul(colorIn);
+ }
+
+ half4 colorOut = half4((m * colorIn) + v);
+
+ if (bool(inHSLA)) {
+ colorOut = $hsl_to_rgb(colorOut.rgb, colorOut.a); // includes clamp and premul
+ } else {
+ colorOut = saturate(colorOut);
+ colorOut.rgb *= colorOut.a;
+ }
+
+ return colorOut;
+}
+
+// This method computes the 4 x-coodinates ([0..1]) that should be used to look
+// up in the Perlin noise shader's noise table.
+$pure half4 noise_helper(half2 noiseVec,
+ half2 stitchData,
+ int stitching,
+ sampler2D permutationSampler) {
+ const half kBlockSize = 256.0;
+
+ half4 floorVal;
+ floorVal.xy = floor(noiseVec);
+ floorVal.zw = floorVal.xy + half2(1);
+
+ // Adjust frequencies if we're stitching tiles
+ if (bool(stitching)) {
+ if (floorVal.x >= stitchData.x) { floorVal.x -= stitchData.x; };
+ if (floorVal.y >= stitchData.y) { floorVal.y -= stitchData.y; };
+ if (floorVal.z >= stitchData.x) { floorVal.z -= stitchData.x; };
+ if (floorVal.w >= stitchData.y) { floorVal.w -= stitchData.y; };
+ }
+
+ half sampleX = sample(permutationSampler, half2(floorVal.x/kBlockSize, 0.5)).r;
+ half sampleY = sample(permutationSampler, half2(floorVal.z/kBlockSize, 0.5)).r;
+
+ half2 latticeIdx = half2(sampleX, sampleY);
+
+ const half kInv255 = 0.003921569; // 1.0 / 255.0
+
+ // Aggressively round to the nearest exact (N / 255) floating point values.
+ // This prevents rounding errors on some platforms (e.g., Tegras)
+ latticeIdx = floor(latticeIdx * half2(255.0) + half2(0.5)) * half2(kInv255);
+
+ // Get (x,y) coordinates with the permuted x
+ half4 noiseXCoords = kBlockSize*latticeIdx.xyxy + floorVal.yyww;
+
+ noiseXCoords /= half4(kBlockSize);
+ return noiseXCoords;
+}
+
+// TODO: Move this to sksl_shared.sksl and try to share with Ganesh
+$pure half4 noise_function(half2 noiseVec,
+ half4 noiseXCoords,
+ sampler2D noiseSampler) {
+
+ half2 fractVal = fract(noiseVec);
+
+ // smooth curve : t^2*(3 - 2*t)
+ half2 noiseSmooth = fractVal*fractVal*(half2(3) - 2*fractVal);
+
+ // This is used to convert the two 16bit integers packed into rgba 8 bit input into
+ // a [-1,1] vector
+ const half kInv256 = 0.00390625; // 1.0 / 256.0
+
+ half4 result;
+
+ for (int channel = 0; channel < 4; channel++) {
+
+ // There are 4 lines in the noise texture, put y coords at center of each.
+ half chanCoord = (half(channel) + 0.5) / 4.0;
+
+ half4 sampleA = sample(noiseSampler, half2(noiseXCoords.x, chanCoord));
+ half4 sampleB = sample(noiseSampler, half2(noiseXCoords.y, chanCoord));
+ half4 sampleC = sample(noiseSampler, half2(noiseXCoords.w, chanCoord));
+ half4 sampleD = sample(noiseSampler, half2(noiseXCoords.z, chanCoord));
+
+ half2 uv;
+ half2 tmpFractVal = fractVal;
+
+ // Compute u, at offset (0,0)
+ uv.x = dot((sampleA.ga + sampleA.rb*kInv256)*2 - half2(1), tmpFractVal);
+
+ // Compute v, at offset (-1,0)
+ tmpFractVal.x -= 1.0;
+ uv.y = dot((sampleB.ga + sampleB.rb*kInv256)*2 - half2(1), tmpFractVal);
+
+ // Compute 'a' as a linear interpolation of 'u' and 'v'
+ half2 ab;
+ ab.x = mix(uv.x, uv.y, noiseSmooth.x);
+
+ // Compute v, at offset (-1,-1)
+ tmpFractVal.y -= 1.0;
+ uv.y = dot((sampleC.ga + sampleC.rb*kInv256)*2 - half2(1), tmpFractVal);
+
+ // Compute u, at offset (0,-1)
+ tmpFractVal.x += 1.0;
+ uv.x = dot((sampleD.ga + sampleD.rb*kInv256)*2 - half2(1), tmpFractVal);
+
+ // Compute 'b' as a linear interpolation of 'u' and 'v'
+ ab.y = mix(uv.x, uv.y, noiseSmooth.x);
+
+ // Compute the noise as a linear interpolation of 'a' and 'b'
+ result[channel] = mix(ab.x, ab.y, noiseSmooth.y);
+ }
+
+ return result;
+}
+
+// permutationSampler is [kBlockSize x 1] A8
+// noiseSampler is [kBlockSize x 4] RGBA8 premul
+$pure half4 perlin_noise_shader(float2 coords,
+ float2 baseFrequency,
+ float2 stitchDataIn,
+ int noiseType,
+ int numOctaves,
+ int stitching,
+ sampler2D permutationSampler,
+ sampler2D noiseSampler) {
+ const int kFractalNoise_Type = 0;
+ const int kTurbulence_Type = 1;
+
+ // There are rounding errors if the floor operation is not performed here
+ half2 noiseVec = half2(floor(coords.xy) * baseFrequency);
+
+ // Clear the color accumulator
+ half4 color = half4(0);
+
+ half2 stitchData = half2(stitchDataIn);
+
+ half ratio = 1.0;
+
+ // Loop over all octaves
+ for (int octave = 0; octave < numOctaves; ++octave) {
+ half4 noiseXCoords = noise_helper(noiseVec, stitchData, stitching, permutationSampler);
+
+ half4 tmp = noise_function(noiseVec, noiseXCoords, noiseSampler);
+
+ if (noiseType != kFractalNoise_Type) {
+ // For kTurbulence_Type the result is: abs(noise[-1,1])
+ tmp = abs(tmp);
+ }
+
+ tmp *= ratio;
+ color += tmp;
+
+ noiseVec *= half2(2.0);
+ ratio *= 0.5;
+ stitchData *= half2(2.0);
+ }
+
+ if (noiseType == kFractalNoise_Type) {
+ // For kFractalNoise_Type the result is: noise[-1,1] * 0.5 + 0.5
+ color = color * half4(0.5) + half4(0.5);
+ }
+
+ // Clamp values
+ color = saturate(color);
+
+ // Pre-multiply the result
+ return half4(color.rgb * color.aaa, color.a);
+}
+
+$pure half4 sk_blend(int blendMode, half4 src, half4 dst) {
+ const int kClear = 0;
+ const int kSrc = 1;
+ const int kDst = 2;
+ const int kSrcOver = 3;
+ const int kDstOver = 4;
+ const int kSrcIn = 5;
+ const int kDstIn = 6;
+ const int kSrcOut = 7;
+ const int kDstOut = 8;
+ const int kSrcATop = 9;
+ const int kDstATop = 10;
+ const int kXor = 11;
+ const int kPlus = 12;
+ const int kModulate = 13;
+ const int kScreen = 14;
+ const int kOverlay = 15;
+ const int kDarken = 16;
+ const int kLighten = 17;
+ const int kColorDodge = 18;
+ const int kColorBurn = 19;
+ const int kHardLight = 20;
+ const int kSoftLight = 21;
+ const int kDifference = 22;
+ const int kExclusion = 23;
+ const int kMultiply = 24;
+ const int kHue = 25;
+ const int kSaturation = 26;
+ const int kColor = 27;
+ const int kLuminosity = 28;
+
+ switch (blendMode) {
+ case kClear: { return blend_clear(src, dst); }
+ case kSrc: { return blend_src(src, dst); }
+ case kDst: { return blend_dst(src, dst); }
+ case kSrcOver: { return blend_porter_duff(half4(1, 0, 0, -1), src, dst); }
+ case kDstOver: { return blend_porter_duff(half4(0, 1, -1, 0), src, dst); }
+ case kSrcIn: { return blend_porter_duff(half4(0, 0, 1, 0), src, dst); }
+ case kDstIn: { return blend_porter_duff(half4(0, 0, 0, 1), src, dst); }
+ case kSrcOut: { return blend_porter_duff(half4(0, 0, -1, 0), src, dst); }
+ case kDstOut: { return blend_porter_duff(half4(0, 0, 0, -1), src, dst); }
+ case kSrcATop: { return blend_porter_duff(half4(0, 0, 1, -1), src, dst); }
+ case kDstATop: { return blend_porter_duff(half4(0, 0, -1, 1), src, dst); }
+ case kXor: { return blend_porter_duff(half4(0, 0, -1, -1), src, dst); }
+ case kPlus: { return blend_porter_duff(half4(1, 1, 0, 0), src, dst); }
+ case kModulate: { return blend_modulate(src, dst); }
+ case kScreen: { return blend_screen(src, dst); }
+ case kOverlay: { return blend_overlay(/*flip=*/0, src, dst); }
+ case kDarken: { return blend_darken(/*mode=*/1, src, dst); }
+ case kLighten: { return blend_darken(/*mode=*/-1, src, dst); }
+ case kColorDodge: { return blend_color_dodge(src, dst); }
+ case kColorBurn: { return blend_color_burn(src, dst); }
+ case kHardLight: { return blend_overlay(/*flip=*/1, src, dst); }
+ case kSoftLight: { return blend_soft_light(src, dst); }
+ case kDifference: { return blend_difference(src, dst); }
+ case kExclusion: { return blend_exclusion(src, dst); }
+ case kMultiply: { return blend_multiply(src, dst); }
+ case kHue: { return blend_hslc(/*flipSat=*/half2(0, 1), src, dst); }
+ case kSaturation: { return blend_hslc(/*flipSat=*/half2(1), src, dst); }
+ case kColor: { return blend_hslc(/*flipSat=*/half2(0), src, dst); }
+ case kLuminosity: { return blend_hslc(/*flipSat=*/half2(1, 0), src, dst); }
+ default: return half4(0); // Avoids 'blend can exit without returning a value' error
+ }
+}
+
+$pure half4 sk_blend_shader(int blendMode, half4 dst, half4 src) {
+ return sk_blend(blendMode, src, dst);
+}
+
+$pure half4 porter_duff_blend_shader(half4 blendOp, half4 dst, half4 src) {
+ return blend_porter_duff(blendOp, src, dst);
+}
+
+$pure half4 sk_blend_colorfilter(half4 dstColor, int blendMode, float4 srcColor) {
+ return sk_blend(blendMode, half4(srcColor), dstColor);
+}
+
+$pure half4 sk_table_colorfilter(half4 inColor, sampler2D s) {
+ half4 coords = unpremul(inColor) * 255.0/256.0 + 0.5/256.0;
+ half4 color = half4(sample(s, half2(coords.r, 3.0/8.0)).r,
+ sample(s, half2(coords.g, 5.0/8.0)).r,
+ sample(s, half2(coords.b, 7.0/8.0)).r,
+ 1);
+ return color * sample(s, half2(coords.a, 1.0/8.0)).r;
+}
+
+$pure half4 sk_gaussian_colorfilter(half4 inColor) {
+ half factor = 1 - inColor.a;
+ factor = exp(-factor * factor * 4) - 0.018;
+ return half4(factor);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Support functions for analytic round rectangles
+
+// Calculates 1/|∇| in device space by applying the chain rule to a local gradient vector and the
+// 2x2 Jacobian describing the transform from local-to-device space. For non-perspective, this is
+// equivalent to the "normal matrix", or the inverse transpose. For perspective, J should be
+// W(u,v) [m00' - m20'u m01' - m21'u] derived from the first two columns of the 3x3 inverse.
+// [m10' - m20'v m11' - m21'v]
+$pure float inverse_grad_len(float2 localGrad, float2x2 jacobian) {
+ // NOTE: By chain rule, the local gradient is on the left side of the Jacobian matrix
+ float2 devGrad = localGrad * jacobian;
+ // NOTE: This uses the L2 norm, which is more accurate than the L1 norm used by fwidth().
+ // TODO: Switch to L1 since it is a 2x perf improvement according to Xcode with little visual
+ // impact, but start with L2 to measure the change separately from the algorithmic update.
+ // return 1.0 / (abs(devGrad.x) + abs(devGrad.y));
+ return inversesqrt(dot(devGrad, devGrad));
+}
+
+// Returns distance from both sides of a stroked circle or ellipse. Elliptical coverage is
+// only accurate if strokeRadius = 0. A positive value represents the interior of the stroke.
+$pure float2 elliptical_distance(float2 uv, float2 radii, float strokeRadius, float2x2 jacobian) {
+ // We do need to evaluate up to two circle equations: one with
+ // R = cornerRadius(r)+strokeRadius(s), and another with R = r-s.
+ // This can be consolidated into a common evaluation against a circle of radius sqrt(r^2+s^2):
+ // (x/(r+/-s))^2 + (y/(r+/-s))^2 = 1
+ // x^2 + y^2 = (r+/-s)^2
+ // x^2 + y^2 = r^2 + s^2 +/- 2rs
+ // (x/sqrt(r^2+s^2))^2 + (y/sqrt(r^2+s^2)) = 1 +/- 2rs/(r^2+s^2)
+ // The 2rs/(r^2+s^2) is the "width" that adjusts the implicit function to the outer or inner
+ // edge of the stroke. For fills and hairlines, s = 0, which means these operations remain valid
+ // for elliptical corners where radii holds the different X and Y corner radii.
+ float2 invR2 = 1.0 / (radii * radii + strokeRadius*strokeRadius);
+ float2 normUV = invR2 * uv;
+ float invGradLength = inverse_grad_len(normUV, jacobian);
+
+ // Since normUV already includes 1/r^2 in the denominator, dot with just 'uv' instead.
+ float f = 0.5 * invGradLength * (dot(uv, normUV) - 1.0);
+
+ // This is 0 for fills/hairlines, which are the only types that allow
+ // elliptical corners (strokeRadius == 0). For regular strokes just use X.
+ float width = radii.x * strokeRadius * invR2.x * invGradLength;
+ return float2(width - f, width + f);
+}
+
+// Accumulates the minimum (and negative maximum) of the outer and inner corner distances in 'dist'
+// for a possibly elliptical corner with 'radii' and relative pixel location specified by
+// 'cornerEdgeDist'. The corner's basis relative to the jacobian is defined in 'xyFlip'.
+void corner_distance(inout float2 dist,
+ float2x2 jacobian,
+ float2 strokeParams,
+ float2 cornerEdgeDist,
+ float2 xyFlip,
+ float2 radii) {
+ float2 uv = radii - cornerEdgeDist;
+ // NOTE: For mitered corners uv > 0 only if it's stroked, and in that case the
+ // subsequent conditions skip calculating anything.
+ if (uv.x > 0.0 && uv.y > 0.0) {
+ if ((radii.x > 0.0 && radii.y > 0.0) ||
+ (strokeParams.x > 0.0 && strokeParams.y < 0.0 /* round-join */)) {
+ // A rounded corner so incorporate outer elliptical distance if we're within the
+ // quarter circle.
+ float2 d = elliptical_distance(uv * xyFlip, radii, strokeParams.x, jacobian);
+ if (radii.x - strokeParams.x <= 0.0) {
+ d.y = 1.0; // disregard inner curve since it's collapsed into an inner miter.
+ } else {
+ d.y *= -1.0; // Negate so that "min" accumulates the maximum value instead
+ }
+ dist = min(dist, d);
+ } else if (strokeParams.y == 0.0 /* bevel-join */) {
+ // Bevels are--by construction--interior mitered, so inner distance is based
+ // purely on the edge distance calculations, but the outer distance is to a 45-degree
+ // line and not the vertical/horizontal lines of the other edges.
+ float bevelDist = (strokeParams.x - uv.x - uv.y) * inverse_grad_len(xyFlip, jacobian);
+ dist.x = min(dist.x, bevelDist);
+ } // Else it's a miter so both inner and outer distances are unmodified
+ } // Else we're not affected by the corner so leave distances unmodified
+}
+
+// Accumulates the minimum (and negative maximum) of the outer and inner corner distances into 'd',
+// for all four corners of a [round] rectangle. 'edgeDists' should be ordered LTRB with positive
+// distance representing the interior of the edge. 'xRadii' and 'yRadii' should hold the per-corner
+// elliptical radii, ordered TL, TR, BR, BL.
+void corner_distances(inout float2 d,
+ float2x2 J,
+ float2 stroke, // {radii, joinStyle}, see StrokeStyle struct definition
+ float4 edgeDists,
+ float4 xRadii,
+ float4 yRadii) {
+ corner_distance(d, J, stroke, edgeDists.xy, float2(-1.0, -1.0), float2(xRadii[0], yRadii[0]));
+ corner_distance(d, J, stroke, edgeDists.zy, float2( 1.0, -1.0), float2(xRadii[1], yRadii[1]));
+ corner_distance(d, J, stroke, edgeDists.zw, float2( 1.0, 1.0), float2(xRadii[2], yRadii[2]));
+ corner_distance(d, J, stroke, edgeDists.xw, float2(-1.0, 1.0), float2(xRadii[3], yRadii[3]));
+}
diff --git a/gfx/skia/skia/src/sksl/sksl_graphite_vert.sksl b/gfx/skia/skia/src/sksl/sksl_graphite_vert.sksl
new file mode 100644
index 0000000000..4f20c0bca5
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/sksl_graphite_vert.sksl
@@ -0,0 +1,535 @@
+// Graphite-specific vertex shader code
+
+const float $PI = 3.141592653589793238;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Support functions for tessellating path renderers
+
+const float $kCubicCurveType = 0; // skgpu::tess::kCubicCurveType
+const float $kConicCurveType = 1; // skgpu::tess::kConicCurveType
+const float $kTriangularConicCurveType = 2; // skgpu::tess::kTriangularConicCurveType
+
+// This function can be used on GPUs with infinity support to infer the curve type from the specific
+// path control-point encoding used by tessellating path renderers. Calling this function on a
+// platform that lacks infinity support may result in a shader compilation error.
+$pure float curve_type_using_inf_support(float4 p23) {
+ if (isinf(p23.z)) {
+ return $kTriangularConicCurveType;
+ }
+ if (isinf(p23.w)) {
+ return $kConicCurveType;
+ }
+ return $kCubicCurveType;
+}
+
+$pure bool $is_conic_curve(float curveType) {
+ return curveType != $kCubicCurveType;
+}
+
+$pure bool $is_triangular_conic_curve(float curveType) {
+ return curveType == $kTriangularConicCurveType;
+}
+
+// Wang's formula gives the minimum number of evenly spaced (in the parametric sense) line segments
+// that a bezier curve must be chopped into in order to guarantee all lines stay within a distance
+// of "1/precision" pixels from the true curve. Its definition for a bezier curve of degree "n" is
+// as follows:
+//
+// maxLength = max([length(p[i+2] - 2p[i+1] + p[i]) for (0 <= i <= n-2)])
+// numParametricSegments = sqrt(maxLength * precision * n*(n - 1)/8)
+//
+// (Goldman, Ron. (2003). 5.6.3 Wang's Formula. "Pyramid Algorithms: A Dynamic Programming Approach
+// to Curves and Surfaces for Geometric Modeling". Morgan Kaufmann Publishers.)
+
+const float $kDegree = 3;
+const float $kPrecision = 4; // Must match skgpu::tess::kPrecision
+const float $kLengthTerm = ($kDegree * ($kDegree - 1) / 8.0) * $kPrecision;
+const float $kLengthTermPow2 = (($kDegree * $kDegree) * (($kDegree - 1) * ($kDegree - 1)) / 64.0) *
+ ($kPrecision * $kPrecision);
+
+// Returns the length squared of the largest forward difference from Wang's cubic formula.
+$pure float $wangs_formula_max_fdiff_p2(float2 p0, float2 p1, float2 p2, float2 p3,
+ float2x2 matrix) {
+ float2 d0 = matrix * (fma(float2(-2), p1, p2) + p0);
+ float2 d1 = matrix * (fma(float2(-2), p2, p3) + p1);
+ return max(dot(d0,d0), dot(d1,d1));
+}
+
+$pure float $wangs_formula_cubic(float2 p0, float2 p1, float2 p2, float2 p3,
+ float2x2 matrix) {
+ float m = $wangs_formula_max_fdiff_p2(p0, p1, p2, p3, matrix);
+ return max(ceil(sqrt($kLengthTerm * sqrt(m))), 1.0);
+}
+
+$pure float $wangs_formula_cubic_log2(float2 p0, float2 p1, float2 p2, float2 p3,
+ float2x2 matrix) {
+ float m = $wangs_formula_max_fdiff_p2(p0, p1, p2, p3, matrix);
+ return ceil(log2(max($kLengthTermPow2 * m, 1.0)) * .25);
+}
+
+$pure float $wangs_formula_conic_p2(float2 p0, float2 p1, float2 p2, float w) {
+ // Translate the bounding box center to the origin.
+ float2 C = (min(min(p0, p1), p2) + max(max(p0, p1), p2)) * 0.5;
+ p0 -= C;
+ p1 -= C;
+ p2 -= C;
+
+ // Compute max length.
+ float m = sqrt(max(max(dot(p0,p0), dot(p1,p1)), dot(p2,p2)));
+
+ // Compute forward differences.
+ float2 dp = fma(float2(-2.0 * w), p1, p0) + p2;
+ float dw = abs(fma(-2.0, w, 2.0));
+
+ // Compute numerator and denominator for parametric step size of linearization. Here, the
+ // epsilon referenced from the cited paper is 1/precision.
+ float rp_minus_1 = max(0.0, fma(m, $kPrecision, -1.0));
+ float numer = length(dp) * $kPrecision + rp_minus_1 * dw;
+ float denom = 4 * min(w, 1.0);
+
+ return numer/denom;
+}
+
+$pure float $wangs_formula_conic(float2 p0, float2 p1, float2 p2, float w) {
+ float n2 = $wangs_formula_conic_p2(p0, p1, p2, w);
+ return max(ceil(sqrt(n2)), 1.0);
+}
+
+$pure float $wangs_formula_conic_log2(float2 p0, float2 p1, float2 p2, float w) {
+ float n2 = $wangs_formula_conic_p2(p0, p1, p2, w);
+ return ceil(log2(max(n2, 1.0)) * .5);
+}
+
+// Returns the normalized difference between a and b, i.e. normalize(a - b), with care taken for
+// if 'a' and/or 'b' have large coordinates.
+$pure float2 $robust_normalize_diff(float2 a, float2 b) {
+ float2 diff = a - b;
+ if (diff == float2(0.0)) {
+ return float2(0.0);
+ } else {
+ float invMag = 1.0 / max(abs(diff.x), abs(diff.y));
+ return normalize(invMag * diff);
+ }
+}
+
+// Returns the cosine of the angle between a and b, assuming a and b are unit vectors already.
+// Guaranteed to be between [-1, 1].
+$pure float $cosine_between_unit_vectors(float2 a, float2 b) {
+ // Since a and b are assumed to be normalized, the cosine is equal to the dot product, although
+ // we clamp that to ensure it falls within the expected range of [-1, 1].
+ return clamp(dot(a, b), -1.0, 1.0);
+}
+
+// Extends the middle radius to either the miter point, or the bevel edge if we surpassed the
+// miter limit and need to revert to a bevel join.
+$pure float $miter_extent(float cosTheta, float miterLimit) {
+ float x = fma(cosTheta, .5, .5);
+ return (x * miterLimit * miterLimit >= 1.0) ? inversesqrt(x) : sqrt(x);
+}
+
+// Returns the number of radial segments required for each radian of rotation, in order for the
+// curve to appear "smooth" as defined by the approximate device-space stroke radius.
+$pure float $num_radial_segments_per_radian(float approxDevStrokeRadius) {
+ return .5 / acos(max(1.0 - (1.0 / $kPrecision) / approxDevStrokeRadius, -1.0));
+}
+
+// Unlike mix(), this does not return b when t==1. But it otherwise seems to get better
+// precision than "a*(1 - t) + b*t" for things like chopping cubics on exact cusp points.
+// We override this result anyway when t==1 so it shouldn't be a problem.
+$pure float $unchecked_mix(float a, float b, float T) {
+ return fma(b - a, T, a);
+}
+$pure float2 $unchecked_mix(float2 a, float2 b, float T) {
+ return fma(b - a, float2(T), a);
+}
+$pure float4 $unchecked_mix(float4 a, float4 b, float4 T) {
+ return fma(b - a, T, a);
+}
+
+// Compute a vertex position for the curve described by p01 and p23 packed control points,
+// tessellated to the given resolve level, and assuming it will be drawn as a filled curve.
+$pure float2 tessellate_filled_curve(float2x2 vectorXform,
+ float resolveLevel, float idxInResolveLevel,
+ float4 p01, float4 p23,
+ float curveType) {
+ float2 localcoord;
+ if ($is_triangular_conic_curve(curveType)) {
+ // This patch is an exact triangle.
+ localcoord = (resolveLevel != 0) ? p01.zw
+ : (idxInResolveLevel != 0) ? p23.xy
+ : p01.xy;
+ } else {
+ float2 p0=p01.xy, p1=p01.zw, p2=p23.xy, p3=p23.zw;
+ float w = -1; // w < 0 tells us to treat the instance as an integral cubic.
+ float maxResolveLevel;
+ if ($is_conic_curve(curveType)) {
+ // Conics are 3 points, with the weight in p3.
+ w = p3.x;
+ maxResolveLevel = $wangs_formula_conic_log2(vectorXform*p0,
+ vectorXform*p1,
+ vectorXform*p2, w);
+ p1 *= w; // Unproject p1.
+ p3 = p2; // Duplicate the endpoint for shared code that also runs on cubics.
+ } else {
+ // The patch is an integral cubic.
+ maxResolveLevel = $wangs_formula_cubic_log2(p0, p1, p2, p3, vectorXform);
+ }
+ if (resolveLevel > maxResolveLevel) {
+ // This vertex is at a higher resolve level than we need. Demote to a lower
+ // resolveLevel, which will produce a degenerate triangle.
+ idxInResolveLevel = floor(ldexp(idxInResolveLevel,
+ int(maxResolveLevel - resolveLevel)));
+ resolveLevel = maxResolveLevel;
+ }
+ // Promote our location to a discrete position in the maximum fixed resolve level.
+ // This is extra paranoia to ensure we get the exact same fp32 coordinates for
+ // colocated points from different resolve levels (e.g., the vertices T=3/4 and
+ // T=6/8 should be exactly colocated).
+ float fixedVertexID = floor(.5 + ldexp(idxInResolveLevel, int(5 - resolveLevel)));
+ if (0 < fixedVertexID && fixedVertexID < 32) {
+ float T = fixedVertexID * (1 / 32.0);
+
+ // Evaluate at T. Use De Casteljau's for its accuracy and stability.
+ float2 ab = mix(p0, p1, T);
+ float2 bc = mix(p1, p2, T);
+ float2 cd = mix(p2, p3, T);
+ float2 abc = mix(ab, bc, T);
+ float2 bcd = mix(bc, cd, T);
+ float2 abcd = mix(abc, bcd, T);
+
+ // Evaluate the conic weight at T.
+ float u = mix(1.0, w, T);
+ float v = w + 1 - u; // == mix(w, 1, T)
+ float uv = mix(u, v, T);
+
+ localcoord = (w < 0) ? /*cubic*/ abcd : /*conic*/ abc/uv;
+ } else {
+ localcoord = (fixedVertexID == 0) ? p0.xy : p3.xy;
+ }
+ }
+ return localcoord;
+}
+
+// Device coords are in xy, local coords are in zw, since for now perspective isn't supported.
+$pure float4 tessellate_stroked_curve(float edgeID, float maxEdges,
+ float2x2 affineMatrix,
+ float2 translate,
+ float maxScale /* derived from affineMatrix */,
+ float4 p01, float4 p23,
+ float2 lastControlPoint,
+ float2 strokeParams,
+ float curveType) {
+ float2 p0=p01.xy, p1=p01.zw, p2=p23.xy, p3=p23.zw;
+ float w = -1; // w<0 means the curve is an integral cubic.
+ if ($is_conic_curve(curveType)) {
+ // Conics are 3 points, with the weight in p3.
+ w = p3.x;
+ p3 = p2; // Setting p3 equal to p2 works for the remaining rotational logic.
+ }
+
+ // Call Wang's formula to determine parametric segments before transform points for hairlines
+ // so that it is consistent with how the CPU tested the control points for chopping.
+ float numParametricSegments;
+ if (w < 0) {
+ if (p0 == p1 && p2 == p3) {
+ numParametricSegments = 1; // a line
+ } else {
+ numParametricSegments = $wangs_formula_cubic(p0, p1, p2, p3, affineMatrix);
+ }
+ } else {
+ numParametricSegments = $wangs_formula_conic(affineMatrix * p0,
+ affineMatrix * p1,
+ affineMatrix * p2, w);
+ }
+
+ // Matches skgpu::tess::StrokeParams
+ float strokeRadius = strokeParams.x;
+ float joinType = strokeParams.y; // <0 = round join, ==0 = bevel join, >0 encodes miter limit
+ bool isHairline = strokeParams.x == 0.0;
+ float numRadialSegmentsPerRadian;
+ if (isHairline) {
+ numRadialSegmentsPerRadian = $num_radial_segments_per_radian(1.0);
+ strokeRadius = 0.5;
+ } else {
+ numRadialSegmentsPerRadian = $num_radial_segments_per_radian(maxScale * strokeParams.x);
+ }
+
+ if (isHairline) {
+ // Hairline case. Transform the points before tessellation. We can still hold off on the
+ // translate until the end; we just need to perform the scale and skew right now.
+ p0 = affineMatrix * p0;
+ p1 = affineMatrix * p1;
+ p2 = affineMatrix * p2;
+ p3 = affineMatrix * p3;
+ lastControlPoint = affineMatrix * lastControlPoint;
+ }
+
+ // Find the starting and ending tangents.
+ float2 tan0 = $robust_normalize_diff((p0 == p1) ? ((p1 == p2) ? p3 : p2) : p1, p0);
+ float2 tan1 = $robust_normalize_diff(p3, (p3 == p2) ? ((p2 == p1) ? p0 : p1) : p2);
+ if (tan0 == float2(0)) {
+ // The stroke is a point. This special case tells us to draw a stroke-width circle as a
+ // 180 degree point stroke instead.
+ tan0 = float2(1,0);
+ tan1 = float2(-1,0);
+ }
+
+ // Determine how many edges to give to the join. We emit the first and final edges
+ // of the join twice: once full width and once restricted to half width. This guarantees
+ // perfect seaming by matching the vertices from the join as well as from the strokes on
+ // either side.
+ float numEdgesInJoin;
+ if (joinType >= 0 /*Is the join not a round type?*/) {
+ // Bevel(0) and miter(+) joins get 1 and 2 segments respectively.
+ // +2 because we emit the beginning and ending edges twice (see above comments).
+ numEdgesInJoin = sign(joinType) + 1 + 2;
+ } else {
+ float2 prevTan = $robust_normalize_diff(p0, lastControlPoint);
+ float joinRads = acos($cosine_between_unit_vectors(prevTan, tan0));
+ float numRadialSegmentsInJoin = max(ceil(joinRads * numRadialSegmentsPerRadian), 1);
+ // +2 because we emit the beginning and ending edges twice (see above comment).
+ numEdgesInJoin = numRadialSegmentsInJoin + 2;
+ // The stroke section needs at least two edges. Don't assign more to the join than
+ // "maxEdges - 2". (This is only relevant when the ideal max edge count calculated
+ // on the CPU had to be limited to maxEdges in the draw call).
+ numEdgesInJoin = min(numEdgesInJoin, maxEdges - 2);
+ }
+
+ // Find which direction the curve turns.
+ // NOTE: Since the curve is not allowed to inflect, we can just check F'(.5) x F''(.5).
+ // NOTE: F'(.5) x F''(.5) has the same sign as (P2 - P0) x (P3 - P1)
+ float turn = cross_length_2d(p2 - p0, p3 - p1);
+ float combinedEdgeID = abs(edgeID) - numEdgesInJoin;
+ if (combinedEdgeID < 0) {
+ tan1 = tan0;
+ // Don't let tan0 become zero. The code as-is isn't built to handle that case. tan0=0
+ // means the join is disabled, and to disable it with the existing code we can leave
+ // tan0 equal to tan1.
+ if (lastControlPoint != p0) {
+ tan0 = $robust_normalize_diff(p0, lastControlPoint);
+ }
+ turn = cross_length_2d(tan0, tan1);
+ }
+
+ // Calculate the curve's starting angle and rotation.
+ float cosTheta = $cosine_between_unit_vectors(tan0, tan1);
+ float rotation = acos(cosTheta);
+ if (turn < 0) {
+ // Adjust sign of rotation to match the direction the curve turns.
+ rotation = -rotation;
+ }
+
+ float numRadialSegments;
+ float strokeOutset = sign(edgeID);
+ if (combinedEdgeID < 0) {
+ // We belong to the preceding join. The first and final edges get duplicated, so we only
+ // have "numEdgesInJoin - 2" segments.
+ numRadialSegments = numEdgesInJoin - 2;
+ numParametricSegments = 1; // Joins don't have parametric segments.
+ p3 = p2 = p1 = p0; // Colocate all points on the junction point.
+ // Shift combinedEdgeID to the range [-1, numRadialSegments]. This duplicates the first
+ // edge and lands one edge at the very end of the join. (The duplicated final edge will
+ // actually come from the section of our strip that belongs to the stroke.)
+ combinedEdgeID += numRadialSegments + 1;
+ // We normally restrict the join on one side of the junction, but if the tangents are
+ // nearly equivalent this could theoretically result in bad seaming and/or cracks on the
+ // side we don't put it on. If the tangents are nearly equivalent then we leave the join
+ // double-sided.
+ float sinEpsilon = 1e-2; // ~= sin(180deg / 3000)
+ bool tangentsNearlyParallel =
+ (abs(turn) * inversesqrt(dot(tan0, tan0) * dot(tan1, tan1))) < sinEpsilon;
+ if (!tangentsNearlyParallel || dot(tan0, tan1) < 0) {
+ // There are two edges colocated at the beginning. Leave the first one double sided
+ // for seaming with the previous stroke. (The double sided edge at the end will
+ // actually come from the section of our strip that belongs to the stroke.)
+ if (combinedEdgeID >= 0) {
+ strokeOutset = (turn < 0) ? min(strokeOutset, 0) : max(strokeOutset, 0);
+ }
+ }
+ combinedEdgeID = max(combinedEdgeID, 0);
+ } else {
+ // We belong to the stroke. Unless numRadialSegmentsPerRadian is incredibly high,
+ // clamping to maxCombinedSegments will be a no-op because the draw call was invoked with
+ // sufficient vertices to cover the worst case scenario of 180 degree rotation.
+ float maxCombinedSegments = maxEdges - numEdgesInJoin - 1;
+ numRadialSegments = max(ceil(abs(rotation) * numRadialSegmentsPerRadian), 1);
+ numRadialSegments = min(numRadialSegments, maxCombinedSegments);
+ numParametricSegments = min(numParametricSegments,
+ maxCombinedSegments - numRadialSegments + 1);
+ }
+
+ // Additional parameters for final tessellation evaluation.
+ float radsPerSegment = rotation / numRadialSegments;
+ float numCombinedSegments = numParametricSegments + numRadialSegments - 1;
+ bool isFinalEdge = (combinedEdgeID >= numCombinedSegments);
+ if (combinedEdgeID > numCombinedSegments) {
+ strokeOutset = 0; // The strip has more edges than we need. Drop this one.
+ }
+ // Edge #2 extends to the miter point.
+ if (abs(edgeID) == 2 && joinType > 0/*Is the join a miter type?*/) {
+ strokeOutset *= $miter_extent(cosTheta, joinType/*miterLimit*/);
+ }
+
+ float2 tangent, strokeCoord;
+ if (combinedEdgeID != 0 && !isFinalEdge) {
+ // Compute the location and tangent direction of the stroke edge with the integral id
+ // "combinedEdgeID", where combinedEdgeID is the sorted-order index of parametric and radial
+ // edges. Start by finding the tangent function's power basis coefficients. These define a
+ // tangent direction (scaled by some uniform value) as:
+ // |T^2|
+ // Tangent_Direction(T) = dx,dy = |A 2B C| * |T |
+ // |. . .| |1 |
+ float2 A, B, C = p1 - p0;
+ float2 D = p3 - p0;
+ if (w >= 0.0) {
+ // P0..P2 represent a conic and P3==P2. The derivative of a conic has a cumbersome
+ // order-4 denominator. However, this isn't necessary if we are only interested in a
+ // vector in the same *direction* as a given tangent line. Since the denominator scales
+ // dx and dy uniformly, we can throw it out completely after evaluating the derivative
+ // with the standard quotient rule. This leaves us with a simpler quadratic function
+ // that we use to find a tangent.
+ C *= w;
+ B = .5*D - C;
+ A = (w - 1.0) * D;
+ p1 *= w;
+ } else {
+ float2 E = p2 - p1;
+ B = E - C;
+ A = fma(float2(-3), E, D);
+ }
+ // FIXME(crbug.com/800804,skbug.com/11268): Consider normalizing the exponents in A,B,C at
+ // this point in order to prevent fp32 overflow.
+
+ // Now find the coefficients that give a tangent direction from a parametric edge ID:
+ //
+ // |parametricEdgeID^2|
+ // Tangent_Direction(parametricEdgeID) = dx,dy = |A B_ C_| * |parametricEdgeID |
+ // |. . .| |1 |
+ //
+ float2 B_ = B * (numParametricSegments * 2.0);
+ float2 C_ = C * (numParametricSegments * numParametricSegments);
+
+ // Run a binary search to determine the highest parametric edge that is located on or before
+ // the combinedEdgeID. A combined ID is determined by the sum of complete parametric and
+ // radial segments behind it. i.e., find the highest parametric edge where:
+ //
+ // parametricEdgeID + floor(numRadialSegmentsAtParametricT) <= combinedEdgeID
+ //
+ float lastParametricEdgeID = 0.0;
+ float maxParametricEdgeID = min(numParametricSegments - 1.0, combinedEdgeID);
+ float negAbsRadsPerSegment = -abs(radsPerSegment);
+ float maxRotation0 = (1.0 + combinedEdgeID) * abs(radsPerSegment);
+ for (int exp = 5 /*max resolve level*/ - 1; exp >= 0; --exp) {
+ // Test the parametric edge at lastParametricEdgeID + 2^exp.
+ float testParametricID = lastParametricEdgeID + exp2(float(exp));
+ if (testParametricID <= maxParametricEdgeID) {
+ float2 testTan = fma(float2(testParametricID), A, B_);
+ testTan = fma(float2(testParametricID), testTan, C_);
+ float cosRotation = dot(normalize(testTan), tan0);
+ float maxRotation = fma(testParametricID, negAbsRadsPerSegment, maxRotation0);
+ maxRotation = min(maxRotation, $PI);
+ // Is rotation <= maxRotation? (i.e., is the number of complete radial segments
+ // behind testT, + testParametricID <= combinedEdgeID?)
+ if (cosRotation >= cos(maxRotation)) {
+ // testParametricID is on or before the combinedEdgeID. Keep it!
+ lastParametricEdgeID = testParametricID;
+ }
+ }
+ }
+
+ // Find the T value of the parametric edge at lastParametricEdgeID.
+ float parametricT = lastParametricEdgeID / numParametricSegments;
+
+ // Now that we've identified the highest parametric edge on or before the
+ // combinedEdgeID, the highest radial edge is easy:
+ float lastRadialEdgeID = combinedEdgeID - lastParametricEdgeID;
+
+ // Find the angle of tan0, i.e. the angle between tan0 and the positive x axis.
+ float angle0 = acos(clamp(tan0.x, -1.0, 1.0));
+ angle0 = tan0.y >= 0.0 ? angle0 : -angle0;
+
+ // Find the tangent vector on the edge at lastRadialEdgeID. By construction it is already
+ // normalized.
+ float radialAngle = fma(lastRadialEdgeID, radsPerSegment, angle0);
+ tangent = float2(cos(radialAngle), sin(radialAngle));
+ float2 norm = float2(-tangent.y, tangent.x);
+
+ // Find the T value where the tangent is orthogonal to norm. This is a quadratic:
+ //
+ // dot(norm, Tangent_Direction(T)) == 0
+ //
+ // |T^2|
+ // norm * |A 2B C| * |T | == 0
+ // |. . .| |1 |
+ //
+ float a=dot(norm,A), b_over_2=dot(norm,B), c=dot(norm,C);
+ float discr_over_4 = max(b_over_2*b_over_2 - a*c, 0.0);
+ float q = sqrt(discr_over_4);
+ if (b_over_2 > 0.0) {
+ q = -q;
+ }
+ q -= b_over_2;
+
+ // Roots are q/a and c/q. Since each curve section does not inflect or rotate more than 180
+ // degrees, there can only be one tangent orthogonal to "norm" inside 0..1. Pick the root
+ // nearest .5.
+ float _5qa = -.5*q*a;
+ float2 root = (abs(fma(q,q,_5qa)) < abs(fma(a,c,_5qa))) ? float2(q,a) : float2(c,q);
+ float radialT = (root.t != 0.0) ? root.s / root.t : 0.0;
+ radialT = clamp(radialT, 0.0, 1.0);
+
+ if (lastRadialEdgeID == 0.0) {
+ // The root finder above can become unstable when lastRadialEdgeID == 0 (e.g., if
+ // there are roots at exatly 0 and 1 both). radialT should always == 0 in this case.
+ radialT = 0.0;
+ }
+
+ // Now that we've identified the T values of the last parametric and radial edges, our final
+ // T value for combinedEdgeID is whichever is larger.
+ float T = max(parametricT, radialT);
+
+ // Evaluate the cubic at T. Use De Casteljau's for its accuracy and stability.
+ float2 ab = $unchecked_mix(p0, p1, T);
+ float2 bc = $unchecked_mix(p1, p2, T);
+ float2 cd = $unchecked_mix(p2, p3, T);
+ float2 abc = $unchecked_mix(ab, bc, T);
+ float2 bcd = $unchecked_mix(bc, cd, T);
+ float2 abcd = $unchecked_mix(abc, bcd, T);
+
+ // Evaluate the conic weight at T.
+ float u = $unchecked_mix(1.0, w, T);
+ float v = w + 1 - u; // == mix(w, 1, T)
+ float uv = $unchecked_mix(u, v, T);
+
+ // If we went with T=parametricT, then update the tangent. Otherwise leave it at the radial
+ // tangent found previously. (In the event that parametricT == radialT, we keep the radial
+ // tangent.)
+ if (T != radialT) {
+ // We must re-normalize here because the tangent is determined by the curve coefficients
+ tangent = w >= 0.0 ? $robust_normalize_diff(bc*u, ab*v)
+ : $robust_normalize_diff(bcd, abc);
+ }
+
+ strokeCoord = (w >= 0.0) ? abc/uv : abcd;
+ } else {
+ // Edges at the beginning and end of the strip use exact endpoints and tangents. This
+ // ensures crack-free seaming between instances.
+ tangent = (combinedEdgeID == 0) ? tan0 : tan1;
+ strokeCoord = (combinedEdgeID == 0) ? p0 : p3;
+ }
+
+ // At this point 'tangent' is normalized, so the orthogonal vector is also normalized.
+ float2 ortho = float2(tangent.y, -tangent.x);
+ strokeCoord += ortho * (strokeRadius * strokeOutset);
+
+ if (isHairline) {
+ // Hairline case. The scale and skew already happened before tessellation.
+ // TODO: There's probably a more efficient way to tessellate the hairline that lets us
+ // avoid inverting the affine matrix to get back to local coords, but it's just a 2x2 so
+ // this works for now.
+ return float4(strokeCoord + translate, inverse(affineMatrix) * strokeCoord);
+ } else {
+ // Normal case. Do the transform after tessellation.
+ return float4(affineMatrix * strokeCoord + translate, strokeCoord);
+ }
+}
diff --git a/gfx/skia/skia/src/sksl/sksl_public.sksl b/gfx/skia/skia/src/sksl/sksl_public.sksl
new file mode 100644
index 0000000000..1168612c70
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/sksl_public.sksl
@@ -0,0 +1,10 @@
+// SkSL intrinsics that are not part of GLSL
+
+// Color space transformation, between the working (destination) space and fixed (known) spaces:
+$pure half3 toLinearSrgb(half3 color);
+$pure half3 fromLinearSrgb(half3 color);
+
+// SkSL intrinsics that reflect Skia's C++ object model:
+ half4 $eval(float2 coords, shader s);
+ half4 $eval(half4 color, colorFilter f);
+ half4 $eval(half4 src, half4 dst, blender b);
diff --git a/gfx/skia/skia/src/sksl/sksl_rt_shader.sksl b/gfx/skia/skia/src/sksl/sksl_rt_shader.sksl
new file mode 100644
index 0000000000..abae14745b
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/sksl_rt_shader.sksl
@@ -0,0 +1 @@
+layout(builtin=15) float4 sk_FragCoord;
diff --git a/gfx/skia/skia/src/sksl/sksl_shared.sksl b/gfx/skia/skia/src/sksl/sksl_shared.sksl
new file mode 100644
index 0000000000..3720e4c872
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/sksl_shared.sksl
@@ -0,0 +1,449 @@
+// Intrinsics that are available to public SkSL (SkRuntimeEffect)
+
+// See "The OpenGL ES Shading Language, Section 8"
+
+// 8.1 : Angle and Trigonometry Functions
+$pure $genType radians($genType degrees);
+$pure $genHType radians($genHType degrees);
+$pure $genType degrees($genType radians);
+$pure $genHType degrees($genHType radians);
+
+$pure $genType sin($genType angle);
+$pure $genHType sin($genHType angle);
+$pure $genType cos($genType angle);
+$pure $genHType cos($genHType angle);
+$pure $genType tan($genType angle);
+$pure $genHType tan($genHType angle);
+
+$pure $genType asin($genType x);
+$pure $genHType asin($genHType x);
+$pure $genType acos($genType x);
+$pure $genHType acos($genHType x);
+$pure $genType atan($genType y, $genType x);
+$pure $genHType atan($genHType y, $genHType x);
+$pure $genType atan($genType y_over_x);
+$pure $genHType atan($genHType y_over_x);
+
+// 8.1 : Angle and Trigonometry Functions (GLSL ES 3.0)
+$pure $es3 $genType sinh($genType x);
+$pure $es3 $genHType sinh($genHType x);
+$pure $es3 $genType cosh($genType x);
+$pure $es3 $genHType cosh($genHType x);
+$pure $es3 $genType tanh($genType x);
+$pure $es3 $genHType tanh($genHType x);
+$pure $es3 $genType asinh($genType x);
+$pure $es3 $genHType asinh($genHType x);
+$pure $es3 $genType acosh($genType x);
+$pure $es3 $genHType acosh($genHType x);
+$pure $es3 $genType atanh($genType x);
+$pure $es3 $genHType atanh($genHType x);
+
+// 8.2 : Exponential Functions
+$pure $genType pow($genType x, $genType y);
+$pure $genHType pow($genHType x, $genHType y);
+$pure $genType exp($genType x);
+$pure $genHType exp($genHType x);
+$pure $genType log($genType x);
+$pure $genHType log($genHType x);
+$pure $genType exp2($genType x);
+$pure $genHType exp2($genHType x);
+$pure $genType log2($genType x);
+$pure $genHType log2($genHType x);
+
+$pure $genType sqrt($genType x);
+$pure $genHType sqrt($genHType x);
+$pure $genType inversesqrt($genType x);
+$pure $genHType inversesqrt($genHType x);
+
+// 8.3 : Common Functions
+$pure $genType abs($genType x);
+$pure $genHType abs($genHType x);
+$pure $genType sign($genType x);
+$pure $genHType sign($genHType x);
+$pure $genType floor($genType x);
+$pure $genHType floor($genHType x);
+$pure $genType ceil($genType x);
+$pure $genHType ceil($genHType x);
+$pure $genType fract($genType x);
+$pure $genHType fract($genHType x);
+$pure $genType mod($genType x, float y);
+$pure $genType mod($genType x, $genType y);
+$pure $genHType mod($genHType x, half y);
+$pure $genHType mod($genHType x, $genHType y);
+
+$pure $genType min($genType x, $genType y);
+$pure $genType min($genType x, float y);
+$pure $genHType min($genHType x, $genHType y);
+$pure $genHType min($genHType x, half y);
+$pure $genType max($genType x, $genType y);
+$pure $genType max($genType x, float y);
+$pure $genHType max($genHType x, $genHType y);
+$pure $genHType max($genHType x, half y);
+$pure $genType clamp($genType x, $genType minVal, $genType maxVal);
+$pure $genType clamp($genType x, float minVal, float maxVal);
+$pure $genHType clamp($genHType x, $genHType minVal, $genHType maxVal);
+$pure $genHType clamp($genHType x, half minVal, half maxVal);
+$pure $genType saturate($genType x); // SkSL extension
+$pure $genHType saturate($genHType x); // SkSL extension
+$pure $genType mix($genType x, $genType y, $genType a);
+$pure $genType mix($genType x, $genType y, float a);
+$pure $genHType mix($genHType x, $genHType y, $genHType a);
+$pure $genHType mix($genHType x, $genHType y, half a);
+$pure $genType step($genType edge, $genType x);
+$pure $genType step(float edge, $genType x);
+$pure $genHType step($genHType edge, $genHType x);
+$pure $genHType step(half edge, $genHType x);
+$pure $genType smoothstep($genType edge0, $genType edge1, $genType x);
+$pure $genType smoothstep(float edge0, float edge1, $genType x);
+$pure $genHType smoothstep($genHType edge0, $genHType edge1, $genHType x);
+$pure $genHType smoothstep(half edge0, half edge1, $genHType x);
+
+// 8.3 : Common Functions (GLSL ES 3.0)
+$pure $es3 $genIType abs($genIType x);
+$pure $es3 $genIType sign($genIType x);
+$pure $es3 $genIType floatBitsToInt ($genType value);
+$pure $es3 $genUType floatBitsToUint($genType value);
+$pure $es3 $genType intBitsToFloat ($genIType value);
+$pure $es3 $genType uintBitsToFloat($genUType value);
+$pure $es3 $genType trunc($genType x);
+$pure $es3 $genHType trunc($genHType x);
+$pure $es3 $genType round($genType x);
+$pure $es3 $genHType round($genHType x);
+$pure $es3 $genType roundEven($genType x);
+$pure $es3 $genHType roundEven($genHType x);
+$pure $es3 $genIType min($genIType x, $genIType y);
+$pure $es3 $genIType min($genIType x, int y);
+$pure $es3 $genUType min($genUType x, $genUType y);
+$pure $es3 $genUType min($genUType x, uint y);
+$pure $es3 $genIType max($genIType x, $genIType y);
+$pure $es3 $genIType max($genIType x, int y);
+$pure $es3 $genUType max($genUType x, $genUType y);
+$pure $es3 $genUType max($genUType x, uint y);
+$pure $es3 $genIType clamp($genIType x, $genIType minVal, $genIType maxVal);
+$pure $es3 $genIType clamp($genIType x, int minVal, int maxVal);
+$pure $es3 $genUType clamp($genUType x, $genUType minVal, $genUType maxVal);
+$pure $es3 $genUType clamp($genUType x, uint minVal, uint maxVal);
+$pure $es3 $genType mix($genType x, $genType y, $genBType a);
+$pure $es3 $genHType mix($genHType x, $genHType y, $genBType a);
+
+// 8.3 : Common Functions (GLSL ES 3.0) -- cannot be used in constant-expressions
+$pure $es3 $genBType isnan($genType x);
+$pure $es3 $genBType isnan($genHType x);
+$pure $es3 $genBType isinf($genType x);
+$pure $es3 $genBType isinf($genHType x);
+$pure $es3 $genType modf($genType x, out $genType i);
+$pure $es3 $genHType modf($genHType x, out $genHType i);
+
+// 8.4 : Floating-Point Pack and Unpack Functions (GLSL ES 3.0)
+$pure $es3 uint packUnorm2x16(float2 v);
+$pure $es3 float2 unpackUnorm2x16(uint p);
+
+// 8.5 : Geometric Functions
+$pure float length($genType x);
+$pure half length($genHType x);
+$pure float distance($genType p0, $genType p1);
+$pure half distance($genHType p0, $genHType p1);
+$pure float dot($genType x, $genType y);
+$pure half dot($genHType x, $genHType y);
+$pure float3 cross(float3 x, float3 y);
+$pure half3 cross(half3 x, half3 y);
+$pure $genType normalize($genType x);
+$pure $genHType normalize($genHType x);
+$pure $genType faceforward($genType N, $genType I, $genType Nref);
+$pure $genHType faceforward($genHType N, $genHType I, $genHType Nref);
+$pure $genType reflect($genType I, $genType N);
+$pure $genHType reflect($genHType I, $genHType N);
+$pure $genType refract($genType I, $genType N, float eta);
+$pure $genHType refract($genHType I, $genHType N, half eta);
+
+// 8.6 : Matrix Functions
+$pure $squareMat matrixCompMult($squareMat x, $squareMat y);
+$pure $squareHMat matrixCompMult($squareHMat x, $squareHMat y);
+$pure $es3 $mat matrixCompMult($mat x, $mat y);
+$pure $es3 $hmat matrixCompMult($hmat x, $hmat y);
+
+// 8.6 : Matrix Functions (GLSL 1.4, poly-filled by SkSL as needed)
+$pure $squareMat inverse($squareMat m);
+$pure $squareHMat inverse($squareHMat m);
+
+// 8.6 : Matrix Functions (GLSL ES 3.0)
+$pure $es3 float determinant($squareMat m);
+$pure $es3 half determinant($squareHMat m);
+$pure $es3 $squareMat transpose($squareMat m);
+$pure $es3 $squareHMat transpose($squareHMat m);
+$pure $es3 float2x3 transpose(float3x2 m);
+$pure $es3 half2x3 transpose(half3x2 m);
+$pure $es3 float2x4 transpose(float4x2 m);
+$pure $es3 half2x4 transpose(half4x2 m);
+$pure $es3 float3x2 transpose(float2x3 m);
+$pure $es3 half3x2 transpose(half2x3 m);
+$pure $es3 float3x4 transpose(float4x3 m);
+$pure $es3 half3x4 transpose(half4x3 m);
+$pure $es3 float4x2 transpose(float2x4 m);
+$pure $es3 half4x2 transpose(half2x4 m);
+$pure $es3 float4x3 transpose(float3x4 m);
+$pure $es3 half4x3 transpose(half3x4 m);
+$pure $es3 $squareMat outerProduct($vec c, $vec r);
+$pure $es3 $squareHMat outerProduct($hvec c, $hvec r);
+$pure $es3 float2x3 outerProduct(float3 c, float2 r);
+$pure $es3 half2x3 outerProduct(half3 c, half2 r);
+$pure $es3 float3x2 outerProduct(float2 c, float3 r);
+$pure $es3 half3x2 outerProduct(half2 c, half3 r);
+$pure $es3 float2x4 outerProduct(float4 c, float2 r);
+$pure $es3 half2x4 outerProduct(half4 c, half2 r);
+$pure $es3 float4x2 outerProduct(float2 c, float4 r);
+$pure $es3 half4x2 outerProduct(half2 c, half4 r);
+$pure $es3 float3x4 outerProduct(float4 c, float3 r);
+$pure $es3 half3x4 outerProduct(half4 c, half3 r);
+$pure $es3 float4x3 outerProduct(float3 c, float4 r);
+$pure $es3 half4x3 outerProduct(half3 c, half4 r);
+
+// 8.7 : Vector Relational Functions
+$pure $bvec lessThan($vec x, $vec y);
+$pure $bvec lessThan($hvec x, $hvec y);
+$pure $bvec lessThan($ivec x, $ivec y);
+$pure $bvec lessThan($svec x, $svec y);
+$pure $bvec lessThanEqual($vec x, $vec y);
+$pure $bvec lessThanEqual($hvec x, $hvec y);
+$pure $bvec lessThanEqual($ivec x, $ivec y);
+$pure $bvec lessThanEqual($svec x, $svec y);
+$pure $bvec greaterThan($vec x, $vec y);
+$pure $bvec greaterThan($hvec x, $hvec y);
+$pure $bvec greaterThan($ivec x, $ivec y);
+$pure $bvec greaterThan($svec x, $svec y);
+$pure $bvec greaterThanEqual($vec x, $vec y);
+$pure $bvec greaterThanEqual($hvec x, $hvec y);
+$pure $bvec greaterThanEqual($ivec x, $ivec y);
+$pure $bvec greaterThanEqual($svec x, $svec y);
+$pure $bvec equal($vec x, $vec y);
+$pure $bvec equal($hvec x, $hvec y);
+$pure $bvec equal($ivec x, $ivec y);
+$pure $bvec equal($svec x, $svec y);
+$pure $bvec equal($bvec x, $bvec y);
+$pure $bvec notEqual($vec x, $vec y);
+$pure $bvec notEqual($hvec x, $hvec y);
+$pure $bvec notEqual($ivec x, $ivec y);
+$pure $bvec notEqual($svec x, $svec y);
+$pure $bvec notEqual($bvec x, $bvec y);
+
+$pure $es3 $bvec lessThan($usvec x, $usvec y);
+$pure $es3 $bvec lessThan($uvec x, $uvec y);
+$pure $es3 $bvec lessThanEqual($uvec x, $uvec y);
+$pure $es3 $bvec lessThanEqual($usvec x, $usvec y);
+$pure $es3 $bvec greaterThan($uvec x, $uvec y);
+$pure $es3 $bvec greaterThan($usvec x, $usvec y);
+$pure $es3 $bvec greaterThanEqual($uvec x, $uvec y);
+$pure $es3 $bvec greaterThanEqual($usvec x, $usvec y);
+$pure $es3 $bvec equal($uvec x, $uvec y);
+$pure $es3 $bvec equal($usvec x, $usvec y);
+$pure $es3 $bvec notEqual($uvec x, $uvec y);
+$pure $es3 $bvec notEqual($usvec x, $usvec y);
+
+$pure bool any($bvec x);
+$pure bool all($bvec x);
+$pure $bvec not($bvec x);
+
+// 8.9 : Fragment Processing Functions (GLSL ES 3.0)
+$pure $es3 $genType dFdx($genType p);
+$pure $es3 $genType dFdy($genType p);
+$pure $es3 $genHType dFdx($genHType p);
+$pure $es3 $genHType dFdy($genHType p);
+$pure $es3 $genType fwidth($genType p);
+$pure $es3 $genHType fwidth($genHType p);
+
+
+// SkSL utility functions
+
+// The max() guards against division by zero when the incoming color is transparent black
+$pure half4 unpremul(half4 color) { return half4 (color.rgb / max(color.a, 0.0001), color.a); }
+$pure float4 unpremul(float4 color) { return float4(color.rgb / max(color.a, 0.0001), color.a); }
+
+// Similar, but used for polar-space CSS colors
+$export $pure half4 $unpremul_polar(half4 color) {
+ return half4(color.r, color.gb / max(color.a, 0.0001), color.a);
+}
+
+// Convert RGBA -> HSLA (including unpremul).
+//
+// Based on work by Sam Hocevar, Emil Persson, and Ian Taylor [1][2][3]. High-level ideas:
+//
+// - minimize the number of branches by sorting and computing the hue phase in parallel (vec4s)
+//
+// - trade the third sorting branch for a potentially faster std::min and leaving 2nd/3rd
+// channels unsorted (based on the observation that swapping both the channels and the bias sign
+// has no effect under abs)
+//
+// - use epsilon offsets for denominators, to avoid explicit zero-checks
+//
+// An additional trick we employ is deferring premul->unpremul conversion until the very end: the
+// alpha factor gets naturally simplified for H and S, and only L requires a dedicated unpremul
+// division (so we trade three divs for one).
+//
+// [1] http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv
+// [2] http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
+// [3] http://www.chilliant.com/rgb2hsv.html
+
+$export $pure half4 $rgb_to_hsl(half3 c, half a) {
+ half4 p = (c.g < c.b) ? half4(c.bg, -1, 2/3.0)
+ : half4(c.gb, 0, -1/3.0);
+ half4 q = (c.r < p.x) ? half4(p.x, c.r, p.yw)
+ : half4(c.r, p.x, p.yz);
+
+ // q.x -> max channel value
+ // q.yz -> 2nd/3rd channel values (unsorted)
+ // q.w -> bias value dependent on max channel selection
+
+ const half kEps = 0.0001;
+ half pmV = q.x;
+ half pmC = pmV - min(q.y, q.z);
+ half pmL = pmV - pmC * 0.5;
+ half H = abs(q.w + (q.y - q.z) / (pmC * 6 + kEps));
+ half S = pmC / (a + kEps - abs(pmL * 2 - a));
+ half L = pmL / (a + kEps);
+
+ return half4(H, S, L, a);
+}
+
+// Convert HSLA -> RGBA (including clamp and premul).
+//
+// Based on work by Sam Hocevar, Emil Persson, and Ian Taylor [1][2][3].
+//
+// [1] http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv
+// [2] http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
+// [3] http://www.chilliant.com/rgb2hsv.html
+
+$export $pure half3 $hsl_to_rgb(half3 hsl) {
+ half C = (1 - abs(2 * hsl.z - 1)) * hsl.y;
+ half3 p = hsl.xxx + half3(0, 2/3.0, 1/3.0);
+ half3 q = saturate(abs(fract(p) * 6 - 3) - 1);
+
+ return (q - 0.5) * C + hsl.z;
+}
+
+$export $pure half4 $hsl_to_rgb(half3 hsl, half a) {
+ return saturate(half4($hsl_to_rgb(hsl) * a, a));
+}
+
+// Color conversion functions used in gradient interpolation, based on
+// https://www.w3.org/TR/css-color-4/#color-conversion-code
+// TODO(skia:13108): For all of these, we can eliminate any linear math at the beginning
+// (by removing the corresponding linear math at the end of the CPU code).
+$export $pure half3 $css_lab_to_xyz(half3 lab) {
+ const half k = 24389 / 27.0;
+ const half e = 216 / 24389.0;
+
+ half3 f;
+ f[1] = (lab[0] + 16) / 116;
+ f[0] = (lab[1] / 500) + f[1];
+ f[2] = f[1] - (lab[2] / 200);
+
+ half3 f_cubed = pow(f, half3(3));
+
+ half3 xyz = half3(
+ f_cubed[0] > e ? f_cubed[0] : (116 * f[0] - 16) / k,
+ lab[0] > k * e ? f_cubed[1] : lab[0] / k,
+ f_cubed[2] > e ? f_cubed[2] : (116 * f[2] - 16) / k
+ );
+
+ const half3 D50 = half3(0.3457 / 0.3585, 1.0, (1.0 - 0.3457 - 0.3585) / 0.3585);
+ return xyz * D50;
+}
+
+// Skia stores all polar colors with hue in the first component, so this "LCH -> Lab" transform
+// actually takes "HCL". This is also used to do the same polar transform for OkHCL to OkLAB.
+// See similar comments & logic in SkGradientShaderBase.cpp.
+$pure half3 $css_hcl_to_lab(half3 hcl) {
+ return half3(
+ hcl[2],
+ hcl[1] * cos(radians(hcl[0])),
+ hcl[1] * sin(radians(hcl[0]))
+ );
+}
+
+$export $pure half3 $css_hcl_to_xyz(half3 hcl) {
+ return $css_lab_to_xyz($css_hcl_to_lab(hcl));
+}
+
+$export $pure half3 $css_oklab_to_linear_srgb(half3 oklab) {
+ half l_ = oklab.x + 0.3963377774 * oklab.y + 0.2158037573 * oklab.z,
+ m_ = oklab.x - 0.1055613458 * oklab.y - 0.0638541728 * oklab.z,
+ s_ = oklab.x - 0.0894841775 * oklab.y - 1.2914855480 * oklab.z;
+
+ half l = l_*l_*l_,
+ m = m_*m_*m_,
+ s = s_*s_*s_;
+
+ return half3(
+ +4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
+ -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
+ -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s
+ );
+}
+
+$export $pure half3 $css_okhcl_to_linear_srgb(half3 okhcl) {
+ return $css_oklab_to_linear_srgb($css_hcl_to_lab(okhcl));
+}
+
+// TODO(skia:13108): Use our optimized version (though it has different range)
+// Doing so might require fixing (re-deriving?) the math for the HWB version below
+$export $pure half3 $css_hsl_to_srgb(half3 hsl) {
+ hsl.x = mod(hsl.x, 360);
+ if (hsl.x < 0) {
+ hsl.x += 360;
+ }
+
+ hsl.yz /= 100;
+
+ half3 k = mod(half3(0, 8, 4) + hsl.x/30, 12);
+ half a = hsl.y * min(hsl.z, 1 - hsl.z);
+ return hsl.z - a * clamp(min(k - 3, 9 - k), -1, 1);
+}
+
+$export $pure half3 $css_hwb_to_srgb(half3 hwb) {
+ hwb.yz /= 100;
+ if (hwb.y + hwb.z >= 1) {
+ half gray = hwb.y / (hwb.y + hwb.z);
+ return half3(gray);
+ }
+ half3 rgb = $css_hsl_to_srgb(half3(hwb.x, 100, 50));
+ rgb *= (1 - hwb.y - hwb.z);
+ rgb += hwb.y;
+ return rgb;
+}
+
+/*
+ * The actual output color space of this function depends on the input color space
+ * (it might be sRGB, linear sRGB, or linear XYZ). The actual space is what's stored
+ * in the gradient/SkColor4fXformer's fIntermediateColorSpace.
+ */
+$export $pure half4 $interpolated_to_rgb_unpremul(half4 color, int colorSpace, int doUnpremul) {
+ const int kDestination = 0;
+ const int kSRGBLinear = 1;
+ const int kLab = 2;
+ const int kOKLab = 3;
+ const int kLCH = 4;
+ const int kOKLCH = 5;
+ const int kSRGB = 6;
+ const int kHSL = 7;
+ const int kHWB = 8;
+
+ if (bool(doUnpremul)) {
+ switch (colorSpace) {
+ case kLab:
+ case kOKLab: color = unpremul(color); break;
+ case kLCH:
+ case kOKLCH:
+ case kHSL:
+ case kHWB: color = $unpremul_polar(color); break;
+ }
+ }
+ switch (colorSpace) {
+ case kLab: { color.rgb = $css_lab_to_xyz(color.rgb); break; }
+ case kOKLab: { color.rgb = $css_oklab_to_linear_srgb(color.rgb); break; }
+ case kLCH: { color.rgb = $css_hcl_to_xyz(color.rgb); break; }
+ case kOKLCH: { color.rgb = $css_okhcl_to_linear_srgb(color.rgb); break; }
+ case kHSL: { color.rgb = $css_hsl_to_srgb(color.rgb); break; }
+ case kHWB: { color.rgb = $css_hwb_to_srgb(color.rgb); break; }
+ }
+ return color;
+}
diff --git a/gfx/skia/skia/src/sksl/sksl_vert.sksl b/gfx/skia/skia/src/sksl/sksl_vert.sksl
new file mode 100644
index 0000000000..de730b3fbf
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/sksl_vert.sksl
@@ -0,0 +1,9 @@
+// defines built-in interfaces supported by SkSL vertex shaders
+
+out sk_PerVertex {
+ layout(builtin=0) float4 sk_Position;
+ layout(builtin=1) float sk_PointSize;
+};
+
+layout(builtin=42) in int sk_VertexID;
+layout(builtin=43) in int sk_InstanceID;
diff --git a/gfx/skia/skia/src/sksl/spirv.h b/gfx/skia/skia/src/sksl/spirv.h
new file mode 100644
index 0000000000..22821ed862
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/spirv.h
@@ -0,0 +1,870 @@
+/*
+** Copyright (c) 2014-2016 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a copy
+** of this software and/or associated documentation files (the "Materials"),
+** to deal in the Materials without restriction, including without limitation
+** the rights to use, copy, modify, merge, publish, distribute, sublicense,
+** and/or sell copies of the Materials, and to permit persons to whom the
+** Materials are furnished to do so, subject to the following conditions:
+**
+** The above copyright notice and this permission notice shall be included in
+** all copies or substantial portions of the Materials.
+**
+** MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS
+** STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND
+** HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+** OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+** THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+** FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS
+** IN THE MATERIALS.
+*/
+
+/*
+** This header is automatically generated by the same tool that creates
+** the Binary Section of the SPIR-V specification.
+*/
+
+/*
+** Enumeration tokens for SPIR-V, in various styles:
+** C, C++, C++11, JSON, Lua, Python
+**
+** - C will have tokens with a "Spv" prefix, e.g.: SpvSourceLanguageGLSL
+** - C++ will have tokens in the "spv" name space, e.g.: spv::SourceLanguageGLSL
+** - C++11 will use enum classes in the spv namespace, e.g.: spv::SourceLanguage::GLSL
+** - Lua will use tables, e.g.: spv.SourceLanguage.GLSL
+** - Python will use dictionaries, e.g.: spv['SourceLanguage']['GLSL']
+**
+** Some tokens act like mask values, which can be OR'd together,
+** while others are mutually exclusive. The mask-like ones have
+** "Mask" in their name, and a parallel enum that has the shift
+** amount (1 << x) for each corresponding enumerant.
+*/
+
+#ifndef spirv_H
+#define spirv_H
+
+typedef unsigned int SpvId;
+
+#define SPV_VERSION 0x10000
+#define SPV_REVISION 4
+
+static const unsigned int SpvMagicNumber = 0x07230203;
+static const unsigned int SpvVersion = 0x00010000;
+static const unsigned int SpvRevision = 4;
+static const unsigned int SpvOpCodeMask = 0xffff;
+static const unsigned int SpvWordCountShift = 16;
+
+typedef enum SpvSourceLanguage_ {
+ SpvSourceLanguageUnknown = 0,
+ SpvSourceLanguageESSL = 1,
+ SpvSourceLanguageGLSL = 2,
+ SpvSourceLanguageOpenCL_C = 3,
+ SpvSourceLanguageOpenCL_CPP = 4,
+} SpvSourceLanguage;
+
+typedef enum SpvExecutionModel_ {
+ SpvExecutionModelVertex = 0,
+ SpvExecutionModelTessellationControl = 1,
+ SpvExecutionModelTessellationEvaluation = 2,
+ SpvExecutionModelGeometry = 3,
+ SpvExecutionModelFragment = 4,
+ SpvExecutionModelGLCompute = 5,
+ SpvExecutionModelKernel = 6,
+} SpvExecutionModel;
+
+typedef enum SpvAddressingModel_ {
+ SpvAddressingModelLogical = 0,
+ SpvAddressingModelPhysical32 = 1,
+ SpvAddressingModelPhysical64 = 2,
+} SpvAddressingModel;
+
+typedef enum SpvMemoryModel_ {
+ SpvMemoryModelSimple = 0,
+ SpvMemoryModelGLSL450 = 1,
+ SpvMemoryModelOpenCL = 2,
+} SpvMemoryModel;
+
+typedef enum SpvExecutionMode_ {
+ SpvExecutionModeInvocations = 0,
+ SpvExecutionModeSpacingEqual = 1,
+ SpvExecutionModeSpacingFractionalEven = 2,
+ SpvExecutionModeSpacingFractionalOdd = 3,
+ SpvExecutionModeVertexOrderCw = 4,
+ SpvExecutionModeVertexOrderCcw = 5,
+ SpvExecutionModePixelCenterInteger = 6,
+ SpvExecutionModeOriginUpperLeft = 7,
+ SpvExecutionModeOriginLowerLeft = 8,
+ SpvExecutionModeEarlyFragmentTests = 9,
+ SpvExecutionModePointMode = 10,
+ SpvExecutionModeXfb = 11,
+ SpvExecutionModeDepthReplacing = 12,
+ SpvExecutionModeDepthGreater = 14,
+ SpvExecutionModeDepthLess = 15,
+ SpvExecutionModeDepthUnchanged = 16,
+ SpvExecutionModeLocalSize = 17,
+ SpvExecutionModeLocalSizeHint = 18,
+ SpvExecutionModeInputPoints = 19,
+ SpvExecutionModeInputLines = 20,
+ SpvExecutionModeInputLinesAdjacency = 21,
+ SpvExecutionModeTriangles = 22,
+ SpvExecutionModeInputTrianglesAdjacency = 23,
+ SpvExecutionModeQuads = 24,
+ SpvExecutionModeIsolines = 25,
+ SpvExecutionModeOutputVertices = 26,
+ SpvExecutionModeOutputPoints = 27,
+ SpvExecutionModeOutputLineStrip = 28,
+ SpvExecutionModeOutputTriangleStrip = 29,
+ SpvExecutionModeVecTypeHint = 30,
+ SpvExecutionModeContractionOff = 31,
+} SpvExecutionMode;
+
+typedef enum SpvStorageClass_ {
+ SpvStorageClassUniformConstant = 0,
+ SpvStorageClassInput = 1,
+ SpvStorageClassUniform = 2,
+ SpvStorageClassOutput = 3,
+ SpvStorageClassWorkgroup = 4,
+ SpvStorageClassCrossWorkgroup = 5,
+ SpvStorageClassPrivate = 6,
+ SpvStorageClassFunction = 7,
+ SpvStorageClassGeneric = 8,
+ SpvStorageClassPushConstant = 9,
+ SpvStorageClassAtomicCounter = 10,
+ SpvStorageClassImage = 11,
+} SpvStorageClass;
+
+typedef enum SpvDim_ {
+ SpvDim1D = 0,
+ SpvDim2D = 1,
+ SpvDim3D = 2,
+ SpvDimCube = 3,
+ SpvDimRect = 4,
+ SpvDimBuffer = 5,
+ SpvDimSubpassData = 6,
+} SpvDim;
+
+typedef enum SpvSamplerAddressingMode_ {
+ SpvSamplerAddressingModeNone = 0,
+ SpvSamplerAddressingModeClampToEdge = 1,
+ SpvSamplerAddressingModeClamp = 2,
+ SpvSamplerAddressingModeRepeat = 3,
+ SpvSamplerAddressingModeRepeatMirrored = 4,
+} SpvSamplerAddressingMode;
+
+typedef enum SpvSamplerFilterMode_ {
+ SpvSamplerFilterModeNearest = 0,
+ SpvSamplerFilterModeLinear = 1,
+} SpvSamplerFilterMode;
+
+typedef enum SpvImageFormat_ {
+ SpvImageFormatUnknown = 0,
+ SpvImageFormatRgba32f = 1,
+ SpvImageFormatRgba16f = 2,
+ SpvImageFormatR32f = 3,
+ SpvImageFormatRgba8 = 4,
+ SpvImageFormatRgba8Snorm = 5,
+ SpvImageFormatRg32f = 6,
+ SpvImageFormatRg16f = 7,
+ SpvImageFormatR11fG11fB10f = 8,
+ SpvImageFormatR16f = 9,
+ SpvImageFormatRgba16 = 10,
+ SpvImageFormatRgb10A2 = 11,
+ SpvImageFormatRg16 = 12,
+ SpvImageFormatRg8 = 13,
+ SpvImageFormatR16 = 14,
+ SpvImageFormatR8 = 15,
+ SpvImageFormatRgba16Snorm = 16,
+ SpvImageFormatRg16Snorm = 17,
+ SpvImageFormatRg8Snorm = 18,
+ SpvImageFormatR16Snorm = 19,
+ SpvImageFormatR8Snorm = 20,
+ SpvImageFormatRgba32i = 21,
+ SpvImageFormatRgba16i = 22,
+ SpvImageFormatRgba8i = 23,
+ SpvImageFormatR32i = 24,
+ SpvImageFormatRg32i = 25,
+ SpvImageFormatRg16i = 26,
+ SpvImageFormatRg8i = 27,
+ SpvImageFormatR16i = 28,
+ SpvImageFormatR8i = 29,
+ SpvImageFormatRgba32ui = 30,
+ SpvImageFormatRgba16ui = 31,
+ SpvImageFormatRgba8ui = 32,
+ SpvImageFormatR32ui = 33,
+ SpvImageFormatRgb10a2ui = 34,
+ SpvImageFormatRg32ui = 35,
+ SpvImageFormatRg16ui = 36,
+ SpvImageFormatRg8ui = 37,
+ SpvImageFormatR16ui = 38,
+ SpvImageFormatR8ui = 39,
+} SpvImageFormat;
+
+typedef enum SpvImageChannelOrder_ {
+ SpvImageChannelOrderR = 0,
+ SpvImageChannelOrderA = 1,
+ SpvImageChannelOrderRG = 2,
+ SpvImageChannelOrderRA = 3,
+ SpvImageChannelOrderRGB = 4,
+ SpvImageChannelOrderRGBA = 5,
+ SpvImageChannelOrderBGRA = 6,
+ SpvImageChannelOrderARGB = 7,
+ SpvImageChannelOrderIntensity = 8,
+ SpvImageChannelOrderLuminance = 9,
+ SpvImageChannelOrderRx = 10,
+ SpvImageChannelOrderRGx = 11,
+ SpvImageChannelOrderRGBx = 12,
+ SpvImageChannelOrderDepth = 13,
+ SpvImageChannelOrderDepthStencil = 14,
+ SpvImageChannelOrdersRGB = 15,
+ SpvImageChannelOrdersRGBx = 16,
+ SpvImageChannelOrdersRGBA = 17,
+ SpvImageChannelOrdersBGRA = 18,
+} SpvImageChannelOrder;
+
+typedef enum SpvImageChannelDataType_ {
+ SpvImageChannelDataTypeSnormInt8 = 0,
+ SpvImageChannelDataTypeSnormInt16 = 1,
+ SpvImageChannelDataTypeUnormInt8 = 2,
+ SpvImageChannelDataTypeUnormInt16 = 3,
+ SpvImageChannelDataTypeUnormShort565 = 4,
+ SpvImageChannelDataTypeUnormShort555 = 5,
+ SpvImageChannelDataTypeUnormInt101010 = 6,
+ SpvImageChannelDataTypeSignedInt8 = 7,
+ SpvImageChannelDataTypeSignedInt16 = 8,
+ SpvImageChannelDataTypeSignedInt32 = 9,
+ SpvImageChannelDataTypeUnsignedInt8 = 10,
+ SpvImageChannelDataTypeUnsignedInt16 = 11,
+ SpvImageChannelDataTypeUnsignedInt32 = 12,
+ SpvImageChannelDataTypeHalfFloat = 13,
+ SpvImageChannelDataTypeFloat = 14,
+ SpvImageChannelDataTypeUnormInt24 = 15,
+ SpvImageChannelDataTypeUnormInt101010_2 = 16,
+} SpvImageChannelDataType;
+
+typedef enum SpvImageOperandsShift_ {
+ SpvImageOperandsBiasShift = 0,
+ SpvImageOperandsLodShift = 1,
+ SpvImageOperandsGradShift = 2,
+ SpvImageOperandsConstOffsetShift = 3,
+ SpvImageOperandsOffsetShift = 4,
+ SpvImageOperandsConstOffsetsShift = 5,
+ SpvImageOperandsSampleShift = 6,
+ SpvImageOperandsMinLodShift = 7,
+} SpvImageOperandsShift;
+
+typedef enum SpvImageOperandsMask_ {
+ SpvImageOperandsMaskNone = 0,
+ SpvImageOperandsBiasMask = 0x00000001,
+ SpvImageOperandsLodMask = 0x00000002,
+ SpvImageOperandsGradMask = 0x00000004,
+ SpvImageOperandsConstOffsetMask = 0x00000008,
+ SpvImageOperandsOffsetMask = 0x00000010,
+ SpvImageOperandsConstOffsetsMask = 0x00000020,
+ SpvImageOperandsSampleMask = 0x00000040,
+ SpvImageOperandsMinLodMask = 0x00000080,
+} SpvImageOperandsMask;
+
+typedef enum SpvFPFastMathModeShift_ {
+ SpvFPFastMathModeNotNaNShift = 0,
+ SpvFPFastMathModeNotInfShift = 1,
+ SpvFPFastMathModeNSZShift = 2,
+ SpvFPFastMathModeAllowRecipShift = 3,
+ SpvFPFastMathModeFastShift = 4,
+} SpvFPFastMathModeShift;
+
+typedef enum SpvFPFastMathModeMask_ {
+ SpvFPFastMathModeMaskNone = 0,
+ SpvFPFastMathModeNotNaNMask = 0x00000001,
+ SpvFPFastMathModeNotInfMask = 0x00000002,
+ SpvFPFastMathModeNSZMask = 0x00000004,
+ SpvFPFastMathModeAllowRecipMask = 0x00000008,
+ SpvFPFastMathModeFastMask = 0x00000010,
+} SpvFPFastMathModeMask;
+
+typedef enum SpvFPRoundingMode_ {
+ SpvFPRoundingModeRTE = 0,
+ SpvFPRoundingModeRTZ = 1,
+ SpvFPRoundingModeRTP = 2,
+ SpvFPRoundingModeRTN = 3,
+} SpvFPRoundingMode;
+
+typedef enum SpvLinkageType_ {
+ SpvLinkageTypeExport = 0,
+ SpvLinkageTypeImport = 1,
+} SpvLinkageType;
+
+typedef enum SpvAccessQualifier_ {
+ SpvAccessQualifierReadOnly = 0,
+ SpvAccessQualifierWriteOnly = 1,
+ SpvAccessQualifierReadWrite = 2,
+} SpvAccessQualifier;
+
+typedef enum SpvFunctionParameterAttribute_ {
+ SpvFunctionParameterAttributeZext = 0,
+ SpvFunctionParameterAttributeSext = 1,
+ SpvFunctionParameterAttributeByVal = 2,
+ SpvFunctionParameterAttributeSret = 3,
+ SpvFunctionParameterAttributeNoAlias = 4,
+ SpvFunctionParameterAttributeNoCapture = 5,
+ SpvFunctionParameterAttributeNoWrite = 6,
+ SpvFunctionParameterAttributeNoReadWrite = 7,
+} SpvFunctionParameterAttribute;
+
+typedef enum SpvDecoration_ {
+ SpvDecorationRelaxedPrecision = 0,
+ SpvDecorationSpecId = 1,
+ SpvDecorationBlock = 2,
+ SpvDecorationBufferBlock = 3,
+ SpvDecorationRowMajor = 4,
+ SpvDecorationColMajor = 5,
+ SpvDecorationArrayStride = 6,
+ SpvDecorationMatrixStride = 7,
+ SpvDecorationGLSLShared = 8,
+ SpvDecorationGLSLPacked = 9,
+ SpvDecorationCPacked = 10,
+ SpvDecorationBuiltIn = 11,
+ SpvDecorationNoPerspective = 13,
+ SpvDecorationFlat = 14,
+ SpvDecorationPatch = 15,
+ SpvDecorationCentroid = 16,
+ SpvDecorationSample = 17,
+ SpvDecorationInvariant = 18,
+ SpvDecorationRestrict = 19,
+ SpvDecorationAliased = 20,
+ SpvDecorationVolatile = 21,
+ SpvDecorationConstant = 22,
+ SpvDecorationCoherent = 23,
+ SpvDecorationNonWritable = 24,
+ SpvDecorationNonReadable = 25,
+ SpvDecorationUniform = 26,
+ SpvDecorationSaturatedConversion = 28,
+ SpvDecorationStream = 29,
+ SpvDecorationLocation = 30,
+ SpvDecorationComponent = 31,
+ SpvDecorationIndex = 32,
+ SpvDecorationBinding = 33,
+ SpvDecorationDescriptorSet = 34,
+ SpvDecorationOffset = 35,
+ SpvDecorationXfbBuffer = 36,
+ SpvDecorationXfbStride = 37,
+ SpvDecorationFuncParamAttr = 38,
+ SpvDecorationFPRoundingMode = 39,
+ SpvDecorationFPFastMathMode = 40,
+ SpvDecorationLinkageAttributes = 41,
+ SpvDecorationNoContraction = 42,
+ SpvDecorationInputAttachmentIndex = 43,
+ SpvDecorationAlignment = 44,
+} SpvDecoration;
+
+typedef enum SpvBuiltIn_ {
+ SpvBuiltInPosition = 0,
+ SpvBuiltInPointSize = 1,
+ SpvBuiltInClipDistance = 3,
+ SpvBuiltInCullDistance = 4,
+ SpvBuiltInVertexId = 5,
+ SpvBuiltInInstanceId = 6,
+ SpvBuiltInPrimitiveId = 7,
+ SpvBuiltInInvocationId = 8,
+ SpvBuiltInLayer = 9,
+ SpvBuiltInViewportIndex = 10,
+ SpvBuiltInTessLevelOuter = 11,
+ SpvBuiltInTessLevelInner = 12,
+ SpvBuiltInTessCoord = 13,
+ SpvBuiltInPatchVertices = 14,
+ SpvBuiltInFragCoord = 15,
+ SpvBuiltInPointCoord = 16,
+ SpvBuiltInFrontFacing = 17,
+ SpvBuiltInSampleId = 18,
+ SpvBuiltInSamplePosition = 19,
+ SpvBuiltInSampleMask = 20,
+ SpvBuiltInFragDepth = 22,
+ SpvBuiltInHelperInvocation = 23,
+ SpvBuiltInNumWorkgroups = 24,
+ SpvBuiltInWorkgroupSize = 25,
+ SpvBuiltInWorkgroupId = 26,
+ SpvBuiltInLocalInvocationId = 27,
+ SpvBuiltInGlobalInvocationId = 28,
+ SpvBuiltInLocalInvocationIndex = 29,
+ SpvBuiltInWorkDim = 30,
+ SpvBuiltInGlobalSize = 31,
+ SpvBuiltInEnqueuedWorkgroupSize = 32,
+ SpvBuiltInGlobalOffset = 33,
+ SpvBuiltInGlobalLinearId = 34,
+ SpvBuiltInSubgroupSize = 36,
+ SpvBuiltInSubgroupMaxSize = 37,
+ SpvBuiltInNumSubgroups = 38,
+ SpvBuiltInNumEnqueuedSubgroups = 39,
+ SpvBuiltInSubgroupId = 40,
+ SpvBuiltInSubgroupLocalInvocationId = 41,
+ SpvBuiltInVertexIndex = 42,
+ SpvBuiltInInstanceIndex = 43,
+} SpvBuiltIn;
+
+typedef enum SpvSelectionControlShift_ {
+ SpvSelectionControlFlattenShift = 0,
+ SpvSelectionControlDontFlattenShift = 1,
+} SpvSelectionControlShift;
+
+typedef enum SpvSelectionControlMask_ {
+ SpvSelectionControlMaskNone = 0,
+ SpvSelectionControlFlattenMask = 0x00000001,
+ SpvSelectionControlDontFlattenMask = 0x00000002,
+} SpvSelectionControlMask;
+
+typedef enum SpvLoopControlShift_ {
+ SpvLoopControlUnrollShift = 0,
+ SpvLoopControlDontUnrollShift = 1,
+} SpvLoopControlShift;
+
+typedef enum SpvLoopControlMask_ {
+ SpvLoopControlMaskNone = 0,
+ SpvLoopControlUnrollMask = 0x00000001,
+ SpvLoopControlDontUnrollMask = 0x00000002,
+} SpvLoopControlMask;
+
+typedef enum SpvFunctionControlShift_ {
+ SpvFunctionControlInlineShift = 0,
+ SpvFunctionControlDontInlineShift = 1,
+ SpvFunctionControlPureShift = 2,
+ SpvFunctionControlConstShift = 3,
+} SpvFunctionControlShift;
+
+typedef enum SpvFunctionControlMask_ {
+ SpvFunctionControlMaskNone = 0,
+ SpvFunctionControlInlineMask = 0x00000001,
+ SpvFunctionControlDontInlineMask = 0x00000002,
+ SpvFunctionControlPureMask = 0x00000004,
+ SpvFunctionControlConstMask = 0x00000008,
+} SpvFunctionControlMask;
+
+typedef enum SpvMemorySemanticsShift_ {
+ SpvMemorySemanticsAcquireShift = 1,
+ SpvMemorySemanticsReleaseShift = 2,
+ SpvMemorySemanticsAcquireReleaseShift = 3,
+ SpvMemorySemanticsSequentiallyConsistentShift = 4,
+ SpvMemorySemanticsUniformMemoryShift = 6,
+ SpvMemorySemanticsSubgroupMemoryShift = 7,
+ SpvMemorySemanticsWorkgroupMemoryShift = 8,
+ SpvMemorySemanticsCrossWorkgroupMemoryShift = 9,
+ SpvMemorySemanticsAtomicCounterMemoryShift = 10,
+ SpvMemorySemanticsImageMemoryShift = 11,
+} SpvMemorySemanticsShift;
+
+typedef enum SpvMemorySemanticsMask_ {
+ SpvMemorySemanticsMaskNone = 0,
+ SpvMemorySemanticsAcquireMask = 0x00000002,
+ SpvMemorySemanticsReleaseMask = 0x00000004,
+ SpvMemorySemanticsAcquireReleaseMask = 0x00000008,
+ SpvMemorySemanticsSequentiallyConsistentMask = 0x00000010,
+ SpvMemorySemanticsUniformMemoryMask = 0x00000040,
+ SpvMemorySemanticsSubgroupMemoryMask = 0x00000080,
+ SpvMemorySemanticsWorkgroupMemoryMask = 0x00000100,
+ SpvMemorySemanticsCrossWorkgroupMemoryMask = 0x00000200,
+ SpvMemorySemanticsAtomicCounterMemoryMask = 0x00000400,
+ SpvMemorySemanticsImageMemoryMask = 0x00000800,
+} SpvMemorySemanticsMask;
+
+typedef enum SpvMemoryAccessShift_ {
+ SpvMemoryAccessVolatileShift = 0,
+ SpvMemoryAccessAlignedShift = 1,
+ SpvMemoryAccessNontemporalShift = 2,
+} SpvMemoryAccessShift;
+
+typedef enum SpvMemoryAccessMask_ {
+ SpvMemoryAccessMaskNone = 0,
+ SpvMemoryAccessVolatileMask = 0x00000001,
+ SpvMemoryAccessAlignedMask = 0x00000002,
+ SpvMemoryAccessNontemporalMask = 0x00000004,
+} SpvMemoryAccessMask;
+
+typedef enum SpvScope_ {
+ SpvScopeCrossDevice = 0,
+ SpvScopeDevice = 1,
+ SpvScopeWorkgroup = 2,
+ SpvScopeSubgroup = 3,
+ SpvScopeInvocation = 4,
+} SpvScope;
+
+typedef enum SpvGroupOperation_ {
+ SpvGroupOperationReduce = 0,
+ SpvGroupOperationInclusiveScan = 1,
+ SpvGroupOperationExclusiveScan = 2,
+} SpvGroupOperation;
+
+typedef enum SpvKernelEnqueueFlags_ {
+ SpvKernelEnqueueFlagsNoWait = 0,
+ SpvKernelEnqueueFlagsWaitKernel = 1,
+ SpvKernelEnqueueFlagsWaitWorkGroup = 2,
+} SpvKernelEnqueueFlags;
+
+typedef enum SpvKernelProfilingInfoShift_ {
+ SpvKernelProfilingInfoCmdExecTimeShift = 0,
+} SpvKernelProfilingInfoShift;
+
+typedef enum SpvKernelProfilingInfoMask_ {
+ SpvKernelProfilingInfoMaskNone = 0,
+ SpvKernelProfilingInfoCmdExecTimeMask = 0x00000001,
+} SpvKernelProfilingInfoMask;
+
+typedef enum SpvCapability_ {
+ SpvCapabilityMatrix = 0,
+ SpvCapabilityShader = 1,
+ SpvCapabilityGeometry = 2,
+ SpvCapabilityTessellation = 3,
+ SpvCapabilityAddresses = 4,
+ SpvCapabilityLinkage = 5,
+ SpvCapabilityKernel = 6,
+ SpvCapabilityVector16 = 7,
+ SpvCapabilityFloat16Buffer = 8,
+ SpvCapabilityFloat16 = 9,
+ SpvCapabilityFloat64 = 10,
+ SpvCapabilityInt64 = 11,
+ SpvCapabilityInt64Atomics = 12,
+ SpvCapabilityImageBasic = 13,
+ SpvCapabilityImageReadWrite = 14,
+ SpvCapabilityImageMipmap = 15,
+ SpvCapabilityPipes = 17,
+ SpvCapabilityGroups = 18,
+ SpvCapabilityDeviceEnqueue = 19,
+ SpvCapabilityLiteralSampler = 20,
+ SpvCapabilityAtomicStorage = 21,
+ SpvCapabilityInt16 = 22,
+ SpvCapabilityTessellationPointSize = 23,
+ SpvCapabilityGeometryPointSize = 24,
+ SpvCapabilityImageGatherExtended = 25,
+ SpvCapabilityStorageImageMultisample = 27,
+ SpvCapabilityUniformBufferArrayDynamicIndexing = 28,
+ SpvCapabilitySampledImageArrayDynamicIndexing = 29,
+ SpvCapabilityStorageBufferArrayDynamicIndexing = 30,
+ SpvCapabilityStorageImageArrayDynamicIndexing = 31,
+ SpvCapabilityClipDistance = 32,
+ SpvCapabilityCullDistance = 33,
+ SpvCapabilityImageCubeArray = 34,
+ SpvCapabilitySampleRateShading = 35,
+ SpvCapabilityImageRect = 36,
+ SpvCapabilitySampledRect = 37,
+ SpvCapabilityGenericPointer = 38,
+ SpvCapabilityInt8 = 39,
+ SpvCapabilityInputAttachment = 40,
+ SpvCapabilitySparseResidency = 41,
+ SpvCapabilityMinLod = 42,
+ SpvCapabilitySampled1D = 43,
+ SpvCapabilityImage1D = 44,
+ SpvCapabilitySampledCubeArray = 45,
+ SpvCapabilitySampledBuffer = 46,
+ SpvCapabilityImageBuffer = 47,
+ SpvCapabilityImageMSArray = 48,
+ SpvCapabilityStorageImageExtendedFormats = 49,
+ SpvCapabilityImageQuery = 50,
+ SpvCapabilityDerivativeControl = 51,
+ SpvCapabilityInterpolationFunction = 52,
+ SpvCapabilityTransformFeedback = 53,
+ SpvCapabilityGeometryStreams = 54,
+ SpvCapabilityStorageImageReadWithoutFormat = 55,
+ SpvCapabilityStorageImageWriteWithoutFormat = 56,
+ SpvCapabilityMultiViewport = 57,
+} SpvCapability;
+
+typedef enum SpvOp_ {
+ SpvOpNop = 0,
+ SpvOpUndef = 1,
+ SpvOpSourceContinued = 2,
+ SpvOpSource = 3,
+ SpvOpSourceExtension = 4,
+ SpvOpName = 5,
+ SpvOpMemberName = 6,
+ SpvOpString = 7,
+ SpvOpLine = 8,
+ SpvOpExtension = 10,
+ SpvOpExtInstImport = 11,
+ SpvOpExtInst = 12,
+ SpvOpMemoryModel = 14,
+ SpvOpEntryPoint = 15,
+ SpvOpExecutionMode = 16,
+ SpvOpCapability = 17,
+ SpvOpTypeVoid = 19,
+ SpvOpTypeBool = 20,
+ SpvOpTypeInt = 21,
+ SpvOpTypeFloat = 22,
+ SpvOpTypeVector = 23,
+ SpvOpTypeMatrix = 24,
+ SpvOpTypeImage = 25,
+ SpvOpTypeSampler = 26,
+ SpvOpTypeSampledImage = 27,
+ SpvOpTypeArray = 28,
+ SpvOpTypeRuntimeArray = 29,
+ SpvOpTypeStruct = 30,
+ SpvOpTypeOpaque = 31,
+ SpvOpTypePointer = 32,
+ SpvOpTypeFunction = 33,
+ SpvOpTypeEvent = 34,
+ SpvOpTypeDeviceEvent = 35,
+ SpvOpTypeReserveId = 36,
+ SpvOpTypeQueue = 37,
+ SpvOpTypePipe = 38,
+ SpvOpTypeForwardPointer = 39,
+ SpvOpConstantTrue = 41,
+ SpvOpConstantFalse = 42,
+ SpvOpConstant = 43,
+ SpvOpConstantComposite = 44,
+ SpvOpConstantSampler = 45,
+ SpvOpConstantNull = 46,
+ SpvOpSpecConstantTrue = 48,
+ SpvOpSpecConstantFalse = 49,
+ SpvOpSpecConstant = 50,
+ SpvOpSpecConstantComposite = 51,
+ SpvOpSpecConstantOp = 52,
+ SpvOpFunction = 54,
+ SpvOpFunctionParameter = 55,
+ SpvOpFunctionEnd = 56,
+ SpvOpFunctionCall = 57,
+ SpvOpVariable = 59,
+ SpvOpImageTexelPointer = 60,
+ SpvOpLoad = 61,
+ SpvOpStore = 62,
+ SpvOpCopyMemory = 63,
+ SpvOpCopyMemorySized = 64,
+ SpvOpAccessChain = 65,
+ SpvOpInBoundsAccessChain = 66,
+ SpvOpPtrAccessChain = 67,
+ SpvOpArrayLength = 68,
+ SpvOpGenericPtrMemSemantics = 69,
+ SpvOpInBoundsPtrAccessChain = 70,
+ SpvOpDecorate = 71,
+ SpvOpMemberDecorate = 72,
+ SpvOpDecorationGroup = 73,
+ SpvOpGroupDecorate = 74,
+ SpvOpGroupMemberDecorate = 75,
+ SpvOpVectorExtractDynamic = 77,
+ SpvOpVectorInsertDynamic = 78,
+ SpvOpVectorShuffle = 79,
+ SpvOpCompositeConstruct = 80,
+ SpvOpCompositeExtract = 81,
+ SpvOpCompositeInsert = 82,
+ SpvOpCopyObject = 83,
+ SpvOpTranspose = 84,
+ SpvOpSampledImage = 86,
+ SpvOpImageSampleImplicitLod = 87,
+ SpvOpImageSampleExplicitLod = 88,
+ SpvOpImageSampleDrefImplicitLod = 89,
+ SpvOpImageSampleDrefExplicitLod = 90,
+ SpvOpImageSampleProjImplicitLod = 91,
+ SpvOpImageSampleProjExplicitLod = 92,
+ SpvOpImageSampleProjDrefImplicitLod = 93,
+ SpvOpImageSampleProjDrefExplicitLod = 94,
+ SpvOpImageFetch = 95,
+ SpvOpImageGather = 96,
+ SpvOpImageDrefGather = 97,
+ SpvOpImageRead = 98,
+ SpvOpImageWrite = 99,
+ SpvOpImage = 100,
+ SpvOpImageQueryFormat = 101,
+ SpvOpImageQueryOrder = 102,
+ SpvOpImageQuerySizeLod = 103,
+ SpvOpImageQuerySize = 104,
+ SpvOpImageQueryLod = 105,
+ SpvOpImageQueryLevels = 106,
+ SpvOpImageQuerySamples = 107,
+ SpvOpConvertFToU = 109,
+ SpvOpConvertFToS = 110,
+ SpvOpConvertSToF = 111,
+ SpvOpConvertUToF = 112,
+ SpvOpUConvert = 113,
+ SpvOpSConvert = 114,
+ SpvOpFConvert = 115,
+ SpvOpQuantizeToF16 = 116,
+ SpvOpConvertPtrToU = 117,
+ SpvOpSatConvertSToU = 118,
+ SpvOpSatConvertUToS = 119,
+ SpvOpConvertUToPtr = 120,
+ SpvOpPtrCastToGeneric = 121,
+ SpvOpGenericCastToPtr = 122,
+ SpvOpGenericCastToPtrExplicit = 123,
+ SpvOpBitcast = 124,
+ SpvOpSNegate = 126,
+ SpvOpFNegate = 127,
+ SpvOpIAdd = 128,
+ SpvOpFAdd = 129,
+ SpvOpISub = 130,
+ SpvOpFSub = 131,
+ SpvOpIMul = 132,
+ SpvOpFMul = 133,
+ SpvOpUDiv = 134,
+ SpvOpSDiv = 135,
+ SpvOpFDiv = 136,
+ SpvOpUMod = 137,
+ SpvOpSRem = 138,
+ SpvOpSMod = 139,
+ SpvOpFRem = 140,
+ SpvOpFMod = 141,
+ SpvOpVectorTimesScalar = 142,
+ SpvOpMatrixTimesScalar = 143,
+ SpvOpVectorTimesMatrix = 144,
+ SpvOpMatrixTimesVector = 145,
+ SpvOpMatrixTimesMatrix = 146,
+ SpvOpOuterProduct = 147,
+ SpvOpDot = 148,
+ SpvOpIAddCarry = 149,
+ SpvOpISubBorrow = 150,
+ SpvOpUMulExtended = 151,
+ SpvOpSMulExtended = 152,
+ SpvOpAny = 154,
+ SpvOpAll = 155,
+ SpvOpIsNan = 156,
+ SpvOpIsInf = 157,
+ SpvOpIsFinite = 158,
+ SpvOpIsNormal = 159,
+ SpvOpSignBitSet = 160,
+ SpvOpLessOrGreater = 161,
+ SpvOpOrdered = 162,
+ SpvOpUnordered = 163,
+ SpvOpLogicalEqual = 164,
+ SpvOpLogicalNotEqual = 165,
+ SpvOpLogicalOr = 166,
+ SpvOpLogicalAnd = 167,
+ SpvOpLogicalNot = 168,
+ SpvOpSelect = 169,
+ SpvOpIEqual = 170,
+ SpvOpINotEqual = 171,
+ SpvOpUGreaterThan = 172,
+ SpvOpSGreaterThan = 173,
+ SpvOpUGreaterThanEqual = 174,
+ SpvOpSGreaterThanEqual = 175,
+ SpvOpULessThan = 176,
+ SpvOpSLessThan = 177,
+ SpvOpULessThanEqual = 178,
+ SpvOpSLessThanEqual = 179,
+ SpvOpFOrdEqual = 180,
+ SpvOpFUnordEqual = 181,
+ SpvOpFOrdNotEqual = 182,
+ SpvOpFUnordNotEqual = 183,
+ SpvOpFOrdLessThan = 184,
+ SpvOpFUnordLessThan = 185,
+ SpvOpFOrdGreaterThan = 186,
+ SpvOpFUnordGreaterThan = 187,
+ SpvOpFOrdLessThanEqual = 188,
+ SpvOpFUnordLessThanEqual = 189,
+ SpvOpFOrdGreaterThanEqual = 190,
+ SpvOpFUnordGreaterThanEqual = 191,
+ SpvOpShiftRightLogical = 194,
+ SpvOpShiftRightArithmetic = 195,
+ SpvOpShiftLeftLogical = 196,
+ SpvOpBitwiseOr = 197,
+ SpvOpBitwiseXor = 198,
+ SpvOpBitwiseAnd = 199,
+ SpvOpNot = 200,
+ SpvOpBitFieldInsert = 201,
+ SpvOpBitFieldSExtract = 202,
+ SpvOpBitFieldUExtract = 203,
+ SpvOpBitReverse = 204,
+ SpvOpBitCount = 205,
+ SpvOpDPdx = 207,
+ SpvOpDPdy = 208,
+ SpvOpFwidth = 209,
+ SpvOpDPdxFine = 210,
+ SpvOpDPdyFine = 211,
+ SpvOpFwidthFine = 212,
+ SpvOpDPdxCoarse = 213,
+ SpvOpDPdyCoarse = 214,
+ SpvOpFwidthCoarse = 215,
+ SpvOpEmitVertex = 218,
+ SpvOpEndPrimitive = 219,
+ SpvOpEmitStreamVertex = 220,
+ SpvOpEndStreamPrimitive = 221,
+ SpvOpControlBarrier = 224,
+ SpvOpMemoryBarrier = 225,
+ SpvOpAtomicLoad = 227,
+ SpvOpAtomicStore = 228,
+ SpvOpAtomicExchange = 229,
+ SpvOpAtomicCompareExchange = 230,
+ SpvOpAtomicCompareExchangeWeak = 231,
+ SpvOpAtomicIIncrement = 232,
+ SpvOpAtomicIDecrement = 233,
+ SpvOpAtomicIAdd = 234,
+ SpvOpAtomicISub = 235,
+ SpvOpAtomicSMin = 236,
+ SpvOpAtomicUMin = 237,
+ SpvOpAtomicSMax = 238,
+ SpvOpAtomicUMax = 239,
+ SpvOpAtomicAnd = 240,
+ SpvOpAtomicOr = 241,
+ SpvOpAtomicXor = 242,
+ SpvOpPhi = 245,
+ SpvOpLoopMerge = 246,
+ SpvOpSelectionMerge = 247,
+ SpvOpLabel = 248,
+ SpvOpBranch = 249,
+ SpvOpBranchConditional = 250,
+ SpvOpSwitch = 251,
+ SpvOpKill = 252,
+ SpvOpReturn = 253,
+ SpvOpReturnValue = 254,
+ SpvOpUnreachable = 255,
+ SpvOpLifetimeStart = 256,
+ SpvOpLifetimeStop = 257,
+ SpvOpGroupAsyncCopy = 259,
+ SpvOpGroupWaitEvents = 260,
+ SpvOpGroupAll = 261,
+ SpvOpGroupAny = 262,
+ SpvOpGroupBroadcast = 263,
+ SpvOpGroupIAdd = 264,
+ SpvOpGroupFAdd = 265,
+ SpvOpGroupFMin = 266,
+ SpvOpGroupUMin = 267,
+ SpvOpGroupSMin = 268,
+ SpvOpGroupFMax = 269,
+ SpvOpGroupUMax = 270,
+ SpvOpGroupSMax = 271,
+ SpvOpReadPipe = 274,
+ SpvOpWritePipe = 275,
+ SpvOpReservedReadPipe = 276,
+ SpvOpReservedWritePipe = 277,
+ SpvOpReserveReadPipePackets = 278,
+ SpvOpReserveWritePipePackets = 279,
+ SpvOpCommitReadPipe = 280,
+ SpvOpCommitWritePipe = 281,
+ SpvOpIsValidReserveId = 282,
+ SpvOpGetNumPipePackets = 283,
+ SpvOpGetMaxPipePackets = 284,
+ SpvOpGroupReserveReadPipePackets = 285,
+ SpvOpGroupReserveWritePipePackets = 286,
+ SpvOpGroupCommitReadPipe = 287,
+ SpvOpGroupCommitWritePipe = 288,
+ SpvOpEnqueueMarker = 291,
+ SpvOpEnqueueKernel = 292,
+ SpvOpGetKernelNDrangeSubGroupCount = 293,
+ SpvOpGetKernelNDrangeMaxSubGroupSize = 294,
+ SpvOpGetKernelWorkGroupSize = 295,
+ SpvOpGetKernelPreferredWorkGroupSizeMultiple = 296,
+ SpvOpRetainEvent = 297,
+ SpvOpReleaseEvent = 298,
+ SpvOpCreateUserEvent = 299,
+ SpvOpIsValidEvent = 300,
+ SpvOpSetUserEventStatus = 301,
+ SpvOpCaptureEventProfilingInfo = 302,
+ SpvOpGetDefaultQueue = 303,
+ SpvOpBuildNDRange = 304,
+ SpvOpImageSparseSampleImplicitLod = 305,
+ SpvOpImageSparseSampleExplicitLod = 306,
+ SpvOpImageSparseSampleDrefImplicitLod = 307,
+ SpvOpImageSparseSampleDrefExplicitLod = 308,
+ SpvOpImageSparseSampleProjImplicitLod = 309,
+ SpvOpImageSparseSampleProjExplicitLod = 310,
+ SpvOpImageSparseSampleProjDrefImplicitLod = 311,
+ SpvOpImageSparseSampleProjDrefExplicitLod = 312,
+ SpvOpImageSparseFetch = 313,
+ SpvOpImageSparseGather = 314,
+ SpvOpImageSparseDrefGather = 315,
+ SpvOpImageSparseTexelsResident = 316,
+ SpvOpNoLine = 317,
+ SpvOpAtomicFlagTestAndSet = 318,
+ SpvOpAtomicFlagClear = 319,
+ SpvOpImageSparseRead = 320,
+} SpvOp;
+
+#endif // #ifndef spirv_H
diff --git a/gfx/skia/skia/src/sksl/tracing/SkRPDebugTrace.cpp b/gfx/skia/skia/src/sksl/tracing/SkRPDebugTrace.cpp
new file mode 100644
index 0000000000..9e3e6254f7
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/tracing/SkRPDebugTrace.cpp
@@ -0,0 +1,32 @@
+/*
+ * 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 "src/sksl/tracing/SkRPDebugTrace.h"
+
+#include <sstream>
+#include <utility>
+
+namespace SkSL {
+
+void SkRPDebugTrace::writeTrace(SkWStream* o) const {
+ // Not yet implemented.
+}
+
+void SkRPDebugTrace::dump(SkWStream* o) const {
+ // Not yet implemented.
+}
+
+void SkRPDebugTrace::setSource(std::string source) {
+ fSource.clear();
+ std::stringstream stream{std::move(source)};
+ while (stream.good()) {
+ fSource.push_back({});
+ std::getline(stream, fSource.back(), '\n');
+ }
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/tracing/SkRPDebugTrace.h b/gfx/skia/skia/src/sksl/tracing/SkRPDebugTrace.h
new file mode 100644
index 0000000000..1c3fa3bc54
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/tracing/SkRPDebugTrace.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKRPDEBUGTRACE
+#define SKRPDEBUGTRACE
+
+#include "include/sksl/SkSLDebugTrace.h"
+#include "src/sksl/tracing/SkSLDebugInfo.h"
+
+#include <string>
+#include <vector>
+
+class SkWStream;
+
+namespace SkSL {
+
+class SkRPDebugTrace : public DebugTrace {
+public:
+ /** Serializes a debug trace to JSON which can be parsed by our debugger. */
+ void writeTrace(SkWStream* o) const override;
+
+ /** Generates a human-readable dump of the debug trace. */
+ void dump(SkWStream* o) const override;
+
+ /** Attaches the SkSL source to be debugged. */
+ void setSource(std::string source);
+
+ /** A 1:1 mapping of slot numbers to debug information. */
+ std::vector<SlotDebugInfo> fSlotInfo;
+ std::vector<FunctionDebugInfo> fFuncInfo;
+
+ /** The SkSL debug trace. */
+ std::vector<TraceInfo> fTraceInfo;
+
+ /** SkVM uniforms live in fSlotInfo; SkRP has dedicated a uniform slot map in fUniformInfo. */
+ std::vector<SlotDebugInfo> fUniformInfo;
+
+ /** The SkSL code, split line-by-line. */
+ std::vector<std::string> fSource;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/tracing/SkSLDebugInfo.h b/gfx/skia/skia/src/sksl/tracing/SkSLDebugInfo.h
new file mode 100644
index 0000000000..74b7a430d4
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/tracing/SkSLDebugInfo.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSLDEBUGINFO
+#define SKSLDEBUGINFO
+
+#include "src/sksl/ir/SkSLType.h"
+
+#include <cstdint>
+#include <string>
+
+namespace SkSL {
+
+struct TraceInfo {
+ enum class Op {
+ kLine, /** data: line number, (unused) */
+ kVar, /** data: slot, value */
+ kEnter, /** data: function index, (unused) */
+ kExit, /** data: function index, (unused) */
+ kScope, /** data: scope delta, (unused) */
+ };
+ Op op;
+ int32_t data[2];
+};
+
+struct SlotDebugInfo {
+ /** The full name of this variable (without component), e.g. `myArray[3].myStruct.myVector` */
+ std::string name;
+ /** The dimensions of this variable: 1x1 is a scalar, Nx1 is a vector, NxM is a matrix. */
+ uint8_t columns = 1, rows = 1;
+ /** Which component of the variable is this slot? (e.g. `vec4.z` is component 2) */
+ uint8_t componentIndex = 0;
+ /** Complex types (arrays/structs) can be tracked as a "group" of adjacent slots. */
+ int groupIndex = 0;
+ /** What kind of numbers belong in this slot? */
+ SkSL::Type::NumberKind numberKind = SkSL::Type::NumberKind::kNonnumeric;
+ /** Where is this variable located in the program? */
+ int line = 0;
+ Position pos = {};
+ /** If this slot holds a function's return value, contains 1; if not, -1. */
+ int fnReturnValue = -1;
+};
+
+struct FunctionDebugInfo {
+ /** Full function declaration: `float myFunction(half4 color)`) */
+ std::string name;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/tracing/SkSLTraceHook.cpp b/gfx/skia/skia/src/sksl/tracing/SkSLTraceHook.cpp
new file mode 100644
index 0000000000..c394f7e0a7
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/tracing/SkSLTraceHook.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/tracing/SkSLDebugInfo.h"
+#include "src/sksl/tracing/SkSLTraceHook.h"
+
+namespace SkSL {
+
+std::unique_ptr<Tracer> Tracer::Make(std::vector<TraceInfo>* traceInfo) {
+ auto hook = std::make_unique<Tracer>();
+ hook->fTraceInfo = traceInfo;
+ return hook;
+}
+
+void Tracer::line(int lineNum) {
+ fTraceInfo->push_back({TraceInfo::Op::kLine, /*data=*/{lineNum, 0}});
+}
+void Tracer::var(int slot, int32_t val) {
+ fTraceInfo->push_back({TraceInfo::Op::kVar, /*data=*/{slot, val}});
+}
+void Tracer::enter(int fnIdx) {
+ fTraceInfo->push_back({TraceInfo::Op::kEnter, /*data=*/{fnIdx, 0}});
+}
+void Tracer::exit(int fnIdx) {
+ fTraceInfo->push_back({TraceInfo::Op::kExit, /*data=*/{fnIdx, 0}});
+}
+void Tracer::scope(int delta) {
+ fTraceInfo->push_back({TraceInfo::Op::kScope, /*data=*/{delta, 0}});
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/tracing/SkSLTraceHook.h b/gfx/skia/skia/src/sksl/tracing/SkSLTraceHook.h
new file mode 100644
index 0000000000..404e1be229
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/tracing/SkSLTraceHook.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSLTRACEHOOK
+#define SKSLTRACEHOOK
+
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+namespace SkSL {
+
+struct TraceInfo;
+
+class TraceHook {
+public:
+ virtual ~TraceHook() = default;
+ virtual void line(int lineNum) = 0;
+ virtual void var(int slot, int32_t val) = 0;
+ virtual void enter(int fnIdx) = 0;
+ virtual void exit(int fnIdx) = 0;
+ virtual void scope(int delta) = 0;
+};
+
+class Tracer : public TraceHook {
+public:
+ static std::unique_ptr<Tracer> Make(std::vector<TraceInfo>* traceInfo);
+
+ void line(int lineNum) override;
+ void var(int slot, int32_t val) override;
+ void enter(int fnIdx) override;
+ void exit(int fnIdx) override;
+ void scope(int delta) override;
+
+private:
+ std::vector<TraceInfo>* fTraceInfo;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/tracing/SkVMDebugTrace.cpp b/gfx/skia/skia/src/sksl/tracing/SkVMDebugTrace.cpp
new file mode 100644
index 0000000000..091bd73720
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/tracing/SkVMDebugTrace.cpp
@@ -0,0 +1,417 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/tracing/SkVMDebugTrace.h"
+
+#ifdef SKSL_ENABLE_TRACING
+
+#include "include/core/SkData.h"
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkStream.h"
+#include "include/core/SkTypes.h"
+#include "src/core/SkStreamPriv.h"
+#include "src/sksl/ir/SkSLType.h"
+#include "src/utils/SkJSON.h"
+#include "src/utils/SkJSONWriter.h"
+
+#include <cstdio>
+#include <cstring>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <utility>
+
+static constexpr char kTraceVersion[] = "20220209";
+
+namespace SkSL {
+
+std::string SkVMDebugTrace::getSlotComponentSuffix(int slotIndex) const {
+ const SkSL::SlotDebugInfo& slot = fSlotInfo[slotIndex];
+
+ if (slot.rows > 1) {
+ return "[" + std::to_string(slot.componentIndex / slot.rows) +
+ "][" + std::to_string(slot.componentIndex % slot.rows) +
+ "]";
+ }
+ if (slot.columns > 1) {
+ switch (slot.componentIndex) {
+ case 0: return ".x";
+ case 1: return ".y";
+ case 2: return ".z";
+ case 3: return ".w";
+ default: return "[???]";
+ }
+ }
+ return {};
+}
+
+double SkVMDebugTrace::interpretValueBits(int slotIndex, int32_t valueBits) const {
+ SkASSERT(slotIndex >= 0);
+ SkASSERT((size_t)slotIndex < fSlotInfo.size());
+ switch (fSlotInfo[slotIndex].numberKind) {
+ case SkSL::Type::NumberKind::kUnsigned: {
+ uint32_t uintValue;
+ static_assert(sizeof(uintValue) == sizeof(valueBits));
+ memcpy(&uintValue, &valueBits, sizeof(uintValue));
+ return uintValue;
+ }
+ case SkSL::Type::NumberKind::kFloat: {
+ float floatValue;
+ static_assert(sizeof(floatValue) == sizeof(valueBits));
+ memcpy(&floatValue, &valueBits, sizeof(floatValue));
+ return floatValue;
+ }
+ default: {
+ return valueBits;
+ }
+ }
+}
+
+std::string SkVMDebugTrace::slotValueToString(int slotIndex, double value) const {
+ SkASSERT(slotIndex >= 0);
+ SkASSERT((size_t)slotIndex < fSlotInfo.size());
+ switch (fSlotInfo[slotIndex].numberKind) {
+ case SkSL::Type::NumberKind::kBoolean: {
+ return value ? "true" : "false";
+ }
+ default: {
+ char buffer[32];
+ snprintf(buffer, std::size(buffer), "%.8g", value);
+ return buffer;
+ }
+ }
+}
+
+std::string SkVMDebugTrace::getSlotValue(int slotIndex, int32_t valueBits) const {
+ return this->slotValueToString(slotIndex, this->interpretValueBits(slotIndex, valueBits));
+}
+
+void SkVMDebugTrace::setTraceCoord(const SkIPoint& coord) {
+ fTraceCoord = coord;
+}
+
+void SkVMDebugTrace::setSource(std::string source) {
+ fSource.clear();
+ std::stringstream stream{std::move(source)};
+ while (stream.good()) {
+ fSource.push_back({});
+ std::getline(stream, fSource.back(), '\n');
+ }
+}
+
+void SkVMDebugTrace::dump(SkWStream* o) const {
+ for (size_t index = 0; index < fSlotInfo.size(); ++index) {
+ const SlotDebugInfo& info = fSlotInfo[index];
+
+ o->writeText("$");
+ o->writeDecAsText(index);
+ o->writeText(" = ");
+ o->writeText(info.name.c_str());
+ o->writeText(" (");
+ switch (info.numberKind) {
+ case Type::NumberKind::kFloat: o->writeText("float"); break;
+ case Type::NumberKind::kSigned: o->writeText("int"); break;
+ case Type::NumberKind::kUnsigned: o->writeText("uint"); break;
+ case Type::NumberKind::kBoolean: o->writeText("bool"); break;
+ case Type::NumberKind::kNonnumeric: o->writeText("???"); break;
+ }
+ if (info.rows * info.columns > 1) {
+ o->writeDecAsText(info.columns);
+ if (info.rows != 1) {
+ o->writeText("x");
+ o->writeDecAsText(info.rows);
+ }
+ o->writeText(" : ");
+ o->writeText("slot ");
+ o->writeDecAsText(info.componentIndex + 1);
+ o->writeText("/");
+ o->writeDecAsText(info.rows * info.columns);
+ }
+ o->writeText(", L");
+ o->writeDecAsText(info.line);
+ o->writeText(")");
+ o->newline();
+ }
+
+ for (size_t index = 0; index < fFuncInfo.size(); ++index) {
+ const FunctionDebugInfo& info = fFuncInfo[index];
+
+ o->writeText("F");
+ o->writeDecAsText(index);
+ o->writeText(" = ");
+ o->writeText(info.name.c_str());
+ o->newline();
+ }
+
+ o->newline();
+
+ if (!fTraceInfo.empty()) {
+ std::string indent = "";
+ for (const SkSL::TraceInfo& traceInfo : fTraceInfo) {
+ int data0 = traceInfo.data[0];
+ int data1 = traceInfo.data[1];
+ switch (traceInfo.op) {
+ case SkSL::TraceInfo::Op::kLine:
+ o->writeText(indent.c_str());
+ o->writeText("line ");
+ o->writeDecAsText(data0);
+ break;
+
+ case SkSL::TraceInfo::Op::kVar: {
+ const SlotDebugInfo& slot = fSlotInfo[data0];
+ o->writeText(indent.c_str());
+ o->writeText(slot.name.c_str());
+ o->writeText(this->getSlotComponentSuffix(data0).c_str());
+ o->writeText(" = ");
+ o->writeText(this->getSlotValue(data0, data1).c_str());
+ break;
+ }
+ case SkSL::TraceInfo::Op::kEnter:
+ o->writeText(indent.c_str());
+ o->writeText("enter ");
+ o->writeText(fFuncInfo[data0].name.c_str());
+ indent += " ";
+ break;
+
+ case SkSL::TraceInfo::Op::kExit:
+ indent.resize(indent.size() - 2);
+ o->writeText(indent.c_str());
+ o->writeText("exit ");
+ o->writeText(fFuncInfo[data0].name.c_str());
+ break;
+
+ case SkSL::TraceInfo::Op::kScope:
+ for (int delta = data0; delta < 0; ++delta) {
+ indent.pop_back();
+ }
+ o->writeText(indent.c_str());
+ o->writeText("scope ");
+ o->writeText((data0 >= 0) ? "+" : "");
+ o->writeDecAsText(data0);
+ for (int delta = data0; delta > 0; --delta) {
+ indent.push_back(' ');
+ }
+ break;
+ }
+ o->newline();
+ }
+ }
+}
+
+void SkVMDebugTrace::writeTrace(SkWStream* w) const {
+ SkJSONWriter json(w);
+
+ json.beginObject(); // root
+ json.appendNString("version", kTraceVersion);
+ json.beginArray("source");
+
+ for (const std::string& line : fSource) {
+ json.appendString(line);
+ }
+
+ json.endArray(); // code
+ json.beginArray("slots");
+
+ for (size_t index = 0; index < fSlotInfo.size(); ++index) {
+ const SlotDebugInfo& info = fSlotInfo[index];
+
+ json.beginObject();
+ json.appendString("name", info.name.data(), info.name.size());
+ json.appendS32("columns", info.columns);
+ json.appendS32("rows", info.rows);
+ json.appendS32("index", info.componentIndex);
+ if (info.groupIndex != info.componentIndex) {
+ json.appendS32("groupIdx", info.groupIndex);
+ }
+ json.appendS32("kind", (int)info.numberKind);
+ json.appendS32("line", info.line);
+ if (info.fnReturnValue >= 0) {
+ json.appendS32("retval", info.fnReturnValue);
+ }
+ json.endObject();
+ }
+
+ json.endArray(); // slots
+ json.beginArray("functions");
+
+ for (size_t index = 0; index < fFuncInfo.size(); ++index) {
+ const FunctionDebugInfo& info = fFuncInfo[index];
+
+ json.beginObject();
+ json.appendString("name", info.name);
+ json.endObject();
+ }
+
+ json.endArray(); // functions
+ json.beginArray("trace");
+
+ for (size_t index = 0; index < fTraceInfo.size(); ++index) {
+ const TraceInfo& trace = fTraceInfo[index];
+ json.beginArray();
+ json.appendS32((int)trace.op);
+
+ // Skip trailing zeros in the data (since most ops only use one value).
+ int lastDataIdx = std::size(trace.data) - 1;
+ while (lastDataIdx >= 0 && !trace.data[lastDataIdx]) {
+ --lastDataIdx;
+ }
+ for (int dataIdx = 0; dataIdx <= lastDataIdx; ++dataIdx) {
+ json.appendS32(trace.data[dataIdx]);
+ }
+ json.endArray();
+ }
+
+ json.endArray(); // trace
+ json.endObject(); // root
+ json.flush();
+}
+
+bool SkVMDebugTrace::readTrace(SkStream* r) {
+ sk_sp<SkData> data = SkCopyStreamToData(r);
+ skjson::DOM json(reinterpret_cast<const char*>(data->bytes()), data->size());
+ const skjson::ObjectValue* root = json.root();
+ if (!root) {
+ return false;
+ }
+
+ const skjson::StringValue* version = (*root)["version"];
+ if (!version || version->str() != kTraceVersion) {
+ return false;
+ }
+
+ const skjson::ArrayValue* source = (*root)["source"];
+ if (!source) {
+ return false;
+ }
+
+ fSource.clear();
+ for (const skjson::StringValue* line : *source) {
+ if (!line) {
+ return false;
+ }
+ fSource.push_back(line->begin());
+ }
+
+ const skjson::ArrayValue* slots = (*root)["slots"];
+ if (!slots) {
+ return false;
+ }
+
+ fSlotInfo.clear();
+ for (const skjson::ObjectValue* element : *slots) {
+ if (!element) {
+ return false;
+ }
+
+ // Grow the slot array to hold this element.
+ fSlotInfo.push_back({});
+ SlotDebugInfo& info = fSlotInfo.back();
+
+ // Populate the SlotInfo with our JSON data.
+ const skjson::StringValue* name = (*element)["name"];
+ const skjson::NumberValue* columns = (*element)["columns"];
+ const skjson::NumberValue* rows = (*element)["rows"];
+ const skjson::NumberValue* index = (*element)["index"];
+ const skjson::NumberValue* groupIdx = (*element)["groupIdx"];
+ const skjson::NumberValue* kind = (*element)["kind"];
+ const skjson::NumberValue* line = (*element)["line"];
+ const skjson::NumberValue* retval = (*element)["retval"];
+ if (!name || !columns || !rows || !index || !kind || !line) {
+ return false;
+ }
+
+ info.name = name->begin();
+ info.columns = **columns;
+ info.rows = **rows;
+ info.componentIndex = **index;
+ info.groupIndex = groupIdx ? **groupIdx : info.componentIndex;
+ info.numberKind = (SkSL::Type::NumberKind)(int)**kind;
+ info.line = **line;
+ info.fnReturnValue = retval ? **retval : -1;
+ }
+
+ const skjson::ArrayValue* functions = (*root)["functions"];
+ if (!functions) {
+ return false;
+ }
+
+ fFuncInfo.clear();
+ for (const skjson::ObjectValue* element : *functions) {
+ if (!element) {
+ return false;
+ }
+
+ // Grow the function array to hold this element.
+ fFuncInfo.push_back({});
+ FunctionDebugInfo& info = fFuncInfo.back();
+
+ // Populate the FunctionInfo with our JSON data.
+ const skjson::StringValue* name = (*element)["name"];
+ if (!name) {
+ return false;
+ }
+
+ info.name = name->begin();
+ }
+
+ const skjson::ArrayValue* trace = (*root)["trace"];
+ if (!trace) {
+ return false;
+ }
+
+ fTraceInfo.clear();
+ fTraceInfo.reserve(trace->size());
+ for (const skjson::ArrayValue* element : *trace) {
+ fTraceInfo.push_back(TraceInfo{});
+ TraceInfo& info = fTraceInfo.back();
+
+ if (!element || element->size() < 1 || element->size() > (1 + std::size(info.data))) {
+ return false;
+ }
+ const skjson::NumberValue* opVal = (*element)[0];
+ if (!opVal) {
+ return false;
+ }
+ info.op = (TraceInfo::Op)(int)**opVal;
+ for (size_t elemIdx = 1; elemIdx < element->size(); ++elemIdx) {
+ const skjson::NumberValue* dataVal = (*element)[elemIdx];
+ if (!dataVal) {
+ return false;
+ }
+ info.data[elemIdx - 1] = **dataVal;
+ }
+ }
+
+ return true;
+}
+
+} // namespace SkSL
+
+#else // SKSL_ENABLE_TRACING
+
+#include <string>
+
+namespace SkSL {
+ void SkVMDebugTrace::setTraceCoord(const SkIPoint &coord) {}
+
+ void SkVMDebugTrace::setSource(std::string source) {}
+
+ bool SkVMDebugTrace::readTrace(SkStream *r) { return false; }
+
+ void SkVMDebugTrace::writeTrace(SkWStream *w) const {}
+
+ void SkVMDebugTrace::dump(SkWStream *o) const {}
+
+ std::string SkVMDebugTrace::getSlotComponentSuffix(int slotIndex) const { return ""; }
+
+ std::string SkVMDebugTrace::getSlotValue(int slotIndex, int32_t value) const { return ""; }
+
+ double SkVMDebugTrace::interpretValueBits(int slotIndex, int32_t valueBits) const { return 0; }
+
+ std::string SkVMDebugTrace::slotValueToString(int slotIndex, double value) const { return ""; }
+}
+#endif
diff --git a/gfx/skia/skia/src/sksl/tracing/SkVMDebugTrace.h b/gfx/skia/skia/src/sksl/tracing/SkVMDebugTrace.h
new file mode 100644
index 0000000000..c9b3525fe7
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/tracing/SkVMDebugTrace.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKVMDEBUGTRACE
+#define SKVMDEBUGTRACE
+
+#include "include/sksl/SkSLDebugTrace.h"
+
+#include "include/core/SkPoint.h"
+#include "src/sksl/tracing/SkSLDebugInfo.h"
+#include "src/sksl/tracing/SkSLTraceHook.h"
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+class SkStream;
+class SkWStream;
+
+namespace SkSL {
+
+class SkVMDebugTrace : public DebugTrace {
+public:
+ /**
+ * Sets the device-coordinate pixel to trace. If it's not set, the point at (0, 0) will be used.
+ */
+ void setTraceCoord(const SkIPoint& coord);
+
+ /** Attaches the SkSL source to be debugged. */
+ void setSource(std::string source);
+
+ /** Serializes a debug trace to JSON which can be parsed by our debugger. */
+ bool readTrace(SkStream* r);
+ void writeTrace(SkWStream* w) const override;
+
+ /** Generates a human-readable dump of the debug trace. */
+ void dump(SkWStream* o) const override;
+
+ /** Returns a slot's component as a variable-name suffix, e.g. ".x" or "[2][2]". */
+ std::string getSlotComponentSuffix(int slotIndex) const;
+
+ /** Bit-casts a slot's value, then converts to text, e.g. "3.14" or "true" or "12345". */
+ std::string getSlotValue(int slotIndex, int32_t value) const;
+
+ /** Bit-casts a value for a given slot into a double, honoring the slot's NumberKind. */
+ double interpretValueBits(int slotIndex, int32_t valueBits) const;
+
+ /** Converts a numeric value into text, based on the slot's NumberKind. */
+ std::string slotValueToString(int slotIndex, double value) const;
+
+ /** The device-coordinate pixel to trace (controlled by setTraceCoord) */
+ SkIPoint fTraceCoord = {};
+
+ /** A 1:1 mapping of slot numbers to debug information. */
+ std::vector<SlotDebugInfo> fSlotInfo;
+ std::vector<FunctionDebugInfo> fFuncInfo;
+
+ /** The SkSL debug trace. */
+ std::vector<TraceInfo> fTraceInfo;
+
+ /** The SkSL code, split line-by-line. */
+ std::vector<std::string> fSource;
+
+ /**
+ * A trace hook which populates fTraceInfo during SkVM program evaluation. This will be created
+ * automatically by the SkSLVMCodeGenerator.
+ */
+ std::unique_ptr<SkSL::TraceHook> fTraceHook;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/tracing/SkVMDebugTracePlayer.cpp b/gfx/skia/skia/src/sksl/tracing/SkVMDebugTracePlayer.cpp
new file mode 100644
index 0000000000..7ae9e4638b
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/tracing/SkVMDebugTracePlayer.cpp
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/tracing/SkSLDebugInfo.h"
+#include "src/sksl/tracing/SkVMDebugTracePlayer.h"
+
+#include <limits.h>
+#include <algorithm>
+#include <utility>
+
+namespace SkSL {
+
+void SkVMDebugTracePlayer::reset(sk_sp<SkVMDebugTrace> debugTrace) {
+ size_t nslots = debugTrace ? debugTrace->fSlotInfo.size() : 0;
+ fDebugTrace = debugTrace;
+ fCursor = 0;
+ fScope = 0;
+ fSlots.clear();
+ fSlots.resize(nslots, {/*fValue=*/0,
+ /*fScope=*/INT_MAX,
+ /*fWriteTime=*/0});
+ fStack.clear();
+ fStack.push_back({/*fFunction=*/-1,
+ /*fLine=*/-1,
+ /*fDisplayMask=*/SkBitSet(nslots)});
+ fDirtyMask.emplace(nslots);
+ fReturnValues.emplace(nslots);
+
+ if (fDebugTrace) {
+ for (size_t slotIdx = 0; slotIdx < nslots; ++slotIdx) {
+ if (fDebugTrace->fSlotInfo[slotIdx].fnReturnValue >= 0) {
+ fReturnValues->set(slotIdx);
+ }
+ }
+
+ for (const TraceInfo& trace : fDebugTrace->fTraceInfo) {
+ if (trace.op == TraceInfo::Op::kLine) {
+ fLineNumbers[trace.data[0]] += 1;
+ }
+ }
+ }
+}
+
+void SkVMDebugTracePlayer::step() {
+ this->tidyState();
+ while (!this->traceHasCompleted()) {
+ if (this->execute(fCursor++)) {
+ break;
+ }
+ }
+}
+
+void SkVMDebugTracePlayer::stepOver() {
+ this->tidyState();
+ size_t initialStackDepth = fStack.size();
+ while (!this->traceHasCompleted()) {
+ bool canEscapeFromThisStackDepth = (fStack.size() <= initialStackDepth);
+ if (this->execute(fCursor++)) {
+ if (canEscapeFromThisStackDepth || this->atBreakpoint()) {
+ break;
+ }
+ }
+ }
+}
+
+void SkVMDebugTracePlayer::stepOut() {
+ this->tidyState();
+ size_t initialStackDepth = fStack.size();
+ while (!this->traceHasCompleted()) {
+ if (this->execute(fCursor++)) {
+ bool hasEscapedFromInitialStackDepth = (fStack.size() < initialStackDepth);
+ if (hasEscapedFromInitialStackDepth || this->atBreakpoint()) {
+ break;
+ }
+ }
+ }
+}
+
+void SkVMDebugTracePlayer::run() {
+ this->tidyState();
+ while (!this->traceHasCompleted()) {
+ if (this->execute(fCursor++)) {
+ if (this->atBreakpoint()) {
+ break;
+ }
+ }
+ }
+}
+
+void SkVMDebugTracePlayer::tidyState() {
+ fDirtyMask->reset();
+
+ // Conceptually this is `fStack.back().fDisplayMask &= ~fReturnValues`, but SkBitSet doesn't
+ // support masking one set of bits against another.
+ fReturnValues->forEachSetIndex([&](int slot) {
+ fStack.back().fDisplayMask.reset(slot);
+ });
+}
+
+bool SkVMDebugTracePlayer::traceHasCompleted() const {
+ return !fDebugTrace || fCursor >= fDebugTrace->fTraceInfo.size();
+}
+
+int32_t SkVMDebugTracePlayer::getCurrentLine() const {
+ SkASSERT(!fStack.empty());
+ return fStack.back().fLine;
+}
+
+int32_t SkVMDebugTracePlayer::getCurrentLineInStackFrame(int stackFrameIndex) const {
+ // The first entry on the stack is the "global" frame before we enter main, so offset our index
+ // by one to account for it.
+ ++stackFrameIndex;
+ SkASSERT(stackFrameIndex > 0);
+ SkASSERT((size_t)stackFrameIndex < fStack.size());
+ return fStack[stackFrameIndex].fLine;
+}
+
+bool SkVMDebugTracePlayer::atBreakpoint() const {
+ return fBreakpointLines.count(this->getCurrentLine());
+}
+
+void SkVMDebugTracePlayer::setBreakpoints(std::unordered_set<int> breakpointLines) {
+ fBreakpointLines = std::move(breakpointLines);
+}
+
+void SkVMDebugTracePlayer::addBreakpoint(int line) {
+ fBreakpointLines.insert(line);
+}
+
+void SkVMDebugTracePlayer::removeBreakpoint(int line) {
+ fBreakpointLines.erase(line);
+}
+
+std::vector<int> SkVMDebugTracePlayer::getCallStack() const {
+ SkASSERT(!fStack.empty());
+ std::vector<int> funcs;
+ funcs.reserve(fStack.size() - 1);
+ for (size_t index = 1; index < fStack.size(); ++index) {
+ funcs.push_back(fStack[index].fFunction);
+ }
+ return funcs;
+}
+
+int SkVMDebugTracePlayer::getStackDepth() const {
+ SkASSERT(!fStack.empty());
+ return fStack.size() - 1;
+}
+
+std::vector<SkVMDebugTracePlayer::VariableData> SkVMDebugTracePlayer::getVariablesForDisplayMask(
+ const SkBitSet& displayMask) const {
+ SkASSERT(displayMask.size() == fSlots.size());
+
+ std::vector<VariableData> vars;
+ displayMask.forEachSetIndex([&](int slot) {
+ double typedValue = fDebugTrace->interpretValueBits(slot, fSlots[slot].fValue);
+ vars.push_back({slot, fDirtyMask->test(slot), typedValue});
+ });
+ // Order the variable list so that the most recently-written variables are shown at the top.
+ std::stable_sort(vars.begin(), vars.end(), [&](const VariableData& a, const VariableData& b) {
+ return fSlots[a.fSlotIndex].fWriteTime > fSlots[b.fSlotIndex].fWriteTime;
+ });
+ return vars;
+}
+
+std::vector<SkVMDebugTracePlayer::VariableData> SkVMDebugTracePlayer::getLocalVariables(
+ int stackFrameIndex) const {
+ // The first entry on the stack is the "global" frame before we enter main, so offset our index
+ // by one to account for it.
+ ++stackFrameIndex;
+ if (stackFrameIndex <= 0 || (size_t)stackFrameIndex >= fStack.size()) {
+ SkDEBUGFAILF("stack frame %d doesn't exist", stackFrameIndex - 1);
+ return {};
+ }
+ return this->getVariablesForDisplayMask(fStack[stackFrameIndex].fDisplayMask);
+}
+
+std::vector<SkVMDebugTracePlayer::VariableData> SkVMDebugTracePlayer::getGlobalVariables() const {
+ if (fStack.empty()) {
+ return {};
+ }
+ return this->getVariablesForDisplayMask(fStack.front().fDisplayMask);
+}
+
+void SkVMDebugTracePlayer::updateVariableWriteTime(int slotIdx, size_t cursor) {
+ // The slotIdx could point to any slot within a variable.
+ // We want to update the write time on EVERY slot associated with this variable.
+ // The SlotInfo's groupIndex gives us enough information to find the affected range.
+ const SkSL::SlotDebugInfo& changedSlot = fDebugTrace->fSlotInfo[slotIdx];
+ slotIdx -= changedSlot.groupIndex;
+ SkASSERT(slotIdx >= 0);
+ SkASSERT(slotIdx < (int)fDebugTrace->fSlotInfo.size());
+
+ for (;;) {
+ fSlots[slotIdx++].fWriteTime = cursor;
+
+ // Stop if we've reached the final slot.
+ if (slotIdx >= (int)fDebugTrace->fSlotInfo.size()) {
+ break;
+ }
+ // Each separate variable-group starts with a groupIndex of 0; stop when we detect this.
+ if (fDebugTrace->fSlotInfo[slotIdx].groupIndex == 0) {
+ break;
+ }
+ }
+}
+
+bool SkVMDebugTracePlayer::execute(size_t position) {
+ if (position >= fDebugTrace->fTraceInfo.size()) {
+ SkDEBUGFAILF("position %zu out of range", position);
+ return true;
+ }
+
+ const TraceInfo& trace = fDebugTrace->fTraceInfo[position];
+ switch (trace.op) {
+ case TraceInfo::Op::kLine: { // data: line number, (unused)
+ SkASSERT(!fStack.empty());
+ int lineNumber = trace.data[0];
+ SkASSERT(lineNumber >= 0);
+ SkASSERT((size_t)lineNumber < fDebugTrace->fSource.size());
+ SkASSERT(fLineNumbers[lineNumber] > 0);
+ fStack.back().fLine = lineNumber;
+ fLineNumbers[lineNumber] -= 1;
+ return true;
+ }
+ case TraceInfo::Op::kVar: { // data: slot, value
+ int slotIdx = trace.data[0];
+ int value = trace.data[1];
+ SkASSERT(slotIdx >= 0);
+ SkASSERT((size_t)slotIdx < fDebugTrace->fSlotInfo.size());
+ fSlots[slotIdx].fValue = value;
+ fSlots[slotIdx].fScope = std::min<>(fSlots[slotIdx].fScope, fScope);
+ this->updateVariableWriteTime(slotIdx, position);
+ if (fDebugTrace->fSlotInfo[slotIdx].fnReturnValue < 0) {
+ // Normal variables are associated with the current function.
+ SkASSERT(fStack.size() > 0);
+ fStack.rbegin()[0].fDisplayMask.set(slotIdx);
+ } else {
+ // Return values are associated with the parent function (since the current function
+ // is exiting and we won't see them there).
+ SkASSERT(fStack.size() > 1);
+ fStack.rbegin()[1].fDisplayMask.set(slotIdx);
+ }
+ fDirtyMask->set(slotIdx);
+ break;
+ }
+ case TraceInfo::Op::kEnter: { // data: function index, (unused)
+ int fnIdx = trace.data[0];
+ SkASSERT(fnIdx >= 0);
+ SkASSERT((size_t)fnIdx < fDebugTrace->fFuncInfo.size());
+ fStack.push_back({/*fFunction=*/fnIdx,
+ /*fLine=*/-1,
+ /*fDisplayMask=*/SkBitSet(fDebugTrace->fSlotInfo.size())});
+ break;
+ }
+ case TraceInfo::Op::kExit: { // data: function index, (unused)
+ SkASSERT(!fStack.empty());
+ SkASSERT(fStack.back().fFunction == trace.data[0]);
+ fStack.pop_back();
+ return true;
+ }
+ case TraceInfo::Op::kScope: { // data: scope delta, (unused)
+ SkASSERT(!fStack.empty());
+ fScope += trace.data[0];
+ if (trace.data[0] < 0) {
+ // If the scope is being reduced, discard variables that are now out of scope.
+ for (size_t slotIdx = 0; slotIdx < fSlots.size(); ++slotIdx) {
+ if (fScope < fSlots[slotIdx].fScope) {
+ fSlots[slotIdx].fScope = INT_MAX;
+ fStack.back().fDisplayMask.reset(slotIdx);
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ return false;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/tracing/SkVMDebugTracePlayer.h b/gfx/skia/skia/src/sksl/tracing/SkVMDebugTracePlayer.h
new file mode 100644
index 0000000000..13ee7b7bd8
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/tracing/SkVMDebugTracePlayer.h
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/tracing/SkVMDebugTrace.h"
+
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkTypes.h"
+#include "src/utils/SkBitSet.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+namespace SkSL {
+
+/**
+ * Plays back a SkVM debug trace, allowing its contents to be viewed like a traditional debugger.
+ */
+class SkVMDebugTracePlayer {
+public:
+ /** Resets playback to the start of the trace. Breakpoints are not cleared. */
+ void reset(sk_sp<SkVMDebugTrace> trace);
+
+ /** Advances the simulation to the next Line op. */
+ void step();
+
+ /**
+ * Advances the simulation to the next Line op, skipping past matched Enter/Exit pairs.
+ * Breakpoints will also stop the simulation even if we haven't reached an Exit.
+ */
+ void stepOver();
+
+ /**
+ * Advances the simulation until we exit from the current stack frame.
+ * Breakpoints will also stop the simulation even if we haven't left the stack frame.
+ */
+ void stepOut();
+
+ /** Advances the simulation until we hit a breakpoint, or the trace completes. */
+ void run();
+
+ /** Breakpoints will force the simulation to stop whenever a desired line is reached. */
+ void setBreakpoints(std::unordered_set<int> breakpointLines);
+ void addBreakpoint(int line);
+ void removeBreakpoint(int line);
+ using BreakpointSet = std::unordered_set<int>;
+ const BreakpointSet& getBreakpoints() { return fBreakpointLines; }
+
+ /** Returns true if we have reached the end of the trace. */
+ bool traceHasCompleted() const;
+
+ /** Returns true if there is a breakpoint set at the current line. */
+ bool atBreakpoint() const;
+
+ /** Retrieves the cursor position. */
+ size_t cursor() { return fCursor; }
+
+ /** Retrieves the current line. */
+ int32_t getCurrentLine() const;
+
+ /** Retrieves the current line for a given stack frame. */
+ int32_t getCurrentLineInStackFrame(int stackFrameIndex) const;
+
+ /** Returns the call stack as an array of FunctionInfo indices. */
+ std::vector<int> getCallStack() const;
+
+ /** Returns the size of the call stack. */
+ int getStackDepth() const;
+
+ /**
+ * Returns every line number reached inside this debug trace, along with the remaining number of
+ * times that this trace will reach it. e.g. {100, 2} means line 100 will be reached twice.
+ */
+ using LineNumberMap = std::unordered_map<int, int>;
+ const LineNumberMap& getLineNumbersReached() const { return fLineNumbers; }
+
+ /** Returns variables from a stack frame, or from global scope. */
+ struct VariableData {
+ int fSlotIndex;
+ bool fDirty; // has this slot been written-to since the last step call?
+ double fValue; // value in slot (with type-conversion applied)
+ };
+ std::vector<VariableData> getLocalVariables(int stackFrameIndex) const;
+ std::vector<VariableData> getGlobalVariables() const;
+
+private:
+ /**
+ * Executes the trace op at the passed-in cursor position. Returns true if we've reached a line
+ * or exit trace op, which indicate a stopping point.
+ */
+ bool execute(size_t position);
+
+ /**
+ * Cleans up temporary state between steps, such as the dirty mask and function return values.
+ */
+ void tidyState();
+
+ /** Updates fWriteTime for the entire variable at a given slot. */
+ void updateVariableWriteTime(int slotIdx, size_t writeTime);
+
+ /** Returns a vector of the indices and values of each slot that is enabled in `bits`. */
+ std::vector<VariableData> getVariablesForDisplayMask(const SkBitSet& bits) const;
+
+ struct StackFrame {
+ int32_t fFunction; // from fFuncInfo
+ int32_t fLine; // our current line number within the function
+ SkBitSet fDisplayMask; // the variable slots which have been touched in this function
+ };
+ struct Slot {
+ int32_t fValue; // values in each slot
+ int fScope; // the scope value of each slot
+ size_t fWriteTime; // when was the variable in this slot most recently written?
+ // (by cursor position)
+ };
+ sk_sp<SkVMDebugTrace> fDebugTrace;
+ size_t fCursor = 0; // position of the read head
+ int fScope = 0; // the current scope depth (as tracked by
+ // trace_scope)
+ std::vector<Slot> fSlots; // the array of all slots
+ std::vector<StackFrame> fStack; // the execution stack
+ std::optional<SkBitSet> fDirtyMask; // variable slots touched during the most-recently
+ // executed step
+ std::optional<SkBitSet> fReturnValues; // variable slots containing return values
+ LineNumberMap fLineNumbers; // holds [line number, the remaining number of
+ // times to reach this line during the trace]
+ BreakpointSet fBreakpointLines; // all breakpoints set by setBreakpointLines
+};
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/transform/SkSLAddConstToVarModifiers.cpp b/gfx/skia/skia/src/sksl/transform/SkSLAddConstToVarModifiers.cpp
new file mode 100644
index 0000000000..f024d7d681
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/transform/SkSLAddConstToVarModifiers.cpp
@@ -0,0 +1,44 @@
+/*
+ * 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/private/SkSLModifiers.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLModifiersPool.h"
+#include "src/sksl/analysis/SkSLProgramUsage.h"
+#include "src/sksl/ir/SkSLVariable.h"
+#include "src/sksl/transform/SkSLTransform.h"
+
+namespace SkSL {
+
+class Expression;
+
+const Modifiers* Transform::AddConstToVarModifiers(const Context& context,
+ const Variable& var,
+ const Expression* initialValue,
+ const ProgramUsage* usage) {
+ // If the variable is already marked as `const`, keep our existing modifiers.
+ const Modifiers* modifiers = &var.modifiers();
+ if (modifiers->fFlags & Modifiers::kConst_Flag) {
+ return modifiers;
+ }
+ // If the variable doesn't have a compile-time-constant initial value, we can't `const` it.
+ if (!initialValue || !Analysis::IsCompileTimeConstant(*initialValue)) {
+ return modifiers;
+ }
+ // This only works for variables that are written-to a single time.
+ ProgramUsage::VariableCounts counts = usage->get(var);
+ if (counts.fWrite != 1) {
+ return modifiers;
+ }
+ // Add `const` to our variable's modifiers, making it eligible for constant-folding.
+ Modifiers constModifiers = *modifiers;
+ constModifiers.fFlags |= Modifiers::kConst_Flag;
+ return context.fModifiersPool->add(constModifiers);
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/transform/SkSLEliminateDeadFunctions.cpp b/gfx/skia/skia/src/sksl/transform/SkSLEliminateDeadFunctions.cpp
new file mode 100644
index 0000000000..6332a9b716
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/transform/SkSLEliminateDeadFunctions.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/private/SkSLProgramElement.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/analysis/SkSLProgramUsage.h"
+#include "src/sksl/ir/SkSLFunctionDeclaration.h"
+#include "src/sksl/ir/SkSLFunctionDefinition.h"
+#include "src/sksl/ir/SkSLProgram.h"
+#include "src/sksl/transform/SkSLTransform.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <memory>
+#include <vector>
+
+namespace SkSL {
+
+static bool dead_function_predicate(const ProgramElement* element, ProgramUsage* usage) {
+ if (!element->is<FunctionDefinition>()) {
+ return false;
+ }
+ const FunctionDefinition& fn = element->as<FunctionDefinition>();
+ if (fn.declaration().isMain() || usage->get(fn.declaration()) > 0) {
+ return false;
+ }
+ // This function is about to be eliminated by remove_if; update ProgramUsage accordingly.
+ usage->remove(*element);
+ return true;
+}
+
+bool Transform::EliminateDeadFunctions(Program& program) {
+ ProgramUsage* usage = program.fUsage.get();
+
+ size_t numOwnedElements = program.fOwnedElements.size();
+ size_t numSharedElements = program.fSharedElements.size();
+
+ if (program.fConfig->fSettings.fRemoveDeadFunctions) {
+ program.fOwnedElements.erase(std::remove_if(program.fOwnedElements.begin(),
+ program.fOwnedElements.end(),
+ [&](const std::unique_ptr<ProgramElement>& pe) {
+ return dead_function_predicate(pe.get(),
+ usage);
+ }),
+ program.fOwnedElements.end());
+ program.fSharedElements.erase(std::remove_if(program.fSharedElements.begin(),
+ program.fSharedElements.end(),
+ [&](const ProgramElement* pe) {
+ return dead_function_predicate(pe, usage);
+ }),
+ program.fSharedElements.end());
+ }
+ return program.fOwnedElements.size() < numOwnedElements ||
+ program.fSharedElements.size() < numSharedElements;
+}
+
+bool Transform::EliminateDeadFunctions(const Context& context,
+ Module& module,
+ ProgramUsage* usage) {
+ size_t numElements = module.fElements.size();
+
+ if (context.fConfig->fSettings.fRemoveDeadFunctions) {
+ module.fElements.erase(std::remove_if(module.fElements.begin(),
+ module.fElements.end(),
+ [&](const std::unique_ptr<ProgramElement>& pe) {
+ return dead_function_predicate(pe.get(), usage);
+ }),
+ module.fElements.end());
+ }
+ return module.fElements.size() < numElements;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/transform/SkSLEliminateDeadGlobalVariables.cpp b/gfx/skia/skia/src/sksl/transform/SkSLEliminateDeadGlobalVariables.cpp
new file mode 100644
index 0000000000..700e176ca5
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/transform/SkSLEliminateDeadGlobalVariables.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/private/SkSLProgramElement.h"
+#include "src/base/SkStringView.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/analysis/SkSLProgramUsage.h"
+#include "src/sksl/ir/SkSLProgram.h"
+#include "src/sksl/ir/SkSLVarDeclarations.h"
+#include "src/sksl/ir/SkSLVariable.h"
+#include "src/sksl/transform/SkSLTransform.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <memory>
+#include <vector>
+
+namespace SkSL {
+
+static bool is_dead_variable(const ProgramElement& element,
+ ProgramUsage* usage,
+ bool onlyPrivateGlobals) {
+ if (!element.is<GlobalVarDeclaration>()) {
+ return false;
+ }
+ const GlobalVarDeclaration& global = element.as<GlobalVarDeclaration>();
+ const VarDeclaration& varDecl = global.varDeclaration();
+ if (onlyPrivateGlobals && !skstd::starts_with(varDecl.var()->name(), '$')) {
+ return false;
+ }
+ if (!usage->isDead(*varDecl.var())) {
+ return false;
+ }
+ // This declaration is about to be eliminated by remove_if; update ProgramUsage accordingly.
+ usage->remove(&varDecl);
+ return true;
+}
+
+bool Transform::EliminateDeadGlobalVariables(const Context& context,
+ Module& module,
+ ProgramUsage* usage,
+ bool onlyPrivateGlobals) {
+ auto isDeadVariable = [&](const ProgramElement& element) {
+ return is_dead_variable(element, usage, onlyPrivateGlobals);
+ };
+
+ size_t numElements = module.fElements.size();
+ if (context.fConfig->fSettings.fRemoveDeadVariables) {
+ module.fElements.erase(std::remove_if(module.fElements.begin(),
+ module.fElements.end(),
+ [&](const std::unique_ptr<ProgramElement>& pe) {
+ return isDeadVariable(*pe);
+ }),
+ module.fElements.end());
+ }
+ return module.fElements.size() < numElements;
+}
+
+bool Transform::EliminateDeadGlobalVariables(Program& program) {
+ auto isDeadVariable = [&](const ProgramElement& element) {
+ return is_dead_variable(element, program.fUsage.get(), /*onlyPrivateGlobals=*/false);
+ };
+
+ size_t numOwnedElements = program.fOwnedElements.size();
+ size_t numSharedElements = program.fSharedElements.size();
+ if (program.fConfig->fSettings.fRemoveDeadVariables) {
+ program.fOwnedElements.erase(std::remove_if(program.fOwnedElements.begin(),
+ program.fOwnedElements.end(),
+ [&](const std::unique_ptr<ProgramElement>& pe) {
+ return isDeadVariable(*pe);
+ }),
+ program.fOwnedElements.end());
+ program.fSharedElements.erase(std::remove_if(program.fSharedElements.begin(),
+ program.fSharedElements.end(),
+ [&](const ProgramElement* pe) {
+ return isDeadVariable(*pe);
+ }),
+ program.fSharedElements.end());
+ }
+ return program.fOwnedElements.size() < numOwnedElements ||
+ program.fSharedElements.size() < numSharedElements;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/transform/SkSLEliminateDeadLocalVariables.cpp b/gfx/skia/skia/src/sksl/transform/SkSLEliminateDeadLocalVariables.cpp
new file mode 100644
index 0000000000..8329cc90c0
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/transform/SkSLEliminateDeadLocalVariables.cpp
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2021 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/core/SkTypes.h"
+#include "include/private/SkSLProgramElement.h"
+#include "include/private/SkSLStatement.h"
+#include "src/core/SkTHash.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/analysis/SkSLProgramUsage.h"
+#include "src/sksl/ir/SkSLBinaryExpression.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLExpressionStatement.h"
+#include "src/sksl/ir/SkSLFunctionDefinition.h"
+#include "src/sksl/ir/SkSLNop.h"
+#include "src/sksl/ir/SkSLProgram.h"
+#include "src/sksl/ir/SkSLVarDeclarations.h"
+#include "src/sksl/ir/SkSLVariable.h"
+#include "src/sksl/ir/SkSLVariableReference.h"
+#include "src/sksl/transform/SkSLProgramWriter.h"
+#include "src/sksl/transform/SkSLTransform.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+namespace SkSL {
+
+class Context;
+
+static bool eliminate_dead_local_variables(const Context& context,
+ SkSpan<std::unique_ptr<ProgramElement>> elements,
+ ProgramUsage* usage) {
+ class DeadLocalVariableEliminator : public ProgramWriter {
+ public:
+ DeadLocalVariableEliminator(const Context& context, ProgramUsage* usage)
+ : fContext(context)
+ , fUsage(usage) {}
+
+ using ProgramWriter::visitProgramElement;
+
+ bool visitExpressionPtr(std::unique_ptr<Expression>& expr) override {
+ if (expr->is<BinaryExpression>()) {
+ // Search for expressions of the form `deadVar = anyExpression`.
+ BinaryExpression& binary = expr->as<BinaryExpression>();
+ if (VariableReference* assignedVar = binary.isAssignmentIntoVariable()) {
+ if (fDeadVariables.contains(assignedVar->variable())) {
+ // Replace `deadVar = anyExpression` with `anyExpression`.
+ fUsage->remove(binary.left().get());
+ expr = std::move(binary.right());
+
+ // If `anyExpression` is now a lone ExpressionStatement, it's highly likely
+ // that we can eliminate it entirely. This flag will let us know to check.
+ fAssignmentWasEliminated = true;
+
+ // Re-process the newly cleaned-up expression. This lets us fully clean up
+ // gnarly assignments like `a = b = 123;` where both `a` and `b` are dead,
+ // or silly double-assignments like `a = a = 123;`.
+ return this->visitExpressionPtr(expr);
+ }
+ }
+ }
+ if (expr->is<VariableReference>()) {
+ SkASSERT(!fDeadVariables.contains(expr->as<VariableReference>().variable()));
+ }
+ return INHERITED::visitExpressionPtr(expr);
+ }
+
+ bool visitStatementPtr(std::unique_ptr<Statement>& stmt) override {
+ if (stmt->is<VarDeclaration>()) {
+ VarDeclaration& varDecl = stmt->as<VarDeclaration>();
+ const Variable* var = varDecl.var();
+ ProgramUsage::VariableCounts* counts = fUsage->fVariableCounts.find(var);
+ SkASSERT(counts);
+ SkASSERT(counts->fVarExists);
+ if (CanEliminate(var, *counts)) {
+ fDeadVariables.add(var);
+ if (var->initialValue()) {
+ // The variable has an initial-value expression, which might have side
+ // effects. ExpressionStatement::Make will preserve side effects, but
+ // replaces pure expressions with Nop.
+ fUsage->remove(stmt.get());
+ stmt = ExpressionStatement::Make(fContext, std::move(varDecl.value()));
+ fUsage->add(stmt.get());
+ } else {
+ // The variable has no initial-value and can be cleanly eliminated.
+ fUsage->remove(stmt.get());
+ stmt = Nop::Make();
+ }
+ fMadeChanges = true;
+
+ // Re-process the newly cleaned-up statement. This lets us fully clean up
+ // gnarly assignments like `a = b = 123;` where both `a` and `b` are dead,
+ // or silly double-assignments like `a = a = 123;`.
+ return this->visitStatementPtr(stmt);
+ }
+ }
+
+ bool result = INHERITED::visitStatementPtr(stmt);
+
+ // If we eliminated an assignment above, we may have left behind an inert
+ // ExpressionStatement.
+ if (fAssignmentWasEliminated) {
+ fAssignmentWasEliminated = false;
+ if (stmt->is<ExpressionStatement>()) {
+ ExpressionStatement& exprStmt = stmt->as<ExpressionStatement>();
+ if (!Analysis::HasSideEffects(*exprStmt.expression())) {
+ // The expression-statement was inert; eliminate it entirely.
+ fUsage->remove(&exprStmt);
+ stmt = Nop::Make();
+ }
+ }
+ }
+
+ return result;
+ }
+
+ static bool CanEliminate(const Variable* var, const ProgramUsage::VariableCounts& counts) {
+ return counts.fVarExists && !counts.fRead && var->storage() == VariableStorage::kLocal;
+ }
+
+ bool fMadeChanges = false;
+ const Context& fContext;
+ ProgramUsage* fUsage;
+ SkTHashSet<const Variable*> fDeadVariables;
+ bool fAssignmentWasEliminated = false;
+
+ using INHERITED = ProgramWriter;
+ };
+
+ DeadLocalVariableEliminator visitor{context, usage};
+
+ for (auto& [var, counts] : usage->fVariableCounts) {
+ if (DeadLocalVariableEliminator::CanEliminate(var, counts)) {
+ // This program contains at least one dead local variable.
+ // Scan the program for any dead local variables and eliminate them all.
+ for (std::unique_ptr<ProgramElement>& pe : elements) {
+ if (pe->is<FunctionDefinition>()) {
+ visitor.visitProgramElement(*pe);
+ }
+ }
+ break;
+ }
+ }
+
+ return visitor.fMadeChanges;
+}
+
+bool Transform::EliminateDeadLocalVariables(const Context& context,
+ Module& module,
+ ProgramUsage* usage) {
+ return eliminate_dead_local_variables(context, SkSpan(module.fElements), usage);
+}
+
+bool Transform::EliminateDeadLocalVariables(Program& program) {
+ return program.fConfig->fSettings.fRemoveDeadVariables
+ ? eliminate_dead_local_variables(*program.fContext,
+ SkSpan(program.fOwnedElements),
+ program.fUsage.get())
+ : false;
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/transform/SkSLEliminateEmptyStatements.cpp b/gfx/skia/skia/src/sksl/transform/SkSLEliminateEmptyStatements.cpp
new file mode 100644
index 0000000000..e36867bd5e
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/transform/SkSLEliminateEmptyStatements.cpp
@@ -0,0 +1,67 @@
+/*
+ * 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/SkSLProgramElement.h"
+#include "include/private/SkSLStatement.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/ir/SkSLBlock.h"
+#include "src/sksl/ir/SkSLFunctionDefinition.h"
+#include "src/sksl/transform/SkSLProgramWriter.h"
+#include "src/sksl/transform/SkSLTransform.h"
+
+#include <algorithm>
+#include <iterator>
+#include <memory>
+#include <vector>
+
+namespace SkSL {
+
+class Expression;
+
+static void eliminate_empty_statements(SkSpan<std::unique_ptr<ProgramElement>> elements) {
+ class EmptyStatementEliminator : public ProgramWriter {
+ public:
+ bool visitExpressionPtr(std::unique_ptr<Expression>& expr) override {
+ // We don't need to look inside expressions at all.
+ return false;
+ }
+
+ bool visitStatementPtr(std::unique_ptr<Statement>& stmt) override {
+ // Work from the innermost blocks to the outermost.
+ INHERITED::visitStatementPtr(stmt);
+
+ if (stmt->is<Block>()) {
+ StatementArray& children = stmt->as<Block>().children();
+ auto iter = std::remove_if(children.begin(), children.end(),
+ [](std::unique_ptr<Statement>& stmt) {
+ return stmt->isEmpty();
+ });
+ children.resize(std::distance(children.begin(), iter));
+ }
+
+ // We always check the entire program.
+ return false;
+ }
+
+ using INHERITED = ProgramWriter;
+ };
+
+ for (std::unique_ptr<ProgramElement>& pe : elements) {
+ if (pe->is<FunctionDefinition>()) {
+ EmptyStatementEliminator visitor;
+ visitor.visitStatementPtr(pe->as<FunctionDefinition>().body());
+ }
+ }
+}
+
+void Transform::EliminateEmptyStatements(Module& module) {
+ return eliminate_empty_statements(SkSpan(module.fElements));
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/transform/SkSLEliminateUnreachableCode.cpp b/gfx/skia/skia/src/sksl/transform/SkSLEliminateUnreachableCode.cpp
new file mode 100644
index 0000000000..23d1b39be0
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/transform/SkSLEliminateUnreachableCode.cpp
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2021 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/core/SkTypes.h"
+#include "include/private/SkSLDefines.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLProgramElement.h"
+#include "include/private/SkSLStatement.h"
+#include "include/private/base/SkTArray.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/analysis/SkSLProgramUsage.h"
+#include "src/sksl/ir/SkSLFunctionDefinition.h"
+#include "src/sksl/ir/SkSLIfStatement.h"
+#include "src/sksl/ir/SkSLNop.h"
+#include "src/sksl/ir/SkSLProgram.h"
+#include "src/sksl/ir/SkSLSwitchCase.h"
+#include "src/sksl/ir/SkSLSwitchStatement.h"
+#include "src/sksl/transform/SkSLProgramWriter.h"
+#include "src/sksl/transform/SkSLTransform.h"
+
+#include <memory>
+#include <vector>
+
+namespace SkSL {
+
+class Expression;
+
+static void eliminate_unreachable_code(SkSpan<std::unique_ptr<ProgramElement>> elements,
+ ProgramUsage* usage) {
+ class UnreachableCodeEliminator : public ProgramWriter {
+ public:
+ UnreachableCodeEliminator(ProgramUsage* usage) : fUsage(usage) {
+ fFoundFunctionExit.push_back(false);
+ fFoundBlockExit.push_back(false);
+ }
+
+ bool visitExpressionPtr(std::unique_ptr<Expression>& expr) override {
+ // We don't need to look inside expressions at all.
+ return false;
+ }
+
+ bool visitStatementPtr(std::unique_ptr<Statement>& stmt) override {
+ if (fFoundFunctionExit.back() || fFoundBlockExit.back()) {
+ // If we already found an exit in this section, anything beyond it is dead code.
+ if (!stmt->is<Nop>()) {
+ // Eliminate the dead statement by substituting a Nop.
+ fUsage->remove(stmt.get());
+ stmt = Nop::Make();
+ }
+ return false;
+ }
+
+ switch (stmt->kind()) {
+ case Statement::Kind::kReturn:
+ case Statement::Kind::kDiscard:
+ // We found a function exit on this path.
+ fFoundFunctionExit.back() = true;
+ break;
+
+ case Statement::Kind::kBreak:
+ // A `break` statement can either be breaking out of a loop or terminating an
+ // individual switch case. We treat both cases the same way: they only apply
+ // to the statements associated with the parent statement (i.e. enclosing loop
+ // block / preceding case label).
+ case Statement::Kind::kContinue:
+ fFoundBlockExit.back() = true;
+ break;
+
+ case Statement::Kind::kExpression:
+ case Statement::Kind::kNop:
+ case Statement::Kind::kVarDeclaration:
+ // These statements don't affect control flow.
+ break;
+
+ case Statement::Kind::kBlock:
+ // Blocks are on the straight-line path and don't affect control flow.
+ return INHERITED::visitStatementPtr(stmt);
+
+ case Statement::Kind::kDo: {
+ // Function-exits are allowed to propagate outside of a do-loop, because it
+ // always executes its body at least once.
+ fFoundBlockExit.push_back(false);
+ bool result = INHERITED::visitStatementPtr(stmt);
+ fFoundBlockExit.pop_back();
+ return result;
+ }
+ case Statement::Kind::kFor: {
+ // Function-exits are not allowed to propagate out, because a for-loop or while-
+ // loop could potentially run zero times.
+ fFoundFunctionExit.push_back(false);
+ fFoundBlockExit.push_back(false);
+ bool result = INHERITED::visitStatementPtr(stmt);
+ fFoundBlockExit.pop_back();
+ fFoundFunctionExit.pop_back();
+ return result;
+ }
+ case Statement::Kind::kIf: {
+ // This statement is conditional and encloses two inner sections of code.
+ // If both sides contain a function-exit or loop-exit, that exit is allowed to
+ // propagate out.
+ IfStatement& ifStmt = stmt->as<IfStatement>();
+
+ fFoundFunctionExit.push_back(false);
+ fFoundBlockExit.push_back(false);
+ bool result = (ifStmt.ifTrue() && this->visitStatementPtr(ifStmt.ifTrue()));
+ bool foundFunctionExitOnTrue = fFoundFunctionExit.back();
+ bool foundLoopExitOnTrue = fFoundBlockExit.back();
+ fFoundFunctionExit.pop_back();
+ fFoundBlockExit.pop_back();
+
+ fFoundFunctionExit.push_back(false);
+ fFoundBlockExit.push_back(false);
+ result |= (ifStmt.ifFalse() && this->visitStatementPtr(ifStmt.ifFalse()));
+ bool foundFunctionExitOnFalse = fFoundFunctionExit.back();
+ bool foundLoopExitOnFalse = fFoundBlockExit.back();
+ fFoundFunctionExit.pop_back();
+ fFoundBlockExit.pop_back();
+
+ fFoundFunctionExit.back() |= foundFunctionExitOnTrue &&
+ foundFunctionExitOnFalse;
+ fFoundBlockExit.back() |= foundLoopExitOnTrue &&
+ foundLoopExitOnFalse;
+ return result;
+ }
+ case Statement::Kind::kSwitch: {
+ // In switch statements we consider unreachable code on a per-case basis.
+ SwitchStatement& sw = stmt->as<SwitchStatement>();
+ bool result = false;
+
+ // Tracks whether we found at least one case that doesn't lead to a return
+ // statement (potentially via fallthrough).
+ bool foundCaseWithoutReturn = false;
+ bool hasDefault = false;
+ for (std::unique_ptr<Statement>& c : sw.cases()) {
+ // We eliminate unreachable code within the statements of the individual
+ // case. Breaks are not allowed to propagate outside the case statement
+ // itself. Function returns are allowed to propagate out only if all cases
+ // have a return AND one of the cases is default (so that we know at least
+ // one of the branches will be taken). This is similar to how we handle if
+ // statements above.
+ fFoundFunctionExit.push_back(false);
+ fFoundBlockExit.push_back(false);
+
+ SwitchCase& sc = c->as<SwitchCase>();
+ result |= this->visitStatementPtr(sc.statement());
+
+ // When considering whether a case has a return we can propagate, we
+ // assume the following:
+ // 1. The default case is always placed last in a switch statement and
+ // it is the last possible label reachable via fallthrough. Thus if
+ // it does not contain a return statement, then we don't propagate a
+ // function return.
+ // 2. In all other cases we prevent the return from propagating only if
+ // we encounter a break statement. If no return or break is found,
+ // we defer the decision to the fallthrough case. We won't propagate
+ // a return unless we eventually encounter a default label.
+ //
+ // See resources/sksl/shared/SwitchWithEarlyReturn.sksl for test cases that
+ // exercise this.
+ if (sc.isDefault()) {
+ foundCaseWithoutReturn |= !fFoundFunctionExit.back();
+ hasDefault = true;
+ } else {
+ // We can only be sure that a case does not lead to a return if it
+ // doesn't fallthrough.
+ foundCaseWithoutReturn |=
+ (!fFoundFunctionExit.back() && fFoundBlockExit.back());
+ }
+
+ fFoundFunctionExit.pop_back();
+ fFoundBlockExit.pop_back();
+ }
+
+ fFoundFunctionExit.back() |= !foundCaseWithoutReturn && hasDefault;
+ return result;
+ }
+ case Statement::Kind::kSwitchCase:
+ // We should never hit this case as switch cases are handled in the previous
+ // case.
+ SkUNREACHABLE;
+ }
+
+ return false;
+ }
+
+ ProgramUsage* fUsage;
+ SkSTArray<32, bool> fFoundFunctionExit;
+ SkSTArray<32, bool> fFoundBlockExit;
+
+ using INHERITED = ProgramWriter;
+ };
+
+ for (std::unique_ptr<ProgramElement>& pe : elements) {
+ if (pe->is<FunctionDefinition>()) {
+ UnreachableCodeEliminator visitor{usage};
+ visitor.visitStatementPtr(pe->as<FunctionDefinition>().body());
+ }
+ }
+}
+
+void Transform::EliminateUnreachableCode(Module& module, ProgramUsage* usage) {
+ return eliminate_unreachable_code(SkSpan(module.fElements), usage);
+}
+
+void Transform::EliminateUnreachableCode(Program& program) {
+ return eliminate_unreachable_code(SkSpan(program.fOwnedElements), program.fUsage.get());
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/transform/SkSLFindAndDeclareBuiltinFunctions.cpp b/gfx/skia/skia/src/sksl/transform/SkSLFindAndDeclareBuiltinFunctions.cpp
new file mode 100644
index 0000000000..cb937dec49
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/transform/SkSLFindAndDeclareBuiltinFunctions.cpp
@@ -0,0 +1,95 @@
+/*
+ * 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/SkTypes.h"
+#include "src/core/SkTHash.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLIntrinsicList.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/analysis/SkSLProgramUsage.h"
+#include "src/sksl/ir/SkSLFunctionDeclaration.h"
+#include "src/sksl/ir/SkSLFunctionDefinition.h"
+#include "src/sksl/ir/SkSLProgram.h"
+#include "src/sksl/transform/SkSLTransform.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <vector>
+
+namespace SkSL {
+
+class ProgramElement;
+
+void Transform::FindAndDeclareBuiltinFunctions(Program& program) {
+ ProgramUsage* usage = program.fUsage.get();
+ Context& context = *program.fContext;
+
+ std::vector<const FunctionDefinition*> addedBuiltins;
+ for (;;) {
+ // Find all the built-ins referenced by the program but not yet included in the code.
+ size_t numBuiltinsAtStart = addedBuiltins.size();
+ for (const auto& [fn, count] : usage->fCallCounts) {
+ if (!fn->isBuiltin() || count == 0) {
+ // Not a built-in; skip it.
+ continue;
+ }
+ if (fn->intrinsicKind() == k_dFdy_IntrinsicKind) {
+ // Programs that invoke the `dFdy` intrinsic will need the RTFlip input.
+ program.fInputs.fUseFlipRTUniform = !context.fConfig->fSettings.fForceNoRTFlip;
+ }
+ if (const FunctionDefinition* builtinDef = fn->definition()) {
+ // Make sure we only add a built-in function once. We rarely add more than a handful
+ // of builtin functions, so linear search here is good enough.
+ if (std::find(addedBuiltins.begin(), addedBuiltins.end(), builtinDef) ==
+ addedBuiltins.end()) {
+ addedBuiltins.push_back(builtinDef);
+ }
+ }
+ }
+
+ if (addedBuiltins.size() == numBuiltinsAtStart) {
+ // If we didn't reference any more built-in functions than before, we're done.
+ break;
+ }
+
+ // Sort the referenced builtin functions into a consistent order; otherwise our output will
+ // become non-deterministic. The exact order isn't particularly important; we sort backwards
+ // because we add elements to the shared-elements in reverse order at the end.
+ std::sort(addedBuiltins.begin() + numBuiltinsAtStart,
+ addedBuiltins.end(),
+ [](const FunctionDefinition* aDefinition, const FunctionDefinition* bDefinition) {
+ const FunctionDeclaration& a = aDefinition->declaration();
+ const FunctionDeclaration& b = bDefinition->declaration();
+ if (a.name() != b.name()) {
+ return a.name() > b.name();
+ }
+ return a.description() > b.description();
+ });
+
+ // Update the ProgramUsage to track all these newly discovered functions.
+ int usageCallCounts = usage->fCallCounts.count();
+
+ for (size_t index = numBuiltinsAtStart; index < addedBuiltins.size(); ++index) {
+ usage->add(*addedBuiltins[index]);
+ }
+
+ if (usage->fCallCounts.count() == usageCallCounts) {
+ // If we aren't making any more unique function calls than before, we're done.
+ break;
+ }
+ }
+
+ // Insert the new functions into the program's shared elements, right at the front.
+ // They are added in reverse so that the deepest dependencies are added to the top.
+ program.fSharedElements.insert(program.fSharedElements.begin(),
+ addedBuiltins.rbegin(), addedBuiltins.rend());
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/transform/SkSLFindAndDeclareBuiltinVariables.cpp b/gfx/skia/skia/src/sksl/transform/SkSLFindAndDeclareBuiltinVariables.cpp
new file mode 100644
index 0000000000..bbc68fa7af
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/transform/SkSLFindAndDeclareBuiltinVariables.cpp
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2021 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/core/SkTypes.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLLayout.h"
+#include "include/private/SkSLModifiers.h"
+#include "include/private/SkSLProgramElement.h"
+#include "include/private/SkSLSymbol.h"
+#include "src/core/SkTHash.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/SkSLUtil.h"
+#include "src/sksl/analysis/SkSLProgramUsage.h"
+#include "src/sksl/ir/SkSLFunctionDeclaration.h"
+#include "src/sksl/ir/SkSLFunctionDefinition.h"
+#include "src/sksl/ir/SkSLInterfaceBlock.h"
+#include "src/sksl/ir/SkSLProgram.h"
+#include "src/sksl/ir/SkSLSymbolTable.h"
+#include "src/sksl/ir/SkSLType.h"
+#include "src/sksl/ir/SkSLVarDeclarations.h"
+#include "src/sksl/ir/SkSLVariable.h"
+#include "src/sksl/transform/SkSLTransform.h"
+
+#include <algorithm>
+#include <memory>
+#include <string_view>
+#include <vector>
+
+namespace SkSL {
+namespace Transform {
+namespace {
+
+class BuiltinVariableScanner {
+public:
+ BuiltinVariableScanner(const Context& context, const SymbolTable& symbols)
+ : fContext(context)
+ , fSymbols(symbols) {}
+
+ void addDeclaringElement(const ProgramElement* decl) {
+ // Make sure we only add a built-in variable once. We only have a small handful of built-in
+ // variables to declare, so linear search here is good enough.
+ if (std::find(fNewElements.begin(), fNewElements.end(), decl) == fNewElements.end()) {
+ fNewElements.push_back(decl);
+ }
+ }
+
+ void addDeclaringElement(const Symbol* symbol) {
+ if (!symbol || !symbol->is<Variable>()) {
+ return;
+ }
+ const Variable& var = symbol->as<Variable>();
+ if (const GlobalVarDeclaration* decl = var.globalVarDeclaration()) {
+ this->addDeclaringElement(decl);
+ } else if (const InterfaceBlock* block = var.interfaceBlock()) {
+ this->addDeclaringElement(block);
+ } else {
+ // Double-check that this variable isn't associated with a global or an interface block.
+ // (Locals and parameters will come along naturally as part of the associated function.)
+ SkASSERTF(var.storage() != VariableStorage::kGlobal &&
+ var.storage() != VariableStorage::kInterfaceBlock,
+ "%.*s", (int)var.name().size(), var.name().data());
+ }
+ }
+
+ void addImplicitFragColorWrite(SkSpan<const std::unique_ptr<ProgramElement>> elements) {
+ for (const std::unique_ptr<ProgramElement>& pe : elements) {
+ if (!pe->is<FunctionDefinition>()) {
+ continue;
+ }
+ const FunctionDefinition& funcDef = pe->as<FunctionDefinition>();
+ if (funcDef.declaration().isMain()) {
+ if (funcDef.declaration().returnType().matches(*fContext.fTypes.fHalf4)) {
+ // We synthesize writes to sk_FragColor if main() returns a color, even if it's
+ // otherwise unreferenced.
+ this->addDeclaringElement(fSymbols.findBuiltinSymbol(Compiler::FRAGCOLOR_NAME));
+ }
+ // Now that main() has been found, we can stop scanning.
+ break;
+ }
+ }
+ }
+
+ static std::string_view GlobalVarBuiltinName(const ProgramElement& elem) {
+ return elem.as<GlobalVarDeclaration>().varDeclaration().var()->name();
+ }
+
+ static std::string_view InterfaceBlockName(const ProgramElement& elem) {
+ return elem.as<InterfaceBlock>().instanceName();
+ }
+
+ void sortNewElements() {
+ std::sort(fNewElements.begin(),
+ fNewElements.end(),
+ [](const ProgramElement* a, const ProgramElement* b) {
+ if (a->kind() != b->kind()) {
+ return a->kind() < b->kind();
+ }
+ switch (a->kind()) {
+ case ProgramElement::Kind::kGlobalVar:
+ SkASSERT(GlobalVarBuiltinName(*a) != GlobalVarBuiltinName(*b));
+ return GlobalVarBuiltinName(*a) < GlobalVarBuiltinName(*b);
+
+ case ProgramElement::Kind::kInterfaceBlock:
+ SkASSERT(InterfaceBlockName(*a) != InterfaceBlockName(*b));
+ return InterfaceBlockName(*a) < InterfaceBlockName(*b);
+
+ default:
+ SkUNREACHABLE;
+ }
+ });
+ }
+
+ const Context& fContext;
+ const SymbolTable& fSymbols;
+ std::vector<const ProgramElement*> fNewElements;
+};
+
+} // namespace
+
+void FindAndDeclareBuiltinVariables(Program& program) {
+ const Context& context = *program.fContext;
+ const SymbolTable& symbols = *program.fSymbols;
+ BuiltinVariableScanner scanner(context, symbols);
+
+ if (ProgramConfig::IsFragment(program.fConfig->fKind)) {
+ // Find main() in the program and check its return type.
+ // If it's half4, we treat that as an implicit write to sk_FragColor and add a reference.
+ scanner.addImplicitFragColorWrite(program.fOwnedElements);
+
+ // Vulkan requires certain builtin variables be present, even if they're unused. At one
+ // time, validation errors would result if sk_Clockwise was missing. Now, it's just (Adreno)
+ // driver bugs that drop or corrupt draws if they're missing.
+ scanner.addDeclaringElement(symbols.findBuiltinSymbol("sk_Clockwise"));
+ }
+
+ // Scan all the variables used by the program and declare any built-ins.
+ for (const auto& [var, counts] : program.fUsage->fVariableCounts) {
+ if (var->isBuiltin()) {
+ scanner.addDeclaringElement(var);
+
+ // Set the FlipRT program input if we find sk_FragCoord or sk_Clockwise.
+ switch (var->modifiers().fLayout.fBuiltin) {
+ case SK_FRAGCOORD_BUILTIN:
+ if (context.fCaps->fCanUseFragCoord) {
+ program.fInputs.fUseFlipRTUniform =
+ !context.fConfig->fSettings.fForceNoRTFlip;
+ }
+ break;
+
+ case SK_CLOCKWISE_BUILTIN:
+ program.fInputs.fUseFlipRTUniform = !context.fConfig->fSettings.fForceNoRTFlip;
+ break;
+ }
+ }
+ }
+
+ // Sort the referenced builtin functions into a consistent order; otherwise our output will
+ // become non-deterministic. The exact order isn't particularly important.
+ scanner.sortNewElements();
+
+ // Add all the newly-declared elements to the program, and update ProgramUsage to match.
+ program.fSharedElements.insert(program.fSharedElements.begin(),
+ scanner.fNewElements.begin(),
+ scanner.fNewElements.end());
+
+ for (const ProgramElement* element : scanner.fNewElements) {
+ program.fUsage->add(*element);
+ }
+}
+
+} // namespace Transform
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/transform/SkSLProgramWriter.h b/gfx/skia/skia/src/sksl/transform/SkSLProgramWriter.h
new file mode 100644
index 0000000000..6e5988aa92
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/transform/SkSLProgramWriter.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkSLProgramWriter_DEFINED
+#define SkSLProgramWriter_DEFINED
+
+#include "src/sksl/analysis/SkSLProgramVisitor.h"
+
+namespace SkSL {
+
+struct ProgramWriterTypes {
+ using Program = SkSL::Program;
+ using Expression = SkSL::Expression;
+ using Statement = SkSL::Statement;
+ using ProgramElement = SkSL::ProgramElement;
+ using UniquePtrExpression = std::unique_ptr<SkSL::Expression>;
+ using UniquePtrStatement = std::unique_ptr<SkSL::Statement>;
+};
+
+extern template class TProgramVisitor<ProgramWriterTypes>;
+
+class ProgramWriter : public TProgramVisitor<ProgramWriterTypes> {
+public:
+ // Subclass these methods if you want access to the unique_ptrs of IRNodes in a program.
+ // This will allow statements or expressions to be replaced during a visit.
+ bool visitExpressionPtr(std::unique_ptr<Expression>& e) override {
+ return this->visitExpression(*e);
+ }
+ bool visitStatementPtr(std::unique_ptr<Statement>& s) override {
+ return this->visitStatement(*s);
+ }
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/gfx/skia/skia/src/sksl/transform/SkSLRenamePrivateSymbols.cpp b/gfx/skia/skia/src/sksl/transform/SkSLRenamePrivateSymbols.cpp
new file mode 100644
index 0000000000..50bb88baf9
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/transform/SkSLRenamePrivateSymbols.cpp
@@ -0,0 +1,243 @@
+/*
+ * 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/SkTypes.h"
+#include "include/private/SkSLIRNode.h"
+#include "include/private/SkSLModifiers.h"
+#include "include/private/SkSLProgramElement.h"
+#include "include/private/SkSLStatement.h"
+#include "include/private/SkSLSymbol.h"
+#include "src/base/SkStringView.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/SkSLModifiersPool.h"
+#include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/ir/SkSLFunctionDeclaration.h"
+#include "src/sksl/ir/SkSLFunctionDefinition.h"
+#include "src/sksl/ir/SkSLFunctionPrototype.h"
+#include "src/sksl/ir/SkSLSymbolTable.h"
+#include "src/sksl/ir/SkSLVarDeclarations.h"
+#include "src/sksl/ir/SkSLVariable.h"
+#include "src/sksl/transform/SkSLProgramWriter.h"
+#include "src/sksl/transform/SkSLTransform.h"
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace SkSL {
+
+class ProgramUsage;
+enum class ProgramKind : int8_t;
+
+static void strip_export_flag(Context& context,
+ const FunctionDeclaration* funcDecl,
+ SymbolTable* symbols) {
+ // Remove `$export` from every overload of this function.
+ Symbol* mutableSym = symbols->findMutable(funcDecl->name());
+ while (mutableSym) {
+ FunctionDeclaration* mutableDecl = &mutableSym->as<FunctionDeclaration>();
+
+ Modifiers modifiers = mutableDecl->modifiers();
+ modifiers.fFlags &= ~Modifiers::kExport_Flag;
+ mutableDecl->setModifiers(context.fModifiersPool->add(modifiers));
+
+ mutableSym = mutableDecl->mutableNextOverload();
+ }
+}
+
+void Transform::RenamePrivateSymbols(Context& context,
+ Module& module,
+ ProgramUsage* usage,
+ ProgramKind kind) {
+ class SymbolRenamer : public ProgramWriter {
+ public:
+ SymbolRenamer(Context& context,
+ ProgramUsage* usage,
+ std::shared_ptr<SymbolTable> symbolBase,
+ ProgramKind kind)
+ : fContext(context)
+ , fUsage(usage)
+ , fSymbolTableStack({std::move(symbolBase)})
+ , fKind(kind) {}
+
+ static std::string FindShortNameForSymbol(const Symbol* sym,
+ const SymbolTable* symbolTable,
+ std::string namePrefix) {
+ static constexpr std::string_view kLetters[] = {
+ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
+ "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
+ "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
+ "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"};
+
+ // Try any single-letter option.
+ for (std::string_view letter : kLetters) {
+ std::string name = namePrefix + std::string(letter);
+ if (symbolTable->find(name) == nullptr) {
+ return name;
+ }
+ }
+
+ // Try every two-letter option.
+ for (std::string_view letterA : kLetters) {
+ for (std::string_view letterB : kLetters) {
+ std::string name = namePrefix + std::string(letterA) + std::string(letterB);
+ if (symbolTable->find(name) == nullptr) {
+ return name;
+ }
+ }
+ }
+
+ // We struck out. Somehow, all 2700 two-letter names have been claimed.
+ SkDEBUGFAILF("Unable to find unique name for '%s'", std::string(sym->name()).c_str());
+ return std::string(sym->name());
+ }
+
+ void minifyVariableName(const Variable* var) {
+ // Some variables are associated with anonymous parameters--these don't have names and
+ // aren't present in the symbol table. Their names are already empty so there's no way
+ // to shrink them further.
+ if (var->name().empty()) {
+ return;
+ }
+
+ // Ensure that this variable is properly set up in the symbol table.
+ SymbolTable* symbols = fSymbolTableStack.back().get();
+ Symbol* mutableSym = symbols->findMutable(var->name());
+ SkASSERTF(mutableSym != nullptr,
+ "symbol table missing '%.*s'", (int)var->name().size(), var->name().data());
+ SkASSERTF(mutableSym == var,
+ "wrong symbol found for '%.*s'", (int)var->name().size(), var->name().data());
+
+ // Look for a new name for this symbol.
+ // Note: we always rename _every_ variable, even ones with single-letter names. This is
+ // a safeguard: if we claimed a name like `i`, and then the program itself contained an
+ // `i` later on, in a nested SymbolTable, the two names would clash. By always renaming
+ // everything, we can ignore that problem.
+ std::string shortName = FindShortNameForSymbol(var, symbols, "");
+ SkASSERT(symbols->findMutable(shortName) == nullptr);
+
+ // Update the symbol's name.
+ const std::string* ownedName = symbols->takeOwnershipOfString(std::move(shortName));
+ symbols->renameSymbol(mutableSym, *ownedName);
+ }
+
+ void minifyFunctionName(const FunctionDeclaration* funcDecl) {
+ // Look for a new name for this function.
+ std::string namePrefix = ProgramConfig::IsRuntimeEffect(fKind) ? "" : "$";
+ SymbolTable* symbols = fSymbolTableStack.back().get();
+ std::string shortName = FindShortNameForSymbol(funcDecl, symbols,
+ std::move(namePrefix));
+ SkASSERT(symbols->findMutable(shortName) == nullptr);
+
+ if (shortName.size() < funcDecl->name().size()) {
+ // Update the function's name. (If the function has overloads, this will rename all
+ // of them at once.)
+ Symbol* mutableSym = symbols->findMutable(funcDecl->name());
+ const std::string* ownedName = symbols->takeOwnershipOfString(std::move(shortName));
+ symbols->renameSymbol(mutableSym, *ownedName);
+ }
+ }
+
+ bool functionNameCanBeMinifiedSafely(const FunctionDeclaration& funcDecl) const {
+ if (ProgramConfig::IsRuntimeEffect(fKind)) {
+ // The only externally-accessible function in a runtime effect is main().
+ return !funcDecl.isMain();
+ } else {
+ // We will only minify $private_functions, and only ones not marked as $export.
+ return skstd::starts_with(funcDecl.name(), '$') &&
+ !(funcDecl.modifiers().fFlags & Modifiers::kExport_Flag);
+ }
+ }
+
+ void minifyFunction(FunctionDefinition& def) {
+ // If the function is private, minify its name.
+ const FunctionDeclaration* funcDecl = &def.declaration();
+ if (this->functionNameCanBeMinifiedSafely(*funcDecl)) {
+ this->minifyFunctionName(funcDecl);
+ }
+
+ // Minify the names of each function parameter.
+ Analysis::SymbolTableStackBuilder symbolTableStackBuilder(def.body().get(),
+ &fSymbolTableStack);
+ for (Variable* param : funcDecl->parameters()) {
+ this->minifyVariableName(param);
+ }
+ }
+
+ void minifyPrototype(FunctionPrototype& proto) {
+ const FunctionDeclaration* funcDecl = &proto.declaration();
+ if (funcDecl->definition()) {
+ // This function is defined somewhere; this isn't just a loose prototype.
+ return;
+ }
+
+ // Eliminate the names of each function parameter.
+ // The parameter names aren't in the symbol table's name lookup map at all.
+ // All we need to do is blank out their names.
+ for (Variable* param : funcDecl->parameters()) {
+ param->setName("");
+ }
+ }
+
+ bool visitProgramElement(ProgramElement& elem) override {
+ switch (elem.kind()) {
+ case ProgramElement::Kind::kFunction:
+ this->minifyFunction(elem.as<FunctionDefinition>());
+ return INHERITED::visitProgramElement(elem);
+
+ case ProgramElement::Kind::kFunctionPrototype:
+ this->minifyPrototype(elem.as<FunctionPrototype>());
+ return INHERITED::visitProgramElement(elem);
+
+ default:
+ return false;
+ }
+ }
+
+ bool visitStatementPtr(std::unique_ptr<Statement>& stmt) override {
+ Analysis::SymbolTableStackBuilder symbolTableStackBuilder(stmt.get(),
+ &fSymbolTableStack);
+ if (stmt->is<VarDeclaration>()) {
+ // Minify the variable's name.
+ VarDeclaration& decl = stmt->as<VarDeclaration>();
+ this->minifyVariableName(decl.var());
+ }
+
+ return INHERITED::visitStatementPtr(stmt);
+ }
+
+ Context& fContext;
+ ProgramUsage* fUsage;
+ std::vector<std::shared_ptr<SymbolTable>> fSymbolTableStack;
+ ProgramKind fKind;
+ using INHERITED = ProgramWriter;
+ };
+
+ // Rename local variables and private functions.
+ SymbolRenamer renamer{context, usage, module.fSymbols, kind};
+ for (std::unique_ptr<ProgramElement>& pe : module.fElements) {
+ renamer.visitProgramElement(*pe);
+ }
+
+ // Strip off modifier `$export` from every function. (Only the minifier checks this flag, so we
+ // can remove it without affecting the meaning of the code.)
+ for (std::unique_ptr<ProgramElement>& pe : module.fElements) {
+ if (pe->is<FunctionDefinition>()) {
+ const FunctionDeclaration* funcDecl = &pe->as<FunctionDefinition>().declaration();
+ if (funcDecl->modifiers().fFlags & Modifiers::kExport_Flag) {
+ strip_export_flag(context, funcDecl, module.fSymbols.get());
+ }
+ }
+ }
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/transform/SkSLReplaceConstVarsWithLiterals.cpp b/gfx/skia/skia/src/sksl/transform/SkSLReplaceConstVarsWithLiterals.cpp
new file mode 100644
index 0000000000..e484104a33
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/transform/SkSLReplaceConstVarsWithLiterals.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2021 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/SkTypes.h"
+#include "include/private/SkSLModifiers.h"
+#include "include/private/SkSLProgramElement.h"
+#include "src/core/SkTHash.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLConstantFolder.h"
+#include "src/sksl/analysis/SkSLProgramUsage.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLFunctionDefinition.h"
+#include "src/sksl/ir/SkSLVariable.h"
+#include "src/sksl/ir/SkSLVariableReference.h"
+#include "src/sksl/transform/SkSLProgramWriter.h"
+#include "src/sksl/transform/SkSLTransform.h"
+
+#include <cstddef>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <vector>
+
+namespace SkSL {
+
+void Transform::ReplaceConstVarsWithLiterals(Module& module, ProgramUsage* usage) {
+ class ConstVarReplacer : public ProgramWriter {
+ public:
+ ConstVarReplacer(ProgramUsage* usage) : fUsage(usage) {}
+
+ using ProgramWriter::visitProgramElement;
+
+ bool visitExpressionPtr(std::unique_ptr<Expression>& expr) override {
+ // If this is a variable...
+ if (expr->is<VariableReference>()) {
+ VariableReference& var = expr->as<VariableReference>();
+ // ... and it's a candidate for size reduction...
+ if (fCandidates.contains(var.variable())) {
+ // ... get its constant value...
+ if (const Expression* value =
+ ConstantFolder::GetConstantValueOrNullForVariable(var)) {
+ // ... and replace it with that value.
+ fUsage->remove(expr.get());
+ expr = value->clone();
+ fUsage->add(expr.get());
+ return false;
+ }
+ }
+ }
+ return INHERITED::visitExpressionPtr(expr);
+ }
+
+ ProgramUsage* fUsage;
+ SkTHashSet<const Variable*> fCandidates;
+
+ using INHERITED = ProgramWriter;
+ };
+
+ ConstVarReplacer visitor{usage};
+
+ for (const auto& [var, count] : usage->fVariableCounts) {
+ // We can only replace const variables that still exist, and that have initial values.
+ if (!count.fVarExists || count.fWrite != 1) {
+ continue;
+ }
+ if (!(var->modifiers().fFlags & Modifiers::kConst_Flag)) {
+ continue;
+ }
+ if (!var->initialValue()) {
+ continue;
+ }
+ // The current size is:
+ // strlen("const type varname=initialvalue;`") + count*strlen("varname").
+ size_t initialvalueSize = ConstantFolder::GetConstantValueForVariable(*var->initialValue())
+ ->description()
+ .size();
+ size_t totalOldSize = var->description().size() + // const type varname
+ 1 + // =
+ initialvalueSize + // initialvalue
+ 1 + // ;
+ count.fRead * var->name().size(); // count * varname
+ // If we replace varname with initialvalue everywhere, the new size would be:
+ // count*strlen("initialvalue")
+ size_t totalNewSize = count.fRead * initialvalueSize; // count * initialvalue
+
+ if (totalNewSize <= totalOldSize) {
+ visitor.fCandidates.add(var);
+ }
+ }
+
+ if (!visitor.fCandidates.empty()) {
+ for (std::unique_ptr<ProgramElement>& pe : module.fElements) {
+ if (pe->is<FunctionDefinition>()) {
+ visitor.visitProgramElement(*pe);
+ }
+ }
+ }
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/transform/SkSLRewriteIndexedSwizzle.cpp b/gfx/skia/skia/src/sksl/transform/SkSLRewriteIndexedSwizzle.cpp
new file mode 100644
index 0000000000..21c68d97b1
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/transform/SkSLRewriteIndexedSwizzle.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/private/SkSLDefines.h"
+#include "src/sksl/SkSLBuiltinTypes.h"
+#include "src/sksl/SkSLContext.h"
+#include "src/sksl/ir/SkSLConstructorCompound.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLIndexExpression.h"
+#include "src/sksl/ir/SkSLLiteral.h"
+#include "src/sksl/ir/SkSLSwizzle.h"
+#include "src/sksl/ir/SkSLType.h"
+#include "src/sksl/transform/SkSLTransform.h"
+
+#include <cstdint>
+#include <memory>
+#include <utility>
+
+namespace SkSL {
+
+std::unique_ptr<Expression> Transform::RewriteIndexedSwizzle(const Context& context,
+ const IndexExpression& indexExpr) {
+ // The index expression _must_ have a swizzle base for this transformation to be valid.
+ if (!indexExpr.base()->is<Swizzle>()) {
+ return nullptr;
+ }
+ const Swizzle& swizzle = indexExpr.base()->as<Swizzle>();
+
+ // Convert the swizzle components to a literal array.
+ ExpressionArray vecArray;
+ vecArray.reserve(swizzle.components().size());
+ for (int8_t comp : swizzle.components()) {
+ vecArray.push_back(Literal::Make(indexExpr.fPosition, comp, context.fTypes.fInt.get()));
+ }
+
+ // Make a compound constructor with the literal array.
+ const Type& vecType = context.fTypes.fInt->toCompound(context, vecArray.size(), /*rows=*/1);
+ std::unique_ptr<Expression> vec =
+ ConstructorCompound::Make(context, indexExpr.fPosition, vecType, std::move(vecArray));
+
+ // Create a rewritten inner-expression corresponding to `vec(1,2,3)[originalIndex]`.
+ std::unique_ptr<Expression> innerExpr = IndexExpression::Make(
+ context, indexExpr.fPosition, std::move(vec), indexExpr.index()->clone());
+
+ // Return a rewritten outer-expression corresponding to `base[vec(1,2,3)[originalIndex]]`.
+ return IndexExpression::Make(
+ context, indexExpr.fPosition, swizzle.base()->clone(), std::move(innerExpr));
+}
+
+} // namespace SkSL
diff --git a/gfx/skia/skia/src/sksl/transform/SkSLTransform.h b/gfx/skia/skia/src/sksl/transform/SkSLTransform.h
new file mode 100644
index 0000000000..e051ec8086
--- /dev/null
+++ b/gfx/skia/skia/src/sksl/transform/SkSLTransform.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2021 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_TRANSFORM
+#define SKSL_TRANSFORM
+
+#include "include/core/SkSpan.h"
+#include <memory>
+#include <vector>
+
+namespace SkSL {
+
+class Context;
+class Expression;
+class IndexExpression;
+struct Modifiers;
+struct Module;
+struct Program;
+class ProgramElement;
+class ProgramUsage;
+class Statement;
+class Variable;
+enum class ProgramKind : int8_t;
+
+namespace Transform {
+
+/**
+ * Checks to see if it would be safe to add `const` to the modifiers of a variable. If so, returns
+ * the modifiers with `const` applied; if not, returns the existing modifiers as-is. Adding `const`
+ * allows the inliner to fold away more values and generate tighter code.
+ */
+const Modifiers* AddConstToVarModifiers(const Context& context,
+ const Variable& var,
+ const Expression* initialValue,
+ const ProgramUsage* usage);
+
+/**
+ * Rewrites indexed swizzles of the form `myVec.zyx[i]` by replacing the swizzle with a lookup into
+ * a constant vector. e.g., the above expression would be rewritten as `myVec[vec3(2, 1, 0)[i]]`.
+ * This roughly matches glslang's handling of the code.
+ */
+std::unique_ptr<Expression> RewriteIndexedSwizzle(const Context& context,
+ const IndexExpression& swizzle);
+
+/**
+ * Copies built-in functions from modules into the program. Relies on ProgramUsage to determine
+ * which functions are necessary.
+ */
+void FindAndDeclareBuiltinFunctions(Program& program);
+
+/**
+ * Scans the finished program for built-in variables like `sk_FragColor` and adds them to the
+ * program's shared elements.
+ */
+void FindAndDeclareBuiltinVariables(Program& program);
+
+/**
+ * Eliminates statements in a block which cannot be reached; for example, a statement
+ * immediately after a `return` or `continue` can safely be eliminated.
+ */
+void EliminateUnreachableCode(Module& module, ProgramUsage* usage);
+void EliminateUnreachableCode(Program& program);
+
+/**
+ * Eliminates empty statements in a module (Nops, or blocks holding only Nops). Not implemented for
+ * Programs because Nops are harmless, but they waste space in long-lived module IR.
+ */
+void EliminateEmptyStatements(Module& module);
+
+/**
+ * Eliminates functions in a program which are never called. Returns true if any changes were made.
+ */
+bool EliminateDeadFunctions(const Context& context, Module& module, ProgramUsage* usage);
+bool EliminateDeadFunctions(Program& program);
+
+/**
+ * Eliminates variables in a program which are never read or written (past their initializer).
+ * Preserves side effects from initializers, if any. Returns true if any changes were made.
+ */
+bool EliminateDeadLocalVariables(const Context& context,
+ Module& module,
+ ProgramUsage* usage);
+bool EliminateDeadLocalVariables(Program& program);
+bool EliminateDeadGlobalVariables(const Context& context,
+ Module& module,
+ ProgramUsage* usage,
+ bool onlyPrivateGlobals);
+bool EliminateDeadGlobalVariables(Program& program);
+
+/** Renames private functions and function-local variables to minimize code size. */
+void RenamePrivateSymbols(Context& context, Module& module, ProgramUsage* usage, ProgramKind kind);
+
+/** Replaces constant variables in a program with their equivalent values. */
+void ReplaceConstVarsWithLiterals(Module& module, ProgramUsage* usage);
+
+} // namespace Transform
+} // namespace SkSL
+
+#endif