[WHLSL] Not all functions should be able to run in all shader stages
authormmaxfield@apple.com <mmaxfield@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 25 Sep 2018 06:36:04 +0000 (06:36 +0000)
committermmaxfield@apple.com <mmaxfield@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 25 Sep 2018 06:36:04 +0000 (06:36 +0000)
https://bugs.webkit.org/show_bug.cgi?id=189121

Reviewed by Filip Pizlo.

ddx() and ddy() can only run in fragment shaders, and the barrier functions can only run in compute shaders.
ddx_coarse() & friends are currently implemented as just calling ddx(), so we only need to modify those functions
if/when we make them native.

The texture sampling functions that use implicit derivatives can run in any shader stage - they just use a LOD of
0. Therefore, those functions are unchanged.

* WebGPUShadingLanguageRI/All.js:
* WebGPUShadingLanguageRI/Intrinsics.js:
* WebGPUShadingLanguageRI/LateChecker.js:
(LateChecker.prototype._checkShaderType):
* WebGPUShadingLanguageRI/NativeFunc.js:
(NativeFunc):
(NativeFunc.prototype.get stage):
(NativeFunc.prototype.toDeclString):
* WebGPUShadingLanguageRI/Parse.js:
(parseFuncDecl):
(parseNativeFunc):
(parseNative):
* WebGPUShadingLanguageRI/Prepare.js:
(let.prepare):
* WebGPUShadingLanguageRI/SPIRV.html:
* WebGPUShadingLanguageRI/StandardLibrary.js:
(let.standardLibrary):
* WebGPUShadingLanguageRI/StatementCloner.js:
(StatementCloner.prototype.visitNativeFunc):
* WebGPUShadingLanguageRI/Test.html:
* WebGPUShadingLanguageRI/Test.js:
(tests.shaderStages):
* WebGPUShadingLanguageRI/WSyntaxError.js:
(WSyntaxError.prototype.toString):
(WSyntaxError):
* WebGPUShadingLanguageRI/index.html:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@236455 268f45cc-cd09-0410-ab3c-d52691b4dbfc

15 files changed:
Tools/ChangeLog
Tools/WebGPUShadingLanguageRI/All.js
Tools/WebGPUShadingLanguageRI/CheckNativeFuncStages.js [new file with mode: 0644]
Tools/WebGPUShadingLanguageRI/Intrinsics.js
Tools/WebGPUShadingLanguageRI/LateChecker.js
Tools/WebGPUShadingLanguageRI/NativeFunc.js
Tools/WebGPUShadingLanguageRI/Parse.js
Tools/WebGPUShadingLanguageRI/Prepare.js
Tools/WebGPUShadingLanguageRI/SPIRV.html
Tools/WebGPUShadingLanguageRI/StandardLibrary.js
Tools/WebGPUShadingLanguageRI/StatementCloner.js
Tools/WebGPUShadingLanguageRI/Test.html
Tools/WebGPUShadingLanguageRI/Test.js
Tools/WebGPUShadingLanguageRI/WSyntaxError.js
Tools/WebGPUShadingLanguageRI/index.html

index 7b50be2..b993618 100644 (file)
@@ -1,3 +1,44 @@
+2018-09-24  Myles C. Maxfield  <mmaxfield@apple.com>
+
+        [WHLSL] Not all functions should be able to run in all shader stages
+        https://bugs.webkit.org/show_bug.cgi?id=189121
+
+        Reviewed by Filip Pizlo.
+
+        ddx() and ddy() can only run in fragment shaders, and the barrier functions can only run in compute shaders.
+        ddx_coarse() & friends are currently implemented as just calling ddx(), so we only need to modify those functions
+        if/when we make them native.
+
+        The texture sampling functions that use implicit derivatives can run in any shader stage - they just use a LOD of
+        0. Therefore, those functions are unchanged.
+
+        * WebGPUShadingLanguageRI/All.js:
+        * WebGPUShadingLanguageRI/Intrinsics.js:
+        * WebGPUShadingLanguageRI/LateChecker.js:
+        (LateChecker.prototype._checkShaderType):
+        * WebGPUShadingLanguageRI/NativeFunc.js:
+        (NativeFunc):
+        (NativeFunc.prototype.get stage):
+        (NativeFunc.prototype.toDeclString):
+        * WebGPUShadingLanguageRI/Parse.js:
+        (parseFuncDecl):
+        (parseNativeFunc):
+        (parseNative):
+        * WebGPUShadingLanguageRI/Prepare.js:
+        (let.prepare):
+        * WebGPUShadingLanguageRI/SPIRV.html:
+        * WebGPUShadingLanguageRI/StandardLibrary.js:
+        (let.standardLibrary):
+        * WebGPUShadingLanguageRI/StatementCloner.js:
+        (StatementCloner.prototype.visitNativeFunc):
+        * WebGPUShadingLanguageRI/Test.html:
+        * WebGPUShadingLanguageRI/Test.js:
+        (tests.shaderStages):
+        * WebGPUShadingLanguageRI/WSyntaxError.js:
+        (WSyntaxError.prototype.toString):
+        (WSyntaxError):
+        * WebGPUShadingLanguageRI/index.html:
+
 2018-09-24  Thomas Denney  <tdenney@apple.com>
 
         [WHLSL] Implement trap statements in Metal code generation
index 4dbeeb8..9046ed4 100644 (file)
@@ -56,6 +56,7 @@ load("Casts.js");
 load("Check.js");
 load("CheckLiteralTypes.js");
 load("CheckLoops.js");
+load("CheckNativeFuncStages.js");
 load("CheckRecursion.js");
 load("CheckRecursiveTypes.js");
 load("CheckReturns.js");
diff --git a/Tools/WebGPUShadingLanguageRI/CheckNativeFuncStages.js b/Tools/WebGPUShadingLanguageRI/CheckNativeFuncStages.js
new file mode 100644 (file)
index 0000000..c3c047f
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+"use strict";
+
+function checkNativeFuncStages(program)
+{
+    class CheckNativeFuncStages extends Visitor {
+        constructor(entryPoint) {
+            super();
+            this._entryPoint = entryPoint;
+        }
+
+        visitCallExpression(node)
+        {
+            if ((node.func instanceof NativeFunc) && node.func.stage && node.func.stage != this._entryPoint.shaderType)
+                throw new WTypeError(node.origin, `Cannot call ${node.func.stage} function ${node.func.name} inside ${this._entryPoint.shadeType} entry point`);
+            node.func.visit(this);
+        }
+    }
+    for (let [name, funcDefs] of program.functions) {
+        for (let funcDef of funcDefs) {
+            if (funcDef.isEntryPoint)
+                funcDef.visit(new CheckNativeFuncStages(funcDef));
+        }
+    }
+}
index 47373ea..04e4b78 100644 (file)
@@ -715,13 +715,13 @@ class Intrinsics {
             });
 
         this._map.set(
-            "native float ddx(float)",
+            "native fragment float ddx(float)",
             func => {
                 func.implementation = ([value]) => EPtr.box(0);
             });
 
         this._map.set(
-            "native float ddy(float)",
+            "native fragment float ddy(float)",
             func => {
                 func.implementation = ([value]) => EPtr.box(0);
             });
@@ -822,19 +822,19 @@ class Intrinsics {
             });
 
         this._map.set(
-            "native void AllMemoryBarrierWithGroupSync()",
+            "native compute void AllMemoryBarrierWithGroupSync()",
             func => {
                 func.implementation = function() {};
             });
 
         this._map.set(
-            "native void DeviceMemoryBarrierWithGroupSync()",
+            "native compute void DeviceMemoryBarrierWithGroupSync()",
             func => {
                 func.implementation = function() {};
             });
 
         this._map.set(
-            "native void GroupMemoryBarrierWithGroupSync()",
+            "native compute void GroupMemoryBarrierWithGroupSync()",
             func => {
                 func.implementation = function() {};
             });
index eb1ce91..e54c7f3 100644 (file)
@@ -72,6 +72,8 @@ class LateChecker extends Visitor {
             break;
         case "compute":
             break;
+        case "compute":
+            break;
         case "test":
             break;
         default:
index f086534..5b06918 100644 (file)
 "use strict";
 
 class NativeFunc extends Func {
-    constructor(origin, name, returnType, parameters, isCast, shaderType)
+    constructor(origin, name, returnType, parameters, isCast, shaderType, stage = null)
     {
         super(origin, name, returnType, parameters, isCast, shaderType);
+        this._stage = stage;
         this.isRestricted = false;
         this.implementation = null;
         this._implementationData = null;
@@ -35,13 +36,18 @@ class NativeFunc extends Func {
     }
 
     get isNative() { return true; }
+    get stage() { return this._stage; }
 
     get implementationData() { return this._implementationData; }
     set implementationData(newImplData) { this._implementationData = newImplData; }
 
     toDeclString()
     {
-        return "native " + super.toDeclString();
+        let result = "native ";
+        if (this.stage)
+            result += `${this.stage} `;
+        result += super.toDeclString();
+        return result;
     }
 }
 
index 3df6d21..4a7a240 100644 (file)
@@ -1290,7 +1290,7 @@ function parse(program, origin, originKind, lineNumberOffset, text)
         return result;
     }
 
-    function parseNativeFunc()
+    function parseNativeFunc(stage = null)
     {
         let func = parseFuncDecl();
         if (func instanceof WSyntaxError)
@@ -1300,7 +1300,7 @@ function parse(program, origin, originKind, lineNumberOffset, text)
         let maybeError = consume(";");
         if (maybeError instanceof WSyntaxError)
             return maybeError;
-        return new NativeFunc(func.origin, func.name, func.returnType, func.parameters, func.isCast, func.shaderType);
+        return new NativeFunc(func.origin, func.name, func.returnType, func.parameters, func.isCast, func.shaderType, stage);
     }
 
     function parseNative()
@@ -1320,7 +1320,10 @@ function parse(program, origin, originKind, lineNumberOffset, text)
                 return maybeError;
             return NativeType.create(origin, name.text, args);
         }
-        return parseNativeFunc();
+        let stage = tryConsume("vertex", "fragment", "compute");
+        if (stage)
+            stage = stage.text;
+        return parseNativeFunc(stage);
     }
 
     function parseRestrictedFuncDef()
index 9fe36ba..c1158cf 100644 (file)
@@ -70,6 +70,7 @@ let prepare = (() => {
         allocateAtEntryPoints(program);
         program.visit(new StructLayoutBuilder());
         lateCheckAndLayoutBuffers(program);
+        checkNativeFuncStages(program);
         if (shouldInline)
             inline(program);
         return program;
index 0a09ba0..ba6170b 100644 (file)
@@ -39,6 +39,7 @@ td {
     <script src="Check.js"></script>
     <script src="CheckLiteralTypes.js"></script>
     <script src="CheckLoops.js"></script>
+    <script src="CheckNativeFuncStages.js"></script>
     <script src="CheckRecursion.js"></script>
     <script src="CheckRecursiveTypes.js"></script>
     <script src="CheckReturns.js"></script>
index 9662cad..fea56ec 100644 (file)
@@ -803,7 +803,8 @@ let standardLibrary = (function() {
         // These functions are unary floating-point scalar functions,
         // which can also be applied to vectors and matrices component-wise.
         (function() {
-            let nativeFunctions = [`cos`, `sin`, `tan`, `acos`, `asin`, `atan`, `cosh`, `sinh`, `tanh`, `ceil`, `exp`, `floor`, `log`, `round`, `trunc`, `ddx`, `ddy`];
+            let nativeFunctions = [`cos`, `sin`, `tan`, `acos`, `asin`, `atan`, `cosh`, `sinh`, `tanh`, `ceil`, `exp`, `floor`, `log`, `round`, `trunc`];
+            let nativeFragmentFunctions = [`ddx`, `ddy`];
             let nonNativeFunctions = [`sqrt`, `log2`, `log10`, `frac`, `exp2`, `degrees`, `radians`, `rcp`, `rsqrt`, `saturate`, `ddx_coarse`, `ddx_fine`, `ddy_coarse`, `ddy_fine`, `fwidth`];
         
             for (let nativeFunction of nativeFunctions) {
@@ -813,6 +814,13 @@ let standardLibrary = (function() {
                 print(`}`);
             }
         
+            for (let nativeFunction of nativeFragmentFunctions) {
+                print(`native fragment float ${nativeFunction}(float);`);
+                print(`half ${nativeFunction}(half x) {`);
+                print(`    return half(${nativeFunction}(float(x)));`);
+                print(`}`);
+            }
+        
             for (let type of [`half`, `float`]) {
                 print(`${type} sqrt(${type} x) {`);
                 print(`    return pow(x, 0.5);`);
@@ -860,7 +868,7 @@ let standardLibrary = (function() {
                 print(`    return abs(ddx(x)) + abs(ddy(x));`);
                 print(`}`);
         
-                for (let outputFunction of nativeFunctions.concat(nonNativeFunctions)) {
+                for (let outputFunction of nativeFunctions.concat(nativeFragmentFunctions.concat(nonNativeFunctions))) {
                     for (let size of [2, 3, 4]) {
                         print(`${type}${size} ${outputFunction}(${type}${size} x) {`);
                         print(`    ${type}${size} result;`);
@@ -1786,9 +1794,9 @@ let standardLibrary = (function() {
         }
         print();
         
-        print(`native void AllMemoryBarrierWithGroupSync();`);
-        print(`native void DeviceMemoryBarrierWithGroupSync();`);
-        print(`native void GroupMemoryBarrierWithGroupSync();`);
+        print(`native compute void AllMemoryBarrierWithGroupSync();`);
+        print(`native compute void DeviceMemoryBarrierWithGroupSync();`);
+        print(`native compute void GroupMemoryBarrierWithGroupSync();`);
         print();
         
         for (let type of [`uchar`, `ushort`, `uint`, `char`, `short`, `int`, `half`, `float`]) {
@@ -1910,6 +1918,8 @@ let standardLibrary = (function() {
         }
         print();
 
+        // You might think that the sampling functions that rely on implicit derivatives can't be called in vertex shaders.
+        // However, they do work; they just a level of 0.
         for (let type of [`uchar`, `ushort`, `uint`, `char`, `short`, `int`, `half`, `float`]) {
             for (let length of [``, `2`, `3`, `4`]) {
                 print(`native ${type}${length} Sample(Texture1D<${type}${length}>, sampler, float location);`);
index 472e7ac..c4bddc9 100644 (file)
@@ -46,7 +46,7 @@ class StatementCloner extends Rewriter {
             node.origin, node.name,
             node.returnType.visit(this),
             node.parameters.map(parameter => parameter.visit(this)),
-            node.isCast, node.shaderType);
+            node.isCast, node.shaderType, node.stage);
         result.isRestricted = node.isRestricted;
         return result;
     }
index a300dac..0285fbe 100644 (file)
@@ -33,6 +33,7 @@
 <script src="Check.js"></script>
 <script src="CheckLiteralTypes.js"></script>
 <script src="CheckLoops.js"></script>
+<script src="CheckNativeFuncStages.js"></script>
 <script src="CheckRecursion.js"></script>
 <script src="CheckRecursiveTypes.js"></script>
 <script src="CheckReturns.js"></script>
index 9612f6d..c94b352 100644 (file)
@@ -5753,6 +5753,132 @@ tests.andReturnedArrayRef = function()
     checkInt(program, callFunction(program, "foo", []), 354);
 }
 
+tests.shaderStages = function()
+{
+    doPrep(`
+        struct Result {
+            float4 output;
+        }
+        fragment Result foo()
+        {
+            float x = 7;
+            float dx = ddx(x);
+            float dy = ddy(x);
+            Result r;
+            r.output = float4(1, 2, 3, 4);
+            return r;
+        }
+    `);
+    doPrep(`
+        compute void foo()
+        {
+            AllMemoryBarrierWithGroupSync();
+            DeviceMemoryBarrierWithGroupSync();
+            GroupMemoryBarrierWithGroupSync();
+        }
+    `);
+    checkFail(
+        () => doPrep(`
+            struct Result {
+                float4 output;
+            }
+            vertex Result foo()
+            {
+                float x = 7;
+                float dx = ddx(x);
+                float dy = ddy(x);
+                Result r;
+                r.output = float4(1, 2, 3, 4);
+                return r;
+            }
+        `),
+        e => e instanceof WTypeError);
+    checkFail(
+        () => doPrep(`
+            struct Result {
+                float4 output;
+            }
+            vertex Result foo()
+            {
+                AllMemoryBarrierWithGroupSync();
+                Result r;
+                r.output = float4(1, 2, 3, 4);
+                return r;
+            }
+        `),
+        e => e instanceof WTypeError);
+    checkFail(
+        () => doPrep(`
+            struct Result {
+                float4 output;
+            }
+            fragment Result foo()
+            {
+                AllMemoryBarrierWithGroupSync();
+                Result r;
+                r.output = float4(1, 2, 3, 4);
+                return r;
+            }
+        `),
+        e => e instanceof WTypeError);
+    checkFail(
+        () => doPrep(`
+            struct Result {
+                float4 output;
+            }
+            vertex Result foo()
+            {
+                DeviceMemoryBarrierWithGroupSync();
+                Result r;
+                r.output = float4(1, 2, 3, 4);
+                return r;
+            }
+        `),
+        e => e instanceof WTypeError);
+    checkFail(
+        () => doPrep(`
+            struct Result {
+                float4 output;
+            }
+            fragment Result foo()
+            {
+                DeviceMemoryBarrierWithGroupSync();
+                Result r;
+                r.output = float4(1, 2, 3, 4);
+                return r;
+            }
+        `),
+        e => e instanceof WTypeError);
+    checkFail(
+        () => doPrep(`
+            struct Result {
+                float4 output;
+            }
+            vertex Result foo()
+            {
+                GroupMemoryBarrierWithGroupSync();
+                Result r;
+                r.output = float4(1, 2, 3, 4);
+                return r;
+            }
+        `),
+        e => e instanceof WTypeError);
+    checkFail(
+        () => doPrep(`
+            struct Result {
+                float4 output;
+            }
+            fragment Result foo()
+            {
+                GroupMemoryBarrierWithGroupSync();
+                Result r;
+                r.output = float4(1, 2, 3, 4);
+                return r;
+            }
+        `),
+        e => e instanceof WTypeError);
+}
+
 tests.casts = function()
 {
     let program = doPrep(`
index 781b0ee..85b5465 100644 (file)
@@ -31,5 +31,10 @@ class WSyntaxError {
         this.originString = originString;
         this.syntaxErrorMessage = message;
     }
+
+    toString()
+    {
+        return this.payload;
+    }
 }
 
index 41693d7..ae01100 100644 (file)
@@ -33,6 +33,7 @@
 <script src="Check.js"></script>
 <script src="CheckLiteralTypes.js"></script>
 <script src="CheckLoops.js"></script>
+<script src="CheckNativeFuncStages.js"></script>
 <script src="CheckRecursion.js"></script>
 <script src="CheckRecursiveTypes.js"></script>
 <script src="CheckReturns.js"></script>