Browse Source

[spirv] Add examples for legalization cases (#1586)

These examples are meant to show what cases are accepted and
what cases are not accepted for HLSL legalization for SPIR-V.
Lei Zhang 7 years ago
parent
commit
07608b397e
28 changed files with 852 additions and 30 deletions
  1. 20 0
      tools/clang/test/CodeGenSPIRV/legal-examples/0-copy-sbuf-ok.hlsl
  2. 21 0
      tools/clang/test/CodeGenSPIRV/legal-examples/1-copy-global-static-ok.hlsl
  3. 41 0
      tools/clang/test/CodeGenSPIRV/legal-examples/10-if-stmt-select-ok.hlsl
  4. 29 0
      tools/clang/test/CodeGenSPIRV/legal-examples/11-if-stmt-const-ok.hlsl
  5. 36 0
      tools/clang/test/CodeGenSPIRV/legal-examples/12-switch-stmt-select-fail.hlsl
  6. 37 0
      tools/clang/test/CodeGenSPIRV/legal-examples/13-switch-stmt-const-ok.hlsl
  7. 32 0
      tools/clang/test/CodeGenSPIRV/legal-examples/14-loop-var-fail.hlsl
  8. 41 0
      tools/clang/test/CodeGenSPIRV/legal-examples/15-loop-var-unroll-ok.hlsl
  9. 67 0
      tools/clang/test/CodeGenSPIRV/legal-examples/16-loop-var-range-fail.hlsl
  10. 33 0
      tools/clang/test/CodeGenSPIRV/legal-examples/17-loop-var-float-fail.hlsl
  11. 29 0
      tools/clang/test/CodeGenSPIRV/legal-examples/18-multi-func-call-ok.hlsl
  12. 27 0
      tools/clang/test/CodeGenSPIRV/legal-examples/19-multi-func-ret-fail.hlsl
  13. 19 0
      tools/clang/test/CodeGenSPIRV/legal-examples/2-write-global-static-ok.hlsl
  14. 34 0
      tools/clang/test/CodeGenSPIRV/legal-examples/20-multi-func-ret-const-ok.hlsl
  15. 40 0
      tools/clang/test/CodeGenSPIRV/legal-examples/21-combined-ok.hlsl
  16. 28 0
      tools/clang/test/CodeGenSPIRV/legal-examples/3-copy-local-struct-ok.hlsl
  17. 35 0
      tools/clang/test/CodeGenSPIRV/legal-examples/4-copy-local-nested-struct-ok.hlsl
  18. 29 0
      tools/clang/test/CodeGenSPIRV/legal-examples/5-func-param-sbuf-ok.hlsl
  19. 23 0
      tools/clang/test/CodeGenSPIRV/legal-examples/6-func-param-rwsbuf-ok.hlsl
  20. 24 0
      tools/clang/test/CodeGenSPIRV/legal-examples/7-func-ret-tmp-var-ok.hlsl
  21. 23 0
      tools/clang/test/CodeGenSPIRV/legal-examples/8-func-ret-direct-ok.hlsl
  22. 31 0
      tools/clang/test/CodeGenSPIRV/legal-examples/9-if-stmt-select-fail.hlsl
  23. 12 0
      tools/clang/test/CodeGenSPIRV/legal-examples/README.md
  24. 73 4
      tools/clang/unittests/SPIRV/CodeGenSPIRVTest.cpp
  25. 48 12
      tools/clang/unittests/SPIRV/FileTestFixture.cpp
  26. 4 3
      tools/clang/unittests/SPIRV/FileTestFixture.h
  27. 14 9
      tools/clang/unittests/SPIRV/FileTestUtils.cpp
  28. 2 2
      tools/clang/unittests/SPIRV/FileTestUtils.h

+ 20 - 0
tools/clang/test/CodeGenSPIRV/legal-examples/0-copy-sbuf-ok.hlsl

@@ -0,0 +1,20 @@
+// Run: %dxc -T cs_6_0 -E main -O3
+
+// CHECK:      [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gSBuffer
+// CHECK-NEXT: [[val:%\d+]] = OpLoad %S [[ptr]]
+// CHECK-NEXT: [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gRWSBuffer
+// CHECK-NEXT:                OpStore [[ptr]] [[val]]
+
+struct S {
+  float4 f;
+};
+
+int i;
+
+StructuredBuffer<S> gSBuffer;
+RWStructuredBuffer<S> gRWSBuffer;
+
+[numthreads(1, 1, 1)]
+void main() {
+  gRWSBuffer[i] = gSBuffer[i];
+}

+ 21 - 0
tools/clang/test/CodeGenSPIRV/legal-examples/1-copy-global-static-ok.hlsl

@@ -0,0 +1,21 @@
+// Run: %dxc -T cs_6_0 -E main -O3
+
+// CHECK:      [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gSBuffer
+// CHECK-NEXT: [[val:%\d+]] = OpLoad %S [[ptr]]
+// CHECK-NEXT: [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gRWSBuffer
+// CHECK-NEXT:                OpStore [[ptr]] [[val]]
+
+struct S {
+  float4 f;
+};
+
+int i;
+
+StructuredBuffer<S> gSBuffer;
+RWStructuredBuffer<S> gRWSBuffer;
+
+static StructuredBuffer<S> sSBuffer = gSBuffer;
+
+void main() {
+  gRWSBuffer[i] = sSBuffer[i];
+}

+ 41 - 0
tools/clang/test/CodeGenSPIRV/legal-examples/10-if-stmt-select-ok.hlsl

@@ -0,0 +1,41 @@
+// Run: %dxc -T cs_6_0 -E main -O3
+
+// CHECK:      [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gSBuffer1
+// CHECK-NEXT: [[val:%\d+]] = OpLoad %S [[ptr]]
+// CHECK-NEXT: [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gRWSBuffer
+// CHECK-NEXT:                OpStore [[ptr]] [[val]]
+
+// CHECK:      [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gSBuffer2
+// CHECK-NEXT: [[val:%\d+]] = OpLoad %S [[ptr]]
+// CHECK-NEXT: [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gRWSBuffer
+// CHECK-NEXT:                OpStore [[ptr]] [[val]]
+
+struct S {
+  float4 f;
+};
+
+struct CombinedBuffers {
+  StructuredBuffer<S> SBuffer;
+  RWStructuredBuffer<S> RWSBuffer;
+};
+
+
+int i;
+
+StructuredBuffer<S> gSBuffer1;
+StructuredBuffer<S> gSBuffer2;
+RWStructuredBuffer<S> gRWSBuffer;
+
+#define constant 0
+
+void main() {
+
+  StructuredBuffer<S> lSBuffer;
+  if (constant > i) {
+    lSBuffer = gSBuffer1;
+    gRWSBuffer[i] = lSBuffer[i];
+  } else {
+    lSBuffer = gSBuffer2;
+    gRWSBuffer[i] = lSBuffer[i];
+  }
+}

+ 29 - 0
tools/clang/test/CodeGenSPIRV/legal-examples/11-if-stmt-const-ok.hlsl

@@ -0,0 +1,29 @@
+// Run: %dxc -T cs_6_0 -E main -O3
+
+// CHECK:      [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gSBuffer2
+// CHECK-NEXT: [[val:%\d+]] = OpLoad %S [[ptr]]
+// CHECK-NEXT: [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gRWSBuffer
+// CHECK-NEXT:                OpStore [[ptr]] [[val]]
+
+struct S {
+  float4 f;
+};
+
+int i;
+
+StructuredBuffer<S> gSBuffer1;
+StructuredBuffer<S> gSBuffer2;
+RWStructuredBuffer<S> gRWSBuffer;
+
+#define constant 0
+
+void main() {
+
+  StructuredBuffer<S> lSBuffer;
+  if (constant > 2) {
+    lSBuffer = gSBuffer1;
+  } else {
+    lSBuffer = gSBuffer2;
+  }
+  gRWSBuffer[i] = lSBuffer[i];
+}

+ 36 - 0
tools/clang/test/CodeGenSPIRV/legal-examples/12-switch-stmt-select-fail.hlsl

@@ -0,0 +1,36 @@
+// Run: %dxc -T cs_6_0 -E main -O3
+
+// CHECK: error
+
+struct S {
+  float4 f;
+};
+
+struct CombinedBuffers {
+  StructuredBuffer<S> SBuffer;
+  RWStructuredBuffer<S> RWSBuffer;
+};
+
+
+int i;
+
+StructuredBuffer<S> gSBuffer1;
+StructuredBuffer<S> gSBuffer2;
+RWStructuredBuffer<S> gRWSBuffer;
+
+#define constant 0
+
+void main() {
+
+  StructuredBuffer<S> lSBuffer;
+  switch(i) {                   // Compiler can't determine which case will run.
+    case 0:                     // Will produce invalid SPIR-V for Vulkan.
+      lSBuffer = gSBuffer1;
+      break;
+    default:
+      lSBuffer = gSBuffer2;
+      break;
+    }
+    gRWSBuffer[i] = lSBuffer[i];
+  }
+}

+ 37 - 0
tools/clang/test/CodeGenSPIRV/legal-examples/13-switch-stmt-const-ok.hlsl

@@ -0,0 +1,37 @@
+// Run: %dxc -T cs_6_0 -E main -O3
+
+// CHECK:      [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gSBuffer1
+// CHECK-NEXT: [[val:%\d+]] = OpLoad %S [[ptr]]
+// CHECK-NEXT: [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gRWSBuffer
+// CHECK-NEXT:                OpStore [[ptr]] [[val]]
+
+struct S {
+  float4 f;
+};
+
+struct CombinedBuffers {
+  StructuredBuffer<S> SBuffer;
+  RWStructuredBuffer<S> RWSBuffer;
+};
+
+
+int i;
+
+StructuredBuffer<S> gSBuffer1;
+StructuredBuffer<S> gSBuffer2;
+RWStructuredBuffer<S> gRWSBuffer;
+
+const static int constant = 0;
+
+void main() {
+  StructuredBuffer<S> lSBuffer;
+  switch(constant) {
+    case 0:
+      lSBuffer = gSBuffer1;
+      break;
+    default:
+      lSBuffer = gSBuffer2;
+      break;
+  }
+  gRWSBuffer[i] = lSBuffer[i];
+}

+ 32 - 0
tools/clang/test/CodeGenSPIRV/legal-examples/14-loop-var-fail.hlsl

@@ -0,0 +1,32 @@
+// Run: %dxc -T cs_6_0 -E main -O3
+
+// CHECK: Using pointers with OpSelect requires capability
+
+struct S {
+  float4 f;
+};
+
+struct CombinedBuffers {
+  StructuredBuffer<S> SBuffer;
+  RWStructuredBuffer<S> RWSBuffer;
+};
+
+StructuredBuffer<S> gSBuffer1;
+StructuredBuffer<S> gSBuffer2;
+RWStructuredBuffer<S> gRWSBuffer;
+
+#define constant 0
+
+void main() {
+
+  StructuredBuffer<S> lSBuffer;
+
+  for( int j = 0; j < 2; j++ ) {
+    if (constant > j) {         // Condition is different for different iterations
+      lSBuffer = gSBuffer1;     // Will produces invalid SPIR-V for Vulkan.
+    } else {
+      lSBuffer = gSBuffer2;
+    }
+    gRWSBuffer[j] = lSBuffer[j];
+  }
+}

+ 41 - 0
tools/clang/test/CodeGenSPIRV/legal-examples/15-loop-var-unroll-ok.hlsl

@@ -0,0 +1,41 @@
+// Run: %dxc -T cs_6_0 -E main -O3
+
+// CHECK:      [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gSBuffer2
+// CHECK-NEXT: [[val:%\d+]] = OpLoad %S [[ptr]]
+// CHECK-NEXT: [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gRWSBuffer
+// CHECK-NEXT:                OpStore [[ptr]] [[val]]
+
+// CHECK:      [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gSBuffer2
+// CHECK-NEXT: [[val:%\d+]] = OpLoad %S [[ptr]]
+// CHECK-NEXT: [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gRWSBuffer
+// CHECK-NEXT:                OpStore [[ptr]] [[val]]
+
+struct S {
+  float4 f;
+};
+
+struct CombinedBuffers {
+  StructuredBuffer<S> SBuffer;
+  RWStructuredBuffer<S> RWSBuffer;
+};
+
+StructuredBuffer<S> gSBuffer1;
+StructuredBuffer<S> gSBuffer2;
+RWStructuredBuffer<S> gRWSBuffer;
+
+#define constant 0
+
+void main() {
+
+  StructuredBuffer<S> lSBuffer;
+
+  [unroll]
+  for( int j = 0; j < 2; j++ ) {
+    if (constant > j) {
+      lSBuffer = gSBuffer1;
+    } else {
+      lSBuffer = gSBuffer2;
+    }
+    gRWSBuffer[j] = lSBuffer[j];
+  }
+}

+ 67 - 0
tools/clang/test/CodeGenSPIRV/legal-examples/16-loop-var-range-fail.hlsl

@@ -0,0 +1,67 @@
+// Run: %dxc -T cs_6_0 -E main -O3
+
+// CHECK: Using pointers with OpSelect requires capability
+
+struct S {
+  float4 f;
+};
+
+struct CombinedBuffers {
+  StructuredBuffer<S> SBuffer;
+  RWStructuredBuffer<S> RWSBuffer;
+};
+
+StructuredBuffer<S> gSBuffer1;
+StructuredBuffer<S> gSBuffer2;
+RWStructuredBuffer<S> gRWSBuffer;
+
+int i;
+
+#define constant 0
+
+void foo1() {
+  StructuredBuffer<S> lSBuffer;
+
+  [unroll]
+  for( int j = i; j < 2; j++ ) {  // Compiler can't determine start iteration
+    if (constant > j) {
+      lSBuffer = gSBuffer1;
+    } else {
+      lSBuffer = gSBuffer2;
+    }
+    gRWSBuffer[j] = lSBuffer[j];
+  }
+}
+
+void foo2() {
+  StructuredBuffer<S> lSBuffer;
+
+  [unroll]
+  for( int j = 0; j < i; j++ ) {  // Compiler can't determine end iteration
+    if (constant > j) {
+      lSBuffer = gSBuffer1;
+    } else {
+      lSBuffer = gSBuffer2;
+    }
+    gRWSBuffer[j] = lSBuffer[j];
+  }
+}
+
+void foo3() {
+  StructuredBuffer<S> lSBuffer;
+
+  [unroll]
+  for( int j = 0; j < 2; j += i ) { // Compiler can't determine step count
+    if (constant > j) {
+      lSBuffer = gSBuffer1;
+    } else {
+      lSBuffer = gSBuffer2;
+    }
+    gRWSBuffer[j] = lSBuffer[j];
+  }
+}
+
+
+void main() {
+  foo1(); foo2(); foo3();
+}

+ 33 - 0
tools/clang/test/CodeGenSPIRV/legal-examples/17-loop-var-float-fail.hlsl

@@ -0,0 +1,33 @@
+// Run: %dxc -T cs_6_0 -E main -O3
+
+// CHECK: Using pointers with OpSelect requires capability
+
+struct S {
+  float4 f;
+};
+
+struct CombinedBuffers {
+  StructuredBuffer<S> SBuffer;
+  RWStructuredBuffer<S> RWSBuffer;
+};
+
+StructuredBuffer<S> gSBuffer1;
+StructuredBuffer<S> gSBuffer2;
+RWStructuredBuffer<S> gRWSBuffer;
+
+#define constant 0
+
+void main() {
+
+  StructuredBuffer<S> lSBuffer;
+
+  [unroll]
+  for( float j = 0; j < 2; j++ ) {  // Can't infer floating point induction values
+    if (constant > j) {
+      lSBuffer = gSBuffer1;
+    } else {
+      lSBuffer = gSBuffer2;
+    }
+    gRWSBuffer[j] = lSBuffer[j];
+  }
+}

+ 29 - 0
tools/clang/test/CodeGenSPIRV/legal-examples/18-multi-func-call-ok.hlsl

@@ -0,0 +1,29 @@
+// Run: %dxc -T cs_6_0 -E main -O3
+
+// CHECK:      [[src:%\d+]] = OpAccessChain %_ptr_Uniform_S %gSBuffer
+// CHECK-NEXT: [[val:%\d+]] = OpLoad %S [[src]]
+// CHECK-NEXT: [[dst:%\d+]] = OpAccessChain %_ptr_Uniform_S %gRWSBuffer1
+// CHECK-NEXT:                OpStore [[dst]] [[val]]
+// CHECK-NEXT: [[val:%\d+]] = OpLoad %S [[src]]
+// CHECK-NEXT: [[dst:%\d+]] = OpAccessChain %_ptr_Uniform_S %gRWSBuffer2
+// CHECK-NEXT:                OpStore [[dst]] [[val]]
+
+struct S {
+  float4 f;
+};
+
+int i;
+
+StructuredBuffer<S> gSBuffer;
+RWStructuredBuffer<S> gRWSBuffer1;
+RWStructuredBuffer<S> gRWSBuffer2;
+
+
+void foo(RWStructuredBuffer<S> pRWSBuffer) {
+  pRWSBuffer[i] = gSBuffer[i];
+}
+
+void main() {
+  foo(gRWSBuffer1);
+  foo(gRWSBuffer2);
+}

+ 27 - 0
tools/clang/test/CodeGenSPIRV/legal-examples/19-multi-func-ret-fail.hlsl

@@ -0,0 +1,27 @@
+// Run: %dxc -T cs_6_0 -E main -O3
+
+// CHECK: Using pointers with OpSelect requires capability
+
+struct S {
+  float4 f;
+};
+
+int i;
+
+StructuredBuffer<S> gSBuffer;
+RWStructuredBuffer<S> gRWSBuffer1;
+RWStructuredBuffer<S> gRWSBuffer2;
+
+RWStructuredBuffer<S> foo(int l) {
+  if (l == 0) {       // Compiler does not know which branch will be taken: 
+                      // Branch taken depends on input i.
+    return gRWSBuffer1;
+  } else {
+    return gRWSBuffer2;
+  }
+}
+
+void main() {
+  RWStructuredBuffer<S> lRWSBuffer = foo(i);
+  lRWSBuffer[i] = gSBuffer[i];
+}

+ 19 - 0
tools/clang/test/CodeGenSPIRV/legal-examples/2-write-global-static-ok.hlsl

@@ -0,0 +1,19 @@
+// Run: %dxc -T cs_6_0 -E main -O3
+
+// CHECK:      [[val:%\d+]] = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+// CHECK:      [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_v4float %gRWSBuffer
+// CHECK-NEXT:                OpStore [[ptr]] [[val]]
+
+struct S {
+  float4 f;
+};
+
+int i;
+
+RWStructuredBuffer<S> gRWSBuffer;
+
+static RWStructuredBuffer<S> sRWSBuffer = gRWSBuffer;
+
+void main() {
+  sRWSBuffer[i].f = 0.0;
+}

+ 34 - 0
tools/clang/test/CodeGenSPIRV/legal-examples/20-multi-func-ret-const-ok.hlsl

@@ -0,0 +1,34 @@
+// Run: %dxc -T cs_6_0 -E main -O3
+
+// CHECK:      [[src:%\d+]] = OpAccessChain %_ptr_Uniform_S %gSBuffer1
+// CHECK-NEXT: [[val:%\d+]] = OpLoad %S [[src]]
+// CHECK-NEXT: [[dst:%\d+]] = OpAccessChain %_ptr_Uniform_S %gRWSBuffer1
+// CHECK-NEXT:                OpStore [[dst]] [[val]]
+// CHECK:      [[src:%\d+]] = OpAccessChain %_ptr_Uniform_S %gSBuffer2
+// CHECK-NEXT: [[val:%\d+]] = OpLoad %S [[src]]
+// CHECK-NEXT: [[dst:%\d+]] = OpAccessChain %_ptr_Uniform_S %gRWSBuffer2
+// CHECK-NEXT:                OpStore [[dst]] [[val]]
+
+struct S {
+  float4 f;
+};
+
+int i;
+
+StructuredBuffer<S> gSBuffer1;
+StructuredBuffer<S> gSBuffer2;
+RWStructuredBuffer<S> gRWSBuffer1;
+RWStructuredBuffer<S> gRWSBuffer2;
+
+StructuredBuffer<S> foo(int l) {
+  if (l == 0) {
+    return gSBuffer1;
+  } else {
+    return gSBuffer2;
+  }
+}
+
+void main() {
+  gRWSBuffer1[i] = foo(0)[i];
+  gRWSBuffer2[i] = foo(1)[i];
+}

+ 40 - 0
tools/clang/test/CodeGenSPIRV/legal-examples/21-combined-ok.hlsl

@@ -0,0 +1,40 @@
+// Run: %dxc -T cs_6_0 -E main -O3
+
+// CHECK:      [[src:%\d+]] = OpAccessChain %_ptr_Uniform_S %gSBuffer
+// CHECK-NEXT: [[val:%\d+]] = OpLoad %S [[src]]
+// CHECK-NEXT: [[dst:%\d+]] = OpAccessChain %_ptr_Uniform_S %gRWSBuffer1
+// CHECK-NEXT:                OpStore [[dst]] [[val]]
+// CHECK-NEXT: [[val:%\d+]] = OpLoad %S [[src]]
+// CHECK-NEXT: [[dst:%\d+]] = OpAccessChain %_ptr_Uniform_S %gRWSBuffer2
+// CHECK-NEXT:                OpStore [[dst]] [[val]]
+
+struct S {
+  float4 f;
+};
+
+int i;
+
+StructuredBuffer<S> gSBuffer1;
+StructuredBuffer<S> gSBuffer2;
+RWStructuredBuffer<S> gRWSBuffer1;
+RWStructuredBuffer<S> gRWSBuffer2;
+
+#define constant 0
+
+StructuredBuffer<S> bar() {
+  if (constant > 2) {
+    return gSBuffer1;
+  } else {
+    return gSBuffer2;
+  }
+}
+
+void foo(RWStructuredBuffer<S> pRWSBuffer) {
+  StructuredBuffer<S> lSBuffer = bar();
+  pRWSBuffer[i] = lSBuffer[i];
+}
+
+void main() {
+  foo(gRWSBuffer1);
+  foo(gRWSBuffer2);
+}

+ 28 - 0
tools/clang/test/CodeGenSPIRV/legal-examples/3-copy-local-struct-ok.hlsl

@@ -0,0 +1,28 @@
+// Run: %dxc -T cs_6_0 -E main -O3
+
+// CHECK:      [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gSBuffer
+// CHECK-NEXT: [[val:%\d+]] = OpLoad %S [[ptr]]
+// CHECK-NEXT: [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gRWSBuffer
+// CHECK-NEXT:                OpStore [[ptr]] [[val]]
+
+struct S {
+  float4 f;
+};
+
+struct CombinedBuffers {
+  StructuredBuffer<S> SBuffer;
+  RWStructuredBuffer<S> RWSBuffer;
+};
+
+
+int i;
+
+StructuredBuffer<S> gSBuffer;
+RWStructuredBuffer<S> gRWSBuffer;
+
+void main() {
+  CombinedBuffers cb;
+  cb.SBuffer = gSBuffer;
+  cb.RWSBuffer = gRWSBuffer;
+  cb.RWSBuffer[i] = cb.SBuffer[i];
+}

+ 35 - 0
tools/clang/test/CodeGenSPIRV/legal-examples/4-copy-local-nested-struct-ok.hlsl

@@ -0,0 +1,35 @@
+// Run: %dxc -T cs_6_0 -E main -O3
+
+// CHECK:      [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gSBuffer
+// CHECK-NEXT: [[val:%\d+]] = OpLoad %S [[ptr]]
+// CHECK-NEXT: [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gRWSBuffer
+// CHECK-NEXT:                OpStore [[ptr]] [[val]]
+
+struct S {
+  float4 f;
+};
+
+struct CombinedBuffers {
+  StructuredBuffer<S> SBuffer;
+  RWStructuredBuffer<S> RWSBuffer;
+};
+
+struct S2 {
+  CombinedBuffers cb;
+};
+
+struct S1 {
+  S2 s2;
+};
+
+int i;
+
+StructuredBuffer<S> gSBuffer;
+RWStructuredBuffer<S> gRWSBuffer;
+
+void main() {
+  S1 s1;
+  s1.s2.cb.SBuffer = gSBuffer;
+  s1.s2.cb.RWSBuffer = gRWSBuffer;
+  s1.s2.cb.RWSBuffer[i] = s1.s2.cb.SBuffer[i];
+}

+ 29 - 0
tools/clang/test/CodeGenSPIRV/legal-examples/5-func-param-sbuf-ok.hlsl

@@ -0,0 +1,29 @@
+// Run: %dxc -T cs_6_0 -E main -O3
+
+// CHECK:      [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gSBuffer
+// CHECK-NEXT: [[val:%\d+]] = OpLoad %S [[ptr]]
+// CHECK-NEXT: [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gRWSBuffer
+// CHECK-NEXT:                OpStore [[ptr]] [[val]]
+
+struct S {
+  float4 f;
+};
+
+struct CombinedBuffers {
+  StructuredBuffer<S> SBuffer;
+  RWStructuredBuffer<S> RWSBuffer;
+};
+
+
+int i;
+
+StructuredBuffer<S> gSBuffer;
+RWStructuredBuffer<S> gRWSBuffer;
+
+void foo(StructuredBuffer<S> pSBuffer) {
+  gRWSBuffer[i] = pSBuffer[i];
+}
+
+void main() {
+  foo(gSBuffer);
+}

+ 23 - 0
tools/clang/test/CodeGenSPIRV/legal-examples/6-func-param-rwsbuf-ok.hlsl

@@ -0,0 +1,23 @@
+// Run: %dxc -T cs_6_0 -E main -O3
+
+// CHECK:      [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gSBuffer
+// CHECK-NEXT: [[val:%\d+]] = OpLoad %S [[ptr]]
+// CHECK-NEXT: [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gRWSBuffer
+// CHECK-NEXT:                OpStore [[ptr]] [[val]]
+
+struct S {
+  float4 f;
+};
+
+int i;
+
+StructuredBuffer<S> gSBuffer;
+RWStructuredBuffer<S> gRWSBuffer;
+
+void foo(RWStructuredBuffer<S> pRWSBuffer) {
+  pRWSBuffer[i] = gSBuffer[i];
+}
+
+void main() {
+  foo(gRWSBuffer);
+}

+ 24 - 0
tools/clang/test/CodeGenSPIRV/legal-examples/7-func-ret-tmp-var-ok.hlsl

@@ -0,0 +1,24 @@
+// Run: %dxc -T cs_6_0 -E main -O3
+
+// CHECK:      [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gSBuffer
+// CHECK-NEXT: [[val:%\d+]] = OpLoad %S [[ptr]]
+// CHECK-NEXT: [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gRWSBuffer
+// CHECK-NEXT:                OpStore [[ptr]] [[val]]
+
+struct S {
+  float4 f;
+};
+
+int i;
+
+StructuredBuffer<S> gSBuffer;
+RWStructuredBuffer<S> gRWSBuffer;
+
+RWStructuredBuffer<S> foo() {
+  return gRWSBuffer;
+}
+
+void main() {
+  RWStructuredBuffer<S> lRWSBuffer = foo();
+  lRWSBuffer[i] = gSBuffer[i];
+}

+ 23 - 0
tools/clang/test/CodeGenSPIRV/legal-examples/8-func-ret-direct-ok.hlsl

@@ -0,0 +1,23 @@
+// Run: %dxc -T cs_6_0 -E main -O3
+
+// CHECK:      [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gSBuffer
+// CHECK-NEXT: [[val:%\d+]] = OpLoad %S [[ptr]]
+// CHECK-NEXT: [[ptr:%\d+]] = OpAccessChain %_ptr_Uniform_S %gRWSBuffer
+// CHECK-NEXT:                OpStore [[ptr]] [[val]]
+
+struct S {
+  float4 f;
+};
+
+int i;
+
+StructuredBuffer<S> gSBuffer;
+RWStructuredBuffer<S> gRWSBuffer;
+
+StructuredBuffer<S> foo() {
+  return gSBuffer;
+}
+
+void main() {
+  gRWSBuffer[i] = foo()[i];
+}

+ 31 - 0
tools/clang/test/CodeGenSPIRV/legal-examples/9-if-stmt-select-fail.hlsl

@@ -0,0 +1,31 @@
+// Run: %dxc -T cs_6_0 -E main -O3
+
+// CHECK: Using pointers with OpSelect requires capability
+
+struct S {
+  float4 f;
+};
+
+struct CombinedBuffers {
+  StructuredBuffer<S> SBuffer;
+  RWStructuredBuffer<S> RWSBuffer;
+};
+
+
+int i;
+
+StructuredBuffer<S> gSBuffer1;
+StructuredBuffer<S> gSBuffer2;
+RWStructuredBuffer<S> gRWSBuffer;
+
+#define constant 0
+
+void main() {
+  StructuredBuffer<S> lSBuffer;
+  if (constant > i) {          // Condition can't be computed at compile time.
+    lSBuffer = gSBuffer1;      // Will produce invalid SPIR-V for Vulkan.
+  } else {
+    lSBuffer = gSBuffer2;
+  }
+  gRWSBuffer[i] = lSBuffer[i];
+}

+ 12 - 0
tools/clang/test/CodeGenSPIRV/legal-examples/README.md

@@ -0,0 +1,12 @@
+Legalization Examples
+=====================
+
+HLSL legalization for SPIR-V is a fuzzy topic; it cannot be expressed precisely
+using some grammar. Instead, we've collected a few simple examples here to
+show what cases are allowed and what not.
+
+These examples are all variants to `0-copy-sbuf.hlsl`. And `*-ok.hlsl` are
+allowed cases, while `*-fail.hlsl` are not allowed cases.
+
+Also keep in mind that legalization is an ongoing effort; support has evolved
+and will continue to evolve over time in response to new use cases in the wild.

+ 73 - 4
tools/clang/unittests/SPIRV/CodeGenSPIRVTest.cpp

@@ -931,7 +931,8 @@ TEST_F(FileTest, IntrinsicsInterlockedMethodsError) {
   runFileTest("intrinsics.interlocked-methods.error.hlsl", Expect::Failure);
   runFileTest("intrinsics.interlocked-methods.error.hlsl", Expect::Failure);
 }
 }
 TEST_F(FileTest, IntrinsicsInterlockedMethodsStaticError) {
 TEST_F(FileTest, IntrinsicsInterlockedMethodsStaticError) {
-  runFileTest("intrinsics.interlocked-methods.static-error.hlsl", Expect::Failure);
+  runFileTest("intrinsics.interlocked-methods.static-error.hlsl",
+              Expect::Failure);
 }
 }
 TEST_F(FileTest, IntrinsicsIsInf) { runFileTest("intrinsics.isinf.hlsl"); }
 TEST_F(FileTest, IntrinsicsIsInf) { runFileTest("intrinsics.isinf.hlsl"); }
 TEST_F(FileTest, IntrinsicsIsNan) { runFileTest("intrinsics.isnan.hlsl"); }
 TEST_F(FileTest, IntrinsicsIsNan) { runFileTest("intrinsics.isnan.hlsl"); }
@@ -1344,9 +1345,7 @@ TEST_F(FileTest, SpirvDebugOpSource) {
   runFileTest("spirv.debug.opsource.hlsl");
   runFileTest("spirv.debug.opsource.hlsl");
 }
 }
 
 
-TEST_F(FileTest, SpirvDebugOpLine) {
-  runFileTest("spirv.debug.opline.hlsl");
-}
+TEST_F(FileTest, SpirvDebugOpLine) { runFileTest("spirv.debug.opline.hlsl"); }
 
 
 TEST_F(FileTest, SpirvDebugDxcCommitInfo) {
 TEST_F(FileTest, SpirvDebugDxcCommitInfo) {
   useVulkan1p1();
   useVulkan1p1();
@@ -1668,4 +1667,74 @@ TEST_F(FileTest, ComputeShaderGroupShared) {
   runFileTest("cs.groupshared.hlsl");
   runFileTest("cs.groupshared.hlsl");
 }
 }
 
 
+// === Legalization examples ===
+
+TEST_F(FileTest, LegalizationExample0) {
+  runFileTest("legal-examples/0-copy-sbuf-ok.hlsl");
+}
+TEST_F(FileTest, LegalizationExample1) {
+  runFileTest("legal-examples/1-copy-global-static-ok.hlsl");
+}
+TEST_F(FileTest, LegalizationExample2) {
+  runFileTest("legal-examples/2-write-global-static-ok.hlsl");
+}
+TEST_F(FileTest, LegalizationExample3) {
+  runFileTest("legal-examples/3-copy-local-struct-ok.hlsl");
+}
+TEST_F(FileTest, LegalizationExample4) {
+  runFileTest("legal-examples/4-copy-local-nested-struct-ok.hlsl");
+}
+TEST_F(FileTest, LegalizationExample5) {
+  runFileTest("legal-examples/5-func-param-sbuf-ok.hlsl");
+}
+TEST_F(FileTest, LegalizationExample6) {
+  runFileTest("legal-examples/6-func-param-rwsbuf-ok.hlsl");
+}
+TEST_F(FileTest, LegalizationExample7) {
+  runFileTest("legal-examples/7-func-ret-tmp-var-ok.hlsl");
+}
+TEST_F(FileTest, LegalizationExample8) {
+  runFileTest("legal-examples/8-func-ret-direct-ok.hlsl");
+}
+TEST_F(FileTest, LegalizationExample9) {
+  runFileTest("legal-examples/9-if-stmt-select-fail.hlsl", Expect::ValFailure);
+}
+TEST_F(FileTest, LegalizationExample10) {
+  runFileTest("legal-examples/10-if-stmt-select-ok.hlsl");
+}
+TEST_F(FileTest, LegalizationExample11) {
+  runFileTest("legal-examples/11-if-stmt-const-ok.hlsl");
+}
+TEST_F(FileTest, LegalizationExample12) {
+  runFileTest("legal-examples/12-switch-stmt-select-fail.hlsl",
+              Expect::Failure);
+}
+TEST_F(FileTest, LegalizationExample13) {
+  runFileTest("legal-examples/13-switch-stmt-const-ok.hlsl");
+}
+TEST_F(FileTest, LegalizationExample14) {
+  runFileTest("legal-examples/14-loop-var-fail.hlsl", Expect::ValFailure);
+}
+TEST_F(FileTest, LegalizationExample15) {
+  runFileTest("legal-examples/15-loop-var-unroll-ok.hlsl");
+}
+TEST_F(FileTest, LegalizationExample16) {
+  runFileTest("legal-examples/16-loop-var-range-fail.hlsl", Expect::ValFailure);
+}
+TEST_F(FileTest, LegalizationExample17) {
+  runFileTest("legal-examples/17-loop-var-float-fail.hlsl", Expect::ValFailure);
+}
+TEST_F(FileTest, LegalizationExample18) {
+  runFileTest("legal-examples/18-multi-func-call-ok.hlsl");
+}
+TEST_F(FileTest, LegalizationExample19) {
+  runFileTest("legal-examples/19-multi-func-ret-fail.hlsl", Expect::ValFailure);
+}
+TEST_F(FileTest, LegalizationExample20) {
+  runFileTest("legal-examples/20-multi-func-ret-const-ok.hlsl");
+}
+TEST_F(FileTest, LegalizationExample21) {
+  runFileTest("legal-examples/21-combined-ok.hlsl");
+}
+
 } // namespace
 } // namespace

+ 48 - 12
tools/clang/unittests/SPIRV/FileTestFixture.cpp

@@ -93,6 +93,17 @@ void FileTest::runFileTest(llvm::StringRef filename, Expect expect,
     // Run CHECK commands via effcee on disassembly.
     // Run CHECK commands via effcee on disassembly.
     result = effcee::Match(generatedSpirvAsm, checkCommands, options);
     result = effcee::Match(generatedSpirvAsm, checkCommands, options);
 
 
+    // Print effcee's error message (if any).
+    if (result.status() != effcee::Result::Status::Ok) {
+      fprintf(stderr, "%s\n", result.message().c_str());
+    }
+
+    // All checks must have passed.
+    ASSERT_EQ(result.status(), effcee::Result::Status::Ok);
+
+    if (runValidation)
+      EXPECT_TRUE(utils::validateSpirvBinary(
+          targetEnv, generatedBinary, relaxLogicalPointer, glLayout, dxLayout));
   } else if (expect == Expect::Warning) {
   } else if (expect == Expect::Warning) {
     ASSERT_TRUE(compileOk);
     ASSERT_TRUE(compileOk);
 
 
@@ -107,7 +118,18 @@ void FileTest::runFileTest(llvm::StringRef filename, Expect expect,
     // Run CHECK commands via effcee on warning messages.
     // Run CHECK commands via effcee on warning messages.
     result = effcee::Match(errorMessages, checkCommands, options);
     result = effcee::Match(errorMessages, checkCommands, options);
 
 
-  } else {
+    // Print effcee's error message (if any).
+    if (result.status() != effcee::Result::Status::Ok) {
+      fprintf(stderr, "%s\n", result.message().c_str());
+    }
+
+    // All checks must have passed.
+    ASSERT_EQ(result.status(), effcee::Result::Status::Ok);
+
+    if (runValidation)
+      EXPECT_TRUE(utils::validateSpirvBinary(
+          targetEnv, generatedBinary, relaxLogicalPointer, glLayout, dxLayout));
+  } else if (expect == Expect::Failure) {
     ASSERT_FALSE(compileOk);
     ASSERT_FALSE(compileOk);
 
 
     auto options = effcee::Options()
     auto options = effcee::Options()
@@ -116,20 +138,34 @@ void FileTest::runFileTest(llvm::StringRef filename, Expect expect,
 
 
     // Run CHECK commands via effcee on error messages.
     // Run CHECK commands via effcee on error messages.
     result = effcee::Match(errorMessages, checkCommands, options);
     result = effcee::Match(errorMessages, checkCommands, options);
-  }
 
 
-  // Print effcee's error message (if any).
-  if (result.status() != effcee::Result::Status::Ok) {
-    fprintf(stderr, "%s\n", result.message().c_str());
-  }
+    // Print effcee's error message (if any).
+    if (result.status() != effcee::Result::Status::Ok) {
+      fprintf(stderr, "%s\n", result.message().c_str());
+    }
 
 
-  // All checks must have passed.
-  ASSERT_EQ(result.status(), effcee::Result::Status::Ok);
+    // All checks must have passed.
+    ASSERT_EQ(result.status(), effcee::Result::Status::Ok);
+  } else {
+    ASSERT_TRUE(compileOk);
+
+    // Disassemble the generated SPIR-V binary.
+    ASSERT_TRUE(utils::disassembleSpirvBinary(
+        generatedBinary, &generatedSpirvAsm, true /* generateHeader */));
+
+    std::string valMessages;
+    EXPECT_FALSE(utils::validateSpirvBinary(targetEnv, generatedBinary,
+                                            relaxLogicalPointer, glLayout,
+                                            dxLayout, &valMessages));
+    auto options = effcee::Options()
+                       .SetChecksName(filename.str())
+                       .SetInputName("<val-message>");
+
+    // Run CHECK commands via effcee on error messages.
+    result = effcee::Match(valMessages, checkCommands, options);
 
 
-  // Run SPIR-V validation for successful compilations
-  if (runValidation && expect != Expect::Failure) {
-    EXPECT_TRUE(utils::validateSpirvBinary(
-        targetEnv, generatedBinary, relaxLogicalPointer, glLayout, dxLayout));
+    // All checks over validation message must have passed.
+    ASSERT_EQ(result.status(), effcee::Result::Status::Ok);
   }
   }
 }
 }
 
 

+ 4 - 3
tools/clang/unittests/SPIRV/FileTestFixture.h

@@ -21,9 +21,10 @@ class FileTest : public ::testing::Test {
 public:
 public:
   /// \brief Expected test result to be
   /// \brief Expected test result to be
   enum class Expect {
   enum class Expect {
-    Success, // Success (with or without warnings) - check disassembly
-    Warning, // Success (with warnings) - check warning message
-    Failure, // Failure (with errors) - check error message
+    Success,    // Success (with or without warnings) - check disassembly
+    Warning,    // Success (with warnings) - check warning message
+    Failure,    // Failure (with errors) - check error message
+    ValFailure, // Validation failure (with errors) - check error message
   };
   };
 
 
   FileTest()
   FileTest()

+ 14 - 9
tools/clang/unittests/SPIRV/FileTestUtils.cpp

@@ -35,16 +35,21 @@ bool disassembleSpirvBinary(std::vector<uint32_t> &binary,
 }
 }
 
 
 bool validateSpirvBinary(spv_target_env env, std::vector<uint32_t> &binary,
 bool validateSpirvBinary(spv_target_env env, std::vector<uint32_t> &binary,
-                         bool relaxLogicalPointer, bool glLayout,
-                         bool dxLayout) {
+                         bool relaxLogicalPointer, bool glLayout, bool dxLayout,
+                         std::string *message) {
   spvtools::ValidatorOptions options;
   spvtools::ValidatorOptions options;
   options.SetRelaxLogicalPointer(relaxLogicalPointer);
   options.SetRelaxLogicalPointer(relaxLogicalPointer);
   options.SetRelaxBlockLayout(!glLayout && !dxLayout);
   options.SetRelaxBlockLayout(!glLayout && !dxLayout);
   options.SetSkipBlockLayout(dxLayout);
   options.SetSkipBlockLayout(dxLayout);
   spvtools::SpirvTools spirvTools(env);
   spvtools::SpirvTools spirvTools(env);
-  spirvTools.SetMessageConsumer(
-      [](spv_message_level_t, const char *, const spv_position_t &,
-         const char *message) { fprintf(stdout, "%s\n", message); });
+  spirvTools.SetMessageConsumer([message](spv_message_level_t, const char *,
+                                          const spv_position_t &,
+                                          const char *msg) {
+    if (message)
+      *message = msg;
+    else
+      fprintf(stdout, "%s\n", msg);
+  });
   return spirvTools.Validate(binary.data(), binary.size(), options);
   return spirvTools.Validate(binary.data(), binary.size(), options);
 }
 }
 
 
@@ -143,10 +148,10 @@ bool runCompilerWithSpirvGeneration(const llvm::StringRef inputFilePath,
     CComPtr<IDxcIncludeHandler> pIncludeHandler;
     CComPtr<IDxcIncludeHandler> pIncludeHandler;
     HRESULT resultStatus;
     HRESULT resultStatus;
 
 
-    bool running_specific_opt_recipe = false;
+    bool requires_opt = false;
     for (const auto &arg : rest)
     for (const auto &arg : rest)
-      if (arg.substr(0, 8) == L"-Oconfig")
-        running_specific_opt_recipe = true;
+      if (arg == L"-O3" || arg.substr(0, 8) == L"-Oconfig")
+        requires_opt = true;
 
 
     std::vector<LPCWSTR> flags;
     std::vector<LPCWSTR> flags;
     flags.push_back(L"-E");
     flags.push_back(L"-E");
@@ -156,7 +161,7 @@ bool runCompilerWithSpirvGeneration(const llvm::StringRef inputFilePath,
     flags.push_back(L"-spirv");
     flags.push_back(L"-spirv");
     // Disable legalization and optimization for testing, unless the caller
     // Disable legalization and optimization for testing, unless the caller
     // wants to run a specific optimization recipe (with -Oconfig).
     // wants to run a specific optimization recipe (with -Oconfig).
-    if (!running_specific_opt_recipe)
+    if (!requires_opt)
       flags.push_back(L"-fcgl");
       flags.push_back(L"-fcgl");
     // Disable validation. We'll run it manually.
     // Disable validation. We'll run it manually.
     flags.push_back(L"-Vd");
     flags.push_back(L"-Vd");

+ 2 - 2
tools/clang/unittests/SPIRV/FileTestUtils.h

@@ -33,8 +33,8 @@ bool disassembleSpirvBinary(std::vector<uint32_t> &binary,
 /// \brief Runs the SPIR-V Tools validation on the given SPIR-V binary.
 /// \brief Runs the SPIR-V Tools validation on the given SPIR-V binary.
 /// Returns true if validation is successful; false otherwise.
 /// Returns true if validation is successful; false otherwise.
 bool validateSpirvBinary(spv_target_env, std::vector<uint32_t> &binary,
 bool validateSpirvBinary(spv_target_env, std::vector<uint32_t> &binary,
-                         bool relaxLogicalPointer, bool glLayout,
-                         bool dxLayout);
+                         bool relaxLogicalPointer, bool glLayout, bool dxLayout,
+                         std::string *message = nullptr);
 
 
 /// \brief Parses the Target Profile and Entry Point from the Run command
 /// \brief Parses the Target Profile and Entry Point from the Run command
 /// Returns the target profile, entry point, and the rest via arguments.
 /// Returns the target profile, entry point, and the rest via arguments.