Bug 1285976 - Adds map of generated WebAssembly source to its binary format. r?luke,shu draft
authorYury Delendik <ydelendik@mozilla.com>
Tue, 19 Jul 2016 16:01:38 -0500
changeset 389667 05fb23e9b0899a093fc8c98030fd1d5f814939aa
parent 389550 5a91e5b49be3c1ba401b057e90c92d7488e3647d
child 525823 8f21226f489ba4d8ca24166dcaad077af4d89482
push id23484
push userydelendik@mozilla.com
push dateTue, 19 Jul 2016 21:01:28 +0000
reviewersluke, shu
bugs1285976
milestone50.0a1
Bug 1285976 - Adds map of generated WebAssembly source to its binary format. r?luke,shu MozReview-Commit-ID: DWPIwLsVFgk
js/src/asmjs/WasmAST.h
js/src/asmjs/WasmBinaryToAST.cpp
js/src/asmjs/WasmBinaryToExperimentalText.cpp
js/src/asmjs/WasmBinaryToExperimentalText.h
js/src/asmjs/WasmInstance.cpp
js/src/asmjs/WasmInstance.h
js/src/jit-test/tests/debug/wasm-04.js
js/src/jit-test/tests/debug/wasm-05.js
js/src/vm/Debugger.cpp
--- 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());
 }