Bug 1343581 - Expose wasm function return value to Debugger.Frame. r?luke
MozReview-Commit-ID: 4XPGHhrZTvM
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.