Emit SPIR-V from WSL compiler (Part 1)
authormmaxfield@apple.com <mmaxfield@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 12 Oct 2017 18:45:04 +0000 (18:45 +0000)
committermmaxfield@apple.com <mmaxfield@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 12 Oct 2017 18:45:04 +0000 (18:45 +0000)
https://bugs.webkit.org/show_bug.cgi?id=177998

Reviewed by Filip Pizlo.

This patch implements the first half of a SPIR-V codegen phase for WSL.
This includes all the operations which aren't actually emitting the contents
of functions themselves. For example, this includes things like representing
WSL types with SPIR-V types, and declaring shaders' inputs and outputs. A
future patch will actually emit the contents of functions.

There are two helper visitors here: SPIRVTypeAnalyzer which generates SPIR-V
types from WSL types, and SPIRVPrimitiveVariableAnalyzer which identifies
input and output variables from a shader (and assigns location values to
each one).

This patch is currently pursuing the "logical mode" detailed in
https://bugs.webkit.org/show_bug.cgi?id=176967. In this mode, each pointer and
array reference operation can be statically traced to the variable or array it
is operating on.

This has the interesting property where accessing a pointer inside an array is
forbidden, because the array index may be computed at runtime, so the compiler
can't know at compile time which variable the pointer operation will be
accessing. However, this isn't true for structs; the program must statically
state which struct member it is accessing. Therefore, pointers or array
references must not transitively appear within an array, but they may appear
within a struct. The same logic applies to array references; those get lowered
to just two indexes in SPIR-V (a lower bound and an upper bound).

So, outside of an array, SPIR-V types don't need to include any pointers because
any operation with the pointer doesn't need access to the runtime value of the
pointer. Inside of an array, pointers are forbidden. Therefore, SPIR-V types
will never include any pointers.

This means that, for example, WSL can represent a linked list in logical mode.
However, a WSL program cannot iterate across the list, because that would require
assigning to a pointer. So instead, a program using a linked list could only say
something like "list.ptr->ptr->ptr->value".

* WebGPUShadingLanguageRI/LateChecker.js:
(LateChecker.prototype._checkShaderType):
* WebGPUShadingLanguageRI/SPIR-V.js:
(SPIRV.OperandChecker.prototype._isStar):
(SPIRV.OperandChecker.prototype.nextComparisonType):
(SPIRV.OperandChecker.prototype.finalize):
(SPIRV.OperandChecker):
* WebGPUShadingLanguageRI/SPIRV.html:
* WebGPUShadingLanguageRI/SPIRVCodegen.js: Added.
(findEntryPoints):
(emitTypes.doEmitTypes):
(emitTypes):
(ConstantFinder.prototype.visitGenericLiteralType):
(ConstantFinder):
(generateSPIRV):
* WebGPUShadingLanguageRI/SPIRVTypeAnalyzer.js: Added.
(SPIRVTypeAnalyzer):
(SPIRVTypeAnalyzer.prototype.get program):
(SPIRVTypeAnalyzer.prototype.get typeMap):
(SPIRVTypeAnalyzer.prototype.get currentId):
(SPIRVTypeAnalyzer.prototype.get stack):
(SPIRVTypeAnalyzer.prototype.visitTypeRef):
(SPIRVTypeAnalyzer.prototype._encounterType):
(SPIRVTypeAnalyzer.prototype.visitNullType):
(SPIRVTypeAnalyzer.prototype.visitGenericLiteralType):
(SPIRVTypeAnalyzer.prototype.visitNativeType):
(SPIRVTypeAnalyzer.prototype.visitEnumType):
(SPIRVTypeAnalyzer.prototype.visitPtrType):
(SPIRVTypeAnalyzer.prototype.visitArrayRefType):
(SPIRVTypeAnalyzer.prototype.visitArrayType):
(SPIRVTypeAnalyzer.prototype.visitStructType):
* WebGPUShadingLanguageRI/SPIRVVariableAnalyzer.js: Added.
(SPIRVPrimitiveVariableAnalyzer):
(SPIRVPrimitiveVariableAnalyzer.prototype.get program):
(SPIRVPrimitiveVariableAnalyzer.prototype.get typeMap):
(SPIRVPrimitiveVariableAnalyzer.prototype.get currentId):
(SPIRVPrimitiveVariableAnalyzer.prototype.get currentLocation):
(SPIRVPrimitiveVariableAnalyzer.prototype.get nameComponents):
(SPIRVPrimitiveVariableAnalyzer.prototype.get result):
(SPIRVPrimitiveVariableAnalyzer.prototype.visitTypeRef):
(SPIRVPrimitiveVariableAnalyzer.prototype.visitNullType):
(SPIRVPrimitiveVariableAnalyzer.prototype.visitGenericLiteralType):
(SPIRVPrimitiveVariableAnalyzer.prototype.visitNativeType):
(SPIRVPrimitiveVariableAnalyzer.prototype.visitEnumType):
(SPIRVPrimitiveVariableAnalyzer.prototype.visitPtrType):
(SPIRVPrimitiveVariableAnalyzer.prototype.visitArrayRefType):
(SPIRVPrimitiveVariableAnalyzer.prototype.visitArrayType):
(SPIRVPrimitiveVariableAnalyzer.prototype.visitStructType):
* WebGPUShadingLanguageRI/WSL.md:
* WebGPUShadingLanguageRI/index.html:

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

Tools/ChangeLog
Tools/WebGPUShadingLanguageRI/LateChecker.js
Tools/WebGPUShadingLanguageRI/SPIR-V.js
Tools/WebGPUShadingLanguageRI/SPIRV.html
Tools/WebGPUShadingLanguageRI/SPIRVCodegen.js [new file with mode: 0644]
Tools/WebGPUShadingLanguageRI/SPIRVTypeAnalyzer.js [new file with mode: 0644]
Tools/WebGPUShadingLanguageRI/SPIRVVariableAnalyzer.js [new file with mode: 0644]
Tools/WebGPUShadingLanguageRI/WSL.md
Tools/WebGPUShadingLanguageRI/index.html

index 5889b5d..a7e0bdd 100644 (file)
@@ -1,3 +1,96 @@
+2017-10-12  Myles C. Maxfield  <mmaxfield@apple.com>
+
+        Emit SPIR-V from WSL compiler (Part 1)
+        https://bugs.webkit.org/show_bug.cgi?id=177998
+
+        Reviewed by Filip Pizlo.
+
+        This patch implements the first half of a SPIR-V codegen phase for WSL.
+        This includes all the operations which aren't actually emitting the contents
+        of functions themselves. For example, this includes things like representing
+        WSL types with SPIR-V types, and declaring shaders' inputs and outputs. A
+        future patch will actually emit the contents of functions.
+
+        There are two helper visitors here: SPIRVTypeAnalyzer which generates SPIR-V
+        types from WSL types, and SPIRVPrimitiveVariableAnalyzer which identifies
+        input and output variables from a shader (and assigns location values to
+        each one).
+
+        This patch is currently pursuing the "logical mode" detailed in
+        https://bugs.webkit.org/show_bug.cgi?id=176967. In this mode, each pointer and
+        array reference operation can be statically traced to the variable or array it
+        is operating on.
+
+        This has the interesting property where accessing a pointer inside an array is
+        forbidden, because the array index may be computed at runtime, so the compiler
+        can't know at compile time which variable the pointer operation will be
+        accessing. However, this isn't true for structs; the program must statically
+        state which struct member it is accessing. Therefore, pointers or array
+        references must not transitively appear within an array, but they may appear
+        within a struct. The same logic applies to array references; those get lowered
+        to just two indexes in SPIR-V (a lower bound and an upper bound).
+
+        So, outside of an array, SPIR-V types don't need to include any pointers because
+        any operation with the pointer doesn't need access to the runtime value of the
+        pointer. Inside of an array, pointers are forbidden. Therefore, SPIR-V types
+        will never include any pointers.
+
+        This means that, for example, WSL can represent a linked list in logical mode.
+        However, a WSL program cannot iterate across the list, because that would require
+        assigning to a pointer. So instead, a program using a linked list could only say
+        something like "list.ptr->ptr->ptr->value".
+
+        * WebGPUShadingLanguageRI/LateChecker.js:
+        (LateChecker.prototype._checkShaderType):
+        * WebGPUShadingLanguageRI/SPIR-V.js:
+        (SPIRV.OperandChecker.prototype._isStar):
+        (SPIRV.OperandChecker.prototype.nextComparisonType):
+        (SPIRV.OperandChecker.prototype.finalize):
+        (SPIRV.OperandChecker):
+        * WebGPUShadingLanguageRI/SPIRV.html:
+        * WebGPUShadingLanguageRI/SPIRVCodegen.js: Added.
+        (findEntryPoints):
+        (emitTypes.doEmitTypes):
+        (emitTypes):
+        (ConstantFinder.prototype.visitGenericLiteralType):
+        (ConstantFinder):
+        (generateSPIRV):
+        * WebGPUShadingLanguageRI/SPIRVTypeAnalyzer.js: Added.
+        (SPIRVTypeAnalyzer):
+        (SPIRVTypeAnalyzer.prototype.get program):
+        (SPIRVTypeAnalyzer.prototype.get typeMap):
+        (SPIRVTypeAnalyzer.prototype.get currentId):
+        (SPIRVTypeAnalyzer.prototype.get stack):
+        (SPIRVTypeAnalyzer.prototype.visitTypeRef):
+        (SPIRVTypeAnalyzer.prototype._encounterType):
+        (SPIRVTypeAnalyzer.prototype.visitNullType):
+        (SPIRVTypeAnalyzer.prototype.visitGenericLiteralType):
+        (SPIRVTypeAnalyzer.prototype.visitNativeType):
+        (SPIRVTypeAnalyzer.prototype.visitEnumType):
+        (SPIRVTypeAnalyzer.prototype.visitPtrType):
+        (SPIRVTypeAnalyzer.prototype.visitArrayRefType):
+        (SPIRVTypeAnalyzer.prototype.visitArrayType):
+        (SPIRVTypeAnalyzer.prototype.visitStructType):
+        * WebGPUShadingLanguageRI/SPIRVVariableAnalyzer.js: Added.
+        (SPIRVPrimitiveVariableAnalyzer):
+        (SPIRVPrimitiveVariableAnalyzer.prototype.get program):
+        (SPIRVPrimitiveVariableAnalyzer.prototype.get typeMap):
+        (SPIRVPrimitiveVariableAnalyzer.prototype.get currentId):
+        (SPIRVPrimitiveVariableAnalyzer.prototype.get currentLocation):
+        (SPIRVPrimitiveVariableAnalyzer.prototype.get nameComponents):
+        (SPIRVPrimitiveVariableAnalyzer.prototype.get result):
+        (SPIRVPrimitiveVariableAnalyzer.prototype.visitTypeRef):
+        (SPIRVPrimitiveVariableAnalyzer.prototype.visitNullType):
+        (SPIRVPrimitiveVariableAnalyzer.prototype.visitGenericLiteralType):
+        (SPIRVPrimitiveVariableAnalyzer.prototype.visitNativeType):
+        (SPIRVPrimitiveVariableAnalyzer.prototype.visitEnumType):
+        (SPIRVPrimitiveVariableAnalyzer.prototype.visitPtrType):
+        (SPIRVPrimitiveVariableAnalyzer.prototype.visitArrayRefType):
+        (SPIRVPrimitiveVariableAnalyzer.prototype.visitArrayType):
+        (SPIRVPrimitiveVariableAnalyzer.prototype.visitStructType):
+        * WebGPUShadingLanguageRI/WSL.md:
+        * WebGPUShadingLanguageRI/index.html:
+
 2017-09-29  Filip Pizlo  <fpizlo@apple.com>
 
         Enable gigacage on iOS
index 8140a22..5d1ef95 100644 (file)
@@ -36,6 +36,7 @@ class LateChecker extends Visitor {
     
     _checkShaderType(node)
     {
+        // FIXME: Tighten these checks. For now, we should only accept int32, uint32, float32, and float64.
         let assertPrimitive = type => {
             if (!type.isPrimitive)
                 throw new WTypeError(node.origin.originString, "Shader signature cannot include non-primitive type: " + type);
index 8fa93f2..80ca704 100644 (file)
@@ -38,7 +38,7 @@ function SPIRV(json) {
         switch (kind.category) {
         case "BitEnum":
         case "ValueEnum":
-            let enumerants = {};
+            let enumerants = { category: kind.category };
             for (let enumerant of kind.enumerants) {
                 enumerants[enumerant.enumerant] = enumerant;
             }
@@ -112,20 +112,24 @@ function SPIRV(json) {
             this._parameters = [];
         }
 
-        nextComparisonType(operand)
+        _isStar(operandInfo)
         {
-            if (this._operandInfoIndex >= this._operandInfos.length)
-                throw new Error("Specified operand does not correspond to any that the instruction expects.");
-            let operandInfo = this._operandInfos[this._operandInfoIndex];
-
-            let isStar = operandInfo.quantifier && operandInfo.quantifier == "*";
             switch (operandInfo.kind) {
                 case "LiteralContextDependentNumber":
                 case "LiteralSpecConstantOpInteger":
                     // These types can be any width.
-                    isStar = true;
-                    break;
+                    return true;
             }
+            return operandInfo.quantifier && operandInfo.quantifier == "*";
+        }
+
+        nextComparisonType(operand)
+        {
+            if (this._operandInfoIndex >= this._operandInfos.length)
+                throw new Error("Specified operand does not correspond to any that the instruction expects.");
+            let operandInfo = this._operandInfos[this._operandInfoIndex];
+
+            let isStar = this._isStar(operandInfo);
 
             if (this._parameters.length != 0) {
                 let result = this._parameters[0];
@@ -176,8 +180,9 @@ function SPIRV(json) {
             if (this._parameters.length != 0)
                 throw new Error("Operand not specified for parameter.");
             for (let i = this._operandInfoIndex; i < this._operandInfos.length; ++i) {
-                let quantifier = this._operandInfos[i].quantifier;
-                if (quantifier != "?" && quantifier != "*")
+                let operandInfo = this._operandInfos[i];
+                let quantifier = operandInfo.quantifier;
+                if (quantifier != "?" && !this._isStar(operandInfo))
                     throw new Error("Did not specify operand " + i + " to instruction.");
             }
         }
@@ -190,7 +195,6 @@ function SPIRV(json) {
         result.ops[attributeName] = class {
             constructor(...operands)
             {
-                // FIXME: Handle OpConstant, OpSpecConstant, OpSpecConstantOp specially.
                 let operandChecker = new OperandChecker(instruction.operands);
                 for (let operand of operands)
                     operandChecker.check(operand);
index 21ac4e2..d173f7e 100644 (file)
 <!DOCTYPE html>
 <html>
 <head>
-<script src="SPIR-V.js"></script>
+<style>
+td {
+    border: solid black 1px;
+}
+</style>
+    <!-- FIXME: Migrate to ES6 modules -->
+    <script src="Node.js"></script>
+    <script src="Type.js"></script>
+    <script src="ReferenceType.js"></script>
+    <script src="Value.js"></script>
+    <script src="Expression.js"></script>
+    <script src="Rewriter.js"></script>
+    <script src="Visitor.js"></script>
+    <script src="CreateLiteral.js"></script>
+    <script src="CreateLiteralType.js"></script>
+    <script src="PropertyAccessExpression.js"></script>
+
+    <script src="AddressSpace.js"></script>
+    <script src="AnonymousVariable.js"></script>
+    <script src="ArrayRefType.js"></script>
+    <script src="ArrayType.js"></script>
+    <script src="Assignment.js"></script>
+    <script src="AutoWrapper.js"></script>
+    <script src="Block.js"></script>
+    <script src="BoolLiteral.js"></script>
+    <script src="Break.js"></script>
+    <script src="CallExpression.js"></script>
+    <script src="CallFunction.js"></script>
+    <script src="Check.js"></script>
+    <script src="CheckLiteralTypes.js"></script>
+    <script src="CheckLoops.js"></script>
+    <script src="CheckRecursion.js"></script>
+    <script src="CheckRecursiveTypes.js"></script>
+    <script src="CheckReturns.js"></script>
+    <script src="CheckUnreachableCode.js"></script>
+    <script src="CheckWrapped.js"></script>
+    <script src="Checker.js"></script>
+    <script src="CloneProgram.js"></script>
+    <script src="CommaExpression.js"></script>
+    <script src="ConstexprFolder.js"></script>
+    <script src="ConstexprTypeParameter.js"></script>
+    <script src="Continue.js"></script>
+    <script src="ConvertPtrToArrayRefExpression.js"></script>
+    <script src="DoWhileLoop.js"></script>
+    <script src="DotExpression.js"></script>
+    <script src="DoubleLiteral.js"></script>
+    <script src="DoubleLiteralType.js"></script>
+    <script src="DereferenceExpression.js"></script>
+    <script src="EArrayRef.js"></script>
+    <script src="EBuffer.js"></script>
+    <script src="EBufferBuilder.js"></script>
+    <script src="EPtr.js"></script>
+    <script src="EnumLiteral.js"></script>
+    <script src="EnumMember.js"></script>
+    <script src="EnumType.js"></script>
+    <script src="EvaluationCommon.js"></script>
+    <script src="Evaluator.js"></script>
+    <script src="ExpressionFinder.js"></script>
+    <script src="ExternalOrigin.js"></script>
+    <script src="Field.js"></script>
+    <script src="FindHighZombies.js"></script>
+    <script src="FlattenProtocolExtends.js"></script>
+    <script src="FlattenedStructOffsetGatherer.js"></script>
+    <script src="FloatLiteral.js"></script>
+    <script src="FloatLiteralType.js"></script>
+    <script src="FoldConstexprs.js"></script>
+    <script src="ForLoop.js"></script>
+    <script src="Func.js"></script>
+    <script src="FuncDef.js"></script>
+    <script src="FuncInstantiator.js"></script>
+    <script src="FuncParameter.js"></script>
+    <script src="FunctionLikeBlock.js"></script>
+    <script src="HighZombieFinder.js"></script>
+    <script src="IdentityExpression.js"></script>
+    <script src="IfStatement.js"></script>
+    <script src="IndexExpression.js"></script>
+    <script src="InferTypesForCall.js"></script>
+    <script src="Inline.js"></script>
+    <script src="Inliner.js"></script>
+    <script src="InstantiateImmediates.js"></script>
+    <script src="IntLiteral.js"></script>
+    <script src="IntLiteralType.js"></script>
+    <script src="Intrinsics.js"></script>
+    <script src="LateChecker.js"></script>
+    <script src="Lexer.js"></script>
+    <script src="LexerToken.js"></script>
+    <script src="LiteralTypeChecker.js"></script>
+    <script src="LogicalExpression.js"></script>
+    <script src="LogicalNot.js"></script>
+    <script src="LoopChecker.js"></script>
+    <script src="MakeArrayRefExpression.js"></script>
+    <script src="MakePtrExpression.js"></script>
+    <script src="NameContext.js"></script>
+    <script src="NameFinder.js"></script>
+    <script src="NameResolver.js"></script>
+    <script src="NativeFunc.js"></script>
+    <script src="NativeFuncInstance.js"></script>
+    <script src="NativeType.js"></script>
+    <script src="NativeTypeInstance.js"></script>
+    <script src="NormalUsePropertyResolver.js"></script>
+    <script src="NullLiteral.js"></script>
+    <script src="NullType.js"></script>
+    <script src="OriginKind.js"></script>
+    <script src="OverloadResolutionFailure.js"></script>
+    <script src="Parse.js"></script>
+    <script src="Prepare.js"></script>
+    <script src="PropertyResolver.js"></script>
+    <script src="Program.js"></script>
+    <script src="ProgramWithUnnecessaryThingsRemoved.js"></script>
+    <script src="Protocol.js"></script>
+    <script src="ProtocolDecl.js"></script>
+    <script src="ProtocolFuncDecl.js"></script>
+    <script src="ProtocolRef.js"></script>
+    <script src="PtrType.js"></script>
+    <script src="ReadModifyWriteExpression.js"></script>
+    <script src="RecursionChecker.js"></script>
+    <script src="RecursiveTypeChecker.js"></script>
+    <script src="ResolveNames.js"></script>
+    <script src="ResolveOverloadImpl.js"></script>
+    <script src="ResolveProperties.js"></script>
+    <script src="ResolveTypeDefs.js"></script>
+    <script src="Return.js"></script>
+    <script src="ReturnChecker.js"></script>
+    <script src="ReturnException.js"></script>
+    <script src="SPIR-V.js"></script>
+    <script src="SPIRVCodegen.js"></script>
+    <script src="SPIRVTypeAnalyzer.js"></script>
+    <script src="SPIRVVariableAnalyzer.js"></script>
+    <script src="StandardLibrary.js"></script>
+    <script src="StatementCloner.js"></script>
+    <script src="StructLayoutBuilder.js"></script>
+    <script src="StructType.js"></script>
+    <script src="Substitution.js"></script>
+    <script src="SwitchCase.js"></script>
+    <script src="SwitchStatement.js"></script>
+    <script src="SynthesizeEnumFunctions.js"></script>
+    <script src="SynthesizeStructAccessors.js"></script>
+    <script src="TrapStatement.js"></script>
+    <script src="TypeDef.js"></script>
+    <script src="TypeDefResolver.js"></script>
+    <script src="TypeOrVariableRef.js"></script>
+    <script src="TypeParameterRewriter.js"></script>
+    <script src="TypeRef.js"></script>
+    <script src="TypeVariable.js"></script>
+    <script src="TypeVariableTracker.js"></script>
+    <script src="TypedValue.js"></script>
+    <script src="UintLiteral.js"></script>
+    <script src="UintLiteralType.js"></script>
+    <script src="UnificationContext.js"></script>
+    <script src="UnreachableCodeChecker.js"></script>
+    <script src="VariableDecl.js"></script>
+    <script src="VariableRef.js"></script>
+    <script src="VisitingSet.js"></script>
+    <script src="WSyntaxError.js"></script>
+    <script src="WTrapError.js"></script>
+    <script src="WTypeError.js"></script>
+    <script src="WhileLoop.js"></script>
+    <script src="WrapChecker.js"></script>
 <script>
+    let code = `
+struct VertexInput {
+    float2 position;
+    float3 color;
+}
+
+struct VertexOutput {
+    float4 wsl_Position;
+    float4 color;
+}
+
+struct FragmentOutput {
+    float4 wsl_Color;
+}
+
+vertex VertexOutput vertexShader(VertexInput vertexInput) {
+    VertexOutput result;
+    result.wsl_Position = float4(vertexInput.position, 0., 1.);
+    result.color = float4(vertexInput.color, 1);
+    
+    int[10] a;
+    int[10u] b;
+    return result;
+}
+
+fragment FragmentOutput fragmentShader(VertexOutput stageIn) {
+    FragmentOutput result;
+    result.wsl_Color = stageIn.color;
+    return result;
+}`;
 
     window.addEventListener("load", function () {
         fetch("spirv.core.grammar.json").then(response => response.json()).then(function (json) {
             let spirv = SPIRV(json);
-            let assembler = new SPIRVAssembler();
-            assembler.append(new spirv.ops.Capability(spirv.kinds.Capability.Shader));
-            assembler.append(new spirv.ops.MemoryModel(spirv.kinds.AddressingModel.Logical, spirv.kinds.MemoryModel.GLSL450));
-            assembler.append(new spirv.ops.EntryPoint(spirv.kinds.ExecutionModel.Fragment, 5, "main", 10, 17));
-            assembler.append(new spirv.ops.ExecutionMode(5, spirv.kinds.ExecutionMode.OriginLowerLeft));
-            assembler.append(new spirv.ops.Decorate(10, spirv.kinds.Decoration.Location, 0));
-            assembler.append(new spirv.ops.Decorate(14, spirv.kinds.Decoration.DescriptorSet, 0));
-            assembler.append(new spirv.ops.Decorate(14, spirv.kinds.Decoration.Binding, 1));
-            assembler.append(new spirv.ops.Decorate(17, spirv.kinds.Decoration.Location, 0));
-            assembler.append(new spirv.ops.TypeVoid(3));
-            assembler.append(new spirv.ops.TypeFunction(4, 3));
-            assembler.append(new spirv.ops.TypeFloat(7, 32));
-            assembler.append(new spirv.ops.TypeVector(8, 7, 4));
-            assembler.append(new spirv.ops.TypePointer(9, spirv.kinds.StorageClass.Output, 8));
-            assembler.append(new spirv.ops.Variable(9, 10, spirv.kinds.StorageClass.Output));
-            assembler.append(new spirv.ops.TypeImage(11, 7, spirv.kinds.Dim["2D"], 0, 0, 0, 1, spirv.kinds.ImageFormat.Unknown));
-            assembler.append(new spirv.ops.TypeSampledImage(12, 11));
-            assembler.append(new spirv.ops.TypePointer(13, spirv.kinds.StorageClass.UniformConstant, 12));
-            assembler.append(new spirv.ops.Variable(13, 14, spirv.kinds.StorageClass.UniformConstant));
-            assembler.append(new spirv.ops.TypePointer(16, spirv.kinds.StorageClass.Input, 8));
-            assembler.append(new spirv.ops.Variable(16, 17, spirv.kinds.StorageClass.Input));
-            assembler.append(new spirv.ops.TypeVector(18, 7, 2));
-            assembler.append(new spirv.ops.Function(3, 5, spirv.kinds.FunctionControl.None, 4));
-            assembler.append(new spirv.ops.Label(6));
-            assembler.append(new spirv.ops.Load(12, 15, 14));
-            assembler.append(new spirv.ops.Load(8, 19, 17));
-            assembler.append(new spirv.ops.VectorShuffle(18, 20, 19, 19, 0, 1));
-            assembler.append(new spirv.ops.ImageSampleImplicitLod(8, 21, 15, 20));
-            assembler.append(new spirv.ops.Store(10, 21));
-            assembler.append(new spirv.ops.Return());
-            assembler.append(new spirv.ops.FunctionEnd());
-            let result = assembler.result;
-            
+            let program = prepare("/internal/test", 0, code);
+            let result = generateSPIRV(spirv, program);
+            return result;
+        }).then(function(result) {
             let resultElement = document.getElementById("Result");
             while (resultElement.childNodes.length != 0)
                 resultElement.removeChild(resultElement.childNodes[0]);
             let anchor = document.createElement("a");
-            let blob = new Blob([result], { type: "application/octet-binary" });
+            let blob = new Blob([result.file], { type: "application/octet-binary" });
             anchor.href = URL.createObjectURL(blob);
             anchor.download = "result.spv";
             anchor.textContent = "Download generated SPIR-V";
             resultElement.appendChild(anchor);
+
+            let table = document.createElement("table");
+            let tableHeader = document.createElement("thead");
+            let headerRow = document.createElement("tr");
+            let nameHeader = document.createElement("th");
+            nameHeader.textContent = "Attribute name";
+            let locationHeader = document.createElement("th");
+            locationHeader.textContent = "Location";
+            headerRow.appendChild(nameHeader);
+            headerRow.appendChild(locationHeader);
+            tableHeader.appendChild(headerRow);
+            table.appendChild(tableHeader);
+            for (let location of result.locations) {
+                let tableRow = document.createElement("tr");
+                let nameElement = document.createElement("td");
+                nameElement.textContent = location.name;
+                let locationElement = document.createElement("td");
+                locationElement.textContent = location.location;
+                tableRow.appendChild(nameElement);
+                tableRow.appendChild(locationElement);
+                table.appendChild(tableRow);
+            }
+            resultElement.appendChild(table);
         }).then(undefined, function () {
             let resultElement = document.getElementById("Result");
             while (resultElement.childNodes.length != 0)
diff --git a/Tools/WebGPUShadingLanguageRI/SPIRVCodegen.js b/Tools/WebGPUShadingLanguageRI/SPIRVCodegen.js
new file mode 100644 (file)
index 0000000..0b54ca9
--- /dev/null
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2017 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 generateSPIRV(spirv, program)
+{
+
+    function findEntryPoints()
+    {
+        let entryPoints = [];
+        for (let functionNames of program.functions.values()) {
+            for (let func of functionNames) {
+                switch (func.shaderType) {
+                case "vertex":
+                case "fragment":
+                    entryPoints.push(func);
+                    break;
+                }
+            }
+        }
+        return entryPoints;
+    }
+
+    let currentId = 3;
+    let currentLocation = 0;
+    let typeMap = new Map();
+    let reverseTypeMap = new Map();
+    let entryPoints = [];
+
+    typeMap.set(program.intrinsics.void, currentId++);
+    typeMap.set(program.intrinsics.uint32, currentId++);
+
+    for (let entryPoint of findEntryPoints()) {
+        let inlinedShader = program.funcInstantiator.getUnique(entryPoint, []);
+        _inlineFunction(program, inlinedShader, new VisitingSet(entryPoint));
+
+        let typeAnalyzer = new SPIRVTypeAnalyzer(program, typeMap, currentId);
+        inlinedShader.visit(typeAnalyzer);
+        currentId = typeAnalyzer.currentId;
+
+        currentLocation = 0;
+        let valueAnalyzer = new SPIRVPrimitiveVariableAnalyzer(program, typeMap, currentId, currentLocation);
+        inlinedShader.returnType.visit(valueAnalyzer);
+        currentId = valueAnalyzer.currentId;
+        let outputValues = valueAnalyzer.result;
+
+        let inputValues = [];
+        for (let parameter of inlinedShader.parameters) {
+            if (parameter.type.type instanceof StructType) {
+                let valueAnalyzer = new SPIRVPrimitiveVariableAnalyzer(program, typeMap, currentId, currentLocation, parameter.name);
+                parameter.visit(valueAnalyzer);
+                currentId = valueAnalyzer.currentId;
+                currentLocation = valueAnalyzer.currentLocation;
+                for (let inputValue of valueAnalyzer.result)
+                    inputValues.push(inputValue);
+            } else if (parameter.type.type instanceof ArrayRefType) {
+                // FIXME: Implement this.
+            }
+        }
+
+        entryPoints.push({ id: currentId++, shader: inlinedShader, inputs: inputValues, outputs: outputValues });
+    }
+    
+    for (let type of typeMap) {
+        if (typeof type[1] == "object")
+            reverseTypeMap.set(type[1].id, type[0]);
+        else
+            reverseTypeMap.set(type[1], type[0]);
+    }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+    function emitTypes(assembler) {
+        let emittedTypes = new Set();
+        function doEmitTypes(type)
+        {
+            if (emittedTypes.has(type[0]))
+                return;
+            emittedTypes.add(type[0]);
+            if (typeof type[1] == "object") {
+                if (type[1].fieldTypes) {
+                    for (let fieldType of type[1].fieldTypes) {
+                        let key = reverseTypeMap.get(fieldType);
+                        let value = typeMap.get(key);
+                        doEmitTypes([key, value]);
+                    }
+                    switch (type[0]) {
+                    case "struct vec2<> { int32 x; int32 y }":
+                    case "struct vec2<> { uint32 x; uint32 y; }":
+                    case "struct vec2<> { float32 x; float32 y; }":
+                    case "struct vec2<> { float64 x; float64 y; }":
+                    case "struct vec3<> { int32 x; int32 y; int32 z; }":
+                    case "struct vec3<> { uint32 x; uint32 y; uint32 z; }":
+                    case "struct vec3<> { float32 x; float32 y; float32 z; }":
+                    case "struct vec3<> { float64 x; float64 y; float64 z; }":
+                    case "struct vec4<> { int32 x; int32 y; int32 z; int32 w; }":
+                    case "struct vec4<> { uint32 x; uint32 y; uint32 z; uint32 w; }":
+                    case "struct vec4<> { float32 x; float32 y; float32 z; float32 w; }":
+                    case "struct vec4<> { float64 x; float64 y; float64 z; float64 w; }":
+                        assembler.append(new spirv.ops.TypeVector(type[1].id, type[1].fieldTypes[0], type[1].fieldTypes.length));
+                        break;
+                    default:
+                        assembler.append(new spirv.ops.TypeStruct(type[1].id, ...type[1].fieldTypes));
+                        break;
+                    }
+                } else {
+                    if (!type[1].elementType)
+                        throw new Error("Unknown type!");
+            
+                    let elementType = type[1].elementType;
+                    let key = reverseTypeMap.get(elementType);
+                    let value = typeMap.get(key);
+                    doEmitTypes([key, value]);
+
+                    let id = currentId++;
+                    assembler.append(new spirv.ops.Constant(typeMap.get(program.intrinsics.uint32), id, type[1].numElements));
+                    assembler.append(new spirv.ops.TypeArray(type[1].id, elementType, id));
+                }
+            } else {
+                switch (type[0].name) {
+                case "void":
+                    assembler.append(new spirv.ops.TypeVoid(type[1]));
+                    break;
+                case "bool":
+                    assembler.append(new spirv.ops.TypeBool(type[1]));
+                    break;
+                case "int32":
+                    assembler.append(new spirv.ops.TypeInt(type[1], 32, 1));
+                    break;
+                case "uint32":
+                case "uint8":
+                    assembler.append(new spirv.ops.TypeInt(type[1], 32, 0));
+                    break;
+                case "float32":
+                    assembler.append(new spirv.ops.TypeFloat(type[1], 32));
+                    break;
+                case "float64":
+                    assembler.append(new spirv.ops.TypeFloat(type[1], 64));
+                    break;
+                }
+            }
+        }
+        doEmitTypes([program.intrinsics.uint32, typeMap.get(program.intrinsics.uint32)]);
+        for (let type of typeMap)
+            doEmitTypes(type)
+    }
+
+
+
+
+
+
+
+
+
+
+
+
+    let constants = new Map();
+    class ConstantFinder extends Visitor {
+        visitGenericLiteralType(node)
+        {
+            let type = node.type;
+            while (type instanceof TypeRef)
+                type = type.type;
+            let values;
+            switch (type) {
+            case program.intrinsics.bool:
+                values = [node.value];
+                break;
+            case program.intrinsics.int32:
+            case program.intrinsics.uint32:
+            case program.intrinsics.uint8:
+                values = [node.value]
+                break;
+            case program.intrinsics.float: {
+                let arrayBuffer = new ArrayBuffer(Math.max(Uint32Array.BYTES_PER_ELEMENT, Float32Array.BYTES_PER_ELEMENT));
+                let floatView = new Float32Array(arrayBuffer);
+                let uintView = new Uint32Array(arrayBuffer);
+                floatView[0] = node.value;
+                values = uintView;
+                break;
+            }
+            case program.intrinsics.double: {
+                let arrayBuffer = new ArrayBuffer(Math.max(Uint32Array.BYTES_PER_ELEMENT, Float64Array.BYTES_PER_ELEMENT));
+                let doubleView = new Float64Array(arrayBuffer);
+                let uintView = new Uint32Array(arrayBuffer);
+                doubleView[0] = node.value;
+                values = uintView;
+                break;
+            }
+            default:
+                throw new Error("Unrecognized literal.");
+            }
+            constants.set(node, { id: currentId++, typeId: typeMap.get(type), type: type, values: values });
+        }
+    }
+    for (let entryPoint of entryPoints)
+        entryPoint.shader.visit(new ConstantFinder());
+
+
+
+
+
+
+
+
+
+
+
+
+    let assembler = new SPIRVAssembler();
+    // 1. All OpCapability instructions
+    assembler.append(new spirv.ops.Capability(spirv.kinds.Capability.Shader));
+    assembler.append(new spirv.ops.Capability(spirv.kinds.Capability.Float64));
+    // 2. Optional OpExtension instructions
+    // 3. Optional OpExtInstImport instructions
+    // 4. The single required OpMemoryModel instruction
+    // FIXME: Figure out if we can use the Simple memory model instead of the GLSL memory model.
+    // The spec says nothing about what the difference between them is. 💯
+    assembler.append(new spirv.ops.MemoryModel(spirv.kinds.AddressingModel.Logical, spirv.kinds.MemoryModel.GLSL450));
+
+    // 5. All entry point declarations
+    for (let entryPoint of entryPoints) {
+        let executionModel;
+        switch (entryPoint.shader.shaderType) {
+        case "vertex":
+            executionModel = spirv.kinds.ExecutionModel.Vertex;
+            break;
+        case "fragment":
+            executionModel = spirv.kinds.ExecutionModel.Fragment;
+            break;
+        }
+        let id = entryPoint.id;
+        let name = entryPoint.shader.name;
+        let interfaceIds = []
+        for (let value of entryPoint.inputs)
+            interfaceIds.push(value.id);
+        for (let value of entryPoint.outputs)
+            interfaceIds.push(value.id);
+        assembler.append(new spirv.ops.EntryPoint(executionModel, id, name, ...interfaceIds));
+    }
+
+    // 6. All execution mode declarations
+    for (let entryPoint of entryPoints) {
+        let id = entryPoint.id;
+        assembler.append(new spirv.ops.ExecutionMode(id, spirv.kinds.ExecutionMode.OriginLowerLeft));
+    }
+
+    // 7. These debug instructions
+    // 8. All annotation instructions
+    // FIXME: There are probably more annotations that are required than just location.
+    let locations = [];
+    for (let entryPoint of entryPoints) {
+        switch (entryPoint.shader.shaderType) {
+        case "vertex":
+            for (let input of entryPoint.inputs) {
+                assembler.append(new spirv.ops.Decorate(input.id, spirv.kinds.Decoration.Location, input.location));
+                locations.push({ name: entryPoint.shader.name + "." + input.name, location: input.location });
+            }
+            break;
+        case "fragment":
+            for (let output of entryPoint.outputs) {
+                assembler.append(new spirv.ops.Decorate(output.id, spirv.kinds.Decoration.Location, output.location));
+                locations.push({ name: entryPoint.shader.name + "." + output.name, location: output.location });
+            }
+            break;
+        }
+    }
+
+    // 9. All type declarations, all constant instructions, and all global variable declarations
+    emitTypes(assembler);
+    let functionType = currentId++;
+    assembler.append(new spirv.ops.TypeFunction(functionType, typeMap.get(program.intrinsics.void)));
+    for (let constant of constants) {
+        if (constant[1].type == program.intrinsics.bool) {
+            if (constant[1].value[0])
+                assembler.append(new spirv.ops.ConstantTrue(constant[1].id));
+            else
+                assembler.append(new spirv.ops.ConstantFalse(constant[1].id));
+        } else
+            assembler.append(new spirv.ops.Constant(constant[1].typeId, constant[1].id, ...constant[1].values));
+    }
+    for (let entryPoint of entryPoints) {
+        for (let input of entryPoint.inputs)
+            assembler.append(new spirv.ops.Variable(input.type, input.id, spirv.kinds.StorageClass.Input));
+        for (let output of entryPoint.outputs)
+            assembler.append(new spirv.ops.Variable(output.type, output.id, spirv.kinds.StorageClass.Output));
+    }
+
+    // 10. All function declarations
+    // 11. All function definitions
+    for (let entryPoint of entryPoints) {
+        assembler.append(new spirv.ops.Function(typeMap.get(program.intrinsics.void), entryPoint.id, [spirv.kinds.FunctionControl.None], functionType));
+        assembler.append(new spirv.ops.FunctionEnd());
+    }
+
+    return { file: assembler.result, locations: locations };
+}
+
diff --git a/Tools/WebGPUShadingLanguageRI/SPIRVTypeAnalyzer.js b/Tools/WebGPUShadingLanguageRI/SPIRVTypeAnalyzer.js
new file mode 100644 (file)
index 0000000..6012374
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2017 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";
+
+class SPIRVTypeAnalyzer extends Visitor {
+    constructor(program, typeMap, currentId)
+    {
+        super();
+        this._program = program;
+        this._typeMap = typeMap;
+        this._currentId = currentId;
+        this._stack = [];
+    }
+
+    get program()
+    {
+        this._program;
+    }
+
+    get typeMap()
+    {
+        return this._typeMap;
+    }
+
+    get currentId()
+    {
+        return this._currentId;
+    }
+
+    get stack()
+    {
+        return this._stack;
+    }
+
+    visitTypeRef(node)
+    {
+        node.type.visit(this);
+    }
+
+    _encounterType(id)
+    {
+        if (this.stack.length > 0)
+            this.stack[this.stack.length - 1].push(id);
+    }
+
+    visitNullType(node)
+    {
+        super.visit(this);
+    }
+
+    visitGenericLiteralType(node)
+    {
+        node.type.visit(this);
+    }
+
+    visitNativeType(node)
+    {
+        if (!this.typeMap.has(node))
+            this.typeMap.set(node, this._currentId++);
+        let id = this.typeMap.get(node);
+        this._encounterType(id);
+    }
+
+    visitEnumType(node)
+    {
+        super.visit(this);
+    }
+
+    visitPtrType(node)
+    {
+        // Intentionally blank
+    }
+
+    visitArrayRefType(node)
+    {
+        this.visitNativeType(program.intrinsics.uint32);
+        this.visitNativeType(program.intrinsics.uint32);
+    }
+
+    // FIXME: Using toString() in these functions is a hack. Instead, we should implement a proper type deduper.
+    visitArrayType(node)
+    {
+        let id;
+        if (this.typeMap.has(node.toString())) {
+            id = this.typeMap.get(node);
+            if (typeof id == "object")
+                id = id.id;
+        } else {
+            let fieldType = [];
+            this.stack.push(fieldType);
+            node.elementType.visit(this);
+            this.stack.pop();
+            if (fieldType.length != 1)
+                throw new Error("Arrays can only have one element type!");
+            id = this._currentId++;
+            node.numElements.visit(this);
+            this.typeMap.set(node.toString(), {id: id, elementType: fieldType[0], numElements: node.numElements.value});
+        }
+        this._encounterType(id);
+    }
+
+    visitStructType(node)
+    {
+        let id;
+        if (this.typeMap.has(node.toString())) {
+            id = this.typeMap.get(node.toString());
+            if (typeof id == "object")
+                id = id.id;
+        } else {
+            let fieldTypes = [];
+            this.stack.push(fieldTypes);
+            for (let field of node.fields)
+                field.visit(this);
+            this.stack.pop();
+            id = this._currentId++;
+            this.typeMap.set(node.toString(), {id: id, fieldTypes: fieldTypes});
+        }
+        this._encounterType(id);
+    }
+}
diff --git a/Tools/WebGPUShadingLanguageRI/SPIRVVariableAnalyzer.js b/Tools/WebGPUShadingLanguageRI/SPIRVVariableAnalyzer.js
new file mode 100644 (file)
index 0000000..8236d9c
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2017 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";
+
+class SPIRVPrimitiveVariableAnalyzer extends Visitor {
+    constructor(program, typeMap, currentId, currentLocation, startName)
+    {
+        super();
+        this._program = program;
+        this._typeMap = typeMap;
+        this._currentId = currentId;
+        this._currentLocation = currentLocation;
+        this._nameComponents = [];
+        if (startName)
+            this._nameComponents.push(startName);
+        this._result = [];
+    }
+
+    get program()
+    {
+        this._program;
+    }
+
+    get typeMap()
+    {
+        return this._typeMap;
+    }
+
+    get currentId()
+    {
+        return this._currentId;
+    }
+
+    get currentLocation()
+    {
+        return this._currentLocation;
+    }
+
+    get nameComponents()
+    {
+        return this._nameComponents;
+    }
+
+    get result()
+    {
+        return this._result;
+    }
+
+    visitTypeRef(node)
+    {
+        node.type.visit(this);
+    }
+
+    visitNullType(node)
+    {
+        super.visit(this);
+    }
+
+    visitGenericLiteralType(node)
+    {
+        node.type.visit(this);
+    }
+
+    visitNativeType(node)
+    {
+        this.result.push({ name: this.nameComponents.join(""), id: this._currentId++, type: this.typeMap.get(node), location: this._currentLocation++ });
+    }
+
+    visitEnumType(node)
+    {
+        super.visit(this);
+    }
+
+    visitPtrType(node)
+    {
+        // Intentionally blank
+    }
+
+    visitArrayRefType(node)
+    {
+        this.visitNativeType(program.intrinsics.uint32);
+        this.visitNativeType(program.intrinsics.uint32);
+    }
+
+    visitArrayType(node)
+    {
+        for (let i = 0; i < node.numElements.value; ++i) {
+            this.nameComponents.push("[");
+            this.nameComponents.push(i.toString());
+            this.nameComponents.push("]");
+            node.elementType.visit(this);
+            this.nameComponents.pop();
+            this.nameComponents.pop();
+            this.nameComponents.pop();
+        }
+    }
+
+    visitStructType(node)
+    {
+        let builtInStructs = [
+            "struct vec2<> { int32 x; int32 y }",
+            "struct vec2<> { uint32 x; uint32 y; }",
+            "struct vec2<> { float32 x; float32 y; }",
+            "struct vec2<> { float64 x; float64 y; }",
+            "struct vec3<> { int32 x; int32 y; int32 z; }",
+            "struct vec3<> { uint32 x; uint32 y; uint32 z; }",
+            "struct vec3<> { float32 x; float32 y; float32 z; }",
+            "struct vec3<> { float64 x; float64 y; float64 z; }",
+            "struct vec4<> { int32 x; int32 y; int32 z; int32 w; }",
+            "struct vec4<> { uint32 x; uint32 y; uint32 z; uint32 w; }",
+            "struct vec4<> { float32 x; float32 y; float32 z; float32 w; }",
+            "struct vec4<> { float64 x; float64 y; float64 z; float64 w; }"
+        ];
+        for (let builtInStruct of builtInStructs) {
+            if (node.toString() == builtInStruct) {
+                this.result.push({ name: this.nameComponents.join(""), id: this._currentId++, type: this.typeMap.get(node.toString()).id, location: this._currentLocation++  });
+                return;
+            }
+        }
+        for (let field of node.fields) {
+            let dotPrefix = this.nameComponents.length > 0;
+            if (dotPrefix)
+                this.nameComponents.push(".");
+            this.nameComponents.push(field.name);
+            field.visit(this);
+            this.nameComponents.pop();
+            if (dotPrefix)
+                this.nameComponents.pop();
+        }
+    }
+}
index 7c62629..d3b6656 100644 (file)
@@ -473,7 +473,7 @@ WSL is a type-safe language based on C syntax. It eliminates some C features, li
 The following additional limitations may be placed on a WSL program:
 
 - `device`, `constant`, and `threadgroup` pointers cannot point to data that may have pointers in it. This safety check is not done as part of the normal type system checks. It's performed only after instantiation.
-- Pointers and array references (collectively, *references*) may be restricted to support compiling to SPIR-V *logical mode*. In this mode, references may never point to data structures that have references in them. References must be initialized upon declaration and never reassigned. Functions that return references must have one return point. Ternary expressions may not return references.
+- Pointers and array references (collectively, *references*) may be restricted to support compiling to SPIR-V *logical mode*. In this mode, arrays must not transitively hold references. References must be initialized upon declaration and never reassigned. Functions that return references must have one return point. Ternary expressions may not return references.
 - Graphics entry points must transitively never refer to the `threadgroup` memory space.
 
 
index a9697fe..3a7e948 100644 (file)
@@ -300,8 +300,8 @@ function doCompilePresentShaderSource() {
     program = result;
     availableVertexShaders = [];
     availableFragmentShaders = [];
-    for (functionNames of program.functions.values()) {
-        for (func of functionNames) {
+    for (let functionNames of program.functions.values()) {
+        for (let func of functionNames) {
             if (func.shaderType == "vertex")
                 availableVertexShaders.push(func);
             if (func.shaderType == "fragment")