diff --git a/js/src/jit-test/tests/debug/wasm-get-return.js b/js/src/jit-test/tests/debug/wasm-get-return.js new file mode 100644 index 000000000000..e7675a91f19a --- /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); + } +); + diff --git a/js/src/vm/Stack-inl.h b/js/src/vm/Stack-inl.h index 358034e9e850..8679d6fb7af9 100644 --- a/js/src/vm/Stack-inl.h +++ b/js/src/vm/Stack-inl.h @@ -423,7 +423,7 @@ AbstractFramePtr::returnValue() const if (isInterpreterFrame()) return asInterpreterFrame()->returnValue(); if (isWasmDebugFrame()) - return UndefinedHandleValue; + return asWasmDebugFrame()->returnValue(); return asBaselineFrame()->returnValue(); } diff --git a/js/src/wasm/WasmCode.cpp b/js/src/wasm/WasmCode.cpp index ed0dd9b46b0c..c836a79ad216 100644 --- a/js/src/wasm/WasmCode.cpp +++ b/js/src/wasm/WasmCode.cpp @@ -471,7 +471,8 @@ 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); @@ -512,6 +513,7 @@ Metadata::deserialize(const uint8_t* cursor) debugTrapFarJumpOffsets.clear(); debugFuncToCodeRange.clear(); debugFuncArgTypes.clear(); + debugFuncReturnTypes.clear(); return cursor; } @@ -1193,6 +1195,13 @@ Code::debugGetLocalTypes(uint32_t funcIndex, ValTypeVector* locals, size_t* args 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, diff --git a/js/src/wasm/WasmCode.h b/js/src/wasm/WasmCode.h index 314207d704bb..c18c32e23cc8 100644 --- a/js/src/wasm/WasmCode.h +++ b/js/src/wasm/WasmCode.h @@ -428,6 +428,7 @@ struct CustomSection typedef Vector CustomSectionVector; typedef Vector FuncArgTypesVector; +typedef Vector 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 @@ -479,6 +480,7 @@ struct Metadata : ShareableBase, MetadataCacheablePod Uint32Vector debugTrapFarJumpOffsets; Uint32Vector debugFuncToCodeRange; FuncArgTypesVector debugFuncArgTypes; + FuncReturnTypesVector debugFuncReturnTypes; bool usesMemory() const { return UsesMemory(memoryUsage); } bool hasSharedMemory() const { return memoryUsage == MemoryUsage::Shared; } @@ -671,6 +673,7 @@ class Code // Stack inspection helpers. bool debugGetLocalTypes(uint32_t funcIndex, ValTypeVector* locals, size_t* argsLength); + ExprType debugGetResultType(uint32_t funcIndex); // about:memory reporting: diff --git a/js/src/wasm/WasmDebugFrame.cpp b/js/src/wasm/WasmDebugFrame.cpp index 5558f0840d3b..274677d92068 100644 --- a/js/src/wasm/WasmDebugFrame.cpp +++ b/js/src/wasm/WasmDebugFrame.cpp @@ -65,6 +65,40 @@ DebugFrame::leaveFrame(JSContext* cx) 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) { @@ -89,10 +123,10 @@ DebugFrame::getLocal(uint32_t localIndex, MutableHandleValue vp) vp.set(NumberValue((double)*static_cast(dataPtr))); break; case jit::MIRType::Float32: - vp.set(NumberValue(*static_cast(dataPtr))); + vp.set(NumberValue(JS::CanonicalizeNaN(*static_cast(dataPtr)))); break; case jit::MIRType::Double: - vp.set(NumberValue(*static_cast(dataPtr))); + vp.set(NumberValue(JS::CanonicalizeNaN(*static_cast(dataPtr)))); break; default: MOZ_CRASH("local type"); diff --git a/js/src/wasm/WasmDebugFrame.h b/js/src/wasm/WasmDebugFrame.h index 3d66a77ace25..09951259d718 100644 --- a/js/src/wasm/WasmDebugFrame.h +++ b/js/src/wasm/WasmDebugFrame.h @@ -40,6 +40,8 @@ class DebugFrame double resultF64_; }; + js::Value cachedReturnJSValue_; + // The fields below are initialized by the baseline compiler. uint32_t funcIndex_; uint32_t reserved0_; @@ -52,6 +54,7 @@ class DebugFrame bool isDebuggee_ : 1; bool prevUpToDate_ : 1; bool hasCachedSavedFrame_ : 1; + bool hasCachedReturnJSValue_ : 1; }; void* reserved1_; }; @@ -91,6 +94,13 @@ class DebugFrame 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_); } diff --git a/js/src/wasm/WasmGenerator.cpp b/js/src/wasm/WasmGenerator.cpp index d27f89cf03cb..70ca474d9012 100644 --- a/js/src/wasm/WasmGenerator.cpp +++ b/js/src/wasm/WasmGenerator.cpp @@ -204,9 +204,12 @@ ModuleGenerator::initWasm(const CompileArgs& args) 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(); } } @@ -1160,6 +1163,7 @@ ModuleGenerator::finish(const ShareableBytes& bytecode) // Additional debug information to copy. metadata_->debugFuncArgTypes = Move(debugFuncArgTypes_); + metadata_->debugFuncReturnTypes = Move(debugFuncReturnTypes_); if (metadata_->debugEnabled) metadata_->debugFuncToCodeRange = Move(funcToCodeRange_); diff --git a/js/src/wasm/WasmGenerator.h b/js/src/wasm/WasmGenerator.h index 09ddd74f14e8..b7a1630a55d6 100644 --- a/js/src/wasm/WasmGenerator.h +++ b/js/src/wasm/WasmGenerator.h @@ -237,6 +237,7 @@ class MOZ_STACK_CLASS ModuleGenerator uint32_t startOfUnpatchedCallsites_; Uint32Vector debugTrapFarJumps_; FuncArgTypesVector debugFuncArgTypes_; + FuncReturnTypesVector debugFuncReturnTypes_; // Parallel compilation bool parallel_; diff --git a/js/src/wasm/WasmTypes.cpp b/js/src/wasm/WasmTypes.cpp index 09049ac2ebcd..99a334ece9db 100644 --- a/js/src/wasm/WasmTypes.cpp +++ b/js/src/wasm/WasmTypes.cpp @@ -129,6 +129,7 @@ WasmHandleDebugTrap() } if (site->kind() == CallSite::LeaveFrame) { DebugFrame* frame = iter.debugFrame(); + frame->updateReturnJSValue(); bool ok = Debugger::onLeaveFrame(cx, frame, nullptr, true); frame->leaveFrame(cx); return ok; @@ -174,6 +175,7 @@ WasmHandleThrow() continue; DebugFrame* frame = iter.debugFrame(); + frame->clearReturnJSValue(); // Assume JSTRAP_ERROR status if no exception is pending -- // no onExceptionUnwind handlers must be fired.