[WHLSL] Metal code generation
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 21 Sep 2018 02:06:43 +0000 (02:06 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 21 Sep 2018 02:06:43 +0000 (02:06 +0000)
https://bugs.webkit.org/show_bug.cgi?id=187735

Patch by Thomas Denney <tdenney@apple.com> on 2018-09-20
Reviewed by Myles C. Maxfield.

Adds support for generating Metal Shading Language from WHLSL. Clients
should include the file MetalCodegenAll.js and then call whlslToMsl
with their program source code to compile to Metal.

* WebGPUShadingLanguageRI/ArrayType.js:
(ArrayType.prototype.get arrayRefType): Adds the arrayRefType method to
all types to find the type of that expression when it is used in a
MakeArrayRefExpression.
* WebGPUShadingLanguageRI/MakeArrayRefExpression.js:
(MakeArrayRefExpression):
(MakeArrayRefExpression.prototype.get type): Uses the new arrayRefType
getter on all types to find the type of the expression.
* WebGPUShadingLanguageRI/Metal/MSLBackend.js: Added.
* WebGPUShadingLanguageRI/Metal/MSLCodegenAll.js: Added.
* WebGPUShadingLanguageRI/Metal/MSLCompileResult.js: Added.
* WebGPUShadingLanguageRI/Metal/MSLConstexprEmitter.js: Added.
* WebGPUShadingLanguageRI/Metal/MSLFunctionDeclaration.js: Added.
* WebGPUShadingLanguageRI/Metal/MSLFunctionDefinition.js: Added.
* WebGPUShadingLanguageRI/Metal/MSLFunctionForwardDeclaration.js: Added.
* WebGPUShadingLanguageRI/Metal/MSLNameMangler.js: Added.
* WebGPUShadingLanguageRI/Metal/MSLNativeFunctionCall.js: Added.
* WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js: Added.
* WebGPUShadingLanguageRI/Metal/MSLTypeAttributes.js: Added.
* WebGPUShadingLanguageRI/Metal/MSLTypeAttributesMap.js: Added.
* WebGPUShadingLanguageRI/Metal/MSLTypeUnifier.js: Added.
* WebGPUShadingLanguageRI/Metal/TypeOf.js: Added.
* WebGPUShadingLanguageRI/Metal/WhlslToMsl.js: Added.
* WebGPUShadingLanguageRI/PropertyResolver.js:
* WebGPUShadingLanguageRI/SynthesizeStructAccessors.js:
* WebGPUShadingLanguageRI/Test.js: Added awkward tests for the compiler
to generate code for.
(tests.incrementAndDecrement):
(tests.returnIntLiteralUint):
(tests.returnIntLiteralFloat):
(tests.nestedSubscriptWithArraysInStructs):
(tests.nestedSubscript):
(tests.lotsOfLocalVariables):
* WebGPUShadingLanguageRI/Type.js:
(Type.prototype.get arrayRefType): See above.

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

22 files changed:
Tools/ChangeLog
Tools/WebGPUShadingLanguageRI/ArrayType.js
Tools/WebGPUShadingLanguageRI/MakeArrayRefExpression.js
Tools/WebGPUShadingLanguageRI/Metal/MSLBackend.js [new file with mode: 0644]
Tools/WebGPUShadingLanguageRI/Metal/MSLCodegenAll.js [new file with mode: 0644]
Tools/WebGPUShadingLanguageRI/Metal/MSLCompileResult.js [new file with mode: 0644]
Tools/WebGPUShadingLanguageRI/Metal/MSLConstexprEmitter.js [new file with mode: 0644]
Tools/WebGPUShadingLanguageRI/Metal/MSLFunctionDeclaration.js [new file with mode: 0644]
Tools/WebGPUShadingLanguageRI/Metal/MSLFunctionDefinition.js [new file with mode: 0644]
Tools/WebGPUShadingLanguageRI/Metal/MSLFunctionForwardDeclaration.js [new file with mode: 0644]
Tools/WebGPUShadingLanguageRI/Metal/MSLNameMangler.js [new file with mode: 0644]
Tools/WebGPUShadingLanguageRI/Metal/MSLNativeFunctionCall.js [new file with mode: 0644]
Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js [new file with mode: 0644]
Tools/WebGPUShadingLanguageRI/Metal/MSLTypeAttributes.js [new file with mode: 0644]
Tools/WebGPUShadingLanguageRI/Metal/MSLTypeAttributesMap.js [new file with mode: 0644]
Tools/WebGPUShadingLanguageRI/Metal/MSLTypeUnifier.js [new file with mode: 0644]
Tools/WebGPUShadingLanguageRI/Metal/TypeOf.js [new file with mode: 0644]
Tools/WebGPUShadingLanguageRI/Metal/WhlslToMsl.js [new file with mode: 0644]
Tools/WebGPUShadingLanguageRI/PropertyResolver.js
Tools/WebGPUShadingLanguageRI/SynthesizeStructAccessors.js
Tools/WebGPUShadingLanguageRI/Test.js
Tools/WebGPUShadingLanguageRI/Type.js

index 4d55d81..c45d35f 100644 (file)
@@ -1,3 +1,50 @@
+2018-09-20  Thomas Denney  <tdenney@apple.com>
+
+        [WHLSL] Metal code generation
+        https://bugs.webkit.org/show_bug.cgi?id=187735
+
+        Reviewed by Myles C. Maxfield.
+
+        Adds support for generating Metal Shading Language from WHLSL. Clients
+        should include the file MetalCodegenAll.js and then call whlslToMsl
+        with their program source code to compile to Metal.
+
+        * WebGPUShadingLanguageRI/ArrayType.js:
+        (ArrayType.prototype.get arrayRefType): Adds the arrayRefType method to
+        all types to find the type of that expression when it is used in a
+        MakeArrayRefExpression.
+        * WebGPUShadingLanguageRI/MakeArrayRefExpression.js:
+        (MakeArrayRefExpression):
+        (MakeArrayRefExpression.prototype.get type): Uses the new arrayRefType
+        getter on all types to find the type of the expression.
+        * WebGPUShadingLanguageRI/Metal/MSLBackend.js: Added.
+        * WebGPUShadingLanguageRI/Metal/MSLCodegenAll.js: Added.
+        * WebGPUShadingLanguageRI/Metal/MSLCompileResult.js: Added.
+        * WebGPUShadingLanguageRI/Metal/MSLConstexprEmitter.js: Added.
+        * WebGPUShadingLanguageRI/Metal/MSLFunctionDeclaration.js: Added.
+        * WebGPUShadingLanguageRI/Metal/MSLFunctionDefinition.js: Added.
+        * WebGPUShadingLanguageRI/Metal/MSLFunctionForwardDeclaration.js: Added.
+        * WebGPUShadingLanguageRI/Metal/MSLNameMangler.js: Added.
+        * WebGPUShadingLanguageRI/Metal/MSLNativeFunctionCall.js: Added.
+        * WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js: Added.
+        * WebGPUShadingLanguageRI/Metal/MSLTypeAttributes.js: Added.
+        * WebGPUShadingLanguageRI/Metal/MSLTypeAttributesMap.js: Added.
+        * WebGPUShadingLanguageRI/Metal/MSLTypeUnifier.js: Added.
+        * WebGPUShadingLanguageRI/Metal/TypeOf.js: Added.
+        * WebGPUShadingLanguageRI/Metal/WhlslToMsl.js: Added.
+        * WebGPUShadingLanguageRI/PropertyResolver.js:
+        * WebGPUShadingLanguageRI/SynthesizeStructAccessors.js:
+        * WebGPUShadingLanguageRI/Test.js: Added awkward tests for the compiler
+        to generate code for.
+        (tests.incrementAndDecrement):
+        (tests.returnIntLiteralUint):
+        (tests.returnIntLiteralFloat):
+        (tests.nestedSubscriptWithArraysInStructs):
+        (tests.nestedSubscript):
+        (tests.lotsOfLocalVariables):
+        * WebGPUShadingLanguageRI/Type.js:
+        (Type.prototype.get arrayRefType): See above.
+
 2018-09-20  Ryan Haddad  <ryanhaddad@apple.com>
 
         Bring up queues for iOS 12
index 0d56a0b..b9d918e 100644 (file)
@@ -48,6 +48,11 @@ class ArrayType extends Type {
         return this.numElements.value;
     }
 
+    get arrayRefType()
+    {
+        return new ArrayRefType(this.origin, "thread", this.elementType);
+    }
+
     toString()
     {
         return this.elementType + "[" + this.numElements + "]";
index 0cc6f6e..6eb6f27 100644 (file)
@@ -29,12 +29,13 @@ class MakeArrayRefExpression extends Expression {
     {
         super(origin);
         this._lValue = lValue;
-        if (this.lValue.variable && this.lValue.variable.type && this.lValue.variable.type.isArray && this.lValue.variable.type.elementType) {
-            this._type = new ArrayRefType(origin, "thread", this.lValue.variable.type.elementType);
-        }
     }
 
-    get type() { return this._type; }
+    get type()
+    {
+        return typeOf(this.lValue).arrayRefType;
+    }
+
     get lValue() { return this._lValue; }
 
     toString()
diff --git a/Tools/WebGPUShadingLanguageRI/Metal/MSLBackend.js b/Tools/WebGPUShadingLanguageRI/Metal/MSLBackend.js
new file mode 100644 (file)
index 0000000..5030a9d
--- /dev/null
@@ -0,0 +1,420 @@
+/*
+ * 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.
+ */
+
+const DefaultMetalSource = `#include <metal_stdlib>
+using namespace metal;
+
+`;
+
+// Handles the compilation of Program AST instances to MSLCompileResult instances, which
+// include the raw MSL. In general clients should call |whlslToMsl|, which parses,
+// typechecks, and inlines the WHLSL before passing it to this compiler.
+class MSLBackend {
+
+    constructor(program)
+    {
+        this._program = program;
+        this._declarations = [];
+        this._funcNameMangler = new MSLNameMangler("F");
+        this._typeUnifier = new MSLTypeUnifier();
+        this._forwardTypeDecls = [];
+        this._typeDefinitions = [];
+        this._forwardFunctionDecls = [];
+        this._functionDefintions = [];
+    }
+
+    get program()
+    {
+        return this._program;
+    }
+
+    get declarations()
+    {
+        return this._declarations;
+    }
+
+    get functionSources()
+    {
+        return this._functionSources;
+    }
+
+    compile()
+    {
+        try {
+            const src = this._msl();
+            const mangledMap = {};
+            for (let func of this._functionDefintions) {
+                const key = func.func.name;
+                const value = this._funcNameMangler.mangle(func.func);
+                mangledMap[key] = value;
+            }
+
+            return new MSLCompileResult(src, null, mangledMap, this._functionSources);
+        } catch (e) {
+            return new MSLCompileResult(null, e, null, null);
+        }
+    }
+
+    _msl()
+    {
+        const functionsToCompile = this._findFunctionsToCompile();
+        for (let func of functionsToCompile)
+            func.visit(this._typeUnifier);
+
+        this._allTypeAttributes = new MSLTypeAttributesMap(functionsToCompile, this._typeUnifier);
+        this._createTypeDecls();
+        this._createFunctionDecls(functionsToCompile);
+
+        let outputStr = DefaultMetalSource;
+
+        const addSection = (title, decls) => {
+            outputStr += `#pragma mark - ${title}\n\n${decls.join("\n\n")}\n\n`;
+        };
+
+        addSection("Forward type declarations", this._forwardTypeDecls);
+        addSection("Type definitions", this._typeDefinitions);
+        addSection("Forward function declarations", this._forwardFunctionDecls);
+        addSection("Function definitions", this._functionDefintions);
+
+        if (!this._allowComments)
+            outputStr = this._removeCommentsAndEmptyLines(outputStr);
+
+        return outputStr;
+    }
+
+    _findFunctionsToCompile()
+    {
+        const entryPointFunctions = [];
+        for (let [name, instances] of this._program.functions) {
+            for (let instance of instances) {
+                if (instance.isEntryPoint)
+                    entryPointFunctions.push(instance);
+            }
+        }
+        const functions = new Set(entryPointFunctions);
+
+        class FindFunctionsThatGetCalled extends Visitor {
+            visitCallExpression(node)
+            {
+                super.visitCallExpression(node);
+                if (node.func instanceof FuncDef) {
+                    functions.add(node.func);
+                    node.func.visit(this);
+                }
+            }
+        }
+        const findFunctionsThatGetCalledVisitor = new FindFunctionsThatGetCalled();
+        for (let entryPoint of entryPointFunctions)
+            entryPoint.visit(findFunctionsThatGetCalledVisitor);
+        return Array.from(functions);
+    }
+
+    _createTypeDecls()
+    {
+        const typesThatNeedDeclaration = this._typeUnifier.typesThatNeedDeclaration();
+        const typeDeclsInOrderInTypeDefOrder = this._sortTypeDefsAndForwardDeclarationsInTopologicalOrder(typesThatNeedDeclaration);
+        for (let type of typeDeclsInOrderInTypeDefOrder) {
+            if (type instanceof StructType)
+                this._forwardTypeDecls.push(this._metalSourceForStructForwardDeclaration(type));
+            else if (type instanceof ArrayRefType)
+                this._forwardTypeDecls.push(this._metalSourceForArrayRefForwardDeclaration(type));
+            else
+                this._forwardTypeDecls.push(this._metalSourceForTypeDef(type));
+        }
+
+        const typeDeclsThatNeedDeclarationInOrder = this._sortTypeDeclarationsInTopologicalOrder(typesThatNeedDeclaration);
+        for (let type of typeDeclsThatNeedDeclarationInOrder) {
+            if (type instanceof StructType)
+                this._typeDefinitions.push(this._metalSourceForStructDefinition(type));
+            else if (type instanceof ArrayRefType)
+                this._typeDefinitions.push(this._metalSourceForArrayRefDefinition(type));
+        }
+    }
+
+    _createFunctionDecls(unifiedFunctionDefs)
+    {
+        for (let func of unifiedFunctionDefs) {
+            this._forwardFunctionDecls.push(new MSLFunctionForwardDeclaration(this._funcNameMangler, func, this._typeUnifier, this._allTypeAttributes));
+            this._functionDefintions.push(new MSLFunctionDefinition(this._funcNameMangler, func, this._typeUnifier, this._allTypeAttributes));
+        }
+    }
+
+    _sortTypeDefsAndForwardDeclarationsInTopologicalOrder(typesToDeclare)
+    {
+        // All types are sorted in topological order in this method; every type needs a typedef.
+        // We do not recurse into structs becasue this is *just* for forward declarations.
+        const declarations = new Array();
+        for (let type of typesToDeclare)
+            declarations.push(type);
+
+        let typeOrder = [];
+        let visitedSet = new Set();
+        const typeUnifier = this._typeUnifier;
+        class TypeOrderer extends Visitor {
+            _visitType(node, visitCallback)
+            {
+                const id = typeUnifier.uniqueTypeId(node);
+                if (!visitedSet.has(id)) {
+                    visitedSet.add(id);
+                    visitCallback();
+                    typeOrder.push(node);
+                }
+            }
+
+            visitNativeType(node)
+            {
+                this._visitType(node, () => {});
+            }
+
+            visitEnumType(node)
+            {
+                node.baseType.visit(this);
+            }
+
+            visitArrayType(node)
+            {
+                this._visitType(node, () => super.visitArrayType(node));
+            }
+
+            visitVectorType(node)
+            {
+                this._visitType(node, () => {});
+            }
+
+            visitMatrixType(node)
+            {
+                this._visitType(node, () => {});
+            }
+
+            visitStructType(node)
+            {
+                // Don't need to recurse into elements for the forward declarations.
+                this._visitType(node, () => {});
+            }
+
+            visitArrayRefType(node)
+            {
+                this._visitType(node, () => super.visitArrayRefType(node));
+            }
+
+            visitReferenceType(node)
+            {
+                this._visitType(node, () => super.visitReferenceType(node));
+            }
+
+            visitTypeRef(node)
+            {
+                super.visitTypeRef(node);
+                node.type.visit(this);
+            }
+        }
+        const typeOrderer = new TypeOrderer();
+        for (let type of typesToDeclare)
+            type.visit(typeOrderer);
+        return typeOrder;
+    }
+
+    _sortTypeDeclarationsInTopologicalOrder(typesToDeclare)
+    {
+        const declarations = new Array();
+        for (let type of typesToDeclare)
+            declarations.push(type);
+
+        let typeOrder = [];
+        let visitedSet = new Set();
+        const typeUnifier = this._typeUnifier;
+        class TypeOrderer extends Visitor {
+            _visitType(node, visitCallback)
+            {
+                const id = typeUnifier.uniqueTypeId(node);
+                if (!visitedSet.has(id)) {
+                    visitedSet.add(id);
+                    visitCallback();
+                    typeOrder.push(node);
+                }
+            }
+
+            visitStructType(node)
+            {
+                this._visitType(node, () => super.visitStructType(node));
+            }
+
+            visitTypeRef(node)
+            {
+                super.visitTypeRef(node);
+                node.type.visit(this);
+            }
+
+            visitArrayRefType(node)
+            {
+                this._visitType(node, () => super.visitArrayRefType(node));
+            }
+
+            visitReferenceType(node)
+            {
+                // We need an empty implementation to avoid recursion.
+            }
+        }
+        const typeOrderer = new TypeOrderer();
+        for (let type of typesToDeclare)
+            type.visit(typeOrderer);
+
+        const typeOrderMap = new Map();
+        for (let i = 0; i < typeOrder.length; i++)
+            typeOrderMap.set(typeOrder[i], i);
+
+        return typeOrder;
+    }
+
+    // Also removes #pragma marks.
+    _removeCommentsAndEmptyLines(src)
+    {
+        const singleLineCommentRegex = /(\/\/|\#pragma)(.*?)($|\n)/;
+        const lines = src.split('\n')
+            .map(line => line.replace(singleLineCommentRegex, '').trimEnd())
+            .filter(line => line.length > 0);
+        return lines.join('\n');
+    }
+
+    _metalSourceForArrayRefDefinition(arrayRefType)
+    {
+        let src = `struct ${this._typeUnifier.uniqueTypeId(arrayRefType)} {\n`;
+        const fakePtrType = new PtrType(arrayRefType.origin, arrayRefType.addressSpace, arrayRefType.elementType);
+        src += `    ${this._typeUnifier.uniqueTypeId(fakePtrType)} ptr;\n`
+        src += "    uint32_t length;\n";
+        src += "};";
+        return src;
+    }
+
+    _metalSourceForArrayRefForwardDeclaration(arrayRefType)
+    {
+        return `struct ${this._typeUnifier.uniqueTypeId(arrayRefType)};`;
+    }
+
+    _metalSourceForStructForwardDeclaration(structType)
+    {
+        return `struct ${this._typeUnifier.uniqueTypeId(structType)};`;
+    }
+
+    _metalSourceForStructDefinition(structType)
+    {
+        const structTypeAttributes = this._allTypeAttributes.attributesForType(structType);
+        let src = `struct ${this._typeUnifier.uniqueTypeId(structType)} {\n`;
+
+        let index = 0;
+        for (let [fieldName, field] of structType.fieldMap) {
+            const mangledFieldName = structTypeAttributes.mangledFieldName(fieldName);
+            src += `    ${this._typeUnifier.uniqueTypeId(field.type)} ${mangledFieldName}`;
+
+            const annotations = [];
+            if (structTypeAttributes.isVertexAttribute)
+                annotations.push(`attribute(${index++})`);
+            if (structTypeAttributes.isVertexOutputOrFragmentInput && fieldName === "wsl_Position")
+                annotations.push("position");
+            if (structTypeAttributes.isFragmentOutput && fieldName === "wsl_Color")
+                annotations.push("color(0)");
+            if (annotations.length)
+                src += ` [[${annotations.join(", ")}]]`;
+            src += `; // ${fieldName} (${field.type}) \n`;
+        }
+
+        src += "};";
+
+        return src;
+    }
+
+    _metalSourceForTypeDef(node)
+    {
+        const name = this._typeUnifier.uniqueTypeId(node);
+        if (node.isArray)
+            return `typedef ${this._typeUnifier.uniqueTypeId(node.elementType)} ${name}[${node.numElementsValue}];`;
+        else if (node.isPtr)
+            return `typedef ${node.addressSpace} ${this._typeUnifier.uniqueTypeId(node.elementType)} (*${name});`;
+        else {
+            class NativeTypeNameVisitor extends Visitor {
+                visitNativeType(node)
+                {
+                    // FIXME: Also add samplers and textures here.
+                    const nativeTypeNameMap = {
+                        "void": "void",
+                        "bool": "bool",
+                        "uchar": "uint8_t",
+                        "ushort": "uint16_t",
+                        "uint": "uint32_t",
+                        "char": "int8_t",
+                        "short": "int16_t",
+                        "int": "int32_t",
+                        "half": "half",
+                        "float": "float",
+                        "atomic_int": "atomic_int",
+                        "atomic_uint": "atomic_uint"
+                    };
+
+                    return nativeTypeNameMap[node.name];
+                }
+
+                visitVectorType(node)
+                {
+                    // Vector type names work slightly differently to native type names.
+                    const elementTypeNameMap = {
+                        "bool": "bool",
+                        "char": "char",
+                        "uchar": "uchar",
+                        "short": "short",
+                        "ushort": "ushort",
+                        "int": "int",
+                        "uint": "uint",
+                        "half": "half",
+                        "float": "float"
+                    };
+
+                    const elementTypeName = elementTypeNameMap[node.elementType.name];
+                    if (!elementTypeName)
+                        throw new Error(`${node.elementType.name} is not a supported vector element type`);
+
+                    return `${elementTypeName}${node.numElementsValue}`;
+                }
+
+                visitMatrixType(node)
+                {
+                    const elementTypeNameMap = {
+                        "half": "half",
+                        "float": "float"
+                    };
+
+                    const elementTypeName = elementTypeNameMap[node.elementType.name];
+                    if (!elementTypeName)
+                        throw new Error(`${node.elementType.name} is not a supported matrix element type`);
+
+                    return `${elementTypeName}${node.numRowsValue}x${node.numColumnsValue}`;
+                }
+            }
+            const nativeName = node.visit(new NativeTypeNameVisitor());
+            if (!nativeName)
+                throw new Error(`Native type name not found for ${node}`);
+            return `typedef ${nativeName} ${name};`;
+        }
+    }
+}
diff --git a/Tools/WebGPUShadingLanguageRI/Metal/MSLCodegenAll.js b/Tools/WebGPUShadingLanguageRI/Metal/MSLCodegenAll.js
new file mode 100644 (file)
index 0000000..e59d3de
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+load("MSLBackend.js");
+load("MSLCompileResult.js");
+load("MSLConstexprEmitter.js");
+load("MSLFunctionDeclaration.js");
+load("MSLFunctionDefinition.js");
+load("MSLFunctionForwardDeclaration.js");
+load("MSLNameMangler.js");
+load("MSLNativeFunctionCall.js");
+load("MSLStatementEmitter.js");
+load("MSLTypeAttributesMap.js");
+load("MSLTypeAttributes.js");
+load("MSLTypeUnifier.js");
+load("TypeOf.js");
+load("WhlslToMsl.js");
\ No newline at end of file
diff --git a/Tools/WebGPUShadingLanguageRI/Metal/MSLCompileResult.js b/Tools/WebGPUShadingLanguageRI/Metal/MSLCompileResult.js
new file mode 100644 (file)
index 0000000..2fd0bb8
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+class MSLCompileResult {
+
+    constructor(src, err, mangledNameMap, functionSources)
+    {
+        this._metalShaderLanguageSource = src;
+        this._error = err;
+        this._originalFunctionNameToMangledNames = mangledNameMap;
+        this._functionSources = functionSources;
+    }
+
+    get metalShaderLanguageSource()
+    {
+        return this._metalShaderLanguageSource;
+    }
+
+    get error()
+    {
+        return this._error;
+    }
+
+    get originalFunctionNameToMangledNames()
+    {
+        return this._originalFunctionNameToMangledNames;
+    }
+
+    get functionSources()
+    {
+        return this._functionSources;
+    }
+
+    get didSucceed()
+    {
+        return !this.error;
+    }
+}
\ No newline at end of file
diff --git a/Tools/WebGPUShadingLanguageRI/Metal/MSLConstexprEmitter.js b/Tools/WebGPUShadingLanguageRI/Metal/MSLConstexprEmitter.js
new file mode 100644 (file)
index 0000000..5d34328
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+// Used in scenarios where having an auxiliary variable is not possible (e.g. switch cases).
+class MSLConstexprEmitter extends Visitor
+{
+    visitIdentityExpression(node)
+    {
+        return node.target.visit(this);
+    }
+
+    visitBoolLiteral(node)
+    {
+        return node.value.toString();
+    }
+
+    visitEnumLiteral(node)
+    {
+        return node.member.value.visit(this);
+    }
+
+    visitGenericLiteral(node)
+    {
+        // FIXME: What happens in the case of halfs/floats/etc
+        return node.value.toString();
+    }
+
+    visitNullLiteral(node)
+    {
+        return "nullptr";
+    }
+}
\ No newline at end of file
diff --git a/Tools/WebGPUShadingLanguageRI/Metal/MSLFunctionDeclaration.js b/Tools/WebGPUShadingLanguageRI/Metal/MSLFunctionDeclaration.js
new file mode 100644 (file)
index 0000000..7c07f44
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * 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.
+ */
+
+// Emits code for the first line of a function declaration or definition.
+class MSLFunctionDeclaration {
+
+    constructor(funcMangler, funcDef, typeUnifier, typeAttributes)
+    {
+        this._funcMangler = funcMangler;
+        this._func = funcDef;
+        this._typeUnifier = typeUnifier;
+        this._typeAttributes = typeAttributes;
+    }
+
+    get funcMangler()
+    {
+        return this._funcMangler;
+    }
+
+    get func()
+    {
+        return this._func;
+    }
+
+    get typeUnifier()
+    {
+        return this._typeUnifier;
+    }
+
+    get typeAttributes()
+    {
+        return this._typeAttributes;
+    }
+
+    get isVertexShader()
+    {
+        return this._func.shaderType == "vertex";
+    }
+
+    get paramMap()
+    {
+        const map = new Map();
+        let counter = 0;
+        for (let param of this._func.parameters)
+            map.set(param, `P${counter++}`);
+        return map;
+    }
+
+    commentLine()
+    {
+        const params = [];
+        for (let param of this.paramMap.keys())
+            params.push(param.name);
+        return `// ${this._func.name}(${params.join(", ")}) @ ${this._func.origin.originString}\n`;
+    }
+
+    toString()
+    {
+        let declLine = "";
+
+        if (this.isShader)
+            declLine += `${this._func.shaderType} `;
+        declLine += `${this._typeUnifier.uniqueTypeId(this._func.returnType)} `;
+        declLine += this._funcMangler.mangle(this.func);
+        declLine += "("
+
+        let params = [];
+        const paramMap = this.paramMap;
+        for (let param of this._func.parameters) {
+            let pStr = `${this._typeUnifier.uniqueTypeId(param.type)} ${paramMap.get(param)}`;
+            // FIXME: The parser doesn't currently support vertex shaders having uint parameters, so this doesn't work.
+            if (this.parameterIsVertexId(param) && this.isVertexShader)
+                pStr += " [[vertex_id]]";
+            else if (this.parameterIsAttribute(param))
+                pStr += " [[stage_in]]";
+            params.push(pStr);
+        }
+
+        declLine += params.join(", ") + ")";
+
+        return declLine;
+    }
+
+    get isShader()
+    {
+        // FIXME: Support WHLSL "compute" shaders (MSL calls these kernel shaders)
+        return this.func.shaderType === "vertex" || this.func.shaderType === "fragment";
+    }
+
+    parameterIsAttribute(node)
+    {
+        // We currently assuming that all parameters to entry points are attributes.
+        // TODO: Better logic for this, i.e. support samplers.
+        return this.isShader;
+    }
+
+    parameterIsVertexId(node)
+    {
+        // FIXME: This isn't final, and isn't formally specified yet.
+        return this.isVertexShader && node.name == "wsl_vertexID" && node.type.name == "uint32";
+    }
+}
\ No newline at end of file
diff --git a/Tools/WebGPUShadingLanguageRI/Metal/MSLFunctionDefinition.js b/Tools/WebGPUShadingLanguageRI/Metal/MSLFunctionDefinition.js
new file mode 100644 (file)
index 0000000..d1f30f2
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+class MSLFunctionDefinition extends MSLFunctionDeclaration
+{
+    toString()
+    {
+        let src = this.commentLine() + "\n" + super.toString();
+        src += "\n{\n";
+        let emitter = new MSLStatementEmitter(this.funcMangler, this.typeUnifier, this.func, this.paramMap, this.func.name, this.typeAttributes);
+        src += emitter.indentedSource();
+        src += "}";
+        return src;
+    }
+}
\ No newline at end of file
diff --git a/Tools/WebGPUShadingLanguageRI/Metal/MSLFunctionForwardDeclaration.js b/Tools/WebGPUShadingLanguageRI/Metal/MSLFunctionForwardDeclaration.js
new file mode 100644 (file)
index 0000000..912544c
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+class MSLFunctionForwardDeclaration extends MSLFunctionDeclaration
+{
+    toString()
+    {
+        return this.commentLine() + "\n" + super.toString() + ";"
+    }
+}
\ No newline at end of file
diff --git a/Tools/WebGPUShadingLanguageRI/Metal/MSLNameMangler.js b/Tools/WebGPUShadingLanguageRI/Metal/MSLNameMangler.js
new file mode 100644 (file)
index 0000000..49a30df
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+class MSLNameMangler {
+
+    constructor(prefix)
+    {
+        this._prefix = prefix;
+        this._counter = 0;
+        this._mangledNameMap = new Map();
+    }
+
+    mangle(key)
+    {
+        if (!this._mangledNameMap.has(key))
+            this._mangledNameMap.set(key, `${this._prefix}${this._counter++}`);
+        return this._mangledNameMap.get(key);
+    }
+}
\ No newline at end of file
diff --git a/Tools/WebGPUShadingLanguageRI/Metal/MSLNativeFunctionCall.js b/Tools/WebGPUShadingLanguageRI/Metal/MSLNativeFunctionCall.js
new file mode 100644 (file)
index 0000000..2ab8717
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * 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 mslNativeFunctionCall(node, resultVariable, args)
+{
+    const key = node.toString();
+
+    // FIXME: Implement the sampling functions.
+    // FIXME: Implement functions like f16tof32, asfloat, etc.
+    // FIXME: Implement tests for all native functions https://bugs.webkit.org/show_bug.cgi?id=189535.
+    const functionsWithTheSameCallingConvention = {
+        "native bool isfinite(float)" : "isfinite",
+        "native bool isinf(float)" : "isinf",
+        "native bool isnormal(float)" : "isnormal",
+        "native bool isnormal(half)" : "isnormal",
+        "native float acos(float)" : "acos",
+        "native float asfloat(int)" : "static_cast<float>",
+        "native float asfloat(uint)" : "static_cast<float>",
+        "native int asint(float)" : "static_cast<int>",
+        "native uint asuint(float)" : "static_cast<uint>",
+        "native float asin(float)" : "asin",
+        "native float atan(float)" : "atan",
+        "native float atan2(float,float)" : "atan2",
+        "native float ceil(float)" : "ceil",
+        "native float cos(float)" : "cos",
+        "native float cosh(float)" : "cosh",
+        "native float ddx(float)" : "dfdx",
+        "native float ddy(float)" : "dfdy",
+        "native float exp(float)" : "exp",
+        "native float floor(float)" : "floor",
+        "native float log(float)" : "log",
+        "native float pow(float,float)" : "pow",
+        "native float round(float)" : "round",
+        "native float sin(float)" : "sin",
+        "native float sinh(float)" : "sinh",
+        "native float sqrt(float)" : "sqrt",
+        "native float tan(float)" : "tan",
+        "native float tanh(float)" : "tanh",
+        "native float trunc(float)" : "trunc",
+    };
+
+    if (key in functionsWithTheSameCallingConvention) {
+        const callString = `${functionsWithTheSameCallingConvention[key]}(${args.join(", ")})`;
+        if (resultVariable)
+            return `${resultVariable} = ${callString};`;
+        else
+            return `${callString};`;
+    }
+
+    const functionsWithDifferentCallingConventions = {
+        "native uint f32tof16(float)" : () => `${resultVariable} = uint(static_cast<ushort>(half(${args[0]})));`,
+        "native float f16tof32(uint)" : () => `${resultVariable} = float(static_cast<half>(ushort(${args[0]})));`
+    };
+    if (key in functionsWithDifferentCallingConventions)
+        return functionsWithDifferentCallingConventions[key]();
+
+    throw new Error(`${node} doesn't have mapping to a native Metal function.`);
+}
\ No newline at end of file
diff --git a/Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js b/Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js
new file mode 100644 (file)
index 0000000..a105016
--- /dev/null
@@ -0,0 +1,796 @@
+/*
+ * 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.
+ */
+
+class MSLStatementEmitter extends Visitor {
+
+    constructor(funcMangler, typeUnifier, func, paramMap, debugName, typeAttributes)
+    {
+        super();
+        this._funcMangler = funcMangler;
+        this._typeUnifier = typeUnifier;
+        this._func = func;
+        this._nameMap = paramMap;
+        this._debugName = debugName;
+        this._typeAttributes = typeAttributes;
+        this._counter = 0;
+        this._indentLevel = 0;
+        this._lines = [];
+
+        this._loopConditionVariableStack = [];
+        this._loopConditionNodeStack = [];
+        this._loopIncrementNodeStack = [];
+
+        this._actualResult = this._func.visit(this);
+    }
+
+    _emitTrap()
+    {
+        this._add('// FIXME: Handle traps.');
+    }
+
+    _zeroInitialize(type, variableName, allowComment = true)
+    {
+        const emitter = this;
+
+        class ZeroInitializer extends Visitor {
+            visitNativeType(node)
+            {
+                if (node.name == "bool")
+                    emitter._add(`${variableName} = false;`);
+                else
+                    emitter._add(`${variableName} = 0;`);
+            }
+
+            visitPtrType(node)
+            {
+                emitter._add(`${variableName} = nullptr;`);
+            }
+
+            visitArrayType(node)
+            {
+                for (let i = 0; i < node.numElements.value; i++)
+                    emitter._zeroInitialize(node.elementType, `${variableName}[${i}]`, false);
+            }
+
+            visitArrayRefType(node)
+            {
+                emitter._add(`${variableName}.ptr = nullptr;`);
+                emitter._add(`${variableName}.length = 0;`);
+            }
+
+            visitEnumType(node)
+            {
+                emitter._zeroInitialize(node.baseType, variableName, false);
+            }
+
+            visitStructType(node)
+            {
+                const typeAttributes = emitter._typeAttributes.attributesForType(node);
+
+                for (let field of node.fields) {
+                    const fieldName = typeAttributes.mangledFieldName(field.name);
+                    emitter._zeroInitialize(field.type, `${variableName}.${fieldName}`, false);
+                }
+            }
+
+            visitVectorType(node)
+            {
+                const elementNames = [ "x", "y", "z", "w" ];
+                for (let i = 0; i < node.numElementsValue; i++)
+                    emitter._add(`${variableName}.${elementNames[i]} = 0;`);
+            }
+
+            visitMatrixType(node)
+            {
+                for (let i = 0; i < node.numRowsValue; i++) {
+                    for (let j = 0; j < node.numRowsValue; j++)
+                        emitter._zeroInitialize(node.elementType, `${variableName}[${i}][${j}]`, false);
+                }
+            }
+
+            visitTypeRef(node)
+            {
+                node.type.visit(this);
+            }
+        }
+        if (allowComment)
+            this._add(`// Zero initialization of ${variableName}`);
+        Node.visit(type, new ZeroInitializer());
+    }
+
+    get actualResult()
+    {
+        return this._actualResult;
+    }
+
+    get lines()
+    {
+        return this._lines;
+    }
+
+    indentedSource()
+    {
+        return this._lines.map(line => "    " + line + "\n").join("");
+    }
+
+    get _loopConditionVariable()
+    {
+        return this._loopConditionVariableStack[this._loopConditionVariableStack.length - 1];
+    }
+
+    get _loopCondition()
+    {
+        return this._loopConditionNodeStack[this._loopConditionNodeStack.length - 1];
+    }
+
+    get _loopIncrement()
+    {
+        return this._loopIncrementNodeStack[this._loopIncrementNodeStack.length - 1];
+    }
+
+    _add(linesString)
+    {
+        for (let line of linesString.split('\n')) {
+            for (let i = 0; i < this._indentLevel; i++)
+                line = "    " + line;
+            this._lines.push(line);
+        }
+    }
+
+    _addLines(lines)
+    {
+        for (let line of lines)
+            this._add(line);
+    }
+
+    _fresh()
+    {
+        return `V${this._counter++}`;
+    }
+
+    _indent(closure)
+    {
+        this._indentLevel++;
+        const result = closure();
+        this._indentLevel--;
+        return result;
+    }
+
+    // Atomic statements.
+
+    visitBreak(node)
+    {
+        this._add("break;")
+    }
+
+    visitContinue(node)
+    {
+        // This is necessary because for loops are compiled as while loops, so we need to do
+        // the loop increment and condition check before returning to the beginning of the loop.
+        this._emitLoopBodyEnd();
+        this._add("continue;");
+    }
+
+    // Basic compound statements.
+
+    visitBlock(block)
+    {
+        for (let statement of block.statements) {
+            this._add(`// ${this._debugName} @ ${statement.origin.originString}`);
+            statement.visit(this);
+        }
+    }
+
+    visitReturn(node)
+    {
+        if (node.value)
+            this._add(`return ${node.value.visit(this)};`);
+        else
+            this._add(`return;`);
+    }
+
+    _visitAndIndent(node)
+    {
+        return this._indent(() => node.visit(this));
+    }
+
+    visitIfStatement(node)
+    {
+        let condition = node.conditional.visit(this);
+        this._add(`if (${condition}) {`);
+        this._visitAndIndent(node.body);
+        if (node.elseBody) {
+            this._add(`} else {`);
+            this._visitAndIndent(node.elseBody);
+            this._add('}');
+        } else
+            this._add('}');
+    }
+
+    visitIdentityExpression(node)
+    {
+        return node.target.visit(this);
+    }
+
+    visitLogicalNot(node)
+    {
+        const type = typeOf(node);
+        let expr = Node.visit(node.operand, this);
+        let id = this._fresh();
+        this._add(`${this._typeUnifier.uniqueTypeId(type)} ${id};`);
+        this._add(`${id} = !(${expr});`);
+        return id;
+    }
+
+    visitDereferenceExpression(node)
+    {
+        const ptr = node.ptr.visit(this);
+        this._add(`if (!${ptr}) {`);
+        this._indent(() => this._emitTrap());
+        this._add('}');
+        const id = this._fresh();
+        this._add(`${this._typeUnifier.uniqueTypeId(node.type)} ${id};`);
+        this._emitMemCopy(node.type, id, `(*${ptr})`);
+        return id;
+    }
+
+    _isOperatorAnder(node)
+    {
+        const anderRegex = /^operator\&\.(.*?)$/;
+        return node instanceof NativeFunc && anderRegex.test(node.name);
+    }
+
+    _isOperatorGetter(node)
+    {
+        const getterRegex = /^operator\.(.*?)$/;
+        return node instanceof NativeFunc && getterRegex.test(node.name);
+    }
+
+    _isOperatorIndexer(node)
+    {
+        return node instanceof NativeFunc && node.name == "operator&[]";
+    }
+
+    _isOperatorSetter(node)
+    {
+        const setterRegex = /^operator\.(.*?)=$/;
+        return node instanceof NativeFunc && setterRegex.test(node.name)
+    }
+
+    _isOperatorCast(node)
+    {
+        return node instanceof NativeFunc && node.name == "operator cast";
+    }
+
+    _isUnaryOperator(node)
+    {
+        const operatorRegex = /^operator\~$/;
+        return node instanceof NativeFunc && operatorRegex.test(node.name);
+    }
+
+    _isBinaryOperator(node)
+    {
+        const operatorRegex = /operator(\+|\-|\*|\/|\^|\&|\||\&\&|\|\||\<\<|\>\>|\<|\<\=|\>|\>\=|\=\=|\!\=)$/;
+        return node instanceof NativeFunc && operatorRegex.test(node.name);
+    }
+
+    _isOperatorValue(node)
+    {
+        return node instanceof NativeFunc && node.name == "operator.value";
+    }
+
+    _isOperatorLength(node)
+    {
+        return node instanceof NativeFunc && node.name == "operator.length";
+    }
+
+    _extractOperatorName(node)
+    {
+        return node.name.substring("operator".length);
+    }
+
+    visitVariableDecl(node)
+    {
+        let id = this._fresh();
+        this._nameMap.set(node, id);
+
+        this._add(`${this._typeUnifier.uniqueTypeId(node.type)} ${id}; // ${node}`);
+
+        if (node.initializer) {
+            let expr = node.initializer.visit(this);
+            this._emitMemCopy(typeOf(node), id, expr);
+        } else
+            this._zeroInitialize(node.type, id);
+    }
+
+    visitVariableRef(node)
+    {
+        if (!this._nameMap.has(node.variable))
+            throw new Error(`${node.variable} not found in this function's (${this._func.name}) variable map`);
+        return this._nameMap.get(node.variable);
+    }
+
+    visitMakeArrayRefExpression(node)
+    {
+        const elemName = this._emitAsLValue(node.lValue);
+        const arrayType = typeOf(node.lValue);
+        const id = this._fresh();
+        this._add(`${this._typeUnifier.uniqueTypeId(node.type)} ${id};`);
+        this._add(`${id}.length = ${node.numElements.value};`);
+        if (arrayType.isArray)
+            this._add(`${id}.ptr = ${elemName};`);
+        else
+            this._add(`${id}.ptr = &(${elemName});`);
+        return id;
+    }
+
+    visitConvertPtrToArrayRefExpression(node)
+    {
+        const lValue = this._emitAsLValue(node.lValue);
+        const type = typeOf(node);
+        const id = this._fresh();
+        this._add(`${this._typeUnifier.uniqueTypeId(type)} ${id};`);
+        this._add(`${id}.length = 1;`);
+        this._add(`${id}.ptr = ${lValue};`);
+        return id;
+    }
+
+    visitAnonymousVariable(node)
+    {
+        let id = this._fresh();
+        this._nameMap.set(node, id);
+        this._add(`${this._typeUnifier.uniqueTypeId(node.type)} ${id}; // ${node.name}`);
+        this._zeroInitialize(node.type, id);
+        return id;
+    }
+
+    visitAssignment(node)
+    {
+        const lhs = this._emitAsLValue(node.lhs);
+        const rhs = node.rhs.visit(this);
+        this._emitMemCopy(typeOf(node), lhs, rhs);
+        return lhs;
+    }
+
+    _emitMemCopy(type, dest, src)
+    {
+        type = typeOf(type);
+        if (type instanceof ArrayType) {
+            for (let i = 0; i < type.numElementsValue; i++)
+                this._emitMemCopy(typeOf(type.elementType), `${dest}[${i}]`, `${src}[${i}]`);
+        } else {
+            this._add(`// ${type}`);
+            this._add(`${dest} = ${src};`);
+        }
+    }
+
+    visitCommaExpression(node)
+    {
+        let result;
+        for (let expr of node.list)
+            result = Node.visit(expr, this);
+        return result;
+    }
+
+    visitCallExpression(node)
+    {
+        const args = [];
+        for (let i = node.argumentList.length; i--;)
+            args.unshift(node.argumentList[i].visit(this));
+
+        let resultVariable;
+        if (node.func.returnType.name !== "void") {
+            resultVariable = this._fresh();
+            this._add(`// Result variable for call ${node.func.name}(${args.join(", ")})`);
+            this._add(`${this._typeUnifier.uniqueTypeId(node.resultType)} ${resultVariable};`);
+        }
+
+        if (node.func instanceof FuncDef) {
+            const mangledCallName = this._funcMangler.mangle(node.func);
+            const callString = `${mangledCallName}(${args.join(", ")})`;
+            if (resultVariable)
+                this._add(`${resultVariable} = ${callString};`);
+            else
+                this._add(`${callString};`);
+        } else
+            this._makeNativeFunctionCall(node.func, resultVariable, args);
+
+        return resultVariable;
+    }
+
+    _makeNativeFunctionCall(node, resultVariable, args)
+    {
+        if (!(node instanceof NativeFunc))
+            throw new Error(`${node} should be a native function.`);
+
+        if (this._isOperatorAnder(node))
+            this._emitOperatorAnder(node, resultVariable, args);
+        else if (node.implementationData instanceof BuiltinVectorGetter)
+            this._emitBuiltinVectorGetter(node, resultVariable, args);
+        else if (node.implementationData instanceof BuiltinVectorSetter)
+            this._emitBuiltinVectorSetter(node, resultVariable, args);
+        else if (node.implementationData instanceof BuiltinMatrixGetter)
+            this._emitBuiltinMatrixGetter(node, resultVariable, args);
+        else if (node.implementationData instanceof BuiltinMatrixSetter)
+            this._emitBuiltinMatrixSetter(node, resultVariable, args);
+        else if (this._isOperatorValue(node))
+            this._add(`${resultVariable} = ${args[0]};`);
+        else if (this._isOperatorLength(node))
+            this._emitOperatorLength(node, resultVariable, args);
+        else if (this._isOperatorSetter(node))
+            this._emitOperatorSetter(node, resultVariable, args);
+        else if (this._isOperatorGetter(node))
+            this._emitOperatorGetter(node, resultVariable, args);
+        else if (this._isOperatorIndexer(node))
+            this._emitOperatorIndexer(node, resultVariable, args);
+        else if (this._isOperatorCast(node))
+            this._emitOperatorCast(node, resultVariable, args);
+        else if (this._isUnaryOperator(node))
+            this._add(`${resultVariable} = ${this._extractOperatorName(node)}(${args[0]});`);
+        else if (this._isBinaryOperator(node))
+            this._add(`${resultVariable} = ${args[0]} ${this._extractOperatorName(node)} ${args[1]};`);
+        else
+            this._add(mslNativeFunctionCall(node, resultVariable, args));
+
+        return resultVariable;
+    }
+
+    _emitCallToMetalFunction(name, result, args)
+    {
+        this._add(`${result} = ${name}(${args.join(", ")});`);
+    }
+
+    _emitBuiltinVectorGetter(node, result, args)
+    {
+        this._add(`${result} = ${args[0]}.${node.implementationData.elementName};`);
+    }
+
+    _emitBuiltinVectorSetter(node, result, args)
+    {
+        this._add(`${result} = ${args[0]};`);
+        this._add(`${result}.${node.implementationData.elementName} = ${args[1]};`);
+    }
+
+    _emitBuiltinMatrixGetter(node, result, args)
+    {
+        this._add(`if (${args[1]} >= ${node.implementationData.height}) {`);
+        this._indent(() => this._emitTrap());
+        this._add('}');
+        this._add(`${result} = ${args[0]}[${args[1]}];`);
+    }
+
+    _emitBuiltinMatrixSetter(node, result, args)
+    {
+        this._add(`if (${args[1]} >= ${node.implementationData.height}) {`);
+        this._indent(() => this._emitTrap());
+        this._add('}');
+        this._add(`${result} = ${args[0]};`);
+        this._add(`${result}[${args[1]}] = ${args[2]};`);
+    }
+
+    _emitOperatorLength(node, result, args)
+    {
+        const paramType = typeOf(node.parameters[0]);
+        if (paramType instanceof ArrayRefType)
+            this._add(`${result} = ${args[0]}.length;`);
+        else if (paramType instanceof ArrayType)
+            this._add(`${result} = ${paramType.numElements.visit(this)};`);
+        else
+            throw new Error(`Unhandled paramter type ${paramType} for operator.length`);
+    }
+
+    _emitOperatorCast(node, result, args)
+    {
+        const retType = this._typeUnifier.uniqueTypeId(node.returnType);
+        this._add(`${result} = ${retType}(${args.join(", ")});`);
+    }
+
+    _emitOperatorIndexer(node, result, args)
+    {
+        if (!(typeOf(node.parameters[0]) instanceof ArrayRefType))
+            throw new Error(`Support for operator&[] ${node.parameters[0]} not implemented.`);
+
+        this._add(`if (${args[1]} >= ${args[0]}.length) {`);
+        this._indent(() => this._emitTrap());
+        this._add(`}`);
+        this._add(`// Operator indexer`);
+        this._add(`${result} = &(${args[0]}.ptr[${args[1]}]);`);
+    }
+
+    _emitOperatorAnder(node, result, args)
+    {
+        const type = node.implementationData.structType;
+        const field = this._typeAttributes.attributesForType(type).mangledFieldName(node.implementationData.name);
+        this._add(`${result} = &((${args[0]})->${field});`);
+    }
+
+    _emitOperatorGetter(node, result, args)
+    {
+        const type = node.implementationData.type;
+        const field = this._typeAttributes.attributesForType(type).mangledFieldName(node.implementationData.name);
+        this._add(`${result} = ${args[0]}.${field};`);
+    }
+
+    _emitOperatorSetter(node, result, args)
+    {
+        // FIXME: Currently no WHLSL source produces structs that only have getters/setters rather than the ander,
+        // and the ander is always preferred over the getter and setter. Therefore this code is untested.
+        const type = node.implementationData.type;
+        const field = this._typeAttributes.attributesForType(type).mangledFieldName(node.implementationData.name);
+        this._add(`${args[0]}.${field} = ${args[1]};`);
+        this._add(`${result} = ${args[0]}`);
+    }
+
+    visitMakePtrExpression(node)
+    {
+        if (node.target instanceof DereferenceExpression)
+            return this._emitAsLValue(node.target.ptr);
+        return `&(${this._emitAsLValue(node.lValue)})`;
+    }
+
+    // Loop code generation. Loops all follow the same style where they declare a conditional variable (boolean)
+    // which is checked on each iteration (all loops are compiled to a while loop or do/while loop). The check is
+    // emitted in _emitLoopCondition. For loops additionally have _emitLoopBodyEnd for the increment on each iteration.
+
+    visitForLoop(node)
+    {
+        this._emitAsLoop(node.condition, node.increment, (conditionVar) => {
+            Node.visit(node.initialization, this);
+            this._emitLoopCondition();
+            this._add(`while (${conditionVar}) {`);
+            this._indent(() => {
+                node.body.visit(this);
+                this._emitLoopBodyEnd();
+            });
+            this._add('}');
+        });
+    }
+
+    visitWhileLoop(node)
+    {
+        this._emitAsLoop(node.conditional, null, (conditionVar) => {
+            this._add(`while (${conditionVar}) {`);
+            this._indent(() => {
+                node.body.visit(this);
+                this._emitLoopCondition();
+            });
+            this._add('}');
+        });
+    }
+
+    visitDoWhileLoop(node)
+    {
+        this._emitAsLoop(node.conditional, null, conditionVar => {
+            this._add("do {");
+            this._indent(() => {
+                node.body.visit(this);
+                this._emitLoopBodyEnd();
+            });
+            this._add(`} while (${conditionVar});`);
+        });
+    }
+
+    _emitAsLoop(conditional, increment, emitLoopBody)
+    {
+        const conditionVar = this._fresh();
+        this._loopConditionVariableStack.push(conditionVar);
+        this._loopConditionNodeStack.push(conditional);
+        this._loopIncrementNodeStack.push(increment);
+        this._add(`bool ${conditionVar} = true;`);
+
+        emitLoopBody(conditionVar);
+
+        this._loopIncrementNodeStack.pop();
+        this._loopConditionVariableStack.pop();
+        this._loopConditionNodeStack.pop();
+    }
+
+    _emitLoopBodyEnd()
+    {
+        this._emitLoopIncrement();
+        this._emitLoopCondition();
+    }
+
+    _emitLoopIncrement()
+    {
+        Node.visit(this._loopIncrement, this);
+    }
+
+    _emitLoopCondition()
+    {
+        if (this._loopCondition) {
+            const conditionResult = this._loopCondition.visit(this);
+            this._add(`${this._loopConditionVariable} = ${conditionResult};`);
+        }
+    }
+
+    visitSwitchStatement(node)
+    {
+        const caseValueEmitter = new MSLConstexprEmitter();
+
+        let switchValue = Node.visit(node.value, this);
+        this._add(`switch (${switchValue}) {`);
+        this._indent(() => {
+            for (let i = 0; i < node.switchCases.length; i++) {
+                let switchCase = node.switchCases[i];
+                if (!switchCase.isDefault)
+                    this._add(`case ${switchCase.value.visit(caseValueEmitter)}: {`);
+                else
+                    this._add("default: {");
+                this._visitAndIndent(switchCase.body);
+                this._add("}");
+            }
+        });
+        this._add("}");
+    }
+
+    visitSwitchCase(node)
+    {
+        throw new Error(`MSLStatementEmitter.visitSwitchCase called; switch statements should be fully handled by MSLStatementEmitter.visitSwitchStatement.`);
+    }
+
+    visitBoolLiteral(node)
+    {
+        const fresh = this._fresh();
+        this._add(`bool ${fresh} = ${node.value};`);
+        return fresh;
+    }
+
+    visitEnumLiteral(node)
+    {
+        return node.member.value.visit(this);
+    }
+
+    visitGenericLiteral(node)
+    {
+        const fresh = this._fresh();
+        this._add(`${this._typeUnifier.uniqueTypeId(node.type)} ${fresh};`);
+        this._add(`${fresh} = ${node.value};`);
+        return fresh;
+    }
+
+    visitNullLiteral(node)
+    {
+        return `nullptr`;
+    }
+
+    visitDotExpression(node)
+    {
+        throw new Error(`MSLStatementEmitter.visitDotExpression called; ${node} should have been converted to an operator function call.`);
+    }
+
+    visitTrapStatement(node)
+    {
+        this._emitTrap();
+    }
+
+    visitLogicalExpression(node)
+    {
+        const result = this._fresh();
+        this._add(`// Result variable for ${node.text}`);
+        this._add(`bool ${result};`);
+        if (node.text == "&&") {
+            const left = node.left.visit(this);
+            this._add(`if (!${left}) {`)
+            this._indent(() => this._add(`${result} = false;`));
+            this._add('} else {');
+            this._indent(() => this._add(`${result} = ${node.right.visit(this)};`));
+            this._add('}');
+        } else if (node.text == "||") {
+            const left = node.left.visit(this);
+            this._add(`${result} = ${left};`);
+            this._add(`if (!${result}) {`);
+            this._indent(() => this._add(`${result} = ${node.right.visit(this)};`));
+            this._add("}");
+        } else
+            throw new Error(`Unrecognized logical expression ${node.text}`);
+        return result;
+    }
+
+    visitTernaryExpression(node)
+    {
+        // FIXME: If each of the three expressions isn't a compound expression or statement then just use ternary syntax.
+        let resultType = typeOf(node.bodyExpression);
+        let resultVar = this._fresh();
+        this._add(`${this._typeUnifier.uniqueTypeId(resultType)} ${resultVar};`);
+        const predicate = node.predicate.visit(this);
+        this._add(`if (${predicate}) {`);
+        this._indent(() => {
+            const bodyExpression = node.bodyExpression.visit(this);
+            this._emitMemCopy(resultType, resultVar, bodyExpression);
+        });
+        this._add("} else {");
+        this._indent(() => {
+            const elseExpression = node.elseExpression.visit(this);
+            this._emitMemCopy(resultType, resultVar, elseExpression);
+        });
+        this._add("}");
+
+        return resultVar;
+    }
+
+    visitIndexExpression(node)
+    {
+        throw new Error("MSLStatementEmitter.visitIndexExpression is not implemented; IndexExpression should not appear in the AST for Metal codegen.");
+    }
+
+    visitNativeFunc(node)
+    {
+        throw new Error("MSLStatementEmitter.visitNativeFunc is not implemented; NativeFunction is not compiled by the Metal codegen.");
+    }
+
+    visitNativeTypeInstance(node)
+    {
+        throw new Error("MSLStatementEmitter.visitNativeTypeInstance is not implemented.");
+    }
+
+    visitReadModifyWriteExpression(node)
+    {
+        throw new Error("MSLStatementEmitter.visitReadModifyWriteExpression is not implemented; it should have been transformed out the tree in earlier stage.");
+    }
+
+    _emitAsLValue(node)
+    {
+        const lOrRValueEmitter = this;
+        class LValueVisitor extends Visitor
+        {
+            visitDereferenceExpression(node)
+            {
+                const target = node.ptr.visit(lOrRValueEmitter);
+                lOrRValueEmitter._add(`if (!(${target})) {`);
+                lOrRValueEmitter._indent(() => lOrRValueEmitter._emitTrap());
+                lOrRValueEmitter._add("}");
+                return `(*(${target}))`;
+            }
+
+            visitVariableRef(node)
+            {
+                return node.visit(lOrRValueEmitter);
+            }
+
+            visitIdentityExpression(node)
+            {
+                return node.target.visit(this);
+            }
+
+            visitCallExpression(node)
+            {
+                // FIXME: Is this comment actually true???
+                // Only occurs for @(...) expressions.
+                return node.visit(lOrRValueEmitter);
+            }
+
+            visitMakePtrExpression(node)
+            {
+                if (node.lValue instanceof DereferenceExpression)
+                    return node.lValue.ptr.visit(lOrRValueEmitter);
+            }
+        }
+        const result = node.visit(new LValueVisitor());
+        if (!result)
+            throw new Error(`${node} not a valid l-value.`);
+        return result;
+    }
+}
\ No newline at end of file
diff --git a/Tools/WebGPUShadingLanguageRI/Metal/MSLTypeAttributes.js b/Tools/WebGPUShadingLanguageRI/Metal/MSLTypeAttributes.js
new file mode 100644 (file)
index 0000000..d6f1676
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+
+class MSLTypeAttributes {
+
+    constructor(type)
+    {
+        this._type = type;
+        this._isVertexAttribute = false;
+        this._isVertexOutputOrFragmentInput = false;
+        this._isFragmentOutput = false;
+        this._fieldMangler = new MSLNameMangler('field');
+
+        if (type instanceof StructType) {
+            for (let field of type.fields)
+                this._fieldMangler.mangle(field.name)
+        }
+    }
+
+    get type()
+    {
+        return this._type;
+    }
+
+    get isVertexAttribute()
+    {
+        return this._isVertexAttribute;
+    }
+
+    set isVertexAttribute(va)
+    {
+        this._isVertexAttribute = va;
+    }
+
+    get isVertexOutputOrFragmentInput()
+    {
+        return this._isVertexOutputOrFragmentInput;
+    }
+
+    set isVertexOutputOrFragmentInput(vo)
+    {
+        this._isVertexOutputOrFragmentInput = vo;
+    }
+
+    get isFragmentOutput()
+    {
+        return this._isFragmentOutput;
+    }
+
+    set isFragmentOutput(fo)
+    {
+        this._isFragmentOutput = fo;
+    }
+
+    get fieldMangler()
+    {
+        return this._fieldMangler;
+    }
+
+    mangledFieldName(fieldName)
+    {
+        return this.fieldMangler.mangle(fieldName);
+    }
+}
\ No newline at end of file
diff --git a/Tools/WebGPUShadingLanguageRI/Metal/MSLTypeAttributesMap.js b/Tools/WebGPUShadingLanguageRI/Metal/MSLTypeAttributesMap.js
new file mode 100644 (file)
index 0000000..373b0e8
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+// Provides lookup for all the top level types in the program.
+class MSLTypeAttributesMap {
+
+    constructor(functionDefs, typeUnifier)
+    {
+        this._typeUnifier = typeUnifier;
+        this._typeAttributeMap = new Map();
+
+        for (let funcDef of functionDefs) {
+            if (funcDef.shaderType == "vertex")
+                this._visitVertexShader(funcDef);
+            else if (funcDef.shaderType == "fragment")
+                this._visitFragmentShader(funcDef);
+        }
+    }
+
+    attributesForType(type)
+    {
+        const key = this._typeUnifier.uniqueTypeId(type);
+        let attrs = this._typeAttributeMap.get(key);
+        if (!attrs)
+            this._typeAttributeMap.set(key, attrs = new MSLTypeAttributes(type));
+        return attrs;
+    }
+
+    _visitVertexShader(func)
+    {
+        this.attributesForType(func.returnType).isVertexOutputOrFragmentInput = true;
+        for (let param of func.parameters)
+            this.attributesForType(param.type).isVertexAttribute = true;
+    }
+
+    _visitFragmentShader(func)
+    {
+        this.attributesForType(func.returnType).isFragmentOutput = true;
+        for (let param of func.parameters)
+            this.attributesForType(param.type).isVertexOutputOrFragmentInput = true;
+    }
+
+    // FIXME: Support compute shaders.
+}
\ No newline at end of file
diff --git a/Tools/WebGPUShadingLanguageRI/Metal/MSLTypeUnifier.js b/Tools/WebGPUShadingLanguageRI/Metal/MSLTypeUnifier.js
new file mode 100644 (file)
index 0000000..0292575
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * 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.
+ */
+
+
+class MSLTypeUnifier extends Visitor {
+
+    constructor()
+    {
+        super();
+
+        this._typeNameMangler = new MSLNameMangler('T');
+        this._allTypes = new Set();
+    }
+
+    get allTypes()
+    {
+        return this._allTypes;
+    }
+
+    uniqueTypeId(type)
+    {
+        const result = type.visit(this);
+        if (!result)
+            throw new Error(`${type} has no unique type ID.`);
+        return result;
+    }
+
+    visitTypeRef(node)
+    {
+        if ((node.typeArguments && node.typeArguments.length) || !node.name) {
+            return node.type.visit(this);
+        }
+
+        if (!this._allTypes.has(node.type))
+            node.type.visit(this);
+
+        const baseType = typeOf(node);
+
+        if (baseType instanceof NativeType || baseType instanceof VectorType || baseType instanceof MatrixType || baseType instanceof EnumType || baseType instanceof ArrayType)
+            return baseType.visit(this);
+
+        return this._typeNameMangler.mangle(node.name);
+    }
+
+    visitStructType(node)
+    {
+        this._allTypes.add(node);
+        for (let field of node.fields)
+            field.visit(this);
+        node._typeID = this._typeNameMangler.mangle(node.name);
+        return node._typeID;
+    }
+
+    visitEnumType(node)
+    {
+        this._allTypes.add(node);
+        node._typeID = node.baseType.visit(this);
+        return node._typeID;
+    }
+
+    visitNativeType(node)
+    {
+        this._allTypes.add(node);
+        node._typeID = this._typeNameMangler.mangle(node.name);
+        return node._typeID;
+    }
+
+    visitPtrType(node)
+    {
+        this._allTypes.add(node);
+        node.elementType.visit(this);
+        node._typeID = this._typeNameMangler.mangle(`${node.elementType.visit(this)}* ${node.addressSpace}`);
+        return node._typeID;
+    }
+
+    visitArrayType(node)
+    {
+        this._allTypes.add(node);
+        node.elementType.visit(this);
+        node._typeID = this._typeNameMangler.mangle(`${node.elementType.visit(this)}[${node.numElements}]`);
+        return node._typeID;
+    }
+
+    visitArrayRefType(node)
+    {
+        this._allTypes.add(node);
+        // The name mangler is used here because array refs are "user-defined" types in the sense
+        // that they will need to be defined as structs in the Metal output.
+        node._typeID = this._typeNameMangler.mangle(`${node.elementType.visit(this)}[] ${node.addressSpace}`);
+        return node._typeID;
+    }
+
+    visitVectorType(node)
+    {
+        this._allTypes.add(node);
+        node._typeID = this._typeNameMangler.mangle(node.toString());
+        return node._typeID;
+    }
+
+    visitMatrixType(node)
+    {
+        this._allTypes.add(node);
+        node._typeID = this._typeNameMangler.mangle(node.toString());
+        return node._typeID;
+    }
+
+    visitMakeArrayRefExpression(node)
+    {
+        super.visitMakeArrayRefExpression(node);
+        node._typeID = node.type.visit(this);
+        // When the array ref is declared in MSL it will contained a pointer to the element type. However, the original
+        // WHLSL doesn't need to contain the type "pointer to the element type", so we ensure that the type is present
+        // here. The logic isn't needed for ConvertPtrToArrayRefExpression because the type is necesssarily present there.
+        new PtrType(node.origin, node.addressSpace ? node.addressSpace : "thread", typeOf(node).elementType).visit(this);
+        return node._typeID;
+    }
+
+    visitGenericLiteralType(node)
+    {
+        node._typeID = node.type.visit(this);
+        return node._typeID;
+    }
+
+    typesThatNeedDeclaration()
+    {
+        const declSet = new Set();
+        const nameSet = new Set();
+        for (let type of this._allTypes) {
+            const name = type.visit(this);
+            if (!nameSet.has(name)) {
+                declSet.add(type);
+                nameSet.add(name);
+            }
+        }
+        return declSet;
+    }
+}
\ No newline at end of file
diff --git a/Tools/WebGPUShadingLanguageRI/Metal/TypeOf.js b/Tools/WebGPUShadingLanguageRI/Metal/TypeOf.js
new file mode 100644 (file)
index 0000000..7258ff3
--- /dev/null
@@ -0,0 +1,325 @@
+/*
+ * 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.
+ */
+
+// FIXME: Rather than being a separate function this should instead happen in the Checker phase that annotates
+// each Node instance with a "type" property. https://bugs.webkit.org/show_bug.cgi?id=189611
+function typeOf(node) {
+    class TypeVisitor extends Visitor {
+        visitAnonymousVariable(node)
+        {
+            return node.type.visit(this);
+        }
+
+        visitArrayRefType(node)
+        {
+            return node;
+        }
+
+        visitArrayType(node)
+        {
+            return node;
+        }
+
+        visitAssignment(node)
+        {
+            return node.type.visit(this);
+        }
+
+        visitCallExpression(node)
+        {
+            return node.func.visit(this);
+        }
+
+        visitCommaExpression(node)
+        {
+            return node.list[node.list.length - 1].visit(this);
+        }
+
+        visitConvertPtrToArrayRefExpression(node)
+        {
+            const ptrType = typeOf(node.lValue);
+            return new ArrayRefType(node.origin, ptrType.addressSpace, ptrType.elementType);
+        }
+
+        visitDotExpression(node)
+        {
+            return node.struct.fieldMap.get(node.fieldName).type.visit(this);
+        }
+
+        visitElementalType(node)
+        {
+            return node;
+        }
+
+        visitEnumType(node)
+        {
+            return node;
+        }
+
+        visitField(node)
+        {
+            return node.type.visit(this);
+        }
+
+        visitFunc(node)
+        {
+            return node.returnType.visit(this);
+        }
+
+        visitFuncDef(node)
+        {
+            return node.returnType.visit(this);
+        }
+
+        visitFuncParameter(node)
+        {
+            return node.type.visit(this);
+        }
+
+        visitFunctionLikeBlock(node)
+        {
+            return node.returnType.visit(this);
+        }
+
+        visitGenericLiteralType(node)
+        {
+            return typeOf(node.type);
+        }
+
+        visitIdentityExpression(node)
+        {
+            return node.target.visit(this);
+        }
+
+        visitIndexExpression(node)
+        {
+            return node.array.elementType.visit(this);
+        }
+
+        visitLogicalExpression(node)
+        {
+            throw new Error("FIXME: Implement TypeVisitor.visitLogicalExpression");
+        }
+
+        visitLogicalNot(node)
+        {
+            return node.operand.visit(this);
+        }
+
+        visitMakeArrayRefExpression(node)
+        {
+            return node.type.visit(this);
+        }
+
+        visitMakePtrExpression(node)
+        {
+            return new PtrType(node.origin, "thread", typeOf(node.lValue));
+        }
+
+        visitMatrixType(node)
+        {
+            return node;
+        }
+
+        visitNativeFunc(node)
+        {
+            return node.returnType.visit(this);
+        }
+
+        visitNativeFuncInstance(node)
+        {
+            return node.returnType.visit(this);
+        }
+
+        visitNativeType(node)
+        {
+            return node;
+        }
+
+        visitNativeTypeInstance(node)
+        {
+            return node;
+        }
+
+        visitNullLiteral(node)
+        {
+            return node.type.visit(this);
+        }
+
+        visitNullType(node)
+        {
+            return node;
+        }
+
+        visitPtrType(node)
+        {
+            return node;
+        }
+
+        visitReadModifyWriteExpression(node)
+        {
+            throw new Error("FIXME: Implement TypeVisitor.visitReadModifyWriteExpression");
+        }
+
+        visitReferenceType(node)
+        {
+            return node;
+        }
+
+        visitStructType(node)
+        {
+            return node;
+        }
+
+        visitTypeDef(node)
+        {
+            return node.type.visit(this);
+        }
+
+        visitTypeRef(node)
+        {
+            return node.type.visit(this);
+        }
+
+        visitVariableDecl(node)
+        {
+            return node.type.visit(this);
+        }
+
+        visitVariableRef(node)
+        {
+            return node.variable.type.visit(this);
+        }
+
+        visitVectorType(node)
+        {
+            return node;
+        }
+
+        visitBlock(node)
+        {
+            throw new Error("Block has no type");
+        }
+
+        visitBoolLiteral(node)
+        {
+            throw new Error("BoolLiteral has no type");
+        }
+
+        visitBreak(node)
+        {
+            throw new Error("Break has no type");
+        }
+
+        visitConstexprTypeParameter(node)
+        {
+            throw new Error("ConstexprTypeParameter has no type");
+        }
+
+        visitContinue(node)
+        {
+            throw new Error("Continue has no type");
+        }
+
+        visitDereferenceExpression(node)
+        {
+            return node.type.visit(this);
+        }
+
+        visitDoWhileLoop(node)
+        {
+            throw new Error("DoWhileLoop has no type");
+        }
+
+        visitEnumLiteral(node)
+        {
+            throw new Error("EnumLiteral has no type");
+        }
+
+        visitEnumMember(node)
+        {
+            throw new Error("EnumMember has no type");
+        }
+
+        visitForLoop(node)
+        {
+            throw new Error("ForLoop has no type");
+        }
+        visitGenericLiteral(node)
+        {
+            return node.type.visit(this);
+        }
+        visitIfStatement(node)
+        {
+            throw new Error("IfStatement has no type");
+        }
+
+        visitProgram(node)
+        {
+            throw new Error("Program has no type");
+        }
+
+        visitProtocolDecl(node)
+        {
+            throw new Error("ProtocolDecl has no type");
+        }
+
+        visitProtocolFuncDecl(node)
+        {
+            throw new Error("ProtocolFuncDecl has no type");
+        }
+
+        visitProtocolRef(node)
+        {
+            throw new Error("ProtocolRef has no type");
+        }
+
+        visitReturn(node)
+        {
+            throw new Error("Return has no type");
+        }
+
+        visitSwitchCase(node)
+        {
+            throw new Error("SwitchCase has no type");
+        }
+
+        visitSwitchStatement(node)
+        {
+            throw new Error("SwitchStatement has no type");
+        }
+
+        visitTrapStatement(node)
+        {
+            throw new Error("TrapStatement has no type");
+        }
+
+        visitWhileLoop(node)
+        {
+            throw new Error("WhileLoop has no type");
+        }
+    }
+    return node.visit(new TypeVisitor());
+}
\ No newline at end of file
diff --git a/Tools/WebGPUShadingLanguageRI/Metal/WhlslToMsl.js b/Tools/WebGPUShadingLanguageRI/Metal/WhlslToMsl.js
new file mode 100644 (file)
index 0000000..5c85f2b
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+// Main wrapper for the compiler. Clients should use this function to compile WHLSL.
+function whlslToMsl(src)
+{
+    let parsedProgram;
+    try {
+        parsedProgram = prepare("/internal/test", 0, src);
+    } catch (e) {
+        return new MSLCompileResult(null, e, null, null);
+    }
+
+    if (!(parsedProgram instanceof Program))
+        return new MSLCompileResult(null, new Error("Compilation failed"), null, null);
+
+    const compiler = new MSLBackend(parsedProgram);
+    return compiler.compile();
+}
\ No newline at end of file
index b5c454a..23184ca 100644 (file)
@@ -168,6 +168,8 @@ class PropertyResolver extends Visitor {
         super.visitMakePtrExpression(node);
         if (!node.lValue.isLValue)
             throw new WTypeError(node.origin.originString, "Not an lvalue: " + node.lValue);
+        if (node.lValue.unifyNode instanceof DereferenceExpression)
+            node.become(new IdentityExpression(node.lValue.unifyNode.ptr));
     }
     
     visitMakeArrayRefExpression(node)
index a018146..53ab6b5 100644 (file)
@@ -39,9 +39,23 @@ function synthesizeStructAccessors(program)
                     implementationData.type.visit(visitor);
                 };
 
+                nativeFunc.visitImplementationData = (implementationData, visitor) => {
+                    // Visiting the type first ensures that the struct layout builder figures out the field's
+                    // offset.
+                    implementationData.type.visit(visitor);
+                };
+
                 nativeFunc.implementation = (argumentList, node) => {
                     return implementation(argumentList, field.offset, type.size, field.type.size);
                 };
+
+                nativeFunc.implementationData = {
+                    structType: type,
+                    name: field.name,
+                    type: field.type,
+                    offset: field.offset,
+                    size: field.type.size
+                };
             }
             
             function createFieldType()
index 5fd4bb3..aa0fb31 100644 (file)
@@ -437,6 +437,27 @@ tests.intSimpleMath = function() {
     checkInt(program, callFunction(program, "foo", [makeInt(program, 7), makeInt(program, -2)]), -3);
 }
 
+tests.incrementAndDecrement = function() {
+    let program = doPrep(`
+    test int foo1() { int x = 0; return x++; }
+    test int foo2() { int x = 0; x++; return x; }
+    test int foo3() { int x = 0; return ++x; }
+    test int foo4() { int x = 0; ++x; return x; }
+    test int foo5() { int x = 0; return x--; }
+    test int foo6() { int x = 0; x--; return x; }
+    test int foo7() { int x = 0; return --x; }
+    test int foo8() { int x = 0; --x; return x; }
+    `);
+    checkInt(program, callFunction(program, "foo1", []), 0);
+    checkInt(program, callFunction(program, "foo2", []), 1);
+    checkInt(program, callFunction(program, "foo3", []), 1);
+    checkInt(program, callFunction(program, "foo4", []), 1);
+    checkInt(program, callFunction(program, "foo5", []), 0);
+    checkInt(program, callFunction(program, "foo6", []), -1);
+    checkInt(program, callFunction(program, "foo7", []), -1);
+    checkInt(program, callFunction(program, "foo8", []), -1);
+}
+
 tests.uintSimpleMath = function() {
     let program = doPrep("test uint foo(uint x, uint y) { return x + y; }");
     checkUint(program, callFunction(program, "foo", [makeUint(program, 7), makeUint(program, 5)]), 12);
@@ -2353,6 +2374,149 @@ tests.nestedSubscriptLValueEmulationSimple = function()
     checkInt(program, callFunction(program, "testSetValuesMutateValuesAndSum", []), 5565);
 }
 
+tests.nestedSubscriptWithArraysInStructs = function()
+{
+    let program = doPrep(`
+        struct Foo {
+            int[7] array;
+        }
+        int sum(Foo foo)
+        {
+            int result = 0;
+            for (uint i = 0; i < foo.array.length; i++)
+                result += foo.array[i];
+            return result;
+        }
+        struct Bar {
+            Foo[6] array;
+        }
+        int sum(Bar bar)
+        {
+            int result = 0;
+            for (uint i = 0; i < bar.array.length; i++)
+                result += sum(bar.array[i]);
+            return result;
+        }
+        struct Baz {
+            Bar[5] array;
+        }
+        int sum(Baz baz)
+        {
+            int result = 0;
+            for (uint i = 0; i < baz.array.length; i++)
+                result += sum(baz.array[i]);
+            return result;
+        }
+        void setValues(thread Baz* baz)
+        {
+            for (uint i = 0; i < baz->array.length; i++) {
+                for (uint j = 0; j < baz->array[i].array.length; j++) {
+                    for (uint k = 0; k < baz->array[i].array[j].array.length; k++)
+                        baz->array[i].array[j].array[k] = int(i + j + k);
+                }
+            }
+        }
+        test int testSetValuesAndSum()
+        {
+            Baz baz;
+            setValues(&baz);
+            return sum(baz);
+        }
+        test int testSetValuesMutateValuesAndSum()
+        {
+            Baz baz;
+            setValues(&baz);
+            for (uint i = baz.array.length; i--;) {
+                for (uint j = baz.array[i].array.length; j--;) {
+                    for (uint k = baz.array[i].array[j].array.length; k--;)
+                        baz.array[i].array[j].array[k] = baz.array[i].array[j].array[k] * int(k);
+                }
+            }
+            return sum(baz);
+        }
+    `);
+    checkInt(program, callFunction(program, "testSetValuesAndSum", []), 1575);
+    checkInt(program, callFunction(program, "testSetValuesMutateValuesAndSum", []), 5565);
+}
+
+tests.nestedSubscript = function()
+{
+    let program = doPrep(`
+        int sum(int[7] array)
+        {
+            int result = 0;
+            for (uint i = array.length; i--;)
+                result += array[i];
+            return result;
+        }
+        int sum(int[6][7] array)
+        {
+            int result = 0;
+            for (uint i = array.length; i--;)
+                result += sum(array[i]);
+            return result;
+        }
+        int sum(int[5][6][7] array)
+        {
+            int result = 0;
+            for (uint i = array.length; i--;)
+                result += sum(array[i]);
+            return result;
+        }
+        void setValues(thread int[][6][7] array)
+        {
+            for (uint i = array.length; i--;) {
+                for (uint j = array[i].length; j--;) {
+                    for (uint k = array[i][j].length; k--;)
+                        array[i][j][k] = int(i + j + k);
+                }
+            }
+        }
+        test int testSetValuesAndSum()
+        {
+            int[5][6][7] array;
+            setValues(@array);
+            return sum(array);
+        }
+        test int testSetValuesMutateValuesAndSum()
+        {
+            int[5][6][7] array;
+            setValues(@array);
+            for (uint i = array.length; i--;) {
+                for (uint j = array[i].length; j--;) {
+                    for (uint k = array[i][j].length; k--;)
+                        array[i][j][k] = array[i][j][k] * int(k);
+                }
+            }
+            return sum(array);
+        }
+    `);
+    checkInt(program, callFunction(program, "testSetValuesAndSum", []), 1575);
+    checkInt(program, callFunction(program, "testSetValuesMutateValuesAndSum", []), 5565);
+}
+
+tests.lotsOfLocalVariables = function()
+{
+    let src = "test int sum() {\n";
+    src += "    int i = 0;\n";
+    let target = 0;
+    const numVars = 1024;
+    for (let i = 0; i < numVars; i++) {
+        const value = i * 3;
+        src += `   i = ${i};\n`;
+        src += `   int V${i} = (i + 3) * (i + 3);\n`;
+        target += (i + 3) * (i + 3);
+    }
+    src += "    int result = 0;\n";
+    for (let i = 0; i < numVars; i++) {
+        src += `    result += V${i};\n`;
+    }
+    src += "    return result;\n";
+    src += "}";
+    let program = doPrep(src);
+    checkInt(program, callFunction(program, "sum", []), target);
+}
+
 tests.operatorBool = function()
 {
     let program = doPrep(`
@@ -7939,13 +8103,6 @@ function* doTest(testFilter)
         throw new Error("Test setup is incomplete.");
     let before = preciseTime();
 
-    print("Compiling standard library...");
-    const compileBefore = preciseTime();
-    yield;
-    prepare();
-    const compileAfter = preciseTime();
-    print(`    OK, took ${Math.round((compileAfter - compileBefore) * 1000)} ms`);
-
     let names = [];
     for (let s in tests)
         names.push(s);
index 2db158d..9a5e56e 100644 (file)
@@ -36,6 +36,11 @@ class Type extends Node {
     get isEnum() { return false; }
     get isPrimitive() { return false; }
     
+    get arrayRefType()
+    {
+        return new ArrayRefType(this.origin, "thread", this);
+    }
+
     // Have to call these on the unifyNode.
     argumentForAndOverload(origin, value)
     {