Bug 1343581 - Expose wasm function return value to Debugger.Frame. r?luke draft
authorYury Delendik <ydelendik@mozilla.com>
Thu, 02 Mar 2017 13:25:17 -0600
changeset 492252 41742349c5f61de7f4abbc85a553586bcf5dc0bc
parent 492009 d29f84406483c721a13cf9a52936ecced0c5c98a
child 493008 8038dd0b6e0cc4294ceed75f1fd1ab9c0e6cc4f9
push id47575
push userydelendik@mozilla.com
push dateThu, 02 Mar 2017 23:31:49 +0000
reviewersluke
bugs1343581
milestone54.0a1
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.