--- a/js/src/asmjs/WasmAST.h
+++ b/js/src/asmjs/WasmAST.h
@@ -166,18 +166,28 @@ class AstSig : public AstBase
static HashNumber hash(Lookup sig) {
return AddContainerToHash(sig.args(), HashNumber(sig.ret()));
}
static bool match(const AstSig* lhs, Lookup rhs) {
return *lhs == rhs;
}
};
+const uint32_t AstNodeUnknownOffset = 0;
+
class AstNode : public AstBase
-{};
+{
+ uint32_t offset_; // if applicable, offset in the binary format file
+
+ public:
+ AstNode() : offset_(AstNodeUnknownOffset) {}
+
+ uint32_t offset() const { return offset_; }
+ void setOffset(uint32_t offset) { offset_ = offset; }
+};
enum class AstExprKind
{
BinaryOperator,
Block,
Branch,
BranchTable,
Call,
--- a/js/src/asmjs/WasmBinaryToAST.cpp
+++ b/js/src/asmjs/WasmBinaryToAST.cpp
@@ -700,297 +700,413 @@ AstDecodeReturn(AstDecodeContext& c)
c.iter().setResult(AstDecodeStackItem(ret, result.expr ? 1 : 0));
return true;
}
static bool
AstDecodeExpr(AstDecodeContext& c)
{
+ uint32_t exprOffset = c.iter().currentOffset();
Expr expr;
if (!c.iter().readExpr(&expr))
return false;
AstExpr* tmp;
switch (expr) {
case Expr::Nop:
if (!c.iter().readNullary())
return false;
tmp = new(c.lifo) AstNop();
if (!tmp)
return false;
c.iter().setResult(AstDecodeStackItem(tmp));
- return true;
+ break;
case Expr::Call:
- return AstDecodeCall(c);
+ if (!AstDecodeCall(c))
+ return false;
+ break;
case Expr::CallIndirect:
- return AstDecodeCallIndirect(c);
+ if (!AstDecodeCallIndirect(c))
+ return false;
+ break;
case Expr::CallImport:
- return AstDecodeCallImport(c);
+ if (!AstDecodeCallImport(c))
+ return false;
+ break;
case Expr::I32Const:
int32_t i32;
if (!c.iter().readI32Const(&i32))
return false;
tmp = new(c.lifo) AstConst(Val((uint32_t)i32));
if (!tmp)
return false;
c.iter().setResult(AstDecodeStackItem(tmp));
- return true;
+ break;
case Expr::I64Const:
int64_t i64;
if (!c.iter().readI64Const(&i64))
return false;
tmp = new(c.lifo) AstConst(Val((uint64_t)i64));
if (!tmp)
return false;
c.iter().setResult(AstDecodeStackItem(tmp));
- return true;
+ break;
case Expr::F32Const:
float f32;
if (!c.iter().readF32Const(&f32))
return false;
tmp = new(c.lifo) AstConst(Val(f32));
if (!tmp)
return false;
c.iter().setResult(AstDecodeStackItem(tmp));
- return true;
+ break;
case Expr::F64Const:
double f64;
if (!c.iter().readF64Const(&f64))
return false;
tmp = new(c.lifo) AstConst(Val(f64));
if (!tmp)
return false;
c.iter().setResult(AstDecodeStackItem(tmp));
- return true;
+ break;
case Expr::GetLocal:
- return AstDecodeGetLocal(c);
+ if (!AstDecodeGetLocal(c))
+ return false;
+ break;
case Expr::SetLocal:
- return AstDecodeSetLocal(c);
+ if (!AstDecodeSetLocal(c))
+ return false;
+ break;
case Expr::Select:
- return AstDecodeSelect(c);
+ if (!AstDecodeSelect(c))
+ return false;
+ break;
case Expr::Block:
case Expr::Loop:
- return AstDecodeBlock(c, expr);
+ if (!AstDecodeBlock(c, expr))
+ return false;
+ break;
case Expr::If:
- return AstDecodeIf(c);
+ if (!AstDecodeIf(c))
+ return false;
+ break;
case Expr::Else:
- return AstDecodeElse(c);
+ if (!AstDecodeElse(c))
+ return false;
+ break;
case Expr::End:
- return AstDecodeEnd(c);
+ if (!AstDecodeEnd(c))
+ return false;
+ break;
case Expr::I32Clz:
case Expr::I32Ctz:
case Expr::I32Popcnt:
- return AstDecodeUnary(c, ValType::I32, expr);
+ if (!AstDecodeUnary(c, ValType::I32, expr))
+ return false;
+ break;
case Expr::I64Clz:
case Expr::I64Ctz:
case Expr::I64Popcnt:
- return AstDecodeUnary(c, ValType::I64, expr);
+ if (!AstDecodeUnary(c, ValType::I64, expr))
+ return false;
+ break;
case Expr::F32Abs:
case Expr::F32Neg:
case Expr::F32Ceil:
case Expr::F32Floor:
case Expr::F32Sqrt:
case Expr::F32Trunc:
case Expr::F32Nearest:
- return AstDecodeUnary(c, ValType::F32, expr);
+ if (!AstDecodeUnary(c, ValType::F32, expr))
+ return false;
+ break;
case Expr::F64Abs:
case Expr::F64Neg:
case Expr::F64Ceil:
case Expr::F64Floor:
case Expr::F64Sqrt:
case Expr::F64Trunc:
case Expr::F64Nearest:
- return AstDecodeUnary(c, ValType::F64, expr);
+ if (!AstDecodeUnary(c, ValType::F64, expr))
+ return false;
+ break;
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:
case Expr::I32Rotl:
case Expr::I32Rotr:
- return AstDecodeBinary(c, ValType::I32, expr);
+ if (!AstDecodeBinary(c, ValType::I32, expr))
+ return false;
+ break;
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:
case Expr::I64Rotl:
case Expr::I64Rotr:
- return AstDecodeBinary(c, ValType::I64, expr);
+ if (!AstDecodeBinary(c, ValType::I64, expr))
+ return false;
+ break;
case Expr::F32Add:
case Expr::F32Sub:
case Expr::F32Mul:
case Expr::F32Div:
case Expr::F32Min:
case Expr::F32Max:
case Expr::F32CopySign:
- return AstDecodeBinary(c, ValType::F32, expr);
+ if (!AstDecodeBinary(c, ValType::F32, expr))
+ return false;
+ break;
case Expr::F64Add:
case Expr::F64Sub:
case Expr::F64Mul:
case Expr::F64Div:
case Expr::F64Min:
case Expr::F64Max:
case Expr::F64CopySign:
- return AstDecodeBinary(c, ValType::F64, expr);
+ if (!AstDecodeBinary(c, ValType::F64, expr))
+ return false;
+ break;
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 AstDecodeComparison(c, ValType::I32, expr);
+ if (!AstDecodeComparison(c, ValType::I32, expr))
+ return false;
+ break;
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 AstDecodeComparison(c, ValType::I64, expr);
+ if (!AstDecodeComparison(c, ValType::I64, expr))
+ return false;
+ break;
case Expr::F32Eq:
case Expr::F32Ne:
case Expr::F32Lt:
case Expr::F32Le:
case Expr::F32Gt:
case Expr::F32Ge:
- return AstDecodeComparison(c, ValType::F32, expr);
+ if (!AstDecodeComparison(c, ValType::F32, expr))
+ return false;
+ break;
case Expr::F64Eq:
case Expr::F64Ne:
case Expr::F64Lt:
case Expr::F64Le:
case Expr::F64Gt:
case Expr::F64Ge:
- return AstDecodeComparison(c, ValType::F64, expr);
+ if (!AstDecodeComparison(c, ValType::F64, expr))
+ return false;
+ break;
case Expr::I32Eqz:
- return AstDecodeConversion(c, ValType::I32, ValType::I32, expr);
+ if (!AstDecodeConversion(c, ValType::I32, ValType::I32, expr))
+ return false;
+ break;
case Expr::I64Eqz:
- return AstDecodeConversion(c, ValType::I64, ValType::I32, expr);
+ if (!AstDecodeConversion(c, ValType::I64, ValType::I32, expr))
+ return false;
+ break;
case Expr::I32TruncSF32:
case Expr::I32TruncUF32:
case Expr::I32ReinterpretF32:
- return AstDecodeConversion(c, ValType::F32, ValType::I32, expr);
+ if (!AstDecodeConversion(c, ValType::F32, ValType::I32, expr))
+ return false;
+ break;
case Expr::I32TruncSF64:
case Expr::I32TruncUF64:
- return AstDecodeConversion(c, ValType::F64, ValType::I32, expr);
+ if (!AstDecodeConversion(c, ValType::F64, ValType::I32, expr))
+ return false;
+ break;
case Expr::I64ExtendSI32:
case Expr::I64ExtendUI32:
- return AstDecodeConversion(c, ValType::I32, ValType::I64, expr);
+ if (!AstDecodeConversion(c, ValType::I32, ValType::I64, expr))
+ return false;
+ break;
case Expr::I64TruncSF32:
case Expr::I64TruncUF32:
- return AstDecodeConversion(c, ValType::F32, ValType::I64, expr);
+ if (!AstDecodeConversion(c, ValType::F32, ValType::I64, expr))
+ return false;
+ break;
case Expr::I64TruncSF64:
case Expr::I64TruncUF64:
case Expr::I64ReinterpretF64:
- return AstDecodeConversion(c, ValType::F64, ValType::I64, expr);
+ if (!AstDecodeConversion(c, ValType::F64, ValType::I64, expr))
+ return false;
+ break;
case Expr::F32ConvertSI32:
case Expr::F32ConvertUI32:
case Expr::F32ReinterpretI32:
- return AstDecodeConversion(c, ValType::I32, ValType::F32, expr);
+ if (!AstDecodeConversion(c, ValType::I32, ValType::F32, expr))
+ return false;
+ break;
case Expr::F32ConvertSI64:
case Expr::F32ConvertUI64:
- return AstDecodeConversion(c, ValType::I64, ValType::F32, expr);
+ if (!AstDecodeConversion(c, ValType::I64, ValType::F32, expr))
+ return false;
+ break;
case Expr::F32DemoteF64:
- return AstDecodeConversion(c, ValType::F64, ValType::F32, expr);
+ if (!AstDecodeConversion(c, ValType::F64, ValType::F32, expr))
+ return false;
+ break;
case Expr::F64ConvertSI32:
case Expr::F64ConvertUI32:
- return AstDecodeConversion(c, ValType::I32, ValType::F64, expr);
+ if (!AstDecodeConversion(c, ValType::I32, ValType::F64, expr))
+ return false;
+ break;
case Expr::F64ConvertSI64:
case Expr::F64ConvertUI64:
case Expr::F64ReinterpretI64:
- return AstDecodeConversion(c, ValType::I64, ValType::F64, expr);
+ if (!AstDecodeConversion(c, ValType::I64, ValType::F64, expr))
+ return false;
+ break;
case Expr::F64PromoteF32:
- return AstDecodeConversion(c, ValType::F32, ValType::F64, expr);
+ if (!AstDecodeConversion(c, ValType::F32, ValType::F64, expr))
+ return false;
+ break;
case Expr::I32Load8S:
case Expr::I32Load8U:
- return AstDecodeLoad(c, ValType::I32, 1, expr);
+ if (!AstDecodeLoad(c, ValType::I32, 1, expr))
+ return false;
+ break;
case Expr::I32Load16S:
case Expr::I32Load16U:
- return AstDecodeLoad(c, ValType::I32, 2, expr);
+ if (!AstDecodeLoad(c, ValType::I32, 2, expr))
+ return false;
+ break;
case Expr::I32Load:
- return AstDecodeLoad(c, ValType::I32, 4, expr);
+ if (!AstDecodeLoad(c, ValType::I32, 4, expr))
+ return false;
+ break;
case Expr::I64Load8S:
case Expr::I64Load8U:
- return AstDecodeLoad(c, ValType::I64, 1, expr);
+ if (!AstDecodeLoad(c, ValType::I64, 1, expr))
+ return false;
+ break;
case Expr::I64Load16S:
case Expr::I64Load16U:
- return AstDecodeLoad(c, ValType::I64, 2, expr);
+ if (!AstDecodeLoad(c, ValType::I64, 2, expr))
+ return false;
+ break;
case Expr::I64Load32S:
case Expr::I64Load32U:
- return AstDecodeLoad(c, ValType::I64, 4, expr);
+ if (!AstDecodeLoad(c, ValType::I64, 4, expr))
+ return false;
+ break;
case Expr::I64Load:
- return AstDecodeLoad(c, ValType::I64, 8, expr);
+ if (!AstDecodeLoad(c, ValType::I64, 8, expr))
+ return false;
+ break;
case Expr::F32Load:
- return AstDecodeLoad(c, ValType::F32, 4, expr);
+ if (!AstDecodeLoad(c, ValType::F32, 4, expr))
+ return false;
+ break;
case Expr::F64Load:
- return AstDecodeLoad(c, ValType::F64, 8, expr);
+ if (!AstDecodeLoad(c, ValType::F64, 8, expr))
+ return false;
+ break;
case Expr::I32Store8:
- return AstDecodeStore(c, ValType::I32, 1, expr);
+ if (!AstDecodeStore(c, ValType::I32, 1, expr))
+ return false;
+ break;
case Expr::I32Store16:
- return AstDecodeStore(c, ValType::I32, 2, expr);
+ if (!AstDecodeStore(c, ValType::I32, 2, expr))
+ return false;
+ break;
case Expr::I32Store:
- return AstDecodeStore(c, ValType::I32, 4, expr);
+ if (!AstDecodeStore(c, ValType::I32, 4, expr))
+ return false;
+ break;
case Expr::I64Store8:
- return AstDecodeStore(c, ValType::I64, 1, expr);
+ if (!AstDecodeStore(c, ValType::I64, 1, expr))
+ return false;
+ break;
case Expr::I64Store16:
- return AstDecodeStore(c, ValType::I64, 2, expr);
+ if (!AstDecodeStore(c, ValType::I64, 2, expr))
+ return false;
+ break;
case Expr::I64Store32:
- return AstDecodeStore(c, ValType::I64, 4, expr);
+ if (!AstDecodeStore(c, ValType::I64, 4, expr))
+ return false;
+ break;
case Expr::I64Store:
- return AstDecodeStore(c, ValType::I64, 8, expr);
+ if (!AstDecodeStore(c, ValType::I64, 8, expr))
+ return false;
+ break;
case Expr::F32Store:
- return AstDecodeStore(c, ValType::F32, 4, expr);
+ if (!AstDecodeStore(c, ValType::F32, 4, expr))
+ return false;
+ break;
case Expr::F64Store:
- return AstDecodeStore(c, ValType::F64, 8, expr);
+ if (!AstDecodeStore(c, ValType::F64, 8, expr))
+ return false;
+ break;
case Expr::Br:
case Expr::BrIf:
- return AstDecodeBranch(c, expr);
+ if (!AstDecodeBranch(c, expr))
+ return false;
+ break;
case Expr::BrTable:
- return AstDecodeBrTable(c);
+ if (!AstDecodeBrTable(c))
+ return false;
+ break;
case Expr::Return:
- return AstDecodeReturn(c);
+ if (!AstDecodeReturn(c))
+ return false;
+ break;
case Expr::Unreachable:
if (!c.iter().readUnreachable())
return false;
tmp = new(c.lifo) AstUnreachable();
if (!tmp)
return false;
c.iter().setResult(AstDecodeStackItem(tmp));
- return true;
+ break;
default:
// Note: it's important not to remove this default since readExpr()
// can return Expr values for which there is no enumerator.
- break;
+ return c.iter().unrecognizedOpcode(expr);
}
- return c.iter().unrecognizedOpcode(expr);
+ AstExpr* lastExpr = c.iter().getResult().expr;
+ if (lastExpr)
+ lastExpr->setOffset(exprOffset);
+ return true;
}
/*****************************************************************************/
// wasm decoding and generation
static bool
AstDecodeTypeSection(AstDecodeContext& c)
{
@@ -1324,16 +1440,17 @@ AstDecodeExportSection(AstDecodeContext&
return AstDecodeFail(c, "export section byte size mismatch");
return true;
}
static bool
AstDecodeFunctionBody(AstDecodeContext &c, uint32_t funcIndex, AstFunc** func)
{
+ uint32_t offset = c.d.currentOffset();
uint32_t bodySize;
if (!c.d.readVarU32(&bodySize))
return AstDecodeFail(c, "expected number of function body bytes");
if (c.d.bytesRemain() < bodySize)
return AstDecodeFail(c, "function body length too big");
const uint8_t* bodyBegin = c.d.currentPosition();
@@ -1398,16 +1515,17 @@ AstDecodeFunctionBody(AstDecodeContext &
AstRef sigRef;
if (!AstDecodeGenerateRef(c, AstName(MOZ_UTF16("type")), sigIndex, &sigRef))
return false;
*func = new(c.lifo) AstFunc(funcName, sigRef, Move(vars), Move(localsNames), Move(body));
if (!*func)
return false;
+ (*func)->setOffset(offset);
return true;
}
static bool
AstDecodeCodeSection(AstDecodeContext &c)
{
uint32_t sectionStart, sectionSize;
--- a/js/src/asmjs/WasmBinaryToExperimentalText.cpp
+++ b/js/src/asmjs/WasmBinaryToExperimentalText.cpp
@@ -53,32 +53,92 @@ enum PrintOperatorPrecedence
NegatePrecedence = 12,
EqzPrecedence = 12,
OperatorPrecedence = 15,
LoadOperatorPrecedence = 15,
CallPrecedence = 15,
GroupPrecedence = 16,
};
+// StringBuffer wrapper to track the position (line and column) within the generated
+// source.
+class WasmPrintBuffer
+{
+ StringBuffer& stringBuffer_;
+ uint32_t lineno_;
+ uint32_t column_;
+
+ public:
+ explicit WasmPrintBuffer(StringBuffer& stringBuffer) : stringBuffer_(stringBuffer), lineno_(1), column_(1) {}
+ inline char processChar(char ch) {
+ if (ch == '\n') {
+ lineno_++; column_ = 1;
+ } else
+ column_++;
+ return ch;
+ }
+ inline char16_t processChar(char16_t ch) {
+ if (ch == '\n') {
+ lineno_++; column_ = 1;
+ } else
+ column_++;
+ return ch;
+ }
+ bool append(const char ch) {
+ return stringBuffer_.append(processChar(ch));
+ }
+ bool append(const char16_t ch) {
+ return stringBuffer_.append(processChar(ch));
+ }
+ bool append(const char* str, size_t length) {
+ for (size_t i = 0; i < length; i++)
+ processChar(str[i]);
+ return stringBuffer_.append(str, length);
+ }
+ bool append(const char16_t* begin, const char16_t* end) {
+ for (const char16_t* p = begin; p != end; p++)
+ processChar(*p);
+ return stringBuffer_.append(begin, end);
+ }
+ bool append(const char16_t* str, size_t length) {
+ return append(str, str + length);
+ }
+ template <size_t ArrayLength>
+ bool append(const char (&array)[ArrayLength]) {
+ return append(array, ArrayLength - 1);
+ }
+ char16_t getChar(size_t index) {
+ return stringBuffer_.getChar(index);
+ }
+ size_t length() {
+ return stringBuffer_.length();
+ }
+ StringBuffer& stringBuffer() { return stringBuffer_; }
+ uint32_t lineno() { return lineno_; }
+ uint32_t column() { return column_; }
+};
+
struct WasmPrintContext
{
JSContext* cx;
AstModule* module;
- StringBuffer& buffer;
+ WasmPrintBuffer& buffer;
const ExperimentalTextFormatting& f;
+ GeneratedSourceMap* maybeSourceMap;
uint32_t indent;
uint32_t currentFuncIndex;
PrintOperatorPrecedence currentPrecedence;
- WasmPrintContext(JSContext* cx, AstModule* module, StringBuffer& buffer, const ExperimentalTextFormatting& f)
+ WasmPrintContext(JSContext* cx, AstModule* module, WasmPrintBuffer& buffer, const ExperimentalTextFormatting& f, GeneratedSourceMap* wasmSourceMap_)
: cx(cx),
module(module),
buffer(buffer),
f(f),
+ maybeSourceMap(wasmSourceMap_),
indent(0),
currentFuncIndex(0),
currentPrecedence(PrintOperatorPrecedence::ExpressionPrecedence)
{}
};
/*****************************************************************************/
// utilities
@@ -118,17 +178,17 @@ PrintIndent(WasmPrintContext& c)
static bool
PrintInt32(WasmPrintContext& c, int32_t num, bool printSign = false)
{
// Negative sign will be printed, printing '+' for non-negative values.
if (printSign && num >= 0) {
if (!c.buffer.append("+"))
return false;
}
- return NumberValueToStringBuffer(c.cx, Int32Value(num), c.buffer);
+ return NumberValueToStringBuffer(c.cx, Int32Value(num), c.buffer.stringBuffer());
}
static bool
PrintInt64(WasmPrintContext& c, int64_t num)
{
if (num < 0 && !c.buffer.append("-"))
return false;
if (!num)
@@ -163,17 +223,17 @@ PrintDouble(WasmPrintContext& c, double
return c.buffer.append("nan");
if (IsInfinite(num)) {
if (num > 0)
return c.buffer.append("infinity");
return c.buffer.append("-infinity");
}
uint32_t startLength = c.buffer.length();
- if (!NumberValueToStringBuffer(c.cx, DoubleValue(num), c.buffer))
+ if (!NumberValueToStringBuffer(c.cx, DoubleValue(num), c.buffer.stringBuffer()))
return false;
MOZ_ASSERT(startLength < c.buffer.length());
// Checking if we need to end number with '.0'.
for (uint32_t i = c.buffer.length() - 1; i >= startLength; i--) {
char16_t ch = c.buffer.getChar(i);
if (ch == '.' || ch == 'e')
return true;
@@ -1264,16 +1324,23 @@ PrintReturn(WasmPrintContext& c, AstRetu
}
return true;
}
static bool
PrintExpr(WasmPrintContext& c, AstExpr& expr)
{
+ if (c.maybeSourceMap) {
+ uint32_t lineno = c.buffer.lineno();
+ uint32_t column = c.buffer.column();
+ if (!c.maybeSourceMap->exprlocs().emplaceBack(lineno, column, expr.offset()))
+ return false;
+ }
+
switch (expr.kind()) {
case AstExprKind::Nop:
return PrintNop(c, expr.as<AstNop>());
case AstExprKind::Unreachable:
return PrintUnreachable(c, expr.as<AstUnreachable>());
case AstExprKind::Call:
return PrintCall(c, expr.as<AstCall>());
case AstExprKind::CallIndirect:
@@ -1533,16 +1600,19 @@ PrintExportSection(WasmPrintContext& c,
}
static bool
PrintFunctionBody(WasmPrintContext& c, AstFunc& func, const AstModule::SigVector& sigs)
{
const AstSig* sig = sigs[func.sig().index()];
c.indent++;
+ 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 (!PrintIndent(c))
return false;
if (!c.buffer.append("var "))
return false;
for (uint32_t i = 0; i < localsNum; i++) {
@@ -1569,16 +1639,23 @@ PrintFunctionBody(WasmPrintContext& c, A
uint32_t exprsNum = func.body().length();
for (uint32_t i = 0; i < exprsNum; i++) {
if (!PrintBlockLevelExpr(c, *func.body()[i], i + 1 == exprsNum))
return false;
}
c.indent--;
+ 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
PrintCodeSection(WasmPrintContext& c, const AstModule::FuncVector& funcs, const AstModule::SigVector& sigs)
{
uint32_t numFuncBodies = funcs.length();
for (uint32_t funcIndex = 0; funcIndex < numFuncBodies; funcIndex++) {
@@ -1694,26 +1771,28 @@ PrintModule(WasmPrintContext& c, AstModu
return true;
}
/*****************************************************************************/
// Top-level functions
bool
wasm::BinaryToExperimentalText(JSContext* cx, const uint8_t* bytes, size_t length,
- StringBuffer& buffer, const ExperimentalTextFormatting& formatting)
+ StringBuffer& buffer, const ExperimentalTextFormatting& formatting,
+ GeneratedSourceMap* sourceMap)
{
LifoAlloc lifo(AST_LIFO_DEFAULT_CHUNK_SIZE);
AstModule* module;
if (!BinaryToAst(cx, bytes, length, lifo, &module))
return false;
- WasmPrintContext c(cx, module, buffer, formatting);
+ WasmPrintBuffer buf(buffer);
+ WasmPrintContext c(cx, module, buf, formatting, sourceMap);
if (!PrintModule(c, *module)) {
if (!cx->isExceptionPending())
ReportOutOfMemory(cx);
return false;
}
return true;
--- a/js/src/asmjs/WasmBinaryToExperimentalText.h
+++ b/js/src/asmjs/WasmBinaryToExperimentalText.h
@@ -14,16 +14,18 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef wasm_binary_to_experimental_text_h
#define wasm_binary_to_experimental_text_h
+#include "mozilla/Vector.h"
+
#include "NamespaceImports.h"
#include "gc/Rooting.h"
#include "js/Class.h"
namespace js {
class StringBuffer;
@@ -38,20 +40,72 @@ struct ExperimentalTextFormatting
ExperimentalTextFormatting()
: allowAsciiOperators(true),
reduceParens(true),
groupBlocks(true)
{}
};
+// The generated source location for the AST node/expression. The offset field refers
+// an offset in an binary format file.
+struct ExprLoc
+{
+ uint32_t lineno;
+ uint32_t column;
+ uint32_t offset;
+ ExprLoc() : lineno(0), column(0), offset(0) {}
+ ExprLoc(uint32_t lineno_, uint32_t column_, uint32_t offset_) : lineno(lineno_), column(column_), offset(offset_) {}
+};
+
+typedef mozilla::Vector<ExprLoc, 0, TempAllocPolicy> ExprLocVector;
+
+// The generated source WebAssembly function lines and expressions ranges.
+struct FunctionLoc
+{
+ size_t startExprsIndex;
+ size_t endExprsIndex;
+ uint32_t startLineno;
+ uint32_t endLineno;
+ FunctionLoc(size_t startExprsIndex_, size_t endExprsIndex_, uint32_t startLineno_, uint32_t endLineno_)
+ : startExprsIndex(startExprsIndex_),
+ endExprsIndex(endExprsIndex_),
+ startLineno(startLineno_),
+ endLineno(endLineno_)
+ {}
+};
+
+typedef mozilla::Vector<FunctionLoc, 0, TempAllocPolicy> FunctionLocVector;
+
+// The generated source map for WebAssembly binary file. This map is generated during
+// building the text buffer (see BinaryToExperimentalText).
+class GeneratedSourceMap
+{
+ ExprLocVector exprlocs_;
+ FunctionLocVector functionlocs_;
+ uint32_t totalLines_;
+
+ public:
+ explicit GeneratedSourceMap(JSContext* cx)
+ : exprlocs_(cx),
+ functionlocs_(cx),
+ totalLines_(0)
+ {}
+ ExprLocVector& exprlocs() { return exprlocs_; }
+ FunctionLocVector& functionlocs() { return functionlocs_; }
+
+ uint32_t totalLines() { return totalLines_; }
+ void setTotalLines(uint32_t val) { totalLines_ = val; }
+};
+
// Translate the given binary representation of a wasm module into the module's textual
// representation.
MOZ_MUST_USE bool
BinaryToExperimentalText(JSContext* cx, const uint8_t* bytes, size_t length, StringBuffer& buffer,
- const ExperimentalTextFormatting& formatting = ExperimentalTextFormatting());
+ const ExperimentalTextFormatting& formatting = ExperimentalTextFormatting(),
+ GeneratedSourceMap* sourceMap = nullptr);
} // namespace wasm
} // namespace js
#endif // namespace wasm_binary_to_experimental_text_h
--- a/js/src/asmjs/WasmInstance.cpp
+++ b/js/src/asmjs/WasmInstance.cpp
@@ -623,36 +623,96 @@ const char experimentalWarning[] =
"|(_ o _) | ||___| / |(_ o _). | |\\_ /| |\n"
"| (_,_) \\ | | _.-` | (_,_). '. | _( )_/ | |\n"
"| |/ \\| |.' _ |.---. \\ :| (_ o _) | |\n"
"| ' /\\ ` || _( )_ |\\ `-' || (_,_) | |\n"
"| / \\ |\\ (_ o _) / \\ / | | | |\n"
"`---' `---` '.(_,_).' `-...-' '--' '--'\n"
"text support (Work In Progress):\n\n";
+const size_t experimentalWarningLinesCount = 12;
+
const char enabledMessage[] =
"Restart with developer tools open to view WebAssembly source";
JSString*
Instance::createText(JSContext* cx)
{
StringBuffer buffer(cx);
if (maybeBytecode_) {
const Bytes& bytes = maybeBytecode_->bytes;
if (!buffer.append(experimentalWarning))
return nullptr;
- if (!BinaryToExperimentalText(cx, bytes.begin(), bytes.length(), buffer))
+ maybeSourceMap_.reset(cx->runtime()->new_<GeneratedSourceMap>(cx));
+ if (!maybeSourceMap_)
+ return nullptr;
+ if (!BinaryToExperimentalText(cx, bytes.begin(), bytes.length(), buffer,
+ ExperimentalTextFormatting(), maybeSourceMap_.get()))
return nullptr;
+#if DEBUG
+ // Checking source map invariant: expression and function locations must be sorted
+ // by line number.
+ uint32_t lastLineno = 0;
+ for (size_t i = 0; i < maybeSourceMap_->exprlocs().length(); i++) {
+ MOZ_ASSERT(lastLineno <= maybeSourceMap_->exprlocs()[i].lineno);
+ lastLineno = maybeSourceMap_->exprlocs()[i].lineno;
+ }
+ lastLineno = 0;
+ for (size_t i = 0; i < maybeSourceMap_->functionlocs().length(); i++) {
+ MOZ_ASSERT(lastLineno <= maybeSourceMap_->functionlocs()[i].startLineno &&
+ maybeSourceMap_->functionlocs()[i].startLineno <=
+ maybeSourceMap_->functionlocs()[i].endLineno);
+ lastLineno = maybeSourceMap_->functionlocs()[i].endLineno + 1;
+ }
+#endif
} else {
if (!buffer.append(enabledMessage))
return nullptr;
}
return buffer.finishString();
}
+struct GeneratedSourceMapLinenoComparator
+{
+ int operator()(const ExprLoc& loc) const {
+ return lineno == loc.lineno ? 0 : lineno < loc.lineno ? -1 : 1;
+ }
+ explicit GeneratedSourceMapLinenoComparator(uint32_t lineno_) : lineno(lineno_) {}
+ const uint32_t lineno;
+};
+
+bool
+Instance::getLineOffsets(size_t lineno, Vector<uint32_t>& offsets)
+{
+ // TODO Ensure text was generated?
+ if (!maybeSourceMap_)
+ return false;
+
+ if (lineno < experimentalWarningLinesCount)
+ return true;
+ lineno -= experimentalWarningLinesCount;
+
+ ExprLocVector& exprlocs = maybeSourceMap_->exprlocs();
+
+ // Binary search for the expression with the specified line number and
+ // rewind to the first expression, if more than one expression on the same line.
+ size_t match;
+ if (!BinarySearchIf(exprlocs, 0, exprlocs.length(),
+ GeneratedSourceMapLinenoComparator(lineno), &match))
+ return true;
+ while (match > 0 && exprlocs[match - 1].lineno == lineno)
+ match--;
+ // Returning all expression offsets that were printed on the specified line.
+ for (size_t i = match; i < exprlocs.length() && exprlocs[i].lineno == lineno; i++) {
+ if (!offsets.append(exprlocs[i].offset))
+ return false;
+ }
+ return true;
+}
+
bool
Instance::getFuncName(JSContext* cx, uint32_t funcIndex, TwoByteName* name) const
{
const Bytes* maybeBytecode = maybeBytecode_ ? &maybeBytecode_.get()->bytes : nullptr;
return metadata_->getFuncName(cx, maybeBytecode, funcIndex, name);
}
JSAtom*
--- a/js/src/asmjs/WasmInstance.h
+++ b/js/src/asmjs/WasmInstance.h
@@ -25,16 +25,18 @@
namespace js {
class WasmActivation;
class WasmInstanceObject;
namespace wasm {
+class GeneratedSourceMap;
+
// Instance represents a wasm instance and provides all the support for runtime
// execution of code in the instance. Instances share various immutable data
// structures with the Module from which they were instantiated and other
// instances instantiated from the same Module. However, an Instance has no
// direct reference to its source Module which allows a Module to be destroyed
// while it still has live Instances.
class Instance
@@ -43,16 +45,18 @@ class Instance
const SharedMetadata metadata_;
const SharedBytes maybeBytecode_;
GCPtrWasmMemoryObject memory_;
SharedTableVector tables_;
bool profilingEnabled_;
CacheableCharsVector funcLabels_;
+ UniquePtr<GeneratedSourceMap> maybeSourceMap_;
+
// Internal helpers:
uint8_t** addressOfMemoryBase() const;
void** addressOfTableBase(size_t tableIndex) const;
FuncImportExit& funcImportToExit(const FuncImport& fi);
MOZ_MUST_USE bool toggleProfiling(JSContext* cx);
// An instance keeps track of its innermost WasmActivation. A WasmActivation
// is pushed for the duration of each call of an export.
@@ -98,17 +102,20 @@ class Instance
bool profilingEnabled() const { return profilingEnabled_; }
const char* profilingLabel(uint32_t funcIndex) const { return funcLabels_[funcIndex].get(); }
// If the source binary was saved (by passing the bytecode to the
// constructor), this method will render the binary as text. Otherwise, a
// diagnostic string will be returned.
+ // Text format support functions:
+
JSString* createText(JSContext* cx);
+ bool getLineOffsets(size_t lineno, Vector<uint32_t>& offsets);
// Return the name associated with a given function index, or generate one
// if none was given by the module.
bool getFuncName(JSContext* cx, uint32_t funcIndex, TwoByteName* name) const;
JSAtom* getFuncAtom(JSContext* cx, uint32_t funcIndex) const;
// Initially, calls to imports in wasm code call out through the generic
--- a/js/src/jit-test/tests/debug/wasm-04.js
+++ b/js/src/jit-test/tests/debug/wasm-04.js
@@ -21,15 +21,14 @@ assertThrowsInstanceOf(() => s.url, Erro
assertThrowsInstanceOf(() => s.startLine, Error);
assertThrowsInstanceOf(() => s.lineCount, Error);
assertThrowsInstanceOf(() => s.sourceStart, Error);
assertThrowsInstanceOf(() => s.sourceLength, Error);
assertThrowsInstanceOf(() => s.global, Error);
assertThrowsInstanceOf(() => s.getChildScripts(), Error);
assertThrowsInstanceOf(() => s.getAllOffsets(), Error);
assertThrowsInstanceOf(() => s.getAllColumnOffsets(), Error);
-assertThrowsInstanceOf(() => s.getLineOffsets(0), Error);
assertThrowsInstanceOf(() => s.setBreakpoint(0, { hit: () => {} }), Error);
assertThrowsInstanceOf(() => s.getBreakpoint(0), Error);
assertThrowsInstanceOf(() => s.clearBreakpoint({}), Error);
assertThrowsInstanceOf(() => s.clearAllBreakpoints(), Error);
assertThrowsInstanceOf(() => s.isInCatchScope(0), Error);
assertThrowsInstanceOf(() => s.getOffsetsCoverage(), Error);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-05.js
@@ -0,0 +1,48 @@
+// Tests that wasm module scripts have text line to bytecode offset information
+// when source text is generated.
+
+load(libdir + "asserts.js");
+
+if (!wasmIsSupported())
+ quit();
+
+// Checking if experimental format generates internal source map to binary file
+// by querying debugger scripts getLineOffsets.
+// (Notice that the source map will not be produced by wasmBinaryToText)
+function getAllOffsets(wast) {
+ var sandbox = newGlobal('');
+ var dbg = new Debugger();
+ dbg.addDebuggee(sandbox);
+ sandbox.eval(`
+ var wasm = wasmTextToBinary('${wast}');
+ var m = Wasm.instantiateModule(wasm);
+ `);
+ var wasmScript = dbg.findScripts().filter(s => s.format == 'wasm')[0];
+ var lines = wasmScript.source.text.split('\n');
+ return lines.map((l, n) => { return { str: l, offsets: wasmScript.getLineOffsets(n + 1) }; });
+}
+
+var result1 = getAllOffsets('(module \
+ (func (nop)) \
+ (func (f32.sqrt (f32.add (f32.const 1.0) (f32.const 2.0)))) \
+)');
+
+var nopLine = result1.filter(i => i.str.indexOf('nop') >= 0);
+assertEq(nopLine.length, 1);
+// The nopLine shall have single offset.
+assertEq(nopLine[0].offsets.length, 1);
+assertEq(nopLine[0].offsets[0] > 0, true);
+
+var sqrtLine = result1.filter(i => i.str.indexOf('sqrt') >= 0);
+assertEq(sqrtLine.length, 1);
+// The sqrtLine shall have 4 offsets but they were produced from AST postorder decoding:
+// f32.sqrt(1.0 + 2.0) ~~> 74,73,63,68
+assertEq(sqrtLine[0].offsets.length, 4);
+assertEq(sqrtLine[0].offsets[2] > 0, true);
+assertEq(sqrtLine[0].offsets[2] < sqrtLine[0].offsets[3], true);
+assertEq(sqrtLine[0].offsets[1] > sqrtLine[0].offsets[2], true);
+assertEq(sqrtLine[0].offsets[0] > sqrtLine[0].offsets[1], true);
+
+var noOffsetLines = result1.filter(i => i.str.indexOf('nop') < 0 && i.str.indexOf('sqrt') < 0);
+// Other lines will not have offsets.
+assertEq(noOffsetLines.every(i => i.offsets.length == 0), true);
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -5988,20 +5988,79 @@ DebuggerScript_getAllColumnOffsets(JSCon
return false;
}
}
args.rval().setObject(*result);
return true;
}
+class DebuggerScriptGetLineOffsetsMatcher
+{
+ JSContext* cx_;
+ size_t lineno_;
+ RootedObject result_;
+
+ public:
+ explicit DebuggerScriptGetLineOffsetsMatcher(JSContext* cx, size_t lineno)
+ : cx_(cx), lineno_(lineno), result_(cx, NewDenseEmptyArray(cx)) { }
+ using ReturnType = bool;
+ ReturnType match(HandleScript script) {
+ if (!result_)
+ return false;
+
+ /*
+ * First pass: determine which offsets in this script are jump targets and
+ * which line numbers jump to them.
+ */
+ FlowGraphSummary flowData(cx_);
+ if (!flowData.populate(cx_, script))
+ return false;
+
+ /* Second pass: build the result array. */
+ for (BytecodeRangeWithPosition r(cx_, script); !r.empty(); r.popFront()) {
+ if (!r.frontIsEntryPoint())
+ continue;
+
+ size_t offset = r.frontOffset();
+
+ /* If the op at offset is an entry point, append offset to result. */
+ if (r.frontLineNumber() == lineno_ &&
+ !flowData[offset].hasNoEdges() &&
+ flowData[offset].lineno() != lineno_)
+ {
+ if (!NewbornArrayPush(cx_, result_, NumberValue(offset)))
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ ReturnType match(Handle<WasmInstanceObject*> instance) {
+ if (!result_)
+ return false;
+
+ Vector<uint32_t> offsets(cx_);
+ if (!instance->instance().getLineOffsets(lineno_, offsets))
+ return false;
+ for (uint32_t i = 0; i < offsets.length(); i++) {
+ if (!NewbornArrayPush(cx_, result_, NumberValue(offsets[i])))
+ return false;
+ }
+ return true;
+ }
+
+ RootedObject& result() { return result_; }
+};
+
static bool
DebuggerScript_getLineOffsets(JSContext* cx, unsigned argc, Value* vp)
{
- THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getLineOffsets", args, obj, script);
+ THIS_DEBUGSCRIPT_REFERENT(cx, argc, vp, "getLineOffsets", args, obj, referent);
if (!args.requireAtLeast(cx, "Debugger.Script.getLineOffsets", 1))
return false;
/* Parse lineno argument. */
RootedValue linenoValue(cx, args[0]);
size_t lineno;
if (!ToNumber(cx, &linenoValue))
return false;
@@ -6009,45 +6068,21 @@ DebuggerScript_getLineOffsets(JSContext*
double d = linenoValue.toNumber();
lineno = size_t(d);
if (lineno != d) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_LINE);
return false;
}
}
- /*
- * First pass: determine which offsets in this script are jump targets and
- * which line numbers jump to them.
- */
- FlowGraphSummary flowData(cx);
- if (!flowData.populate(cx, script))
- return false;
-
- /* Second pass: build the result array. */
- RootedObject result(cx, NewDenseEmptyArray(cx));
- if (!result)
- return false;
- for (BytecodeRangeWithPosition r(cx, script); !r.empty(); r.popFront()) {
- if (!r.frontIsEntryPoint())
- continue;
-
- size_t offset = r.frontOffset();
-
- /* If the op at offset is an entry point, append offset to result. */
- if (r.frontLineNumber() == lineno &&
- !flowData[offset].hasNoEdges() &&
- flowData[offset].lineno() != lineno)
- {
- if (!NewbornArrayPush(cx, result, NumberValue(offset)))
- return false;
- }
- }
-
- args.rval().setObject(*result);
+ DebuggerScriptGetLineOffsetsMatcher matcher(cx, lineno);
+ if (!referent.match(matcher))
+ return false;
+
+ args.rval().setObject(*matcher.result());
return true;
}
bool
Debugger::observesFrame(AbstractFramePtr frame) const
{
return observesScript(frame.script());
}