Bug 1274618 - Experimental WASM text format. r?sunfish draft
authorYury Delendik <ydelendik@mozilla.com>
Mon, 23 May 2016 17:03:24 -0500
changeset 369848 3ab46f9dcce6671fe69f2b0bc7ace6f4b359895e
parent 369821 ff49731c7d5e4918fd66bf54658c6addea02b5c1
child 521636 54173ccbe8c2a18fb894322b79b283c0fc338bcd
push id18934
push userydelendik@mozilla.com
push dateMon, 23 May 2016 22:04:31 +0000
reviewerssunfish
bugs1274618
milestone49.0a1
Bug 1274618 - Experimental WASM text format. r?sunfish MozReview-Commit-ID: 1Bi5x74h4CP
js/src/asmjs/WasmBinaryToExperimentalText.cpp
js/src/asmjs/WasmBinaryToExperimentalText.h
js/src/asmjs/WasmModule.cpp
js/src/builtin/TestingFunctions.cpp
js/src/jit-test/tests/wasm/totext2.js
js/src/moz.build
new file mode 100644
--- /dev/null
+++ b/js/src/asmjs/WasmBinaryToExperimentalText.cpp
@@ -0,0 +1,1647 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ *
+ * Copyright 2015 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "asmjs/WasmBinaryToExperimentalText.h"
+
+#include "mozilla/CheckedInt.h"
+
+#include "jsnum.h"
+#include "jsprf.h"
+
+#include "asmjs/Wasm.h"
+#include "asmjs/WasmAST.h"
+#include "asmjs/WasmBinaryToAST.h"
+#include "asmjs/WasmTypes.h"
+#include "vm/ArrayBufferObject.h"
+#include "vm/StringBuffer.h"
+
+using namespace js;
+using namespace js::wasm;
+
+using mozilla::CheckedInt;
+using mozilla::IsInfinite;
+using mozilla::IsNaN;
+using mozilla::IsNegativeZero;
+
+enum PrintOperatorPrecedence
+{
+    ExpressionPrecedence = 0,
+    AssignmentPrecedence = 1,
+    StoreOperatorPrecedence = 1,
+    SelectPrecedence = 2,
+    BitwiseOrPrecedence = 3,
+    BitwiseXorPrecedence = 4,
+    BitwiseAndPrecedence = 5,
+    EqualityPrecedence = 6,
+    ComparisonPrecedence = 7,
+    BitwiseShiftPrecedence = 8,
+    MinMaxPrecedence = 9,
+    AdditionPrecedence = 10,
+    MultiplicationPrecedence = 11,
+    CopySignPrecedence = 12,
+    ConversionPrecedence = 13,
+    UnaryOperatorPrecedence = 13,
+    LoadOperatorPrecedence = 14,
+    CallPrecedence = 15,
+    GroupPrecedence = 16,
+};
+
+struct WasmPrintContext
+{
+    JSContext* cx;
+    AstModule* module;
+    StringBuffer& buffer;
+    const ExperimentalTextFormatting& f;
+    uint32_t indent;
+
+    uint32_t currentFuncIndex;
+    PrintOperatorPrecedence currentPrecedence;
+
+    WasmPrintContext(JSContext* cx, AstModule* module, StringBuffer& buffer, const ExperimentalTextFormatting& f)
+      : cx(cx),
+        module(module),
+        buffer(buffer),
+        f(f),
+        indent(0),
+        currentFuncIndex(0),
+        currentPrecedence(PrintOperatorPrecedence::ExpressionPrecedence)
+    {}
+};
+
+/*****************************************************************************/
+// utilities
+
+static bool
+PrintIndent(WasmPrintContext& c)
+{
+    for (uint32_t i = 0; i < c.indent; i++) {
+        if (!c.buffer.append("  "))
+            return false;
+    }
+    return true;
+}
+
+static bool
+PrintInt32(WasmPrintContext& c, int32_t num, bool printSign = false)
+{
+    // Negative sign will be printed, printing '+' for non-negative values.
+    if (printSign && num >= 0) {
+        if (!c.buffer.append("+"))
+            return false;
+    }
+    return NumberValueToStringBuffer(c.cx, Int32Value(num), c.buffer);
+}
+
+static bool
+PrintInt64(WasmPrintContext& c, int64_t num)
+{
+    if (num < 0 && !c.buffer.append("-"))
+        return false;
+    if (!num)
+        return c.buffer.append("0");
+
+    uint64_t abs = mozilla::Abs(num);
+    uint64_t n = abs;
+    uint64_t pow = 1;
+    while (n) {
+        pow *= 10;
+        n /= 10;
+    }
+    pow /= 10;
+
+    n = abs;
+    while (pow) {
+        if (!c.buffer.append((char16_t)(MOZ_UTF16('0') + n / pow)))
+            return false;
+        n -= (n / pow) * pow;
+        pow /= 10;
+    }
+
+    return true;
+}
+
+static bool
+PrintDouble(WasmPrintContext& c, double num)
+{
+    if (IsNegativeZero(num))
+        return c.buffer.append("-0.0");
+    if (IsNaN(num))
+        return c.buffer.append("nan");
+    if (IsInfinite(num)) {
+        if (num > 0)
+            return c.buffer.append("infinity");
+        return c.buffer.append("-infinity");
+    }
+
+    uint32_t startLength = c.buffer.length();
+    if (!NumberValueToStringBuffer(c.cx, DoubleValue(num), c.buffer))
+        return false;
+    MOZ_ASSERT(startLength < c.buffer.length());
+
+    // Checking if we need to end number with '.0'.
+    for (uint32_t i = c.buffer.length() - 1; i >= startLength; i--) {
+        char16_t ch = c.buffer.getChar(i);
+        if (ch == '.' || ch == 'e')
+            return true;
+    }
+    return c.buffer.append(".0");
+}
+
+static bool
+PrintEscapedString(WasmPrintContext& c, const AstName& s)
+{
+    size_t length = s.length();
+    const char16_t* p = s.begin();
+    for (size_t i = 0; i < length; i++) {
+        char16_t byte = p[i];
+        switch (byte) {
+          case '\n':
+            if (!c.buffer.append("\\n"))
+                return false;
+            break;
+          case '\r':
+            if (!c.buffer.append("\\0d"))
+                return false;
+            break;
+          case '\t':
+            if (!c.buffer.append("\\t"))
+                return false;
+            break;
+          case '\f':
+            if (!c.buffer.append("\\0c"))
+                return false;
+            break;
+          case '\b':
+            if (!c.buffer.append("\\08"))
+                return false;
+            break;
+          case '\\':
+            if (!c.buffer.append("\\\\"))
+                return false;
+            break;
+          case '"' :
+            if (!c.buffer.append("\\\""))
+                return false;
+            break;
+          case '\'':
+            if (!c.buffer.append("\\'"))
+                return false;
+            break;
+          default:
+            if (byte >= 32 && byte < 127) {
+                if (!c.buffer.append((char)byte))
+                    return false;
+            } else {
+                char digit1 = byte / 16, digit2 = byte % 16;
+                if (!c.buffer.append("\\"))
+                    return false;
+                if (!c.buffer.append((char)(digit1 < 10 ? digit1 + '0' : digit1 - 10 + 'a')))
+                    return false;
+                if (!c.buffer.append((char)(digit2 < 10 ? digit2 + '0' : digit2 - 10 + 'a')))
+                    return false;
+            }
+            break;
+        }
+    }
+    return true;
+}
+
+static bool
+PrintExprType(WasmPrintContext& c, ExprType type)
+{
+    switch (type) {
+      case ExprType::Void: return true; // ignoring void
+      case ExprType::I32: return c.buffer.append("i32");
+      case ExprType::I64: return c.buffer.append("i64");
+      case ExprType::F32: return c.buffer.append("f32");
+      case ExprType::F64: return c.buffer.append("f64");
+      default:;
+    }
+
+    MOZ_CRASH("bad type");
+}
+
+static bool
+PrintValType(WasmPrintContext& c, ValType type)
+{
+    return PrintExprType(c, ToExprType(type));
+}
+
+static bool
+PrintName(WasmPrintContext& c, const AstName& name)
+{
+    return c.buffer.append(name.begin(), name.end());
+}
+
+static bool
+PrintRef(WasmPrintContext& c, const AstRef& ref)
+{
+    if (ref.name().empty())
+        return PrintInt32(c, ref.index());
+
+    return PrintName(c, ref.name());
+}
+
+static bool
+PrintExpr(WasmPrintContext& c, AstExpr& expr);
+
+static bool
+PrintFullLine(WasmPrintContext& c, AstExpr& expr)
+{
+    if (!PrintIndent(c))
+        return false;
+    if (!PrintExpr(c, expr))
+        return false;
+    return c.buffer.append('\n');
+}
+
+/*****************************************************************************/
+// binary format parsing and rendering
+
+static bool
+PrintNop(WasmPrintContext& c, AstNop& nop)
+{
+    return c.buffer.append("nop");
+}
+
+static bool
+PrintUnreachable(WasmPrintContext& c, AstUnreachable& unreachable)
+{
+    return c.buffer.append("unreachable");
+}
+
+static bool
+PrintCallArgs(WasmPrintContext& c, const AstExprVector& args)
+{
+    PrintOperatorPrecedence lastPrecedence = c.currentPrecedence;
+    c.currentPrecedence = ExpressionPrecedence;
+
+    if (!c.buffer.append("("))
+        return false;
+    for (uint32_t i = 0; i < args.length(); i++) {
+        if (!PrintExpr(c, *args[i]))
+            return false;
+        if (i + 1 == args.length())
+            break;
+        if (!c.buffer.append(","))
+            return false;
+    }
+    if (!c.buffer.append(")"))
+        return false;
+
+    c.currentPrecedence = lastPrecedence;
+    return true;
+}
+
+static bool
+PrintCall(WasmPrintContext& c, AstCall& call)
+{
+    if (call.expr() == Expr::Call) {
+        if (!c.buffer.append("call "))
+            return false;
+    } else if (call.expr() == Expr::CallImport) {
+        if (!c.buffer.append("call_import "))
+            return false;
+    } else {
+        return false;
+    }
+
+    if (!PrintRef(c, call.func()))
+        return false;
+
+    if (!c.buffer.append(" "))
+        return false;
+
+    if (!PrintCallArgs(c, call.args()))
+        return false;
+
+    return true;
+}
+
+static bool
+PrintCallIndirect(WasmPrintContext& c, AstCallIndirect& call)
+{
+    if (!c.buffer.append("call_indirect "))
+        return false;
+    if (!PrintRef(c, call.sig()))
+        return false;
+
+    if (!c.buffer.append(" ["))
+        return false;
+
+    if (!PrintExpr(c, *call.index()))
+        return false;
+
+    if (!c.buffer.append("] "))
+        return false;
+    if (!PrintCallArgs(c, call.args()))
+        return false;
+    return true;
+}
+
+static bool
+PrintConst(WasmPrintContext& c, AstConst& cst)
+{
+    switch (ToExprType(cst.val().type())) {
+      case ExprType::I32:
+        if (!PrintInt32(c, (uint32_t)cst.val().i32()))
+            return false;
+        break;
+      case ExprType::I64:
+        if (!PrintInt64(c, (uint32_t)cst.val().i64()))
+            return false;
+        if (!c.buffer.append("i64"))
+            return false;
+        break;
+      case ExprType::F32:
+        if (!PrintDouble(c, (double)cst.val().f32()))
+            return false;
+        if (!c.buffer.append("f"))
+            return false;
+        break;
+      case ExprType::F64:
+        if (!PrintDouble(c, cst.val().f64()))
+            return false;
+        break;
+      default:
+        MOZ_CRASH("bad const type");
+        break;
+    }
+
+    return true;
+}
+
+static bool
+PrintGetLocal(WasmPrintContext& c, AstGetLocal& gl)
+{
+    if (!PrintRef(c, gl.local()))
+        return false;
+    return true;
+}
+
+static bool
+PrintSetLocal(WasmPrintContext& c, AstSetLocal& sl)
+{
+    PrintOperatorPrecedence lastPrecedence = c.currentPrecedence;
+
+    if (c.f.reduceParens ? lastPrecedence > AssignmentPrecedence : lastPrecedence != ExpressionPrecedence) {
+      if (!c.buffer.append("("))
+          return false;
+    }
+
+    if (!PrintRef(c, sl.local()))
+        return false;
+    if (!c.buffer.append(" = "))
+        return false;
+
+    c.currentPrecedence = AssignmentPrecedence;
+
+    if (!PrintExpr(c, sl.value()))
+        return false;
+
+    if (c.f.reduceParens ? lastPrecedence > AssignmentPrecedence : lastPrecedence != ExpressionPrecedence) {
+      if (!c.buffer.append(")"))
+          return false;
+    }
+
+    c.currentPrecedence = lastPrecedence;
+    return true;
+}
+
+static bool
+PrintExprList(WasmPrintContext& c, const AstExprVector& exprs, uint32_t startFrom = 0)
+{
+    for (uint32_t i = startFrom; i < exprs.length(); i++) {
+        if (!PrintFullLine(c, *exprs[i]))
+            return false;
+    }
+    return true;
+}
+
+static bool
+PrintGroupedBlock(WasmPrintContext& c, AstBlock& block)
+{
+    uint32_t skip = 0;
+    if (block.exprs().length() > 0 &&
+        block.exprs()[0]->kind() == AstExprKind::Block) {
+        if (!PrintGroupedBlock(c, *static_cast<AstBlock*>(block.exprs()[0])))
+            return false;
+        skip = 1;
+    }
+    c.indent++;
+    if (!PrintExprList(c, block.exprs(), skip))
+        return false;
+    c.indent--;
+    if (!PrintIndent(c))
+        return false;
+    if (!PrintName(c, block.breakName()))
+        return false;
+    if (!c.buffer.append(":\n"))
+        return false;
+    return true;
+}
+
+static bool
+PrintBlockName(WasmPrintContext& c, const AstName& name) {
+    if (name.empty())
+        return true;
+
+    if (!PrintIndent(c))
+        return false;
+    if (!PrintName(c, name))
+        return false;
+    return c.buffer.append(":\n");
+}
+
+static bool
+PrintBlock(WasmPrintContext& c, AstBlock& block)
+{
+    PrintOperatorPrecedence lastPrecedence = c.currentPrecedence;
+    if (block.expr() == Expr::Block) {
+        if (!c.buffer.append("{\n"))
+            return false;
+    } else if (block.expr() == Expr::Loop) {
+        if (!c.buffer.append("loop"))
+            return false;
+        if (!block.continueName().empty()) {
+            if (!c.buffer.append(" "))
+                return false;
+            if (!PrintName(c, block.continueName()))
+                return false;
+        }
+        if (!c.buffer.append(" {\n"))
+            return false;
+    } else
+        return false;
+
+    c.currentPrecedence = ExpressionPrecedence;
+
+    bool skip = 0;
+    if (c.f.groupBlocks && block.expr() == Expr::Block &&
+        block.exprs().length() > 0 && block.exprs()[0]->kind() == AstExprKind::Block) {
+        if (!PrintGroupedBlock(c, *static_cast<AstBlock*>(block.exprs()[0])))
+            return false;
+        skip = 1;
+        if (block.exprs().length() == 1 && block.breakName().empty()) {
+          // Special case to resolve ambiguity in parsing of optional end block label.
+          if (!PrintIndent(c))
+              return false;
+          if (!c.buffer.append("$exit$:\n"))
+              return false;
+        }
+    }
+
+    c.indent++;
+    if (!PrintExprList(c, block.exprs(), skip))
+        return false;
+    c.indent--;
+    c.currentPrecedence = lastPrecedence;
+
+    if (!PrintBlockName(c, block.breakName()))
+      return false;
+
+    if (!PrintIndent(c))
+        return false;
+
+    return c.buffer.append("}");
+}
+
+static bool
+PrintUnaryOperator(WasmPrintContext& c, AstUnaryOperator& op)
+{
+    PrintOperatorPrecedence lastPrecedence = c.currentPrecedence;
+
+    const char* opStr;
+    const char* prefixStr = nullptr;
+    switch (op.expr()) {
+      case Expr::I32Eqz:     opStr = "i32.eqz"; prefixStr = "!"; break;
+      case Expr::I32Clz:     opStr = "i32.clz"; break;
+      case Expr::I32Ctz:     opStr = "i32.ctz"; break;
+      case Expr::I32Popcnt:  opStr = "i32.popcnt"; break;
+      case Expr::I64Eqz:     opStr = "i64.eqz"; prefixStr = "!"; break;
+      case Expr::I64Clz:     opStr = "i64.clz"; break;
+      case Expr::I64Ctz:     opStr = "i64.ctz"; break;
+      case Expr::I64Popcnt:  opStr = "i64.popcnt"; break;
+      case Expr::F32Abs:     opStr = "f32.abs"; break;
+      case Expr::F32Neg:     opStr = "f32.neg"; prefixStr = "-"; break;
+      case Expr::F32Ceil:    opStr = "f32.ceil"; break;
+      case Expr::F32Floor:   opStr = "f32.floor"; break;
+      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 = "-"; break;
+      case Expr::F64Ceil:    opStr = "f64.ceil"; break;
+      case Expr::F64Floor:   opStr = "f64.floor"; break;
+      case Expr::F64Sqrt:    opStr = "f64.sqrt"; break;
+      default: return false;
+    }
+
+    if (c.f.reduceParens && lastPrecedence > UnaryOperatorPrecedence) {
+      if (!c.buffer.append("("))
+          return false;
+    }
+
+    c.currentPrecedence = UnaryOperatorPrecedence;
+    if (c.f.allowAsciiOperators && prefixStr) {
+      if (!c.buffer.append(prefixStr, strlen(prefixStr)))
+          return false;
+    } else {
+        if (!c.buffer.append(opStr, strlen(opStr)))
+            return false;
+
+        if (!c.buffer.append(" "))
+            return false;
+    }
+
+    if (!PrintExpr(c, *op.op()))
+        return false;
+
+    if (c.f.reduceParens && lastPrecedence > UnaryOperatorPrecedence) {
+      if (!c.buffer.append(")"))
+          return false;
+    }
+    c.currentPrecedence = lastPrecedence;
+
+    return true;
+}
+
+static bool
+PrintBinaryOperator(WasmPrintContext& c, AstBinaryOperator& op)
+{
+    PrintOperatorPrecedence lastPrecedence = c.currentPrecedence;
+
+    const char* opStr;
+    const char* infixStr = nullptr;
+    PrintOperatorPrecedence precedence;
+    switch (op.expr()) {
+      case Expr::I32Add:      opStr = "i32.add"; infixStr = "+"; precedence = AdditionPrecedence; break;
+      case Expr::I32Sub:      opStr = "i32.sub"; infixStr = "-"; precedence = AdditionPrecedence; break;
+      case Expr::I32Mul:      opStr = "i32.mul"; infixStr = "*"; precedence = MultiplicationPrecedence; break;
+      case Expr::I32DivS:     opStr = "i32.div_s"; infixStr = "/s"; precedence = MultiplicationPrecedence; break;
+      case Expr::I32DivU:     opStr = "i32.div_u"; infixStr = "/u"; precedence = MultiplicationPrecedence; break;
+      case Expr::I32RemS:     opStr = "i32.rem_s"; infixStr = "%s"; precedence = MultiplicationPrecedence; break;
+      case Expr::I32RemU:     opStr = "i32.rem_u"; infixStr = "%u"; precedence = MultiplicationPrecedence; break;
+      case Expr::I32And:      opStr = "i32.and"; infixStr = "&"; precedence = BitwiseAndPrecedence; break;
+      case Expr::I32Or:       opStr = "i32.or"; infixStr = "|"; precedence = BitwiseOrPrecedence; break;
+      case Expr::I32Xor:      opStr = "i32.xor"; infixStr = "^"; precedence = BitwiseXorPrecedence; break;
+      case Expr::I32Shl:      opStr = "i32.shl"; infixStr = "<<"; precedence = BitwiseShiftPrecedence; break;
+      case Expr::I32ShrS:     opStr = "i32.shr_s"; infixStr = ">>s"; precedence = BitwiseShiftPrecedence; break;
+      case Expr::I32ShrU:     opStr = "i32.shr_u"; infixStr = ">>u"; precedence = BitwiseShiftPrecedence; break;
+      case Expr::I64Add:      opStr = "i64.add"; infixStr = "+"; precedence = AdditionPrecedence; break;
+      case Expr::I64Sub:      opStr = "i64.sub"; infixStr = "-"; precedence = AdditionPrecedence; break;
+      case Expr::I64Mul:      opStr = "i64.mul"; infixStr = "*"; precedence = MultiplicationPrecedence; break;
+      case Expr::I64DivS:     opStr = "i64.div_s"; infixStr = "/s"; precedence = MultiplicationPrecedence; break;
+      case Expr::I64DivU:     opStr = "i64.div_u"; infixStr = "/u"; precedence = MultiplicationPrecedence; break;
+      case Expr::I64RemS:     opStr = "i64.rem_s"; infixStr = "%s"; precedence = MultiplicationPrecedence; break;
+      case Expr::I64RemU:     opStr = "i64.rem_u"; infixStr = "%u"; precedence = MultiplicationPrecedence; break;
+      case Expr::I64And:      opStr = "i64.and"; infixStr = "&"; precedence = BitwiseAndPrecedence; break;
+      case Expr::I64Or:       opStr = "i64.or"; infixStr = "|"; precedence = BitwiseOrPrecedence; break;
+      case Expr::I64Xor:      opStr = "i64.xor"; infixStr = "^"; precedence = BitwiseXorPrecedence; break;
+      case Expr::I64Shl:      opStr = "i64.shl"; infixStr = "<<"; precedence = BitwiseShiftPrecedence; break;
+      case Expr::I64ShrS:     opStr = "i64.shr_s"; infixStr = ">>s"; precedence = BitwiseShiftPrecedence; break;
+      case Expr::I64ShrU:     opStr = "i64.shr_u"; infixStr = ">>u"; precedence = BitwiseShiftPrecedence; break;
+      case Expr::F32Add:      opStr = "f32.add"; infixStr = "+"; precedence = AdditionPrecedence; break;
+      case Expr::F32Sub:      opStr = "f32.sub"; infixStr = "-"; precedence = AdditionPrecedence; break;
+      case Expr::F32Mul:      opStr = "f32.mul"; infixStr = "*"; precedence = MultiplicationPrecedence; break;
+      case Expr::F32Div:      opStr = "f32.div"; infixStr = "/"; precedence = MultiplicationPrecedence; break;
+      case Expr::F32Min:      opStr = "f32.min"; precedence = MinMaxPrecedence; break;
+      case Expr::F32Max:      opStr = "f32.max"; precedence = MinMaxPrecedence; break;
+      case Expr::F32CopySign: opStr = "f32.copysign"; precedence = CopySignPrecedence; break;
+      case Expr::F64Add:      opStr = "f64.add"; infixStr = "+"; precedence = AdditionPrecedence; break;
+      case Expr::F64Sub:      opStr = "f64.sub"; infixStr = "-"; precedence = AdditionPrecedence; break;
+      case Expr::F64Mul:      opStr = "f64.mul"; infixStr = "*"; precedence = MultiplicationPrecedence; break;
+      case Expr::F64Div:      opStr = "f64.div"; infixStr = "/"; precedence = MultiplicationPrecedence; break;
+      case Expr::F64Min:      opStr = "f64.min"; precedence = MinMaxPrecedence; break;
+      case Expr::F64Max:      opStr = "f64.max"; precedence = MinMaxPrecedence; break;
+      case Expr::F64CopySign: opStr = "f64.copysign"; precedence = CopySignPrecedence; break;
+      default: return false;
+    }
+
+    c.currentPrecedence = precedence;
+    if (c.f.reduceParens && lastPrecedence > AdditionPrecedence) {
+      if (!c.buffer.append("("))
+          return false;
+    }
+
+    if (!c.f.allowAsciiOperators || !infixStr) {
+      if (!c.buffer.append(opStr, strlen(opStr)))
+          return false;
+      if (!c.buffer.append(" "))
+          return false;
+    }
+    if (!PrintExpr(c, *op.lhs()))
+        return false;
+    if (!c.buffer.append(" "))
+        return false;
+    if (c.f.allowAsciiOperators && infixStr) {
+      // case of  A / (B / C)
+      c.currentPrecedence = (PrintOperatorPrecedence)(precedence + 1);
+
+      if (!c.buffer.append(infixStr, strlen(infixStr)))
+          return false;
+      if (!c.buffer.append(" "))
+          return false;
+    }
+    if (!PrintExpr(c, *op.rhs()))
+        return false;
+
+    if (c.f.reduceParens && lastPrecedence > AdditionPrecedence) {
+      if (!c.buffer.append(")"))
+          return false;
+    }
+    c.currentPrecedence = lastPrecedence;
+
+    return true;
+}
+
+static bool
+PrintTernaryOperator(WasmPrintContext& c, AstTernaryOperator& op)
+{
+    PrintOperatorPrecedence lastPrecedence = c.currentPrecedence;
+
+    const char* opStr;
+    switch (op.expr()) {
+      case Expr::Select: opStr = "select"; break;
+      default: return false;
+    }
+
+    c.currentPrecedence = SelectPrecedence;
+    if (c.f.reduceParens && lastPrecedence > SelectPrecedence) {
+      if (!c.buffer.append("("))
+          return false;
+    }
+
+    if (!c.buffer.append(opStr, strlen(opStr)))
+        return false;
+
+    if (!c.buffer.append(" "))
+        return false;
+    if (!PrintExpr(c, *op.op0()))
+        return false;
+    if (!c.buffer.append(","))
+        return false;
+    if (!PrintExpr(c, *op.op1()))
+        return false;
+    if (!c.buffer.append(" ? "))
+        return false;
+    if (!PrintExpr(c, *op.op2()))
+        return false;
+
+    if (c.f.reduceParens && lastPrecedence > SelectPrecedence) {
+      if (!c.buffer.append(")"))
+          return false;
+    }
+    c.currentPrecedence = lastPrecedence;
+
+    return true;
+}
+
+static bool
+PrintComparisonOperator(WasmPrintContext& c, AstComparisonOperator& op)
+{
+    PrintOperatorPrecedence lastPrecedence = c.currentPrecedence;
+
+    const char* opStr;
+    const char* infixStr = nullptr;
+    switch (op.expr()) {
+      case Expr::I32Eq:  opStr = "i32.eq"; infixStr = "=="; break;
+      case Expr::I32Ne:  opStr = "i32.ne"; infixStr = "!="; break;
+      case Expr::I32LtS: opStr = "i32.lt_s"; infixStr = "<s"; break;
+      case Expr::I32LtU: opStr = "i32.lt_u"; infixStr = "<u"; break;
+      case Expr::I32LeS: opStr = "i32.le_s"; infixStr = "<=s"; break;
+      case Expr::I32LeU: opStr = "i32.le_u"; infixStr = "<=u"; break;
+      case Expr::I32GtS: opStr = "i32.gt_s"; infixStr = ">s"; break;
+      case Expr::I32GtU: opStr = "i32.gt_u"; infixStr = ">u"; break;
+      case Expr::I32GeS: opStr = "i32.ge_s"; infixStr = ">=s"; break;
+      case Expr::I32GeU: opStr = "i32.ge_u"; infixStr = ">=u"; break;
+      case Expr::I64Eq:  opStr = "i64.eq"; infixStr = "=="; break;
+      case Expr::I64Ne:  opStr = "i64.ne"; infixStr = "!="; break;
+      case Expr::I64LtS: opStr = "i64.lt_s"; infixStr = "<s"; break;
+      case Expr::I64LtU: opStr = "i64.lt_u"; infixStr = "<u"; break;
+      case Expr::I64LeS: opStr = "i64.le_s"; infixStr = "<=s"; break;
+      case Expr::I64LeU: opStr = "i64.le_u"; infixStr = "<=u"; break;
+      case Expr::I64GtS: opStr = "i64.gt_s"; infixStr = ">s"; break;
+      case Expr::I64GtU: opStr = "i64.gt_u"; infixStr = ">u"; break;
+      case Expr::I64GeS: opStr = "i64.ge_s"; infixStr = ">=s"; break;
+      case Expr::I64GeU: opStr = "i64.ge_u"; infixStr = ">=u"; break;
+      case Expr::F32Eq:  opStr = "f32.eq"; infixStr = "=="; break;
+      case Expr::F32Ne:  opStr = "f32.ne"; infixStr = "!="; break;
+      case Expr::F32Lt:  opStr = "f32.lt"; infixStr = "<"; break;
+      case Expr::F32Le:  opStr = "f32.le"; infixStr = "<="; break;
+      case Expr::F32Gt:  opStr = "f32.gt"; infixStr = ">"; break;
+      case Expr::F32Ge:  opStr = "f32.ge"; infixStr = ">="; break;
+      case Expr::F64Eq:  opStr = "f64.eq"; infixStr = "=="; break;
+      case Expr::F64Ne:  opStr = "f64.ne"; infixStr = "!="; break;
+      case Expr::F64Lt:  opStr = "f64.lt"; infixStr = "<"; break;
+      case Expr::F64Le:  opStr = "f64.le"; infixStr = "<="; break;
+      case Expr::F64Gt:  opStr = "f64.gt"; infixStr = ">"; break;
+      case Expr::F64Ge:  opStr = "f64.ge"; infixStr = ">="; break;
+      default: return false;
+    }
+
+    c.currentPrecedence = ComparisonPrecedence;
+    if (c.f.reduceParens && lastPrecedence > ComparisonPrecedence) {
+      if (!c.buffer.append("("))
+          return false;
+    }
+
+    if (!c.f.allowAsciiOperators || !infixStr) {
+        if (!c.buffer.append(opStr, strlen(opStr)))
+            return false;
+        if (!c.buffer.append(" "))
+            return false;
+    }
+    if (!PrintExpr(c, *op.lhs()))
+        return false;
+    if (!c.buffer.append(" "))
+        return false;
+    if (c.f.allowAsciiOperators && infixStr) {
+        if (!c.buffer.append(infixStr, strlen(infixStr)))
+            return false;
+        if (!c.buffer.append(" "))
+            return false;
+    }
+    if (!PrintExpr(c, *op.rhs()))
+        return false;
+
+    if (c.f.reduceParens && lastPrecedence > ComparisonPrecedence) {
+      if (!c.buffer.append(")"))
+          return false;
+    }
+    c.currentPrecedence = lastPrecedence;
+
+    return true;
+}
+
+static bool
+PrintConversionOperator(WasmPrintContext& c, AstConversionOperator& op)
+{
+    PrintOperatorPrecedence lastPrecedence = c.currentPrecedence;
+
+    const char* opStr;
+    switch (op.expr()) {
+      case Expr::I32WrapI64:        opStr = "i32.wrap/i64"; break;
+      case Expr::I32TruncSF32:      opStr = "i32.trunc_s/f32"; break;
+      case Expr::I32TruncUF32:      opStr = "i32.trunc_u/f32"; break;
+      case Expr::I32ReinterpretF32: opStr = "i32.reinterpret/f32"; break;
+      case Expr::I32TruncSF64:      opStr = "i32.trunc_s/f64"; break;
+      case Expr::I32TruncUF64:      opStr = "i32.trunc_u/f64"; break;
+      case Expr::I64ExtendSI32:     opStr = "i64.extend_s/i32"; break;
+      case Expr::I64ExtendUI32:     opStr = "i64.extend_u/i32"; break;
+      case Expr::I64TruncSF32:      opStr = "i64.trunc_s/f32"; break;
+      case Expr::I64TruncUF32:      opStr = "i64.trunc_u/f32"; break;
+      case Expr::I64TruncSF64:      opStr = "i64.trunc_s/f64"; break;
+      case Expr::I64TruncUF64:      opStr = "i64.trunc_u/f64"; break;
+      case Expr::I64ReinterpretF64: opStr = "i64.reinterpret/f64"; break;
+      case Expr::F32ConvertSI32:    opStr = "f32.convert_s/i32"; break;
+      case Expr::F32ConvertUI32:    opStr = "f32.convert_u/i32"; break;
+      case Expr::F32ReinterpretI32: opStr = "f32.reinterpret/i32"; break;
+      case Expr::F32ConvertSI64:    opStr = "f32.convert_s/i64"; break;
+      case Expr::F32ConvertUI64:    opStr = "f32.convert_u/i64"; break;
+      case Expr::F32DemoteF64:      opStr = "f32.demote/f64"; break;
+      case Expr::F64ConvertSI32:    opStr = "f64.convert_s/i32"; break;
+      case Expr::F64ConvertUI32:    opStr = "f64.convert_u/i32"; break;
+      case Expr::F64ConvertSI64:    opStr = "f64.convert_s/i64"; break;
+      case Expr::F64ConvertUI64:    opStr = "f64.convert_u/i64"; break;
+      case Expr::F64ReinterpretI64: opStr = "f64.reinterpret/i64"; break;
+      case Expr::F64PromoteF32:     opStr = "f64.promote/f32"; break;
+      default: return false;
+    }
+
+    c.currentPrecedence = ConversionPrecedence;
+    if (c.f.reduceParens && lastPrecedence > ConversionPrecedence) {
+      if (!c.buffer.append("("))
+          return false;
+    }
+
+    if (!c.buffer.append(opStr, strlen(opStr)))
+        return false;
+
+    if (!c.buffer.append(" "))
+        return false;
+
+    if (!PrintExpr(c, *op.op()))
+        return false;
+
+    if (c.f.reduceParens && lastPrecedence > ConversionPrecedence) {
+      if (!c.buffer.append(")"))
+          return false;
+    }
+    c.currentPrecedence = lastPrecedence;
+
+    return true;
+}
+
+static bool
+PrintIf(WasmPrintContext& c, AstIf& if_)
+{
+    PrintOperatorPrecedence lastPrecedence = c.currentPrecedence;
+
+    c.currentPrecedence = ExpressionPrecedence;
+    if (!c.buffer.append("if ("))
+        return false;
+    if (!PrintExpr(c, if_.cond()))
+        return false;
+
+    if (!c.buffer.append(") {\n"))
+        return false;
+
+    c.indent++;
+    if (!PrintExprList(c, if_.thenExprs()))
+        return false;
+    c.indent--;
+
+    if (!PrintBlockName(c, if_.thenName()))
+        return false;
+
+    if (if_.hasElse()) {
+        if (!PrintIndent(c))
+            return false;
+        if (!c.buffer.append("} else {\n"))
+            return false;
+
+        c.indent++;
+        if (!PrintExprList(c, if_.elseExprs()))
+            return false;
+        c.indent--;
+        if (!PrintBlockName(c, if_.elseName()))
+            return false;
+    }
+
+    if (!PrintIndent(c))
+        return false;
+
+    c.currentPrecedence = lastPrecedence;
+
+    return c.buffer.append("}");
+}
+
+static bool
+PrintLoadStoreAddress(WasmPrintContext& c, const AstLoadStoreAddress& lsa, uint32_t defaultAlignLog2)
+{
+    PrintOperatorPrecedence lastPrecedence = c.currentPrecedence;
+
+    c.currentPrecedence = ExpressionPrecedence;
+
+    if (!c.buffer.append(" ["))
+        return false;
+    if (!PrintExpr(c, lsa.base()))
+        return false;
+
+    if (lsa.offset() != 0) {
+      if (!c.buffer.append(","))
+          return false;
+      if (!PrintInt32(c, lsa.offset(), true))
+          return false;
+    }
+    if (!c.buffer.append("]"))
+        return false;
+
+    uint32_t alignLog2 = lsa.flags();
+    if (defaultAlignLog2 != alignLog2) {
+      if (!c.buffer.append(":align="))
+          return false;
+      if (!PrintInt32(c, 1 << alignLog2))
+          return false;
+    }
+
+    c.currentPrecedence = lastPrecedence;
+    return true;
+}
+
+static bool
+PrintLoad(WasmPrintContext& c, AstLoad& load)
+{
+    PrintOperatorPrecedence lastPrecedence = c.currentPrecedence;
+
+    c.currentPrecedence = LoadOperatorPrecedence;
+    if (c.f.reduceParens && lastPrecedence > LoadOperatorPrecedence) {
+      if (!c.buffer.append("("))
+          return false;
+    }
+
+    uint32_t defaultAlignLog2;
+    switch (load.expr()) {
+      case Expr::I32Load8S:
+        if (!c.buffer.append("i32.load8_s"))
+            return false;
+        defaultAlignLog2 = 0;
+        break;
+      case Expr::I64Load8S:
+        if (!c.buffer.append("i64.load8_s"))
+            return false;
+        defaultAlignLog2 = 0;
+        break;
+      case Expr::I32Load8U:
+        if (!c.buffer.append("i32.load8_u"))
+            return false;
+        defaultAlignLog2 = 0;
+        break;
+      case Expr::I64Load8U:
+        if (!c.buffer.append("i64.load8_u"))
+            return false;
+        defaultAlignLog2 = 0;
+        break;
+      case Expr::I32Load16S:
+        if (!c.buffer.append("i32.load16_s"))
+            return false;
+        defaultAlignLog2 = 1;
+        break;
+      case Expr::I64Load16S:
+        if (!c.buffer.append("i64.load16_s"))
+            return false;
+        defaultAlignLog2 = 1;
+        break;
+      case Expr::I32Load16U:
+        if (!c.buffer.append("i32.load16_u"))
+            return false;
+        defaultAlignLog2 = 1;
+        break;
+      case Expr::I64Load16U:
+        if (!c.buffer.append("i64.load16_u"))
+            return false;
+        defaultAlignLog2 = 1;
+        break;
+      case Expr::I64Load32S:
+        if (!c.buffer.append("i64.load32_s"))
+            return false;
+        defaultAlignLog2 = 2;
+        break;
+      case Expr::I64Load32U:
+        if (!c.buffer.append("i64.load32_u"))
+            return false;
+        defaultAlignLog2 = 2;
+        break;
+      case Expr::I32Load:
+        if (!c.buffer.append("i32.load"))
+            return false;
+        defaultAlignLog2 = 2;
+        break;
+      case Expr::I64Load:
+        if (!c.buffer.append("i64.load"))
+            return false;
+        defaultAlignLog2 = 3;
+        break;
+      case Expr::F32Load:
+        if (!c.buffer.append("f32.load"))
+            return false;
+        defaultAlignLog2 = 2;
+        break;
+      case Expr::F64Load:
+        if (!c.buffer.append("f64.load"))
+            return false;
+        defaultAlignLog2 = 3;
+        break;
+      default:
+        return false;
+    }
+
+    if (!PrintLoadStoreAddress(c, load.address(), defaultAlignLog2))
+        return false;
+
+    if (c.f.reduceParens && lastPrecedence > LoadOperatorPrecedence) {
+      if (!c.buffer.append(")"))
+          return false;
+    }
+    c.currentPrecedence = lastPrecedence;
+
+    return true;
+}
+
+static bool
+PrintStore(WasmPrintContext& c, AstStore& store)
+{
+    PrintOperatorPrecedence lastPrecedence = c.currentPrecedence;
+
+    c.currentPrecedence = StoreOperatorPrecedence;
+    if (c.f.reduceParens ? lastPrecedence > StoreOperatorPrecedence : lastPrecedence != ExpressionPrecedence) {
+      if (!c.buffer.append("("))
+          return false;
+    }
+
+    uint32_t defaultAlignLog2;
+    switch (store.expr()) {
+      case Expr::I32Store8:
+        if (!c.buffer.append("i32.store8"))
+            return false;
+        defaultAlignLog2 = 0;
+        break;
+      case Expr::I64Store8:
+        if (!c.buffer.append("i64.store8"))
+            return false;
+        defaultAlignLog2 = 0;
+        break;
+      case Expr::I32Store16:
+        if (!c.buffer.append("i32.store16"))
+            return false;
+        defaultAlignLog2 = 1;
+        break;
+      case Expr::I64Store16:
+        if (!c.buffer.append("i64.store16"))
+            return false;
+        defaultAlignLog2 = 1;
+        break;
+      case Expr::I64Store32:
+        if (!c.buffer.append("i64.store32"))
+            return false;
+        defaultAlignLog2 = 2;
+        break;
+      case Expr::I32Store:
+        if (!c.buffer.append("i32.store"))
+            return false;
+        defaultAlignLog2 = 2;
+        break;
+      case Expr::I64Store:
+        if (!c.buffer.append("i64.store"))
+            return false;
+        defaultAlignLog2 = 3;
+        break;
+      case Expr::F32Store:
+        if (!c.buffer.append("f32.store"))
+            return false;
+        defaultAlignLog2 = 2;
+        break;
+      case Expr::F64Store:
+        if (!c.buffer.append("f64.store"))
+            return false;
+        defaultAlignLog2 = 3;
+        break;
+      default:
+        return false;
+    }
+
+    if (!PrintLoadStoreAddress(c, store.address(), defaultAlignLog2))
+        return false;
+
+    if (!c.buffer.append(","))
+        return false;
+
+    if (!PrintExpr(c, store.value()))
+        return false;
+
+    if (c.f.reduceParens ? lastPrecedence > StoreOperatorPrecedence : lastPrecedence != ExpressionPrecedence) {
+      if (!c.buffer.append(")"))
+          return false;
+    }
+
+    c.currentPrecedence = lastPrecedence;
+    return true;
+}
+
+static bool
+PrintBranch(WasmPrintContext& c, AstBranch& branch)
+{
+    Expr expr = branch.expr();
+    MOZ_ASSERT(expr == Expr::BrIf || expr == Expr::Br);
+
+    if (expr == Expr::BrIf ? !c.buffer.append("br_if ") : !c.buffer.append("br "))
+        return false;
+
+    if (expr == Expr::BrIf) {
+        if (!PrintExpr(c, branch.cond()))
+            return false;
+        if (!c.buffer.append(","))
+            return false;
+    }
+
+    if (branch.maybeValue()) {
+        if (!PrintExpr(c, *(branch.maybeValue())))
+            return false;
+        if (!c.buffer.append(","))
+            return false;
+    }
+
+    if (!PrintRef(c, branch.target()))
+        return false;
+
+    return true;
+}
+
+static bool
+PrintBrTable(WasmPrintContext& c, AstBranchTable& table)
+{
+    if (!c.buffer.append("br_table "))
+        return false;
+
+    // Index
+    if (!PrintExpr(c, table.index()))
+        return false;
+
+    if (!c.buffer.append(","))
+        return false;
+
+    if (table.maybeValue()) {
+      if (!PrintExpr(c, *(table.maybeValue())))
+          return false;
+      if (!c.buffer.append(","))
+          return false;
+    }
+
+    uint32_t tableLength = table.table().length();
+    if (tableLength > 0) {
+        if (!c.buffer.append("["))
+            return false;
+        for (uint32_t i = 0; i < tableLength; i++) {
+            if (!PrintRef(c, table.table()[i]))
+                return false;
+            if (i + 1 == tableLength)
+                break;
+            if (!c.buffer.append(","))
+                return false;
+        }
+        if (!c.buffer.append("],"))
+            return false;
+    }
+
+    if (!PrintRef(c, table.def()))
+        return false;
+
+    return true;
+}
+
+static bool
+PrintReturn(WasmPrintContext& c, AstReturn& ret)
+{
+    if (!c.buffer.append("return"))
+        return false;
+
+    if (ret.maybeExpr()) {
+        if (!c.buffer.append(" "))
+            return false;
+        if (!PrintExpr(c, *(ret.maybeExpr())))
+            return false;
+    }
+
+    return true;
+}
+
+static bool
+PrintExpr(WasmPrintContext& c, AstExpr& expr)
+{
+    switch (expr.kind()) {
+      case AstExprKind::Nop:
+        return PrintNop(c, expr.as<AstNop>());
+      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>());
+      case AstExprKind::GetLocal:
+        return PrintGetLocal(c, expr.as<AstGetLocal>());
+      case AstExprKind::SetLocal:
+        return PrintSetLocal(c, expr.as<AstSetLocal>());
+      case AstExprKind::Block:
+        return PrintBlock(c, expr.as<AstBlock>());
+      case AstExprKind::If:
+        return PrintIf(c, expr.as<AstIf>());
+      case AstExprKind::UnaryOperator:
+        return PrintUnaryOperator(c, expr.as<AstUnaryOperator>());
+      case AstExprKind::BinaryOperator:
+        return PrintBinaryOperator(c, expr.as<AstBinaryOperator>());
+      case AstExprKind::TernaryOperator:
+        return PrintTernaryOperator(c, expr.as<AstTernaryOperator>());
+      case AstExprKind::ComparisonOperator:
+        return PrintComparisonOperator(c, expr.as<AstComparisonOperator>());
+      case AstExprKind::ConversionOperator:
+        return PrintConversionOperator(c, expr.as<AstConversionOperator>());
+      case AstExprKind::Load:
+        return PrintLoad(c, expr.as<AstLoad>());
+      case AstExprKind::Store:
+        return PrintStore(c, expr.as<AstStore>());
+      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>());
+      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;
+}
+
+static bool
+PrintSignature(WasmPrintContext& c, const AstSig& sig, const AstNameVector* maybeLocals = nullptr)
+{
+    uint32_t paramsNum = sig.args().length();
+
+    if (!c.buffer.append("("))
+        return false;
+    if (maybeLocals) {
+      for (uint32_t i = 0; i < paramsNum; i++) {
+          const AstName& name = (*maybeLocals)[i];
+          if (!name.empty()) {
+              if (!PrintName(c, name))
+                  return false;
+              if (!c.buffer.append(":"))
+                  return false;
+          }
+          ValType arg = sig.args()[i];
+          if (!PrintValType(c, arg))
+              return false;
+          if (i + 1 == paramsNum)
+              break;
+          if (!c.buffer.append(","))
+              return false;
+      }
+    } else if (paramsNum > 0) {
+      for (uint32_t i = 0; i < paramsNum; i++) {
+          ValType arg = sig.args()[i];
+          if (!PrintValType(c, arg))
+              return false;
+          if (i + 1 == paramsNum)
+              break;
+          if (!c.buffer.append(","))
+              return false;
+      }
+    }
+    if (!c.buffer.append(") : ("))
+        return false;
+    if (sig.ret() != ExprType::Void) {
+        if (!PrintExprType(c, sig.ret()))
+            return false;
+    }
+    if (!c.buffer.append(")"))
+        return false;
+    return true;
+}
+
+static bool
+PrintTypeSection(WasmPrintContext& c, const AstModule::SigVector& sigs)
+{
+    uint32_t numSigs = sigs.length();
+    if (!numSigs)
+        return true;
+
+    for (uint32_t sigIndex = 0; sigIndex < numSigs; sigIndex++) {
+        const AstSig* sig = sigs[sigIndex];
+        if (!PrintIndent(c))
+            return false;
+        if (!c.buffer.append("type "))
+            return false;
+        if (!sig->name().empty()) {
+          if (!PrintName(c, sig->name()))
+              return false;
+          if (!c.buffer.append(" of "))
+              return false;
+        }
+        if (!c.buffer.append("function "))
+            return false;
+        if (!PrintSignature(c, *sig))
+            return false;
+        if (!c.buffer.append("\n"))
+            return false;
+    }
+
+    if (!c.buffer.append("\n"))
+        return false;
+
+    return true;
+}
+
+static bool
+PrintTableSection(WasmPrintContext& c, AstTable* maybeTable, const AstModule::FuncVector& funcs)
+{
+    if (!maybeTable)
+        return true;
+
+    uint32_t numTableElems = maybeTable->elems().length();
+
+    if (!c.buffer.append("table ["))
+        return false;
+
+    for (uint32_t i = 0; i < numTableElems; i++) {
+        AstRef& elem = maybeTable->elems()[i];
+        AstFunc* func = funcs[elem.index()];
+        if (func->name().empty()) {
+            if (!PrintInt32(c, elem.index()))
+                return false;
+        } else {
+          if (!PrintName(c, func->name()))
+              return false;
+        }
+        if (i + 1 == numTableElems)
+            break;
+        if (!c.buffer.append(","))
+            return false;
+    }
+
+    if (!c.buffer.append("]\n\n"))
+        return false;
+
+    return true;
+}
+
+static bool
+PrintImport(WasmPrintContext& c, AstImport& import, const AstModule::SigVector& sigs)
+{
+    const AstSig* sig = sigs[import.sig().index()];
+    if (!PrintIndent(c))
+        return false;
+    if (!c.buffer.append("import "))
+        return false;
+    if (!c.buffer.append("\""))
+        return false;
+
+    const AstName& funcName = import.func();
+    if (!PrintEscapedString(c, funcName))
+        return false;
+
+    if (!c.buffer.append("\" as "))
+        return false;
+
+    if (!PrintName(c, import.name()))
+        return false;
+
+    if (!c.buffer.append(" from \""))
+        return false;
+
+    const AstName& moduleName = import.module();
+    if (!PrintEscapedString(c, moduleName))
+        return false;
+
+    if (!c.buffer.append("\" typeof function "))
+        return false;
+
+    if (!PrintSignature(c, *sig))
+        return false;
+    if (!c.buffer.append("\n"))
+        return false;
+
+    return true;
+}
+
+
+static bool
+PrintImportSection(WasmPrintContext& c, const AstModule::ImportVector& imports, const AstModule::SigVector& sigs)
+{
+    uint32_t numImports = imports.length();
+
+    for (uint32_t i = 0; i < numImports; i++) {
+        if (!PrintImport(c, *imports[i], sigs))
+            return false;
+    }
+
+    if (numImports) {
+      if (!c.buffer.append("\n"))
+          return false;
+    }
+
+    return true;
+}
+
+static bool
+PrintExport(WasmPrintContext& c, AstExport& export_, const AstModule::FuncVector& funcs)
+{
+    if (!PrintIndent(c))
+        return false;
+    if (!c.buffer.append("export "))
+        return false;
+    if (export_.kind() == AstExportKind::Memory) {
+        if (!c.buffer.append("memory"))
+          return false;
+    } else {
+        const AstFunc* func = funcs[export_.func().index()];
+        if (func->name().empty()) {
+            if (!PrintInt32(c, export_.func().index()))
+                return false;
+        } else {
+            if (!PrintName(c, func->name()))
+                return false;
+        }
+    }
+    if (!c.buffer.append(" as \""))
+        return false;
+    if (!PrintEscapedString(c, export_.name()))
+        return false;
+    if (!c.buffer.append("\"\n"))
+        return false;
+
+    return true;
+}
+
+static bool
+PrintExportSection(WasmPrintContext& c, const AstModule::ExportVector& exports, const AstModule::FuncVector& funcs)
+{
+    uint32_t numExports = exports.length();
+    for (uint32_t i = 0; i < numExports; i++) {
+        if (!PrintExport(c, *exports[i], funcs))
+            return false;
+    }
+    if (numExports) {
+      if (!c.buffer.append("\n"))
+          return false;
+    }
+    return true;
+}
+
+static bool
+PrintFunctionBody(WasmPrintContext& c, AstFunc& func, const AstModule::SigVector& sigs)
+{
+    const AstSig* sig = sigs[func.sig().index()];
+    c.indent++;
+
+    uint32_t argsNum = sig->args().length();
+    uint32_t localsNum = func.vars().length();
+    if (localsNum > 0) {
+        if (!PrintIndent(c))
+            return false;
+        if (!c.buffer.append("var "))
+            return false;
+        for (uint32_t i = 0; i < localsNum; i++) {
+            const AstName& name = func.locals()[argsNum + i];
+            if (!name.empty()) {
+              if (!PrintName(c, name))
+                  return false;
+              if (!c.buffer.append(":"))
+                  return false;
+            }
+            ValType local = func.vars()[i];
+            if (!PrintValType(c, local))
+                return false;
+            if (i + 1 == localsNum)
+                break;
+            if (!c.buffer.append(","))
+                return false;
+        }
+        if (!c.buffer.append("\n"))
+            return false;
+    }
+
+
+    uint32_t exprsNum = func.body().length();
+    for (uint32_t i = 0; i < exprsNum; i++) {
+      if (!PrintFullLine(c, *func.body()[i]))
+          return false;
+    }
+
+    c.indent--;
+
+    return true;
+}
+
+static bool
+PrintCodeSection(WasmPrintContext& c, const AstModule::FuncVector& funcs, const AstModule::SigVector& sigs)
+{
+    uint32_t numFuncBodies = funcs.length();
+    for (uint32_t funcIndex = 0; funcIndex < numFuncBodies; funcIndex++) {
+        AstFunc* func = funcs[funcIndex];
+        uint32_t sigIndex = func->sig().index();
+        AstSig* sig = sigs[sigIndex];
+
+        if (!PrintIndent(c))
+            return false;
+        if (!c.buffer.append("function "))
+            return false;
+        if (!func->name().empty()) {
+          if (!PrintName(c, func->name()))
+              return false;
+        }
+
+        if (!PrintSignature(c, *sig, &(func->locals())))
+            return false;
+        if (!c.buffer.append(" {\n"))
+            return false;
+
+        c.currentFuncIndex = funcIndex;
+
+        if (!PrintFunctionBody(c, *func, sigs))
+            return false;
+
+        if (!PrintIndent(c))
+            return false;
+        if (!c.buffer.append("}\n\n"))
+            return false;
+    }
+
+   return true;
+}
+
+
+static bool
+PrintDataSection(WasmPrintContext& c, AstMemory* maybeMemory)
+{
+    if (!maybeMemory)
+        return true;
+
+    if (!PrintIndent(c))
+        return false;
+    if (!c.buffer.append("memory "))
+        return false;
+    if (!PrintInt32(c, maybeMemory->initialSize()))
+       return false;
+    Maybe<uint32_t> memMax = maybeMemory->maxSize();
+    if (memMax) {
+        if (!c.buffer.append(","))
+            return false;
+        if (!PrintInt32(c, *memMax))
+            return false;
+    }
+
+    c.indent++;
+
+    uint32_t numSegments = maybeMemory->segments().length();
+    if (!numSegments) {
+      if (!c.buffer.append(" {}\n\n"))
+          return false;
+      return true;
+    }
+    if (!c.buffer.append(" {\n"))
+        return false;
+
+    for (uint32_t i = 0; i < numSegments; i++) {
+        const AstSegment* segment = maybeMemory->segments()[i];
+
+        if (!PrintIndent(c))
+            return false;
+        if (!c.buffer.append("segment "))
+           return false;
+        if (!PrintInt32(c, segment->offset()))
+           return false;
+        if (!c.buffer.append(" \""))
+           return false;
+
+        PrintEscapedString(c, segment->text());
+
+        if (!c.buffer.append("\"\n"))
+           return false;
+    }
+
+    c.indent--;
+    if (!c.buffer.append("}\n\n"))
+        return false;
+
+    return true;
+}
+
+static bool
+PrintModule(WasmPrintContext& c, AstModule& module)
+{
+    if (!PrintTypeSection(c, module.sigs()))
+        return false;
+
+    if (!PrintImportSection(c, module.imports(), module.sigs()))
+        return false;
+
+    if (!PrintTableSection(c, module.maybeTable(), module.funcs()))
+        return false;
+
+    if (!PrintExportSection(c, module.exports(), module.funcs()))
+        return false;
+
+    if (!PrintCodeSection(c, module.funcs(), module.sigs()))
+        return false;
+
+    if (!PrintDataSection(c, module.maybeMemory()))
+        return false;
+
+    return true;
+}
+
+/*****************************************************************************/
+// Top-level functions
+
+bool
+wasm::BinaryToExperimentalText(JSContext* cx, const uint8_t* bytes, size_t length,
+                               StringBuffer& buffer, const ExperimentalTextFormatting& formatting)
+{
+
+    LifoAlloc lifo(AST_LIFO_DEFAULT_CHUNK_SIZE);
+
+    AstModule* module;
+    if (!BinaryToAst(cx, bytes, length, lifo, &module))
+        return false;
+
+    WasmPrintContext c(cx, module, buffer, formatting);
+
+    if (!PrintModule(c, *module)) {
+        if (!cx->isExceptionPending())
+            ReportOutOfMemory(cx);
+        return false;
+    }
+
+    return true;
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/asmjs/WasmBinaryToExperimentalText.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ *
+ * Copyright 2015 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef wasm_binary_to_experimental_text_h
+#define wasm_binary_to_experimental_text_h
+
+#include "NamespaceImports.h"
+
+#include "gc/Rooting.h"
+#include "js/Class.h"
+
+namespace js {
+
+class StringBuffer;
+
+namespace wasm {
+
+struct ExperimentalTextFormatting
+{
+    bool allowAsciiOperators:1;
+    bool reduceParens:1;
+    bool groupBlocks:1;
+
+    ExperimentalTextFormatting()
+     : allowAsciiOperators(true),
+       reduceParens(true),
+       groupBlocks(true)
+    {}
+};
+
+// Translate the given binary representation of a wasm module into the module's textual
+// representation.
+
+MOZ_MUST_USE bool
+BinaryToExperimentalText(JSContext* cx, const uint8_t* bytes, size_t length, StringBuffer& buffer,
+                         const ExperimentalTextFormatting& formatting);
+
+}  // namespace wasm
+
+}  // namespace js
+
+#endif // namespace wasm_binary_to_experimental_text_h
--- a/js/src/asmjs/WasmModule.cpp
+++ b/js/src/asmjs/WasmModule.cpp
@@ -20,16 +20,17 @@
 
 #include "mozilla/Atomics.h"
 #include "mozilla/BinarySearch.h"
 #include "mozilla/EnumeratedRange.h"
 #include "mozilla/PodOperations.h"
 
 #include "jsprf.h"
 
+#include "asmjs/WasmBinaryToExperimentalText.h"
 #include "asmjs/WasmBinaryToText.h"
 #include "asmjs/WasmSerialize.h"
 #include "builtin/AtomicsObject.h"
 #include "builtin/SIMD.h"
 #ifdef JS_ION_PERF
 # include "jit/PerfSpewer.h"
 #endif
 #include "jit/BaselineJIT.h"
@@ -1668,17 +1669,17 @@ const char enabledMessage[] =
 
 JSString*
 Module::createText(JSContext* cx)
 {
     StringBuffer buffer(cx);
     if (!source_.empty()) {
         if (!buffer.append(experimentalWarning))
             return nullptr;
-        if (!BinaryToText(cx, source_.begin(), source_.length(), buffer))
+        if (!BinaryToExperimentalText(cx, source_.begin(), source_.length(), buffer, ExperimentalTextFormatting()))
             return nullptr;
     } else {
         if (!buffer.append(enabledMessage))
             return nullptr;
     }
     return buffer.finishString();
 }
 
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -16,16 +16,17 @@
 #include "jsfriendapi.h"
 #include "jsgc.h"
 #include "jsobj.h"
 #include "jsprf.h"
 #include "jswrapper.h"
 
 #include "asmjs/AsmJS.h"
 #include "asmjs/Wasm.h"
+#include "asmjs/WasmBinaryToExperimentalText.h"
 #include "asmjs/WasmBinaryToText.h"
 #include "asmjs/WasmTextToBinary.h"
 #include "builtin/Promise.h"
 #include "builtin/SelfHostingDefines.h"
 #include "jit/InlinableNatives.h"
 #include "jit/JitFrameIterator.h"
 #include "js/Debug.h"
 #include "js/HashTable.h"
@@ -575,21 +576,40 @@ WasmBinaryToText(JSContext* cx, unsigned
 
     Vector<uint8_t> copy(cx);
     if (code->bufferUnshared()->hasInlineData()) {
         if (!copy.append(bytes, length))
             return false;
         bytes = copy.begin();
     }
 
+    bool experimental = false;
+    if (args.length() > 1) {
+        JSString* opt = JS::ToString(cx, args[1]);
+        if (!opt)
+            return false;
+        bool match;
+        if (!JS_StringEqualsAscii(cx, opt, "experimental", &match))
+            return false;
+        experimental = match;
+    }
+
     StringBuffer buffer(cx);
-    if (!wasm::BinaryToText(cx, bytes, length, buffer)) {
-        if (!cx->isExceptionPending())
-            JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_FAIL, "print error");
-        return false;
+    if (experimental) {
+        if (!wasm::BinaryToExperimentalText(cx, bytes, length, buffer, wasm::ExperimentalTextFormatting())) {
+            if (!cx->isExceptionPending())
+                JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_FAIL, "print error");
+            return false;
+        }
+    } else {
+        if (!wasm::BinaryToText(cx, bytes, length, buffer)) {
+            if (!cx->isExceptionPending())
+                JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_FAIL, "print error");
+            return false;
+        }
     }
 
     JSString* result = buffer.finishString();
     if (!result)
         return false;
 
     args.rval().setString(result);
     return true;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/totext2.js
@@ -0,0 +1,72 @@
+if (!wasmIsSupported())
+     quit();
+
+load(libdir + "asserts.js");
+
+function runTest(code, expected) {
+  var binary = wasmTextToBinary(code);
+  var s = wasmBinaryToText(binary, "experimental");
+  s = s.replace(/\s+/g, ' ');
+  print("TEXT: " + s);
+  assertEq(expected, s);
+}
+
+// Smoke test
+runTest(`
+(module
+  (func (param i32) (result f64)
+     (local $l f32)
+     (block
+        (set_local $l (f32.const 0.0))
+        (loop $exit $cont
+           (br_if $exit (get_local 0))
+           (br 2)
+        )
+        (if (i32.const 1)
+           (f64.min (f64.neg (f64.const 1)) (f64.const 0))
+           (f64.add (f64.const 0.5) (f64.load offset=0 (i32.const 0)) )
+        )
+     )
+     (i32.store16 (i32.const 8) (i32.const 128))
+
+     (return (f64.const 0))
+  )
+  (export "test" 0)
+  (memory 1 10)
+)`,
+"type $type$0 of function (i32) : (f64) " +
+"export $func$0 as \"test\" " +
+"function $func$0($var$0:i32) : (f64) {" +
+" var $var$1:f32 { $var$1 = 0.0f loop { br_if $var$0,$label$0 br $label$1 $label$0: }" +
+" if (1) { f64.min -1.0 0.0 } else { 0.5 + f64.load [0] } $label$1: }" +
+" i32.store16 [8],128 return 0.0 "+
+"} memory 1,10 {} ");
+
+// function calls
+runTest(`
+(module
+  (type $type1 (func (param i32) (result i32)))
+  (import $import1 "mod" "test" (param f32) (result f32))
+  (table $func1 $func2)
+  (func $func1 (param i32) (param f32) (nop))
+  (func $func2 (param i32) (result i32) (get_local 0))
+  (func $test
+    (call $func1
+      (call_indirect $type1 (i32.const 1) (i32.const 2))
+      (call_import $import1 (f32.const 1.0))
+    )
+  )
+  (export "test" $test)
+  (memory 1 65535)
+)`,
+"type $type$0 of function (i32) : (i32) " +
+"type $type$1 of function (f32) : (f32) " +
+"type $type$2 of function (i32,f32) : () " +
+"type $type$3 of function () : () " +
+"import \"test\" as $import$0 from \"mod\" typeof function (f32) : (f32) " +
+"table [$func$0,$func$1] export $func$2 as \"test\" " +
+"function $func$0($var$0:i32,$var$1:f32) : () { nop } " +
+"function $func$1($var$0:i32) : (i32) { $var$0 } " +
+"function $func$2() : () {" +
+" call $func$0 (call_indirect $type$0 [1] (2),call_import $import$0 (1.0f)) " +
+"} memory 1,65535 {} ");
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -153,16 +153,17 @@ EXPORTS.js += [
 ]
 
 UNIFIED_SOURCES += [
     'asmjs/AsmJS.cpp',
     'asmjs/Wasm.cpp',
     'asmjs/WasmBinary.cpp',
     'asmjs/WasmBinaryIterator.cpp',
     'asmjs/WasmBinaryToAST.cpp',
+    'asmjs/WasmBinaryToExperimentalText.cpp',
     'asmjs/WasmBinaryToText.cpp',
     'asmjs/WasmFrameIterator.cpp',
     'asmjs/WasmGenerator.cpp',
     'asmjs/WasmIonCompile.cpp',
     'asmjs/WasmModule.cpp',
     'asmjs/WasmSignalHandlers.cpp',
     'asmjs/WasmStubs.cpp',
     'asmjs/WasmTextToBinary.cpp',