Bug 1343581 - Expose wasm function return value to Debugger.Frame. r=luke
authorYury Delendik <ydelendik@mozilla.com>
Thu, 02 Mar 2017 13:25:17 -0600
changeset 394677 45cc0fe2c02284fccdd71700d93c184e01927a29
parent 394676 30601e17a9251d4e8d51d0dbbfaf43f5da085839
child 394678 c9c38951c6b8807828c13a97a5b3ac22dc724c86
push id1468
push userasasaki@mozilla.com
push dateMon, 05 Jun 2017 19:31:07 +0000
treeherdermozilla-release@0641fc6ee9d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1343581
milestone54.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1343581 - Expose wasm function return value to Debugger.Frame. r=luke MozReview-Commit-ID: 4XPGHhrZTvM
js/src/jit-test/tests/debug/wasm-get-return.js
js/src/vm/Stack-inl.h
js/src/wasm/WasmCode.cpp
js/src/wasm/WasmCode.h
js/src/wasm/WasmDebugFrame.cpp
js/src/wasm/WasmDebugFrame.h
js/src/wasm/WasmGenerator.cpp
js/src/wasm/WasmGenerator.h
js/src/wasm/WasmTypes.cpp
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-get-return.js
@@ -0,0 +1,62 @@
+// |jit-test| test-also-wasm-baseline
+// Tests that wasm frame opPop event can access function resumption value.
+
+load(libdir + "wasm.js");
+load(libdir + 'eqArrayHelper.js');
+
+function monitorFrameOnPopReturns(wast, expected) {
+    var values = [];
+    wasmRunWithDebugger(
+        wast,
+        undefined,
+        function ({dbg}) {
+            dbg.onEnterFrame = function (frame) {
+                if (frame.type != 'wasmcall') return;
+                frame.onPop = function (value) {
+                    values.push(value.return);
+                };
+            };
+        },
+        function ({error}) {
+            assertEq(error, undefined);
+        }
+    );
+    assertEqArray(values, expected);
+}
+
+monitorFrameOnPopReturns(
+  `(module (func (export "test")))`,
+  [undefined]);
+monitorFrameOnPopReturns(
+  `(module (func (export "test") (result i32) (i32.const 42)))`,
+  [42]);
+monitorFrameOnPopReturns(
+  `(module (func (export "test") (result f32) (f32.const 0.5)))`,
+  [0.5]);
+monitorFrameOnPopReturns(
+  `(module (func (export "test") (result f64) (f64.const -42.75)))`,
+  [-42.75]);
+monitorFrameOnPopReturns(
+  `(module (func (result i64) (i64.const 2)) (func (export "test") (call 0) (drop)))`,
+  [2, undefined]);
+
+// Checking if throwing frame has right resumption value.
+var throwCount = 0;
+wasmRunWithDebugger(
+    '(module (func (unreachable)) (func (export "test") (result i32) (call 0) (i32.const 1)))',
+    undefined,
+    function ({dbg, g}) {
+        dbg.onEnterFrame = function (frame) {
+            if (frame.type != 'wasmcall') return;
+            frame.onPop = function (value) {
+                if ('throw' in value)
+                    throwCount++;
+            };
+        };
+    },
+    function ({error}) {
+        assertEq(error != undefined, true);
+        assertEq(throwCount, 2);
+    }
+);
+
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -418,17 +418,17 @@ FrameIter::unaliasedForEachActual(JSCont
 }
 
 inline HandleValue
 AbstractFramePtr::returnValue() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->returnValue();
     if (isWasmDebugFrame())
-        return UndefinedHandleValue;
+        return asWasmDebugFrame()->returnValue();
     return asBaselineFrame()->returnValue();
 }
 
 inline void
 AbstractFramePtr::setReturnValue(const Value& rval) const
 {
     if (isInterpreterFrame()) {
         asInterpreterFrame()->setReturnValue(rval);
--- a/js/src/wasm/WasmCode.cpp
+++ b/js/src/wasm/WasmCode.cpp
@@ -466,17 +466,18 @@ Metadata::serializedSize() const
            SerializedPodVectorSize(customSections) +
            filename.serializedSize();
 }
 
 uint8_t*
 Metadata::serialize(uint8_t* cursor) const
 {
     MOZ_ASSERT(!debugEnabled && debugTrapFarJumpOffsets.empty() &&
-               debugFuncArgTypes.empty() && debugFuncToCodeRange.empty());
+               debugFuncArgTypes.empty() && debugFuncReturnTypes.empty() &&
+               debugFuncToCodeRange.empty());
     cursor = WriteBytes(cursor, &pod(), sizeof(pod()));
     cursor = SerializeVector(cursor, funcImports);
     cursor = SerializeVector(cursor, funcExports);
     cursor = SerializeVector(cursor, sigIds);
     cursor = SerializePodVector(cursor, globals);
     cursor = SerializePodVector(cursor, tables);
     cursor = SerializePodVector(cursor, memoryAccesses);
     cursor = SerializePodVector(cursor, memoryPatches);
@@ -507,16 +508,17 @@ Metadata::deserialize(const uint8_t* cur
     (cursor = DeserializePodVector(cursor, &callThunks)) &&
     (cursor = DeserializePodVector(cursor, &funcNames)) &&
     (cursor = DeserializePodVector(cursor, &customSections)) &&
     (cursor = filename.deserialize(cursor));
     debugEnabled = false;
     debugTrapFarJumpOffsets.clear();
     debugFuncToCodeRange.clear();
     debugFuncArgTypes.clear();
+    debugFuncReturnTypes.clear();
     return cursor;
 }
 
 size_t
 Metadata::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
 {
     return SizeOfVectorExcludingThis(funcImports, mallocSizeOf) +
            SizeOfVectorExcludingThis(funcExports, mallocSizeOf) +
@@ -1188,16 +1190,23 @@ Code::debugGetLocalTypes(uint32_t funcIn
     // In wasm, the Code points to the function start via funcLineOrBytecode.
     MOZ_ASSERT(!metadata_->isAsmJS() && maybeBytecode_);
     size_t offsetInModule = range.funcLineOrBytecode();
     Decoder d(maybeBytecode_->begin() + offsetInModule,  maybeBytecode_->end(),
               offsetInModule, /* error = */ nullptr);
     return DecodeLocalEntries(d, metadata_->kind, locals);
 }
 
+ExprType
+Code::debugGetResultType(uint32_t funcIndex)
+{
+    MOZ_ASSERT(metadata_->debugEnabled);
+    return metadata_->debugFuncReturnTypes[funcIndex];
+}
+
 void
 Code::addSizeOfMisc(MallocSizeOf mallocSizeOf,
                     Metadata::SeenSet* seenMetadata,
                     ShareableBytes::SeenSet* seenBytes,
                     size_t* code,
                     size_t* data) const
 {
     *code += segment_->length();
--- a/js/src/wasm/WasmCode.h
+++ b/js/src/wasm/WasmCode.h
@@ -423,16 +423,17 @@ struct CustomSection
     CustomSection() = default;
     CustomSection(NameInBytecode name, uint32_t offset, uint32_t length)
       : name(name), offset(offset), length(length)
     {}
 };
 
 typedef Vector<CustomSection, 0, SystemAllocPolicy> CustomSectionVector;
 typedef Vector<ValTypeVector, 0, SystemAllocPolicy> FuncArgTypesVector;
+typedef Vector<ExprType, 0, SystemAllocPolicy> FuncReturnTypesVector;
 
 // Metadata holds all the data that is needed to describe compiled wasm code
 // at runtime (as opposed to data that is only used to statically link or
 // instantiate a module).
 //
 // Metadata is built incrementally by ModuleGenerator and then shared immutably
 // between modules.
 
@@ -474,16 +475,17 @@ struct Metadata : ShareableBase<Metadata
     CustomSectionVector   customSections;
     CacheableChars        filename;
 
     // Debug-enabled code is not serialized.
     bool                  debugEnabled;
     Uint32Vector          debugTrapFarJumpOffsets;
     Uint32Vector          debugFuncToCodeRange;
     FuncArgTypesVector    debugFuncArgTypes;
+    FuncReturnTypesVector debugFuncReturnTypes;
 
     bool usesMemory() const { return UsesMemory(memoryUsage); }
     bool hasSharedMemory() const { return memoryUsage == MemoryUsage::Shared; }
 
     const FuncExport& lookupFuncExport(uint32_t funcIndex) const;
 
     // AsmJSMetadata derives Metadata iff isAsmJS(). Mostly this distinction is
     // encapsulated within AsmJS.cpp, but the additional virtual functions allow
@@ -666,16 +668,17 @@ class Code
 
     bool stepModeEnabled(uint32_t funcIndex) const;
     bool incrementStepModeCount(JSContext* cx, uint32_t funcIndex);
     bool decrementStepModeCount(JSContext* cx, uint32_t funcIndex);
 
     // Stack inspection helpers.
 
     bool debugGetLocalTypes(uint32_t funcIndex, ValTypeVector* locals, size_t* argsLength);
+    ExprType debugGetResultType(uint32_t funcIndex);
 
     // about:memory reporting:
 
     void addSizeOfMisc(MallocSizeOf mallocSizeOf,
                        Metadata::SeenSet* seenMetadata,
                        ShareableBytes::SeenSet* seenBytes,
                        size_t* code,
                        size_t* data) const;
--- a/js/src/wasm/WasmDebugFrame.cpp
+++ b/js/src/wasm/WasmDebugFrame.cpp
@@ -60,16 +60,50 @@ DebugFrame::leaveFrame(JSContext* cx)
 {
    if (!observing_)
        return;
 
    instance()->code().adjustEnterAndLeaveFrameTrapsState(cx, /* enabled = */ false);
    observing_ = false;
 }
 
+void
+DebugFrame::clearReturnJSValue()
+{
+    hasCachedReturnJSValue_ = true;
+    cachedReturnJSValue_.setUndefined();
+}
+
+void
+DebugFrame::updateReturnJSValue()
+{
+    hasCachedReturnJSValue_ = true;
+    ExprType returnType = instance()->code().debugGetResultType(funcIndex());
+    switch (returnType) {
+      case ExprType::Void:
+          cachedReturnJSValue_.setUndefined();
+          break;
+      case ExprType::I32:
+          cachedReturnJSValue_.setInt32(resultI32_);
+          break;
+      case ExprType::I64:
+          // Just display as a Number; it's ok if we lose some precision
+          cachedReturnJSValue_.setDouble((double)resultI64_);
+          break;
+      case ExprType::F32:
+          cachedReturnJSValue_.setDouble(JS::CanonicalizeNaN(resultF32_));
+          break;
+      case ExprType::F64:
+          cachedReturnJSValue_.setDouble(JS::CanonicalizeNaN(resultF64_));
+          break;
+      default:
+          MOZ_CRASH("result type");
+    }
+}
+
 bool
 DebugFrame::getLocal(uint32_t localIndex, MutableHandleValue vp)
 {
     ValTypeVector locals;
     size_t argsLength;
     if (!instance()->code().debugGetLocalTypes(funcIndex(), &locals, &argsLength))
         return false;
 
@@ -84,19 +118,19 @@ DebugFrame::getLocal(uint32_t localIndex
       case jit::MIRType::Int32:
           vp.set(Int32Value(*static_cast<int32_t*>(dataPtr)));
           break;
       case jit::MIRType::Int64:
           // Just display as a Number; it's ok if we lose some precision
           vp.set(NumberValue((double)*static_cast<int64_t*>(dataPtr)));
           break;
       case jit::MIRType::Float32:
-          vp.set(NumberValue(*static_cast<float*>(dataPtr)));
+          vp.set(NumberValue(JS::CanonicalizeNaN(*static_cast<float*>(dataPtr))));
           break;
       case jit::MIRType::Double:
-          vp.set(NumberValue(*static_cast<double*>(dataPtr)));
+          vp.set(NumberValue(JS::CanonicalizeNaN(*static_cast<double*>(dataPtr))));
           break;
       default:
           MOZ_CRASH("local type");
     }
     return true;
 }
 
--- a/js/src/wasm/WasmDebugFrame.h
+++ b/js/src/wasm/WasmDebugFrame.h
@@ -35,28 +35,31 @@ class DebugFrame
     union
     {
         int32_t resultI32_;
         int64_t resultI64_;
         float   resultF32_;
         double  resultF64_;
     };
 
+    js::Value   cachedReturnJSValue_;
+
     // 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;
+            bool    hasCachedReturnJSValue_ : 1;
         };
         void*   reserved1_;
     };
 
     TlsData*    tlsData_;
     Frame       frame_;
 
     explicit DebugFrame() {}
@@ -86,16 +89,23 @@ class DebugFrame
     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_; }
 
+    inline HandleValue returnValue() const {
+        MOZ_ASSERT(hasCachedReturnJSValue_);
+        return HandleValue::fromMarkedLocation(&cachedReturnJSValue_);
+    }
+    void updateReturnJSValue();
+    void clearReturnJSValue();
+
     bool getLocal(uint32_t localIndex, MutableHandleValue vp);
 
     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_); }
 };
--- a/js/src/wasm/WasmGenerator.cpp
+++ b/js/src/wasm/WasmGenerator.cpp
@@ -199,19 +199,22 @@ ModuleGenerator::initWasm(const CompileA
         metadata_->startFuncIndex.emplace(*env_->startFuncIndex);
         if (!exportedFuncs_.put(*env_->startFuncIndex))
             return false;
     }
 
     if (metadata_->debugEnabled) {
         if (!debugFuncArgTypes_.resize(env_->funcSigs.length()))
             return false;
+        if (!debugFuncReturnTypes_.resize(env_->funcSigs.length()))
+            return false;
         for (size_t i = 0; i < debugFuncArgTypes_.length(); i++) {
             if (!debugFuncArgTypes_[i].appendAll(env_->funcSigs[i]->args()))
                 return false;
+            debugFuncReturnTypes_[i] = env_->funcSigs[i]->ret();
         }
     }
 
     return true;
 }
 
 bool
 ModuleGenerator::init(UniqueModuleEnvironment env, const CompileArgs& args,
@@ -1155,16 +1158,17 @@ ModuleGenerator::finish(const ShareableB
     metadata_->maxMemoryLength = env_->maxMemoryLength;
     metadata_->tables = Move(env_->tables);
     metadata_->globals = Move(env_->globals);
     metadata_->funcNames = Move(env_->funcNames);
     metadata_->customSections = Move(env_->customSections);
 
     // Additional debug information to copy.
     metadata_->debugFuncArgTypes = Move(debugFuncArgTypes_);
+    metadata_->debugFuncReturnTypes = Move(debugFuncReturnTypes_);
     if (metadata_->debugEnabled)
         metadata_->debugFuncToCodeRange = Move(funcToCodeRange_);
 
     // These Vectors can get large and the excess capacity can be significant,
     // so realloc them down to size.
     metadata_->memoryAccesses.podResizeToFit();
     metadata_->memoryPatches.podResizeToFit();
     metadata_->boundsChecks.podResizeToFit();
--- a/js/src/wasm/WasmGenerator.h
+++ b/js/src/wasm/WasmGenerator.h
@@ -232,16 +232,17 @@ class MOZ_STACK_CLASS ModuleGenerator
     jit::TempAllocator              masmAlloc_;
     jit::MacroAssembler             masm_;
     Uint32Vector                    funcToCodeRange_;
     Uint32Set                       exportedFuncs_;
     uint32_t                        lastPatchedCallsite_;
     uint32_t                        startOfUnpatchedCallsites_;
     Uint32Vector                    debugTrapFarJumps_;
     FuncArgTypesVector              debugFuncArgTypes_;
+    FuncReturnTypesVector           debugFuncReturnTypes_;
 
     // Parallel compilation
     bool                            parallel_;
     uint32_t                        outstanding_;
     CompileTaskVector               tasks_;
     CompileTaskPtrVector            freeTasks_;
     UniqueFuncBytesVector           freeFuncBytes_;
     CompileTask*                    currentTask_;
--- a/js/src/wasm/WasmTypes.cpp
+++ b/js/src/wasm/WasmTypes.cpp
@@ -124,16 +124,17 @@ WasmHandleDebugTrap()
             // TODO properly handle JSTRAP_RETURN and resume wasm execution.
             JS_ReportErrorASCII(cx, "Unexpected resumption value from onEnterFrame");
             return false;
         }
         return status == JSTRAP_CONTINUE;
     }
     if (site->kind() == CallSite::LeaveFrame) {
         DebugFrame* frame = iter.debugFrame();
+        frame->updateReturnJSValue();
         bool ok = Debugger::onLeaveFrame(cx, frame, nullptr, true);
         frame->leaveFrame(cx);
         return ok;
     }
 
     DebugFrame* frame = iter.debugFrame();
     Code& code = iter.instance()->code();
     MOZ_ASSERT(code.hasBreakpointTrapAtOffset(site->lineOrBytecode()));
@@ -169,16 +170,17 @@ WasmHandleThrow()
     MOZ_ASSERT(activation);
     JSContext* cx = activation->cx();
 
     for (FrameIterator iter(activation, FrameIterator::Unwind::True); !iter.done(); ++iter) {
         if (!iter.debugEnabled())
             continue;
 
         DebugFrame* frame = iter.debugFrame();
+        frame->clearReturnJSValue();
 
         // Assume JSTRAP_ERROR status if no exception is pending --
         // no onExceptionUnwind handlers must be fired.
         if (cx->isExceptionPending()) {
             JSTrapStatus status = Debugger::onExceptionUnwind(cx, frame);
             if (status == JSTRAP_RETURN) {
                 // Unexpected trap return -- raising error since throw recovery
                 // is not yet implemented in the wasm baseline.