Bug 1254984 - Experimental text representation of WebAssembly binary code (r=luke)
authorYury Delendik <ydelendik@mozilla.com>
Fri, 11 Mar 2016 19:47:25 -0600
changeset 288403 3e871f2d5b4bb29d788201568d68fe48a84113f9
parent 288402 79bf9cf6fc79be06cdd63abec65a375704e262ba
child 288404 92e23bc3f31d2b4e33575bc5448e1f0e3ef13b4e
push id30079
push userryanvm@gmail.com
push dateSat, 12 Mar 2016 20:24:19 +0000
treeherdermozilla-central@d1d47ba19ce9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1254984
milestone48.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 1254984 - Experimental text representation of WebAssembly binary code (r=luke) MozReview-Commit-ID: 5xNHZYiaVoW
js/src/asmjs/Wasm.cpp
js/src/asmjs/WasmBinaryToText.cpp
js/src/asmjs/WasmBinaryToText.h
js/src/asmjs/WasmText.cpp
js/src/asmjs/WasmText.h
js/src/asmjs/WasmTextToBinary.cpp
js/src/asmjs/WasmTextToBinary.h
js/src/builtin/TestingFunctions.cpp
js/src/jit-test/tests/wasm/totext1.js
js/src/moz.build
--- a/js/src/asmjs/Wasm.cpp
+++ b/js/src/asmjs/Wasm.cpp
@@ -18,17 +18,16 @@
 
 #include "asmjs/Wasm.h"
 
 #include "mozilla/CheckedInt.h"
 
 #include "jsprf.h"
 
 #include "asmjs/WasmGenerator.h"
-#include "asmjs/WasmText.h"
 #include "vm/ArrayBufferObject.h"
 
 #include "jsatominlines.h"
 #include "jsobjinlines.h"
 
 using namespace js;
 using namespace js::wasm;
 
new file mode 100644
--- /dev/null
+++ b/js/src/asmjs/WasmBinaryToText.cpp
@@ -0,0 +1,1704 @@
+/* -*- 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/WasmBinaryToText.h"
+
+#include "mozilla/CheckedInt.h"
+
+#include "jsnum.h"
+
+#include "asmjs/Wasm.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::IsNaN;
+
+struct WasmRenderContext
+{
+    JSContext* cx;
+    Decoder& d;
+    StringBuffer& buffer;
+    uint32_t indent;
+
+    DeclaredSigVector signatures;
+    Uint32Vector funcSigs;
+    Uint32Vector importSigs;
+    bool has_return;
+
+    WasmRenderContext(JSContext* cx, Decoder& d, StringBuffer& buffer)
+      : cx(cx), d(d), buffer(buffer), indent(0)
+    {}
+};
+
+/*****************************************************************************/
+// utilities
+
+static bool
+RenderFail(WasmRenderContext& c, const char* str)
+{
+    uint32_t offset = c.d.currentOffset();
+    char offsetStr[sizeof "4294967295"];
+    JS_snprintf(offsetStr, sizeof offsetStr, "%" PRIu32, offset);
+    JS_ReportErrorNumber(c.cx, GetErrorMessage, nullptr, JSMSG_WASM_DECODE_FAIL, offsetStr, str);
+    return false;
+}
+
+static bool
+RenderIndent(WasmRenderContext& c)
+{
+    for (uint32_t i = 0; i < c.indent; i++) {
+        if (!c.buffer.append("  "))
+            return false;
+    }
+    return true;
+}
+
+static bool
+RenderInt32(WasmRenderContext& c, int32_t num)
+{
+    return NumberValueToStringBuffer(c.cx, Int32Value(num), c.buffer);
+}
+
+static bool
+RenderDouble(WasmRenderContext& c, double num)
+{
+    return NumberValueToStringBuffer(c.cx, DoubleValue(num), c.buffer);
+}
+
+static bool
+RenderString(WasmRenderContext& c, const uint8_t* s, size_t length)
+{
+    for (size_t i = 0; i < length; i++) {
+        uint8_t byte = s[i];
+        bool success;
+        switch (byte) {
+            case '\n': success = c.buffer.append("\\n"); break;
+            case '\r': success = c.buffer.append("\\0d"); break;
+            case '\t': success = c.buffer.append("\\t"); break;
+            case '\f': success = c.buffer.append("\\0c"); break;
+            case '\b': success = c.buffer.append("\\08"); break;
+            case '\\': success = c.buffer.append("\\\\"); break;
+            case '"' : success = c.buffer.append("\\\""); break;
+            case '\'' : success = c.buffer.append("\\'"); break;
+            default: {
+                if (byte >= 32 && byte < 127) {
+                    success = c.buffer.append((char)byte);
+                } else {
+                  char digit1 = byte / 16, digit2 = byte % 16;
+                  success = c.buffer.append("\\") &&
+                            c.buffer.append((char)(digit1 < 10 ? digit1 + '0' : digit1 + 'a' - 10)) &&
+                            c.buffer.append((char)(digit2 < 10 ? digit2 + '0' : digit2 + 'a' - 10));
+                }
+            }
+        }
+        if (!success)
+            return false;
+    }
+    return true;
+}
+
+static bool
+RenderExprType(WasmRenderContext& 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: /* we don't care about asm.js types */ return false;
+    }
+}
+
+static bool
+RenderValType(WasmRenderContext& c, ValType type)
+{
+    return RenderExprType(c, ToExprType(type));
+}
+
+static bool
+RenderExpr(WasmRenderContext& c);
+
+static bool
+RenderFullLine(WasmRenderContext& c)
+{
+    if (!RenderIndent(c))
+        return false;
+    if (!RenderExpr(c))
+        return false;
+    return c.buffer.append('\n');
+}
+
+/*****************************************************************************/
+// binary format parsing and rendering
+
+static bool
+RenderNop(WasmRenderContext& c)
+{
+    return c.buffer.append("(nop)");
+}
+
+static bool
+RenderCallWithSig(WasmRenderContext& c, uint32_t sigIndex)
+{
+    const DeclaredSig& sig = c.signatures[sigIndex];
+    for (uint32_t i = 0; i < sig.args().length(); i++) {
+        if (!c.buffer.append(" "))
+            return false;
+        if (!RenderExpr(c))
+            return false;
+    }
+
+    return true;
+}
+
+static bool
+RenderCall(WasmRenderContext& c)
+{
+    uint32_t funcIndex;
+    if (!c.d.readVarU32(&funcIndex))
+        return RenderFail(c, "unable to read import index");
+
+    if (!c.buffer.append("(call $func$"))
+        return false;
+    if (!RenderInt32(c, funcIndex))
+        return false;
+
+    uint32_t sigIndex = c.funcSigs[funcIndex];
+    if (!RenderCallWithSig(c, sigIndex))
+        return false;
+    if (!c.buffer.append(")"))
+        return false;
+    return true;
+}
+
+static bool
+RenderCallImport(WasmRenderContext& c)
+{
+    uint32_t importIndex;
+    if (!c.d.readVarU32(&importIndex))
+        return RenderFail(c, "unable to read import index");
+
+    if (!c.buffer.append("(call_import $import$"))
+        return false;
+    if (!RenderInt32(c, importIndex))
+        return false;
+
+    uint32_t sigIndex = c.importSigs[importIndex];
+    if (!RenderCallWithSig(c, sigIndex))
+        return false;
+    if (!c.buffer.append(")"))
+        return false;
+
+    return true;
+}
+
+static bool
+RenderCallIndirect(WasmRenderContext& c)
+{
+    uint32_t sigIndex;
+    if (!c.d.readVarU32(&sigIndex))
+        return RenderFail(c, "unable to read indirect call signature index");
+
+    if (!c.buffer.append("(call_indirect $type$"))
+        return false;
+    if (!RenderInt32(c, sigIndex))
+        return false;
+
+    if (!c.buffer.append(" "))
+        return false;
+
+    if (!RenderExpr(c))
+        return false;
+
+    if (!c.buffer.append(" "))
+        return false;
+
+    if (!RenderCallWithSig(c, sigIndex))
+        return false;
+    if (!c.buffer.append(")"))
+        return false;
+
+    return true;
+}
+
+static bool
+RenderConstI32(WasmRenderContext& c)
+{
+    if (!c.buffer.append("(i32.const "))
+        return false;
+
+    int32_t i32;
+    if (!c.d.readVarS32(&i32))
+        return RenderFail(c, "unable to read i32.const immediate");
+
+    if (!RenderInt32(c, (uint32_t)i32))
+        return false;
+
+    if (!c.buffer.append(")"))
+        return false;
+    return true;
+}
+
+static bool
+RenderConstI64(WasmRenderContext& c)
+{
+    if (!c.buffer.append("(i64.const "))
+        return false;
+
+    uint64_t u64;
+    if (!c.d.readVarU64(&u64))
+        return RenderFail(c, "unable to read i64.const immediate");
+
+    if (u64 != (uint64_t)(double)u64)
+        return RenderFail(c, "unable to render i64.const immediate");
+
+    if (!RenderDouble(c, u64))
+        return false;
+
+    if (!c.buffer.append(")"))
+        return false;
+    return true;
+}
+
+static bool
+RenderConstF32(WasmRenderContext& c)
+{
+    if (!c.buffer.append("(f32.const "))
+        return false;
+
+    float value;
+    if (!c.d.readFixedF32(&value))
+        return RenderFail(c, "unable to read f32.const immediate");
+
+    if (IsNaN(value)) {
+        const float jsNaN = (float)JS::GenericNaN();
+        if (memcmp(&value, &jsNaN, sizeof(value)) != 0)
+            return RenderFail(c, "NYI: NaN literals with custom payloads");
+    }
+
+    if (!RenderDouble(c, (double)value))
+        return false;
+
+    if (!c.buffer.append(")"))
+        return false;
+    return true;
+}
+
+static bool
+RenderConstF64(WasmRenderContext& c)
+{
+    if (!c.buffer.append("(f64.const "))
+        return false;
+
+    double value;
+    if (!c.d.readFixedF64(&value))
+        return RenderFail(c, "unable to read f64.const immediate");
+
+    if (IsNaN(value)) {
+        const double jsNaN = JS::GenericNaN();
+        if (memcmp(&value, &jsNaN, sizeof(value)) != 0)
+            return RenderFail(c, "NYI: NaN literals with custom payloads");
+    }
+
+    if (!RenderDouble(c, value))
+        return false;
+
+    if (!c.buffer.append(")"))
+        return false;
+    return true;
+}
+
+static bool
+RenderGetLocal(WasmRenderContext& c)
+{
+    uint32_t localIndex;
+    if (!c.d.readVarU32(&localIndex))
+        return RenderFail(c, "unable to read get_local index");
+
+    if (!c.buffer.append("(get_local $var$"))
+        return false;
+    if (!RenderInt32(c, localIndex))
+        return false;
+    if (!c.buffer.append(")"))
+        return false;
+    return true;
+}
+
+static bool
+RenderSetLocal(WasmRenderContext& c)
+{
+    uint32_t localIndex;
+    if (!c.d.readVarU32(&localIndex))
+        return RenderFail(c, "unable to read set_local index");
+
+    if (!c.buffer.append("(set_local $var$"))
+        return false;
+    if (!RenderInt32(c, localIndex))
+        return false;
+    if (!c.buffer.append(" "))
+        return false;
+
+    if (!RenderExpr(c))
+        return false;
+
+    if (!c.buffer.append(")"))
+        return false;
+    return true;
+}
+
+static bool
+RenderBlock(WasmRenderContext& c)
+{
+    if (!c.buffer.append("(block"))
+        return false;
+
+    c.indent++;
+
+    uint32_t numExprs;
+    if (!c.d.readVarU32(&numExprs))
+        return RenderFail(c, "unable to read block's number of expressions");
+
+    for (uint32_t i = 0; i < numExprs; i++) {
+        if (!c.buffer.append(" "))
+            return false;
+        if (!RenderExpr(c))
+            return false;
+    }
+
+    c.indent--;
+    if (!c.buffer.append(")"))
+        return false;
+
+    return true;
+}
+
+static bool
+RenderLoop(WasmRenderContext& c)
+{
+    if (!c.buffer.append("(loop"))
+        return false;
+
+    c.indent++;
+
+    uint32_t numExprs;
+    if (!c.d.readVarU32(&numExprs))
+        return RenderFail(c, "unable to read block's number of expressions");
+
+    for (uint32_t i = 0; i < numExprs; i++) {
+        if (!c.buffer.append(" "))
+            return false;
+        if (!RenderExpr(c))
+            return false;
+    }
+
+    c.indent--;
+    if (!c.buffer.append(")"))
+        return false;
+
+    return true;
+}
+
+static bool
+RenderUnaryOperator(WasmRenderContext& c, Expr expr, ValType argType)
+{
+    if (!c.buffer.append("("))
+      return false;
+
+    switch (expr) {
+      case Expr::I32Clz:     c.buffer.append("i32.clz"); break;
+      case Expr::I32Ctz:     c.buffer.append("i32.ctz"); break;
+      case Expr::I32Popcnt:  c.buffer.append("i32.popcnt"); break;
+      case Expr::I64Clz:     c.buffer.append("i64.clz"); break;
+      case Expr::I64Ctz:     c.buffer.append("i64.ctz"); break;
+      case Expr::I64Popcnt:  c.buffer.append("i64.popcnt"); break;
+      case Expr::F32Abs:     c.buffer.append("f32.abs"); break;
+      case Expr::F32Neg:     c.buffer.append("f32.neg"); break;
+      case Expr::F32Ceil:    c.buffer.append("f32.ceil"); break;
+      case Expr::F32Floor:   c.buffer.append("f32.floor"); break;
+      case Expr::F32Sqrt:    c.buffer.append("f32.sqrt"); break;
+      case Expr::F32Trunc:   c.buffer.append("f32.trunc"); break;
+      case Expr::F32Nearest: c.buffer.append("f32.nearest"); break;
+      case Expr::F64Abs:     c.buffer.append("f64.abs"); break;
+      case Expr::F64Neg:     c.buffer.append("f64.neg"); break;
+      case Expr::F64Ceil:    c.buffer.append("f64.ceil"); break;
+      case Expr::F64Floor:   c.buffer.append("f64.floor"); break;
+      case Expr::F64Sqrt:    c.buffer.append("f64.sqrt"); break;
+      default: return false;
+    }
+
+    if (!c.buffer.append(" "))
+        return false;
+
+    if (!RenderExpr(c))
+        return false;
+
+    if (!c.buffer.append(")"))
+        return false;
+    return true;
+}
+
+static bool
+RenderBinaryOperator(WasmRenderContext& c, Expr expr, ValType argType)
+{
+    if (!c.buffer.append("("))
+      return false;
+
+    switch (expr) {
+      case Expr::I32Add:      c.buffer.append("i32.add"); break;
+      case Expr::I32Sub:      c.buffer.append("i32.sub"); break;
+      case Expr::I32Mul:      c.buffer.append("i32.mul"); break;
+      case Expr::I32DivS:     c.buffer.append("i32.div_s"); break;
+      case Expr::I32DivU:     c.buffer.append("i32.div_u"); break;
+      case Expr::I32RemS:     c.buffer.append("i32.rem_s"); break;
+      case Expr::I32RemU:     c.buffer.append("i32.rem_u"); break;
+      case Expr::I32And:      c.buffer.append("i32.and"); break;
+      case Expr::I32Or:       c.buffer.append("i32.or"); break;
+      case Expr::I32Xor:      c.buffer.append("i32.xor"); break;
+      case Expr::I32Shl:      c.buffer.append("i32.shl"); break;
+      case Expr::I32ShrS:     c.buffer.append("i32.shr_s"); break;
+      case Expr::I32ShrU:     c.buffer.append("i32.shr_u"); break;
+      case Expr::I64Add:      c.buffer.append("i64.add"); break;
+      case Expr::I64Sub:      c.buffer.append("i64.sub"); break;
+      case Expr::I64Mul:      c.buffer.append("i64.mul"); break;
+      case Expr::I64DivS:     c.buffer.append("i64.div_s"); break;
+      case Expr::I64DivU:     c.buffer.append("i64.div_u"); break;
+      case Expr::I64RemS:     c.buffer.append("i64.rem_s"); break;
+      case Expr::I64RemU:     c.buffer.append("i64.rem_u"); break;
+      case Expr::I64And:      c.buffer.append("i64.and"); break;
+      case Expr::I64Or:       c.buffer.append("i64.or"); break;
+      case Expr::I64Xor:      c.buffer.append("i64.xor"); break;
+      case Expr::I64Shl:      c.buffer.append("i64.shl"); break;
+      case Expr::I64ShrS:     c.buffer.append("i64.shr_s"); break;
+      case Expr::I64ShrU:     c.buffer.append("i64.shr_u"); break;
+      case Expr::F32Add:      c.buffer.append("f32.add"); break;
+      case Expr::F32Sub:      c.buffer.append("f32.sub"); break;
+      case Expr::F32Mul:      c.buffer.append("f32.mul"); break;
+      case Expr::F32Div:      c.buffer.append("f32.div"); break;
+      case Expr::F32Min:      c.buffer.append("f32.min"); break;
+      case Expr::F32Max:      c.buffer.append("f32.max"); break;
+      case Expr::F32CopySign: c.buffer.append("f32.copysign"); break;
+      case Expr::F64Add:      c.buffer.append("f64.add"); break;
+      case Expr::F64Sub:      c.buffer.append("f64.sub"); break;
+      case Expr::F64Mul:      c.buffer.append("f64.mul"); break;
+      case Expr::F64Div:      c.buffer.append("f64.div"); break;
+      case Expr::F64Min:      c.buffer.append("f64.min"); break;
+      case Expr::F64Max:      c.buffer.append("f64.mix"); break;
+      default: return false;
+    }
+    if (!c.buffer.append(" "))
+        return false;
+    if (!RenderExpr(c))
+        return false;
+    if (!c.buffer.append(" "))
+        return false;
+    if (!RenderExpr(c))
+        return false;
+    if (!c.buffer.append(")"))
+        return false;
+    return true;
+}
+
+static bool
+RenderComparisonOperator(WasmRenderContext& c, Expr expr, ValType argType)
+{
+    if (!c.buffer.append("("))
+      return false;
+
+    switch (expr) {
+      case Expr::I32Eq:  c.buffer.append("i32.eq"); break;
+      case Expr::I32Ne:  c.buffer.append("i32.ne"); break;
+      case Expr::I32LtS: c.buffer.append("i32.lt_s"); break;
+      case Expr::I32LtU: c.buffer.append("i32.lt_u"); break;
+      case Expr::I32LeS: c.buffer.append("i32.le_s"); break;
+      case Expr::I32LeU: c.buffer.append("i32.le_u"); break;
+      case Expr::I32GtS: c.buffer.append("i32.gt_s"); break;
+      case Expr::I32GtU: c.buffer.append("i32.gt_u"); break;
+      case Expr::I32GeS: c.buffer.append("i32.ge_s"); break;
+      case Expr::I32GeU: c.buffer.append("i32.ge_u"); break;
+      case Expr::I64Eq:  c.buffer.append("i64.eq"); break;
+      case Expr::I64Ne:  c.buffer.append("i64.ne"); break;
+      case Expr::I64LtS: c.buffer.append("i64.lt_s"); break;
+      case Expr::I64LtU: c.buffer.append("i64.lt_u"); break;
+      case Expr::I64LeS: c.buffer.append("i64.le_s"); break;
+      case Expr::I64LeU: c.buffer.append("i64.le_u"); break;
+      case Expr::I64GtS: c.buffer.append("i64.gt_s"); break;
+      case Expr::I64GtU: c.buffer.append("i64.gt_u"); break;
+      case Expr::I64GeS: c.buffer.append("i64.ge_s"); break;
+      case Expr::I64GeU: c.buffer.append("i64.ge_u"); break;
+      case Expr::F32Eq:  c.buffer.append("f32.eq"); break;
+      case Expr::F32Ne:  c.buffer.append("f32.ne"); break;
+      case Expr::F32Lt:  c.buffer.append("f32.lt"); break;
+      case Expr::F32Le:  c.buffer.append("f32.le"); break;
+      case Expr::F32Gt:  c.buffer.append("f32.gt"); break;
+      case Expr::F32Ge:  c.buffer.append("f32.ge"); break;
+      case Expr::F64Eq:  c.buffer.append("f64.eq"); break;
+      case Expr::F64Ne:  c.buffer.append("f64.ne"); break;
+      case Expr::F64Lt:  c.buffer.append("f64.lt"); break;
+      case Expr::F64Le:  c.buffer.append("f64.le"); break;
+      case Expr::F64Gt:  c.buffer.append("f64.gt"); break;
+      case Expr::F64Ge:  c.buffer.append("f64.ge"); break;
+      default: return false;
+    }
+
+    if (!c.buffer.append(" "))
+        return false;
+    if (!RenderExpr(c))
+        return false;
+    if (!c.buffer.append(" "))
+        return false;
+    if (!RenderExpr(c))
+        return false;
+    if (!c.buffer.append(")"))
+        return false;
+    return true;
+}
+
+static bool
+RenderConversionOperator(WasmRenderContext& c, Expr expr, ValType to, ValType argType)
+{
+    if (!c.buffer.append("("))
+      return false;
+
+    switch (expr) {
+      case Expr::I32WrapI64:        c.buffer.append("i32.warp/i64"); break;
+      case Expr::I32TruncSF32:      c.buffer.append("i32.trunc_s/f32"); break;
+      case Expr::I32TruncUF32:      c.buffer.append("i32.trunc_u/f32"); break;
+      case Expr::I32ReinterpretF32: c.buffer.append("i32.reinterpret/f32"); break;
+      case Expr::I32TruncSF64:      c.buffer.append("i32.trunc_s/f64"); break;
+      case Expr::I32TruncUF64:      c.buffer.append("i32.trunc_u/f64"); break;
+      case Expr::I64ExtendSI32:     c.buffer.append("i64.extend_s/i32"); break;
+      case Expr::I64ExtendUI32:     c.buffer.append("i64.extend_u/i32"); break;
+      case Expr::I64TruncSF32:      c.buffer.append("i64.trunc_s/f32"); break;
+      case Expr::I64TruncUF32:      c.buffer.append("i64.trunc_u/f32"); break;
+      case Expr::I64TruncSF64:      c.buffer.append("i64.trunc_s/f64"); break;
+      case Expr::I64TruncUF64:      c.buffer.append("i64.trunc_u/f64"); break;
+      case Expr::I64ReinterpretF64: c.buffer.append("i64.reinterpret/f64"); break;
+      case Expr::F32ConvertSI32:    c.buffer.append("f32.convert_s/i32"); break;
+      case Expr::F32ConvertUI32:    c.buffer.append("f32.convert_u/i32"); break;
+      case Expr::F32ReinterpretI32: c.buffer.append("f32.reinterpret/i32"); break;
+      case Expr::F32ConvertSI64:    c.buffer.append("f32.convert_s/i64"); break;
+      case Expr::F32ConvertUI64:    c.buffer.append("f32.convert_u/i64"); break;
+      case Expr::F32DemoteF64:      c.buffer.append("f32.demote/f64"); break;
+      case Expr::F64ConvertSI32:    c.buffer.append("f64.convert_s/i32"); break;
+      case Expr::F64ConvertUI32:    c.buffer.append("f64.convert_u/i32"); break;
+      case Expr::F64ConvertSI64:    c.buffer.append("f64.convert_s/i64"); break;
+      case Expr::F64ConvertUI64:    c.buffer.append("f64.convert_u/i64"); break;
+      case Expr::F64ReinterpretI64: c.buffer.append("f64.reinterpret/i64"); break;
+      case Expr::F64PromoteF32:     c.buffer.append("f64.promote/f32"); break;
+      default: return false;
+    }
+
+    if (!c.buffer.append(" "))
+        return false;
+
+    if (!RenderExpr(c))
+        return false;
+
+    if (!c.buffer.append(")"))
+        return false;
+    return true;
+}
+
+static bool
+RenderIfElse(WasmRenderContext& c, bool hasElse)
+{
+    if (!c.buffer.append("(if "))
+        return false;
+    if (!RenderExpr(c))
+        return false;
+
+    if (!c.buffer.append(" "))
+        return false;
+
+    c.indent++;
+    if (!RenderExpr(c))
+        return false;
+    c.indent--;
+
+    if (hasElse) {
+        if (!c.buffer.append(" "))
+            return false;
+
+        c.indent++;
+        if (!RenderExpr(c))
+            return false;
+        c.indent--;
+    }
+
+    if (!c.buffer.append(")"))
+        return false;
+
+    return true;
+}
+
+static bool
+RenderLoadStoreAddress(WasmRenderContext& c)
+{
+    uint32_t flags;
+    if (!c.d.readVarU32(&flags))
+        return RenderFail(c, "expected memory access flags");
+
+    uint32_t offset;
+    if (!c.d.readVarU32(&offset))
+        return RenderFail(c, "expected memory access offset");
+
+    if (offset != 0) {
+      if (!c.buffer.append(" offset="))
+          return false;
+      if (!RenderInt32(c, offset))
+          return false;
+    }
+
+    if (!c.buffer.append(" "))
+        return false;
+
+    if (!RenderExpr(c))
+        return false;
+
+    return true;
+}
+
+static bool
+RenderLoad(WasmRenderContext& c, Expr expr, ValType loadType)
+{
+    if (!c.buffer.append("("))
+        return false;
+    if (!RenderValType(c, loadType))
+        return false;
+    if (!c.buffer.append(".load"))
+        return false;
+    switch (expr) {
+      case Expr::I32Load8S:
+      case Expr::I64Load8S:
+        if (!c.buffer.append("8_s"))
+            return false;
+        break;
+      case Expr::I32Load8U:
+      case Expr::I64Load8U:
+        if (!c.buffer.append("8_u"))
+            return false;
+        break;
+      case Expr::I32Load16S:
+      case Expr::I64Load16S:
+        if (!c.buffer.append("16_s"))
+            return false;
+        break;
+      case Expr::I32Load16U:
+      case Expr::I64Load16U:
+        if (!c.buffer.append("16_u"))
+            return false;
+        break;
+      case Expr::I64Load32S:
+        if (!c.buffer.append("32_s"))
+            return false;
+        break;
+      case Expr::I64Load32U:
+        if (!c.buffer.append("32_u"))
+            return false;
+        break;
+      default: /* rest of them have not prefix */
+        break;
+    }
+
+    if (!RenderLoadStoreAddress(c))
+        return false;
+
+    if (!c.buffer.append(")"))
+        return false;
+    return true;
+}
+
+static bool
+RenderStore(WasmRenderContext& c, Expr expr, ValType storeType)
+{
+    if (!c.buffer.append("("))
+        return false;
+    if (!RenderValType(c, storeType))
+        return false;
+    if (!c.buffer.append(".store"))
+        return false;
+    switch (expr) {
+      case Expr::I32Store8:
+      case Expr::I64Store8:
+        if (!c.buffer.append("8"))
+            return false;
+        break;
+      case Expr::I32Store16:
+      case Expr::I64Store16:
+        if (!c.buffer.append("16"))
+            return false;
+        break;
+      case Expr::I64Store32:
+        if (!c.buffer.append("32"))
+            return false;
+        break;
+      default: /* rest of them have not prefix */
+        break;
+    }
+
+    if (!RenderLoadStoreAddress(c))
+        return false;
+
+    if (!c.buffer.append(" "))
+        return false;
+
+    if (!RenderExpr(c))
+        return false;
+
+    if (!c.buffer.append(")"))
+        return false;
+
+    return true;
+}
+
+static bool
+RenderBranch(WasmRenderContext& c, Expr expr)
+{
+    uint32_t relativeDepth;
+    if (!c.d.readVarU32(&relativeDepth))
+        return RenderFail(c, "expected relative depth");
+
+    Expr value;
+    if (!c.d.readExpr(&value))
+        return RenderFail(c, "expected branch value");
+    if (value != Expr::Nop)
+        return RenderFail(c, "NYI: branch values");
+
+    if (expr == Expr::BrIf) {
+        if (!c.buffer.append("(br_if "))
+          return false;
+
+        if (!RenderInt32(c, relativeDepth))
+            return false;
+
+        if (!c.buffer.append(" "))
+            return false;
+
+        if (!RenderExpr(c))
+            return false;
+    } else {
+        if (!c.buffer.append("(br "))
+            return false;
+        if (!RenderInt32(c, relativeDepth))
+            return false;
+    }
+    if (!c.buffer.append(")"))
+        return false;
+    return true;
+}
+
+static bool
+RenderBrTable(WasmRenderContext& c)
+{
+    uint32_t tableLength;
+    if (!c.d.readVarU32(&tableLength))
+        return false;
+
+    if (!c.buffer.append("(br_table "))
+        return false;
+
+    for (uint32_t i = 0; i < tableLength; i++) {
+        uint32_t depth;
+        if (!c.d.readFixedU32(&depth))
+            return RenderFail(c, "missing br_table entry");
+
+        if (!RenderInt32(c, depth))
+            return false;
+
+        if (!c.buffer.append(" "))
+            return false;
+    }
+
+    uint32_t defaultDepth;
+    if (!c.d.readFixedU32(&defaultDepth))
+        return RenderFail(c, "expected default relative depth");
+
+    if (!RenderInt32(c, defaultDepth))
+        return false;
+
+    if (!c.buffer.append(" "))
+        return false;
+
+    if (!RenderExpr(c))
+        return false;
+
+    if (!c.buffer.append(")"))
+        return false;
+
+    return true;
+}
+
+static bool
+RenderReturn(WasmRenderContext& c)
+{
+    if (!c.buffer.append("(return"))
+        return false;
+
+    if (c.has_return) {
+        if (!c.buffer.append(" "))
+            return false;
+        if (!RenderExpr(c))
+            return false;
+    }
+
+    if (!c.buffer.append(")"))
+        return false;
+    return true;
+}
+
+static bool
+RenderExpr(WasmRenderContext& c)
+{
+    Expr expr;
+    if (!c.d.readExpr(&expr))
+        return RenderFail(c, "unable to read expression");
+
+    switch (expr) {
+      case Expr::Nop:
+        return RenderNop(c);
+      case Expr::Call:
+        return RenderCall(c);
+      case Expr::CallImport:
+        return RenderCallImport(c);
+      case Expr::CallIndirect:
+        return RenderCallIndirect(c);
+      case Expr::I32Const:
+        return RenderConstI32(c);
+      case Expr::I64Const:
+        return RenderConstI64(c);
+      case Expr::F32Const:
+        return RenderConstF32(c);
+      case Expr::F64Const:
+        return RenderConstF64(c);
+      case Expr::GetLocal:
+        return RenderGetLocal(c);
+      case Expr::SetLocal:
+        return RenderSetLocal(c);
+      case Expr::Block:
+        return RenderBlock(c);
+      case Expr::Loop:
+        return RenderLoop(c);
+      case Expr::If:
+        return RenderIfElse(c, false);
+      case Expr::IfElse:
+        return RenderIfElse(c, true);
+      case Expr::I32Clz:
+      case Expr::I32Ctz:
+      case Expr::I32Popcnt:
+        return RenderUnaryOperator(c, expr, ValType::I32);
+      case Expr::I64Clz:
+      case Expr::I64Ctz:
+      case Expr::I64Popcnt:
+        return RenderFail(c, "NYI: i64") &&
+            RenderUnaryOperator(c, expr, ValType::I64);
+      case Expr::F32Abs:
+      case Expr::F32Neg:
+      case Expr::F32Ceil:
+      case Expr::F32Floor:
+      case Expr::F32Sqrt:
+        return RenderUnaryOperator(c, expr, ValType::F32);
+      case Expr::F32Trunc:
+        return RenderFail(c, "NYI: trunc");
+      case Expr::F32Nearest:
+        return RenderFail(c, "NYI: nearest");
+      case Expr::F64Abs:
+      case Expr::F64Neg:
+      case Expr::F64Ceil:
+      case Expr::F64Floor:
+      case Expr::F64Sqrt:
+        return RenderUnaryOperator(c, expr, ValType::F64);
+      case Expr::F64Trunc:
+        return RenderFail(c, "NYI: trunc");
+      case Expr::F64Nearest:
+        return RenderFail(c, "NYI: nearest");
+      case Expr::I32Add:
+      case Expr::I32Sub:
+      case Expr::I32Mul:
+      case Expr::I32DivS:
+      case Expr::I32DivU:
+      case Expr::I32RemS:
+      case Expr::I32RemU:
+      case Expr::I32And:
+      case Expr::I32Or:
+      case Expr::I32Xor:
+      case Expr::I32Shl:
+      case Expr::I32ShrS:
+      case Expr::I32ShrU:
+        return RenderBinaryOperator(c, expr, ValType::I32);
+      case Expr::I64Add:
+      case Expr::I64Sub:
+      case Expr::I64Mul:
+      case Expr::I64DivS:
+      case Expr::I64DivU:
+      case Expr::I64RemS:
+      case Expr::I64RemU:
+      case Expr::I64And:
+      case Expr::I64Or:
+      case Expr::I64Xor:
+      case Expr::I64Shl:
+      case Expr::I64ShrS:
+      case Expr::I64ShrU:
+        return RenderBinaryOperator(c, expr, ValType::I64);
+      case Expr::F32Add:
+      case Expr::F32Sub:
+      case Expr::F32Mul:
+      case Expr::F32Div:
+      case Expr::F32Min:
+      case Expr::F32Max:
+        return RenderBinaryOperator(c, expr, ValType::F32);
+      case Expr::F32CopySign:
+        return RenderFail(c, "NYI: copysign");
+      case Expr::F64Add:
+      case Expr::F64Sub:
+      case Expr::F64Mul:
+      case Expr::F64Div:
+      case Expr::F64Min:
+      case Expr::F64Max:
+        return RenderBinaryOperator(c, expr, ValType::F64);
+      case Expr::F64CopySign:
+        return RenderFail(c, "NYI: copysign");
+      case Expr::I32Eq:
+      case Expr::I32Ne:
+      case Expr::I32LtS:
+      case Expr::I32LtU:
+      case Expr::I32LeS:
+      case Expr::I32LeU:
+      case Expr::I32GtS:
+      case Expr::I32GtU:
+      case Expr::I32GeS:
+      case Expr::I32GeU:
+        return RenderComparisonOperator(c, expr, ValType::I32);
+      case Expr::I64Eq:
+      case Expr::I64Ne:
+      case Expr::I64LtS:
+      case Expr::I64LtU:
+      case Expr::I64LeS:
+      case Expr::I64LeU:
+      case Expr::I64GtS:
+      case Expr::I64GtU:
+      case Expr::I64GeS:
+      case Expr::I64GeU:
+        return RenderComparisonOperator(c, expr, ValType::I64);
+      case Expr::F32Eq:
+      case Expr::F32Ne:
+      case Expr::F32Lt:
+      case Expr::F32Le:
+      case Expr::F32Gt:
+      case Expr::F32Ge:
+        return RenderComparisonOperator(c, expr, ValType::F32);
+      case Expr::F64Eq:
+      case Expr::F64Ne:
+      case Expr::F64Lt:
+      case Expr::F64Le:
+      case Expr::F64Gt:
+      case Expr::F64Ge:
+        return RenderComparisonOperator(c, expr, ValType::F64);
+      case Expr::I32WrapI64:
+        return RenderConversionOperator(c, expr, ValType::I32, ValType::I64);
+      case Expr::I32TruncSF32:
+      case Expr::I32TruncUF32:
+        return RenderConversionOperator(c, expr, ValType::I32, ValType::F32);
+      case Expr::I32ReinterpretF32:
+        return RenderFail(c, "NYI: reinterpret");
+      case Expr::I32TruncSF64:
+      case Expr::I32TruncUF64:
+        return RenderConversionOperator(c, expr, ValType::I32, ValType::F64);
+      case Expr::I64ExtendSI32:
+      case Expr::I64ExtendUI32:
+        return RenderConversionOperator(c, expr, ValType::I64, ValType::I32);
+      case Expr::I64TruncSF32:
+      case Expr::I64TruncUF32:
+        return RenderConversionOperator(c, expr, ValType::I64, ValType::F32);
+      case Expr::I64TruncSF64:
+      case Expr::I64TruncUF64:
+        return RenderConversionOperator(c, expr, ValType::I64, ValType::F64);
+      case Expr::I64ReinterpretF64:
+        return RenderFail(c, "NYI: i64");
+      case Expr::F32ConvertSI32:
+      case Expr::F32ConvertUI32:
+        return RenderConversionOperator(c, expr, ValType::F32, ValType::I32);
+      case Expr::F32ReinterpretI32:
+        return RenderFail(c, "NYI: reinterpret");
+      case Expr::F32ConvertSI64:
+      case Expr::F32ConvertUI64:
+        return RenderFail(c, "NYI: i64") &&
+               RenderConversionOperator(c, expr, ValType::F32, ValType::I64);
+      case Expr::F32DemoteF64:
+        return RenderConversionOperator(c, expr, ValType::F32, ValType::I64);
+      case Expr::F64ConvertSI32:
+      case Expr::F64ConvertUI32:
+        return RenderConversionOperator(c, expr, ValType::F64, ValType::I32);
+      case Expr::F64ConvertSI64:
+      case Expr::F64ConvertUI64:
+      case Expr::F64ReinterpretI64:
+        return RenderFail(c, "NYI: i64") &&
+               RenderConversionOperator(c, expr, ValType::F64, ValType::I64);
+      case Expr::F64PromoteF32:
+        return RenderConversionOperator(c, expr, ValType::F64, ValType::F32);
+      case Expr::I32Load8S:
+      case Expr::I32Load8U:
+      case Expr::I32Load16S:
+      case Expr::I32Load16U:
+      case Expr::I32Load:
+        return RenderLoad(c, expr, ValType::I32);
+      case Expr::I64Load:
+      case Expr::I64Load8S:
+      case Expr::I64Load8U:
+      case Expr::I64Load16S:
+      case Expr::I64Load16U:
+      case Expr::I64Load32S:
+      case Expr::I64Load32U:
+        return RenderFail(c, "NYI: i64") &&
+               RenderLoad(c, expr, ValType::I64);
+      case Expr::F32Load:
+        return RenderLoad(c, expr, ValType::F32);
+      case Expr::F64Load:
+        return RenderLoad(c, expr, ValType::F64);
+      case Expr::I32Store8:
+        return RenderStore(c, expr, ValType::I32);
+      case Expr::I32Store16:
+        return RenderStore(c, expr, ValType::I32);
+      case Expr::I32Store:
+        return RenderStore(c, expr, ValType::I32);
+      case Expr::I64Store:
+      case Expr::I64Store8:
+      case Expr::I64Store16:
+      case Expr::I64Store32:
+        return RenderFail(c, "NYI: i64") &&
+               RenderStore(c, expr, ValType::I64);
+      case Expr::F32Store:
+        return RenderStore(c, expr, ValType::F32);
+      case Expr::F64Store:
+        return RenderStore(c, expr, ValType::F64);
+      case Expr::Br:
+        return RenderBranch(c, expr);
+      case Expr::BrIf:
+        return RenderBranch(c, expr);
+      case Expr::BrTable:
+        return RenderBrTable(c);
+      case Expr::Return:
+        return RenderReturn(c);
+      default:
+        // Note: it's important not to remove this default since readExpr()
+        // can return Expr values for which there is no enumerator.
+        break;
+    }
+
+    return RenderFail(c, "bad expression code");
+}
+
+static bool
+RenderSignature(WasmRenderContext& c, const DeclaredSig& sig, bool varAssignment)
+{
+    uint32_t paramsNum = sig.args().length();
+
+    if (varAssignment) {
+      for (uint32_t i = 0; i < paramsNum; i++) {
+          if (!c.buffer.append(" (param "))
+              return false;
+          if (!c.buffer.append("$var$"))
+              return false;
+          if (!RenderInt32(c, i))
+              return false;
+          if (!c.buffer.append(" "))
+              return false;
+          ValType arg = sig.arg(i);
+          if (!RenderValType(c, arg))
+              return false;
+          if (!c.buffer.append(")"))
+              return false;
+      }
+    } else if (paramsNum > 0) {
+      if (!c.buffer.append(" (param"))
+          return false;
+      for (uint32_t i = 0; i < paramsNum; i++) {
+          c.buffer.append(" ");
+          ValType arg = sig.arg(i);
+          if (!RenderValType(c, arg))
+              return false;
+      }
+      if (!c.buffer.append(")"))
+          return false;
+    }
+    if (sig.ret() != ExprType::Void) {
+        if (!c.buffer.append(" (result "))
+            return false;
+        if (!RenderExprType(c, sig.ret()))
+            return false;
+        if (!c.buffer.append(")"))
+            return false;
+    }
+    return true;
+}
+
+static bool
+RenderSignatures(WasmRenderContext& c)
+{
+    uint32_t sectionStart;
+    if (!c.d.startSection(SignaturesId, &sectionStart))
+        return RenderFail(c, "failed to start section");
+    if (sectionStart == Decoder::NotStarted)
+        return true;
+
+    uint32_t numSigs;
+    if (!c.d.readVarU32(&numSigs))
+        return RenderFail(c, "expected number of signatures");
+
+    if (!c.signatures.resize(numSigs))
+        return false;
+
+    for (uint32_t sigIndex = 0; sigIndex < numSigs; sigIndex++) {
+        uint32_t numArgs;
+        if (!c.d.readVarU32(&numArgs))
+            return RenderFail(c, "bad number of signature args");
+
+        ValTypeVector args;
+        if (!args.resize(numArgs))
+            return false;
+
+        ExprType result;
+        if (!c.d.readExprType(&result))
+            return RenderFail(c, "bad expression type");
+
+        for (uint32_t i = 0; i < numArgs; i++) {
+            ValType arg;
+            if (!c.d.readValType(&arg))
+                return RenderFail(c, "bad value type");
+            args[i] = arg;
+        }
+
+        c.signatures[sigIndex] = Sig(Move(args), result);
+
+        if (!RenderIndent(c))
+            return false;
+        if (!c.buffer.append("(type $type$"))
+            return false;
+        if (!RenderInt32(c, sigIndex))
+            return false;
+        if (!c.buffer.append(" (func"))
+            return false;
+        if (!RenderSignature(c, c.signatures[sigIndex], false))
+            return false;
+        if (!c.buffer.append("))\n"))
+            return false;
+    }
+
+    if (!c.d.finishSection(sectionStart))
+        return RenderFail(c, "decls section byte size mismatch");
+
+    return true;
+}
+
+static bool
+RenderFunctionSignatures(WasmRenderContext& c)
+{
+    uint32_t sectionStart;
+    if (!c.d.startSection(FunctionSignaturesId, &sectionStart))
+        return RenderFail(c, "failed to start section");
+    if (sectionStart == Decoder::NotStarted)
+        return true;
+
+    uint32_t numDecls;
+    if (!c.d.readVarU32(&numDecls))
+        return RenderFail(c, "expected number of declarations");
+
+    if (!c.funcSigs.resize(numDecls))
+        return false;
+
+    for (uint32_t i = 0; i < numDecls; i++) {
+        uint32_t sigIndex;
+        if (!c.d.readVarU32(&sigIndex))
+            return RenderFail(c, "expected signature index");
+        c.funcSigs[i] = sigIndex;
+    }
+
+    if (!c.d.finishSection(sectionStart))
+        return RenderFail(c, "decls section byte size mismatch");
+
+    return true;
+}
+
+static bool
+RenderFunctionTable(WasmRenderContext& c)
+{
+    uint32_t sectionStart;
+    if (!c.d.startSection(FunctionTableId, &sectionStart))
+        return RenderFail(c, "failed to start section");
+    if (sectionStart == Decoder::NotStarted)
+        return true;
+
+    uint32_t numTableElems;
+    if (!c.d.readVarU32(&numTableElems))
+        return RenderFail(c, "expected number of table elems");
+
+    Uint32Vector elems;
+    if (!elems.resize(numTableElems))
+        return false;
+
+    for (uint32_t i = 0; i < numTableElems; i++) {
+        uint32_t funcIndex;
+        if (!c.d.readVarU32(&funcIndex))
+            return RenderFail(c, "expected table element");
+
+        elems[i] = funcIndex;
+    }
+
+    if (!c.d.finishSection(sectionStart))
+        return RenderFail(c, "table section byte size mismatch");
+
+    return true;
+}
+
+static bool
+RenderImport(WasmRenderContext& c, uint32_t importIndex)
+{
+    uint32_t sigIndex;
+    if (!c.d.readVarU32(&sigIndex))
+        return RenderFail(c, "expected signature index");
+
+    c.importSigs[importIndex] = sigIndex;
+
+    if (!RenderIndent(c))
+        return false;
+    if (!c.buffer.append("(import $import$"))
+        return false;
+    if (!RenderInt32(c, importIndex))
+        return false;
+    if (!c.buffer.append(" \""))
+        return false;
+
+    Bytes moduleName;
+    if (!c.d.readBytes(&moduleName))
+        return RenderFail(c, "expected import module name");
+
+    if (moduleName.empty())
+        return RenderFail(c, "module name cannot be empty");
+
+    if (!RenderString(c, moduleName.begin(), moduleName.length()))
+        return false;
+
+    if (!c.buffer.append("\" \""))
+        return false;
+
+    Bytes funcName;
+    if (!c.d.readBytes(&funcName))
+        return RenderFail(c, "expected import func name");
+
+    if (!RenderString(c, funcName.begin(), funcName.length()))
+        return false;
+
+    if (!c.buffer.append("\""))
+        return false;
+
+    if (!RenderSignature(c, c.signatures[sigIndex], false))
+        return false;
+    if (!c.buffer.append(")\n"))
+        return false;
+
+    return true;
+}
+
+
+static bool
+RenderImportTable(WasmRenderContext& c)
+{
+    uint32_t sectionStart;
+    if (!c.d.startSection(ImportTableId, &sectionStart))
+        return RenderFail(c, "failed to start section");
+    if (sectionStart == Decoder::NotStarted)
+        return true;
+
+    uint32_t numImports;
+    if (!c.d.readVarU32(&numImports))
+        return RenderFail(c, "failed to read number of imports");
+
+    if (!c.importSigs.resize(numImports))
+        return false;
+
+    for (uint32_t i = 0; i < numImports; i++) {
+        if (!RenderImport(c, i))
+            return false;
+    }
+
+    if (!c.d.finishSection(sectionStart))
+        return RenderFail(c, "import section byte size mismatch");
+
+    return true;
+}
+
+static bool
+RenderMemory(WasmRenderContext& c, uint32_t* memInitial, uint32_t* memMax)
+{
+    *memInitial = 0;
+    *memMax = 0;
+
+    uint32_t sectionStart;
+    if (!c.d.startSection(MemoryId, &sectionStart))
+        return RenderFail(c, "failed to start section");
+    if (sectionStart == Decoder::NotStarted)
+        return true;
+
+    uint32_t initialSizePages;
+    if (!c.d.readVarU32(&initialSizePages))
+        return RenderFail(c, "expected initial memory size");
+    *memInitial = initialSizePages;
+
+    uint32_t maxSizePages;
+    if (!c.d.readVarU32(&maxSizePages))
+        return RenderFail(c, "expected initial memory size");
+    *memMax = maxSizePages;
+
+    uint8_t exported;
+    if (!c.d.readFixedU8(&exported))
+        return RenderFail(c, "expected exported byte");
+
+    if (!c.d.finishSection(sectionStart))
+        return RenderFail(c, "memory section byte size mismatch");
+
+    return true;
+}
+
+static bool
+RenderExportName(WasmRenderContext& c)
+{
+    Bytes fieldBytes;
+    if (!c.d.readBytes(&fieldBytes)) {
+        RenderFail(c, "expected export name");
+        return false;
+    }
+
+    if (!RenderString(c, fieldBytes.begin(), fieldBytes.length()))
+        return false;
+
+    return true;
+}
+
+static bool
+RenderFunctionExport(WasmRenderContext& c)
+{
+    uint32_t funcIndex;
+    if (!c.d.readVarU32(&funcIndex))
+        return RenderFail(c, "expected export internal index");
+
+    if (!RenderIndent(c))
+        return false;
+    if (!c.buffer.append("(export \""))
+        return false;
+    if (!RenderExportName(c))
+        return false;
+    if (!c.buffer.append("\" "))
+        return false;
+    if (!c.buffer.append("$func$"))
+        return false;
+    if (!RenderInt32(c, funcIndex))
+        return false;
+    if (!c.buffer.append(")\n"))
+        return false;
+
+    return true;
+}
+
+static bool
+RenderExportTable(WasmRenderContext& c)
+{
+    uint32_t sectionStart;
+    if (!c.d.startSection(ExportTableId, &sectionStart))
+        return RenderFail(c, "failed to start section");
+    if (sectionStart == Decoder::NotStarted)
+        return true;
+
+    uint32_t numExports;
+    if (!c.d.readVarU32(&numExports))
+        return RenderFail(c, "failed to read number of exports");
+
+    for (uint32_t i = 0; i < numExports; i++) {
+        if (!RenderFunctionExport(c))
+            return false;
+    }
+
+    if (!c.d.finishSection(sectionStart))
+        return RenderFail(c, "export section byte size mismatch");
+
+    return true;
+}
+
+static bool
+RenderFunctionBody(WasmRenderContext& c, uint32_t funcIndex, uint32_t paramsNum)
+{
+    uint32_t bodySize;
+    if (!c.d.readVarU32(&bodySize))
+        return RenderFail(c, "expected number of function body bytes");
+
+    if (c.d.bytesRemain() < bodySize)
+        return RenderFail(c, "function body length too big");
+
+    const uint8_t* bodyBegin = c.d.currentPosition();
+    const uint8_t* bodyEnd = bodyBegin + bodySize;
+
+    c.indent++;
+
+    ValTypeVector locals;
+    if (!DecodeLocalEntries(c.d, &locals))
+        return RenderFail(c, "failed decoding local entries");
+
+    uint32_t localsNum = locals.length();
+    if (localsNum > 0) {
+        if (!RenderIndent(c))
+            return false;
+        for (uint32_t i = 0; i < localsNum; i++) {
+            if (!c.buffer.append("(local "))
+                return false;
+            if (!c.buffer.append("$var$"))
+                return false;
+            if (!RenderInt32(c, i + paramsNum))
+                return false;
+            ValType local = locals[i];
+            if (!c.buffer.append(" "))
+                return false;
+            if (!RenderValType(c, local))
+                return false;
+            if (!c.buffer.append(") "))
+                return false;
+        }
+        if (!c.buffer.append("\n"))
+            return false;
+    }
+
+    while (c.d.currentPosition() < bodyEnd) {
+      if (!RenderFullLine(c))
+          return false;
+    }
+
+    if (c.d.currentPosition() != bodyEnd)
+        return RenderFail(c, "function body length mismatch");
+
+    c.indent--;
+
+    return true;
+}
+
+static bool
+RenderFunctionBodies(WasmRenderContext& c)
+{
+    uint32_t sectionStart;
+    if (!c.d.startSection(FunctionBodiesId, &sectionStart))
+        return RenderFail(c, "failed to start section");
+
+    if (sectionStart == Decoder::NotStarted)
+        return true;
+
+    uint32_t numFuncSigs = c.funcSigs.length();
+
+    uint32_t numFuncBodies;
+    if (!c.d.readVarU32(&numFuncBodies))
+        return RenderFail(c, "expected function body count");
+    if (numFuncBodies != numFuncSigs)
+        return RenderFail(c, "function body count does not match function signature count");
+    for (uint32_t funcIndex = 0; funcIndex < numFuncBodies; funcIndex++) {
+        uint32_t sigIndex = c.funcSigs[funcIndex];
+        const DeclaredSig& sig = c.signatures[sigIndex];
+
+        if (!RenderIndent(c))
+            return false;
+        if (!c.buffer.append("(func $func$"))
+            return false;
+        if (!RenderInt32(c, funcIndex))
+            return false;
+
+        if (!RenderSignature(c, sig, true))
+            return false;
+        if (!c.buffer.append("\n"))
+            return false;
+
+        c.has_return = sig.ret() != ExprType::Void;
+
+        c.indent++;
+        if (!RenderFunctionBody(c, funcIndex, sig.args().length()))
+            return false;
+        c.indent--;
+        if (!RenderIndent(c))
+            return false;
+        if (!c.buffer.append(")\n"))
+            return false;
+    }
+
+    if (!c.d.finishSection(sectionStart))
+        return RenderFail(c, "function section byte size mismatch");
+
+   return true;
+}
+
+
+static bool
+RenderDataSegments(WasmRenderContext& c, uint32_t memInitial, uint32_t memMax)
+{
+    if (!RenderIndent(c))
+        return false;
+    if (!c.buffer.append("(memory "))
+        return false;
+    if (!RenderInt32(c, memInitial))
+       return false;
+    if (memMax != 0 && ~memMax != 0) {
+        if (!c.buffer.append(" "))
+            return false;
+        if (!RenderInt32(c, memMax))
+            return false;
+    }
+
+    c.indent++;
+    uint32_t sectionStart;
+    if (!c.d.startSection(DataSegmentsId, &sectionStart))
+        return RenderFail(c, "failed to start section");
+    if (sectionStart == Decoder::NotStarted) {
+      if (!c.buffer.append(")\n"))
+          return false;
+      return true;
+    }
+    if (!c.buffer.append("\n"))
+        return false;
+
+    uint32_t numSegments;
+    if (!c.d.readVarU32(&numSegments))
+        return RenderFail(c, "failed to read number of data segments");
+
+    for (uint32_t i = 0; i < numSegments; i++) {
+        uint32_t dstOffset;
+        if (!c.d.readVarU32(&dstOffset))
+            return RenderFail(c, "expected segment destination offset");
+
+        if (!RenderIndent(c))
+            return false;
+        if (!c.buffer.append("(segment "))
+           return false;
+        if (!RenderInt32(c, dstOffset))
+           return false;
+        if (!c.buffer.append(" \""))
+           return false;
+
+        uint32_t numBytes;
+        if (!c.d.readVarU32(&numBytes))
+            return RenderFail(c, "expected segment size");
+
+        const uint8_t* src;
+        if (!c.d.readBytesRaw(numBytes, &src))
+            return RenderFail(c, "data segment shorter than declared");
+
+        RenderString(c, src, numBytes);
+
+        if (!c.buffer.append("\")\n"))
+           return false;
+    }
+
+    if (!c.d.finishSection(sectionStart))
+        return RenderFail(c, "data section byte size mismatch");
+
+    c.indent--;
+    if (!c.buffer.append(")\n"))
+        return false;
+
+    return true;
+}
+
+static bool
+RenderModule(WasmRenderContext& c)
+{
+    uint32_t u32;
+    if (!c.d.readFixedU32(&u32) || u32 != MagicNumber)
+        return RenderFail(c, "failed to match magic number");
+
+    if (!c.d.readFixedU32(&u32) || u32 != EncodingVersion)
+        return RenderFail(c, "failed to match binary version");
+
+    if (!c.buffer.append("(module\n"))
+        return false;
+
+    c.indent++;
+
+    if (!RenderSignatures(c))
+        return false;
+
+    if (!RenderImportTable(c))
+        return false;
+
+    if (!RenderFunctionSignatures(c))
+        return false;
+
+    if (!RenderFunctionTable(c))
+        return false;
+
+    uint32_t memInitial, memMax;
+    if (!RenderMemory(c, &memInitial, &memMax))
+        return false;
+
+    if (!RenderExportTable(c))
+        return false;
+
+    if (!RenderFunctionBodies(c))
+        return false;
+
+    if (!RenderDataSegments(c, memInitial, memMax))
+        return false;
+
+    c.indent--;
+
+    if (!c.buffer.append(")"))
+        return false;
+
+    return true;
+}
+
+/*****************************************************************************/
+// Top-level functions
+
+bool
+wasm::BinaryToText(JSContext* cx, Handle<TypedArrayObject*> code, StringBuffer& buffer)
+{
+    MOZ_ASSERT(!code->isSharedMemory());
+
+    if (!TypedArrayObject::ensureHasBuffer(cx, code))
+        return false;
+
+    const uint8_t* bufferStart = code->bufferUnshared()->dataPointer();
+    const uint8_t* bytes = bufferStart + code->byteOffset();
+    uint32_t length = code->byteLength();
+
+    Vector<uint8_t> copy(cx);
+    if (code->bufferUnshared()->hasInlineData()) {
+        if (!copy.append(bytes, length))
+            return false;
+        bytes = copy.begin();
+    }
+
+    Decoder d(bytes, bytes + length);
+    WasmRenderContext c(cx, d, buffer);
+
+    if (!RenderModule(c)) {
+        if (!cx->isExceptionPending())
+            ReportOutOfMemory(cx);
+        return false;
+    }
+
+    return true;
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/asmjs/WasmBinaryToText.h
@@ -0,0 +1,44 @@
+/* -*- 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_text_h
+#define wasm_binary_to_text_h
+
+#include "NamespaceImports.h"
+
+#include "gc/Rooting.h"
+#include "js/Class.h"
+
+namespace js {
+
+class TypedArrayObject;
+class StringBuffer;
+
+namespace wasm {
+
+// Translate the given binary representation of a wasm module (given by a
+// typed array) into the module's textual representation.
+
+bool
+BinaryToText(JSContext* cx, Handle<TypedArrayObject*> code, StringBuffer& buffer);
+
+}  // namespace wasm
+
+}  // namespace js
+
+#endif // namespace wasm_binary_to_text_h
rename from js/src/asmjs/WasmText.cpp
rename to js/src/asmjs/WasmTextToBinary.cpp
--- a/js/src/asmjs/WasmText.cpp
+++ b/js/src/asmjs/WasmTextToBinary.cpp
@@ -11,17 +11,17 @@
  *
  * 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/WasmText.h"
+#include "asmjs/WasmTextToBinary.h"
 
 #include "mozilla/CheckedInt.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/Maybe.h"
 
 #include "jsdtoa.h"
 #include "jsnum.h"
 #include "jsprf.h"
rename from js/src/asmjs/WasmText.h
rename to js/src/asmjs/WasmTextToBinary.h
--- a/js/src/asmjs/WasmText.h
+++ b/js/src/asmjs/WasmTextToBinary.h
@@ -11,18 +11,18 @@
  *
  * 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_text_h
-#define wasm_text_h
+#ifndef wasm_text_to_binary_h
+#define wasm_text_to_binary_h
 
 #include "asmjs/WasmBinary.h"
 #include "js/Utility.h"
 
 namespace js {
 namespace wasm {
 
 // Translate the textual representation of a wasm module (given by a
@@ -30,9 +30,9 @@ namespace wasm {
 // other than out-of-memory an error message string will be stored in 'error'.
 
 extern bool
 TextToBinary(const char16_t* text, Bytes* bytes, UniqueChars* error);
 
 } // namespace wasm
 } // namespace js
 
-#endif // wasm_text_h
+#endif // wasm_text_to_binary_h
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -16,32 +16,34 @@
 #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/WasmText.h"
+#include "asmjs/WasmBinaryToText.h"
+#include "asmjs/WasmTextToBinary.h"
 #include "jit/InlinableNatives.h"
 #include "jit/JitFrameIterator.h"
 #include "js/Debug.h"
 #include "js/HashTable.h"
 #include "js/StructuredClone.h"
 #include "js/UbiNode.h"
 #include "js/UbiNodeBreadthFirst.h"
 #include "js/UbiNodeShortestPaths.h"
 #include "js/UniquePtr.h"
 #include "js/Vector.h"
 #include "vm/GlobalObject.h"
 #include "vm/Interpreter.h"
 #include "vm/ProxyObject.h"
 #include "vm/SavedStacks.h"
 #include "vm/Stack.h"
+#include "vm/StringBuffer.h"
 #include "vm/TraceLogging.h"
 
 #include "jscntxtinlines.h"
 #include "jsobjinlines.h"
 
 #include "vm/NativeObject-inl.h"
 #include "vm/ScopeObject-inl.h"
 
@@ -539,16 +541,48 @@ WasmTextToBinary(JSContext* cx, unsigned
 
     memcpy(obj->as<TypedArrayObject>().viewDataUnshared(), bytes.begin(), bytes.length());
 
     args.rval().setObject(*obj);
     return true;
 }
 
 static bool
+WasmBinaryToText(JSContext* cx, unsigned argc, Value* vp)
+{
+    MOZ_ASSERT(cx->runtime()->options().wasm());
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (!args.get(0).isObject() || !args.get(0).toObject().is<TypedArrayObject>()) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_BUF_ARG);
+        return false;
+    }
+
+    Rooted<TypedArrayObject*> code(cx, &args[0].toObject().as<TypedArrayObject>());
+    if (code->isSharedMemory()) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_BUF_ARG);
+        return false;
+    }
+
+    StringBuffer buffer(cx);
+    if (!wasm::BinaryToText(cx, code, buffer)) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_TEXT_FAIL,
+                             "print error");
+        return false;
+    }
+
+    JSString* result = buffer.finishString();
+    if (!result)
+        return false;
+
+    args.rval().setString(result);
+    return true;
+}
+
+static bool
 IsLazyFunction(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.length() != 1) {
         JS_ReportError(cx, "The function takes exactly one argument.");
         return false;
     }
     if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
@@ -3673,16 +3707,20 @@ gc::ZealModeHelpText),
     JS_FN_HELP("wasmIsSupported", WasmIsSupported, 0, 0,
 "wasmIsSupported()",
 "  Returns a boolean indicating whether WebAssembly is supported on the current device."),
 
     JS_FN_HELP("wasmTextToBinary", WasmTextToBinary, 1, 0,
 "wasmTextToBinary(str)",
 "  Translates the given text wasm module into its binary encoding."),
 
+    JS_FN_HELP("wasmBinaryToText", WasmBinaryToText, 1, 0,
+"wasmBinaryToText(bin)",
+"  Translates binary encoding to text format"),
+
     JS_FN_HELP("isLazyFunction", IsLazyFunction, 1, 0,
 "isLazyFunction(fun)",
 "  True if fun is a lazy JSFunction."),
 
     JS_FN_HELP("isRelazifiableFunction", IsRelazifiableFunction, 1, 0,
 "isRelazifiableFunction(fun)",
 "  Ture if fun is a JSFunction with a relazifiable JSScript."),
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/totext1.js
@@ -0,0 +1,144 @@
+if (!wasmIsSupported())
+     quit();
+ 
+load(libdir + "asserts.js");
+
+function runTest(code) {
+  var expected = wasmTextToBinary(code);
+  var s = wasmBinaryToText(expected);
+  print("TEXT: " + s);
+  var roundtrip = wasmTextToBinary(s);
+  assertDeepEq(expected, roundtrip);
+}
+
+// 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)
+)`);
+
+// Constants, stores and loads
+runTest(`
+(module (func
+  (local i32) (local f32) (local f64)
+  (i32.const 0)
+  (i32.const 100002)
+  (f32.const 0.0)
+  (f32.const 1.5)
+  (f64.const 0.0)
+  (f64.const -10.25)
+  (i32.store (i32.const 0) (i32.load (i32.const 0)))
+  (i32.store8 (i32.const 1) (i32.load8_s (i32.const 2)))
+  (i32.store8 (i32.const 3) (i32.load8_u (i32.const 4)))
+  (i32.store16 (i32.const 2) (i32.load16_s (i32.const 0)))
+  (i32.store16 (i32.const 1) (i32.load16_u (i32.const 0)))
+  (f32.store (i32.const 5) (f32.load (i32.const 6)))
+  (f64.store (i32.const 5) (f64.load (i32.const 6)))
+  (set_local 0 (get_local 0))
+  (set_local 2 (get_local 2))
+)(memory 100))`);
+
+// Branching
+runTest(`
+(module
+(func
+  (block (block (block (nop))))
+  (block (loop))
+  (if (i32.const 0) (block $label (nop)))
+  (if (i32.const 1) (nop) (loop $exit $cont (block)))
+  (block $l (br $l))
+  (block $m (block (block (br $m))))
+  (block $k (br_if 0 (i32.const 0)) (return))
+  (block $n (block (block (br_if 2 (i32.const 1)) (nop))))
+  (block $1 (block $2 (block $3 (br_table $2 $3 $1 (i32.const 1)) )) (nop))
+  (loop $exit $cont (br_if $cont (i32.const 0)) (nop))
+  (return)
+)
+(func (result f32) (return (f32.const -0.5)))
+(memory 0)
+)`);
+
+// i32, f32 and f64 operations
+runTest(`
+(module
+  (func $iadd (param $x i32) (param $y i32) (result i32) (i32.add (get_local $x) (get_local $y)))
+  (func $isub (param $x i32) (param $y i32) (result i32) (i32.sub (get_local $x) (get_local $y)))
+  (func $imul (param $x i32) (param $y i32) (result i32) (i32.mul (get_local $x) (get_local $y)))
+  (func $idiv_s (param $x i32) (param $y i32) (result i32) (i32.div_s (get_local $x) (get_local $y)))
+  (func $idiv_u (param $x i32) (param $y i32) (result i32) (i32.div_u (get_local $x) (get_local $y)))
+  (func $irem_s (param $x i32) (param $y i32) (result i32) (i32.rem_s (get_local $x) (get_local $y)))
+  (func $irem_u (param $x i32) (param $y i32) (result i32) (i32.rem_u (get_local $x) (get_local $y)))
+  (func $iand (param $x i32) (param $y i32) (result i32) (i32.and (get_local $x) (get_local $y)))
+  (func $ior (param $x i32) (param $y i32) (result i32) (i32.or (get_local $x) (get_local $y)))
+  (func $ixor (param $x i32) (param $y i32) (result i32) (i32.xor (get_local $x) (get_local $y)))
+  (func $ishl (param $x i32) (param $y i32) (result i32) (i32.shl (get_local $x) (get_local $y)))
+  (func $ishr_s (param $x i32) (param $y i32) (result i32) (i32.shr_s (get_local $x) (get_local $y)))
+  (func $ishr_u (param $x i32) (param $y i32) (result i32) (i32.shr_u (get_local $x) (get_local $y)))
+  (func $iclz (param $x i32) (result i32) (i32.clz (get_local $x)))
+  (func $ictz (param $x i32) (result i32) (i32.ctz (get_local $x)))
+  (func $ipopcnt (param $x i32) (result i32) (i32.popcnt (get_local $x)))
+  (func $ieq (param $x i32) (param $y i32) (result i32) (i32.eq (get_local $x) (get_local $y)))
+  (func $ine (param $x i32) (param $y i32) (result i32) (i32.ne (get_local $x) (get_local $y)))
+  (func $ilt_s (param $x i32) (param $y i32) (result i32) (i32.lt_s (get_local $x) (get_local $y)))
+  (func $ilt_u (param $x i32) (param $y i32) (result i32) (i32.lt_u (get_local $x) (get_local $y)))
+  (func $ile_s (param $x i32) (param $y i32) (result i32) (i32.le_s (get_local $x) (get_local $y)))
+  (func $ile_u (param $x i32) (param $y i32) (result i32) (i32.le_u (get_local $x) (get_local $y)))
+  (func $igt_s (param $x i32) (param $y i32) (result i32) (i32.gt_s (get_local $x) (get_local $y)))
+  (func $igt_u (param $x i32) (param $y i32) (result i32) (i32.gt_u (get_local $x) (get_local $y)))
+  (func $ige_s (param $x i32) (param $y i32) (result i32) (i32.ge_s (get_local $x) (get_local $y)))
+  (func $ige_u (param $x i32) (param $y i32) (result i32) (i32.ge_u (get_local $x) (get_local $y)))
+
+  (func $fadd (param $x f32) (param $y f32) (result f32) (f32.add (get_local $x) (get_local $y)))
+  (func $fsub (param $x f32) (param $y f32) (result f32) (f32.sub (get_local $x) (get_local $y)))
+  (func $fmul (param $x f32) (param $y f32) (result f32) (f32.mul (get_local $x) (get_local $y)))
+  (func $fdiv (param $x f32) (param $y f32) (result f32) (f32.div (get_local $x) (get_local $y)))
+  (func $fsqrt (param $x f32) (result f32) (f32.sqrt (get_local $x)))
+  (func $fmin (param $x f32) (param $y f32) (result f32) (f32.min (get_local $x) (get_local $y)))
+  (func $fmax (param $x f32) (param $y f32) (result f32) (f32.max (get_local $x) (get_local $y)))
+  (func $fceil (param $x f32) (result f32) (f32.ceil (get_local $x)))
+  (func $ffloor (param $x f32) (result f32) (f32.floor (get_local $x)))
+  (func $fabs (param $x f32) (result f32) (f32.abs (get_local $x)))
+  (func $fneg (param $x f32) (result f32) (f32.neg (get_local $x)))
+
+  (func $dadd (param $x f64) (param $y f64) (result f64) (f64.add (get_local $x) (get_local $y)))
+  (func $dsub (param $x f64) (param $y f64) (result f64) (f64.sub (get_local $x) (get_local $y)))
+  (func $dmul (param $x f64) (param $y f64) (result f64) (f64.mul (get_local $x) (get_local $y)))
+  (func $ddiv (param $x f64) (param $y f64) (result f64) (f64.div (get_local $x) (get_local $y)))
+  (func $dceil (param $x f64) (result f64) (f64.ceil (get_local $x)))
+  (func $dfloor (param $x f64) (result f64) (f64.floor (get_local $x)))
+  (func $dabs (param $x f64) (result f64) (f64.abs (get_local $x)))
+  (func $dneg (param $x f64) (result f64) (f64.neg (get_local $x)))
+(memory 0))`);
+
+// conversions
+runTest(`
+(module
+  (func $itrunc_s_f32 (param $x f32) (result i32) (i32.trunc_s/f32 (get_local $x)))
+  (func $itrunc_u_f32 (param $x f32) (result i32) (i32.trunc_u/f32 (get_local $x)))
+  (func $itrunc_s_f64 (param $x f64) (result i32) (i32.trunc_s/f64 (get_local $x)))
+  (func $itrunc_u_f64 (param $x f64) (result i32) (i32.trunc_u/f64 (get_local $x)))
+  (func $fconvert_s_i32 (param $x i32) (result f32) (f32.convert_s/i32 (get_local $x)))
+  (func $dconvert_s_i32 (param $x i32) (result f64) (f64.convert_s/i32 (get_local $x)))
+  (func $fconvert_u_i32 (param $x i32) (result f32) (f32.convert_u/i32 (get_local $x)))
+  (func $dconvert_u_i32 (param $x i32) (result f64) (f64.convert_u/i32 (get_local $x)))
+  (func $dpromote_f32 (param $x f32) (result f64) (f64.promote/f32 (get_local $x)))
+  (func $fdemote_f64 (param $x f64) (result f32) (f32.demote/f64 (get_local $x)))
+(memory 0))`);
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -142,23 +142,24 @@ EXPORTS.js += [
     '../public/Vector.h',
     '../public/WeakMapPtr.h',
 ]
 
 UNIFIED_SOURCES += [
     'asmjs/AsmJS.cpp',
     'asmjs/Wasm.cpp',
     'asmjs/WasmBinary.cpp',
+    'asmjs/WasmBinaryToText.cpp',
     'asmjs/WasmFrameIterator.cpp',
     'asmjs/WasmGenerator.cpp',
     'asmjs/WasmIonCompile.cpp',
     'asmjs/WasmModule.cpp',
     'asmjs/WasmSignalHandlers.cpp',
     'asmjs/WasmStubs.cpp',
-    'asmjs/WasmText.cpp',
+    'asmjs/WasmTextToBinary.cpp',
     'asmjs/WasmTypes.cpp',
     'builtin/AtomicsObject.cpp',
     'builtin/Eval.cpp',
     'builtin/Intl.cpp',
     'builtin/MapObject.cpp',
     'builtin/ModuleObject.cpp',
     'builtin/Object.cpp',
     'builtin/Profilers.cpp',