Add "if" statements to WSL
authormmaxfield@apple.com <mmaxfield@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 8 Sep 2017 02:57:23 +0000 (02:57 +0000)
committermmaxfield@apple.com <mmaxfield@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 8 Sep 2017 02:57:23 +0000 (02:57 +0000)
https://bugs.webkit.org/show_bug.cgi?id=176294

Reviewed by Filip Pizlo.

Fairly straightforward implementation. ReturnChecker only returns true iff there is an else block,
and both the if body and the else body recursively return true.

In order to accept both syntaxes:
if (foo)
    bar;
... and ...
if (foo) {
    bar;
}
This patch lets parseStatement() call parseBlock(). This means that the following is valid:
int x = 7;
if (foo) {
    int x = 8;
    // x is 8 here!
}
// x is 7 here!

This production means that these blocks don't require "if" statements, so you can just have:
int foo() {
   int x = 7;
   {
       int x = 8;
       // x is 8 here!
   }
   // x is 7 here!
}

However, this patch doesn't touch the following use-case:
if (int x = bar()) {
    // use x here
}

* WebGPUShadingLanguageRI/All.js:
* WebGPUShadingLanguageRI/Checker.js:
* WebGPUShadingLanguageRI/Evaluator.js:
(Evaluator.prototype.visitIfStatement):
* WebGPUShadingLanguageRI/IfStatement.js: Copied from Tools/WebGPUShadingLanguageRI/TypeDef.js.
(IfStatement):
(IfStatement.prototype.get origin):
(IfStatement.prototype.get conditional):
(IfStatement.prototype.get body):
(IfStatement.prototype.get elseBody):
(IfStatement.prototype.toString):
* WebGPUShadingLanguageRI/NameResolver.js:
(NameResolver.prototype.visitIfStatement):
* WebGPUShadingLanguageRI/Parse.js:
(parseTypeParameters):
(parseIfStatement):
(parseStatement):
* WebGPUShadingLanguageRI/ReturnChecker.js:
(ReturnChecker.prototype.visitIfStatement):
* WebGPUShadingLanguageRI/Rewriter.js:
(Rewriter.prototype.visitIfStatement):
(Rewriter):
* WebGPUShadingLanguageRI/Test.html:
* WebGPUShadingLanguageRI/Test.js:
(TEST_variableShadowing):
(TEST_ifStatement):
(TEST_ifElseStatement):
(TEST_ifElseIfStatement):
(TEST_ifElseIfElseStatement):
(TEST_returnIf):
(TEST_protocolMonoPolySigDoublePolyDefExplicit): Deleted.
* WebGPUShadingLanguageRI/TypeDef.js:
(TypeDef.prototype.toString):
(TypeDef):
* WebGPUShadingLanguageRI/Visitor.js:
(Visitor.prototype.visitIfStatement):

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

13 files changed:
Tools/ChangeLog
Tools/WebGPUShadingLanguageRI/All.js
Tools/WebGPUShadingLanguageRI/Checker.js
Tools/WebGPUShadingLanguageRI/Evaluator.js
Tools/WebGPUShadingLanguageRI/IfStatement.js [new file with mode: 0644]
Tools/WebGPUShadingLanguageRI/NameResolver.js
Tools/WebGPUShadingLanguageRI/Parse.js
Tools/WebGPUShadingLanguageRI/ReturnChecker.js
Tools/WebGPUShadingLanguageRI/Rewriter.js
Tools/WebGPUShadingLanguageRI/Test.html
Tools/WebGPUShadingLanguageRI/Test.js
Tools/WebGPUShadingLanguageRI/TypeDef.js
Tools/WebGPUShadingLanguageRI/Visitor.js

index 3adb3bd..6f31de4 100644 (file)
@@ -1,3 +1,80 @@
+2017-09-07  Myles C. Maxfield  <mmaxfield@apple.com>
+
+        Add "if" statements to WSL
+        https://bugs.webkit.org/show_bug.cgi?id=176294
+
+        Reviewed by Filip Pizlo.
+
+        Fairly straightforward implementation. ReturnChecker only returns true iff there is an else block,
+        and both the if body and the else body recursively return true.
+
+        In order to accept both syntaxes:
+        if (foo)
+            bar;
+        ... and ...
+        if (foo) {
+            bar;
+        }
+        This patch lets parseStatement() call parseBlock(). This means that the following is valid:
+        int x = 7;
+        if (foo) {
+            int x = 8;
+            // x is 8 here!
+        }
+        // x is 7 here!
+
+        This production means that these blocks don't require "if" statements, so you can just have:
+        int foo() {
+           int x = 7;
+           {
+               int x = 8;
+               // x is 8 here!
+           }
+           // x is 7 here!
+        }
+
+        However, this patch doesn't touch the following use-case:
+        if (int x = bar()) {
+            // use x here
+        }
+
+        * WebGPUShadingLanguageRI/All.js:
+        * WebGPUShadingLanguageRI/Checker.js:
+        * WebGPUShadingLanguageRI/Evaluator.js:
+        (Evaluator.prototype.visitIfStatement):
+        * WebGPUShadingLanguageRI/IfStatement.js: Copied from Tools/WebGPUShadingLanguageRI/TypeDef.js.
+        (IfStatement):
+        (IfStatement.prototype.get origin):
+        (IfStatement.prototype.get conditional):
+        (IfStatement.prototype.get body):
+        (IfStatement.prototype.get elseBody):
+        (IfStatement.prototype.toString):
+        * WebGPUShadingLanguageRI/NameResolver.js:
+        (NameResolver.prototype.visitIfStatement):
+        * WebGPUShadingLanguageRI/Parse.js:
+        (parseTypeParameters):
+        (parseIfStatement):
+        (parseStatement):
+        * WebGPUShadingLanguageRI/ReturnChecker.js:
+        (ReturnChecker.prototype.visitIfStatement):
+        * WebGPUShadingLanguageRI/Rewriter.js:
+        (Rewriter.prototype.visitIfStatement):
+        (Rewriter):
+        * WebGPUShadingLanguageRI/Test.html:
+        * WebGPUShadingLanguageRI/Test.js:
+        (TEST_variableShadowing):
+        (TEST_ifStatement):
+        (TEST_ifElseStatement):
+        (TEST_ifElseIfStatement):
+        (TEST_ifElseIfElseStatement):
+        (TEST_returnIf):
+        (TEST_protocolMonoPolySigDoublePolyDefExplicit): Deleted.
+        * WebGPUShadingLanguageRI/TypeDef.js:
+        (TypeDef.prototype.toString):
+        (TypeDef):
+        * WebGPUShadingLanguageRI/Visitor.js:
+        (Visitor.prototype.visitIfStatement):
+
 2017-09-07  Lucas Forschler  <lforschler@apple.com>
 
         Test commit after server upgrade from subversion 1.9.5 to 1.9.7
index 4e5666d..60b75fd 100644 (file)
@@ -65,6 +65,7 @@ load("FuncDef.js");
 load("FuncInstantiator.js");
 load("FuncParameter.js");
 load("FunctionLikeBlock.js");
+load("IfStatement.js");
 load("Inline.js");
 load("Inliner.js");
 load("InstantiateImmediates.js");
index 126437b..b66d94c 100644 (file)
@@ -220,11 +220,23 @@ class Checker extends Visitor {
     {
         let resultType = node.operand.visit(this);
         if (!resultType)
-            throw new Error("Trying to negate something with no type: " + node.value);
+            throw new Error("Trying to negate something with no type: " + node.operand);
         if (!resultType.equals(this._program.intrinsics.bool))
-            throw new WError("Trying to negate something that isn't a bool: " + node.value);
+            throw new WError("Trying to negate something that isn't a bool: " + node.operand);
         return this._program.intrinsics.bool;
     }
+
+    visitIfStatement(node)
+    {
+        let conditionalResultType = node.conditional.visit(this);
+        if (!conditionalResultType)
+            throw new Error("Trying to negate something with no type: " + node.conditional);
+        if (!conditionalResultType.equals(this._program.intrinsics.bool))
+            throw new WError("Trying to negate something that isn't a bool: " + node.conditional);
+        node.body.visit(this);
+        if (node.elseBody)
+            node.elseBody.visit(this);
+    }
     
     visitCommaExpression(node)
     {
index a1ba41f..77219e6 100644 (file)
@@ -150,6 +150,14 @@ class Evaluator extends Visitor {
     {
         return EPtr.box(!node.operand.visit(this).loadValue());
     }
+
+    visitIfStatement(node)
+    {
+        if (node.conditional.visit(this).loadValue())
+            return node.body.visit(this);
+        else if (node.elseBody)
+            return node.elseBody.visit(this);
+    }
     
     visitCallExpression(node)
     {
diff --git a/Tools/WebGPUShadingLanguageRI/IfStatement.js b/Tools/WebGPUShadingLanguageRI/IfStatement.js
new file mode 100644 (file)
index 0000000..76de5f9
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * 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 IfStatement extends Node {
+    constructor(origin, conditional, body, elseBody)
+    {
+        super();
+        this._origin = origin;
+        this._conditional = conditional;
+        this._body = body;
+        this._elseBody = elseBody;
+    }
+
+    get origin() { return this._origin; }
+    get conditional() { return this._conditional; }
+    get body() { return this._body; }
+    get elseBody() { return this._elseBody; }
+
+    toString()
+    {
+        let result = "if (" + this.conditional + ") " + this.body;
+        if (this.elseBody)
+            return result + " else " + this.elseBody;
+        return result;
+    }
+};
+
index 455a593..116e318 100644 (file)
@@ -90,6 +90,15 @@ class NameResolver extends Visitor {
         for (let statement of node.statements)
             statement.visit(checker);
     }
+
+    visitIfStatement(node)
+    {
+        node.conditional.visit(this);
+        // If statement's bodies might not be Blocks, so we need to explicitly give them a new context.
+        node.body.visit(new NameResolver(new NameContext(this._nameContext)));
+        if (node.elseBody)
+            node.elseBody.visit(new NameResolver(new NameContext(this._nameContext)));
+    }
     
     visitProtocolDecl(node)
     {
index efda049..a61a48c 100644 (file)
@@ -148,7 +148,7 @@ function parse(program, origin, lineNumberOffset, text)
                 let type = parseType();
                 let name = consumeKind("identifier");
                 assertNext(",", ">", ">>");
-                return new ConstexprTypeParameter(type.origin, name, type);
+                return new ConstexprTypeParameter(type.origin, name.text, type);
             });
             if (constexpr)
                 result.push(constexpr);
@@ -535,6 +535,19 @@ function parse(program, origin, lineNumberOffset, text)
         consume(";");
         return new Return(origin, expression);
     }
+
+    function parseIfStatement()
+    {
+        let origin = consume("if");
+        consume("(");
+        let conditional = parseExpression();
+        consume(")");
+        let body = parseStatement();
+        let elseBody;
+        if (tryConsume("else"))
+            elseBody = parseStatement();
+        return new IfStatement(origin, new CastExpression(conditional.origin, new TypeRef(conditional.origin, "bool", []), [], [conditional]), body, elseBody);
+    }
     
     function parseVariableDecls()
     {
@@ -567,6 +580,10 @@ function parse(program, origin, lineNumberOffset, text)
             return parseDo();
         if (token.text == "for")
             return parseFor();
+        if (token.text == "if")
+            return parseIfStatement();
+        if (token.text == "{")
+            return parseBlock();
         let variableDecl = lexer.backtrackingScope(parseVariableDecls);
         if (variableDecl)
             return variableDecl;
index 9035fb2..472c0d0 100644 (file)
@@ -40,8 +40,12 @@ class ReturnChecker extends Visitor {
         // https://bugs.webkit.org/show_bug.cgi?id=176263
         return node.statements.reduce((result, statement) => result || statement.visit(this), false);
     }
-    
-    // When we add control flow statements, we'll need to return true only if both blocks return true.
+
+    visitIfStatement(node)
+    {
+        return node.elseBody && node.body.visit(this) && node.elseBody.visit(this);
+    }
+
     // If a loop returns, then it counts only if the loop is guaranteed to run at least once.
     
     visitReturn(node)
index 5cec804..95881ab 100644 (file)
@@ -283,5 +283,10 @@ class Rewriter extends VisitorBase {
     {
         return new LogicalNot(node.origin, node.operand.visit(this));
     }
+
+    visitIfStatement(node)
+    {
+        return new IfStatement(node.origin, node.conditional.visit(this), node.body.visit(this), node.elseBody ? node.elseBody.visit(this) : undefined);
+    }
 }
 
index 46c8227..abff96a 100644 (file)
@@ -41,6 +41,7 @@
 <script src="FuncInstantiator.js"></script>
 <script src="FuncParameter.js"></script>
 <script src="FunctionLikeBlock.js"></script>
+<script src="IfStatement.js"></script>
 <script src="Inline.js"></script>
 <script src="Inliner.js"></script>
 <script src="InstantiateImmediates.js"></script>
index 3537e84..7af744f 100644 (file)
@@ -1109,6 +1109,233 @@ function TEST_protocolDoublePolySigDoublePolyDefExplicit()
     checkInt(program, callFunction(program, "foo", [], [makeInt(program, 54), makeInt(program, 12), makeInt(program, 39)]), 54 + 12 + 39);
 }
 
+function TEST_variableShadowing()
+{
+    let program = doPrep(`
+        int foo()
+        {
+            int y;
+            int x = 7;
+            {
+                int x = 8;
+                y = x;
+            }
+            return y;
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [], []), 8);
+    program = doPrep(`
+        int foo()
+        {
+            int y;
+            int x = 7;
+            {
+                int x = 8;
+            }
+            y = x;
+            return y;
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [], []), 7);
+}
+
+function TEST_ifStatement()
+{
+    let program = doPrep(`
+        int foo(int x)
+        {
+            int y = 6;
+            if (x == 7) {
+                y = 8;
+            }
+            return y;
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 3)]), 6);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 4)]), 6);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 5)]), 6);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 6)]), 6);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 7)]), 8);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 8)]), 6);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 9)]), 6);
+}
+
+function TEST_ifElseStatement()
+{
+    let program = doPrep(`
+        int foo(int x)
+        {
+            int y = 6;
+            if (x == 7) {
+                y = 8;
+            } else {
+                y = 9;
+            }
+            return y;
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 3)]), 9);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 4)]), 9);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 5)]), 9);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 6)]), 9);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 7)]), 8);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 8)]), 9);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 9)]), 9);
+}
+
+function TEST_ifElseIfStatement()
+{
+    let program = doPrep(`
+        int foo(int x)
+        {
+            int y = 6;
+            if (x == 7) {
+                y = 8;
+            } else if (x == 8) {
+                y = 9;
+            }
+            return y;
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 3)]), 6);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 4)]), 6);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 5)]), 6);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 6)]), 6);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 7)]), 8);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 8)]), 9);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 9)]), 6);
+}
+
+function TEST_ifElseIfElseStatement()
+{
+    let program = doPrep(`
+        int foo(int x)
+        {
+            int y = 6;
+            if (x == 7) {
+                y = 8;
+            } else if (x == 8) {
+                y = 9;
+            } else {
+                y = 10;
+            }
+            return y;
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 3)]), 10);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 4)]), 10);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 5)]), 10);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 6)]), 10);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 7)]), 8);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 8)]), 9);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 9)]), 10);
+}
+
+function TEST_returnIf()
+{
+    checkFail(
+        () => doPrep(`
+            int foo(int x)
+            {
+                int y = 6;
+                if (x == 7) {
+                    return y;
+                }
+            }
+        `),
+        (e) => e instanceof WTypeError);
+    checkFail(
+        () => doPrep(`
+            int foo(int x)
+            {
+                int y = 6;
+                if (x == 7) {
+                    return y;
+                } else {
+                    y = 8;
+                }
+            }
+        `),
+        (e) => e instanceof WTypeError);
+    checkFail(
+        () => doPrep(`
+            int foo(int x)
+            {
+                int y = 6;
+                if (x == 7) {
+                    y = 8;
+                } else {
+                    return y;
+                }
+            }
+        `),
+        (e) => e instanceof WTypeError);
+    let program = doPrep(`
+        int foo(int x)
+        {
+            int y = 6;
+            if (x == 7) {
+                return 8;
+            } else {
+                return 10;
+            }
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 3)]), 10);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 4)]), 10);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 5)]), 10);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 6)]), 10);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 7)]), 8);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 8)]), 10);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 9)]), 10);
+    checkFail(
+        () => doPrep(`
+            int foo(int x)
+            {
+                int y = 6;
+                if (x == 7) {
+                    return 8;
+                } else if (x == 9) {
+                    return 10;
+                }
+            }
+        `),
+        (e) => e instanceof WTypeError);
+    program = doPrep(`
+        int foo(int x)
+        {
+            int y = 6;
+            if (x == 7) {
+                return 8;
+            } else {
+                y = 9;
+            }
+            return y;
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 3)]), 9);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 4)]), 9);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 5)]), 9);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 6)]), 9);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 7)]), 8);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 8)]), 9);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 9)]), 9);
+    checkFail(
+        () => doPrep(`
+            int foo(int x)
+            {
+                int y = 6;
+                if (x == 7) {
+                    return 8;
+                } else {
+                    return 10;
+                }
+                return 11;
+            }
+        `),
+        (e) => e instanceof WTypeError);
+}
+
 function TEST_protocolMonoPolySigDoublePolyDefExplicit()
 {
     checkFail(
index 69b6574..6c0f9c1 100644 (file)
@@ -38,5 +38,10 @@ class TypeDef extends Type {
     get name() { return this._name; }
     get typeParameters() { return this._typeParameters; }
     get type() { return this._type; }
+    
+    toString()
+    {
+        return "typedef " + this.name + "<" + this.typeParameters + "> = " + this.type;
+    }
 }
 
index f71b28b..d6e8669 100644 (file)
@@ -208,6 +208,14 @@ class Visitor extends VisitorBase {
             node.variable.visit(this);
     }
     
+    visitIfStatement(node)
+    {
+        node.conditional.visit(this);
+        node.body.visit(this);
+        if (node.elseBody)
+            node.elseBody.visit(this);
+    }
+
     visitReturn(node)
     {
         if (node.value)