1745 lines
48 KiB
C++
1745 lines
48 KiB
C++
/* -*- 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 "wasm/WasmBinaryToText.h"
|
|
|
|
#include "jsnum.h"
|
|
#include "jsprf.h"
|
|
|
|
#include "vm/ArrayBufferObject.h"
|
|
#include "vm/StringBuffer.h"
|
|
#include "wasm/WasmAST.h"
|
|
#include "wasm/WasmBinaryToAST.h"
|
|
#include "wasm/WasmTextUtils.h"
|
|
#include "wasm/WasmTypes.h"
|
|
|
|
using namespace js;
|
|
using namespace js::wasm;
|
|
|
|
using mozilla::IsInfinite;
|
|
using mozilla::IsNaN;
|
|
using mozilla::IsNegativeZero;
|
|
|
|
struct WasmRenderContext
|
|
{
|
|
JSContext* cx;
|
|
AstModule* module;
|
|
WasmPrintBuffer& buffer;
|
|
GeneratedSourceMap* maybeSourceMap;
|
|
uint32_t indent;
|
|
|
|
uint32_t currentFuncIndex;
|
|
|
|
WasmRenderContext(JSContext* cx, AstModule* module, WasmPrintBuffer& buffer, GeneratedSourceMap* sourceMap)
|
|
: cx(cx), module(module), buffer(buffer), maybeSourceMap(sourceMap), indent(0), currentFuncIndex(0)
|
|
{}
|
|
|
|
StringBuffer& sb() { return buffer.stringBuffer(); }
|
|
};
|
|
|
|
/*****************************************************************************/
|
|
// utilities
|
|
|
|
// Return true on purpose, so that we have a useful error message to provide to
|
|
// the user.
|
|
static bool
|
|
Fail(WasmRenderContext& c, const char* msg)
|
|
{
|
|
c.buffer.stringBuffer().clear();
|
|
|
|
return c.buffer.append("There was a problem when rendering the wasm text format: ") &&
|
|
c.buffer.append(msg, strlen(msg)) &&
|
|
c.buffer.append("\nYou should consider file a bug on Bugzilla in the "
|
|
"Core:::JavaScript Engine::JIT component at "
|
|
"https://bugzilla.mozilla.org/enter_bug.cgi.");
|
|
}
|
|
|
|
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.sb());
|
|
}
|
|
|
|
static bool
|
|
RenderInt64(WasmRenderContext& c, int64_t num)
|
|
{
|
|
if (num < 0 && !c.buffer.append("-"))
|
|
return false;
|
|
if (!num)
|
|
return c.buffer.append("0");
|
|
return RenderInBase<10>(c.sb(), mozilla::Abs(num));
|
|
}
|
|
|
|
static bool
|
|
RenderDouble(WasmRenderContext& c, RawF64 num)
|
|
{
|
|
double d = num.fp();
|
|
if (IsNaN(d))
|
|
return RenderNaN(c.sb(), num);
|
|
if (IsNegativeZero(d))
|
|
return c.buffer.append("-0");
|
|
if (IsInfinite(d)) {
|
|
if (d > 0)
|
|
return c.buffer.append("infinity");
|
|
return c.buffer.append("-infinity");
|
|
}
|
|
return NumberValueToStringBuffer(c.cx, DoubleValue(d), c.sb());
|
|
}
|
|
|
|
static bool
|
|
RenderFloat32(WasmRenderContext& c, RawF32 num)
|
|
{
|
|
float f = num.fp();
|
|
if (IsNaN(f))
|
|
return RenderNaN(c.sb(), num);
|
|
return RenderDouble(c, RawF64(double(f)));
|
|
}
|
|
|
|
static bool
|
|
RenderEscapedString(WasmRenderContext& 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 + 'a' - 10)))
|
|
return false;
|
|
if (!c.buffer.append((char)(digit2 < 10 ? digit2 + '0' : digit2 + 'a' - 10)))
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
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:;
|
|
}
|
|
|
|
MOZ_CRASH("bad type");
|
|
}
|
|
|
|
static bool
|
|
RenderValType(WasmRenderContext& c, ValType type)
|
|
{
|
|
return RenderExprType(c, ToExprType(type));
|
|
}
|
|
|
|
static bool
|
|
RenderName(WasmRenderContext& c, const AstName& name)
|
|
{
|
|
return c.buffer.append(name.begin(), name.end());
|
|
}
|
|
|
|
static bool
|
|
RenderRef(WasmRenderContext& c, const AstRef& ref)
|
|
{
|
|
if (ref.name().empty())
|
|
return RenderInt32(c, ref.index());
|
|
|
|
return RenderName(c, ref.name());
|
|
}
|
|
|
|
static bool
|
|
RenderBlockNameAndSignature(WasmRenderContext& c, const AstName& name, ExprType type)
|
|
{
|
|
if (!name.empty()) {
|
|
if (!c.buffer.append(' '))
|
|
return false;
|
|
|
|
if (!RenderName(c, name))
|
|
return false;
|
|
}
|
|
|
|
if (!IsVoid(type)) {
|
|
if (!c.buffer.append(' '))
|
|
return false;
|
|
|
|
if (!RenderExprType(c, type))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
RenderExpr(WasmRenderContext& c, AstExpr& expr, bool newLine = true);
|
|
|
|
#define MAP_AST_EXPR(c, expr) \
|
|
if (c.maybeSourceMap) { \
|
|
uint32_t lineno = c.buffer.lineno(); \
|
|
uint32_t column = c.buffer.column(); \
|
|
if (!c.maybeSourceMap->exprlocs().emplaceBack(lineno, column, expr.offset())) \
|
|
return false; \
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
// binary format parsing and rendering
|
|
|
|
static bool
|
|
RenderNop(WasmRenderContext& c, AstNop& nop)
|
|
{
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
MAP_AST_EXPR(c, nop);
|
|
return c.buffer.append("nop");
|
|
}
|
|
|
|
static bool
|
|
RenderDrop(WasmRenderContext& c, AstDrop& drop)
|
|
{
|
|
if (!RenderExpr(c, drop.value()))
|
|
return false;
|
|
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
MAP_AST_EXPR(c, drop);
|
|
return c.buffer.append("drop");
|
|
}
|
|
|
|
static bool
|
|
RenderUnreachable(WasmRenderContext& c, AstUnreachable& unreachable)
|
|
{
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
MAP_AST_EXPR(c, unreachable);
|
|
return c.buffer.append("unreachable");
|
|
}
|
|
|
|
static bool
|
|
RenderCallArgs(WasmRenderContext& c, const AstExprVector& args)
|
|
{
|
|
for (uint32_t i = 0; i < args.length(); i++) {
|
|
if (!RenderExpr(c, *args[i]))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
RenderCall(WasmRenderContext& c, AstCall& call)
|
|
{
|
|
if (!RenderCallArgs(c, call.args()))
|
|
return false;
|
|
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
|
|
MAP_AST_EXPR(c, call);
|
|
if (call.op() == Op::Call) {
|
|
if (!c.buffer.append("call "))
|
|
return false;
|
|
} else {
|
|
return Fail(c, "unexpected operator");
|
|
}
|
|
|
|
return RenderRef(c, call.func());
|
|
}
|
|
|
|
static bool
|
|
RenderCallIndirect(WasmRenderContext& c, AstCallIndirect& call)
|
|
{
|
|
if (!RenderCallArgs(c, call.args()))
|
|
return false;
|
|
|
|
if (!RenderExpr(c, *call.index()))
|
|
return false;
|
|
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
|
|
MAP_AST_EXPR(c, call);
|
|
if (!c.buffer.append("call_indirect "))
|
|
return false;
|
|
return RenderRef(c, call.sig());
|
|
}
|
|
|
|
static bool
|
|
RenderConst(WasmRenderContext& c, AstConst& cst)
|
|
{
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
|
|
MAP_AST_EXPR(c, cst);
|
|
if (!RenderValType(c, cst.val().type()))
|
|
return false;
|
|
if (!c.buffer.append(".const "))
|
|
return false;
|
|
|
|
switch (ToExprType(cst.val().type())) {
|
|
case ExprType::I32:
|
|
return RenderInt32(c, (int32_t)cst.val().i32());
|
|
case ExprType::I64:
|
|
return RenderInt64(c, (int64_t)cst.val().i64());
|
|
case ExprType::F32:
|
|
return RenderFloat32(c, cst.val().f32());
|
|
case ExprType::F64:
|
|
return RenderDouble(c, cst.val().f64());
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
RenderGetLocal(WasmRenderContext& c, AstGetLocal& gl)
|
|
{
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
|
|
MAP_AST_EXPR(c, gl);
|
|
if (!c.buffer.append("get_local "))
|
|
return false;
|
|
return RenderRef(c, gl.local());
|
|
}
|
|
|
|
static bool
|
|
RenderSetLocal(WasmRenderContext& c, AstSetLocal& sl)
|
|
{
|
|
if (!RenderExpr(c, sl.value()))
|
|
return false;
|
|
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
|
|
MAP_AST_EXPR(c, sl);
|
|
if (!c.buffer.append("set_local "))
|
|
return false;
|
|
return RenderRef(c, sl.local());
|
|
}
|
|
|
|
static bool
|
|
RenderTeeLocal(WasmRenderContext& c, AstTeeLocal& tl)
|
|
{
|
|
if (!RenderExpr(c, tl.value()))
|
|
return false;
|
|
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
|
|
MAP_AST_EXPR(c, tl);
|
|
if (!c.buffer.append("tee_local "))
|
|
return false;
|
|
return RenderRef(c, tl.local());
|
|
}
|
|
|
|
static bool
|
|
RenderGetGlobal(WasmRenderContext& c, AstGetGlobal& gg)
|
|
{
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
|
|
MAP_AST_EXPR(c, gg);
|
|
if (!c.buffer.append("get_global "))
|
|
return false;
|
|
return RenderRef(c, gg.global());
|
|
}
|
|
|
|
static bool
|
|
RenderSetGlobal(WasmRenderContext& c, AstSetGlobal& sg)
|
|
{
|
|
if (!RenderExpr(c, sg.value()))
|
|
return false;
|
|
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
|
|
MAP_AST_EXPR(c, sg);
|
|
if (!c.buffer.append("set_global "))
|
|
return false;
|
|
return RenderRef(c, sg.global());
|
|
}
|
|
|
|
static bool
|
|
RenderExprList(WasmRenderContext& c, const AstExprVector& exprs)
|
|
{
|
|
for (uint32_t i = 0; i < exprs.length(); i++) {
|
|
if (!RenderExpr(c, *exprs[i]))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
RenderBlock(WasmRenderContext& c, AstBlock& block)
|
|
{
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
|
|
MAP_AST_EXPR(c, block);
|
|
if (block.op() == Op::Block) {
|
|
if (!c.buffer.append("block"))
|
|
return false;
|
|
} else if (block.op() == Op::Loop) {
|
|
if (!c.buffer.append("loop"))
|
|
return false;
|
|
} else {
|
|
return Fail(c, "unexpected block kind");
|
|
}
|
|
|
|
if (!RenderBlockNameAndSignature(c, block.name(), block.type()))
|
|
return false;
|
|
|
|
if (!c.buffer.append('\n'))
|
|
return false;
|
|
|
|
c.indent++;
|
|
if (!RenderExprList(c, block.exprs()))
|
|
return false;
|
|
c.indent--;
|
|
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
|
|
return c.buffer.append("end");
|
|
}
|
|
|
|
static bool
|
|
RenderFirst(WasmRenderContext& c, AstFirst& first)
|
|
{
|
|
return RenderExprList(c, first.exprs());
|
|
}
|
|
|
|
static bool
|
|
RenderCurrentMemory(WasmRenderContext& c, AstCurrentMemory& cm)
|
|
{
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
|
|
return c.buffer.append("current_memory\n");
|
|
}
|
|
|
|
static bool
|
|
RenderGrowMemory(WasmRenderContext& c, AstGrowMemory& gm)
|
|
{
|
|
if (!RenderExpr(c, *gm.operand()))
|
|
return false;
|
|
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
|
|
MAP_AST_EXPR(c, gm);
|
|
return c.buffer.append("grow_memory\n");
|
|
}
|
|
|
|
static bool
|
|
RenderUnaryOperator(WasmRenderContext& c, AstUnaryOperator& unary)
|
|
{
|
|
if (!RenderExpr(c, *unary.operand()))
|
|
return false;
|
|
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
|
|
MAP_AST_EXPR(c, unary);
|
|
const char* opStr;
|
|
switch (unary.op()) {
|
|
case Op::I32Eqz: opStr = "i32.eqz"; break;
|
|
case Op::I32Clz: opStr = "i32.clz"; break;
|
|
case Op::I32Ctz: opStr = "i32.ctz"; break;
|
|
case Op::I32Popcnt: opStr = "i32.popcnt"; break;
|
|
case Op::I64Clz: opStr = "i64.clz"; break;
|
|
case Op::I64Ctz: opStr = "i64.ctz"; break;
|
|
case Op::I64Popcnt: opStr = "i64.popcnt"; break;
|
|
case Op::F32Abs: opStr = "f32.abs"; break;
|
|
case Op::F32Neg: opStr = "f32.neg"; break;
|
|
case Op::F32Ceil: opStr = "f32.ceil"; break;
|
|
case Op::F32Floor: opStr = "f32.floor"; break;
|
|
case Op::F32Sqrt: opStr = "f32.sqrt"; break;
|
|
case Op::F32Trunc: opStr = "f32.trunc"; break;
|
|
case Op::F32Nearest: opStr = "f32.nearest"; break;
|
|
case Op::F64Abs: opStr = "f64.abs"; break;
|
|
case Op::F64Neg: opStr = "f64.neg"; break;
|
|
case Op::F64Ceil: opStr = "f64.ceil"; break;
|
|
case Op::F64Floor: opStr = "f64.floor"; break;
|
|
case Op::F64Nearest: opStr = "f64.nearest"; break;
|
|
case Op::F64Sqrt: opStr = "f64.sqrt"; break;
|
|
case Op::F64Trunc: opStr = "f64.trunc"; break;
|
|
default: return Fail(c, "unexpected unary operator");
|
|
}
|
|
|
|
return c.buffer.append(opStr, strlen(opStr));
|
|
}
|
|
|
|
static bool
|
|
RenderBinaryOperator(WasmRenderContext& c, AstBinaryOperator& binary)
|
|
{
|
|
if (!RenderExpr(c, *binary.lhs()))
|
|
return false;
|
|
if (!RenderExpr(c, *binary.rhs()))
|
|
return false;
|
|
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
|
|
MAP_AST_EXPR(c, binary);
|
|
const char* opStr;
|
|
switch (binary.op()) {
|
|
case Op::I32Add: opStr = "i32.add"; break;
|
|
case Op::I32Sub: opStr = "i32.sub"; break;
|
|
case Op::I32Mul: opStr = "i32.mul"; break;
|
|
case Op::I32DivS: opStr = "i32.div_s"; break;
|
|
case Op::I32DivU: opStr = "i32.div_u"; break;
|
|
case Op::I32RemS: opStr = "i32.rem_s"; break;
|
|
case Op::I32RemU: opStr = "i32.rem_u"; break;
|
|
case Op::I32And: opStr = "i32.and"; break;
|
|
case Op::I32Or: opStr = "i32.or"; break;
|
|
case Op::I32Xor: opStr = "i32.xor"; break;
|
|
case Op::I32Shl: opStr = "i32.shl"; break;
|
|
case Op::I32ShrS: opStr = "i32.shr_s"; break;
|
|
case Op::I32ShrU: opStr = "i32.shr_u"; break;
|
|
case Op::I32Rotl: opStr = "i32.rotl"; break;
|
|
case Op::I32Rotr: opStr = "i32.rotr"; break;
|
|
case Op::I64Add: opStr = "i64.add"; break;
|
|
case Op::I64Sub: opStr = "i64.sub"; break;
|
|
case Op::I64Mul: opStr = "i64.mul"; break;
|
|
case Op::I64DivS: opStr = "i64.div_s"; break;
|
|
case Op::I64DivU: opStr = "i64.div_u"; break;
|
|
case Op::I64RemS: opStr = "i64.rem_s"; break;
|
|
case Op::I64RemU: opStr = "i64.rem_u"; break;
|
|
case Op::I64And: opStr = "i64.and"; break;
|
|
case Op::I64Or: opStr = "i64.or"; break;
|
|
case Op::I64Xor: opStr = "i64.xor"; break;
|
|
case Op::I64Shl: opStr = "i64.shl"; break;
|
|
case Op::I64ShrS: opStr = "i64.shr_s"; break;
|
|
case Op::I64ShrU: opStr = "i64.shr_u"; break;
|
|
case Op::I64Rotl: opStr = "i64.rotl"; break;
|
|
case Op::I64Rotr: opStr = "i64.rotr"; break;
|
|
case Op::F32Add: opStr = "f32.add"; break;
|
|
case Op::F32Sub: opStr = "f32.sub"; break;
|
|
case Op::F32Mul: opStr = "f32.mul"; break;
|
|
case Op::F32Div: opStr = "f32.div"; break;
|
|
case Op::F32Min: opStr = "f32.min"; break;
|
|
case Op::F32Max: opStr = "f32.max"; break;
|
|
case Op::F32CopySign: opStr = "f32.copysign"; break;
|
|
case Op::F64Add: opStr = "f64.add"; break;
|
|
case Op::F64Sub: opStr = "f64.sub"; break;
|
|
case Op::F64Mul: opStr = "f64.mul"; break;
|
|
case Op::F64Div: opStr = "f64.div"; break;
|
|
case Op::F64Min: opStr = "f64.min"; break;
|
|
case Op::F64Max: opStr = "f64.max"; break;
|
|
case Op::F64CopySign: opStr = "f64.copysign"; break;
|
|
default: return Fail(c, "unexpected binary operator");
|
|
}
|
|
|
|
return c.buffer.append(opStr, strlen(opStr));
|
|
}
|
|
|
|
static bool
|
|
RenderTernaryOperator(WasmRenderContext& c, AstTernaryOperator& ternary)
|
|
{
|
|
if (!RenderExpr(c, *ternary.op0()))
|
|
return false;
|
|
if (!RenderExpr(c, *ternary.op1()))
|
|
return false;
|
|
if (!RenderExpr(c, *ternary.op2()))
|
|
return false;
|
|
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
|
|
MAP_AST_EXPR(c, ternary);
|
|
const char* opStr;
|
|
switch (ternary.op()) {
|
|
case Op::Select: opStr = "select"; break;
|
|
default: return Fail(c, "unexpected ternary operator");
|
|
}
|
|
|
|
return c.buffer.append(opStr, strlen(opStr));
|
|
}
|
|
|
|
static bool
|
|
RenderComparisonOperator(WasmRenderContext& c, AstComparisonOperator& comp)
|
|
{
|
|
if (!RenderExpr(c, *comp.lhs()))
|
|
return false;
|
|
if (!RenderExpr(c, *comp.rhs()))
|
|
return false;
|
|
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
|
|
MAP_AST_EXPR(c, comp);
|
|
const char* opStr;
|
|
switch (comp.op()) {
|
|
case Op::I32Eq: opStr = "i32.eq"; break;
|
|
case Op::I32Ne: opStr = "i32.ne"; break;
|
|
case Op::I32LtS: opStr = "i32.lt_s"; break;
|
|
case Op::I32LtU: opStr = "i32.lt_u"; break;
|
|
case Op::I32LeS: opStr = "i32.le_s"; break;
|
|
case Op::I32LeU: opStr = "i32.le_u"; break;
|
|
case Op::I32GtS: opStr = "i32.gt_s"; break;
|
|
case Op::I32GtU: opStr = "i32.gt_u"; break;
|
|
case Op::I32GeS: opStr = "i32.ge_s"; break;
|
|
case Op::I32GeU: opStr = "i32.ge_u"; break;
|
|
case Op::I64Eq: opStr = "i64.eq"; break;
|
|
case Op::I64Ne: opStr = "i64.ne"; break;
|
|
case Op::I64LtS: opStr = "i64.lt_s"; break;
|
|
case Op::I64LtU: opStr = "i64.lt_u"; break;
|
|
case Op::I64LeS: opStr = "i64.le_s"; break;
|
|
case Op::I64LeU: opStr = "i64.le_u"; break;
|
|
case Op::I64GtS: opStr = "i64.gt_s"; break;
|
|
case Op::I64GtU: opStr = "i64.gt_u"; break;
|
|
case Op::I64GeS: opStr = "i64.ge_s"; break;
|
|
case Op::I64GeU: opStr = "i64.ge_u"; break;
|
|
case Op::F32Eq: opStr = "f32.eq"; break;
|
|
case Op::F32Ne: opStr = "f32.ne"; break;
|
|
case Op::F32Lt: opStr = "f32.lt"; break;
|
|
case Op::F32Le: opStr = "f32.le"; break;
|
|
case Op::F32Gt: opStr = "f32.gt"; break;
|
|
case Op::F32Ge: opStr = "f32.ge"; break;
|
|
case Op::F64Eq: opStr = "f64.eq"; break;
|
|
case Op::F64Ne: opStr = "f64.ne"; break;
|
|
case Op::F64Lt: opStr = "f64.lt"; break;
|
|
case Op::F64Le: opStr = "f64.le"; break;
|
|
case Op::F64Gt: opStr = "f64.gt"; break;
|
|
case Op::F64Ge: opStr = "f64.ge"; break;
|
|
default: return Fail(c, "unexpected comparison operator");
|
|
}
|
|
|
|
return c.buffer.append(opStr, strlen(opStr));
|
|
}
|
|
|
|
static bool
|
|
RenderConversionOperator(WasmRenderContext& c, AstConversionOperator& conv)
|
|
{
|
|
if (!RenderExpr(c, *conv.operand()))
|
|
return false;
|
|
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
|
|
MAP_AST_EXPR(c, conv);
|
|
const char* opStr;
|
|
switch (conv.op()) {
|
|
case Op::I32WrapI64: opStr = "i32.wrap/i64"; break;
|
|
case Op::I32TruncSF32: opStr = "i32.trunc_s/f32"; break;
|
|
case Op::I32TruncUF32: opStr = "i32.trunc_u/f32"; break;
|
|
case Op::I32ReinterpretF32: opStr = "i32.reinterpret/f32"; break;
|
|
case Op::I32TruncSF64: opStr = "i32.trunc_s/f64"; break;
|
|
case Op::I32TruncUF64: opStr = "i32.trunc_u/f64"; break;
|
|
case Op::I64ExtendSI32: opStr = "i64.extend_s/i32"; break;
|
|
case Op::I64ExtendUI32: opStr = "i64.extend_u/i32"; break;
|
|
case Op::I64TruncSF32: opStr = "i64.trunc_s/f32"; break;
|
|
case Op::I64TruncUF32: opStr = "i64.trunc_u/f32"; break;
|
|
case Op::I64TruncSF64: opStr = "i64.trunc_s/f64"; break;
|
|
case Op::I64TruncUF64: opStr = "i64.trunc_u/f64"; break;
|
|
case Op::I64ReinterpretF64: opStr = "i64.reinterpret/f64"; break;
|
|
case Op::F32ConvertSI32: opStr = "f32.convert_s/i32"; break;
|
|
case Op::F32ConvertUI32: opStr = "f32.convert_u/i32"; break;
|
|
case Op::F32ReinterpretI32: opStr = "f32.reinterpret/i32"; break;
|
|
case Op::F32ConvertSI64: opStr = "f32.convert_s/i64"; break;
|
|
case Op::F32ConvertUI64: opStr = "f32.convert_u/i64"; break;
|
|
case Op::F32DemoteF64: opStr = "f32.demote/f64"; break;
|
|
case Op::F64ConvertSI32: opStr = "f64.convert_s/i32"; break;
|
|
case Op::F64ConvertUI32: opStr = "f64.convert_u/i32"; break;
|
|
case Op::F64ConvertSI64: opStr = "f64.convert_s/i64"; break;
|
|
case Op::F64ConvertUI64: opStr = "f64.convert_u/i64"; break;
|
|
case Op::F64ReinterpretI64: opStr = "f64.reinterpret/i64"; break;
|
|
case Op::F64PromoteF32: opStr = "f64.promote/f32"; break;
|
|
case Op::I32Eqz: opStr = "i32.eqz"; break;
|
|
case Op::I64Eqz: opStr = "i64.eqz"; break;
|
|
default: return Fail(c, "unexpected conversion operator");
|
|
}
|
|
return c.buffer.append(opStr, strlen(opStr));
|
|
}
|
|
|
|
static bool
|
|
RenderIf(WasmRenderContext& c, AstIf& if_)
|
|
{
|
|
if (!RenderExpr(c, if_.cond()))
|
|
return false;
|
|
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
|
|
MAP_AST_EXPR(c, if_);
|
|
if (!c.buffer.append("if"))
|
|
return false;
|
|
if (!RenderBlockNameAndSignature(c, if_.name(), if_.type()))
|
|
return false;
|
|
if (!c.buffer.append('\n'))
|
|
return false;
|
|
|
|
c.indent++;
|
|
if (!RenderExprList(c, if_.thenExprs()))
|
|
return false;
|
|
c.indent--;
|
|
|
|
if (if_.hasElse()) {
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
|
|
if (!c.buffer.append("else\n"))
|
|
return false;
|
|
|
|
c.indent++;
|
|
if (!RenderExprList(c, if_.elseExprs()))
|
|
return false;
|
|
c.indent--;
|
|
}
|
|
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
|
|
return c.buffer.append("end");
|
|
}
|
|
|
|
static bool
|
|
RenderLoadStoreBase(WasmRenderContext& c, const AstLoadStoreAddress& lsa)
|
|
{
|
|
return RenderExpr(c, lsa.base());
|
|
}
|
|
|
|
static bool
|
|
RenderLoadStoreAddress(WasmRenderContext& c, const AstLoadStoreAddress& lsa, uint32_t defaultAlignLog2)
|
|
{
|
|
if (lsa.offset() != 0) {
|
|
if (!c.buffer.append(" offset="))
|
|
return false;
|
|
if (!RenderInt32(c, lsa.offset()))
|
|
return false;
|
|
}
|
|
|
|
uint32_t alignLog2 = lsa.flags();
|
|
if (defaultAlignLog2 != alignLog2) {
|
|
if (!c.buffer.append(" align="))
|
|
return false;
|
|
if (!RenderInt32(c, 1 << alignLog2))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
RenderLoad(WasmRenderContext& c, AstLoad& load)
|
|
{
|
|
if (!RenderLoadStoreBase(c, load.address()))
|
|
return false;
|
|
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
|
|
MAP_AST_EXPR(c, load);
|
|
uint32_t defaultAlignLog2;
|
|
switch (load.op()) {
|
|
case Op::I32Load8S:
|
|
if (!c.buffer.append("i32.load8_s"))
|
|
return false;
|
|
defaultAlignLog2 = 0;
|
|
break;
|
|
case Op::I64Load8S:
|
|
if (!c.buffer.append("i64.load8_s"))
|
|
return false;
|
|
defaultAlignLog2 = 0;
|
|
break;
|
|
case Op::I32Load8U:
|
|
if (!c.buffer.append("i32.load8_u"))
|
|
return false;
|
|
defaultAlignLog2 = 0;
|
|
break;
|
|
case Op::I64Load8U:
|
|
if (!c.buffer.append("i64.load8_u"))
|
|
return false;
|
|
defaultAlignLog2 = 0;
|
|
break;
|
|
case Op::I32Load16S:
|
|
if (!c.buffer.append("i32.load16_s"))
|
|
return false;
|
|
defaultAlignLog2 = 1;
|
|
break;
|
|
case Op::I64Load16S:
|
|
if (!c.buffer.append("i64.load16_s"))
|
|
return false;
|
|
defaultAlignLog2 = 1;
|
|
break;
|
|
case Op::I32Load16U:
|
|
if (!c.buffer.append("i32.load16_u"))
|
|
return false;
|
|
defaultAlignLog2 = 1;
|
|
break;
|
|
case Op::I64Load16U:
|
|
if (!c.buffer.append("i64.load16_u"))
|
|
return false;
|
|
defaultAlignLog2 = 1;
|
|
break;
|
|
case Op::I64Load32S:
|
|
if (!c.buffer.append("i64.load32_s"))
|
|
return false;
|
|
defaultAlignLog2 = 2;
|
|
break;
|
|
case Op::I64Load32U:
|
|
if (!c.buffer.append("i64.load32_u"))
|
|
return false;
|
|
defaultAlignLog2 = 2;
|
|
break;
|
|
case Op::I32Load:
|
|
if (!c.buffer.append("i32.load"))
|
|
return false;
|
|
defaultAlignLog2 = 2;
|
|
break;
|
|
case Op::I64Load:
|
|
if (!c.buffer.append("i64.load"))
|
|
return false;
|
|
defaultAlignLog2 = 3;
|
|
break;
|
|
case Op::F32Load:
|
|
if (!c.buffer.append("f32.load"))
|
|
return false;
|
|
defaultAlignLog2 = 2;
|
|
break;
|
|
case Op::F64Load:
|
|
if (!c.buffer.append("f64.load"))
|
|
return false;
|
|
defaultAlignLog2 = 3;
|
|
break;
|
|
default:
|
|
return Fail(c, "unexpected load operator");
|
|
}
|
|
|
|
return RenderLoadStoreAddress(c, load.address(), defaultAlignLog2);
|
|
}
|
|
|
|
static bool
|
|
RenderStore(WasmRenderContext& c, AstStore& store)
|
|
{
|
|
if (!RenderLoadStoreBase(c, store.address()))
|
|
return false;
|
|
|
|
if (!RenderExpr(c, store.value()))
|
|
return false;
|
|
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
|
|
MAP_AST_EXPR(c, store);
|
|
uint32_t defaultAlignLog2;
|
|
switch (store.op()) {
|
|
case Op::I32Store8:
|
|
if (!c.buffer.append("i32.store8"))
|
|
return false;
|
|
defaultAlignLog2 = 0;
|
|
break;
|
|
case Op::I64Store8:
|
|
if (!c.buffer.append("i64.store8"))
|
|
return false;
|
|
defaultAlignLog2 = 0;
|
|
break;
|
|
case Op::I32Store16:
|
|
if (!c.buffer.append("i32.store16"))
|
|
return false;
|
|
defaultAlignLog2 = 1;
|
|
break;
|
|
case Op::I64Store16:
|
|
if (!c.buffer.append("i64.store16"))
|
|
return false;
|
|
defaultAlignLog2 = 1;
|
|
break;
|
|
case Op::I64Store32:
|
|
if (!c.buffer.append("i64.store32"))
|
|
return false;
|
|
defaultAlignLog2 = 2;
|
|
break;
|
|
case Op::I32Store:
|
|
if (!c.buffer.append("i32.store"))
|
|
return false;
|
|
defaultAlignLog2 = 2;
|
|
break;
|
|
case Op::I64Store:
|
|
if (!c.buffer.append("i64.store"))
|
|
return false;
|
|
defaultAlignLog2 = 3;
|
|
break;
|
|
case Op::F32Store:
|
|
if (!c.buffer.append("f32.store"))
|
|
return false;
|
|
defaultAlignLog2 = 2;
|
|
break;
|
|
case Op::F64Store:
|
|
if (!c.buffer.append("f64.store"))
|
|
return false;
|
|
defaultAlignLog2 = 3;
|
|
break;
|
|
default:
|
|
return Fail(c, "unexpected store operator");
|
|
}
|
|
|
|
return RenderLoadStoreAddress(c, store.address(), defaultAlignLog2);
|
|
}
|
|
|
|
static bool
|
|
RenderBranch(WasmRenderContext& c, AstBranch& branch)
|
|
{
|
|
Op op = branch.op();
|
|
MOZ_ASSERT(op == Op::BrIf || op == Op::Br);
|
|
|
|
if (op == Op::BrIf) {
|
|
if (!RenderExpr(c, branch.cond()))
|
|
return false;
|
|
}
|
|
|
|
if (branch.maybeValue()) {
|
|
if (!RenderExpr(c, *(branch.maybeValue())))
|
|
return false;
|
|
}
|
|
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
|
|
MAP_AST_EXPR(c, branch);
|
|
if (op == Op::BrIf ? !c.buffer.append("br_if ") : !c.buffer.append("br "))
|
|
return false;
|
|
|
|
return RenderRef(c, branch.target());
|
|
}
|
|
|
|
static bool
|
|
RenderBrTable(WasmRenderContext& c, AstBranchTable& table)
|
|
{
|
|
if (table.maybeValue()) {
|
|
if (!RenderExpr(c, *(table.maybeValue())))
|
|
return false;
|
|
}
|
|
|
|
// Index
|
|
if (!RenderExpr(c, table.index()))
|
|
return false;
|
|
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
|
|
MAP_AST_EXPR(c, table);
|
|
if (!c.buffer.append("br_table "))
|
|
return false;
|
|
|
|
uint32_t tableLength = table.table().length();
|
|
for (uint32_t i = 0; i < tableLength; i++) {
|
|
if (!RenderRef(c, table.table()[i]))
|
|
return false;
|
|
|
|
if (!c.buffer.append(" "))
|
|
return false;
|
|
}
|
|
|
|
return RenderRef(c, table.def());
|
|
}
|
|
|
|
static bool
|
|
RenderReturn(WasmRenderContext& c, AstReturn& ret)
|
|
{
|
|
if (ret.maybeExpr()) {
|
|
if (!RenderExpr(c, *(ret.maybeExpr())))
|
|
return false;
|
|
}
|
|
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
|
|
MAP_AST_EXPR(c, ret);
|
|
return c.buffer.append("return");
|
|
}
|
|
|
|
static bool
|
|
RenderExpr(WasmRenderContext& c, AstExpr& expr, bool newLine /* = true */)
|
|
{
|
|
switch (expr.kind()) {
|
|
case AstExprKind::Drop:
|
|
if (!RenderDrop(c, expr.as<AstDrop>()))
|
|
return false;
|
|
break;
|
|
case AstExprKind::Nop:
|
|
if (!RenderNop(c, expr.as<AstNop>()))
|
|
return false;
|
|
break;
|
|
case AstExprKind::Unreachable:
|
|
if (!RenderUnreachable(c, expr.as<AstUnreachable>()))
|
|
return false;
|
|
break;
|
|
case AstExprKind::Call:
|
|
if (!RenderCall(c, expr.as<AstCall>()))
|
|
return false;
|
|
break;
|
|
case AstExprKind::CallIndirect:
|
|
if (!RenderCallIndirect(c, expr.as<AstCallIndirect>()))
|
|
return false;
|
|
break;
|
|
case AstExprKind::Const:
|
|
if (!RenderConst(c, expr.as<AstConst>()))
|
|
return false;
|
|
break;
|
|
case AstExprKind::GetLocal:
|
|
if (!RenderGetLocal(c, expr.as<AstGetLocal>()))
|
|
return false;
|
|
break;
|
|
case AstExprKind::SetLocal:
|
|
if (!RenderSetLocal(c, expr.as<AstSetLocal>()))
|
|
return false;
|
|
break;
|
|
case AstExprKind::GetGlobal:
|
|
if (!RenderGetGlobal(c, expr.as<AstGetGlobal>()))
|
|
return false;
|
|
break;
|
|
case AstExprKind::SetGlobal:
|
|
if (!RenderSetGlobal(c, expr.as<AstSetGlobal>()))
|
|
return false;
|
|
break;
|
|
case AstExprKind::TeeLocal:
|
|
if (!RenderTeeLocal(c, expr.as<AstTeeLocal>()))
|
|
return false;
|
|
break;
|
|
case AstExprKind::Block:
|
|
if (!RenderBlock(c, expr.as<AstBlock>()))
|
|
return false;
|
|
break;
|
|
case AstExprKind::If:
|
|
if (!RenderIf(c, expr.as<AstIf>()))
|
|
return false;
|
|
break;
|
|
case AstExprKind::UnaryOperator:
|
|
if (!RenderUnaryOperator(c, expr.as<AstUnaryOperator>()))
|
|
return false;
|
|
break;
|
|
case AstExprKind::BinaryOperator:
|
|
if (!RenderBinaryOperator(c, expr.as<AstBinaryOperator>()))
|
|
return false;
|
|
break;
|
|
case AstExprKind::TernaryOperator:
|
|
if (!RenderTernaryOperator(c, expr.as<AstTernaryOperator>()))
|
|
return false;
|
|
break;
|
|
case AstExprKind::ComparisonOperator:
|
|
if (!RenderComparisonOperator(c, expr.as<AstComparisonOperator>()))
|
|
return false;
|
|
break;
|
|
case AstExprKind::ConversionOperator:
|
|
if (!RenderConversionOperator(c, expr.as<AstConversionOperator>()))
|
|
return false;
|
|
break;
|
|
case AstExprKind::Load:
|
|
if (!RenderLoad(c, expr.as<AstLoad>()))
|
|
return false;
|
|
break;
|
|
case AstExprKind::Store:
|
|
if (!RenderStore(c, expr.as<AstStore>()))
|
|
return false;
|
|
break;
|
|
case AstExprKind::Branch:
|
|
if (!RenderBranch(c, expr.as<AstBranch>()))
|
|
return false;
|
|
break;
|
|
case AstExprKind::BranchTable:
|
|
if (!RenderBrTable(c, expr.as<AstBranchTable>()))
|
|
return false;
|
|
break;
|
|
case AstExprKind::Return:
|
|
if (!RenderReturn(c, expr.as<AstReturn>()))
|
|
return false;
|
|
break;
|
|
case AstExprKind::First:
|
|
newLine = false;
|
|
if (!RenderFirst(c, expr.as<AstFirst>()))
|
|
return false;
|
|
break;
|
|
case AstExprKind::CurrentMemory:
|
|
if (!RenderCurrentMemory(c, expr.as<AstCurrentMemory>()))
|
|
return false;
|
|
break;
|
|
case AstExprKind::GrowMemory:
|
|
if (!RenderGrowMemory(c, expr.as<AstGrowMemory>()))
|
|
return false;
|
|
break;
|
|
default:
|
|
MOZ_CRASH("Bad AstExprKind");
|
|
}
|
|
|
|
return !newLine || c.buffer.append("\n");
|
|
}
|
|
|
|
static bool
|
|
RenderSignature(WasmRenderContext& c, const AstSig& sig, const AstNameVector* maybeLocals = nullptr)
|
|
{
|
|
uint32_t paramsNum = sig.args().length();
|
|
|
|
if (maybeLocals) {
|
|
for (uint32_t i = 0; i < paramsNum; i++) {
|
|
if (!c.buffer.append(" (param "))
|
|
return false;
|
|
const AstName& name = (*maybeLocals)[i];
|
|
if (!name.empty()) {
|
|
if (!RenderName(c, name))
|
|
return false;
|
|
if (!c.buffer.append(" "))
|
|
return false;
|
|
}
|
|
ValType arg = sig.args()[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++) {
|
|
if (!c.buffer.append(" "))
|
|
return false;
|
|
ValType arg = sig.args()[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
|
|
RenderTypeSection(WasmRenderContext& 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 (!RenderIndent(c))
|
|
return false;
|
|
if (!c.buffer.append("(type"))
|
|
return false;
|
|
if (!sig->name().empty()) {
|
|
if (!c.buffer.append(" "))
|
|
return false;
|
|
if (!RenderName(c, sig->name()))
|
|
return false;
|
|
}
|
|
if (!c.buffer.append(" (func"))
|
|
return false;
|
|
if (!RenderSignature(c, *sig))
|
|
return false;
|
|
if (!c.buffer.append("))\n"))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
RenderLimits(WasmRenderContext& c, const Limits& limits)
|
|
{
|
|
if (!RenderInt32(c, limits.initial))
|
|
return false;
|
|
if (limits.maximum) {
|
|
if (!c.buffer.append(" "))
|
|
return false;
|
|
if (!RenderInt32(c, *limits.maximum))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
RenderResizableTable(WasmRenderContext& c, const Limits& table)
|
|
{
|
|
if (!c.buffer.append("(table "))
|
|
return false;
|
|
if (!RenderLimits(c, table))
|
|
return false;
|
|
return c.buffer.append(" anyfunc)");
|
|
}
|
|
|
|
static bool
|
|
RenderTableSection(WasmRenderContext& c, const AstModule& module)
|
|
{
|
|
if (!module.hasTable())
|
|
return true;
|
|
for (const AstResizable& table : module.tables()) {
|
|
if (table.imported)
|
|
continue;
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
if (!RenderResizableTable(c, table.limits))
|
|
return false;
|
|
if (!c.buffer.append("\n"))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
RenderInlineExpr(WasmRenderContext& c, AstExpr& expr)
|
|
{
|
|
if (!c.buffer.append("("))
|
|
return false;
|
|
|
|
uint32_t prevIndent = c.indent;
|
|
c.indent = 0;
|
|
if (!RenderExpr(c, expr, /* newLine */ false))
|
|
return false;
|
|
c.indent = prevIndent;
|
|
|
|
return c.buffer.append(")");
|
|
}
|
|
|
|
static bool
|
|
RenderElemSection(WasmRenderContext& c, const AstModule& module)
|
|
{
|
|
for (const AstElemSegment* segment : module.elemSegments()) {
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
if (!c.buffer.append("(elem "))
|
|
return false;
|
|
if (!RenderInlineExpr(c, *segment->offset()))
|
|
return false;
|
|
|
|
for (const AstRef& elem : segment->elems()) {
|
|
if (!c.buffer.append(" "))
|
|
return false;
|
|
|
|
uint32_t index = elem.index();
|
|
AstName name = index < module.funcImportNames().length()
|
|
? module.funcImportNames()[index]
|
|
: module.funcs()[index - module.funcImportNames().length()]->name();
|
|
|
|
if (name.empty()) {
|
|
if (!RenderInt32(c, index))
|
|
return false;
|
|
} else {
|
|
if (!RenderName(c, name))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!c.buffer.append(")\n"))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
RenderGlobal(WasmRenderContext& c, const AstGlobal& glob, bool inImport = false)
|
|
{
|
|
if (!c.buffer.append("(global "))
|
|
return false;
|
|
|
|
if (!inImport) {
|
|
if (!RenderName(c, glob.name()))
|
|
return false;
|
|
if (!c.buffer.append(" "))
|
|
return false;
|
|
}
|
|
|
|
if (glob.isMutable()) {
|
|
if (!c.buffer.append("(mut "))
|
|
return false;
|
|
if (!RenderValType(c, glob.type()))
|
|
return false;
|
|
if (!c.buffer.append(")"))
|
|
return false;
|
|
} else {
|
|
if (!RenderValType(c, glob.type()))
|
|
return false;
|
|
}
|
|
|
|
if (glob.hasInit()) {
|
|
if (!c.buffer.append(" "))
|
|
return false;
|
|
if (!RenderInlineExpr(c, glob.init()))
|
|
return false;
|
|
}
|
|
|
|
if (!c.buffer.append(")"))
|
|
return false;
|
|
|
|
return inImport || c.buffer.append("\n");
|
|
}
|
|
|
|
static bool
|
|
RenderGlobalSection(WasmRenderContext& c, const AstModule& module)
|
|
{
|
|
if (module.globals().empty())
|
|
return true;
|
|
|
|
for (const AstGlobal* global : module.globals()) {
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
if (!RenderGlobal(c, *global))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
RenderResizableMemory(WasmRenderContext& c, Limits memory)
|
|
{
|
|
if (!c.buffer.append("(memory "))
|
|
return false;
|
|
|
|
MOZ_ASSERT(memory.initial % PageSize == 0);
|
|
memory.initial /= PageSize;
|
|
|
|
if (memory.maximum) {
|
|
MOZ_ASSERT(*memory.maximum % PageSize == 0);
|
|
*memory.maximum /= PageSize;
|
|
}
|
|
|
|
if (!RenderLimits(c, memory))
|
|
return false;
|
|
|
|
return c.buffer.append(")");
|
|
}
|
|
|
|
static bool
|
|
RenderImport(WasmRenderContext& c, AstImport& import, const AstModule& module)
|
|
{
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
if (!c.buffer.append("(import "))
|
|
return false;
|
|
if (!RenderName(c, import.name()))
|
|
return false;
|
|
if (!c.buffer.append(" \""))
|
|
return false;
|
|
|
|
const AstName& moduleName = import.module();
|
|
if (!RenderEscapedString(c, moduleName))
|
|
return false;
|
|
|
|
if (!c.buffer.append("\" \""))
|
|
return false;
|
|
|
|
const AstName& fieldName = import.field();
|
|
if (!RenderEscapedString(c, fieldName))
|
|
return false;
|
|
|
|
if (!c.buffer.append("\" "))
|
|
return false;
|
|
|
|
switch (import.kind()) {
|
|
case DefinitionKind::Function: {
|
|
const AstSig* sig = module.sigs()[import.funcSig().index()];
|
|
if (!RenderSignature(c, *sig))
|
|
return false;
|
|
break;
|
|
}
|
|
case DefinitionKind::Table: {
|
|
if (!RenderResizableTable(c, import.limits()))
|
|
return false;
|
|
break;
|
|
}
|
|
case DefinitionKind::Memory: {
|
|
if (!RenderResizableMemory(c, import.limits()))
|
|
return false;
|
|
break;
|
|
}
|
|
case DefinitionKind::Global: {
|
|
const AstGlobal& glob = import.global();
|
|
if (!RenderGlobal(c, glob, /* inImport */ true))
|
|
return false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return c.buffer.append(")\n");
|
|
}
|
|
|
|
static bool
|
|
RenderImportSection(WasmRenderContext& c, const AstModule& module)
|
|
{
|
|
for (AstImport* import : module.imports()) {
|
|
if (!RenderImport(c, *import, module))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
RenderExport(WasmRenderContext& c, AstExport& export_,
|
|
const AstModule::NameVector& funcImportNames,
|
|
const AstModule::FuncVector& funcs)
|
|
{
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
if (!c.buffer.append("(export \""))
|
|
return false;
|
|
if (!RenderEscapedString(c, export_.name()))
|
|
return false;
|
|
if (!c.buffer.append("\" "))
|
|
return false;
|
|
|
|
switch (export_.kind()) {
|
|
case DefinitionKind::Function: {
|
|
uint32_t index = export_.ref().index();
|
|
AstName name = index < funcImportNames.length()
|
|
? funcImportNames[index]
|
|
: funcs[index - funcImportNames.length()]->name();
|
|
if (name.empty()) {
|
|
if (!RenderInt32(c, index))
|
|
return false;
|
|
} else {
|
|
if (!RenderName(c, name))
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
case DefinitionKind::Table: {
|
|
if (!c.buffer.append("table"))
|
|
return false;
|
|
break;
|
|
}
|
|
case DefinitionKind::Memory: {
|
|
if (!c.buffer.append("memory"))
|
|
return false;
|
|
break;
|
|
}
|
|
case DefinitionKind::Global: {
|
|
if (!c.buffer.append("global"))
|
|
return false;
|
|
if (!RenderRef(c, export_.ref()))
|
|
return false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return c.buffer.append(")\n");
|
|
}
|
|
|
|
static bool
|
|
RenderExportSection(WasmRenderContext& c, const AstModule::ExportVector& exports,
|
|
const AstModule::NameVector& funcImportNames,
|
|
const AstModule::FuncVector& funcs)
|
|
{
|
|
uint32_t numExports = exports.length();
|
|
for (uint32_t i = 0; i < numExports; i++) {
|
|
if (!RenderExport(c, *exports[i], funcImportNames, funcs))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
RenderFunctionBody(WasmRenderContext& c, AstFunc& func, const AstModule::SigVector& sigs)
|
|
{
|
|
const AstSig* sig = sigs[func.sig().index()];
|
|
|
|
size_t startExprIndex = c.maybeSourceMap ? c.maybeSourceMap->exprlocs().length() : 0;
|
|
uint32_t startLineno = c.buffer.lineno();
|
|
|
|
uint32_t argsNum = sig->args().length();
|
|
uint32_t localsNum = func.vars().length();
|
|
if (localsNum > 0) {
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
for (uint32_t i = 0; i < localsNum; i++) {
|
|
if (!c.buffer.append("(local "))
|
|
return false;
|
|
const AstName& name = func.locals()[argsNum + i];
|
|
if (!name.empty()) {
|
|
if (!RenderName(c, name))
|
|
return false;
|
|
if (!c.buffer.append(" "))
|
|
return false;
|
|
}
|
|
ValType local = func.vars()[i];
|
|
if (!RenderValType(c, local))
|
|
return false;
|
|
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 (!RenderExpr(c, *func.body()[i]))
|
|
return false;
|
|
}
|
|
|
|
size_t endExprIndex = c.maybeSourceMap ? c.maybeSourceMap->exprlocs().length() : 0;
|
|
uint32_t endLineno = c.buffer.lineno();
|
|
|
|
if (c.maybeSourceMap) {
|
|
if (!c.maybeSourceMap->functionlocs().emplaceBack(startExprIndex, endExprIndex,
|
|
startLineno, endLineno))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
RenderCodeSection(WasmRenderContext& 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 (!RenderIndent(c))
|
|
return false;
|
|
if (!c.buffer.append("(func "))
|
|
return false;
|
|
if (!func->name().empty()) {
|
|
if (!RenderName(c, func->name()))
|
|
return false;
|
|
}
|
|
|
|
if (!RenderSignature(c, *sig, &(func->locals())))
|
|
return false;
|
|
if (!c.buffer.append("\n"))
|
|
return false;
|
|
|
|
c.currentFuncIndex = funcIndex;
|
|
|
|
c.indent++;
|
|
if (!RenderFunctionBody(c, *func, sigs))
|
|
return false;
|
|
c.indent--;
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
if (!c.buffer.append(")\n"))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
RenderMemorySection(WasmRenderContext& c, const AstModule& module)
|
|
{
|
|
if (!module.hasMemory())
|
|
return true;
|
|
|
|
for (const AstResizable& memory : module.memories()) {
|
|
if (memory.imported)
|
|
continue;
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
if (!RenderResizableMemory(c, memory.limits))
|
|
return false;
|
|
if (!c.buffer.append("\n"))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
RenderDataSection(WasmRenderContext& c, const AstModule& module)
|
|
{
|
|
uint32_t numSegments = module.dataSegments().length();
|
|
if (!numSegments)
|
|
return true;
|
|
|
|
for (const AstDataSegment* seg : module.dataSegments()) {
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
if (!c.buffer.append("(data "))
|
|
return false;
|
|
if (!RenderInlineExpr(c, *seg->offset()))
|
|
return false;
|
|
if (!c.buffer.append("\n"))
|
|
return false;
|
|
|
|
c.indent++;
|
|
for (const AstName& fragment : seg->fragments()) {
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
if (!c.buffer.append("\""))
|
|
return false;
|
|
if (!RenderEscapedString(c, fragment))
|
|
return false;
|
|
if (!c.buffer.append("\"\n"))
|
|
return false;
|
|
}
|
|
c.indent--;
|
|
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
if (!c.buffer.append(")\n"))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
RenderStartSection(WasmRenderContext& c, AstModule& module)
|
|
{
|
|
if (!module.hasStartFunc())
|
|
return true;
|
|
|
|
if (!RenderIndent(c))
|
|
return false;
|
|
if (!c.buffer.append("(start "))
|
|
return false;
|
|
if (!RenderRef(c, module.startFunc().func()))
|
|
return false;
|
|
if (!c.buffer.append(")\n"))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
RenderModule(WasmRenderContext& c, AstModule& module)
|
|
{
|
|
if (!c.buffer.append("(module\n"))
|
|
return false;
|
|
|
|
c.indent++;
|
|
|
|
if (!RenderTypeSection(c, module.sigs()))
|
|
return false;
|
|
|
|
if (!RenderImportSection(c, module))
|
|
return false;
|
|
|
|
if (!RenderTableSection(c, module))
|
|
return false;
|
|
|
|
if (!RenderMemorySection(c, module))
|
|
return false;
|
|
|
|
if (!RenderGlobalSection(c, module))
|
|
return false;
|
|
|
|
if (!RenderExportSection(c, module.exports(), module.funcImportNames(), module.funcs()))
|
|
return false;
|
|
|
|
if (!RenderStartSection(c, module))
|
|
return false;
|
|
|
|
if (!RenderElemSection(c, module))
|
|
return false;
|
|
|
|
if (!RenderCodeSection(c, module.funcs(), module.sigs()))
|
|
return false;
|
|
|
|
if (!RenderDataSection(c, module))
|
|
return false;
|
|
|
|
c.indent--;
|
|
|
|
if (!c.buffer.append(")"))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
#undef MAP_AST_EXPR
|
|
|
|
/*****************************************************************************/
|
|
// Top-level functions
|
|
|
|
bool
|
|
wasm::BinaryToText(JSContext* cx, const uint8_t* bytes, size_t length, StringBuffer& buffer, GeneratedSourceMap* sourceMap)
|
|
{
|
|
LifoAlloc lifo(AST_LIFO_DEFAULT_CHUNK_SIZE);
|
|
|
|
AstModule* module;
|
|
if (!BinaryToAst(cx, bytes, length, lifo, &module))
|
|
return false;
|
|
|
|
WasmPrintBuffer buf(buffer);
|
|
WasmRenderContext c(cx, module, buf, sourceMap);
|
|
|
|
if (!RenderModule(c, *module)) {
|
|
if (!cx->isExceptionPending())
|
|
ReportOutOfMemory(cx);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|