Bug 187735 - [WHLSL] Metal code generation
Summary: [WHLSL] Metal code generation
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: WebGPU (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Thomas Denney
URL:
Keywords: InRadar
Depends on: 187728 188988 188991 189083 189502
Blocks: 176199 187738 189202 189795
  Show dependency treegraph
 
Reported: 2018-07-17 11:42 PDT by Thomas Denney
Modified: 2018-10-13 19:16 PDT (History)
7 users (show)

See Also:


Attachments
Patch (157.21 KB, patch)
2018-07-17 11:43 PDT, Thomas Denney
no flags Details | Formatted Diff | Diff
Archive of layout-test-results from ews202 for win-future (12.81 MB, application/zip)
2018-07-17 13:26 PDT, EWS Watchlist
no flags Details
Patch (160.86 KB, patch)
2018-07-17 17:10 PDT, Thomas Denney
no flags Details | Formatted Diff | Diff
Patch (160.86 KB, patch)
2018-07-17 19:28 PDT, Thomas Denney
no flags Details | Formatted Diff | Diff
Patch (160.86 KB, patch)
2018-07-17 19:30 PDT, Thomas Denney
no flags Details | Formatted Diff | Diff
WIP (173.03 KB, patch)
2018-08-27 20:06 PDT, Thomas Denney
no flags Details | Formatted Diff | Diff
WIP (174.60 KB, patch)
2018-08-29 10:03 PDT, Thomas Denney
no flags Details | Formatted Diff | Diff
Patch (168.07 KB, patch)
2018-08-31 20:22 PDT, Thomas Denney
no flags Details | Formatted Diff | Diff
Patch sans TypeScript (155.63 KB, patch)
2018-09-04 15:05 PDT, Thomas Denney
no flags Details | Formatted Diff | Diff
Patch sans TypeScript (155.95 KB, patch)
2018-09-04 16:31 PDT, Thomas Denney
no flags Details | Formatted Diff | Diff
Archive of layout-test-results from ews107 for mac-sierra-wk2 (3.64 MB, application/zip)
2018-09-04 18:43 PDT, EWS Watchlist
no flags Details
WIP (223.95 KB, patch)
2018-09-11 19:35 PDT, Thomas Denney
no flags Details | Formatted Diff | Diff
Archive of layout-test-results from ews106 for mac-sierra-wk2 (3.48 MB, application/zip)
2018-09-11 23:02 PDT, EWS Watchlist
no flags Details
WIP (202.73 KB, patch)
2018-09-12 19:10 PDT, Thomas Denney
no flags Details | Formatted Diff | Diff
WIP (162.99 KB, patch)
2018-09-13 13:50 PDT, Thomas Denney
no flags Details | Formatted Diff | Diff
Patch (151.65 KB, patch)
2018-09-13 21:49 PDT, Thomas Denney
no flags Details | Formatted Diff | Diff
Patch (151.58 KB, patch)
2018-09-13 23:04 PDT, Thomas Denney
no flags Details | Formatted Diff | Diff
Patch (152.64 KB, patch)
2018-09-14 15:19 PDT, Thomas Denney
no flags Details | Formatted Diff | Diff
Patch (152.43 KB, patch)
2018-09-14 16:18 PDT, Thomas Denney
no flags Details | Formatted Diff | Diff
Archive of layout-test-results from ews104 for mac-sierra-wk2 (3.17 MB, application/zip)
2018-09-14 17:40 PDT, EWS Watchlist
no flags Details
Patch (152.62 KB, patch)
2018-09-17 14:04 PDT, Thomas Denney
no flags Details | Formatted Diff | Diff
Patch (152.93 KB, patch)
2018-09-17 15:48 PDT, Thomas Denney
no flags Details | Formatted Diff | Diff
Patch (155.59 KB, patch)
2018-09-19 00:19 PDT, Thomas Denney
no flags Details | Formatted Diff | Diff
Patch (98.77 KB, patch)
2018-09-19 23:27 PDT, Thomas Denney
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Thomas Denney 2018-07-17 11:42:47 PDT
Adding WHLSL compiler
Comment 1 Thomas Denney 2018-07-17 11:43:10 PDT
Created attachment 345174 [details]
Patch
Comment 2 EWS Watchlist 2018-07-17 11:45:29 PDT
Attachment 345174 [details] did not pass style-queue:


ERROR: Tools/WebGPUShadingLanguageRI/Metal/tsconfig.json:14:  Expecting property name enclosed in double quotes: line 14 column 9 (char 309)  [json/syntax] [5]
Total errors found: 1 in 31 files


If any of these errors are false positives, please file a bug against check-webkit-style.
Comment 3 EWS Watchlist 2018-07-17 13:25:55 PDT
Comment on attachment 345174 [details]
Patch

Attachment 345174 [details] did not pass win-ews (win):
Output: https://webkit-queues.webkit.org/results/8566221

New failing tests:
fast/loader/plain-text-document.html
Comment 4 EWS Watchlist 2018-07-17 13:26:06 PDT
Created attachment 345185 [details]
Archive of layout-test-results from ews202 for win-future

The attached test failures were seen while running run-webkit-tests on the win-ews.
Bot: ews202  Port: win-future  Platform: CYGWIN_NT-6.1-2.9.0-0.318-5-3-x86_64-64bit
Comment 5 Dean Jackson 2018-07-17 14:24:11 PDT
Comment on attachment 345174 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=345174&action=review

Can we have some tests that check input v expected output?

> Tools/WebGPUShadingLanguageRI/Metal/ArrayRefDefinition.ts:26
> +/// Writes the definitions of the structures used for the different array ref types present in the program

While this isn't C++ code, and also isn't JS code, we should probably still follow the coding guidelines. That would mean using // and ending the sentence with a full stop (period). The same goes for other comments in the patch.

> Tools/WebGPUShadingLanguageRI/Metal/Deinliner.ts:48
> +        if (a == ReturnStyle.Always || a == ReturnStyle.SometimesEarly) {
> +            return a;
> +        } else {
> +            return b;
> +        }

Single line conditionals.

> Tools/WebGPUShadingLanguageRI/Metal/Deinliner.ts:138
> +            if (result == ReturnStyle.Always) {
> +                return ReturnStyle.SometimesEarly;
> +            }

Single line conditionals don't use {} in WebKit.

> Tools/WebGPUShadingLanguageRI/Metal/Deinliner.ts:186
> +    const returnStyle = returnStyleForNodeOrNull(node);
> +    return returnStyle != ReturnStyle.SometimesEarly;

No need for temporary local variable.

> Tools/WebGPUShadingLanguageRI/Metal/FindTopLevelTypeAttributes.ts:69
> +        case "vertex":
> +            this.visitVertexShader(func);
> +        case "fragment":
> +            this.visitFragmentShader(func);

I assume TypeScript doesn't need break in cases?

> Tools/WebGPUShadingLanguageRI/Metal/FindTopLevelTypeAttributes.ts:78
> +        for (let param of func.parameters) {
> +            this.attributesForType(param.type).isVertexAttribute = true;
> +        }

Single line statement.

> Tools/WebGPUShadingLanguageRI/Metal/FindTopLevelTypeAttributes.ts:85
> +        for (let param of func.parameters) {
> +            this.attributesForType(param.type).isVertexOutputOrFragmentInput = true;
> +        }

Ditto.

> Tools/WebGPUShadingLanguageRI/Metal/LineNumbers.ts:29
> +    const lineNos = [];
> +    let lineNo = 1;

Use lineNumbers and lineNumber.

> Tools/WebGPUShadingLanguageRI/Metal/LineNumbers.ts:34
> +        if (src[i] == '\n') {
> +            lineNo++;
> +        }

Single line.

> Tools/WebGPUShadingLanguageRI/Metal/MSLFunctionDeclaration.ts:60
> +        for (let param of this._func.parameters) {
> +            map.set(param.name, `P${counter++}`);
> +        }

Single line.

> Tools/WebGPUShadingLanguageRI/Metal/MSLFunctionDeclaration.ts:69
> +        for (let [paramName, param] of this.paramMap) {
> +            params.push(paramName);
> +        }

Single line.

> Tools/WebGPUShadingLanguageRI/Metal/MSLFunctionDeclaration.ts:85
> +        if (this._func.shaderType === "vertex" || this._func.shaderType === "fragment") {
> +            declLine += `${this._func.shaderType} `;
> +        }

Should you return if it isn't one of these types?

> Tools/WebGPUShadingLanguageRI/Metal/MSLLexer.ts:87
> +                    for (let innerTok of this._lex(commentText, this.commentMatchers)) {
> +                        tokens.push(innerTok);
> +                    }

SIngle line.

> Tools/WebGPUShadingLanguageRI/Metal/MSLLexer.ts:105
> +                    } else {
> +                        tokens.push(new Token(matcher.type, match[0]));
> +                    }

Ditto.

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.ts:74
> +        if (this.ancestorReturnType.name === "void") {
> +            this._add(`return;`);
> +        } else {
> +            const resultVar = this._fresh();
> +            this._add(`${this._typeNamer.mslTypeName(this.ancestorReturnType)} ${resultVar};`);
> +            this._zeroInitialize(this.ancestorReturnType, resultVar);
> +            this._add(`return ${resultVar};`);
> +        }

For things like this we normally do early returns. e.g.

if (this.ancestorReturnType.name === "void") {
  this._add(`return;`);
  return;
}

const resultVar....

Also, you don't need a `` string for the "return". I'm not sure there is really a performance win though.

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.ts:91
> +                if (node.name == "bool") {
> +                    emitter._add(`${variableName} = false;`);
> +                } else {
> +                    emitter._add(`${variableName} = 0;`);
> +                }
> +            }

Single line conditionals.

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.ts:102
> +                for (let i = 0; i < node.numElements.value; i++) {
> +                    emitter._zeroInitialize(node.elementType, `${variableName}[${i}]`, false);
> +                }

Single line.

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.ts:244
> +        } else {
> +            this._add('}');
> +        }

Single line.

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.ts:263
> +        } else {
> +            throw new Error(`Couldn't determine type of ! expression ${node}'`);
> +        }

Single line.

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.ts:277
> +        if (this._canAvoidDereferenceBecauseDereferencingReferenceNode(node.ptr)) {
> +            return this._doNotProduceReferenceFromReferenceNode(node.ptr);
> +        } else {

Single line.

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.ts:370
> +                if (anderRegex.test(node.nativeFuncInstance.name)) {
> +                    return emitter._handleFieldAccess(node);
> +                } else if (node.nativeFuncInstance.name === "operator&[]") {
> +                    return emitter._handleIndexAccess(node);
> +                }

Again.

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.ts:425
> +        } else {
> +            return this._nameMap.get(node.name);
> +        }

Again.

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.ts:441
> +        for (let expr of node.list) {
> +            result = Node.visit(expr, this);
> +        }

Again. I'll stop pointing them out now.
Comment 6 Thomas Denney 2018-07-17 17:10:55 PDT
Created attachment 345208 [details]
Patch
Comment 7 EWS Watchlist 2018-07-17 17:14:49 PDT
Attachment 345208 [details] did not pass style-queue:


ERROR: Tools/WebGPUShadingLanguageRI/Metal/tsconfig.json:14:  Expecting property name enclosed in double quotes: line 14 column 9 (char 309)  [json/syntax] [5]
Total errors found: 1 in 31 files


If any of these errors are false positives, please file a bug against check-webkit-style.
Comment 8 Thomas Denney 2018-07-17 19:28:40 PDT
Created attachment 345219 [details]
Patch
Comment 9 Thomas Denney 2018-07-17 19:30:04 PDT
Created attachment 345220 [details]
Patch
Comment 10 Thomas Denney 2018-08-27 20:06:19 PDT
Created attachment 348257 [details]
WIP
Comment 11 Thomas Denney 2018-08-29 10:03:51 PDT
Created attachment 348407 [details]
WIP
Comment 12 Thomas Denney 2018-08-31 20:22:53 PDT
Created attachment 348703 [details]
Patch
Comment 13 Thomas Denney 2018-09-04 15:05:35 PDT
Created attachment 348852 [details]
Patch sans TypeScript
Comment 14 Thomas Denney 2018-09-04 16:31:17 PDT
Created attachment 348869 [details]
Patch sans TypeScript
Comment 15 EWS Watchlist 2018-09-04 18:43:19 PDT
Comment on attachment 348869 [details]
Patch sans TypeScript

Attachment 348869 [details] did not pass mac-wk2-ews (mac-wk2):
Output: https://webkit-queues.webkit.org/results/9096384

New failing tests:
accessibility/smart-invert-reference.html
Comment 16 EWS Watchlist 2018-09-04 18:43:21 PDT
Created attachment 348884 [details]
Archive of layout-test-results from ews107 for mac-sierra-wk2

The attached test failures were seen while running run-webkit-tests on the mac-wk2-ews.
Bot: ews107  Port: mac-sierra-wk2  Platform: Mac OS X 10.12.6
Comment 17 Myles C. Maxfield 2018-09-06 16:57:22 PDT
Comment on attachment 348869 [details]
Patch sans TypeScript

View in context: https://bugs.webkit.org/attachment.cgi?id=348869&action=review

Halfway done reviewing

> Tools/WebGPUShadingLanguageRI/MakeArrayRefExpression.js:40
> +            while (lValueType instanceof TypeRef && lValueType.type)
> +                lValueType = lValueType.type;

Why is this necessary to do in the constructor? Our code should be resilient to TypeRefs.

> Tools/WebGPUShadingLanguageRI/MakeArrayRefExpression.js:44
> +            if (lValueType.isArray && lValueType.elementType)
> +                this._type = new ArrayRefType(origin, "thread", lValueType.elementType);
> +            else
> +                this._type = new ArrayRefType(origin, "thread", lValueType);

Can't we do this with a virtual function instead of hardcoding type names?

> Tools/WebGPUShadingLanguageRI/Metal/ArrayRefDefinition.js:39
> +function metalSourceForArrayRefDefinition(arrayRefType, typeNamer)
> +{
> +    let src = `struct ${typeNamer.uniqueTypeId(arrayRefType)} {\n`;
> +    const fakePtrType = new PtrType(arrayRefType.origin, arrayRefType.addressSpace, arrayRefType.elementType);
> +    src += `    ${metalSourceForVarDeclaration(typeNamer, fakePtrType, "ptr")};\n`;
> +    src += "    uint32_t length;\n";
> +    src += "};";
> +    return src;
> +}
> +
> +function metalSourceForArrayRefForwardDeclaration(arrayRefType, typeNamer)
> +{
> +    return `struct ${typeNamer.uniqueTypeId(arrayRefType)};`;
> +}

Why is this in its own file? It's only called in one place.

> Tools/WebGPUShadingLanguageRI/Metal/CompileResult.js:26
> +class CompileResult {

Does Javascript have the concept of namespaces? We want to make sure that the Metal backend's classes don't collide with any other codegen's classes. If there aren't real namespaces, we should prefix the names that get put in the global scope (probably except for whlslToMsl()).

> Tools/WebGPUShadingLanguageRI/Metal/ConstexprEmitter.js:37
> +    visitEnumLiteral(node)
> +    {
> +        return node.member.value.toString();
> +    }

Does this actually work? Is this tested?

We should make sure this file and https://bugs.webkit.org/show_bug.cgi?id=189029 stay in sync

> Tools/WebGPUShadingLanguageRI/Metal/FunctionLikeBlockCanBeInlined.js:28
> +function functionLikeBlockCanBeInlined(node)

We should make a bug to add whatever infrastructure is necessary to allow inlining for every function.

> Tools/WebGPUShadingLanguageRI/Metal/LineNumbers.js:38
> +function lineNumbers(src)
> +{
> +    const lineNumbers = [];
> +    let lineNumber = 1;
> +    for (let i = 0; i < src.length; i++) {
> +        lineNumbers.push(lineNumber);
> +        if (src[i] == '\n')
> +            lineNumber++;
> +    }
> +    return lineNumbers;
> +}

Why not use the .origin property on parsed nodes? For every expression, you'll know where it came from in the source. No need to calculate it twice.

> Tools/WebGPUShadingLanguageRI/Metal/MSLFunctionDeclaration.js:31
> +        this._funcMangler = funcMangler;

This is a layering inversion. The mangler is only used here for a single place; why doesn't whoever constructs a MSLFunctionDeclaration pass in the mangled name? This could also apply to the other arguments here.

> Tools/WebGPUShadingLanguageRI/Metal/MSLFunctionDeclaration.js:41
> +    get funcMangler()
> +    {
> +        return this._funcMangler;
> +    }

Similar to above; this seems upside-down.

> Tools/WebGPUShadingLanguageRI/Metal/MSLFunctionDeclaration.js:94
> +        // What WSL calls `compute` shaders Metal calls `kernel` shaders.
> +        // We're only focussing on `vertex` and `fragment` for now.

Do we have a bug for this? Seems important.

> Tools/WebGPUShadingLanguageRI/Metal/MSLFunctionDeclaration.js:99
> +            declLine += `${this._func.shaderType} `;
> +        declLine += `${this._typeNamer.mslTypeName(this._func.returnType)} `;
> +        declLine += this._funcMangler.mangle(this.func);
> +        declLine += "("

Doesn't Javascript have a better way to do string building? Even appending a bunch of strings to an array and then joining the array at the last minute would be better.

> Tools/WebGPUShadingLanguageRI/Metal/MSLFunctionDeclaration.js:120
> +        // We currently assuming that all parameters to entry points are attributes.

:(

Open a bug?

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:40
> +        this._counter = counter ? counter : new Counter("V");

Seems over-engineered. Getters/setters of a Number seems simpler.

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:714
> +        if (canBeInlined)

If it can be inlined, then inline it? That seems like a non-optimal heuristic. We should have an explicit step to record whether or not each function should be inlined, and then this step will just consult that list.

Initially, inlining nothing seems like the safest bet.

> Tools/WebGPUShadingLanguageRI/Metal/MSLTypeNamer.js:66
> +                    "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"

Samplers, textures

> Tools/WebGPUShadingLanguageRI/Metal/MSLTypeNamer.js:129
> +                    "bool": "bool",

Bool matrices don't exist

> Tools/WebGPUShadingLanguageRI/Metal/MetalCodegen.js:84
> +    _tryGenerateMsl()

Functions that don't return a sentinel don't need to be named "try"

> Tools/WebGPUShadingLanguageRI/Metal/MetalCodegen.js:86
> +        // Step 1: Find all the functions to compile

Comments should answer "why," not "how." I recommend deleting these.

> Tools/WebGPUShadingLanguageRI/Metal/MetalCodegen.js:119
> +    _findEntryPointDefinitions()

How about findReachableFunctions()?

> Tools/WebGPUShadingLanguageRI/Metal/MetalCodegen.js:188
> +            } else if (type instanceof ArrayRefType) {
> +                this._forwardTypeDecls.push(metalSourceForArrayRefForwardDeclaration(type, this._typeNamer));
> +                this._typeDefinitions.push(metalSourceForArrayRefDefinition(type, this._typeNamer));

We should probably allow Arrays (in addition to ArrayRefs). They are strictly less powerful.

> Tools/WebGPUShadingLanguageRI/Metal/Token.js:26
> +// The output of MSLLexer.

This notion is better encapsulated with names (either of variables or the class)

> Tools/WebGPUShadingLanguageRI/Metal/TypeOf.js:26
> +function typeOf(node) {

I'm not sure why this is necessary. I think we should make the Checker always attach the type of everything to itself. It could be implemented with a getter that's overridden by every class.

> Tools/WebGPUShadingLanguageRI/Metal/TypeOf.js:131
> +            return node.type.visit(this);

Doesn't seem right. The type of an array ref is not equal to the inner type.

> Tools/WebGPUShadingLanguageRI/Metal/TypeOf.js:226
> +            throw new Error("BoolLiteral has no type");

Why isn't the type of a BoolLiteral a bool? NullLiteral has a type...

> Tools/WebGPUShadingLanguageRI/Metal/TypeUnifier.js:75
> +        node.baseType.visit(this);

Why not call super? (And ditto for all the other occurrences in this file)

> Tools/WebGPUShadingLanguageRI/Metal/TypeUnifier.js:96
> +        return `${node.elementType.visit(this)}[${node.numElements}]`;

I think this has been renamed to numElementsValue

> Tools/WebGPUShadingLanguageRI/Metal/TypeUnifier.js:118
> +    visitMakeArrayRefExpression(node)
> +    {
> +        if (!node.type)
> +            throw new Error(node);
> +        return node.type.visit(this);
> +    }

Why just MakeArrayRefExpression? Shouldn't we be visiting every expression? Similarly, we should make sure every expression has a .type property.

> Tools/WebGPUShadingLanguageRI/Metal/TypeUnifier.js:123
> +        const nameSet = new Set();

Seems like this set will always have the exact same set of things as in declSet. Why have two?

> Tools/WebGPUShadingLanguageRI/Metal/WHLSLLexer.js:71
> +function lexWHLSL(src)

We already have a WHLSL lexer. Why reimplement it?

> Tools/WebGPUShadingLanguageRI/Metal/compiler.js:11
> +/*
> + * 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.

I'd recommend a better name for this file. Also, using correct casing.

> Tools/WebGPUShadingLanguageRI/Metal/index.html:173
> +    <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="../NativeType.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="../BuiltinMatrixGetter.js"></script>
> +    <script src="../BuiltinMatrixSetter.js"></script>
> +    <script src="../BuiltinVectorGetter.js"></script>
> +    <script src="../BuiltinVectorSetter.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="../CheckTypesWithArguments.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="../Continue.js"></script>
> +    <script src="../ConvertPtrToArrayRefExpression.js"></script>
> +    <script src="../DoWhileLoop.js"></script>
> +    <script src="../DotExpression.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="../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="../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="../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="../MatrixType.js"></script>
> +    <script src="../NameContext.js"></script>
> +    <script src="../NameFinder.js"></script>
> +    <script src="../NameResolver.js"></script>
> +    <script src="../NativeFunc.js"></script>
> +    <script src="../NormalUsePropertyResolver.js"></script>
> +    <script src="../NullLiteral.js"></script>
> +    <script src="../NullType.js"></script>
> +    <script src="../OperatorAnderIndexer.js"></script>
> +    <script src="../OperatorArrayRefLength.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="../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="../StandardLibrary.js"></script>
> +    <script src="../StatementCloner.js"></script>
> +    <script src="../StructLayoutBuilder.js"></script>
> +    <script src="../StructType.js"></script>
> +    <script src="../SwitchCase.js"></script>
> +    <script src="../SwitchStatement.js"></script>
> +    <script src="../SynthesizeArrayOperatorLength.js"></script>
> +    <script src="../SynthesizeEnumFunctions.js"></script>
> +    <script src="../SynthesizeStructAccessors.js"></script>
> +    <script src="../SynthesizeCopyConstructorOperator.js"></script>
> +    <script src="../SynthesizeDefaultConstructorOperator.js"></script>
> +    <script src="../TernaryExpression.js"></script>
> +    <script src="../TrapStatement.js"></script>
> +    <script src="../TypeDef.js"></script>
> +    <script src="../TypeDefResolver.js"></script>
> +    <script src="../TypeRef.js"></script>
> +    <script src="../TypeOverloadResolutionFailure.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="../VectorType.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 src="ArrayRefDefinition.js"></script>
> +    <script src="CompileResult.js"></script>
> +    <script src="ConstexprEmitter.js"></script>
> +    <script src="Counter.js"></script>
> +    <script src="FunctionLikeBlockCanBeInlined.js"></script>
> +    <script src="LineNumbers.js"></script>
> +    <script src="MSLFunctionDeclaration.js"></script>
> +    <script src="MSLFunctionDefinition.js"></script>
> +    <script src="MSLFunctionForwardDeclaration.js"></script>
> +    <script src="MSLLexer.js"></script>
> +    <script src="MSLStatementEmitter.js"></script>
> +    <script src="MSLTypeNamer.js"></script>
> +    <script src="MetalCodegen.js"></script>
> +    <script src="NameMangler.js"></script>
> +    <script src="SortTypeDeclarations.js"></script>
> +    <script src="StructDefinition.js"></script>
> +    <script src="Token.js"></script>
> +    <script src="TypeAttributeMap.js"></script>
> +    <script src="TypeAttributes.js"></script>
> +    <script src="TypeOf.js"></script>
> +    <script src="TypeUnifier.js"></script>
> +    <script src="VarDeclaration.js"></script>
> +    <script src="WHLSLLexer.js"></script>

We've really got to find a better way to do this. https://bugs.webkit.org/show_bug.cgi?id=189378

> Tools/WebGPUShadingLanguageRI/StandardLibrary.js:43
> +        for (var type of [`bool`, `uchar`, `ushort`, `uint`, `char`, `short`, `int`, `half`, `float`]) {
> +            for (var size of [2, 3, 4]) {

:(

> Tools/WebGPUShadingLanguageRI/Test.js:35
> +} else if (this.load)

Why?

> Tools/WebGPUShadingLanguageRI/Test.js:74
>  {
> -    return TypedValue.box(program.intrinsics.int, value);
> +    return TypedValue.box(program.intrinsics.int, (value | 0));
>  }
>  
>  function makeUint(program, value)
>  {
> -    return TypedValue.box(program.intrinsics.uint, value);
> +    return TypedValue.box(program.intrinsics.uint, (value >>> 0));
>  }
>  
>  function makeUchar(program, value)
>  {
> -    return TypedValue.box(program.intrinsics.uchar, value);
> +    return TypedValue.box(program.intrinsics.uchar, (value >>> 0) & 0xff);
>  }
>  
>  function makeBool(program, value)
>  {
> -    return TypedValue.box(program.intrinsics.bool, value);
> +    return TypedValue.box(program.intrinsics.bool, !!value);
>  }

These have been moved to Casts.js

> Tools/WebGPUShadingLanguageRI/Test.js:1080
> -    checkNumber(program, callFunction(program, "foo", []), 42);
> +    checkUint(program, callFunction(program, "foo", [], []), 42);

Are you are the extra argument to callFunction is right?

Can we delete checkNumber()?

> Tools/WebGPUShadingLanguageRI/Test.js:1086
> -    checkNumber(program, callFunction(program, "foo", []), 42);
> +    checkFloat(program, callFunction(program, "foo", [], []), 42);

Ditto.

> Tools/WebGPUShadingLanguageRI/Test.js:-2421
> -
> -    checkFail(
> -        () => doPrep(`
> -            void foo()
> -            {
> -                float3 x;
> -                bool r = bool(x);
> -            }
> -        `),
> -        e => e instanceof WTypeError);
> -
> -    checkFail(
> -        () => doPrep(`
> -            void foo()
> -            {
> -                float3x3 x;
> -                bool r = bool(x);
> -            }
> -        `),
> -        e => e instanceof WTypeError);

I've since deleted this, I think you'll need to rebase.

> Tools/WebGPUShadingLanguageRI/Test.js:-2711
> -    checkUchar(program, callFunction(program, "foo", [makeUchar(program, 65535), makeUint(program, 2)]), 252);

Why?

> Tools/WebGPUShadingLanguageRI/Test.js:-2726
> -    checkUchar(program, callFunction(program, "foo", [makeUchar(program, 65535), makeUint(program, 2)]), 255);
> -    checkUchar(program, callFunction(program, "foo", [makeUchar(program, -1), makeUint(program, 5)]), 255);

Why?
Comment 18 Myles C. Maxfield 2018-09-07 17:46:03 PDT
Comment on attachment 348869 [details]
Patch sans TypeScript

View in context: https://bugs.webkit.org/attachment.cgi?id=348869&action=review

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:210
> +        // 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.

Why? For loops are implemented in the interpreter, why shouldn't they be implemented in Metal?

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:211
> +        this._emitLoopBodyEnd();

Wha happens if a continue is inside a switch inside a loop?

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:221
> +        for (let stmt of block.statements) {

No need to smoosh together "statement" into "stmt"

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:225
> +        }

Why don't blocks emit "{"?

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:232
> +        if (this._resultVar)

What's the difference between _resultVar and the returned value?

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:235
> +            this._add(`return ${expr};`);

All expressions should be saved into variables.

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:291
> +        const type = typeOf(node);
> +        if (type) {

Should just assume this is bool. We prove that elsewhere.

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:306
> +        if (this._canAvoidDereferenceBecauseDereferencingReferenceNode(node.ptr))
> +            return this._doNotProduceReferenceFromReferenceNode(node.ptr);

I'd recommend putting this kind of thing in a follow-up patch.

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:308
> +            return `(*(${node.ptr.visit(this)}))`;

All expressions should be saved into local variables.

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:427
> +        if (node.variable instanceof AnonymousVariable)
> +            return Node.visit(node.variable, this);
> +        let name = node.name;
> +        if (!this._nameMap.has(name))
> +            throw new Error(`${node.name} not found in this function's variable map`);

We should be mapping variables to names, not names to names. That way we can unify these paths.

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:467
> +        // Anonymous variable names are of the form anonVar<i> (where i is an integer), i.e.
> +        // the set of anonymous variable names is disjoint from the set of definable variable names.
> +        if (!this._nameMap.has(node.name)) {
> +            let id = this._fresh();
> +            this._nameMap.set(node.name, id);
> +            this._add(metalSourceForVarDeclaration(this._typeNamer, node.type, id) + `; // ${node.name}`);
> +            this._zeroInitialize(node.type, id);
> +            return id;
> +        } else
> +            return this._nameMap.get(node.name);

Anonymous variables should be treated exactly the same as regular variables.

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:475
> +        return rhs;

I think you mean lhs here

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:540
> +        else
> +            throw new Error(`Unimplemented native function '${node.name}' at MSLStatementEmitter.makeFunctionCall`);

Without inlining, this is no longer correct.

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:565
> +        this._emitReturnZeroValue();

This isn't right. This should be a trap, the whole program should stop executing. We want more than just the current function to return zeroes.

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:575
> +        this._emitReturnZeroValue();

Ditto.

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:606
> +        this._emitReturnZeroValue();

Ditto

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:624
> +        if (arg instanceof MakePtrExpression) {
> +            const argId = Node.visit(arg.lValue, this);
> +            if (arg.lValue && arg.lValue instanceof VariableRef)
> +                return `${argId}.${field}`;
> +            else
> +                return `(${argId}).${field}`;
> +        } else
> +            return `${arg.visit(this)}->${field}`;

Why does the field access care what type the inner thing is? We should try to make it so that we can just do a recursive call here and the right thing happens.

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:632
> +    _emitOperatorAnder(node, result, args)
> +    {
> +        const type = node.implementationData.type;
> +        const field = this._typeAttributes.attributesForType(type).mangledFieldName(node.implementationData.name);
> +        this._add(`${result} = (${this._typeNamer.mslTypeName(node.returnType)})&((${args[0]})->${field});`);
> +    }

Which anders aren't implemented in the language?

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:646
> +        // FIXME: Avoid so much special casing.

Yes please. See the above comment for _handleFieldAccess

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:685
> +        const expr = node.lValue.visit(this);
> +        return `&(${expr})`;

Why doesn't this get saved into a variable?

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:732
> +        const emitter = new MSLStatementEmitter(this._funcMangler, this._typeNamer, node.body, nameMap, node.origin.text, this._lineNumbers, this._typeAttributes, resultVar, this._counter, this.ancestorReturnType);

Why is it necessary to create a new MSLStatementEmitter? Why can't we just keep recursing?

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:743
> +    // which is checked on each iteration (all loops are compiled to a while loop or do/while loop). The check is

Why?

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:749
> +            Node.visit(node.initialization, this); // initalization can be null

No need for this comment

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:866
> +        return `(nullptr)`;

Why parentheses?

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:928
> +        this._indent();

There should be a function which takes a lambda that does the indent/unindent for you

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:944
> +    // Unimplemented methods. I write out comments for these rather than throwing exceptions because it makes it easier for me
> +    // to see where the nodes occur in the abstract syntax tree

We should throw exceptions for these. Writing out the location might be valuable for your own debugging, but it isn't for committing
Comment 19 Thomas Denney 2018-09-07 17:52:52 PDT
(In reply to Myles C. Maxfield from comment #18)
> Comment on attachment 348869 [details]
> Patch sans TypeScript
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=348869&action=review
> 
> > Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:210
> > +        // 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.
> 
> Why? For loops are implemented in the interpreter, why shouldn't they be
> implemented in Metal?

The initializer, condition, or the increment of the for loop may be a compound expression in WHLSL that has to compile to several statements in Metal. It is awkward to retain the for loop structure in Metal.
Comment 20 Myles C. Maxfield 2018-09-07 19:21:03 PDT
(In reply to Thomas Denney from comment #19)
> (In reply to Myles C. Maxfield from comment #18)
> > Comment on attachment 348869 [details]
> > Patch sans TypeScript
> > 
> > View in context:
> > https://bugs.webkit.org/attachment.cgi?id=348869&action=review
> > 
> > > Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:210
> > > +        // 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.
> > 
> > Why? For loops are implemented in the interpreter, why shouldn't they be
> > implemented in Metal?
> 
> The initializer, condition, or the increment of the for loop may be a
> compound expression in WHLSL that has to compile to several statements in
> Metal. It is awkward to retain the for loop structure in Metal.

👍
Comment 21 Thomas Denney 2018-09-11 09:40:48 PDT
(In reply to Myles C. Maxfield from comment #17)
> Comment on attachment 348869 [details]
> Patch sans TypeScript
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=348869&action=review

> > Tools/WebGPUShadingLanguageRI/Test.js:-2421
> > -
> > -    checkFail(
> > -        () => doPrep(`
> > -            void foo()
> > -            {
> > -                float3 x;
> > -                bool r = bool(x);
> > -            }
> > -        `),
> > -        e => e instanceof WTypeError);
> > -
> > -    checkFail(
> > -        () => doPrep(`
> > -            void foo()
> > -            {
> > -                float3x3 x;
> > -                bool r = bool(x);
> > -            }
> > -        `),
> > -        e => e instanceof WTypeError);
> 
> I've since deleted this, I think you'll need to rebase.

They’re still in master.
Comment 22 Thomas Denney 2018-09-11 19:35:16 PDT
Created attachment 349505 [details]
WIP
Comment 23 EWS Watchlist 2018-09-11 23:02:55 PDT
Comment on attachment 349505 [details]
WIP

Attachment 349505 [details] did not pass mac-wk2-ews (mac-wk2):
Output: https://webkit-queues.webkit.org/results/9187455

New failing tests:
accessibility/smart-invert-reference.html
Comment 24 EWS Watchlist 2018-09-11 23:02:57 PDT
Created attachment 349524 [details]
Archive of layout-test-results from ews106 for mac-sierra-wk2

The attached test failures were seen while running run-webkit-tests on the mac-wk2-ews.
Bot: ews106  Port: mac-sierra-wk2  Platform: Mac OS X 10.12.6
Comment 25 Thomas Denney 2018-09-12 13:57:03 PDT
(In reply to Myles C. Maxfield from comment #18)
> Comment on attachment 348869 [details]
> Patch sans TypeScript
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=348869&action=review

> > Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:211
> > +        this._emitLoopBodyEnd();
> 
> Wha happens if a continue is inside a switch inside a loop?

I think this is OK. In WHLSL/C/C++ using a continue in a switch in a loop still affects the outer loop. _emitLoopBodyEnd just emits the loop increment statement and the condition statement, so the semantics are retained.
Comment 26 Radar WebKit Bug Importer 2018-09-12 18:52:23 PDT
<rdar://problem/44403031>
Comment 27 Thomas Denney 2018-09-12 19:10:43 PDT
Created attachment 349612 [details]
WIP
Comment 28 Thomas Denney 2018-09-13 13:50:55 PDT
Created attachment 349696 [details]
WIP
Comment 29 Thomas Denney 2018-09-13 19:37:58 PDT
(In reply to Myles C. Maxfield from comment #17)
> Comment on attachment 348869 [details]
> Patch sans TypeScript
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=348869&action=review

> > Tools/WebGPUShadingLanguageRI/Metal/MSLFunctionDeclaration.js:31
> > +        this._funcMangler = funcMangler;
> 
> This is a layering inversion. The mangler is only used here for a single
> place; why doesn't whoever constructs a MSLFunctionDeclaration pass in the
> mangled name? This could also apply to the other arguments here.
> 
> > Tools/WebGPUShadingLanguageRI/Metal/MSLFunctionDeclaration.js:41
> > +    get funcMangler()
> > +    {
> > +        return this._funcMangler;
> > +    }
> 
> Similar to above; this seems upside-down.

MSLFunctionForwardDeclaration and MSLFunctionDefinition  are concrete subclasses of this class, and they each use all of the parameters; the former uses them for correctly constructing all of the parameters (e.g. with attributes) whilst the latter needs to pass all of them to its internal MSLStatementEmitter, which then needs to use the function name mangler for function calls, the parameter map for extending to the internal variable map, and the type attributes for correctly accessing type properties.
Comment 30 Thomas Denney 2018-09-13 21:49:13 PDT
Created attachment 349733 [details]
Patch
Comment 31 Thomas Denney 2018-09-13 23:04:59 PDT
Created attachment 349735 [details]
Patch
Comment 32 Thomas Denney 2018-09-14 15:19:59 PDT
Created attachment 349817 [details]
Patch
Comment 33 Thomas Denney 2018-09-14 16:18:34 PDT
Created attachment 349826 [details]
Patch
Comment 34 EWS Watchlist 2018-09-14 17:40:14 PDT
Comment on attachment 349826 [details]
Patch

Attachment 349826 [details] did not pass mac-wk2-ews (mac-wk2):
Output: https://webkit-queues.webkit.org/results/9222200

New failing tests:
accessibility/smart-invert-reference.html
Comment 35 EWS Watchlist 2018-09-14 17:40:16 PDT
Created attachment 349839 [details]
Archive of layout-test-results from ews104 for mac-sierra-wk2

The attached test failures were seen while running run-webkit-tests on the mac-wk2-ews.
Bot: ews104  Port: mac-sierra-wk2  Platform: Mac OS X 10.12.6
Comment 36 Thomas Denney 2018-09-17 14:04:32 PDT
Created attachment 349939 [details]
Patch
Comment 37 Thomas Denney 2018-09-17 15:48:41 PDT
Created attachment 349957 [details]
Patch
Comment 38 Thomas Denney 2018-09-19 00:19:14 PDT
Created attachment 350095 [details]
Patch
Comment 39 Myles C. Maxfield 2018-09-19 00:54:19 PDT
Comment on attachment 349957 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=349957&action=review

What have we done to make sure "return" and "continue" and "break" all play nicely together, e.g. in a switch statement in a loop in an inlined function?

> Tools/WebGPUShadingLanguageRI/Metal/MSLFunctionDeclaration.js:26
> +// Emits code for for the first line of a function declaration or definition.

for for

> Tools/WebGPUShadingLanguageRI/Metal/MSLFunctionDeclaration.js:81
> +        let declLine = this.commentLine();

Seems weird to have a function declaration include a comment line. I suggest not calling commentLine() here, but instead calling it in all the places that call this function.

Also, having a global switch to control these comments would be useful, since in production we don't want any text in the original source to appear in the generated code.

> Tools/WebGPUShadingLanguageRI/Metal/MSLFunctionDeclaration.js:97
> +            else if (this.parameterIsAttribute(param))
> +                pStr += " [[stage_in]]";

I thought only one thing could have [[stage_in]]? Should there be a stage where we gather and partition the inputs/outputs of the shader?

> Tools/WebGPUShadingLanguageRI/Metal/MSLFunctionDeclaration.js:115
> +        // We currently assuming that all parameters to entry points are attributes.
> +        // TODO: Better logic for this, i.e. support samplers.

We have to do this before we present. Probably should file a bug.

> Tools/WebGPUShadingLanguageRI/Metal/MSLNativeFunctionCall.js:75
> +        "native uint f32tof16(float)" : () => `${resultVariable} = uint(static_cast<ushort>(half(${args[0]})));`,

Pretty sure this shouldn't be a static_cast. It places the result "stored in the low-half of the uint."

https://docs.microsoft.com/en-us/windows/desktop/direct3dhlsl/f32tof16

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:85
> +                emitter._zeroInitialize(node.baseType, variableName, false);

How does this work?
enum Foo : int {
    Bar = 1,
    Baz = 2
}
Can't really zero-initialize that type, can we :(

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:222
> +        // At the moment the parser emits Block nodes for the if and else bodies, so this allows us

Really?

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:362
> +        this._nameMap.set(node, id);

Excellent

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:442
> +        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));

I like this.

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:614
> +    // Switch statements.

No need for this. Please delete.

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:641
> +    // Literals

Ditto.

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:714
> +            if (node.isLValue)
> +                this._add(`${resultVar} = &(${bodyExpression});`);

I thought we were getting rid of this?

> Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:722
> +            if (node.isLValue)
> +                this._add(`${resultVar} = &(${elseExpression});`);

Ditto

> Tools/WebGPUShadingLanguageRI/Metal/MSLTypeNamer.js:86
> +                name = `${node.addressSpace} ${typeNamer.mslTypeName(node.elementType)}*`;

Wouldn't it be easier to emit a collection of typedefs at the top of the MSL source for every used type in the entire program? You could do a prepass to populate it, so you wouldn't have to build up these long strings. It would work like how each WHLSL expression gets mapped to a MSL statement with exactly one operation - each used WHLSL type in the entire program would map to a MSL typedef with exactly one construction.

> Tools/WebGPUShadingLanguageRI/Metal/MSLTypeNamer.js:91
> +                name = `${node.addressSpace ? node.addressSpace + " " : ""}${typeNamer.mslTypeName(node.elementType)}*`;

Better to emit "thread" to be explicit.

> Tools/WebGPUShadingLanguageRI/Metal/MSLVarDeclaration.js:45
> +    type = typeOf(type); // Unwrap TypeRef if necessary
> +    if (type.isArray) {
> +        let arrayType = type;
> +        const sizes = [ arrayType.numElementsValue ];
> +        let elementType = typeOf(arrayType.elementType);
> +        while (elementType.isArray) {
> +            arrayType = elementType;
> +            sizes.push(arrayType.numElementsValue);
> +            elementType = arrayType.elementType;
> +        }
> +        if (treatArrayAsPointer)
> +            return `${arrayType.addressSpace ? arrayType.addressSpace : "thread"} ${typeNamer.mslTypeName(elementType)} (*${name})${sizes.slice(1).map(size => `[${size}]`).join('')}`;
> +        else
> +            return `${typeNamer.mslTypeName(elementType)} ${name}${sizes.map(size => `[${size}]`).join('')}`;
> +    } else if (type.isPtr)
> +        return (type).addressSpace + " " + mslVarDeclaration(typeNamer, (type).elementType, `(*${name})`);
> +    else
> +        return `${typeNamer.mslTypeName(type)} ${name}`;

If we typedef every used type, this logic could probably be much simpler.

> Tools/WebGPUShadingLanguageRI/Metal/TypeOf.js:27
> +// FIXME: Rather than being a separate function this should instead be a separate preparation phase that annotates
> +// each Node instance with a "type" property. https://bugs.webkit.org/show_bug.cgi?id=189611

Doesn't need to be a separate phase. Checker can do it.

> Tools/WebGPUShadingLanguageRI/Metal/TypeOf.js:47
> +            return node.type.visit(this);

I don't understand why there are recursive calls on the .type properties. Why not just return node.type?

> Tools/WebGPUShadingLanguageRI/PropertyResolver.js:172
> +        if (node.lValue.unifyNode instanceof DereferenceExpression)
> +            node.become(new IdentityExpression(node.lValue.unifyNode.ptr));

Can you remind me again why this is necessary? This could potentially have side effects around lvalue validation, and we want to make sure our compiler matches the spec exactly. I don't think this kind of optimization should be made without making sure it's not observable.
Comment 40 Thomas Denney 2018-09-19 10:39:37 PDT
(In reply to Myles C. Maxfield from comment #39)
> Comment on attachment 349957 [details]
> > Tools/WebGPUShadingLanguageRI/Metal/MSLFunctionDeclaration.js:97
> > +            else if (this.parameterIsAttribute(param))
> > +                pStr += " [[stage_in]]";
> 
> I thought only one thing could have [[stage_in]]? Should there be a stage
> where we gather and partition the inputs/outputs of the shader?
> 
> > Tools/WebGPUShadingLanguageRI/Metal/MSLFunctionDeclaration.js:115
> > +        // We currently assuming that all parameters to entry points are attributes.
> > +        // TODO: Better logic for this, i.e. support samplers.
> 
> We have to do this before we present. Probably should file a bug.
> 

I’ve opened https://bugs.webkit.org/show_bug.cgi?id=189751, which covers both of these.

> > Tools/WebGPUShadingLanguageRI/Metal/MSLNativeFunctionCall.js:75
> > +        "native uint f32tof16(float)" : () => `${resultVariable} = uint(static_cast<ushort>(half(${args[0]})));`,
> 
> Pretty sure this shouldn't be a static_cast. It places the result "stored in
> the low-half of the uint."
> 
> https://docs.microsoft.com/en-us/windows/desktop/direct3dhlsl/f32tof16
> 

half -> float reduces the size to 16 bits, half -> ushort keeps the result in 16 bits, ushort -> uint places the 16 bits of the ushort in the lower 16 bits of the uint.

> > Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:85
> > +                emitter._zeroInitialize(node.baseType, variableName, false);
> 
> How does this work?
> enum Foo : int {
>     Bar = 1,
>     Baz = 2
> }
> Can't really zero-initialize that type, can we :(
> 

See https://bugs.webkit.org/show_bug.cgi?id=189755. I’ll fix it in that bug seen as this will affect the interpreter as well. No tests catch this at the moment.

> > Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:222
> > +        // At the moment the parser emits Block nodes for the if and else bodies, so this allows us
> 
> Really?

Nope, just checked.

> > Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:714
> > +            if (node.isLValue)
> > +                this._add(`${resultVar} = &(${bodyExpression});`);
> 
> I thought we were getting rid of this?
> 
> > Tools/WebGPUShadingLanguageRI/Metal/MSLStatementEmitter.js:722
> > +            if (node.isLValue)
> > +                this._add(`${resultVar} = &(${elseExpression});`);
> 
> Ditto

I’ve now removed the codegen for this, also see https://bugs.webkit.org/show_bug.cgi?id=189290.

> 
> > Tools/WebGPUShadingLanguageRI/Metal/MSLTypeNamer.js:86
> > +                name = `${node.addressSpace} ${typeNamer.mslTypeName(node.elementType)}*`;
> 
> Wouldn't it be easier to emit a collection of typedefs at the top of the MSL
> source for every used type in the entire program? You could do a prepass to
> populate it, so you wouldn't have to build up these long strings. It would
> work like how each WHLSL expression gets mapped to a MSL statement with
> exactly one operation - each used WHLSL type in the entire program would map
> to a MSL typedef with exactly one construction.
> 

This would work, although *this* code will still need to be there in order to generate the typedefs.

> > Tools/WebGPUShadingLanguageRI/Metal/MSLTypeNamer.js:91
> > +                name = `${node.addressSpace ? node.addressSpace + " " : ""}${typeNamer.mslTypeName(node.elementType)}*`;
> 
> Better to emit "thread" to be explicit.

Will do so long as there are no issues with Metal. At the moment I was avoiding this because in WHLSL only the outermost array type gets an address space, and that address space could be something other than thread.

> 
> > Tools/WebGPUShadingLanguageRI/Metal/MSLVarDeclaration.js:45
> > +    type = typeOf(type); // Unwrap TypeRef if necessary
> > +    if (type.isArray) {
> > +        let arrayType = type;
> > +        const sizes = [ arrayType.numElementsValue ];
> > +        let elementType = typeOf(arrayType.elementType);
> > +        while (elementType.isArray) {
> > +            arrayType = elementType;
> > +            sizes.push(arrayType.numElementsValue);
> > +            elementType = arrayType.elementType;
> > +        }
> > +        if (treatArrayAsPointer)
> > +            return `${arrayType.addressSpace ? arrayType.addressSpace : "thread"} ${typeNamer.mslTypeName(elementType)} (*${name})${sizes.slice(1).map(size => `[${size}]`).join('')}`;
> > +        else
> > +            return `${typeNamer.mslTypeName(elementType)} ${name}${sizes.map(size => `[${size}]`).join('')}`;
> > +    } else if (type.isPtr)
> > +        return (type).addressSpace + " " + mslVarDeclaration(typeNamer, (type).elementType, `(*${name})`);
> > +    else
> > +        return `${typeNamer.mslTypeName(type)} ${name}`;
> 
> If we typedef every used type, this logic could probably be much simpler.

Yeah. I like this idea.

> 
> > Tools/WebGPUShadingLanguageRI/Metal/TypeOf.js:27
> > +// FIXME: Rather than being a separate function this should instead be a separate preparation phase that annotates
> > +// each Node instance with a "type" property. https://bugs.webkit.org/show_bug.cgi?id=189611
> 
> Doesn't need to be a separate phase. Checker can do it.
> 
> > Tools/WebGPUShadingLanguageRI/Metal/TypeOf.js:47
> > +            return node.type.visit(this);
> 
> I don't understand why there are recursive calls on the .type properties.
> Why not just return node.type?

All the other code assumes that typeOf doesn’t return a TypeRef; the recursive call unwraps the TypeRef. Applies to all other methods here.

> 
> > Tools/WebGPUShadingLanguageRI/PropertyResolver.js:172
> > +        if (node.lValue.unifyNode instanceof DereferenceExpression)
> > +            node.become(new IdentityExpression(node.lValue.unifyNode.ptr));
> 
> Can you remind me again why this is necessary? This could potentially have
> side effects around lvalue validation, and we want to make sure our compiler
> matches the spec exactly. I don't think this kind of optimization should be
> made without making sure it's not observable.

I’ll add some tests to make sure this isn’t observable, although I currently can't think of a case where this is incorrect w.r.t the interpreter. Without this check, the codegen will generate the following for  &(*x):

int v0 = *x;
int* v1 = &v0;

However, this means that v1 != x, but it the interpreter/spec expect v1 == x (C works the same way). Therefore I collapse expressions of the form &(*x) to avoid the code generation being incorrect.
Comment 41 Thomas Denney 2018-09-19 23:27:39 PDT
Created attachment 350174 [details]
Patch
Comment 42 WebKit Commit Bot 2018-09-20 19:06:51 PDT
Comment on attachment 350174 [details]
Patch

Clearing flags on attachment: 350174

Committed r236299: <https://trac.webkit.org/changeset/236299>
Comment 43 WebKit Commit Bot 2018-09-20 19:06:53 PDT
All reviewed patches have been landed.  Closing bug.
Comment 44 Myles C. Maxfield 2018-10-13 19:16:03 PDT
Migrated to https://github.com/gpuweb/WHLSL/issues/109