[sksl][compute] Enable SPIR-V compute test outputs

* The compute tests now compile to SPIR-V, with a .asm.comp extension.
  Most of the output contains compilation errors which will be addressed
  in follow up CLs.

* Support the GLCompute and LocalSize entry point decorations. The
  LocalSize is hardcoded to (16, 16, 1) until SkSL supports local size
  declarations.

Bug: b/262428625

Change-Id: I08795badd75788e0724f03d77e1da537d07ceb51
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/729097
Reviewed-by: John Stiles <johnstiles@google.com>
Commit-Queue: Arman Uguray <armansito@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 08e6231..b4aadc5 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -834,9 +834,11 @@
         sources += [ src ]
 
         foreach(outExtension, invoker.outExtensions) {
-          # SPIR-V uses separate extensions for .vert shaders.
+          # SPIR-V uses separate extensions for .vert and .compute shaders.
           if (ext == "vert" && outExtension == ".asm.frag") {
             outExtension = ".asm.vert"
+          } else if (ext == "compute" && outExtension == ".asm.frag") {
+            outExtension = ".asm.comp"
           }
           outputs +=
               [ target_out_dir + "/" +
diff --git a/bazel/exporter/gni_exporter.go b/bazel/exporter/gni_exporter.go
index 7cd256e..4d0eae8 100644
--- a/bazel/exporter/gni_exporter.go
+++ b/bazel/exporter/gni_exporter.go
@@ -97,7 +97,7 @@
 sksl_wgsl_tests_sources = sksl_blend_tests + sksl_shared_tests + sksl_wgsl_tests
 
 sksl_spirv_tests_sources =
-    sksl_blend_tests + sksl_shared_tests + sksl_spirv_tests
+    sksl_blend_tests + sksl_compute_tests + sksl_shared_tests + sksl_spirv_tests
 
 sksl_skrp_tests_sources = sksl_folding_tests + sksl_rte_tests + sksl_shared_tests
 
diff --git a/gn/compile_sksl_tests.py b/gn/compile_sksl_tests.py
index 8d296dd..dd95b69 100755
--- a/gn/compile_sksl_tests.py
+++ b/gn/compile_sksl_tests.py
@@ -41,6 +41,8 @@
     os.remove(worklist.name)
 
 def extensionForSpirvAsm(ext):
+    if (ext == '.compute'):
+        return '.comp'
     return ext if (ext == '.frag' or ext == '.vert') else '.frag'
 
 if settings != "--settings" and settings != "--nosettings":
diff --git a/gn/sksl_tests.gni b/gn/sksl_tests.gni
index ba0a59b..4327283 100644
--- a/gn/sksl_tests.gni
+++ b/gn/sksl_tests.gni
@@ -944,7 +944,7 @@
 sksl_wgsl_tests_sources = sksl_blend_tests + sksl_shared_tests + sksl_wgsl_tests
 
 sksl_spirv_tests_sources =
-    sksl_blend_tests + sksl_shared_tests + sksl_spirv_tests
+    sksl_blend_tests + sksl_compute_tests + sksl_shared_tests + sksl_spirv_tests
 
 sksl_skrp_tests_sources =
     sksl_folding_tests + sksl_rte_tests + sksl_shared_tests
diff --git a/resources/sksl/BUILD.bazel b/resources/sksl/BUILD.bazel
index da04d5e..0bc798e 100644
--- a/resources/sksl/BUILD.bazel
+++ b/resources/sksl/BUILD.bazel
@@ -98,6 +98,7 @@
     name = "sksl_spirv_tests_sources",
     srcs = [
         ":sksl_blend_tests",
+        ":sksl_compute_tests",
         ":sksl_shared_tests",
         ":sksl_spirv_tests",
     ],
diff --git a/src/sksl/codegen/SkSLSPIRVCodeGenerator.cpp b/src/sksl/codegen/SkSLSPIRVCodeGenerator.cpp
index ae6099a..ac58c9e 100644
--- a/src/sksl/codegen/SkSLSPIRVCodeGenerator.cpp
+++ b/src/sksl/codegen/SkSLSPIRVCodeGenerator.cpp
@@ -1142,6 +1142,10 @@
                                                 SpvImageFormatUnknown},
                                           fConstantBuffer);
         }
+        case Type::TypeKind::kAtomic:
+            // TODO(b/262428625): Implement atomics
+            fContext.fErrors->error(type->fPosition, "atomics are not yet supported");
+            return NA;
         default: {
             SkDEBUGFAILF("invalid type: %s", type->description().c_str());
             return NA;
@@ -4360,6 +4364,8 @@
         this->writeWord(SpvExecutionModelVertex, out);
     } else if (ProgramConfig::IsFragment(program.fConfig->fKind)) {
         this->writeWord(SpvExecutionModelFragment, out);
+    } else if (ProgramConfig::IsCompute(program.fConfig->fKind)) {
+        this->writeWord(SpvExecutionModelGLCompute, out);
     } else {
         SK_ABORT("cannot write this kind of program to SPIR-V\n");
     }
@@ -4374,6 +4380,12 @@
                                fFunctionMap[main],
                                SpvExecutionModeOriginUpperLeft,
                                out);
+    } else if (ProgramConfig::IsCompute(program.fConfig->fKind)) {
+        this->writeInstruction(SpvOpExecutionMode,
+                               fFunctionMap[main],
+                               SpvExecutionModeLocalSize,
+                               16, 16, 1, // TODO(b/240615224): Set these based on the SkSL source.
+                               out);
     }
     for (const ProgramElement* e : program.elements()) {
         if (e->is<Extension>()) {
diff --git a/tests/sksl/compute/ArrayAdd.asm.comp b/tests/sksl/compute/ArrayAdd.asm.comp
new file mode 100644
index 0000000..eae11ca
--- /dev/null
+++ b/tests/sksl/compute/ArrayAdd.asm.comp
@@ -0,0 +1,61 @@
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %sk_GlobalInvocationID
+OpExecutionMode %main LocalSize 16 16 1
+OpName %inputBlock "inputBlock"
+OpMemberName %inputBlock 0 "offset"
+OpMemberName %inputBlock 1 "src"
+OpName %outputBlock "outputBlock"
+OpMemberName %outputBlock 0 "dest"
+OpName %sk_GlobalInvocationID "sk_GlobalInvocationID"
+OpName %main "main"
+OpDecorate %_runtimearr_int ArrayStride 16
+OpMemberDecorate %inputBlock 0 Offset 0
+OpMemberDecorate %inputBlock 1 Offset 16
+OpDecorate %inputBlock BufferBlock
+OpDecorate %3 Binding 0
+OpDecorate %3 DescriptorSet 0
+OpMemberDecorate %outputBlock 0 Offset 0
+OpDecorate %outputBlock BufferBlock
+OpDecorate %9 Binding 1
+OpDecorate %9 DescriptorSet 0
+OpDecorate %sk_GlobalInvocationID BuiltIn GlobalInvocationId
+%uint = OpTypeInt 32 0
+%int = OpTypeInt 32 1
+%_runtimearr_int = OpTypeRuntimeArray %int
+%inputBlock = OpTypeStruct %uint %_runtimearr_int
+%_ptr_Uniform_inputBlock = OpTypePointer Uniform %inputBlock
+%3 = OpVariable %_ptr_Uniform_inputBlock Uniform
+%outputBlock = OpTypeStruct %_runtimearr_int
+%_ptr_Uniform_outputBlock = OpTypePointer Uniform %outputBlock
+%9 = OpVariable %_ptr_Uniform_outputBlock Uniform
+%v3uint = OpTypeVector %uint 3
+%_ptr_Input_v3uint = OpTypePointer Input %v3uint
+%sk_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%void = OpTypeVoid
+%16 = OpTypeFunction %void
+%int_1 = OpConstant %int 1
+%_ptr_Uniform_int = OpTypePointer Uniform %int
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%main = OpFunction %void None %16
+%17 = OpLabel
+%19 = OpLoad %v3uint %sk_GlobalInvocationID
+%20 = OpCompositeExtract %uint %19 0
+%21 = OpAccessChain %_ptr_Uniform_int %3 %int_1 %20
+%23 = OpLoad %int %21
+%24 = OpLoad %v3uint %sk_GlobalInvocationID
+%25 = OpCompositeExtract %uint %24 0
+%27 = OpAccessChain %_ptr_Uniform_uint %3 %int_0
+%29 = OpLoad %uint %27
+%30 = OpIAdd %uint %25 %29
+%31 = OpAccessChain %_ptr_Uniform_int %3 %int_1 %30
+%32 = OpLoad %int %31
+%33 = OpIAdd %int %23 %32
+%34 = OpLoad %v3uint %sk_GlobalInvocationID
+%35 = OpCompositeExtract %uint %34 0
+%36 = OpAccessChain %_ptr_Uniform_int %9 %int_0 %35
+OpStore %36 %33
+OpReturn
+OpFunctionEnd
diff --git a/tests/sksl/compute/AtomicDeclarations.asm.comp b/tests/sksl/compute/AtomicDeclarations.asm.comp
new file mode 100644
index 0000000..a01355a
--- /dev/null
+++ b/tests/sksl/compute/AtomicDeclarations.asm.comp
@@ -0,0 +1,18 @@
+### Compilation failed:
+
+error: atomics are not yet supported
+error: atomics are not yet supported
+error: atomics are not yet supported
+error: atomics are not yet supported
+error: atomics are not yet supported
+error: atomics are not yet supported
+error: 25: unsupported intrinsic 'uint atomicAdd(atomicUint a, uint value)'
+    atomicAdd(wgAtomicArray[1], atomicLoad(wgAtomic));
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+error: 26: unsupported intrinsic 'uint atomicAdd(atomicUint a, uint value)'
+    atomicAdd(wgAtomicArray[0], atomicLoad(wgAtomicArray[1]));
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+error: 27: unsupported intrinsic 'uint atomicAdd(atomicUint a, uint value)'
+    atomicAdd(wgNestedStructWithAtomicMember.nestedStructWithAtomicMember.structMemberAtomic, 1);
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+9 errors
diff --git a/tests/sksl/compute/AtomicOperations.asm.comp b/tests/sksl/compute/AtomicOperations.asm.comp
new file mode 100644
index 0000000..7e53b5c
--- /dev/null
+++ b/tests/sksl/compute/AtomicOperations.asm.comp
@@ -0,0 +1,20 @@
+### Compilation failed:
+
+error: atomics are not yet supported
+error: atomics are not yet supported
+error: 10: unsupported intrinsic 'void atomicStore(atomicUint a, uint value)'
+        atomicStore(localCounter, 0);
+        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+error: 14: unsupported intrinsic 'void workgroupBarrier()'
+    workgroupBarrier();
+    ^^^^^^^^^^^^^^^^^^
+error: 17: unsupported intrinsic 'uint atomicAdd(atomicUint a, uint value)'
+    atomicAdd(localCounter, 1);
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^
+error: 22: unsupported intrinsic 'void workgroupBarrier()'
+    workgroupBarrier();
+    ^^^^^^^^^^^^^^^^^^
+error: 26: unsupported intrinsic 'uint atomicAdd(atomicUint a, uint value)'
+        atomicAdd(globalCounter, atomicLoad(localCounter));
+        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+7 errors
diff --git a/tests/sksl/compute/AtomicOperationsOverArrayAndStruct.asm.comp b/tests/sksl/compute/AtomicOperationsOverArrayAndStruct.asm.comp
new file mode 100644
index 0000000..0c4bf58
--- /dev/null
+++ b/tests/sksl/compute/AtomicOperationsOverArrayAndStruct.asm.comp
@@ -0,0 +1,27 @@
+### Compilation failed:
+
+error: atomics are not yet supported
+error: atomics are not yet supported
+error: atomics are not yet supported
+error: 16: unsupported intrinsic 'void atomicStore(atomicUint a, uint value)'
+        atomicStore(localCounts[0], 0);
+        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+error: 17: unsupported intrinsic 'void atomicStore(atomicUint a, uint value)'
+        atomicStore(localCounts[1], 0);
+        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+error: 21: unsupported intrinsic 'void workgroupBarrier()'
+    workgroupBarrier();
+    ^^^^^^^^^^^^^^^^^^
+error: 25: unsupported intrinsic 'uint atomicAdd(atomicUint a, uint value)'
+    atomicAdd(localCounts[idx], 1);
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+error: 30: unsupported intrinsic 'void workgroupBarrier()'
+    workgroupBarrier();
+    ^^^^^^^^^^^^^^^^^^
+error: 34: unsupported intrinsic 'uint atomicAdd(atomicUint a, uint value)'
+        atomicAdd(globalCounts.firstHalfCount, atomicLoad(localCounts[0]));
+        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+error: 35: unsupported intrinsic 'uint atomicAdd(atomicUint a, uint value)'
+        atomicAdd(globalCounts.secondHalfCount, atomicLoad(localCounts[1]));
+        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+10 errors
diff --git a/tests/sksl/compute/Barrier.asm.comp b/tests/sksl/compute/Barrier.asm.comp
new file mode 100644
index 0000000..393b5cd
--- /dev/null
+++ b/tests/sksl/compute/Barrier.asm.comp
@@ -0,0 +1,9 @@
+### Compilation failed:
+
+error: 2: unsupported intrinsic 'void workgroupBarrier()'
+    workgroupBarrier();
+    ^^^^^^^^^^^^^^^^^^
+error: 3: unsupported intrinsic 'void storageBarrier()'
+    storageBarrier();
+    ^^^^^^^^^^^^^^^^
+2 errors
diff --git a/tests/sksl/compute/BuiltinStageInputs.asm.comp b/tests/sksl/compute/BuiltinStageInputs.asm.comp
new file mode 100644
index 0000000..06187d4
--- /dev/null
+++ b/tests/sksl/compute/BuiltinStageInputs.asm.comp
@@ -0,0 +1,66 @@
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %sk_GlobalInvocationID %sk_LocalInvocationID %sk_LocalInvocationIndex %sk_NumWorkgroups %sk_WorkgroupID
+OpExecutionMode %main LocalSize 16 16 1
+OpName %outputs "outputs"
+OpMemberName %outputs 0 "outputBuffer"
+OpName %sk_GlobalInvocationID "sk_GlobalInvocationID"
+OpName %sk_LocalInvocationID "sk_LocalInvocationID"
+OpName %sk_LocalInvocationIndex "sk_LocalInvocationIndex"
+OpName %sk_NumWorkgroups "sk_NumWorkgroups"
+OpName %sk_WorkgroupID "sk_WorkgroupID"
+OpName %helper_I "helper_I"
+OpName %main "main"
+OpDecorate %_runtimearr_uint ArrayStride 16
+OpMemberDecorate %outputs 0 Offset 0
+OpDecorate %outputs BufferBlock
+OpDecorate %4 Binding 0
+OpDecorate %4 DescriptorSet 0
+OpDecorate %sk_GlobalInvocationID BuiltIn GlobalInvocationId
+OpDecorate %sk_LocalInvocationID BuiltIn LocalInvocationId
+OpDecorate %sk_LocalInvocationIndex BuiltIn LocalInvocationIndex
+OpDecorate %sk_NumWorkgroups BuiltIn NumWorkgroups
+OpDecorate %sk_WorkgroupID BuiltIn WorkgroupId
+%uint = OpTypeInt 32 0
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%outputs = OpTypeStruct %_runtimearr_uint
+%_ptr_Uniform_outputs = OpTypePointer Uniform %outputs
+%4 = OpVariable %_ptr_Uniform_outputs Uniform
+%v3uint = OpTypeVector %uint 3
+%_ptr_Input_v3uint = OpTypePointer Input %v3uint
+%sk_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%sk_LocalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%_ptr_Input_uint = OpTypePointer Input %uint
+%sk_LocalInvocationIndex = OpVariable %_ptr_Input_uint Input
+%sk_NumWorkgroups = OpVariable %_ptr_Input_v3uint Input
+%sk_WorkgroupID = OpVariable %_ptr_Input_v3uint Input
+%17 = OpTypeFunction %uint
+%void = OpTypeVoid
+%31 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%helper_I = OpFunction %uint None %17
+%18 = OpLabel
+%19 = OpLoad %v3uint %sk_NumWorkgroups
+%20 = OpCompositeExtract %uint %19 0
+%21 = OpLoad %v3uint %sk_WorkgroupID
+%22 = OpCompositeExtract %uint %21 0
+%23 = OpIAdd %uint %20 %22
+%24 = OpLoad %v3uint %sk_LocalInvocationID
+%25 = OpCompositeExtract %uint %24 0
+%26 = OpIAdd %uint %23 %25
+%27 = OpLoad %v3uint %sk_GlobalInvocationID
+%28 = OpCompositeExtract %uint %27 0
+%29 = OpIAdd %uint %26 %28
+OpReturnValue %29
+OpFunctionEnd
+%main = OpFunction %void None %31
+%32 = OpLabel
+%33 = OpFunctionCall %uint %helper_I
+%36 = OpLoad %uint %sk_LocalInvocationIndex
+%37 = OpAccessChain %_ptr_Uniform_uint %4 %int_0 %36
+OpStore %37 %33
+OpReturn
+OpFunctionEnd
diff --git a/tests/sksl/compute/Desaturate.asm.comp b/tests/sksl/compute/Desaturate.asm.comp
new file mode 100644
index 0000000..404a157
--- /dev/null
+++ b/tests/sksl/compute/Desaturate.asm.comp
@@ -0,0 +1,15 @@
+### Compilation failed:
+
+error: 10: unsupported intrinsic '$pure uint textureWidth($genTexture2D t)'
+    if (sk_GlobalInvocationID.x < textureWidth(src) && sk_GlobalInvocationID.y < textureHeight(src)) {
+                                  ^^^^^^^^^^^^^^^^^
+error: 10: unsupported intrinsic '$pure uint textureHeight($genTexture2D t)'
+    if (sk_GlobalInvocationID.x < textureWidth(src) && sk_GlobalInvocationID.y < textureHeight(src)) {
+                                                                                 ^^^^^^^^^^^^^^^^^^
+error: 11: unsupported intrinsic '$pure half4 textureRead($readableTexture2D t, uint2 pos)'
+        textureWrite(dest, sk_GlobalInvocationID.xy, desaturate(textureRead(src, sk_GlobalInvocationID.xy)));
+                                                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+error: 11: unsupported intrinsic 'void textureWrite($writableTexture2D t, uint2 pos, half4 color)'
+        textureWrite(dest, sk_GlobalInvocationID.xy, desaturate(textureRead(src, sk_GlobalInvocationID.xy)));
+        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+4 errors
diff --git a/tests/sksl/compute/DesaturateFunction.asm.comp b/tests/sksl/compute/DesaturateFunction.asm.comp
new file mode 100644
index 0000000..7543648
--- /dev/null
+++ b/tests/sksl/compute/DesaturateFunction.asm.comp
@@ -0,0 +1,15 @@
+### Compilation failed:
+
+error: 5: unsupported intrinsic '$pure half4 textureRead($readableTexture2D t, uint2 pos)'
+    half4 color = textureRead(src, sk_GlobalInvocationID.xy);
+                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+error: 7: unsupported intrinsic 'void textureWrite($writableTexture2D t, uint2 pos, half4 color)'
+    textureWrite(dest, sk_GlobalInvocationID.xy, color);
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+error: 11: unsupported intrinsic '$pure uint textureWidth($genTexture2D t)'
+    if (sk_GlobalInvocationID.x < textureWidth(src) && sk_GlobalInvocationID.y < textureHeight(src)) {
+                                  ^^^^^^^^^^^^^^^^^
+error: 11: unsupported intrinsic '$pure uint textureHeight($genTexture2D t)'
+    if (sk_GlobalInvocationID.x < textureWidth(src) && sk_GlobalInvocationID.y < textureHeight(src)) {
+                                                                                 ^^^^^^^^^^^^^^^^^^
+4 errors
diff --git a/tests/sksl/compute/DesaturateReadWrite.asm.comp b/tests/sksl/compute/DesaturateReadWrite.asm.comp
new file mode 100644
index 0000000..e12369b
--- /dev/null
+++ b/tests/sksl/compute/DesaturateReadWrite.asm.comp
@@ -0,0 +1,15 @@
+### Compilation failed:
+
+error: 9: unsupported intrinsic '$pure uint textureWidth($genTexture2D t)'
+    if (sk_GlobalInvocationID.x < textureWidth(tex) && sk_GlobalInvocationID.y < textureHeight(tex)) {
+                                  ^^^^^^^^^^^^^^^^^
+error: 9: unsupported intrinsic '$pure uint textureHeight($genTexture2D t)'
+    if (sk_GlobalInvocationID.x < textureWidth(tex) && sk_GlobalInvocationID.y < textureHeight(tex)) {
+                                                                                 ^^^^^^^^^^^^^^^^^^
+error: 10: unsupported intrinsic '$pure half4 textureRead($readableTexture2D t, uint2 pos)'
+        textureWrite(tex, sk_GlobalInvocationID.xy, desaturate(textureRead(tex, sk_GlobalInvocationID.xy)));
+                                                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+error: 10: unsupported intrinsic 'void textureWrite($writableTexture2D t, uint2 pos, half4 color)'
+        textureWrite(tex, sk_GlobalInvocationID.xy, desaturate(textureRead(tex, sk_GlobalInvocationID.xy)));
+        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+4 errors
diff --git a/tests/sksl/compute/MatrixMultiply.asm.comp b/tests/sksl/compute/MatrixMultiply.asm.comp
new file mode 100644
index 0000000..f385e00
--- /dev/null
+++ b/tests/sksl/compute/MatrixMultiply.asm.comp
@@ -0,0 +1,160 @@
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %sk_GlobalInvocationID
+OpExecutionMode %main LocalSize 16 16 1
+OpName %sizeBuffer "sizeBuffer"
+OpMemberName %sizeBuffer 0 "sizes"
+OpName %inputs1 "inputs1"
+OpMemberName %inputs1 0 "data1"
+OpName %inputs2 "inputs2"
+OpMemberName %inputs2 0 "data2"
+OpName %result "result"
+OpMemberName %result 0 "resultData"
+OpName %sk_GlobalInvocationID "sk_GlobalInvocationID"
+OpName %main "main"
+OpName %resultCell "resultCell"
+OpName %result_0 "result"
+OpName %i "i"
+OpName %a "a"
+OpName %b "b"
+OpName %index "index"
+OpDecorate %_runtimearr_v2int ArrayStride 16
+OpMemberDecorate %sizeBuffer 0 Offset 0
+OpDecorate %sizeBuffer BufferBlock
+OpDecorate %3 Binding 0
+OpDecorate %3 DescriptorSet 0
+OpDecorate %_runtimearr_float ArrayStride 16
+OpMemberDecorate %inputs1 0 Offset 0
+OpDecorate %inputs1 BufferBlock
+OpDecorate %9 Binding 1
+OpDecorate %9 DescriptorSet 0
+OpMemberDecorate %inputs2 0 Offset 0
+OpDecorate %inputs2 BufferBlock
+OpDecorate %14 Binding 2
+OpDecorate %14 DescriptorSet 0
+OpMemberDecorate %result 0 Offset 0
+OpDecorate %result BufferBlock
+OpDecorate %17 Binding 3
+OpDecorate %17 DescriptorSet 0
+OpDecorate %sk_GlobalInvocationID BuiltIn GlobalInvocationId
+%int = OpTypeInt 32 1
+%v2int = OpTypeVector %int 2
+%_runtimearr_v2int = OpTypeRuntimeArray %v2int
+%sizeBuffer = OpTypeStruct %_runtimearr_v2int
+%_ptr_Uniform_sizeBuffer = OpTypePointer Uniform %sizeBuffer
+%3 = OpVariable %_ptr_Uniform_sizeBuffer Uniform
+%float = OpTypeFloat 32
+%_runtimearr_float = OpTypeRuntimeArray %float
+%inputs1 = OpTypeStruct %_runtimearr_float
+%_ptr_Uniform_inputs1 = OpTypePointer Uniform %inputs1
+%9 = OpVariable %_ptr_Uniform_inputs1 Uniform
+%inputs2 = OpTypeStruct %_runtimearr_float
+%_ptr_Uniform_inputs2 = OpTypePointer Uniform %inputs2
+%14 = OpVariable %_ptr_Uniform_inputs2 Uniform
+%result = OpTypeStruct %_runtimearr_float
+%_ptr_Uniform_result = OpTypePointer Uniform %result
+%17 = OpVariable %_ptr_Uniform_result Uniform
+%uint = OpTypeInt 32 0
+%v3uint = OpTypeVector %uint 3
+%_ptr_Input_v3uint = OpTypePointer Input %v3uint
+%sk_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%void = OpTypeVoid
+%25 = OpTypeFunction %void
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_v2int = OpTypePointer Uniform %v2int
+%int_1 = OpConstant %int 1
+%int_2 = OpConstant %int 2
+%_ptr_Function_v2int = OpTypePointer Function %v2int
+%_ptr_Function_float = OpTypePointer Function %float
+%float_0 = OpConstant %float 0
+%_ptr_Function_int = OpTypePointer Function %int
+%bool = OpTypeBool
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%main = OpFunction %void None %25
+%26 = OpLabel
+%resultCell = OpVariable %_ptr_Function_v2int Function
+%result_0 = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+%a = OpVariable %_ptr_Function_int Function
+%b = OpVariable %_ptr_Function_int Function
+%index = OpVariable %_ptr_Function_int Function
+%28 = OpAccessChain %_ptr_Uniform_v2int %3 %int_0 %int_0
+%30 = OpLoad %v2int %28
+%31 = OpCompositeExtract %int %30 0
+%33 = OpAccessChain %_ptr_Uniform_v2int %3 %int_0 %int_1
+%34 = OpLoad %v2int %33
+%35 = OpCompositeExtract %int %34 1
+%36 = OpCompositeConstruct %v2int %31 %35
+%38 = OpAccessChain %_ptr_Uniform_v2int %3 %int_0 %int_2
+OpStore %38 %36
+%41 = OpLoad %v3uint %sk_GlobalInvocationID
+%42 = OpCompositeExtract %uint %41 0
+%43 = OpBitcast %int %42
+%44 = OpLoad %v3uint %sk_GlobalInvocationID
+%45 = OpCompositeExtract %uint %44 1
+%46 = OpBitcast %int %45
+%47 = OpCompositeConstruct %v2int %43 %46
+OpStore %resultCell %47
+OpStore %result_0 %float_0
+OpStore %i %int_0
+OpBranch %53
+%53 = OpLabel
+OpLoopMerge %57 %56 None
+OpBranch %54
+%54 = OpLabel
+%58 = OpLoad %int %i
+%59 = OpAccessChain %_ptr_Uniform_v2int %3 %int_0 %int_0
+%60 = OpLoad %v2int %59
+%61 = OpCompositeExtract %int %60 1
+%62 = OpSLessThan %bool %58 %61
+OpBranchConditional %62 %55 %57
+%55 = OpLabel
+%65 = OpLoad %int %i
+%66 = OpLoad %v2int %resultCell
+%67 = OpCompositeExtract %int %66 0
+%68 = OpAccessChain %_ptr_Uniform_v2int %3 %int_0 %int_0
+%69 = OpLoad %v2int %68
+%70 = OpCompositeExtract %int %69 1
+%71 = OpIMul %int %67 %70
+%72 = OpIAdd %int %65 %71
+OpStore %a %72
+%74 = OpLoad %v2int %resultCell
+%75 = OpCompositeExtract %int %74 1
+%76 = OpLoad %int %i
+%77 = OpAccessChain %_ptr_Uniform_v2int %3 %int_0 %int_1
+%78 = OpLoad %v2int %77
+%79 = OpCompositeExtract %int %78 1
+%80 = OpIMul %int %76 %79
+%81 = OpIAdd %int %75 %80
+OpStore %b %81
+%82 = OpLoad %float %result_0
+%83 = OpAccessChain %_ptr_Uniform_float %9 %int_0 %72
+%85 = OpLoad %float %83
+%86 = OpAccessChain %_ptr_Uniform_float %14 %int_0 %81
+%87 = OpLoad %float %86
+%88 = OpFMul %float %85 %87
+%89 = OpFAdd %float %82 %88
+OpStore %result_0 %89
+OpBranch %56
+%56 = OpLabel
+%90 = OpLoad %int %i
+%91 = OpIAdd %int %90 %int_1
+OpStore %i %91
+OpBranch %53
+%57 = OpLabel
+%93 = OpLoad %v2int %resultCell
+%94 = OpCompositeExtract %int %93 1
+%95 = OpLoad %v2int %resultCell
+%96 = OpCompositeExtract %int %95 0
+%97 = OpAccessChain %_ptr_Uniform_v2int %3 %int_0 %int_1
+%98 = OpLoad %v2int %97
+%99 = OpCompositeExtract %int %98 1
+%100 = OpIMul %int %96 %99
+%101 = OpIAdd %int %94 %100
+OpStore %index %101
+%102 = OpLoad %float %result_0
+%103 = OpAccessChain %_ptr_Uniform_float %17 %int_0 %101
+OpStore %103 %102
+OpReturn
+OpFunctionEnd
diff --git a/tests/sksl/compute/Raytrace.asm.comp b/tests/sksl/compute/Raytrace.asm.comp
new file mode 100644
index 0000000..43a3bd0
--- /dev/null
+++ b/tests/sksl/compute/Raytrace.asm.comp
@@ -0,0 +1,18 @@
+### Compilation failed:
+
+error: 8: unsupported intrinsic '$pure uint textureWidth($genTexture2D t)'
+    float x = (float(sk_GlobalInvocationID.x * 2 - textureWidth(dest)) / float(textureWidth(dest)));
+                                                   ^^^^^^^^^^^^^^^^^^
+error: 8: unsupported intrinsic '$pure uint textureWidth($genTexture2D t)'
+    float x = (float(sk_GlobalInvocationID.x * 2 - textureWidth(dest)) / float(textureWidth(dest)));
+                                                                               ^^^^^^^^^^^^^^^^^^
+error: 9: unsupported intrinsic '$pure uint textureHeight($genTexture2D t)'
+    float y = (float(sk_GlobalInvocationID.y * 2 - textureHeight(dest)) / float(textureHeight(dest)));
+                                                   ^^^^^^^^^^^^^^^^^^^
+error: 9: unsupported intrinsic '$pure uint textureHeight($genTexture2D t)'
+    float y = (float(sk_GlobalInvocationID.y * 2 - textureHeight(dest)) / float(textureHeight(dest)));
+                                                                                ^^^^^^^^^^^^^^^^^^^
+error: 25: unsupported intrinsic 'void textureWrite($writableTexture2D t, uint2 pos, half4 color)'
+    textureWrite(dest, sk_GlobalInvocationID.xy, pixel);
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+5 errors
diff --git a/tests/sksl/compute/Uniforms.asm.comp b/tests/sksl/compute/Uniforms.asm.comp
new file mode 100644
index 0000000..109b597
--- /dev/null
+++ b/tests/sksl/compute/Uniforms.asm.comp
@@ -0,0 +1,49 @@
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %sk_GlobalInvocationID
+OpExecutionMode %main LocalSize 16 16 1
+OpName %constants "constants"
+OpMemberName %constants 0 "x"
+OpName %outputBuffer "outputBuffer"
+OpMemberName %outputBuffer 0 "results"
+OpName %sk_GlobalInvocationID "sk_GlobalInvocationID"
+OpName %main "main"
+OpMemberDecorate %constants 0 Offset 0
+OpDecorate %constants Block
+OpDecorate %3 Binding 0
+OpDecorate %3 DescriptorSet 0
+OpDecorate %_runtimearr_int ArrayStride 16
+OpMemberDecorate %outputBuffer 0 Offset 0
+OpDecorate %outputBuffer BufferBlock
+OpDecorate %7 Binding 1
+OpDecorate %7 DescriptorSet 0
+OpDecorate %sk_GlobalInvocationID BuiltIn GlobalInvocationId
+%int = OpTypeInt 32 1
+%constants = OpTypeStruct %int
+%_ptr_Uniform_constants = OpTypePointer Uniform %constants
+%3 = OpVariable %_ptr_Uniform_constants Uniform
+%_runtimearr_int = OpTypeRuntimeArray %int
+%outputBuffer = OpTypeStruct %_runtimearr_int
+%_ptr_Uniform_outputBuffer = OpTypePointer Uniform %outputBuffer
+%7 = OpVariable %_ptr_Uniform_outputBuffer Uniform
+%uint = OpTypeInt 32 0
+%v3uint = OpTypeVector %uint 3
+%_ptr_Input_v3uint = OpTypePointer Input %v3uint
+%sk_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%void = OpTypeVoid
+%16 = OpTypeFunction %void
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_int = OpTypePointer Uniform %int
+%main = OpFunction %void None %16
+%17 = OpLabel
+%19 = OpLoad %v3uint %sk_GlobalInvocationID
+%20 = OpCompositeExtract %uint %19 0
+%21 = OpAccessChain %_ptr_Uniform_int %7 %int_0 %20
+%23 = OpLoad %int %21
+%24 = OpAccessChain %_ptr_Uniform_int %3 %int_0
+%25 = OpLoad %int %24
+%26 = OpIMul %int %23 %25
+OpStore %21 %26
+OpReturn
+OpFunctionEnd
diff --git a/tests/sksl/compute/Workgroup.asm.comp b/tests/sksl/compute/Workgroup.asm.comp
new file mode 100644
index 0000000..ca63ab2
--- /dev/null
+++ b/tests/sksl/compute/Workgroup.asm.comp
@@ -0,0 +1,9 @@
+### Compilation failed:
+
+error: 30: unsupported intrinsic 'void workgroupBarrier()'
+    workgroupBarrier();
+    ^^^^^^^^^^^^^^^^^^
+error: 42: unsupported intrinsic 'void workgroupBarrier()'
+        workgroupBarrier();
+        ^^^^^^^^^^^^^^^^^^
+2 errors
diff --git a/tools/skslc/Main.cpp b/tools/skslc/Main.cpp
index 256aedc..6aeff3a 100644
--- a/tools/skslc/Main.cpp
+++ b/tools/skslc/Main.cpp
@@ -520,7 +520,7 @@
     } else if (skstd::ends_with(inputPath, ".rts")) {
         kind = SkSL::ProgramKind::kRuntimeShader;
     } else {
-        printf("input filename must end in '.vert', '.frag', '.rtb', '.rtcf', "
+        printf("input filename must end in '.vert', '.frag', '.compute', '.rtb', '.rtcf', "
                "'.rts' or '.sksl'\n");
         return ResultCode::kInputError;
     }
@@ -599,7 +599,8 @@
                     return compiler.toSPIRV(program, out);
                 });
     } else if (skstd::ends_with(outputPath, ".asm.frag") ||
-               skstd::ends_with(outputPath, ".asm.vert")) {
+               skstd::ends_with(outputPath, ".asm.vert") ||
+               skstd::ends_with(outputPath, ".asm.comp")) {
         return compileProgram(
                 [](SkSL::Compiler& compiler, SkSL::Program& program, SkSL::OutputStream& out) {
                     // Compile program to SPIR-V assembly in a string-stream.
@@ -736,7 +737,7 @@
                 });
     } else {
         printf("expected output path to end with one of: .glsl, .html, .metal, .hlsl, .wgsl, "
-               ".spirv, .asm.vert, .asm.frag, .skrp, .stage (got '%s')\n",
+               ".spirv, .asm.vert, .asm.frag, .asm.comp, .skrp, .stage (got '%s')\n",
                outputPath.c_str());
         return ResultCode::kConfigurationError;
     }