Bug 1308056: wasm: add syntax for inline import/export in functions; r=luke
authorBenjamin Bouvier <benj@benj.me>
Fri, 07 Oct 2016 11:58:18 +0200
changeset 360407 d375dc2204750eaa95dbf00a0ee4f12d89b04605
parent 360406 36b0d3332ae4384840067d97c2e4fdfbabe31da0
child 360408 968f741d2686152e75722b955d15ee889c549fe2
push id6795
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 14:19:46 +0000
treeherdermozilla-beta@76101b503191 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1308056
milestone52.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1308056: wasm: add syntax for inline import/export in functions; r=luke MozReview-Commit-ID: 7lrs8wG3Tn0
js/src/asmjs/WasmTextToBinary.cpp
js/src/jit-test/tests/wasm/table-gc.js
js/src/jit-test/tests/wasm/tables.js
js/src/jit-test/tests/wasm/text.js
--- a/js/src/asmjs/WasmTextToBinary.cpp
+++ b/js/src/asmjs/WasmTextToBinary.cpp
@@ -2489,110 +2489,59 @@ ParseLocalOrParam(WasmParseContext& c, A
         return locals->append(AstName()) && ParseValueTypeList(c, localTypes);
 
     WasmToken token;
     return locals->append(c.ts.get().name()) &&
            c.ts.match(WasmToken::ValueType, &token, c.error) &&
            localTypes->append(token.valueType());
 }
 
-static AstFunc*
-ParseFunc(WasmParseContext& c, AstModule* module)
+static bool
+ParseInlineImport(WasmParseContext& c, InlineImport* import)
+{
+    return c.ts.match(WasmToken::Text, &import->module, c.error) &&
+           c.ts.match(WasmToken::Text, &import->field, c.error);
+}
+
+static bool
+ParseInlineExport(WasmParseContext& c, DefinitionKind kind, AstModule* module,
+                  AstRef ref = AstRef())
 {
-    AstValTypeVector vars(c.lifo);
-    AstValTypeVector args(c.lifo);
-    AstNameVector locals(c.lifo);
-
-    AstName exportName = c.ts.getIfText();
-    AstName funcName = c.ts.getIfName();
-
-    AstRef sig;
-
+    WasmToken name;
+    if (!c.ts.match(WasmToken::Text, &name, c.error))
+        return false;
+
+    AstExport* exp = nullptr;
+    if (!ref.isInvalid())
+        exp = new(c.lifo) AstExport(name.text(), kind, ref);
+    else
+        exp = new(c.lifo) AstExport(name.text(), kind);
+
+    return exp && module->append(exp);
+}
+
+static bool
+MaybeParseTypeUse(WasmParseContext& c, AstRef* sig)
+{
     WasmToken openParen;
-    if (exportName.empty() && c.ts.getIf(WasmToken::OpenParen, &openParen)) {
-        if (c.ts.getIf(WasmToken::Export)) {
-            WasmToken text;
-            if (!c.ts.match(WasmToken::Text, &text, c.error))
-                return nullptr;
-            if (!c.ts.match(WasmToken::CloseParen, c.error))
-                return nullptr;
-            exportName = text.text();
-        } else {
-            c.ts.unget(openParen);
-        }
-    }
-
-    if (!exportName.empty()) {
-        if (funcName.empty())
-            funcName = exportName;
-        AstExport* exp = new(c.lifo) AstExport(exportName, DefinitionKind::Function,
-                                               AstRef(funcName, AstNoIndex));
-        if (!exp || !module->append(exp))
-            return nullptr;
-    }
-
     if (c.ts.getIf(WasmToken::OpenParen, &openParen)) {
         if (c.ts.getIf(WasmToken::Type)) {
-            if (!c.ts.matchRef(&sig, c.error))
-                return nullptr;
+            if (!c.ts.matchRef(sig, c.error))
+                return false;
             if (!c.ts.match(WasmToken::CloseParen, c.error))
-                return nullptr;
+                return false;
         } else {
             c.ts.unget(openParen);
         }
     }
-
-    AstExprVector body(c.lifo);
-    ExprType result = ExprType::Void;
-
-    while (c.ts.getIf(WasmToken::OpenParen)) {
-        WasmToken token = c.ts.get();
-        switch (token.kind()) {
-          case WasmToken::Local:
-            if (!ParseLocalOrParam(c, &locals, &vars))
-                return nullptr;
-            break;
-          case WasmToken::Param:
-            if (!vars.empty()) {
-                c.ts.generateError(token, c.error);
-                return nullptr;
-            }
-            if (!ParseLocalOrParam(c, &locals, &args))
-                return nullptr;
-            break;
-          case WasmToken::Result:
-            if (!ParseResult(c, &result))
-                return nullptr;
-            break;
-          default:
-            c.ts.unget(token);
-            AstExpr* expr = ParseExprInsideParens(c);
-            if (!expr || !body.append(expr))
-                return nullptr;
-            break;
-        }
-        if (!c.ts.match(WasmToken::CloseParen, c.error))
-            return nullptr;
-    }
-
-    if (!ParseExprList(c, &body, true))
-        return nullptr;
-
-    if (sig.isInvalid()) {
-        uint32_t sigIndex;
-        if (!module->declare(AstSig(Move(args), result), &sigIndex))
-            return nullptr;
-        sig.setIndex(sigIndex);
-    }
-
-    return new(c.lifo) AstFunc(funcName, sig, Move(vars), Move(locals), Move(body));
+    return true;
 }
 
 static bool
-ParseFuncType(WasmParseContext& c, AstSig* sig)
+ParseFuncSig(WasmParseContext& c, AstSig* sig)
 {
     AstValTypeVector args(c.lifo);
     ExprType result = ExprType::Void;
 
     while (c.ts.getIf(WasmToken::OpenParen)) {
         WasmToken token = c.ts.get();
         switch (token.kind()) {
           case WasmToken::Param:
@@ -2610,28 +2559,143 @@ ParseFuncType(WasmParseContext& c, AstSi
         if (!c.ts.match(WasmToken::CloseParen, c.error))
             return false;
     }
 
     *sig = AstSig(Move(args), result);
     return true;
 }
 
+static bool
+ParseFuncType(WasmParseContext& c, AstRef* ref, AstModule* module)
+{
+    if (!MaybeParseTypeUse(c, ref))
+        return false;
+
+    if (ref->isInvalid()) {
+        AstSig sig(c.lifo);
+        if (!ParseFuncSig(c, &sig))
+            return false;
+        uint32_t sigIndex;
+        if (!module->declare(Move(sig), &sigIndex))
+            return false;
+        ref->setIndex(sigIndex);
+    }
+
+    return true;
+}
+
+static bool
+ParseFunc(WasmParseContext& c, AstModule* module)
+{
+    AstValTypeVector vars(c.lifo);
+    AstValTypeVector args(c.lifo);
+    AstNameVector locals(c.lifo);
+
+    AstName funcName = c.ts.getIfName();
+
+    // Inline imports and exports.
+    WasmToken openParen;
+    if (c.ts.getIf(WasmToken::OpenParen, &openParen)) {
+        if (c.ts.getIf(WasmToken::Import)) {
+            if (module->funcs().length()) {
+                c.ts.generateError(openParen, "import after function definition", c.error);
+                return false;
+            }
+
+            InlineImport names;
+            if (!ParseInlineImport(c, &names))
+                return false;
+            if (!c.ts.match(WasmToken::CloseParen, c.error))
+                return false;
+
+            AstRef sig;
+            if (!ParseFuncType(c, &sig, module))
+                return false;
+
+            auto* imp = new(c.lifo) AstImport(funcName, names.module.text(), names.field.text(), sig);
+            return imp && module->append(imp);
+        }
+
+        if (c.ts.getIf(WasmToken::Export)) {
+            AstRef ref = funcName.empty()
+                         ? AstRef(AstName(), module->funcImportNames().length() + module->funcs().length())
+                         : AstRef(funcName, AstNoIndex);
+            if (!ParseInlineExport(c, DefinitionKind::Function, module, ref))
+                return false;
+            if (!c.ts.match(WasmToken::CloseParen, c.error))
+                return false;
+        } else {
+            c.ts.unget(openParen);
+        }
+    }
+
+    AstRef sigRef;
+    if (!MaybeParseTypeUse(c, &sigRef))
+        return false;
+
+    AstExprVector body(c.lifo);
+
+    ExprType result = ExprType::Void;
+    while (c.ts.getIf(WasmToken::OpenParen)) {
+        WasmToken token = c.ts.get();
+        switch (token.kind()) {
+          case WasmToken::Local:
+            if (!ParseLocalOrParam(c, &locals, &vars))
+                return false;
+            break;
+          case WasmToken::Param:
+            if (!vars.empty()) {
+                c.ts.generateError(token, c.error);
+                return false;
+            }
+            if (!ParseLocalOrParam(c, &locals, &args))
+                return false;
+            break;
+          case WasmToken::Result:
+            if (!ParseResult(c, &result))
+                return false;
+            break;
+          default:
+            c.ts.unget(token);
+            AstExpr* expr = ParseExprInsideParens(c);
+            if (!expr || !body.append(expr))
+                return false;
+            break;
+        }
+        if (!c.ts.match(WasmToken::CloseParen, c.error))
+            return false;
+    }
+
+    if (!ParseExprList(c, &body, true))
+        return false;
+
+    if (sigRef.isInvalid()) {
+        uint32_t sigIndex;
+        if (!module->declare(AstSig(Move(args), result), &sigIndex))
+            return false;
+        sigRef.setIndex(sigIndex);
+    }
+
+    auto* func = new(c.lifo) AstFunc(funcName, sigRef, Move(vars), Move(locals), Move(body));
+    return func && module->append(func);
+}
+
 static AstSig*
 ParseTypeDef(WasmParseContext& c)
 {
     AstName name = c.ts.getIfName();
 
     if (!c.ts.match(WasmToken::OpenParen, c.error))
         return nullptr;
     if (!c.ts.match(WasmToken::Func, c.error))
         return nullptr;
 
     AstSig sig(c.lifo);
-    if (!ParseFuncType(c, &sig))
+    if (!ParseFuncSig(c, &sig))
         return nullptr;
 
     if (!c.ts.match(WasmToken::CloseParen, c.error))
         return nullptr;
 
     return new(c.lifo) AstSig(name, Move(sig));
 }
 
@@ -2804,51 +2868,38 @@ ParseImport(WasmParseContext& c, AstModu
             if (!c.ts.match(WasmToken::CloseParen, c.error))
                 return nullptr;
             return new(c.lifo) AstImport(name, moduleName.text(), fieldName.text(),
                                          AstGlobal(AstName(), typeToken.valueType(), flags));
         }
         if (c.ts.getIf(WasmToken::Func)) {
             AstName name = c.ts.getIfName();
 
-            WasmToken token;
-            if (c.ts.getIf(WasmToken::Type, &token)) {
-                if (!c.ts.matchRef(&sigRef, c.error))
-                    return nullptr;
-            } else {
-                AstSig sig(c.lifo);
-                if (!ParseFuncType(c, &sig))
-                    return nullptr;
-
-                uint32_t sigIndex;
-                if (!module->declare(Move(sig), &sigIndex))
-                    return nullptr;
-
-                sigRef.setIndex(sigIndex);
-            }
+            AstRef sigRef;
+            if (!ParseFuncType(c, &sigRef, module))
+                return nullptr;
             if (!c.ts.match(WasmToken::CloseParen, c.error))
                 return nullptr;
 
-            return new(c.lifo) AstImport(name, moduleName.text(), fieldName.text(),
-                                         sigRef);
+            return new(c.lifo) AstImport(name, moduleName.text(), fieldName.text(), sigRef);
         }
 
         if (c.ts.getIf(WasmToken::Type)) {
             if (!c.ts.matchRef(&sigRef, c.error))
                 return nullptr;
             if (!c.ts.match(WasmToken::CloseParen, c.error))
                 return nullptr;
         } else {
             c.ts.unget(openParen);
         }
     }
 
     if (sigRef.isInvalid()) {
         AstSig sig(c.lifo);
-        if (!ParseFuncType(c, &sig))
+        if (!ParseFuncSig(c, &sig))
             return nullptr;
 
         uint32_t sigIndex;
         if (!module->declare(Move(sig), &sigIndex))
             return nullptr;
         sigRef.setIndex(sigIndex);
     }
 
@@ -2926,35 +2977,16 @@ ParseExport(WasmParseContext& c)
         break;
     }
 
     c.ts.generateError(exportee, c.error);
     return nullptr;
 }
 
 static bool
-ParseInlineImport(WasmParseContext& c, InlineImport* import)
-{
-    return c.ts.match(WasmToken::Text, &import->module, c.error) &&
-           c.ts.match(WasmToken::Text, &import->field, c.error);
-}
-
-static bool
-ParseInlineExport(WasmParseContext& c, DefinitionKind kind, AstModule* module)
-{
-    WasmToken name;
-    if (!c.ts.match(WasmToken::Text, &name, c.error))
-        return false;
-    auto* exp = new(c.lifo) AstExport(name.text(), kind);
-    if (!exp || !module->append(exp))
-        return false;
-    return true;
-}
-
-static bool
 ParseTable(WasmParseContext& c, WasmToken token, AstModule* module)
 {
     AstName name = c.ts.getIfName();
 
     if (c.ts.getIf(WasmToken::OpenParen)) {
         // Either an import and we're done, or an export and continue.
         if (c.ts.getIf(WasmToken::Import)) {
             InlineImport names;
@@ -3134,18 +3166,17 @@ ParseModule(const char16_t* text, LifoAl
           }
           case WasmToken::Elem: {
             AstElemSegment* segment = ParseElemSegment(c);
             if (!segment || !module->append(segment))
                 return nullptr;
             break;
           }
           case WasmToken::Func: {
-            AstFunc* func = ParseFunc(c, module);
-            if (!func || !module->append(func))
+            if (!ParseFunc(c, module))
                 return nullptr;
             break;
           }
           default:
             c.ts.generateError(section, c.error);
             return nullptr;
         }
 
--- a/js/src/jit-test/tests/wasm/table-gc.js
+++ b/js/src/jit-test/tests/wasm/table-gc.js
@@ -4,17 +4,17 @@
 
 load(libdir + 'wasm.js');
 
 const Module = WebAssembly.Module;
 const Instance = WebAssembly.Instance;
 const Table = WebAssembly.Table;
 
 var caller = `(type $v2i (func (result i32))) (func $call (param $i i32) (result i32) (call_indirect $v2i (get_local $i))) (export "call" $call)`
-var callee = i => `(func $f${i} (type $v2i) (result i32) (i32.const ${i}))`;
+var callee = i => `(func $f${i} (type $v2i) (i32.const ${i}))`;
 
 // A table should not hold exported functions alive and exported functions
 // should not hold their originating table alive. Live exported functions should
 // hold instances alive and instances hold imported tables alive. Nothing
 // should hold the export object alive.
 resetFinalizeCount();
 var i = wasmEvalText(`(module (table 2 anyfunc) (export "tbl" table) (elem (i32.const 0) $f0) ${callee(0)} ${caller})`);
 var e = i.exports;
--- a/js/src/jit-test/tests/wasm/tables.js
+++ b/js/src/jit-test/tests/wasm/tables.js
@@ -31,17 +31,17 @@ var m = new Module(wasmTextToBinary(`
         (elem (get_global 0) $f0 $f0)
         ${callee(0)})
 `));
 var tbl = new Table({initial:50, element:"anyfunc"});
 assertEq(new Instance(m, {globals:{a:20, table:tbl}}) instanceof Instance, true);
 assertErrorMessage(() => new Instance(m, {globals:{a:50, table:tbl}}), RangeError, /elem segment does not fit/);
 
 var caller = `(type $v2i (func (result i32))) (func $call (param $i i32) (result i32) (call_indirect $v2i (get_local $i))) (export "call" $call)`
-var callee = i => `(func $f${i} (type $v2i) (result i32) (i32.const ${i}))`;
+var callee = i => `(func $f${i} (type $v2i) (i32.const ${i}))`;
 
 var call = wasmEvalText(`(module (table 10 anyfunc) ${callee(0)} ${caller})`).exports.call;
 assertErrorMessage(() => call(0), Error, /indirect call to null/);
 assertErrorMessage(() => call(10), Error, /out-of-range/);
 
 var call = wasmEvalText(`(module (table 10 anyfunc) (elem (i32.const 0)) ${callee(0)} ${caller})`).exports.call;
 assertErrorMessage(() => call(0), Error, /indirect call to null/);
 assertErrorMessage(() => call(10), Error, /out-of-range/);
@@ -150,19 +150,19 @@ assertErrorMessage(() => e3.call(2), Err
 
 // Call signatures are matched structurally:
 
 var call = wasmEvalText(`(module
     (type $v2i1 (func (result i32)))
     (type $v2i2 (func (result i32)))
     (type $i2v (func (param i32)))
     (table anyfunc (elem $a $b $c))
-    (func $a (type $v2i1) (result i32) (i32.const 0))
-    (func $b (type $v2i2) (result i32) (i32.const 1))
-    (func $c (type $i2v) (param i32))
+    (func $a (type $v2i1) (i32.const 0))
+    (func $b (type $v2i2) (i32.const 1))
+    (func $c (type $i2v))
     (func $call (param i32) (result i32) (call_indirect $v2i1 (get_local 0)))
     (export "call" $call)
 )`).exports.call;
 assertEq(call(0), 0);
 assertEq(call(1), 1);
 assertErrorMessage(() => call(2), Error, /indirect call signature mismatch/);
 
 var call = wasmEvalText(`(module
--- a/js/src/jit-test/tests/wasm/text.js
+++ b/js/src/jit-test/tests/wasm/text.js
@@ -56,10 +56,41 @@ assertErrorMessage(() => wasmEvalText('(
 wasmEvalText('(module (table $t (import "mod" "field") 1 anyfunc))', {mod: {field: table}});
 
 assertErrorMessage(() => wasmEvalText('(module (table $t (export "mod") 1))'), SyntaxError, parsingError);
 assertErrorMessage(() => wasmEvalText('(module (table $t (export "mod") anyfunc))'), SyntaxError, parsingError);
 assertErrorMessage(() => wasmEvalText('(module (table $t (export "mod") anyfunc 1 2 3))'), SyntaxError, parsingError);
 assertEq(wasmEvalText('(module (table $t (export "tbl") anyfunc (elem)))').exports.tbl instanceof Table, true);
 assertEq(wasmEvalText('(module (func) (table $t (export "tbl") anyfunc (elem 0 0 0)))').exports.tbl instanceof Table, true);
 
+// Functions.
+assertErrorMessage(() => wasmEvalText('(module (func $t import))'), SyntaxError, parsingError);
+assertErrorMessage(() => wasmEvalText('(module (func $t (import)))'), SyntaxError, parsingError);
+assertErrorMessage(() => wasmEvalText('(module (func $t (import "mod")))'), SyntaxError, parsingError);
+assertErrorMessage(() => wasmEvalText('(module (func $t (import "mod" "func" (local i32))))'), SyntaxError, parsingError);
+
+const func = i => 42 + i;
+wasmEvalText('(module (func $t (import "mod" "func")))', { mod: {func} });
+wasmEvalText('(module (func $t (import "mod" "func")(param i32)))', { mod: {func} });
+wasmEvalText('(module (func $t (import "mod" "func")(result i32)))', { mod: {func} });
+wasmEvalText('(module (func $t (import "mod" "func")(param i32) (result i32)))', { mod: {func} });
+wasmEvalText('(module (func $t (import "mod" "func")(result i32) (param i32)))', { mod: {func} });
+
+assertErrorMessage(() => wasmEvalText('(module (func $t (import "mod" "func") (type)))', { mod: {func} }), SyntaxError, parsingError);
+wasmEvalText('(module (type $t (func)) (func $t (import "mod" "func") (type $t)))', { mod: {func} });
+
+assertErrorMessage(() => wasmEvalText('(module (func $t (export))))'), SyntaxError, parsingError);
+wasmEvalText('(module (func (export "f")))');
+wasmEvalText('(module (func $f (export "f")))');
+wasmEvalText('(module (func $f (export "f") (result i32) (param i32) (i32.add (get_local 0) (i32.const 42))))');
+
+assertErrorMessage(() => wasmEvalText(`
+    (module
+        (type $tf (func (param i32) (result i32)))
+        (func (import "mod" "a") (type $tf))
+        (func (export "f1"))
+        (func (import "mod" "b") (type $tf))
+        (func (export "f2"))
+    )
+`), SyntaxError, /import after function definition/);
+
 // Note: the s-expression text format is temporary, this file is mostly just to
 // hold basic error smoke tests.