Bug 1286948 - Extends AbstractFramePtr to reference wasm::DebugFrame. r?shu, luke draft
authorYury Delendik <ydelendik@mozilla.com>
Sat, 07 Jan 2017 10:36:11 -0600
changeset 457374 1f11073d790aae82c14fcee802ad106a20ea4416
parent 457373 9ffdce038fd7497e9d5ac2e82ffab6856cdc2288
child 457375 c5ef138fd59f59bc0186c1056b661a27e6a04d6e
push id40734
push userydelendik@mozilla.com
push dateSat, 07 Jan 2017 16:43:50 +0000
reviewersshu, luke
bugs1286948
milestone53.0a1
Bug 1286948 - Extends AbstractFramePtr to reference wasm::DebugFrame. r?shu, luke Debugger.cpp use FrameIter to walk the stack. Having extended AbstractFramePtr helps with simulating wasm callstack as JS one. MozReview-Commit-ID: 7zGWZp33sme
js/src/gdb/mozilla/Interpreter.py
js/src/gdb/tests/test-Interpreter.cpp
js/src/jit/shared/Assembler-shared.h
js/src/moz.build
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
js/src/vm/EnvironmentObject.cpp
js/src/vm/EnvironmentObject.h
js/src/vm/SavedStacks.cpp
js/src/vm/Stack-inl.h
js/src/vm/Stack.cpp
js/src/vm/Stack.h
js/src/wasm/WasmBaselineCompile.cpp
js/src/wasm/WasmDebugFrame.cpp
js/src/wasm/WasmDebugFrame.h
js/src/wasm/WasmFrameIterator.cpp
js/src/wasm/WasmFrameIterator.h
js/src/wasm/WasmTypes.h
--- a/js/src/gdb/mozilla/Interpreter.py
+++ b/js/src/gdb/mozilla/Interpreter.py
@@ -11,16 +11,17 @@ from mozilla.prettyprinters import prett
 class InterpreterTypeCache(object):
     def __init__(self):
         self.tValue = gdb.lookup_type('JS::Value')
         self.tJSOp = gdb.lookup_type('JSOp')
         self.tScriptFrameIterData = gdb.lookup_type('js::ScriptFrameIter::Data')
         self.tInterpreterFrame = gdb.lookup_type('js::InterpreterFrame')
         self.tBaselineFrame = gdb.lookup_type('js::jit::BaselineFrame')
         self.tRematerializedFrame = gdb.lookup_type('js::jit::RematerializedFrame')
+        self.tDebugFrame = gdb.lookup_type('js::wasm::DebugFrame')
 
 @pretty_printer('js::InterpreterRegs')
 class InterpreterRegs(object):
     def __init__(self, value, cache):
         self.value = value
         self.cache = cache
         if not cache.mod_Interpreter:
             cache.mod_Interpreter = InterpreterTypeCache()
@@ -42,17 +43,18 @@ class InterpreterRegs(object):
         return '{{ {}, {}, {} }}'.format(fp_, sp, pc)
 
 @pretty_printer('js::AbstractFramePtr')
 class AbstractFramePtr(object):
     Tag_ScriptFrameIterData = 0x0
     Tag_InterpreterFrame = 0x1
     Tag_BaselineFrame = 0x2
     Tag_RematerializedFrame = 0x3
-    TagMask = 0x3
+    Tag_WasmDebugFrame = 0x4
+    TagMask = 0x7
 
     def __init__(self, value, cache):
         self.value = value
         self.cache = cache
         if not cache.mod_Interpreter:
             cache.mod_Interpreter = InterpreterTypeCache()
         self.itc = cache.mod_Interpreter
 
@@ -67,14 +69,17 @@ class AbstractFramePtr(object):
             label = 'js::InterpreterFrame'
             ptr = ptr.cast(self.itc.tInterpreterFrame.pointer())
         if tag == AbstractFramePtr.Tag_BaselineFrame:
             label = 'js::jit::BaselineFrame'
             ptr = ptr.cast(self.itc.tBaselineFrame.pointer())
         if tag == AbstractFramePtr.Tag_RematerializedFrame:
             label = 'js::jit::RematerializedFrame'
             ptr = ptr.cast(self.itc.tRematerializedFrame.pointer())
+        if tag == AbstractFramePtr.Tag_WasmDebugFrame:
+            label = 'js::wasm::DebugFrame'
+            ptr = ptr.cast(self.itc.tDebugFrame.pointer())
         return 'AbstractFramePtr (({} *) {})'.format(label, ptr)
 
     # Provide the ptr_ field as a child, so it prints after the pretty string
     # provided above.
     def children(self):
         yield ('ptr_', self.value['ptr_'])
--- a/js/src/gdb/tests/test-Interpreter.cpp
+++ b/js/src/gdb/tests/test-Interpreter.cpp
@@ -45,16 +45,23 @@ GDBTestInitAbstractFramePtr(AbstractFram
 
 void
 GDBTestInitAbstractFramePtr(AbstractFramePtr& frame, jit::RematerializedFrame* ptr)
 {
     MOZ_ASSERT((uintptr_t(ptr) & AbstractFramePtr::TagMask) == 0);
     frame.ptr_ = uintptr_t(ptr) | AbstractFramePtr::Tag_RematerializedFrame;
 }
 
+void
+GDBTestInitAbstractFramePtr(AbstractFramePtr& frame, wasm::DebugFrame* ptr)
+{
+    MOZ_ASSERT((uintptr_t(ptr) & AbstractFramePtr::TagMask) == 0);
+    frame.ptr_ = uintptr_t(ptr) | AbstractFramePtr::Tag_WasmDebugFrame;
+}
+
 } // namespace js
 
 FRAGMENT(Interpreter, Regs) {
   struct FakeFrame {
     js::InterpreterFrame frame;
     JS::Value slot0;
     JS::Value slot1;
     JS::Value slot2;
@@ -78,15 +85,18 @@ FRAGMENT(Interpreter, AbstractFramePtr) 
     GDBTestInitAbstractFramePtr(ifptr, (js::InterpreterFrame*) uintptr_t(0x8badf00));
 
     js::AbstractFramePtr bfptr;
     GDBTestInitAbstractFramePtr(bfptr, (js::jit::BaselineFrame*) uintptr_t(0xbadcafe0));
 
     js::AbstractFramePtr rfptr;
     GDBTestInitAbstractFramePtr(rfptr, (js::jit::RematerializedFrame*) uintptr_t(0xdabbad00));
 
+    js::AbstractFramePtr sfptr;
+    GDBTestInitAbstractFramePtr(sfptr, (js::wasm::DebugFrame*) uintptr_t(0xcb98ad00));
+
     breakpoint();
 
     (void) sfidptr;
     (void) ifptr;
     (void) bfptr;
     (void) rfptr;
 }
--- a/js/src/jit/shared/Assembler-shared.h
+++ b/js/src/jit/shared/Assembler-shared.h
@@ -660,38 +660,16 @@ class CodeLocationLabel
         return raw_;
     }
 };
 
 } // namespace jit
 
 namespace wasm {
 
-// As an invariant across architectures, within wasm code:
-//   $sp % WasmStackAlignment = (sizeof(wasm::Frame) + masm.framePushed) % WasmStackAlignment
-// Thus, wasm::Frame represents the bytes pushed after the call (which occurred
-// with a WasmStackAlignment-aligned StackPointer) that are not included in
-// masm.framePushed.
-
-struct Frame
-{
-    // The caller's saved frame pointer. In non-profiling mode, internal
-    // wasm-to-wasm calls don't update fp and thus don't save the caller's
-    // frame pointer; the space is reserved, however, so that profiling mode can
-    // reuse the same function body without recompiling.
-    uint8_t* callerFP;
-
-    // The return address pushed by the call (in the case of ARM/MIPS the return
-    // address is pushed by the first instruction of the prologue).
-    void* returnAddress;
-};
-
-static_assert(sizeof(Frame) == 2 * sizeof(void*), "?!");
-static const uint32_t FrameBytesAfterReturnAddress = sizeof(void*);
-
 // Represents an instruction to be patched and the intended pointee. These
 // links are accumulated in the MacroAssembler, but patching is done outside
 // the MacroAssembler (in Module::staticallyLink).
 
 struct SymbolicAccess
 {
     SymbolicAccess(jit::CodeOffset patchAt, SymbolicAddress target)
       : patchAt(patchAt), target(target) {}
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -368,16 +368,17 @@ UNIFIED_SOURCES += [
     'wasm/WasmBaselineCompile.cpp',
     'wasm/WasmBinaryIterator.cpp',
     'wasm/WasmBinaryToAST.cpp',
     'wasm/WasmBinaryToExperimentalText.cpp',
     'wasm/WasmBinaryToText.cpp',
     'wasm/WasmCode.cpp',
     'wasm/WasmCompartment.cpp',
     'wasm/WasmCompile.cpp',
+    'wasm/WasmDebugFrame.cpp',
     'wasm/WasmFrameIterator.cpp',
     'wasm/WasmGenerator.cpp',
     'wasm/WasmInstance.cpp',
     'wasm/WasmIonCompile.cpp',
     'wasm/WasmJS.cpp',
     'wasm/WasmModule.cpp',
     'wasm/WasmSignalHandlers.cpp',
     'wasm/WasmStubs.cpp',
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -2044,16 +2044,18 @@ Debugger::onSingleStep(JSContext* cx, Mu
         JSScript* trappingScript = iter.script();
         GlobalObject* global = cx->global();
         if (GlobalObject::DebuggerVector* debuggers = global->getDebuggers()) {
             for (auto p = debuggers->begin(); p != debuggers->end(); p++) {
                 Debugger* dbg = *p;
                 for (FrameMap::Range r = dbg->frames.all(); !r.empty(); r.popFront()) {
                     AbstractFramePtr frame = r.front().key();
                     NativeObject* frameobj = r.front().value();
+                    if (frame.isWasmDebugFrame())
+                        continue;
                     if (frame.script() == trappingScript &&
                         !frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined())
                     {
                         stepperCount++;
                     }
                 }
             }
         }
@@ -6256,43 +6258,54 @@ DebuggerScript_getLineOffsets(JSContext*
 
     args.rval().setObject(*matcher.result());
     return true;
 }
 
 bool
 Debugger::observesFrame(AbstractFramePtr frame) const
 {
+    if (frame.isWasmDebugFrame())
+        return observesWasm(frame.wasmInstance());
+
     return observesScript(frame.script());
 }
 
 bool
 Debugger::observesFrame(const FrameIter& iter) const
 {
     // Skip frames not yet fully initialized during their prologue.
     if (iter.isInterp() && iter.isFunctionFrame()) {
         const Value& thisVal = iter.interpFrame()->thisArgument();
         if (thisVal.isMagic() && thisVal.whyMagic() == JS_IS_CONSTRUCTING)
             return false;
     }
     if (iter.isWasm())
-        return false;
+        return observesWasm(iter.wasmInstance());
     return observesScript(iter.script());
 }
 
 bool
 Debugger::observesScript(JSScript* script) const
 {
     if (!enabled)
         return false;
     // Don't ever observe self-hosted scripts: the Debugger API can break
     // self-hosted invariants.
     return observesGlobal(&script->global()) && !script->selfHosted();
 }
 
+bool
+Debugger::observesWasm(wasm::Instance* instance) const
+{
+    if (!enabled || !instance->code().metadata().debugEnabled)
+        return false;
+    return false; // TODO check global
+}
+
 /* static */ bool
 Debugger::replaceFrameGuts(JSContext* cx, AbstractFramePtr from, AbstractFramePtr to,
                            ScriptFrameIter& iter)
 {
     auto removeFromDebuggerFramesOnExit = MakeScopeExit([&] {
         // Remove any remaining old entries on exit, as the 'from' frame will
         // be gone. This is only done in the failure case. On failure, the
         // removeToDebuggerFramesOnExit lambda below will rollback any frames
@@ -7370,16 +7383,21 @@ DebuggerFrame::getIsConstructing(JSConte
 
     result = iter.isFunctionFrame() && iter.isConstructing();
     return true;
 }
 
 static void
 UpdateFrameIterPc(FrameIter& iter)
 {
+    if (iter.abstractFramePtr().isWasmDebugFrame()) {
+        // Wasm debug frames don't need their pc updated -- it's null.
+        return;
+    }
+
     if (iter.abstractFramePtr().isRematerializedFrame()) {
 #ifdef DEBUG
         // Rematerialized frames don't need their pc updated. The reason we
         // need to update pc is because we might get the same Debugger.Frame
         // object for multiple re-entries into debugger code from debuggee
         // code. This reentrancy is not possible with rematerialized frames,
         // because when returning to debuggee code, we would have bailed out
         // to baseline.
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -912,16 +912,17 @@ class Debugger : private mozilla::Linked
 
     inline bool observesEnterFrame() const;
     inline bool observesNewScript() const;
     inline bool observesNewGlobalObject() const;
     inline bool observesGlobal(GlobalObject* global) const;
     bool observesFrame(AbstractFramePtr frame) const;
     bool observesFrame(const FrameIter& iter) const;
     bool observesScript(JSScript* script) const;
+    bool observesWasm(wasm::Instance* instance) const;
 
     /*
      * If env is nullptr, call vp->setNull() and return true. Otherwise, find
      * or create a Debugger.Environment object for the given Env. On success,
      * store the Environment object in *vp and return true.
      */
     MOZ_MUST_USE bool wrapEnvironment(JSContext* cx, Handle<Env*> env, MutableHandleValue vp);
     MOZ_MUST_USE bool wrapEnvironment(JSContext* cx, Handle<Env*> env,
--- a/js/src/vm/EnvironmentObject.cpp
+++ b/js/src/vm/EnvironmentObject.cpp
@@ -17,16 +17,17 @@
 #include "frontend/ParseNode.h"
 #include "gc/Policy.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/AsyncFunction.h"
 #include "vm/GlobalObject.h"
 #include "vm/ProxyObject.h"
 #include "vm/Shape.h"
 #include "vm/Xdr.h"
+#include "wasm/WasmInstance.h"
 
 #include "jsatominlines.h"
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
 #include "vm/Stack-inl.h"
 
 using namespace js;
@@ -1228,16 +1229,27 @@ EnvironmentIter::EnvironmentIter(JSConte
     env_(cx, frame.environmentChain()),
     frame_(frame)
 {
     assertSameCompartment(cx, frame);
     settle();
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 }
 
+EnvironmentIter::EnvironmentIter(JSContext* cx, JSObject* env, Scope* scope, AbstractFramePtr frame
+                                 MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
+  : si_(cx, ScopeIter(scope)),
+    env_(cx, env),
+    frame_(frame)
+{
+    assertSameCompartment(cx, frame);
+    settle();
+    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+}
+
 void
 EnvironmentIter::incrementScopeIter()
 {
     if (si_.scope()->is<GlobalScope>()) {
         // GlobalScopes may be syntactic or non-syntactic. Non-syntactic
         // GlobalScopes correspond to zero or more non-syntactic
         // EnvironmentsObjects followed by the global lexical scope, then the
         // GlobalObject or another non-EnvironmentObject object.
@@ -1248,35 +1260,40 @@ EnvironmentIter::incrementScopeIter()
     }
 }
 
 void
 EnvironmentIter::settle()
 {
     // Check for trying to iterate a function or eval frame before the prologue has
     // created the CallObject, in which case we have to skip.
-    if (frame_ && frame_.script()->initialEnvironmentShape() && !frame_.hasInitialEnvironment()) {
+    if (frame_ && frame_.hasScript() &&
+        frame_.script()->initialEnvironmentShape() && !frame_.hasInitialEnvironment())
+    {
         // Skip until we're at the enclosing scope of the script.
         while (si_.scope() != frame_.script()->enclosingScope()) {
             if (env_->is<LexicalEnvironmentObject>() &&
                 !env_->as<LexicalEnvironmentObject>().isExtensible() &&
                 &env_->as<LexicalEnvironmentObject>().scope() == si_.scope())
             {
                 MOZ_ASSERT(si_.kind() == ScopeKind::NamedLambda ||
                            si_.kind() == ScopeKind::StrictNamedLambda);
                 env_ = &env_->as<EnvironmentObject>().enclosingEnvironment();
             }
             incrementScopeIter();
         }
     }
 
     // Check if we have left the extent of the initial frame after we've
     // settled on a static scope.
-    if (frame_ && (!si_ || si_.scope() == frame_.script()->enclosingScope()))
+    if (frame_ && (frame_.isWasmDebugFrame() ||
+                   (!si_ || si_.scope() == frame_.script()->enclosingScope())))
+    {
         frame_ = NullFramePtr();
+    }
 
 #ifdef DEBUG
     if (si_) {
         if (hasSyntacticEnvironment()) {
             Scope* scope = si_.scope();
             if (scope->is<LexicalScope>()) {
                 MOZ_ASSERT(scope == &env_->as<LexicalEnvironmentObject>().scope());
             } else if (scope->is<FunctionScope>()) {
@@ -2757,17 +2774,22 @@ DebugEnvironments::updateLiveEnvironment
             continue;
 
         if (frame.isFunctionFrame() && frame.callee()->isGenerator())
             continue;
 
         if (!frame.isDebuggee())
             continue;
 
-        for (EnvironmentIter ei(cx, frame, i.pc()); ei.withinInitialFrame(); ei++) {
+        RootedObject env(cx);
+        RootedScope scope(cx);
+        if (!GetFrameEnvironmentAndScope(cx, frame, i.pc(), &env, &scope))
+            return false;
+
+        for (EnvironmentIter ei(cx, env, scope, frame); ei.withinInitialFrame(); ei++) {
             if (ei.hasSyntacticEnvironment() && !ei.scope().is<GlobalScope>()) {
                 MOZ_ASSERT(ei.environment().compartment() == cx->compartment());
                 DebugEnvironments* envs = ensureCompartmentData(cx);
                 if (!envs)
                     return false;
                 if (!envs->liveEnvs.put(&ei.environment(), LiveEnvironmentVal(ei)))
                     return false;
             }
@@ -3006,17 +3028,22 @@ js::GetDebugEnvironmentForFunction(JSCon
 
 JSObject*
 js::GetDebugEnvironmentForFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc)
 {
     assertSameCompartment(cx, frame);
     if (CanUseDebugEnvironmentMaps(cx) && !DebugEnvironments::updateLiveEnvironments(cx))
         return nullptr;
 
-    EnvironmentIter ei(cx, frame, pc);
+    RootedObject env(cx);
+    RootedScope scope(cx);
+    if (!GetFrameEnvironmentAndScope(cx, frame, pc, &env, &scope))
+        return nullptr;
+
+    EnvironmentIter ei(cx, env, scope, frame);
     return GetDebugEnvironment(cx, ei);
 }
 
 JSObject*
 js::GetDebugEnvironmentForGlobalLexicalEnvironment(JSContext* cx)
 {
     EnvironmentIter ei(cx, &cx->global()->lexicalEnvironment(), &cx->global()->emptyGlobalScope());
     return GetDebugEnvironment(cx, ei);
@@ -3101,17 +3128,22 @@ js::GetModuleEnvironmentForScript(JSScri
     }
     return nullptr;
 }
 
 bool
 js::GetThisValueForDebuggerMaybeOptimizedOut(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc,
                                              MutableHandleValue res)
 {
-    for (EnvironmentIter ei(cx, frame, pc); ei; ei++) {
+    RootedObject scopeChain(cx);
+    RootedScope scope(cx);
+    if (!GetFrameEnvironmentAndScope(cx, frame, pc, &scopeChain, &scope))
+        return false;
+
+    for (EnvironmentIter ei(cx, scopeChain, scope, frame); ei; ei++) {
         if (ei.scope().kind() == ScopeKind::Module) {
             res.setUndefined();
             return true;
         }
 
         if (!ei.scope().is<FunctionScope>() ||
             ei.scope().as<FunctionScope>().canonicalFunction()->hasLexicalThis())
         {
@@ -3175,17 +3207,16 @@ js::GetThisValueForDebuggerMaybeOptimize
                 res.setMagic(JS_OPTIMIZED_OUT);
 
             return true;
         }
 
         MOZ_CRASH("'this' binding must be found");
     }
 
-    RootedObject scopeChain(cx, frame.environmentChain());
     return GetNonSyntacticGlobalThis(cx, scopeChain, res);
 }
 
 bool
 js::CheckLexicalNameConflict(JSContext* cx, Handle<LexicalEnvironmentObject*> lexicalEnv,
                              HandleObject varObj, HandlePropertyName name)
 {
     const char* redeclKind = nullptr;
@@ -3444,16 +3475,35 @@ js::PushVarEnvironmentObject(JSContext* 
 {
     VarEnvironmentObject* env = VarEnvironmentObject::create(cx, scope, frame);
     if (!env)
         return false;
     frame.pushOnEnvironmentChain(*env);
     return true;
 }
 
+bool
+js::GetFrameEnvironmentAndScope(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc,
+                                MutableHandleObject env, MutableHandleScope scope)
+{
+    env.set(frame.environmentChain());
+
+    if (frame.isWasmDebugFrame()) {
+        RootedWasmInstanceObject instance(cx, frame.wasmInstance()->object());
+        uint32_t funcIndex = frame.asWasmDebugFrame()->funcIndex();
+        scope.set(WasmInstanceObject::getFunctionScope(cx, instance, funcIndex));
+        if (!scope)
+            return false;
+    } else {
+        scope.set(frame.script()->innermostScope(pc));
+    }
+    return true;
+}
+
+
 #ifdef DEBUG
 
 typedef HashSet<PropertyName*> PropertyNameSet;
 
 static bool
 RemoveReferencedNames(JSContext* cx, HandleScript script, PropertyNameSet& remainingNames)
 {
     // Remove from remainingNames --- the closure variables in some outer
--- a/js/src/vm/EnvironmentObject.h
+++ b/js/src/vm/EnvironmentObject.h
@@ -667,16 +667,22 @@ class MOZ_RAII EnvironmentIter
     EnvironmentIter(JSContext* cx, JSObject* env, Scope* scope
                     MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
 
     // Constructing from a frame. Places the EnvironmentIter on the innermost
     // environment at pc.
     EnvironmentIter(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc
                     MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
 
+    // Constructing from an environment, scope and frame. The frame is given
+    // to initialize to proper enclosing environment/scope.
+    EnvironmentIter(JSContext* cx, JSObject* env, Scope* scope, AbstractFramePtr frame
+                    MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
+
+
     bool done() const {
         return si_.done();
     }
 
     explicit operator bool() const {
         return !done();
     }
 
@@ -1115,16 +1121,20 @@ CheckEvalDeclarationConflicts(JSContext*
                               HandleObject varObj);
 
 MOZ_MUST_USE bool
 InitFunctionEnvironmentObjects(JSContext* cx, AbstractFramePtr frame);
 
 MOZ_MUST_USE bool
 PushVarEnvironmentObject(JSContext* cx, HandleScope scope, AbstractFramePtr frame);
 
+MOZ_MUST_USE bool
+GetFrameEnvironmentAndScope(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc,
+                            MutableHandleObject env, MutableHandleScope scope);
+
 #ifdef DEBUG
 bool
 AnalyzeEntrainedVariables(JSContext* cx, HandleScript script);
 #endif
 
 } // namespace js
 
 namespace JS {
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -152,18 +152,18 @@ struct SavedFrame::Lookup {
         asyncCause(asyncCause),
         parent(parent),
         principals(principals),
         framePtr(framePtr),
         pc(pc),
         activation(activation)
     {
         MOZ_ASSERT(source);
-        MOZ_ASSERT_IF(framePtr.isSome(), pc);
         MOZ_ASSERT_IF(framePtr.isSome(), activation);
+        MOZ_ASSERT_IF(framePtr.isSome() && !activation->isWasm(), pc);
 
 #ifdef JS_MORE_DETERMINISTIC
         column = 0;
 #endif
     }
 
     explicit Lookup(SavedFrame& savedFrame)
       : source(savedFrame.getSource()),
@@ -1319,17 +1319,17 @@ SavedStacks::insertFrames(JSContext* cx,
         }
 
         // The bit set means that the next older parent (frame, pc) pair *must*
         // be in the cache.
         if (capture.is<JS::AllFrames>())
             parentIsInCache = iter.hasCachedSavedFrame();
 
         auto principals = iter.compartment()->principals();
-        auto displayAtom = iter.isFunctionFrame() ? iter.functionDisplayAtom() : nullptr;
+        auto displayAtom = (iter.isWasm() || iter.isFunctionFrame()) ? iter.functionDisplayAtom() : nullptr;
         if (!stackChain->emplaceBack(location.source(),
                                      location.line(),
                                      location.column(),
                                      displayAtom,
                                      nullptr,
                                      nullptr,
                                      principals,
                                      LiveSavedFrameCache::getFramePtr(iter),
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -14,16 +14,18 @@
 #include "jscntxt.h"
 #include "jsscript.h"
 
 #include "jit/BaselineFrame.h"
 #include "jit/RematerializedFrame.h"
 #include "js/Debug.h"
 #include "vm/EnvironmentObject.h"
 #include "vm/GeneratorObject.h"
+#include "wasm/WasmDebugFrame.h"
+#include "wasm/WasmInstance.h"
 
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
 #include "jit/BaselineFrame-inl.h"
 
 namespace js {
 
@@ -439,16 +441,18 @@ AbstractFramePtr::setReturnValue(const V
 
 inline JSObject*
 AbstractFramePtr::environmentChain() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->environmentChain();
     if (isBaselineFrame())
         return asBaselineFrame()->environmentChain();
+    if (isWasmDebugFrame())
+        return asWasmDebugFrame()->environmentChain();
     return asRematerializedFrame()->environmentChain();
 }
 
 template <typename SpecificEnvironment>
 inline void
 AbstractFramePtr::pushOnEnvironmentChain(SpecificEnvironment& env)
 {
     if (isInterpreterFrame()) {
@@ -575,36 +579,42 @@ AbstractFramePtr::createSingleton() cons
 
 inline bool
 AbstractFramePtr::isGlobalFrame() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->isGlobalFrame();
     if (isBaselineFrame())
         return asBaselineFrame()->isGlobalFrame();
+    if (isWasmDebugFrame())
+        return false;
     return asRematerializedFrame()->isGlobalFrame();
 }
 
 inline bool
 AbstractFramePtr::isModuleFrame() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->isModuleFrame();
     if (isBaselineFrame())
         return asBaselineFrame()->isModuleFrame();
+    if (isWasmDebugFrame())
+        return false;
     return asRematerializedFrame()->isModuleFrame();
 }
 
 inline bool
 AbstractFramePtr::isEvalFrame() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->isEvalFrame();
     if (isBaselineFrame())
         return asBaselineFrame()->isEvalFrame();
+    if (isWasmDebugFrame())
+        return false;
     MOZ_ASSERT(isRematerializedFrame());
     return false;
 }
 
 inline bool
 AbstractFramePtr::isDebuggerEvalFrame() const
 {
     if (isInterpreterFrame())
@@ -617,77 +627,108 @@ AbstractFramePtr::isDebuggerEvalFrame() 
 
 inline bool
 AbstractFramePtr::hasCachedSavedFrame() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->hasCachedSavedFrame();
     if (isBaselineFrame())
         return asBaselineFrame()->hasCachedSavedFrame();
+    if (isWasmDebugFrame())
+        return asWasmDebugFrame()->hasCachedSavedFrame();
     return asRematerializedFrame()->hasCachedSavedFrame();
 }
 
 inline void
 AbstractFramePtr::setHasCachedSavedFrame()
 {
     if (isInterpreterFrame())
         asInterpreterFrame()->setHasCachedSavedFrame();
     else if (isBaselineFrame())
         asBaselineFrame()->setHasCachedSavedFrame();
+    else if (isWasmDebugFrame())
+        asWasmDebugFrame()->setHasCachedSavedFrame();
     else
         asRematerializedFrame()->setHasCachedSavedFrame();
 }
 
 inline bool
 AbstractFramePtr::isDebuggee() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->isDebuggee();
     if (isBaselineFrame())
         return asBaselineFrame()->isDebuggee();
+    if (isWasmDebugFrame())
+        return asWasmDebugFrame()->isDebuggee();
     return asRematerializedFrame()->isDebuggee();
 }
 
 inline void
 AbstractFramePtr::setIsDebuggee()
 {
     if (isInterpreterFrame())
         asInterpreterFrame()->setIsDebuggee();
     else if (isBaselineFrame())
         asBaselineFrame()->setIsDebuggee();
+    else if (isWasmDebugFrame())
+        asWasmDebugFrame()->setIsDebuggee();
     else
         asRematerializedFrame()->setIsDebuggee();
 }
 
 inline void
 AbstractFramePtr::unsetIsDebuggee()
 {
     if (isInterpreterFrame())
         asInterpreterFrame()->unsetIsDebuggee();
     else if (isBaselineFrame())
         asBaselineFrame()->unsetIsDebuggee();
+    else if (isWasmDebugFrame())
+        asWasmDebugFrame()->unsetIsDebuggee();
     else
         asRematerializedFrame()->unsetIsDebuggee();
 }
 
 inline bool
-AbstractFramePtr::hasArgs() const {
+AbstractFramePtr::hasArgs() const
+{
     return isFunctionFrame();
 }
 
+inline bool
+AbstractFramePtr::hasScript() const
+{
+    return !isWasmDebugFrame();
+}
+
 inline JSScript*
 AbstractFramePtr::script() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->script();
     if (isBaselineFrame())
         return asBaselineFrame()->script();
     return asRematerializedFrame()->script();
 }
 
+inline wasm::Instance*
+AbstractFramePtr::wasmInstance() const
+{
+    return asWasmDebugFrame()->instance();
+}
+
+inline GlobalObject*
+AbstractFramePtr::global() const
+{
+    if (isWasmDebugFrame())
+        return &wasmInstance()->object()->global();
+    return &script()->global();
+}
+
 inline JSFunction*
 AbstractFramePtr::callee() const
 {
     if (isInterpreterFrame())
         return &asInterpreterFrame()->callee();
     if (isBaselineFrame())
         return asBaselineFrame()->callee();
     return asRematerializedFrame()->callee();
@@ -705,16 +746,18 @@ AbstractFramePtr::calleev() const
 
 inline bool
 AbstractFramePtr::isFunctionFrame() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->isFunctionFrame();
     if (isBaselineFrame())
         return asBaselineFrame()->isFunctionFrame();
+    if (isWasmDebugFrame())
+        return false;
     return asRematerializedFrame()->isFunctionFrame();
 }
 
 inline bool
 AbstractFramePtr::isNonStrictDirectEvalFrame() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->isNonStrictDirectEvalFrame();
@@ -777,44 +820,54 @@ AbstractFramePtr::initArgsObj(ArgumentsO
 
 inline bool
 AbstractFramePtr::prevUpToDate() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->prevUpToDate();
     if (isBaselineFrame())
         return asBaselineFrame()->prevUpToDate();
+    if (isWasmDebugFrame())
+        return asWasmDebugFrame()->prevUpToDate();
     return asRematerializedFrame()->prevUpToDate();
 }
 
 inline void
 AbstractFramePtr::setPrevUpToDate() const
 {
     if (isInterpreterFrame()) {
         asInterpreterFrame()->setPrevUpToDate();
         return;
     }
     if (isBaselineFrame()) {
         asBaselineFrame()->setPrevUpToDate();
         return;
     }
+    if (isWasmDebugFrame()) {
+        asWasmDebugFrame()->setPrevUpToDate();
+        return;
+    }
     asRematerializedFrame()->setPrevUpToDate();
 }
 
 inline void
 AbstractFramePtr::unsetPrevUpToDate() const
 {
     if (isInterpreterFrame()) {
         asInterpreterFrame()->unsetPrevUpToDate();
         return;
     }
     if (isBaselineFrame()) {
         asBaselineFrame()->unsetPrevUpToDate();
         return;
     }
+    if (isWasmDebugFrame()) {
+        asWasmDebugFrame()->unsetPrevUpToDate();
+        return;
+    }
     asRematerializedFrame()->unsetPrevUpToDate();
 }
 
 inline Value&
 AbstractFramePtr::thisArgument() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->thisArgument();
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -12,16 +12,17 @@
 
 #include "gc/Marking.h"
 #include "jit/BaselineFrame.h"
 #include "jit/JitcodeMap.h"
 #include "jit/JitCompartment.h"
 #include "js/GCAPI.h"
 #include "vm/Debugger.h"
 #include "vm/Opcodes.h"
+#include "wasm/WasmDebugFrame.h"
 
 #include "jit/JitFrameIterator-inl.h"
 #include "vm/EnvironmentObject-inl.h"
 #include "vm/Interpreter-inl.h"
 #include "vm/Probes-inl.h"
 
 using namespace js;
 
@@ -546,17 +547,17 @@ FrameIter::settleOnActivation()
         if (activation->isWasm()) {
             data_.wasmFrames_ = wasm::FrameIterator(*data_.activations_->asWasm());
 
             if (data_.wasmFrames_.done()) {
                 ++data_.activations_;
                 continue;
             }
 
-            data_.pc_ = (jsbytecode*)data_.wasmFrames_.pc();
+            data_.pc_ = nullptr;
             data_.state_ = WASM;
             return;
         }
 
         MOZ_ASSERT(activation->isInterpreter());
 
         InterpreterActivation* interpAct = activation->asInterpreter();
         data_.interpFrames_ = InterpreterFrameIterator(interpAct);
@@ -679,17 +680,17 @@ FrameIter::popJitFrame()
 }
 
 void
 FrameIter::popWasmFrame()
 {
     MOZ_ASSERT(data_.state_ == WASM);
 
     ++data_.wasmFrames_;
-    data_.pc_ = (jsbytecode*)data_.wasmFrames_.pc();
+    data_.pc_ = nullptr;
     if (data_.wasmFrames_.done())
         popActivation();
 }
 
 FrameIter&
 FrameIter::operator++()
 {
     switch (data_.state_) {
@@ -727,17 +728,16 @@ FrameIter::operator++()
 
 FrameIter::Data*
 FrameIter::copyData() const
 {
     Data* data = data_.cx_->new_<Data>(data_);
     if (!data)
         return nullptr;
 
-    MOZ_ASSERT(data_.state_ != WASM);
     if (data && data_.jitFrames_.isIonScripted())
         data->ionInlineFrameNo_ = ionInlineFrames_.frameNo();
     return data;
 }
 
 AbstractFramePtr
 FrameIter::copyDataAsAbstractFramePtr() const
 {
@@ -753,17 +753,17 @@ FrameIter::rawFramePtr() const
     switch (data_.state_) {
       case DONE:
         return nullptr;
       case JIT:
         return data_.jitFrames_.fp();
       case INTERP:
         return interpFrame();
       case WASM:
-        return data_.wasmFrames_.fp();
+        return nullptr;
     }
     MOZ_CRASH("Unexpected state");
 }
 
 JSCompartment*
 FrameIter::compartment() const
 {
     switch (data_.state_) {
@@ -805,33 +805,33 @@ FrameIter::isFunctionFrame() const
         break;
       case INTERP:
         return interpFrame()->isFunctionFrame();
       case JIT:
         if (data_.jitFrames_.isBaselineJS())
             return data_.jitFrames_.baselineFrame()->isFunctionFrame();
         return script()->functionNonDelazifying();
       case WASM:
-        return true;
+        return false;
     }
     MOZ_CRASH("Unexpected state");
 }
 
 JSAtom*
 FrameIter::functionDisplayAtom() const
 {
-    MOZ_ASSERT(isFunctionFrame());
-
     switch (data_.state_) {
       case DONE:
         break;
       case INTERP:
       case JIT:
+        MOZ_ASSERT(isFunctionFrame());
         return calleeTemplate()->displayAtom();
       case WASM:
+        MOZ_ASSERT(isWasm());
         return data_.wasmFrames_.functionDisplayAtom();
     }
 
     MOZ_CRASH("Unexpected state");
 }
 
 ScriptSource*
 FrameIter::scriptSource() const
@@ -940,60 +940,64 @@ FrameIter::ensureHasRematerializedFrame(
     return !!activation()->asJit()->getRematerializedFrame(cx, data_.jitFrames_);
 }
 
 bool
 FrameIter::hasUsableAbstractFramePtr() const
 {
     switch (data_.state_) {
       case DONE:
-      case WASM:
         return false;
       case JIT:
         if (data_.jitFrames_.isBaselineJS())
             return true;
 
         MOZ_ASSERT(data_.jitFrames_.isIonScripted());
         return !!activation()->asJit()->lookupRematerializedFrame(data_.jitFrames_.fp(),
                                                                   ionInlineFrames_.frameNo());
         break;
       case INTERP:
         return true;
+      case WASM:
+        return data_.wasmFrames_.debugEnabled();
     }
     MOZ_CRASH("Unexpected state");
 }
 
 AbstractFramePtr
 FrameIter::abstractFramePtr() const
 {
     MOZ_ASSERT(hasUsableAbstractFramePtr());
     switch (data_.state_) {
       case DONE:
-      case WASM:
         break;
       case JIT: {
         if (data_.jitFrames_.isBaselineJS())
             return data_.jitFrames_.baselineFrame();
 
         MOZ_ASSERT(data_.jitFrames_.isIonScripted());
         return activation()->asJit()->lookupRematerializedFrame(data_.jitFrames_.fp(),
                                                                 ionInlineFrames_.frameNo());
         break;
       }
       case INTERP:
         MOZ_ASSERT(interpFrame());
         return AbstractFramePtr(interpFrame());
+      case WASM:
+        MOZ_ASSERT(data_.wasmFrames_.debugEnabled());
+        return data_.wasmFrames_.debugFrame();
     }
     MOZ_CRASH("Unexpected state");
 }
 
 void
 FrameIter::updatePcQuadratic()
 {
     switch (data_.state_) {
+      case WASM:
       case DONE:
         break;
       case INTERP: {
         InterpreterFrame* frame = interpFrame();
         InterpreterActivation* activation = data_.activations_->asInterpreter();
 
         // Look for the current frame.
         data_.interpFrames_ = InterpreterFrameIterator(activation);
@@ -1022,20 +1026,16 @@ FrameIter::updatePcQuadratic()
                 ++data_.jitFrames_;
 
             // Update the pc.
             MOZ_ASSERT(data_.jitFrames_.baselineFrame() == frame);
             data_.jitFrames_.baselineScriptAndPc(nullptr, &data_.pc_);
             return;
         }
         break;
-      case WASM:
-        // Update the pc.
-        data_.pc_ = (jsbytecode*)data_.wasmFrames_.pc();
-        break;
     }
     MOZ_CRASH("Unexpected state");
 }
 
 JSFunction*
 FrameIter::calleeTemplate() const
 {
     switch (data_.state_) {
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -48,16 +48,17 @@ class EnvironmentIter;
 class EnvironmentCoordinate;
 
 class SavedFrame;
 
 namespace jit {
 class CommonFrameLayout;
 }
 namespace wasm {
+class DebugFrame;
 class Instance;
 }
 
 // VM stack layout
 //
 // A JSRuntime's stack consists of a linked list of activations. Every activation
 // contains a number of scripted frames that are either running in the interpreter
 // (InterpreterActivation) or JIT code (JitActivation). The frames inside a single
@@ -121,17 +122,18 @@ class AbstractFramePtr
 
     uintptr_t ptr_;
 
     enum {
         Tag_ScriptFrameIterData = 0x0,
         Tag_InterpreterFrame = 0x1,
         Tag_BaselineFrame = 0x2,
         Tag_RematerializedFrame = 0x3,
-        TagMask = 0x3
+        Tag_WasmDebugFrame = 0x4,
+        TagMask = 0x7
     };
 
   public:
     AbstractFramePtr()
       : ptr_(0)
     {}
 
     MOZ_IMPLICIT AbstractFramePtr(InterpreterFrame* fp)
@@ -147,16 +149,22 @@ class AbstractFramePtr
     }
 
     MOZ_IMPLICIT AbstractFramePtr(jit::RematerializedFrame* fp)
       : ptr_(fp ? uintptr_t(fp) | Tag_RematerializedFrame : 0)
     {
         MOZ_ASSERT_IF(fp, asRematerializedFrame() == fp);
     }
 
+    MOZ_IMPLICIT AbstractFramePtr(wasm::DebugFrame* fp)
+      : ptr_(fp ? uintptr_t(fp) | Tag_WasmDebugFrame : 0)
+    {
+        MOZ_ASSERT_IF(fp, asWasmDebugFrame() == fp);
+    }
+
     static AbstractFramePtr FromRaw(void* raw) {
         AbstractFramePtr frame;
         frame.ptr_ = uintptr_t(raw);
         return frame;
     }
 
     bool isScriptFrameIterData() const {
         return !!ptr_ && (ptr_ & TagMask) == Tag_ScriptFrameIterData;
@@ -183,16 +191,25 @@ class AbstractFramePtr
         return (ptr_ & TagMask) == Tag_RematerializedFrame;
     }
     jit::RematerializedFrame* asRematerializedFrame() const {
         MOZ_ASSERT(isRematerializedFrame());
         jit::RematerializedFrame* res = (jit::RematerializedFrame*)(ptr_ & ~TagMask);
         MOZ_ASSERT(res);
         return res;
     }
+    bool isWasmDebugFrame() const {
+        return (ptr_ & TagMask) == Tag_WasmDebugFrame;
+    }
+    wasm::DebugFrame* asWasmDebugFrame() const {
+        MOZ_ASSERT(isWasmDebugFrame());
+        wasm::DebugFrame* res = (wasm::DebugFrame*)(ptr_ & ~TagMask);
+        MOZ_ASSERT(res);
+        return res;
+    }
 
     void* raw() const { return reinterpret_cast<void*>(ptr_); }
 
     bool operator ==(const AbstractFramePtr& other) const { return ptr_ == other.ptr_; }
     bool operator !=(const AbstractFramePtr& other) const { return ptr_ != other.ptr_; }
 
     explicit operator bool() const { return !!ptr_; }
 
@@ -210,17 +227,20 @@ class AbstractFramePtr
     inline bool hasInitialEnvironment() const;
     inline bool isGlobalFrame() const;
     inline bool isModuleFrame() const;
     inline bool isEvalFrame() const;
     inline bool isDebuggerEvalFrame() const;
     inline bool hasCachedSavedFrame() const;
     inline void setHasCachedSavedFrame();
 
+    inline bool hasScript() const;
     inline JSScript* script() const;
+    inline wasm::Instance* wasmInstance() const;
+    inline GlobalObject* global() const;
     inline JSFunction* callee() const;
     inline Value calleev() const;
     inline Value& thisArgument() const;
 
     inline Value newTarget() const;
 
     inline bool debuggerNeedsCheckPrimitiveReturn() const;
 
@@ -254,16 +274,17 @@ class AbstractFramePtr
 
     inline HandleValue returnValue() const;
     inline void setReturnValue(const Value& rval) const;
 
     friend void GDBTestInitAbstractFramePtr(AbstractFramePtr&, void*);
     friend void GDBTestInitAbstractFramePtr(AbstractFramePtr&, InterpreterFrame*);
     friend void GDBTestInitAbstractFramePtr(AbstractFramePtr&, jit::BaselineFrame*);
     friend void GDBTestInitAbstractFramePtr(AbstractFramePtr&, jit::RematerializedFrame*);
+    friend void GDBTestInitAbstractFramePtr(AbstractFramePtr& frame, wasm::DebugFrame* ptr);
 };
 
 class NullFramePtr : public AbstractFramePtr
 {
   public:
     NullFramePtr()
       : AbstractFramePtr()
     { }
@@ -1783,16 +1804,21 @@ class FrameIter
     const char16_t* displayURL() const;
     unsigned computeLine(uint32_t* column = nullptr) const;
     JSAtom* functionDisplayAtom() const;
     bool mutedErrors() const;
 
     bool hasScript() const { return !isWasm(); }
 
     // -----------------------------------------------------------
+    //  The following functions can only be called when isWasm()
+    // -----------------------------------------------------------
+    inline wasm::Instance* wasmInstance() const;
+
+    // -----------------------------------------------------------
     // The following functions can only be called when hasScript()
     // -----------------------------------------------------------
 
     inline JSScript* script() const;
 
     bool        isConstructing() const;
     jsbytecode* pc() const { MOZ_ASSERT(!done()); return data_.pc_; }
     void        updatePcQuadratic();
@@ -1843,17 +1869,17 @@ class FrameIter
     bool ensureHasRematerializedFrame(JSContext* cx);
 
     // True when isInterp() or isBaseline(). True when isIon() if it
     // has a rematerialized frame. False otherwise false otherwise.
     bool hasUsableAbstractFramePtr() const;
 
     // -----------------------------------------------------------
     // The following functions can only be called when isInterp(),
-    // isBaseline(), or isIon(). Further, abstractFramePtr() can
+    // isBaseline(), isWasm() or isIon(). Further, abstractFramePtr() can
     // only be called when hasUsableAbstractFramePtr().
     // -----------------------------------------------------------
 
     AbstractFramePtr abstractFramePtr() const;
     AbstractFramePtr copyDataAsAbstractFramePtr() const;
     Data* copyData() const;
 
     // This can only be called when isInterp():
@@ -2025,16 +2051,24 @@ FrameIter::script() const
     if (data_.state_ == INTERP)
         return interpFrame()->script();
     MOZ_ASSERT(data_.state_ == JIT);
     if (data_.jitFrames_.isIonJS())
         return ionInlineFrames_.script();
     return data_.jitFrames_.script();
 }
 
+inline wasm::Instance*
+FrameIter::wasmInstance() const
+{
+    MOZ_ASSERT(!done());
+    MOZ_ASSERT(data_.state_ == WASM);
+    return data_.wasmFrames_.instance();
+}
+
 inline bool
 FrameIter::isIon() const
 {
     return isJit() && data_.jitFrames_.isIonJS();
 }
 
 inline bool
 FrameIter::isBaseline() const
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -99,16 +99,17 @@
 # include "jit/arm/Assembler-arm.h"
 #endif
 #if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
 # include "jit/x86-shared/Architecture-x86-shared.h"
 # include "jit/x86-shared/Assembler-x86-shared.h"
 #endif
 
 #include "wasm/WasmBinaryIterator.h"
+#include "wasm/WasmDebugFrame.h"
 #include "wasm/WasmGenerator.h"
 #include "wasm/WasmSignalHandlers.h"
 #include "wasm/WasmValidate.h"
 
 #include "jit/MacroAssembler-inl.h"
 
 using mozilla::DebugOnly;
 using mozilla::FloatingPoint;
@@ -2132,16 +2133,24 @@ class BaseCompiler
               default:
                 MOZ_CRASH("Function argument type");
             }
         }
 
         // The TLS pointer is always passed as a hidden argument in WasmTlsReg.
         // Save it into its assigned local slot.
         storeToFramePtr(WasmTlsReg, localInfo_[tlsSlot_].offs());
+        if (debugEnabled_) {
+            // Initialize funcIndex and flag fields of DebugFrame.
+            size_t debugFrame = masm.framePushed() - DebugFrame::offsetOfFrame();
+            masm.store32(Imm32(func_.index()),
+                         Address(masm.getStackPointer(), debugFrame + DebugFrame::offsetOfFuncIndex()));
+            masm.storePtr(ImmWord(0),
+                          Address(masm.getStackPointer(), debugFrame + DebugFrame::offsetOfFlagsWord()));
+        }
 
         // Initialize the stack locals to zero.
         //
         // The following are all Bug 1316820:
         //
         // TODO / OPTIMIZE: on x64, at least, scratch will be a 64-bit
         // register and we can move 64 bits at a time.
         //
@@ -7770,16 +7779,27 @@ BaseCompiler::init()
     // entries for special locals. Currently the only special local is the TLS
     // pointer.
     tlsSlot_ = locals_.length();
     if (!localInfo_.resize(locals_.length() + 1))
         return false;
 
     localSize_ = 0;
 
+    // Reserve a stack slot for the TLS pointer outside the varLow..varHigh
+    // range so it isn't zero-filled like the normal locals.
+    localInfo_[tlsSlot_].init(MIRType::Pointer, pushLocal(sizeof(void*)));
+    if (debugEnabled_) {
+        // If debug information is generated, constructing DebugFrame record:
+        // reserving some data before TLS pointer. The TLS pointer allocated
+        // above and regular wasm::Frame data starts after locals.
+        localSize_ += DebugFrame::offsetOfTlsData();
+        MOZ_ASSERT(DebugFrame::offsetOfFrame() == localSize_);
+    }
+
     for (ABIArgIter<const ValTypeVector> i(args); !i.done(); i++) {
         Local& l = localInfo_[i.index()];
         switch (i.mirType()) {
           case MIRType::Int32:
             if (i->argInRegister())
                 l.init(MIRType::Int32, pushLocal(4));
             else
                 l.init(MIRType::Int32, -(i->offsetFromArgBase() + sizeof(Frame)));
@@ -7802,20 +7822,16 @@ BaseCompiler::init()
             else
                 l.init(MIRType::Float32, -(i->offsetFromArgBase() + sizeof(Frame)));
             break;
           default:
             MOZ_CRASH("Argument type");
         }
     }
 
-    // Reserve a stack slot for the TLS pointer outside the varLow..varHigh
-    // range so it isn't zero-filled like the normal locals.
-    localInfo_[tlsSlot_].init(MIRType::Pointer, pushLocal(sizeof(void*)));
-
     varLow_ = localSize_;
 
     for (size_t i = args.length(); i < locals_.length(); i++) {
         Local& l = localInfo_[i];
         switch (locals_[i]) {
           case ValType::I32:
             l.init(MIRType::Int32, pushLocal(4));
             break;
new file mode 100644
--- /dev/null
+++ b/js/src/wasm/WasmDebugFrame.cpp
@@ -0,0 +1,65 @@
+/* -*- 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 2016 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/WasmDebugFrame.h"
+
+#include "vm/EnvironmentObject.h"
+#include "wasm/WasmInstance.h"
+
+#include "jsobjinlines.h"
+
+using namespace js;
+using namespace js::wasm;
+
+Instance*
+DebugFrame::instance() const
+{
+    return tlsData_->instance;
+}
+
+GlobalObject*
+DebugFrame::global() const
+{
+    return &instance()->object()->global();
+}
+
+JSObject*
+DebugFrame::environmentChain() const
+{
+    return &global()->lexicalEnvironment();
+}
+
+void
+DebugFrame::observeFrame(JSContext* cx)
+{
+   if (observing_)
+       return;
+
+   // TODO make sure wasm::Code onLeaveFrame traps are on
+   observing_ = true;
+}
+
+void
+DebugFrame::leaveFrame(JSContext* cx)
+{
+   if (!observing_)
+       return;
+
+   // TODO make sure wasm::Code onLeaveFrame traps are off
+   observing_ = false;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/wasm/WasmDebugFrame.h
@@ -0,0 +1,110 @@
+/* -*- 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 2016 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef wasmdebugframe_js_h
+#define wasmdebugframe_js_h
+
+#include "gc/Barrier.h"
+#include "js/RootingAPI.h"
+#include "js/TracingAPI.h"
+#include "wasm/WasmTypes.h"
+
+namespace js {
+
+class WasmFunctionCallObject;
+
+namespace wasm {
+
+class DebugFrame
+{
+    union
+    {
+        int32_t resultI32_;
+        int64_t resultI64_;
+        float   resultF32_;
+        double  resultF64_;
+    };
+
+    // The fields below are initialized by the baseline compiler.
+    uint32_t    funcIndex_;
+    uint32_t    reserved0_;
+
+    union
+    {
+        struct
+        {
+            bool    observing_ : 1;
+            bool    isDebuggee_ : 1;
+            bool    prevUpToDate_ : 1;
+            bool    hasCachedSavedFrame_ : 1;
+        };
+        void*   reserved1_;
+    };
+
+    TlsData*    tlsData_;
+    Frame       frame_;
+
+    explicit DebugFrame() {}
+
+  public:
+    inline uint32_t funcIndex() const { return funcIndex_; }
+    inline TlsData* tlsData() const { return tlsData_; }
+    inline Frame& frame() { return frame_; }
+
+    Instance* instance() const;
+    GlobalObject* global() const;
+
+    JSObject* environmentChain() const;
+
+    void observeFrame(JSContext* cx);
+    void leaveFrame(JSContext* cx);
+
+    void trace(JSTracer* trc);
+
+    // These are opaque boolean flags used by the debugger and
+    // saved-frame-chains code.
+    inline bool isDebuggee() const { return isDebuggee_; }
+    inline void setIsDebuggee() { isDebuggee_ = true; }
+    inline void unsetIsDebuggee() { isDebuggee_ = false; }
+
+    inline bool prevUpToDate() const { return prevUpToDate_; }
+    inline void setPrevUpToDate() { prevUpToDate_ = true; }
+    inline void unsetPrevUpToDate() { prevUpToDate_ = false; }
+
+    inline bool hasCachedSavedFrame() const { return hasCachedSavedFrame_; }
+    inline void setHasCachedSavedFrame() { hasCachedSavedFrame_ = true; }
+
+    inline void* resultsPtr() { return &resultI32_; }
+
+    static constexpr size_t offsetOfResults() { return offsetof(DebugFrame, resultI32_); }
+    static constexpr size_t offsetOfFlagsWord() { return offsetof(DebugFrame, reserved1_); }
+    static constexpr size_t offsetOfFuncIndex() { return offsetof(DebugFrame, funcIndex_); }
+    static constexpr size_t offsetOfTlsData() { return offsetof(DebugFrame, tlsData_); }
+    static constexpr size_t offsetOfFrame() { return offsetof(DebugFrame, frame_); }
+};
+
+static_assert(DebugFrame::offsetOfResults() == 0, "results shall be at offset 0");
+static_assert(DebugFrame::offsetOfTlsData() + sizeof(TlsData*) == DebugFrame::offsetOfFrame(),
+              "TLS pointer must be a field just before the wasm frame");
+static_assert(sizeof(DebugFrame) % 8 == 0 && DebugFrame::offsetOfFrame() % 8 == 0,
+              "DebugFrame and its portion is 8-bytes aligned for AbstractFramePtr");
+
+} // namespace wasm
+} // namespace js
+
+#endif // wasmdebugframe_js_h
--- a/js/src/wasm/WasmFrameIterator.cpp
+++ b/js/src/wasm/WasmFrameIterator.cpp
@@ -13,16 +13,17 @@
  * 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/WasmFrameIterator.h"
 
+#include "wasm/WasmDebugFrame.h"
 #include "wasm/WasmInstance.h"
 
 #include "jit/MacroAssembler-inl.h"
 
 using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
 
@@ -39,16 +40,23 @@ ReturnAddressFromFP(void* fp)
 }
 
 static uint8_t*
 CallerFPFromFP(void* fp)
 {
     return reinterpret_cast<Frame*>(fp)->callerFP;
 }
 
+static TlsData*
+TlsDataFromFP(void *fp)
+{
+    void* debugFrame = (uint8_t*)fp - DebugFrame::offsetOfFrame();
+    return reinterpret_cast<DebugFrame*>(debugFrame)->tlsData();
+}
+
 FrameIterator::FrameIterator()
   : activation_(nullptr),
     code_(nullptr),
     callsite_(nullptr),
     codeRange_(nullptr),
     fp_(nullptr),
     pc_(nullptr),
     missingFrameMessage_(false)
@@ -203,16 +211,40 @@ FrameIterator::functionDisplayAtom() con
 unsigned
 FrameIterator::lineOrBytecode() const
 {
     MOZ_ASSERT(!done());
     return callsite_ ? callsite_->lineOrBytecode()
                      : (codeRange_ ? codeRange_->funcLineOrBytecode() : 0);
 }
 
+Instance*
+FrameIterator::instance() const
+{
+    MOZ_ASSERT(!done() && debugEnabled());
+    return TlsDataFromFP(fp_ + callsite_->stackDepth())->instance;
+}
+
+bool
+FrameIterator::debugEnabled() const
+{
+    MOZ_ASSERT(!done() && code_);
+    MOZ_ASSERT_IF(!missingFrameMessage_, codeRange_->kind() == CodeRange::Function);
+    return code_->metadata().debugEnabled;
+}
+
+DebugFrame*
+FrameIterator::debugFrame() const
+{
+    MOZ_ASSERT(!done() && debugEnabled());
+    // The fp() points to wasm::Frame.
+    void* buf = static_cast<uint8_t*>(fp_ + callsite_->stackDepth()) - DebugFrame::offsetOfFrame();
+    return static_cast<DebugFrame*>(buf);
+}
+
 /*****************************************************************************/
 // Prologue/epilogue code generation
 
 // These constants reflect statically-determined offsets in the profiling
 // prologue/epilogue. The offsets are dynamically asserted during code
 // generation.
 #if defined(JS_CODEGEN_X64)
 # if defined(DEBUG)
--- a/js/src/wasm/WasmFrameIterator.h
+++ b/js/src/wasm/WasmFrameIterator.h
@@ -28,16 +28,18 @@ namespace js {
 class WasmActivation;
 namespace jit { class MacroAssembler; }
 
 namespace wasm {
 
 class CallSite;
 class Code;
 class CodeRange;
+class DebugFrame;
+class Instance;
 class SigIdDesc;
 struct CallThunk;
 struct FuncOffsets;
 struct ProfilingOffsets;
 struct TrapOffset;
 
 // Iterates over the frames of a single WasmActivation, called synchronously
 // from C++ in the thread of the asm.js.
@@ -64,18 +66,20 @@ class FrameIterator
     explicit FrameIterator(const WasmActivation& activation);
     void operator++();
     bool done() const;
     const char* filename() const;
     const char16_t* displayURL() const;
     bool mutedErrors() const;
     JSAtom* functionDisplayAtom() const;
     unsigned lineOrBytecode() const;
-    inline void* fp() const { return fp_; }
-    inline uint8_t* pc() const { return pc_; }
+    const CodeRange* codeRange() const { return codeRange_; }
+    Instance* instance() const;
+    bool debugEnabled() const;
+    DebugFrame* debugFrame() const;
 };
 
 // An ExitReason describes the possible reasons for leaving compiled wasm code
 // or the state of not having left compiled wasm code (ExitReason::None).
 enum class ExitReason : uint32_t
 {
     None,          // default state, the pc is in wasm code
     ImportJit,     // fast-path call directly into JIT code
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -1485,12 +1485,34 @@ struct MemoryPatch
 
     void offsetBy(uint32_t delta) {
         offset += delta;
     }
 };
 
 WASM_DECLARE_POD_VECTOR(MemoryPatch, MemoryPatchVector)
 
+// As an invariant across architectures, within wasm code:
+//   $sp % WasmStackAlignment = (sizeof(wasm::Frame) + masm.framePushed) % WasmStackAlignment
+// Thus, wasm::Frame represents the bytes pushed after the call (which occurred
+// with a WasmStackAlignment-aligned StackPointer) that are not included in
+// masm.framePushed.
+
+struct Frame
+{
+    // The caller's saved frame pointer. In non-profiling mode, internal
+    // wasm-to-wasm calls don't update fp and thus don't save the caller's
+    // frame pointer; the space is reserved, however, so that profiling mode can
+    // reuse the same function body without recompiling.
+    uint8_t* callerFP;
+
+    // The return address pushed by the call (in the case of ARM/MIPS the return
+    // address is pushed by the first instruction of the prologue).
+    void* returnAddress;
+};
+
+static_assert(sizeof(Frame) == 2 * sizeof(void*), "?!");
+static const uint32_t FrameBytesAfterReturnAddress = sizeof(void*);
+
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_types_h