Bug 1286948 - Adds prolog and epilog debug traps and handlers. r=luke

Using toggled call/traps to invoke handler to process enter and leave
frame events.

MozReview-Commit-ID: APTt3N6Zt0P

--HG--
extra : rebase_source : 1121020f29539e2155bfaea1dc36f07d9a45d003
This commit is contained in:
Yury Delendik 2017-01-07 10:38:44 -06:00
Родитель 89670717cb
Коммит 76fa09762a
22 изменённых файлов: 469 добавлений и 14 удалений

Просмотреть файл

@ -127,7 +127,7 @@ testError(
(func (export "") (call $foo)) (func (export "") (call $foo))
)`, )`,
WebAssembly.RuntimeError, WebAssembly.RuntimeError,
["", ">", "1,>", "0,1,>", "trap handling,0,1,>", "inline stub,0,1,>", ""]); ["", ">", "1,>", "0,1,>", "trap handling,0,1,>", "inline stub,0,1,>", "trap handling,0,1,>", "inline stub,0,1,>", ""]);
testError( testError(
`(module `(module
@ -142,7 +142,7 @@ WebAssembly.RuntimeError,
// Technically we have this one *one-instruction* interval where // Technically we have this one *one-instruction* interval where
// the caller is lost (the stack with "1,>"). It's annoying to fix and shouldn't // the caller is lost (the stack with "1,>"). It's annoying to fix and shouldn't
// mess up profiles in practice so we ignore it. // mess up profiles in practice so we ignore it.
["", ">", "0,>", "1,0,>", "1,>", "trap handling,0,>", "inline stub,0,>", ""]); ["", ">", "0,>", "1,0,>", "1,>", "trap handling,0,>", "inline stub,0,>", "trap handling,0,>", "inline stub,0,>", ""]);
(function() { (function() {
var e = wasmEvalText(` var e = wasmEvalText(`

Просмотреть файл

@ -529,6 +529,12 @@ class MacroAssembler : public MacroAssemblerSpecific
static void patchNopToNearJump(uint8_t* jump, uint8_t* target) PER_SHARED_ARCH; static void patchNopToNearJump(uint8_t* jump, uint8_t* target) PER_SHARED_ARCH;
static void patchNearJumpToNop(uint8_t* jump) PER_SHARED_ARCH; static void patchNearJumpToNop(uint8_t* jump) PER_SHARED_ARCH;
// Emit a nop that can be patched to and from a nop and a call with int32
// relative displacement.
CodeOffset nopPatchableToCall(const wasm::CallSiteDesc& desc) PER_SHARED_ARCH;
static void patchNopToCall(uint8_t* callsite, uint8_t* target) PER_SHARED_ARCH;
static void patchCallToNop(uint8_t* callsite) PER_SHARED_ARCH;
public: public:
// =============================================================== // ===============================================================
// ABI function calls. // ABI function calls.

Просмотреть файл

@ -5128,6 +5128,34 @@ MacroAssembler::patchNearJumpToNop(uint8_t* jump)
new (jump) InstNOP(); new (jump) InstNOP();
} }
CodeOffset
MacroAssembler::nopPatchableToCall(const wasm::CallSiteDesc& desc)
{
CodeOffset offset(currentOffset());
ma_nop();
append(desc, CodeOffset(currentOffset()), framePushed());
return offset;
}
void
MacroAssembler::patchNopToCall(uint8_t* call, uint8_t* target)
{
uint8_t* inst = call - 4;
MOZ_ASSERT(reinterpret_cast<Instruction*>(inst)->is<InstBLImm>() ||
reinterpret_cast<Instruction*>(inst)->is<InstNOP>());
new (inst) InstBLImm(BOffImm(target - inst), Assembler::Always);
}
void
MacroAssembler::patchCallToNop(uint8_t* call)
{
uint8_t* inst = call - 4;
MOZ_ASSERT(reinterpret_cast<Instruction*>(inst)->is<InstBLImm>() ||
reinterpret_cast<Instruction*>(inst)->is<InstNOP>());
new (inst) InstNOP();
}
void void
MacroAssembler::pushReturnAddress() MacroAssembler::pushReturnAddress()
{ {

Просмотреть файл

@ -587,6 +587,25 @@ MacroAssembler::patchNearJumpToNop(uint8_t* jump)
MOZ_CRASH("NYI"); MOZ_CRASH("NYI");
} }
CodeOffset
MacroAssembler::nopPatchableToCall(const wasm::CallSiteDesc& desc)
{
MOZ_CRASH("NYI");
return CodeOffset();
}
void
MacroAssembler::patchNopToCall(uint8_t* call, uint8_t* target)
{
MOZ_CRASH("NYI");
}
void
MacroAssembler::patchCallToNop(uint8_t* call)
{
MOZ_CRASH("NYI");
}
void void
MacroAssembler::pushReturnAddress() MacroAssembler::pushReturnAddress()
{ {

Просмотреть файл

@ -1678,6 +1678,25 @@ MacroAssembler::call(JitCode* c)
callJitNoProfiler(ScratchRegister); callJitNoProfiler(ScratchRegister);
} }
CodeOffset
MacroAssembler::nopPatchableToCall(const wasm::CallSiteDesc& desc)
{
MOZ_CRASH("NYI");
return CodeOffset();
}
void
MacroAssembler::patchNopToCall(uint8_t* call, uint8_t* target)
{
MOZ_CRASH("NYI");
}
void
MacroAssembler::patchCallToNop(uint8_t* call)
{
MOZ_CRASH("NYI");
}
void void
MacroAssembler::pushReturnAddress() MacroAssembler::pushReturnAddress()
{ {

Просмотреть файл

@ -1106,6 +1106,13 @@ class AssemblerX86Shared : public AssemblerShared
X86Encoding::BaseAssembler::patchJumpToTwoByteNop(jump); X86Encoding::BaseAssembler::patchJumpToTwoByteNop(jump);
} }
static void patchFiveByteNopToCall(uint8_t* callsite, uint8_t* target) {
X86Encoding::BaseAssembler::patchFiveByteNopToCall(callsite, target);
}
static void patchCallToFiveByteNop(uint8_t* callsite) {
X86Encoding::BaseAssembler::patchCallToFiveByteNop(callsite);
}
void breakpoint() { void breakpoint() {
masm.int3(); masm.int3();
} }

Просмотреть файл

@ -116,6 +116,40 @@ public:
jump[1] = OP_NOP; jump[1] = OP_NOP;
} }
static void patchFiveByteNopToCall(uint8_t* callsite, uint8_t* target)
{
// Note: the offset is relative to the address of the instruction after
// the call which is five bytes.
uint8_t* inst = callsite - sizeof(int32_t) - 1;
// The nop can be already patched as call, overriding the call.
// See also nop_five.
MOZ_ASSERT(inst[0] == OP_NOP_0F || inst[0] == OP_CALL_rel32);
MOZ_ASSERT_IF(inst[0] == OP_NOP_0F, inst[1] == OP_NOP_1F ||
inst[2] == OP_NOP_44 ||
inst[3] == OP_NOP_00 ||
inst[4] == OP_NOP_00);
inst[0] = OP_CALL_rel32;
SetRel32(callsite, target);
}
static void patchCallToFiveByteNop(uint8_t* callsite)
{
// See also patchFiveByteNopToCall and nop_five.
uint8_t* inst = callsite - sizeof(int32_t) - 1;
// The call can be already patched as nop.
if (inst[0] == OP_NOP_0F) {
MOZ_ASSERT(inst[1] == OP_NOP_1F || inst[2] == OP_NOP_44 ||
inst[3] == OP_NOP_00 || inst[4] == OP_NOP_00);
return;
}
MOZ_ASSERT(inst[0] == OP_CALL_rel32);
inst[0] = OP_NOP_0F;
inst[1] = OP_NOP_1F;
inst[2] = OP_NOP_44;
inst[3] = OP_NOP_00;
inst[4] = OP_NOP_00;
}
/* /*
* The nop multibytes sequences are directly taken from the Intel's * The nop multibytes sequences are directly taken from the Intel's
* architecture software developer manual. * architecture software developer manual.

Просмотреть файл

@ -741,6 +741,28 @@ MacroAssembler::patchNearJumpToNop(uint8_t* jump)
Assembler::patchJumpToTwoByteNop(jump); Assembler::patchJumpToTwoByteNop(jump);
} }
CodeOffset
MacroAssembler::nopPatchableToCall(const wasm::CallSiteDesc& desc)
{
CodeOffset offset(currentOffset());
masm.nop_five();
append(desc, CodeOffset(currentOffset()), framePushed());
MOZ_ASSERT_IF(!oom(), size() - offset.offset() == ToggledCallSize(nullptr));
return offset;
}
void
MacroAssembler::patchNopToCall(uint8_t* callsite, uint8_t* target)
{
Assembler::patchFiveByteNopToCall(callsite, target);
}
void
MacroAssembler::patchCallToNop(uint8_t* callsite)
{
Assembler::patchCallToFiveByteNop(callsite);
}
// =============================================================== // ===============================================================
// Jit Frames. // Jit Frames.

Просмотреть файл

@ -2080,6 +2080,11 @@ class BaseCompiler
labelPool_.free(label); labelPool_.free(label);
} }
void insertBreakablePoint(CallSiteDesc::Kind kind) {
const uint32_t offset = iter_.currentOffset();
masm.nopPatchableToCall(CallSiteDesc(offset, kind));
}
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// //
// Function prologue and epilogue. // Function prologue and epilogue.
@ -2167,6 +2172,58 @@ class BaseCompiler
for (int32_t i = varLow_ ; i < varHigh_ ; i += 4) for (int32_t i = varLow_ ; i < varHigh_ ; i += 4)
storeToFrameI32(scratch, i + 4); storeToFrameI32(scratch, i + 4);
} }
if (debugEnabled_)
insertBreakablePoint(CallSiteDesc::EnterFrame);
}
void saveResult() {
MOZ_ASSERT(debugEnabled_);
size_t debugFrameOffset = masm.framePushed() - DebugFrame::offsetOfFrame();
Address resultsAddress(StackPointer, debugFrameOffset + DebugFrame::offsetOfResults());
switch (func_.sig().ret()) {
case ExprType::Void:
break;
case ExprType::I32:
masm.store32(RegI32(ReturnReg), resultsAddress);
break;
case ExprType::I64:
masm.store64(RegI64(ReturnReg64), resultsAddress);
break;
case ExprType::F64:
masm.storeDouble(RegF64(ReturnDoubleReg), resultsAddress);
break;
case ExprType::F32:
masm.storeFloat32(RegF32(ReturnFloat32Reg), resultsAddress);
break;
default:
MOZ_CRASH("Function return type");
}
}
void restoreResult() {
MOZ_ASSERT(debugEnabled_);
size_t debugFrameOffset = masm.framePushed() - DebugFrame::offsetOfFrame();
Address resultsAddress(StackPointer, debugFrameOffset + DebugFrame::offsetOfResults());
switch (func_.sig().ret()) {
case ExprType::Void:
break;
case ExprType::I32:
masm.load32(resultsAddress, RegI32(ReturnReg));
break;
case ExprType::I64:
masm.load64(resultsAddress, RegI64(ReturnReg64));
break;
case ExprType::F64:
masm.loadDouble(resultsAddress, RegF64(ReturnDoubleReg));
break;
case ExprType::F32:
masm.loadFloat32(resultsAddress, RegF32(ReturnFloat32Reg));
break;
default:
MOZ_CRASH("Function return type");
}
} }
bool endFunction() { bool endFunction() {
@ -2188,6 +2245,14 @@ class BaseCompiler
masm.bind(&returnLabel_); masm.bind(&returnLabel_);
if (debugEnabled_) {
// Store and reload the return value from DebugFrame::return so that
// it can be clobbered, and/or modified by the debug trap.
saveResult();
insertBreakablePoint(CallSiteDesc::LeaveFrame);
restoreResult();
}
// Restore the TLS register in case it was overwritten by the function. // Restore the TLS register in case it was overwritten by the function.
loadFromFramePtr(WasmTlsReg, frameOffsetFromSlot(tlsSlot_, MIRType::Pointer)); loadFromFramePtr(WasmTlsReg, frameOffsetFromSlot(tlsSlot_, MIRType::Pointer));

Просмотреть файл

@ -346,7 +346,8 @@ CodeRange::CodeRange(Kind kind, Offsets offsets)
kind_(kind) kind_(kind)
{ {
MOZ_ASSERT(begin_ <= end_); MOZ_ASSERT(begin_ <= end_);
MOZ_ASSERT(kind_ == Entry || kind_ == Inline || kind_ == FarJumpIsland); MOZ_ASSERT(kind_ == Entry || kind_ == Inline ||
kind_ == FarJumpIsland || kind_ == DebugTrap);
} }
CodeRange::CodeRange(Kind kind, ProfilingOffsets offsets) CodeRange::CodeRange(Kind kind, ProfilingOffsets offsets)
@ -458,7 +459,7 @@ Metadata::serializedSize() const
uint8_t* uint8_t*
Metadata::serialize(uint8_t* cursor) const Metadata::serialize(uint8_t* cursor) const
{ {
MOZ_ASSERT(!debugEnabled); MOZ_ASSERT(!debugEnabled && debugTrapFarJumpOffsets.empty());
cursor = WriteBytes(cursor, &pod(), sizeof(pod())); cursor = WriteBytes(cursor, &pod(), sizeof(pod()));
cursor = SerializeVector(cursor, funcImports); cursor = SerializeVector(cursor, funcImports);
cursor = SerializeVector(cursor, funcExports); cursor = SerializeVector(cursor, funcExports);
@ -496,6 +497,7 @@ Metadata::deserialize(const uint8_t* cursor)
(cursor = DeserializePodVector(cursor, &customSections)) && (cursor = DeserializePodVector(cursor, &customSections)) &&
(cursor = filename.deserialize(cursor)); (cursor = filename.deserialize(cursor));
debugEnabled = false; debugEnabled = false;
debugTrapFarJumpOffsets.clear();
return cursor; return cursor;
} }
@ -573,6 +575,7 @@ Code::Code(UniqueCodeSegment segment,
: segment_(Move(segment)), : segment_(Move(segment)),
metadata_(&metadata), metadata_(&metadata),
maybeBytecode_(maybeBytecode), maybeBytecode_(maybeBytecode),
enterAndLeaveFrameTrapsCounter_(0),
profilingEnabled_(false) profilingEnabled_(false)
{ {
MOZ_ASSERT_IF(metadata_->debugEnabled, maybeBytecode); MOZ_ASSERT_IF(metadata_->debugEnabled, maybeBytecode);
@ -820,6 +823,52 @@ Code::ensureProfilingState(JSRuntime* rt, bool newProfilingEnabled)
return true; return true;
} }
void
Code::toggleDebugTrap(uint32_t offset, bool enabled)
{
MOZ_ASSERT(offset);
uint8_t* trap = segment_->base() + offset;
const Uint32Vector& farJumpOffsets = metadata_->debugTrapFarJumpOffsets;
if (enabled) {
MOZ_ASSERT(farJumpOffsets.length() > 0);
size_t i = 0;
while (i < farJumpOffsets.length() && offset < farJumpOffsets[i])
i++;
if (i >= farJumpOffsets.length() ||
(i > 0 && offset - farJumpOffsets[i - 1] < farJumpOffsets[i] - offset))
i--;
uint8_t* farJump = segment_->base() + farJumpOffsets[i];
MacroAssembler::patchNopToCall(trap, farJump);
} else {
MacroAssembler::patchCallToNop(trap);
}
}
void
Code::adjustEnterAndLeaveFrameTrapsState(JSContext* cx, bool enabled)
{
MOZ_ASSERT(metadata_->debugEnabled);
MOZ_ASSERT_IF(!enabled, enterAndLeaveFrameTrapsCounter_ > 0);
bool wasEnabled = enterAndLeaveFrameTrapsCounter_ > 0;
if (enabled)
++enterAndLeaveFrameTrapsCounter_;
else
--enterAndLeaveFrameTrapsCounter_;
bool stillEnabled = enterAndLeaveFrameTrapsCounter_ > 0;
if (wasEnabled == stillEnabled)
return;
AutoWritableJitCode awjc(cx->runtime(), segment_->base(), segment_->codeLength());
AutoFlushICache afc("Code::adjustEnterAndLeaveFrameTrapsState");
AutoFlushICache::setRange(uintptr_t(segment_->base()), segment_->codeLength());
for (const CallSite& callSite : metadata_->callSites) {
if (callSite.kind() != CallSite::EnterFrame && callSite.kind() != CallSite::LeaveFrame)
continue;
toggleDebugTrap(callSite.returnAddressOffset(), stillEnabled);
}
}
void void
Code::addSizeOfMisc(MallocSizeOf mallocSizeOf, Code::addSizeOfMisc(MallocSizeOf mallocSizeOf,
Metadata::SeenSet* seenMetadata, Metadata::SeenSet* seenMetadata,

Просмотреть файл

@ -19,16 +19,19 @@
#ifndef wasm_code_h #ifndef wasm_code_h
#define wasm_code_h #define wasm_code_h
#include "js/HashTable.h"
#include "wasm/WasmTypes.h" #include "wasm/WasmTypes.h"
namespace js { namespace js {
struct AsmJSMetadata; struct AsmJSMetadata;
class WasmActivation;
namespace wasm { namespace wasm {
struct LinkData; struct LinkData;
struct Metadata; struct Metadata;
class FrameIterator;
// A wasm CodeSegment owns the allocated executable code for a wasm module. // A wasm CodeSegment owns the allocated executable code for a wasm module.
// This allocation also currently includes the global data segment, which allows // This allocation also currently includes the global data segment, which allows
@ -240,6 +243,8 @@ class CodeRange
ImportJitExit, // fast-path calling from wasm into JIT code ImportJitExit, // fast-path calling from wasm into JIT code
ImportInterpExit, // slow-path calling from wasm into C++ interp ImportInterpExit, // slow-path calling from wasm into C++ interp
TrapExit, // calls C++ to report and jumps to throw stub TrapExit, // calls C++ to report and jumps to throw stub
DebugTrap, // calls C++ to handle debug event such as
// enter/leave frame or breakpoint
FarJumpIsland, // inserted to connect otherwise out-of-range insns FarJumpIsland, // inserted to connect otherwise out-of-range insns
Inline // stub that is jumped-to, not called, and thus Inline // stub that is jumped-to, not called, and thus
// replaces/loses preceding innermost frame // replaces/loses preceding innermost frame
@ -469,6 +474,7 @@ struct Metadata : ShareableBase<Metadata>, MetadataCacheablePod
// Debug-enabled code is not serialized. // Debug-enabled code is not serialized.
bool debugEnabled; bool debugEnabled;
Uint32Vector debugTrapFarJumpOffsets;
bool usesMemory() const { return UsesMemory(memoryUsage); } bool usesMemory() const { return UsesMemory(memoryUsage); }
bool hasSharedMemory() const { return memoryUsage == MemoryUsage::Shared; } bool hasSharedMemory() const { return memoryUsage == MemoryUsage::Shared; }
@ -574,8 +580,11 @@ class Code
const SharedBytes maybeBytecode_; const SharedBytes maybeBytecode_;
UniqueGeneratedSourceMap maybeSourceMap_; UniqueGeneratedSourceMap maybeSourceMap_;
CacheableCharsVector funcLabels_; CacheableCharsVector funcLabels_;
uint32_t enterAndLeaveFrameTrapsCounter_;
bool profilingEnabled_; bool profilingEnabled_;
void toggleDebugTrap(uint32_t offset, bool enabled);
public: public:
Code(UniqueCodeSegment segment, Code(UniqueCodeSegment segment,
const Metadata& metadata, const Metadata& metadata,
@ -614,6 +623,12 @@ class Code
bool profilingEnabled() const { return profilingEnabled_; } bool profilingEnabled() const { return profilingEnabled_; }
const char* profilingLabel(uint32_t funcIndex) const { return funcLabels_[funcIndex].get(); } const char* profilingLabel(uint32_t funcIndex) const { return funcLabels_[funcIndex].get(); }
// The Code can track enter/leave frame events. Any such event triggers
// debug trap. The enter frame events enabled across all functions, but
// the leave frame events only for particular function.
void adjustEnterAndLeaveFrameTrapsState(JSContext* cx, bool enabled);
// about:memory reporting: // about:memory reporting:
void addSizeOfMisc(MallocSizeOf mallocSizeOf, void addSizeOfMisc(MallocSizeOf mallocSizeOf,

Просмотреть файл

@ -50,7 +50,7 @@ DebugFrame::observeFrame(JSContext* cx)
if (observing_) if (observing_)
return; return;
// TODO make sure wasm::Code onLeaveFrame traps are on instance()->code().adjustEnterAndLeaveFrameTrapsState(cx, /* enabled = */ true);
observing_ = true; observing_ = true;
} }
@ -60,6 +60,6 @@ DebugFrame::leaveFrame(JSContext* cx)
if (!observing_) if (!observing_)
return; return;
// TODO make sure wasm::Code onLeaveFrame traps are off instance()->code().adjustEnterAndLeaveFrameTrapsState(cx, /* enabled = */ false);
observing_ = false; observing_ = false;
} }

Просмотреть файл

@ -151,6 +151,7 @@ FrameIterator::settle()
case CodeRange::ImportJitExit: case CodeRange::ImportJitExit:
case CodeRange::ImportInterpExit: case CodeRange::ImportInterpExit:
case CodeRange::TrapExit: case CodeRange::TrapExit:
case CodeRange::DebugTrap:
case CodeRange::Inline: case CodeRange::Inline:
case CodeRange::FarJumpIsland: case CodeRange::FarJumpIsland:
MOZ_CRASH("Should not encounter an exit during iteration"); MOZ_CRASH("Should not encounter an exit during iteration");
@ -240,6 +241,14 @@ FrameIterator::debugFrame() const
return static_cast<DebugFrame*>(buf); return static_cast<DebugFrame*>(buf);
} }
const CallSite*
FrameIterator::debugTrapCallsite() const
{
MOZ_ASSERT(!done() && debugEnabled());
MOZ_ASSERT(callsite_->kind() == CallSite::EnterFrame || callsite_->kind() == CallSite::LeaveFrame);
return callsite_;
}
/*****************************************************************************/ /*****************************************************************************/
// Prologue/epilogue code generation // Prologue/epilogue code generation
@ -593,6 +602,7 @@ ProfilingFrameIterator::initFromFP()
case CodeRange::ImportJitExit: case CodeRange::ImportJitExit:
case CodeRange::ImportInterpExit: case CodeRange::ImportInterpExit:
case CodeRange::TrapExit: case CodeRange::TrapExit:
case CodeRange::DebugTrap:
case CodeRange::Inline: case CodeRange::Inline:
case CodeRange::FarJumpIsland: case CodeRange::FarJumpIsland:
MOZ_CRASH("Unexpected CodeRange kind"); MOZ_CRASH("Unexpected CodeRange kind");
@ -721,6 +731,7 @@ ProfilingFrameIterator::ProfilingFrameIterator(const WasmActivation& activation,
callerFP_ = nullptr; callerFP_ = nullptr;
break; break;
} }
case CodeRange::DebugTrap:
case CodeRange::Inline: { case CodeRange::Inline: {
// The throw stub clears WasmActivation::fp on it's way out. // The throw stub clears WasmActivation::fp on it's way out.
if (!fp) { if (!fp) {
@ -777,6 +788,7 @@ ProfilingFrameIterator::operator++()
case CodeRange::ImportJitExit: case CodeRange::ImportJitExit:
case CodeRange::ImportInterpExit: case CodeRange::ImportInterpExit:
case CodeRange::TrapExit: case CodeRange::TrapExit:
case CodeRange::DebugTrap:
case CodeRange::Inline: case CodeRange::Inline:
case CodeRange::FarJumpIsland: case CodeRange::FarJumpIsland:
stackAddress_ = callerFP_; stackAddress_ = callerFP_;
@ -803,6 +815,7 @@ ProfilingFrameIterator::label() const
const char* importInterpDescription = "slow FFI trampoline (in asm.js)"; const char* importInterpDescription = "slow FFI trampoline (in asm.js)";
const char* nativeDescription = "native call (in asm.js)"; const char* nativeDescription = "native call (in asm.js)";
const char* trapDescription = "trap handling (in asm.js)"; const char* trapDescription = "trap handling (in asm.js)";
const char* debugTrapDescription = "debug trap handling (in asm.js)";
switch (exitReason_) { switch (exitReason_) {
case ExitReason::None: case ExitReason::None:
@ -815,6 +828,8 @@ ProfilingFrameIterator::label() const
return nativeDescription; return nativeDescription;
case ExitReason::Trap: case ExitReason::Trap:
return trapDescription; return trapDescription;
case ExitReason::DebugTrap:
return debugTrapDescription;
} }
switch (codeRange_->kind()) { switch (codeRange_->kind()) {
@ -823,6 +838,7 @@ ProfilingFrameIterator::label() const
case CodeRange::ImportJitExit: return importJitDescription; case CodeRange::ImportJitExit: return importJitDescription;
case CodeRange::ImportInterpExit: return importInterpDescription; case CodeRange::ImportInterpExit: return importInterpDescription;
case CodeRange::TrapExit: return trapDescription; case CodeRange::TrapExit: return trapDescription;
case CodeRange::DebugTrap: return debugTrapDescription;
case CodeRange::Inline: return "inline stub (in asm.js)"; case CodeRange::Inline: return "inline stub (in asm.js)";
case CodeRange::FarJumpIsland: return "interstitial (in asm.js)"; case CodeRange::FarJumpIsland: return "interstitial (in asm.js)";
} }

Просмотреть файл

@ -75,6 +75,7 @@ class FrameIterator
Instance* instance() const; Instance* instance() const;
bool debugEnabled() const; bool debugEnabled() const;
DebugFrame* debugFrame() const; DebugFrame* debugFrame() const;
const CallSite* debugTrapCallsite() const;
}; };
// An ExitReason describes the possible reasons for leaving compiled wasm code // An ExitReason describes the possible reasons for leaving compiled wasm code
@ -85,7 +86,8 @@ enum class ExitReason : uint32_t
ImportJit, // fast-path call directly into JIT code ImportJit, // fast-path call directly into JIT code
ImportInterp, // slow-path call into C++ Invoke() ImportInterp, // slow-path call into C++ Invoke()
Native, // call to native C++ code (e.g., Math.sin, ToInt32(), interrupt) Native, // call to native C++ code (e.g., Math.sin, ToInt32(), interrupt)
Trap // call to trap handler for the trap in WasmActivation::trap Trap, // call to trap handler for the trap in WasmActivation::trap
DebugTrap // call to debug trap handler
}; };
// Iterates over the frames of a single WasmActivation, given an // Iterates over the frames of a single WasmActivation, given an

Просмотреть файл

@ -378,6 +378,28 @@ ModuleGenerator::patchCallSites(TrapExitOffsetArray* maybeTrapExits)
masm_.patchCall(callerOffset, *existingTrapFarJumps[cs.trap()]); masm_.patchCall(callerOffset, *existingTrapFarJumps[cs.trap()]);
break; break;
} }
case CallSiteDesc::EnterFrame:
case CallSiteDesc::LeaveFrame: {
Uint32Vector& jumps = metadata_->debugTrapFarJumpOffsets;
if (jumps.empty() ||
uint32_t(abs(int32_t(jumps.back()) - int32_t(callerOffset))) >= JumpRange())
{
Offsets offsets;
offsets.begin = masm_.currentOffset();
uint32_t jumpOffset = masm_.farJumpWithPatch().offset();
offsets.end = masm_.currentOffset();
if (masm_.oom())
return false;
if (!metadata_->codeRanges.emplaceBack(CodeRange::FarJumpIsland, offsets))
return false;
if (!debugTrapFarJumps_.emplaceBack(jumpOffset))
return false;
if (!jumps.emplaceBack(offsets.begin))
return false;
}
break;
}
} }
} }
@ -385,7 +407,7 @@ ModuleGenerator::patchCallSites(TrapExitOffsetArray* maybeTrapExits)
} }
bool bool
ModuleGenerator::patchFarJumps(const TrapExitOffsetArray& trapExits) ModuleGenerator::patchFarJumps(const TrapExitOffsetArray& trapExits, const Offsets& debugTrapStub)
{ {
for (CallThunk& callThunk : metadata_->callThunks) { for (CallThunk& callThunk : metadata_->callThunks) {
uint32_t funcIndex = callThunk.u.funcIndex; uint32_t funcIndex = callThunk.u.funcIndex;
@ -397,6 +419,9 @@ ModuleGenerator::patchFarJumps(const TrapExitOffsetArray& trapExits)
for (const TrapFarJump& farJump : masm_.trapFarJumps()) for (const TrapFarJump& farJump : masm_.trapFarJumps())
masm_.patchFarJump(farJump.jump, trapExits[farJump.trap].begin); masm_.patchFarJump(farJump.jump, trapExits[farJump.trap].begin);
for (uint32_t debugTrapFarJump : debugTrapFarJumps_)
masm_.patchFarJump(CodeOffset(debugTrapFarJump), debugTrapStub.begin);
return true; return true;
} }
@ -512,6 +537,7 @@ ModuleGenerator::finishCodegen()
Offsets unalignedAccessExit; Offsets unalignedAccessExit;
Offsets interruptExit; Offsets interruptExit;
Offsets throwStub; Offsets throwStub;
Offsets debugTrapStub;
{ {
TempAllocator alloc(&lifo_); TempAllocator alloc(&lifo_);
@ -539,6 +565,7 @@ ModuleGenerator::finishCodegen()
unalignedAccessExit = GenerateUnalignedExit(masm, &throwLabel); unalignedAccessExit = GenerateUnalignedExit(masm, &throwLabel);
interruptExit = GenerateInterruptExit(masm, &throwLabel); interruptExit = GenerateInterruptExit(masm, &throwLabel);
throwStub = GenerateThrowStub(masm, &throwLabel); throwStub = GenerateThrowStub(masm, &throwLabel);
debugTrapStub = GenerateDebugTrapStub(masm, &throwLabel);
if (masm.oom() || !masm_.asmMergeWith(masm)) if (masm.oom() || !masm_.asmMergeWith(masm))
return false; return false;
@ -588,6 +615,10 @@ ModuleGenerator::finishCodegen()
if (!metadata_->codeRanges.emplaceBack(CodeRange::Inline, throwStub)) if (!metadata_->codeRanges.emplaceBack(CodeRange::Inline, throwStub))
return false; return false;
debugTrapStub.offsetBy(offsetInWhole);
if (!metadata_->codeRanges.emplaceBack(CodeRange::DebugTrap, debugTrapStub))
return false;
// Fill in LinkData with the offsets of these stubs. // Fill in LinkData with the offsets of these stubs.
linkData_.outOfBoundsOffset = outOfBoundsExit.begin; linkData_.outOfBoundsOffset = outOfBoundsExit.begin;
@ -600,7 +631,7 @@ ModuleGenerator::finishCodegen()
if (!patchCallSites(&trapExits)) if (!patchCallSites(&trapExits))
return false; return false;
if (!patchFarJumps(trapExits)) if (!patchFarJumps(trapExits, debugTrapStub))
return false; return false;
// Code-generation is complete! // Code-generation is complete!
@ -1151,6 +1182,7 @@ ModuleGenerator::finish(const ShareableBytes& bytecode)
metadata_->codeRanges.podResizeToFit(); metadata_->codeRanges.podResizeToFit();
metadata_->callSites.podResizeToFit(); metadata_->callSites.podResizeToFit();
metadata_->callThunks.podResizeToFit(); metadata_->callThunks.podResizeToFit();
metadata_->debugTrapFarJumpOffsets.podResizeToFit();
// For asm.js, the tables vector is over-allocated (to avoid resize during // For asm.js, the tables vector is over-allocated (to avoid resize during
// parallel copilation). Shrink it back down to fit. // parallel copilation). Shrink it back down to fit.
@ -1168,6 +1200,15 @@ ModuleGenerator::finish(const ShareableBytes& bytecode)
} }
#endif #endif
// Assert debugTrapFarJumpOffsets are sorted.
#ifdef DEBUG
uint32_t lastOffset = 0;
for (uint32_t debugTrapFarJumpOffset : metadata_->debugTrapFarJumpOffsets) {
MOZ_ASSERT(debugTrapFarJumpOffset >= lastOffset);
lastOffset = debugTrapFarJumpOffset;
}
#endif
if (!finishLinkData(code)) if (!finishLinkData(code))
return nullptr; return nullptr;

Просмотреть файл

@ -234,6 +234,7 @@ class MOZ_STACK_CLASS ModuleGenerator
Uint32Set exportedFuncs_; Uint32Set exportedFuncs_;
uint32_t lastPatchedCallsite_; uint32_t lastPatchedCallsite_;
uint32_t startOfUnpatchedCallsites_; uint32_t startOfUnpatchedCallsites_;
Uint32Vector debugTrapFarJumps_;
// Parallel compilation // Parallel compilation
bool parallel_; bool parallel_;
@ -254,7 +255,7 @@ class MOZ_STACK_CLASS ModuleGenerator
const CodeRange& funcCodeRange(uint32_t funcIndex) const; const CodeRange& funcCodeRange(uint32_t funcIndex) const;
uint32_t numFuncImports() const; uint32_t numFuncImports() const;
MOZ_MUST_USE bool patchCallSites(TrapExitOffsetArray* maybeTrapExits = nullptr); MOZ_MUST_USE bool patchCallSites(TrapExitOffsetArray* maybeTrapExits = nullptr);
MOZ_MUST_USE bool patchFarJumps(const TrapExitOffsetArray& trapExits); MOZ_MUST_USE bool patchFarJumps(const TrapExitOffsetArray& trapExits, const Offsets& debugTrapStub);
MOZ_MUST_USE bool finishTask(CompileTask* task); MOZ_MUST_USE bool finishTask(CompileTask* task);
MOZ_MUST_USE bool finishOutstandingTask(); MOZ_MUST_USE bool finishOutstandingTask();
MOZ_MUST_USE bool finishFuncExports(); MOZ_MUST_USE bool finishFuncExports();

Просмотреть файл

@ -329,7 +329,8 @@ Instance::Instance(JSContext* cx,
object_(object), object_(object),
code_(Move(code)), code_(Move(code)),
memory_(memory), memory_(memory),
tables_(Move(tables)) tables_(Move(tables)),
enterFrameTrapsEnabled_(false)
{ {
MOZ_ASSERT(funcImports.length() == metadata().funcImports.length()); MOZ_ASSERT(funcImports.length() == metadata().funcImports.length());
MOZ_ASSERT(tables_.length() == metadata().tables.length()); MOZ_ASSERT(tables_.length() == metadata().tables.length());
@ -832,6 +833,16 @@ Instance::ensureProfilingState(JSContext* cx, bool newProfilingEnabled)
return true; return true;
} }
void
Instance::ensureEnterFrameTrapsState(JSContext* cx, bool enabled)
{
if (enterFrameTrapsEnabled_ == enabled)
return;
code_->adjustEnterAndLeaveFrameTrapsState(cx, enabled);
enterFrameTrapsEnabled_ = enabled;
}
void void
Instance::addSizeOfMisc(MallocSizeOf mallocSizeOf, Instance::addSizeOfMisc(MallocSizeOf mallocSizeOf,
Metadata::SeenSet* seenMetadata, Metadata::SeenSet* seenMetadata,

Просмотреть файл

@ -41,6 +41,7 @@ class Instance
GCPtrWasmMemoryObject memory_; GCPtrWasmMemoryObject memory_;
SharedTableVector tables_; SharedTableVector tables_;
TlsData tlsData_; TlsData tlsData_;
bool enterFrameTrapsEnabled_;
// Internal helpers: // Internal helpers:
const void** addressOfSigId(const SigIdDesc& sigId) const; const void** addressOfSigId(const SigIdDesc& sigId) const;
@ -124,6 +125,11 @@ class Instance
MOZ_MUST_USE bool ensureProfilingState(JSContext* cx, bool enabled); MOZ_MUST_USE bool ensureProfilingState(JSContext* cx, bool enabled);
// Debug support:
bool debugEnabled() const { return code_->metadata().debugEnabled; }
bool enterFrameTrapsEnabled() const { return enterFrameTrapsEnabled_; }
void ensureEnterFrameTrapsState(JSContext* cx, bool enabled);
// about:memory reporting: // about:memory reporting:
void addSizeOfMisc(MallocSizeOf mallocSizeOf, void addSizeOfMisc(MallocSizeOf mallocSizeOf,

Просмотреть файл

@ -946,6 +946,10 @@ static const LiveRegisterSet AllRegsExceptSP(
GeneralRegisterSet(Registers::AllMask & ~(uint32_t(1) << Registers::StackPointer)), GeneralRegisterSet(Registers::AllMask & ~(uint32_t(1) << Registers::StackPointer)),
FloatRegisterSet(FloatRegisters::AllMask)); FloatRegisterSet(FloatRegisters::AllMask));
static const LiveRegisterSet AllAllocatableRegs = LiveRegisterSet(
GeneralRegisterSet(Registers::AllocatableMask),
FloatRegisterSet(FloatRegisters::AllMask));
// The async interrupt-callback exit is called from arbitrarily-interrupted wasm // The async interrupt-callback exit is called from arbitrarily-interrupted wasm
// code. That means we must first save *all* registers and restore *all* // code. That means we must first save *all* registers and restore *all*
// registers (except the stack pointer) when we resume. The address to resume to // registers (except the stack pointer) when we resume. The address to resume to
@ -1127,6 +1131,11 @@ wasm::GenerateThrowStub(MacroAssembler& masm, Label* throwLabel)
Offsets offsets; Offsets offsets;
offsets.begin = masm.currentOffset(); offsets.begin = masm.currentOffset();
masm.andToStackPtr(Imm32(~(ABIStackAlignment - 1)));
if (ShadowStackSpace)
masm.subFromStackPtr(Imm32(ShadowStackSpace));
masm.call(SymbolicAddress::HandleDebugThrow);
// We are about to pop all frames in this WasmActivation. Set fp to null to // We are about to pop all frames in this WasmActivation. Set fp to null to
// maintain the invariant that fp is either null or pointing to a valid // maintain the invariant that fp is either null or pointing to a valid
// frame. // frame.
@ -1146,3 +1155,50 @@ wasm::GenerateThrowStub(MacroAssembler& masm, Label* throwLabel)
offsets.end = masm.currentOffset(); offsets.end = masm.currentOffset();
return offsets; return offsets;
} }
// Generate a stub that handle toggable enter/leave frame traps or breakpoints.
// The trap records frame pointer (via GenerateExitPrologue) and saves most of
// registers to not affect the code generated by WasmBaselineCompile.
Offsets
wasm::GenerateDebugTrapStub(MacroAssembler& masm, Label* throwLabel)
{
masm.haltingAlign(CodeAlignment);
masm.setFramePushed(0);
ProfilingOffsets offsets;
GenerateExitPrologue(masm, 0, ExitReason::DebugTrap, &offsets);
// Save all registers used between baseline compiler operations.
masm.PushRegsInMask(AllAllocatableRegs);
uint32_t framePushed = masm.framePushed();
// This method might be called with unaligned stack -- aligning and
// saving old stack pointer at the top.
Register scratch = ABINonArgReturnReg0;
masm.moveStackPtrTo(scratch);
masm.subFromStackPtr(Imm32(sizeof(intptr_t)));
masm.andToStackPtr(Imm32(~(ABIStackAlignment - 1)));
masm.storePtr(scratch, Address(masm.getStackPointer(), 0));
if (ShadowStackSpace)
masm.subFromStackPtr(Imm32(ShadowStackSpace));
masm.assertStackAlignment(ABIStackAlignment);
masm.call(SymbolicAddress::HandleDebugTrap);
masm.branchIfFalseBool(ReturnReg, throwLabel);
if (ShadowStackSpace)
masm.addToStackPtr(Imm32(ShadowStackSpace));
masm.Pop(scratch);
masm.moveToStackPtr(scratch);
masm.setFramePushed(framePushed);
masm.PopRegsInMask(AllAllocatableRegs);
GenerateExitEpilogue(masm, 0, ExitReason::DebugTrap, &offsets);
offsets.end = masm.currentOffset();
return offsets;
}

Просмотреть файл

@ -58,6 +58,10 @@ GenerateInterruptExit(jit::MacroAssembler& masm, jit::Label* throwLabel);
extern Offsets extern Offsets
GenerateThrowStub(jit::MacroAssembler& masm, jit::Label* throwLabel); GenerateThrowStub(jit::MacroAssembler& masm, jit::Label* throwLabel);
extern Offsets
GenerateDebugTrapStub(jit::MacroAssembler& masm, jit::Label* throwLabel);
} // namespace wasm } // namespace wasm
} // namespace js } // namespace js

Просмотреть файл

@ -98,6 +98,52 @@ WasmHandleExecutionInterrupt()
return success; return success;
} }
static bool
WasmHandleDebugTrap()
{
WasmActivation* activation = JSRuntime::innermostWasmActivation();
JSContext* cx = activation->cx();
FrameIterator iter(*activation);
MOZ_ASSERT(iter.debugEnabled());
const CallSite* site = iter.debugTrapCallsite();
MOZ_ASSERT(site);
if (site->kind() == CallSite::EnterFrame) {
if (!iter.instance()->enterFrameTrapsEnabled())
return true;
DebugFrame* frame = iter.debugFrame();
frame->setIsDebuggee();
frame->observeFrame(cx);
// TODO call onEnterFrame
return true;
}
if (site->kind() == CallSite::LeaveFrame) {
DebugFrame* frame = iter.debugFrame();
// TODO call onLeaveFrame
frame->leaveFrame(cx);
return true;
}
// TODO baseline debug traps
MOZ_CRASH();
return true;
}
static void
WasmHandleDebugThrow()
{
WasmActivation* activation = JSRuntime::innermostWasmActivation();
JSContext* cx = activation->cx();
for (FrameIterator iter(*activation); !iter.done(); ++iter) {
if (!iter.debugEnabled())
continue;
DebugFrame* frame = iter.debugFrame();
// TODO call onExceptionUnwind and onLeaveFrame
frame->leaveFrame(cx);
}
}
static void static void
WasmReportTrap(int32_t trapIndex) WasmReportTrap(int32_t trapIndex)
{ {
@ -277,6 +323,10 @@ wasm::AddressOf(SymbolicAddress imm, ExclusiveContext* cx)
return FuncCast(WasmReportOverRecursed, Args_General0); return FuncCast(WasmReportOverRecursed, Args_General0);
case SymbolicAddress::HandleExecutionInterrupt: case SymbolicAddress::HandleExecutionInterrupt:
return FuncCast(WasmHandleExecutionInterrupt, Args_General0); return FuncCast(WasmHandleExecutionInterrupt, Args_General0);
case SymbolicAddress::HandleDebugTrap:
return FuncCast(WasmHandleDebugTrap, Args_General0);
case SymbolicAddress::HandleDebugThrow:
return FuncCast(WasmHandleDebugThrow, Args_General0);
case SymbolicAddress::ReportTrap: case SymbolicAddress::ReportTrap:
return FuncCast(WasmReportTrap, Args_General1); return FuncCast(WasmReportTrap, Args_General1);
case SymbolicAddress::ReportOutOfBounds: case SymbolicAddress::ReportOutOfBounds:

Просмотреть файл

@ -890,14 +890,16 @@ struct TrapOffset
class CallSiteDesc class CallSiteDesc
{ {
uint32_t lineOrBytecode_ : 30; uint32_t lineOrBytecode_ : 29;
uint32_t kind_ : 2; uint32_t kind_ : 3;
public: public:
enum Kind { enum Kind {
Func, // pc-relative call to a specific function Func, // pc-relative call to a specific function
Dynamic, // dynamic callee called via register Dynamic, // dynamic callee called via register
Symbolic, // call to a single symbolic callee Symbolic, // call to a single symbolic callee
TrapExit // call to a trap exit TrapExit, // call to a trap exit
EnterFrame, // call to a enter frame handler
LeaveFrame // call to a leave frame handler
}; };
CallSiteDesc() {} CallSiteDesc() {}
explicit CallSiteDesc(Kind kind) explicit CallSiteDesc(Kind kind)
@ -1014,6 +1016,8 @@ enum class SymbolicAddress
InterruptUint32, InterruptUint32,
ReportOverRecursed, ReportOverRecursed,
HandleExecutionInterrupt, HandleExecutionInterrupt,
HandleDebugTrap,
HandleDebugThrow,
ReportTrap, ReportTrap,
ReportOutOfBounds, ReportOutOfBounds,
ReportUnalignedAccess, ReportUnalignedAccess,