Bug 1311994 - Baldr: add flags immediates to current_memory/grow_memory/call_indirect (r=sunfish)
authorLuke Wagner <luke@mozilla.com>
Mon, 24 Oct 2016 13:20:53 -0500
changeset 319247 7fd8ec5750d3cdcfc0852998177b7a58d8d904c0
parent 319246 25937c3220ee8ecb7beb3771d39022c6f71b7f3d
child 319248 3e3bb5d0a8a9ca195c9b9861c1f6073732fc874c
push id30865
push usercbook@mozilla.com
push dateTue, 25 Oct 2016 08:31:38 +0000
treeherdermozilla-central@78b863e9fcd9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssunfish
bugs1311994
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 1311994 - Baldr: add flags immediates to current_memory/grow_memory/call_indirect (r=sunfish) MozReview-Commit-ID: 4qwtPliyesH
js/src/asmjs/WasmAST.h
js/src/asmjs/WasmBaselineCompile.cpp
js/src/asmjs/WasmBinary.h
js/src/asmjs/WasmBinaryIterator.cpp
js/src/asmjs/WasmBinaryIterator.h
js/src/asmjs/WasmBinaryToAST.cpp
js/src/asmjs/WasmBinaryToExperimentalText.cpp
js/src/asmjs/WasmBinaryToText.cpp
js/src/asmjs/WasmCompile.cpp
js/src/asmjs/WasmGeneratedSourceMap.h
js/src/asmjs/WasmIonCompile.cpp
js/src/asmjs/WasmTextToBinary.cpp
--- a/js/src/asmjs/WasmAST.h
+++ b/js/src/asmjs/WasmAST.h
@@ -190,32 +190,33 @@ enum class AstExprKind
     Block,
     Branch,
     BranchTable,
     Call,
     CallIndirect,
     ComparisonOperator,
     Const,
     ConversionOperator,
+    CurrentMemory,
     Drop,
     First,
     GetGlobal,
     GetLocal,
+    GrowMemory,
     If,
     Load,
     Nop,
     Pop,
     Return,
     SetGlobal,
     SetLocal,
     TeeLocal,
     Store,
     TernaryOperator,
     UnaryOperator,
-    NullaryOperator,
     Unreachable
 };
 
 class AstExpr : public AstNode
 {
     const AstExprKind kind_;
     ExprType type_;
 
@@ -540,16 +541,38 @@ class AstStore : public AstExpr
         value_(value)
     {}
 
     Expr expr() const { return expr_; }
     const AstLoadStoreAddress& address() const { return address_; }
     AstExpr& value() const { return *value_; }
 };
 
+class AstCurrentMemory final : public AstExpr
+{
+  public:
+    static const AstExprKind Kind = AstExprKind::CurrentMemory;
+    explicit AstCurrentMemory()
+      : AstExpr(Kind, ExprType::I32)
+    {}
+};
+
+class AstGrowMemory final : public AstExpr
+{
+    AstExpr* op_;
+
+  public:
+    static const AstExprKind Kind = AstExprKind::GrowMemory;
+    explicit AstGrowMemory(AstExpr* op)
+      : AstExpr(Kind, ExprType::I32), op_(op)
+    {}
+
+    AstExpr* op() const { return op_; }
+};
+
 class AstBranchTable : public AstExpr
 {
     AstExpr& index_;
     AstRef default_;
     AstRefVector table_;
     AstExpr* value_;
 
   public:
@@ -868,30 +891,16 @@ class AstModule : public AstNode
     bool append(AstGlobal* glob) {
         return globals_.append(glob);
     }
     const AstGlobalVector& globals() const {
         return globals_;
     }
 };
 
-class AstNullaryOperator final : public AstExpr
-{
-    Expr expr_;
-
-  public:
-    static const AstExprKind Kind = AstExprKind::NullaryOperator;
-    explicit AstNullaryOperator(Expr expr)
-      : AstExpr(Kind, ExprType::Limit),
-        expr_(expr)
-    {}
-
-    Expr expr() const { return expr_; }
-};
-
 class AstUnaryOperator final : public AstExpr
 {
     Expr expr_;
     AstExpr* op_;
 
   public:
     static const AstExprKind Kind = AstExprKind::UnaryOperator;
     explicit AstUnaryOperator(Expr expr, AstExpr* op)
--- a/js/src/asmjs/WasmBaselineCompile.cpp
+++ b/js/src/asmjs/WasmBaselineCompile.cpp
@@ -2297,16 +2297,19 @@ class BaseCompiler
     void builtinCall(SymbolicAddress builtin, const FunctionCall& call)
     {
         callSymbolic(builtin, call);
     }
 
     void builtinInstanceMethodCall(SymbolicAddress builtin, const ABIArg& instanceArg,
                                    const FunctionCall& call)
     {
+        // Builtin method calls assumed the TLS register has been set.
+        loadFromFramePtr(WasmTlsReg, frameOffsetFromSlot(tlsSlot_, MIRType::Pointer));
+
         CallSiteDesc desc(call.lineOrBytecode_, CallSiteDesc::Symbolic);
         masm.wasmCallBuiltinInstanceMethod(instanceArg, builtin);
     }
 
     //////////////////////////////////////////////////////////////////////
     //
     // Sundry low-level code generators.
 
@@ -6224,33 +6227,33 @@ BaseCompiler::emitTeeStoreWithCoercion(V
 }
 
 bool
 BaseCompiler::emitGrowMemory()
 {
     if (deadCode_)
         return true;
 
-    uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
+    Nothing arg;
+    if (!iter_.readGrowMemory(&arg))
+        return false;
 
     sync();
 
     uint32_t numArgs = 1;
     size_t stackSpace = stackConsumed(numArgs);
 
-    FunctionCall baselineCall(lineOrBytecode);
+    FunctionCall baselineCall(readCallSiteLineOrBytecode());
     beginCall(baselineCall, EscapesSandbox(true), IsBuiltinCall(true));
 
     ABIArg instanceArg = reserveArgument(baselineCall);
 
-    if (!emitCallArgs(SigI_, baselineCall))
-        return false;
-
-    if (!iter_.readCallReturn(ExprType::I32))
-        return false;
+    startCallArgs(baselineCall, stackArgAreaSize(SigI_));
+
+    passArg(baselineCall, ValType::I32, peek(0));
 
     builtinInstanceMethodCall(SymbolicAddress::GrowMemory, instanceArg, baselineCall);
 
     endCall(baselineCall);
 
     popValueStackBy(numArgs);
     masm.freeStack(stackSpace);
 
@@ -6260,30 +6263,27 @@ BaseCompiler::emitGrowMemory()
 }
 
 bool
 BaseCompiler::emitCurrentMemory()
 {
     if (deadCode_)
         return true;
 
-    uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
+    if (!iter_.readCurrentMemory())
+        return false;
 
     sync();
 
-    FunctionCall baselineCall(lineOrBytecode);
+    FunctionCall baselineCall(readCallSiteLineOrBytecode());
     beginCall(baselineCall, EscapesSandbox(true), IsBuiltinCall(true));
 
     ABIArg instanceArg = reserveArgument(baselineCall);
 
-    if (!emitCallArgs(Sig_, baselineCall))
-        return false;
-
-    if (!iter_.readCallReturn(ExprType::I32))
-        return false;
+    startCallArgs(baselineCall, stackArgAreaSize(Sig_));
 
     builtinInstanceMethodCall(SymbolicAddress::CurrentMemory, instanceArg, baselineCall);
 
     endCall(baselineCall);
 
     pushReturned(baselineCall, ExprType::I32);
 
     return true;
--- a/js/src/asmjs/WasmBinary.h
+++ b/js/src/asmjs/WasmBinary.h
@@ -148,16 +148,21 @@ enum class DefinitionKind
 };
 
 enum class GlobalFlags
 {
     IsMutable                            = 0x1,
     AllowedMask                          = 0x1
 };
 
+enum class MemoryTableFlags
+{
+    Default                              = 0x0
+};
+
 enum class Expr : uint32_t // fix type so we can cast from any u16 in decoder
 {
     // Control flow operators
     Unreachable                          = 0x00,
     Nop                                  = 0x01,
     Block                                = 0x02,
     Loop                                 = 0x03,
     If                                   = 0x04,
--- a/js/src/asmjs/WasmBinaryIterator.cpp
+++ b/js/src/asmjs/WasmBinaryIterator.cpp
@@ -105,17 +105,16 @@ wasm::Classify(Expr expr)
       case Expr::F32x4neg:
       case Expr::F32x4sqrt:
       case Expr::F32x4abs:
       case Expr::F32x4reciprocalApproximation:
       case Expr::F32x4reciprocalSqrtApproximation:
       case Expr::B8x16not:
       case Expr::B16x8not:
       case Expr::B32x4not:
-      case Expr::GrowMemory:
         return ExprKind::Unary;
       case Expr::I32Add:
       case Expr::I32Sub:
       case Expr::I32Mul:
       case Expr::I32DivS:
       case Expr::I32DivU:
       case Expr::I32RemS:
       case Expr::I32RemU:
@@ -486,13 +485,15 @@ wasm::Classify(Expr expr)
       case Expr::F32x4equal:
       case Expr::F32x4notEqual:
       case Expr::F32x4greaterThan:
       case Expr::F32x4greaterThanOrEqual:
       case Expr::F32x4lessThan:
       case Expr::F32x4lessThanOrEqual:
         return ExprKind::SimdComparison;
       case Expr::CurrentMemory:
-        return ExprKind::Nullary;
+        return ExprKind::CurrentMemory;
+      case Expr::GrowMemory:
+        return ExprKind::GrowMemory;
     }
     MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unimplemented opcode");
 }
 #endif
--- a/js/src/asmjs/WasmBinaryIterator.h
+++ b/js/src/asmjs/WasmBinaryIterator.h
@@ -63,16 +63,18 @@ enum class ExprKind {
     Nullary,
     Unary,
     Binary,
     Comparison,
     Conversion,
     Load,
     Store,
     TeeStore,
+    CurrentMemory,
+    GrowMemory,
     Select,
     GetLocal,
     SetLocal,
     TeeLocal,
     GetGlobal,
     SetGlobal,
     TeeGlobal,
     Call,
@@ -556,17 +558,18 @@ class MOZ_STACK_CLASS ExprIter : private
     MOZ_MUST_USE bool readComparison(ValType operandType, Value* lhs, Value* rhs);
     MOZ_MUST_USE bool readLoad(ValType resultType, uint32_t byteSize,
                                LinearMemoryAddress<Value>* addr);
     MOZ_MUST_USE bool readStore(ValType resultType, uint32_t byteSize,
                                 LinearMemoryAddress<Value>* addr, Value* value);
     MOZ_MUST_USE bool readTeeStore(ValType resultType, uint32_t byteSize,
                                    LinearMemoryAddress<Value>* addr, Value* value);
     MOZ_MUST_USE bool readNop();
-    MOZ_MUST_USE bool readNullary(ValType retType);
+    MOZ_MUST_USE bool readCurrentMemory();
+    MOZ_MUST_USE bool readGrowMemory(Value* input);
     MOZ_MUST_USE bool readSelect(ValType* type,
                                  Value* trueValue, Value* falseValue, Value* condition);
     MOZ_MUST_USE bool readGetLocal(const ValTypeVector& locals, uint32_t* id);
     MOZ_MUST_USE bool readSetLocal(const ValTypeVector& locals, uint32_t* id, Value* value);
     MOZ_MUST_USE bool readTeeLocal(const ValTypeVector& locals, uint32_t* id, Value* value);
     MOZ_MUST_USE bool readGetGlobal(const GlobalDescVector& globals, uint32_t* id);
     MOZ_MUST_USE bool readSetGlobal(const GlobalDescVector& globals, uint32_t* id, Value* value);
     MOZ_MUST_USE bool readTeeGlobal(const GlobalDescVector& globals, uint32_t* id, Value* value);
@@ -1343,23 +1346,51 @@ ExprIter<Policy>::readNop()
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::Nop);
 
     return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readNullary(ValType retType)
+ExprIter<Policy>::readCurrentMemory()
 {
-    MOZ_ASSERT(Classify(expr_) == ExprKind::Nullary);
+    MOZ_ASSERT(Classify(expr_) == ExprKind::CurrentMemory);
+
+    uint32_t flags;
+    if (!readVarU32(&flags))
+        return false;
+
+    if (Validate && flags != uint32_t(MemoryTableFlags::Default))
+        return fail("unexpected flags");
+
+    if (!push(ValType::I32))
+        return false;
+
+    return true;
+}
 
-    if (!push(retType))
+template <typename Policy>
+inline bool
+ExprIter<Policy>::readGrowMemory(Value* input)
+{
+    MOZ_ASSERT(Classify(expr_) == ExprKind::GrowMemory);
+
+    uint32_t flags;
+    if (!readVarU32(&flags))
         return false;
 
+    if (Validate && flags != uint32_t(MemoryTableFlags::Default))
+        return fail("unexpected flags");
+
+    if (!popWithType(ValType::I32, input))
+        return false;
+
+    infalliblePush(ValType::I32);
+
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readSelect(ValType* type, Value* trueValue, Value* falseValue, Value* condition)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::Select);
@@ -1720,16 +1751,23 @@ template <typename Policy>
 inline bool
 ExprIter<Policy>::readCallIndirect(uint32_t* sigIndex, Value* callee)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::CallIndirect);
 
     if (!readVarU32(sigIndex))
         return fail("unable to read call_indirect signature index");
 
+    uint32_t flags;
+    if (!readVarU32(&flags))
+        return false;
+
+    if (Validate && flags != uint32_t(MemoryTableFlags::Default))
+        return fail("unexpected flags");
+
     if (reachable_) {
         if (!popWithType(ValType::I32, callee))
             return false;
     }
 
     return true;
 }
 
--- a/js/src/asmjs/WasmBinaryToAST.cpp
+++ b/js/src/asmjs/WasmBinaryToAST.cpp
@@ -668,32 +668,16 @@ AstDecodeUnary(AstDecodeContext& c, ValT
 
     if (!c.push(AstDecodeStackItem(unary)))
         return false;
 
     return true;
 }
 
 static bool
-AstDecodeNullary(AstDecodeContext& c, ValType type, Expr expr)
-{
-    if (!c.iter().readNullary(type))
-        return false;
-
-    AstNullaryOperator* nullary = new(c.lifo) AstNullaryOperator(expr);
-    if (!nullary)
-        return false;
-
-    if (!c.push(AstDecodeStackItem(nullary)))
-        return false;
-
-    return true;
-}
-
-static bool
 AstDecodeBinary(AstDecodeContext& c, ValType type, Expr expr)
 {
     if (!c.iter().readBinary(type, nullptr, nullptr))
         return false;
 
     AstDecodeStackItem rhs = c.popCopy();
     AstDecodeStackItem lhs = c.popCopy();
 
@@ -811,16 +795,50 @@ AstDecodeStore(AstDecodeContext& c, ValT
 
     if (!c.push(AstDecodeStackItem(wrapped)))
         return false;
 
     return true;
 }
 
 static bool
+AstDecodeCurrentMemory(AstDecodeContext& c)
+{
+    if (!c.iter().readCurrentMemory())
+        return false;
+
+    AstCurrentMemory* gm = new(c.lifo) AstCurrentMemory();
+    if (!gm)
+        return false;
+
+    if (!c.push(AstDecodeStackItem(gm)))
+        return false;
+
+    return true;
+}
+
+static bool
+AstDecodeGrowMemory(AstDecodeContext& c)
+{
+    if (!c.iter().readGrowMemory(nullptr))
+        return false;
+
+    AstDecodeStackItem op = c.popCopy();
+
+    AstGrowMemory* gm = new(c.lifo) AstGrowMemory(op.expr);
+    if (!gm)
+        return false;
+
+    if (!c.push(AstDecodeStackItem(gm)))
+        return false;
+
+    return true;
+}
+
+static bool
 AstDecodeBranch(AstDecodeContext& c, Expr expr)
 {
     MOZ_ASSERT(expr == Expr::Br || expr == Expr::BrIf);
 
     uint32_t depth;
     ExprType type;
     AstDecodeStackItem value;
     AstDecodeStackItem cond;
@@ -1083,17 +1101,16 @@ AstDecodeExpr(AstDecodeContext& c)
         break;
       case Expr::End:
         if (!AstDecodeEnd(c))
             return false;
         break;
       case Expr::I32Clz:
       case Expr::I32Ctz:
       case Expr::I32Popcnt:
-      case Expr::GrowMemory:
         if (!AstDecodeUnary(c, ValType::I32, expr))
             return false;
         break;
       case Expr::I64Clz:
       case Expr::I64Ctz:
       case Expr::I64Popcnt:
         if (!AstDecodeUnary(c, ValType::I64, expr))
             return false;
@@ -1356,16 +1373,24 @@ AstDecodeExpr(AstDecodeContext& c)
       case Expr::F32Store:
         if (!AstDecodeStore(c, ValType::F32, 4, expr))
             return false;
         break;
       case Expr::F64Store:
         if (!AstDecodeStore(c, ValType::F64, 8, expr))
             return false;
         break;
+      case Expr::CurrentMemory:
+        if (!AstDecodeCurrentMemory(c))
+            return false;
+        break;
+      case Expr::GrowMemory:
+        if (!AstDecodeGrowMemory(c))
+            return false;
+        break;
       case Expr::SetGlobal:
         if (!AstDecodeSetGlobal(c))
             return false;
         break;
       case Expr::GetGlobal:
         if (!AstDecodeGetGlobal(c))
             return false;
         break;
@@ -1377,20 +1402,16 @@ AstDecodeExpr(AstDecodeContext& c)
       case Expr::BrTable:
         if (!AstDecodeBrTable(c))
             return false;
         break;
       case Expr::Return:
         if (!AstDecodeReturn(c))
             return false;
         break;
-      case Expr::CurrentMemory:
-        if (!AstDecodeNullary(c, ValType::I32, expr))
-            return false;
-        break;
       case Expr::Unreachable:
         if (!c.iter().readUnreachable())
             return false;
         tmp = new(c.lifo) AstUnreachable();
         if (!tmp)
             return false;
         if (!c.push(AstDecodeStackItem(tmp)))
             return false;
--- a/js/src/asmjs/WasmBinaryToExperimentalText.cpp
+++ b/js/src/asmjs/WasmBinaryToExperimentalText.cpp
@@ -312,29 +312,16 @@ PrintBlockLevelExpr(WasmPrintContext& c,
     }
     return c.buffer.append('\n');
 }
 
 /*****************************************************************************/
 // binary format parsing and rendering
 
 static bool
-PrintNullaryOperator(WasmPrintContext& c, AstNullaryOperator& op)
-{
-    const char* opStr;
-
-    switch (op.expr()) {
-        case Expr::CurrentMemory:   opStr = "curent_memory"; break;
-        default:  return false;
-    }
-
-    return c.buffer.append(opStr, strlen(opStr));
-}
-
-static bool
 PrintNop(WasmPrintContext& c)
 {
     return c.buffer.append("nop");
 }
 
 static bool
 PrintDrop(WasmPrintContext& c, AstDrop& drop)
 {
@@ -649,17 +636,16 @@ PrintUnaryOperator(WasmPrintContext& c, 
       case Expr::F32Sqrt:    opStr = "f32.sqrt"; break;
       case Expr::F32Trunc:   opStr = "f32.trunc"; break;
       case Expr::F32Nearest: opStr = "f32.nearest"; break;
       case Expr::F64Abs:     opStr = "f64.abs"; break;
       case Expr::F64Neg:     opStr = "f64.neg"; prefixStr = "-"; precedence = NegatePrecedence; break;
       case Expr::F64Ceil:    opStr = "f64.ceil"; break;
       case Expr::F64Floor:   opStr = "f64.floor"; break;
       case Expr::F64Sqrt:    opStr = "f64.sqrt"; break;
-      case Expr::GrowMemory: opStr = "grow_memory"; break;
       default: return false;
     }
 
     if (c.f.allowAsciiOperators && prefixStr) {
         if (!c.f.reduceParens || lastPrecedence > precedence) {
             if (!c.buffer.append("("))
                 return false;
         }
@@ -1357,32 +1343,55 @@ PrintFirst(WasmPrintContext& c, AstFirst
 
     if (!c.buffer.append(")"))
         return false;
 
     return true;
 }
 
 static bool
+PrintCurrentMemory(WasmPrintContext& c, AstCurrentMemory& cm)
+{
+    return c.buffer.append("current_memory");
+}
+
+static bool
+PrintGrowMemory(WasmPrintContext& c, AstGrowMemory& gm)
+{
+    if (!c.buffer.append("grow_memory("))
+        return false;
+
+    PrintOperatorPrecedence lastPrecedence = c.currentPrecedence;
+    c.currentPrecedence = ExpressionPrecedence;
+
+    if (!PrintExpr(c, *gm.op()))
+        return false;
+
+    if (!c.buffer.append(")"))
+        return false;
+
+    c.currentPrecedence = lastPrecedence;
+    return true;
+}
+
+static bool
 PrintExpr(WasmPrintContext& c, AstExpr& expr)
 {
     if (c.maybeSourceMap) {
         uint32_t lineno = c.buffer.lineno();
         uint32_t column = c.buffer.column();
         if (!c.maybeSourceMap->exprlocs().emplaceBack(lineno, column, expr.offset()))
             return false;
     }
 
     switch (expr.kind()) {
       case AstExprKind::Nop:
         return PrintNop(c);
       case AstExprKind::Drop:
         return PrintDrop(c, expr.as<AstDrop>());
-      case AstExprKind::NullaryOperator:
-        return PrintNullaryOperator(c, expr.as<AstNullaryOperator>());
       case AstExprKind::Unreachable:
         return PrintUnreachable(c, expr.as<AstUnreachable>());
       case AstExprKind::Call:
         return PrintCall(c, expr.as<AstCall>());
       case AstExprKind::CallIndirect:
         return PrintCallIndirect(c, expr.as<AstCallIndirect>());
       case AstExprKind::Const:
         return PrintConst(c, expr.as<AstConst>());
@@ -1413,16 +1422,20 @@ PrintExpr(WasmPrintContext& c, AstExpr& 
       case AstExprKind::Branch:
         return PrintBranch(c, expr.as<AstBranch>());
       case AstExprKind::BranchTable:
         return PrintBrTable(c, expr.as<AstBranchTable>());
       case AstExprKind::Return:
         return PrintReturn(c, expr.as<AstReturn>());
       case AstExprKind::First:
         return PrintFirst(c, expr.as<AstFirst>());
+      case AstExprKind::CurrentMemory:
+        return PrintCurrentMemory(c, expr.as<AstCurrentMemory>());
+      case AstExprKind::GrowMemory:
+        return PrintGrowMemory(c, expr.as<AstGrowMemory>());
       default:
         // Note: it's important not to remove this default since readExpr()
         // can return Expr values for which there is no enumerator.
         break;
     }
 
     return false;
 }
--- a/js/src/asmjs/WasmBinaryToText.cpp
+++ b/js/src/asmjs/WasmBinaryToText.cpp
@@ -459,28 +459,35 @@ RenderBlock(WasmRenderContext& c, AstBlo
 
 static bool
 RenderFirst(WasmRenderContext& c, AstFirst& first)
 {
     return RenderExprList(c, first.exprs());
 }
 
 static bool
-RenderNullaryOperator(WasmRenderContext& c, AstNullaryOperator& op)
+RenderCurrentMemory(WasmRenderContext& c, AstCurrentMemory& cm)
 {
     if (!RenderIndent(c))
         return false;
 
-    const char* opStr;
-    switch (op.expr()) {
-      case Expr::CurrentMemory: opStr = "current_memory"; break;
-      default: return false;
-    }
+    return c.buffer.append("current_memory\n");
+}
 
-    return c.buffer.append(opStr, strlen(opStr));
+static bool
+RenderGrowMemory(WasmRenderContext& c, AstGrowMemory& gm)
+{
+    if (!RenderExpr(c, *gm.op()))
+        return false;
+
+    if (!RenderIndent(c))
+        return false;
+
+    MAP_AST_EXPR(c, gm);
+    return c.buffer.append("grow_memory\n");
 }
 
 static bool
 RenderUnaryOperator(WasmRenderContext& c, AstUnaryOperator& op)
 {
     if (!RenderExpr(c, *op.op()))
         return false;
 
@@ -506,17 +513,16 @@ RenderUnaryOperator(WasmRenderContext& c
       case Expr::F32Nearest: opStr = "f32.nearest"; break;
       case Expr::F64Abs:     opStr = "f64.abs"; break;
       case Expr::F64Neg:     opStr = "f64.neg"; break;
       case Expr::F64Ceil:    opStr = "f64.ceil"; break;
       case Expr::F64Floor:   opStr = "f64.floor"; break;
       case Expr::F64Nearest: opStr = "f64.nearest"; break;
       case Expr::F64Sqrt:    opStr = "f64.sqrt"; break;
       case Expr::F64Trunc:   opStr = "f64.trunc"; break;
-      case Expr::GrowMemory: opStr = "grow_memory"; break;
       default: return false;
     }
 
     return c.buffer.append(opStr, strlen(opStr));
 }
 
 static bool
 RenderBinaryOperator(WasmRenderContext& c, AstBinaryOperator& op)
@@ -1044,20 +1050,16 @@ RenderExpr(WasmRenderContext& c, AstExpr
       case AstExprKind::Block:
         if (!RenderBlock(c, expr.as<AstBlock>()))
             return false;
         break;
       case AstExprKind::If:
         if (!RenderIf(c, expr.as<AstIf>()))
             return false;
         break;
-      case AstExprKind::NullaryOperator:
-        if (!RenderNullaryOperator(c, expr.as<AstNullaryOperator>()))
-            return false;
-        break;
       case AstExprKind::UnaryOperator:
         if (!RenderUnaryOperator(c, expr.as<AstUnaryOperator>()))
             return false;
         break;
       case AstExprKind::BinaryOperator:
         if (!RenderBinaryOperator(c, expr.as<AstBinaryOperator>()))
             return false;
         break;
@@ -1093,16 +1095,24 @@ RenderExpr(WasmRenderContext& c, AstExpr
         if (!RenderReturn(c, expr.as<AstReturn>()))
             return false;
         break;
       case AstExprKind::First:
         newLine = false;
         if (!RenderFirst(c, expr.as<AstFirst>()))
             return false;
         break;
+      case AstExprKind::CurrentMemory:
+        if (!RenderCurrentMemory(c, expr.as<AstCurrentMemory>()))
+            return false;
+        break;
+      case AstExprKind::GrowMemory:
+        if (!RenderGrowMemory(c, expr.as<AstGrowMemory>()))
+            return false;
+        break;
       default:
         // Note: it's important not to remove this default since readExpr()
         // can return Expr values for which there is no enumerator.
         return false;
     }
 
     return !newLine || c.buffer.append("\n");
 }
--- a/js/src/asmjs/WasmCompile.cpp
+++ b/js/src/asmjs/WasmCompile.cpp
@@ -403,19 +403,19 @@ DecodeFunctionBodyExprs(FunctionDecoder&
             CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I64, 4, nullptr, nullptr));
           case Expr::I64Store:
             CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I64, 8, nullptr, nullptr));
           case Expr::F32Store:
             CHECK(f.checkHasMemory() && f.iter().readStore(ValType::F32, 4, nullptr, nullptr));
           case Expr::F64Store:
             CHECK(f.checkHasMemory() && f.iter().readStore(ValType::F64, 8, nullptr, nullptr));
           case Expr::GrowMemory:
-            CHECK(f.checkHasMemory() && f.iter().readUnary(ValType::I32, nullptr));
+            CHECK(f.checkHasMemory() && f.iter().readGrowMemory(nullptr));
           case Expr::CurrentMemory:
-            CHECK(f.checkHasMemory() && f.iter().readNullary(ValType::I32));
+            CHECK(f.checkHasMemory() && f.iter().readCurrentMemory());
           case Expr::Br:
             CHECK(f.iter().readBr(nullptr, nullptr, nullptr));
           case Expr::BrIf:
             CHECK(f.iter().readBrIf(nullptr, nullptr, nullptr, nullptr));
           case Expr::BrTable:
             CHECK(DecodeBrTable(f));
           case Expr::Return:
             CHECK(f.iter().readReturn(nullptr));
--- a/js/src/asmjs/WasmGeneratedSourceMap.h
+++ b/js/src/asmjs/WasmGeneratedSourceMap.h
@@ -120,16 +120,18 @@ class WasmPrintBuffer
             processChar(*p);
         return stringBuffer_.append(begin, end);
     }
     bool append(const char16_t* str, size_t length) {
         return append(str, str + length);
     }
     template <size_t ArrayLength>
     bool append(const char (&array)[ArrayLength]) {
+        static_assert(ArrayLength > 0, "null-terminated");
+        MOZ_ASSERT(array[ArrayLength - 1] == '\0');
         return append(array, ArrayLength - 1);
     }
     char16_t getChar(size_t index) {
         return stringBuffer_.getChar(index);
     }
     size_t length() {
         return stringBuffer_.length();
     }
--- a/js/src/asmjs/WasmIonCompile.cpp
+++ b/js/src/asmjs/WasmIonCompile.cpp
@@ -3075,17 +3075,17 @@ EmitGrowMemory(FunctionCompiler& f)
     CallCompileState args(f, lineOrBytecode);
     if (!f.startCall(&args))
         return false;
 
     if (!f.passInstance(&args))
         return false;
 
     MDefinition* delta;
-    if (!f.iter().readUnary(ValType::I32, &delta))
+    if (!f.iter().readGrowMemory(&delta))
         return false;
 
     if (!f.passArg(delta, ValType::I32, &args))
         return false;
 
     // As a short-cut, pretend this is an inter-module call so that any pinned
     // heap pointer will be reloaded after the call. This hack will go away once
     // we can stop pinning registers.
@@ -3101,17 +3101,17 @@ EmitGrowMemory(FunctionCompiler& f)
 
 static bool
 EmitCurrentMemory(FunctionCompiler& f)
 {
     uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
 
     CallCompileState args(f, lineOrBytecode);
 
-    if (!f.iter().readNullary(ValType::I32))
+    if (!f.iter().readCurrentMemory())
         return false;
 
     if (!f.startCall(&args))
         return false;
 
     if (!f.passInstance(&args))
         return false;
 
--- a/js/src/asmjs/WasmTextToBinary.cpp
+++ b/js/src/asmjs/WasmTextToBinary.cpp
@@ -73,30 +73,32 @@ class WasmToken
         BrIf,
         BrTable,
         Call,
         CallIndirect,
         CloseParen,
         ComparisonOpcode,
         Const,
         ConversionOpcode,
+        CurrentMemory,
         Data,
         Drop,
         Elem,
         Else,
         End,
         EndOfFile,
         Equal,
         Error,
         Export,
         Float,
         Func,
         GetGlobal,
         GetLocal,
         Global,
+        GrowMemory,
         If,
         Import,
         Index,
         Memory,
         NegativeZero,
         Load,
         Local,
         Loop,
@@ -115,17 +117,16 @@ class WasmToken
         Start,
         Store,
         Table,
         TeeLocal,
         TernaryOpcode,
         Text,
         Then,
         Type,
-        NullaryOpcode,
         UnaryOpcode,
         Unreachable,
         UnsignedInteger,
         ValueType
     };
   private:
     Kind kind_;
     const char16_t* begin_;
@@ -198,17 +199,17 @@ class WasmToken
     explicit WasmToken(Kind kind, Expr expr, const char16_t* begin, const char16_t* end)
       : kind_(kind),
         begin_(begin),
         end_(end)
     {
         MOZ_ASSERT(begin != end);
         MOZ_ASSERT(kind_ == UnaryOpcode || kind_ == BinaryOpcode || kind_ == TernaryOpcode ||
                    kind_ == ComparisonOpcode || kind_ == ConversionOpcode ||
-                   kind_ == Load || kind_ == Store || kind_ == NullaryOpcode);
+                   kind_ == Load || kind_ == Store);
         u.expr_ = expr;
     }
     explicit WasmToken(const char16_t* begin)
       : kind_(Error),
         begin_(begin),
         end_(begin)
     {}
     Kind kind() const {
@@ -249,45 +250,46 @@ class WasmToken
     }
     ValType valueType() const {
         MOZ_ASSERT(kind_ == ValueType || kind_ == Const);
         return u.valueType_;
     }
     Expr expr() const {
         MOZ_ASSERT(kind_ == UnaryOpcode || kind_ == BinaryOpcode || kind_ == TernaryOpcode ||
                    kind_ == ComparisonOpcode || kind_ == ConversionOpcode ||
-                   kind_ == Load || kind_ == Store || kind_ == NullaryOpcode);
+                   kind_ == Load || kind_ == Store);
         return u.expr_;
     }
     bool isOpcode() const {
         switch (kind_) {
           case BinaryOpcode:
           case Block:
           case Br:
           case BrIf:
           case BrTable:
           case Call:
           case CallIndirect:
           case ComparisonOpcode:
           case Const:
           case ConversionOpcode:
+          case CurrentMemory:
           case Drop:
           case GetGlobal:
           case GetLocal:
+          case GrowMemory:
           case If:
           case Load:
           case Loop:
           case Nop:
           case Return:
           case SetGlobal:
           case SetLocal:
           case Store:
           case TeeLocal:
           case TernaryOpcode:
-          case NullaryOpcode:
           case UnaryOpcode:
           case Unreachable:
             return true;
           case Align:
           case AnyFunc:
           case CloseParen:
           case Data:
           case Elem:
@@ -849,17 +851,17 @@ WasmTokenStream::next()
 
       case 'c':
         if (consume(u"call")) {
             if (consume(u"_indirect"))
                 return WasmToken(WasmToken::CallIndirect, begin, cur_);
             return WasmToken(WasmToken::Call, begin, cur_);
         }
         if (consume(u"current_memory"))
-            return WasmToken(WasmToken::NullaryOpcode, Expr::CurrentMemory, begin, cur_);
+            return WasmToken(WasmToken::CurrentMemory, begin, cur_);
         break;
 
       case 'd':
         if (consume(u"data"))
             return WasmToken(WasmToken::Data, begin, cur_);
         if (consume(u"drop"))
             return WasmToken(WasmToken::Drop, begin, cur_);
         break;
@@ -1086,17 +1088,17 @@ WasmTokenStream::next()
       case 'g':
         if (consume(u"get_global"))
             return WasmToken(WasmToken::GetGlobal, begin, cur_);
         if (consume(u"get_local"))
             return WasmToken(WasmToken::GetLocal, begin, cur_);
         if (consume(u"global"))
             return WasmToken(WasmToken::Global, begin, cur_);
         if (consume(u"grow_memory"))
-            return WasmToken(WasmToken::UnaryOpcode, Expr::GrowMemory, begin, cur_);
+            return WasmToken(WasmToken::GrowMemory, begin, cur_);
         break;
 
       case 'i':
         if (consume(u"i32")) {
             if (!consume(u"."))
                 return WasmToken(WasmToken::ValueType, ValType::I32, begin, cur_);
 
             switch (*cur_) {
@@ -1401,17 +1403,17 @@ WasmTokenStream::next()
         if (consume(u"mut"))
             return WasmToken(WasmToken::Mutable, begin, cur_);
         break;
 
       case 'n':
         if (consume(u"nan"))
             return nan(begin);
         if (consume(u"nop"))
-            return WasmToken(WasmToken::NullaryOpcode, Expr::Nop, begin, cur_);
+            return WasmToken(WasmToken::Nop, begin, cur_);
         break;
 
       case 'o':
         if (consume(u"offset"))
             return WasmToken(WasmToken::Offset, begin, cur_);
         break;
 
       case 'p':
@@ -2083,22 +2085,16 @@ ParseUnaryOperator(WasmParseContext& c, 
 {
     AstExpr* op = ParseExpr(c, inParens);
     if (!op)
         return nullptr;
 
     return new(c.lifo) AstUnaryOperator(expr, op);
 }
 
-static AstNullaryOperator*
-ParseNullaryOperator(WasmParseContext& c, Expr expr)
-{
-    return new(c.lifo) AstNullaryOperator(expr);
-}
-
 static AstBinaryOperator*
 ParseBinaryOperator(WasmParseContext& c, Expr expr, bool inParens)
 {
     AstExpr* lhs = ParseExpr(c, inParens);
     if (!lhs)
         return nullptr;
 
     AstExpr* rhs = ParseExpr(c, inParens);
@@ -2376,16 +2372,26 @@ ParseBranchTable(WasmParseContext& c, Wa
             if (!c.ts.match(WasmToken::CloseParen, c.error))
                 return nullptr;
         }
     }
 
     return new(c.lifo) AstBranchTable(*index, def, Move(table), value);
 }
 
+static AstGrowMemory*
+ParseGrowMemory(WasmParseContext& c, bool inParens)
+{
+    AstExpr* op = ParseExpr(c, inParens);
+    if (!op)
+        return nullptr;
+
+    return new(c.lifo) AstGrowMemory(op);
+}
+
 static AstExpr*
 ParseExprBody(WasmParseContext& c, WasmToken token, bool inParens)
 {
     switch (token.kind()) {
       case WasmToken::Unreachable:
         return new(c.lifo) AstUnreachable;
       case WasmToken::BinaryOpcode:
         return ParseBinaryOperator(c, token.expr(), inParens);
@@ -2428,18 +2434,22 @@ ParseExprBody(WasmParseContext& c, WasmT
       case WasmToken::Store:
         return ParseStore(c, token.expr(), inParens);
       case WasmToken::TeeLocal:
         return ParseTeeLocal(c, inParens);
       case WasmToken::TernaryOpcode:
         return ParseTernaryOperator(c, token.expr(), inParens);
       case WasmToken::UnaryOpcode:
         return ParseUnaryOperator(c, token.expr(), inParens);
-      case WasmToken::NullaryOpcode:
-        return ParseNullaryOperator(c, token.expr());
+      case WasmToken::Nop:
+        return new(c.lifo) AstNop();
+      case WasmToken::CurrentMemory:
+        return new(c.lifo) AstCurrentMemory();
+      case WasmToken::GrowMemory:
+        return ParseGrowMemory(c, inParens);
       default:
         c.ts.generateError(token, c.error);
         return nullptr;
     }
 }
 
 static AstExpr*
 ParseExprInsideParens(WasmParseContext& c)
@@ -3567,16 +3577,22 @@ ResolveTeeLocal(Resolver& r, AstTeeLocal
 
 static bool
 ResolveUnaryOperator(Resolver& r, AstUnaryOperator& b)
 {
     return ResolveExpr(r, *b.op());
 }
 
 static bool
+ResolveGrowMemory(Resolver& r, AstGrowMemory& gm)
+{
+    return ResolveExpr(r, *gm.op());
+}
+
+static bool
 ResolveBinaryOperator(Resolver& r, AstBinaryOperator& b)
 {
     return ResolveExpr(r, *b.lhs()) &&
            ResolveExpr(r, *b.rhs());
 }
 
 static bool
 ResolveTernaryOperator(Resolver& r, AstTernaryOperator& b)
@@ -3659,18 +3675,18 @@ ResolveBranchTable(Resolver& r, AstBranc
 }
 
 static bool
 ResolveExpr(Resolver& r, AstExpr& expr)
 {
     switch (expr.kind()) {
       case AstExprKind::Nop:
       case AstExprKind::Pop:
-      case AstExprKind::NullaryOperator:
       case AstExprKind::Unreachable:
+      case AstExprKind::CurrentMemory:
         return true;
       case AstExprKind::Drop:
         return ResolveDropOperator(r, expr.as<AstDrop>());
       case AstExprKind::BinaryOperator:
         return ResolveBinaryOperator(r, expr.as<AstBinaryOperator>());
       case AstExprKind::Block:
         return ResolveBlock(r, expr.as<AstBlock>());
       case AstExprKind::Branch:
@@ -3706,16 +3722,18 @@ ResolveExpr(Resolver& r, AstExpr& expr)
       case AstExprKind::BranchTable:
         return ResolveBranchTable(r, expr.as<AstBranchTable>());
       case AstExprKind::TeeLocal:
         return ResolveTeeLocal(r, expr.as<AstTeeLocal>());
       case AstExprKind::TernaryOperator:
         return ResolveTernaryOperator(r, expr.as<AstTernaryOperator>());
       case AstExprKind::UnaryOperator:
         return ResolveUnaryOperator(r, expr.as<AstUnaryOperator>());
+      case AstExprKind::GrowMemory:
+        return ResolveGrowMemory(r, expr.as<AstGrowMemory>());
     }
     MOZ_CRASH("Bad expr kind");
 }
 
 static bool
 ResolveFunc(Resolver& r, AstFunc& func)
 {
     r.beginFunc();
@@ -3925,16 +3943,19 @@ EncodeCallIndirect(Encoder& e, AstCallIn
         return false;
 
     if (!e.writeExpr(Expr::CallIndirect))
         return false;
 
     if (!e.writeVarU32(c.sig().index()))
         return false;
 
+    if (!e.writeVarU32(uint32_t(MemoryTableFlags::Default)))
+        return false;
+
     return true;
 }
 
 static bool
 EncodeConst(Encoder& e, AstConst& c)
 {
     switch (c.val().type()) {
       case ValType::I32:
@@ -4003,22 +4024,16 @@ EncodeSetGlobal(Encoder& e, AstSetGlobal
 static bool
 EncodeUnaryOperator(Encoder& e, AstUnaryOperator& b)
 {
     return EncodeExpr(e, *b.op()) &&
            e.writeExpr(b.expr());
 }
 
 static bool
-EncodeNullaryOperator(Encoder& e, AstNullaryOperator& b)
-{
-    return e.writeExpr(b.expr());
-}
-
-static bool
 EncodeBinaryOperator(Encoder& e, AstBinaryOperator& b)
 {
     return EncodeExpr(e, *b.lhs()) &&
            EncodeExpr(e, *b.rhs()) &&
            e.writeExpr(b.expr());
 }
 
 static bool
@@ -4135,16 +4150,43 @@ EncodeBranchTable(Encoder& e, AstBranchT
 
     if (!e.writeVarU32(bt.def().index()))
         return false;
 
     return true;
 }
 
 static bool
+EncodeCurrentMemory(Encoder& e, AstCurrentMemory& cm)
+{
+    if (!e.writeExpr(Expr::CurrentMemory))
+        return false;
+
+    if (!e.writeVarU32(uint32_t(MemoryTableFlags::Default)))
+        return false;
+
+    return true;
+}
+
+static bool
+EncodeGrowMemory(Encoder& e, AstGrowMemory& gm)
+{
+    if (!EncodeExpr(e, *gm.op()))
+        return false;
+
+    if (!e.writeExpr(Expr::GrowMemory))
+        return false;
+
+    if (!e.writeVarU32(uint32_t(MemoryTableFlags::Default)))
+        return false;
+
+    return true;
+}
+
+static bool
 EncodeExpr(Encoder& e, AstExpr& expr)
 {
     switch (expr.kind()) {
       case AstExprKind::Pop:
         return true;
       case AstExprKind::Nop:
         return e.writeExpr(Expr::Nop);
       case AstExprKind::Unreachable:
@@ -4188,18 +4230,20 @@ EncodeExpr(Encoder& e, AstExpr& expr)
       case AstExprKind::Store:
         return EncodeStore(e, expr.as<AstStore>());
       case AstExprKind::BranchTable:
         return EncodeBranchTable(e, expr.as<AstBranchTable>());
       case AstExprKind::TernaryOperator:
         return EncodeTernaryOperator(e, expr.as<AstTernaryOperator>());
       case AstExprKind::UnaryOperator:
         return EncodeUnaryOperator(e, expr.as<AstUnaryOperator>());
-      case AstExprKind::NullaryOperator:
-        return EncodeNullaryOperator(e, expr.as<AstNullaryOperator>());
+      case AstExprKind::CurrentMemory:
+        return EncodeCurrentMemory(e, expr.as<AstCurrentMemory>());
+      case AstExprKind::GrowMemory:
+        return EncodeGrowMemory(e, expr.as<AstGrowMemory>());
     }
     MOZ_CRASH("Bad expr kind");
 }
 
 /*****************************************************************************/
 // wasm AST binary serialization
 
 static bool