Bug 1360211: Merge WasmActivation into JitActivation and make wasm->jit calls faster; r=luke, r=jandem

This is a folded patch containing the patches in the bug named as:

- Handle the "untrusted FP" in wasm->jit exit;
- Handle failures in wasm->jit fast path;
- Profiling frame iteration;

MozReview-Commit-ID: 2hs8yyKGQ9v

--HG--
extra : rebase_source : e71f6bb5558a4c78c7ae4ce0b6b8cfa11d0b10f7
This commit is contained in:
Benjamin Bouvier 2017-09-27 21:16:29 +02:00
parent 5335ccdcde
commit 8f85f6c936
49 changed files with 836 additions and 586 deletions

View File

@ -46,9 +46,17 @@ struct ForEachTrackedOptimizationTypeInfoOp;
// contents to become out of date.
class MOZ_NON_PARAM JS_PUBLIC_API(ProfilingFrameIterator)
{
public:
enum class Kind : bool {
JSJit,
Wasm
};
private:
JSContext* cx_;
uint32_t sampleBufferGen_;
js::Activation* activation_;
Kind kind_;
static const unsigned StorageSpace = 8 * sizeof(void*);
alignas(void*) unsigned char storage_[StorageSpace];
@ -79,6 +87,7 @@ class MOZ_NON_PARAM JS_PUBLIC_API(ProfilingFrameIterator)
return *static_cast<const js::jit::JitProfilingFrameIterator*>(storage());
}
void settleFrames();
void settle();
bool hasSampleBufferGen() const {

View File

@ -40,7 +40,8 @@ SizeOfFramePrefix = {
'JitFrame_BaselineJS': 'JitFrameLayout',
'JitFrame_BaselineStub': 'BaselineStubFrameLayout',
'JitFrame_IonStub': 'JitStubFrameLayout',
'JitFrame_Entry': 'JitFrameLayout',
'JitFrame_CppToJSJit': 'JitFrameLayout',
'JitFrame_WasmToJSJit': 'JitFrameLayout',
'JitFrame_Rectifier': 'RectifierFrameLayout',
'JitFrame_IonAccessorIC': 'IonAccessorICFrameLayout',
'JitFrame_IonICCall': 'IonICCallFrameLayout',
@ -367,7 +368,7 @@ class UnwinderState(object):
self.typecache.FRAME_HEADER_SIZE_MASK)
header_size = header_size * self.typecache.void_starstar.sizeof
frame_type = long(value & self.typecache.FRAMETYPE_MASK)
if frame_type == self.typecache.JitFrame_Entry:
if frame_type == self.typecache.JitFrame_CppToJSJit:
# Trampoline-x64.cpp pushes a JitFrameLayout object, but
# the stack pointer is actually adjusted as if a
# CommonFrameLayout object was pushed.
@ -451,7 +452,7 @@ class UnwinderState(object):
def unwind_entry_frame(self, pc, pending_frame):
sp = self.next_sp
# Notify the frame filter.
self.add_frame(sp, name = 'JitFrame_Entry')
self.add_frame(sp, name = 'JitFrame_CppToJSJit')
# Make an unwind_info for the per-architecture code to fill in.
frame_id = SpiderMonkeyFrameId(sp, pc)
unwind_info = pending_frame.create_unwind_info(frame_id)
@ -472,7 +473,7 @@ class UnwinderState(object):
return None
if self.next_sp is not None:
if self.next_type == self.typecache.JitFrame_Entry:
if self.next_type == self.typecache.JitFrame_CppToJSJit:
return self.unwind_entry_frame(pc, pending_frame)
return self.unwind_ordinary(pc, pending_frame)
# Maybe we've found an exit frame. FIXME I currently don't

View File

@ -27,7 +27,7 @@ def do_unwinder_test():
first = False
elif frame.function() == "<<JitFrame_Exit>>":
found_exit = True
elif frame.function() == "<<JitFrame_Entry>>":
elif frame.function() == "<<JitFrame_CppToJSJit>>":
found_entry = True
elif frame.function() == "main":
found_main = True

View File

@ -102,7 +102,12 @@ static const uint32_t BAILOUT_RETURN_OVERRECURSED = 2;
// This address is a magic number made to cause crashes while indicating that we
// are making an attempt to mark the stack during a bailout.
static uint8_t* const FAKE_EXITFP_FOR_BAILOUT = reinterpret_cast<uint8_t*>(0xba1);
static const uint32_t FAKE_EXITFP_FOR_BAILOUT_ADDR = 0xba2;
static uint8_t* const FAKE_EXITFP_FOR_BAILOUT =
reinterpret_cast<uint8_t*>(FAKE_EXITFP_FOR_BAILOUT_ADDR);
static_assert(!(FAKE_EXITFP_FOR_BAILOUT_ADDR & JitActivation::ExitFpWasmBit),
"FAKE_EXITFP_FOR_BAILOUT could be mistaken as a low-bit tagged wasm exit fp");
// BailoutStack is an architecture specific pointer to the stack, given by the
// bailout handler.

View File

@ -362,7 +362,7 @@ struct BaselineStackBuilder
// in the baseline frame is meaningless, since Ion saves all registers
// before calling other ion frames, and the entry frame saves all
// registers too.
if (type == JitFrame_IonJS || type == JitFrame_Entry || type == JitFrame_IonICCall)
if (JSJitFrameIter::isEntry(type) || type == JitFrame_IonJS || type == JitFrame_IonICCall)
return nullptr;
// BaselineStub - Baseline calling into Ion.
@ -1515,14 +1515,14 @@ jit::BailoutIonToBaseline(JSContext* cx, JitActivation* activation,
// The caller of the top frame must be one of the following:
// IonJS - Ion calling into Ion.
// BaselineStub - Baseline calling into Ion.
// Entry - Interpreter or other calling into Ion.
// Entry / WasmToJSJit - Interpreter or other (wasm) calling into Ion.
// Rectifier - Arguments rectifier calling into Ion.
MOZ_ASSERT(iter.isBailoutJS());
#if defined(DEBUG) || defined(JS_JITSPEW)
FrameType prevFrameType = iter.prevType();
MOZ_ASSERT(prevFrameType == JitFrame_IonJS ||
MOZ_ASSERT(JSJitFrameIter::isEntry(prevFrameType) ||
prevFrameType == JitFrame_IonJS ||
prevFrameType == JitFrame_BaselineStub ||
prevFrameType == JitFrame_Entry ||
prevFrameType == JitFrame_Rectifier ||
prevFrameType == JitFrame_IonICCall);
#endif

View File

@ -93,7 +93,7 @@ class DebugModeOSRVolatileJitFrameIter : public JitFrameIter
public:
explicit DebugModeOSRVolatileJitFrameIter(JSContext* cx)
: JitFrameIter(cx->activation())
: JitFrameIter(cx->activation()->asJit())
{
stack = &cx->liveVolatileJitFrameIter_.ref();
prev = *stack;

View File

@ -3005,9 +3005,12 @@ InvalidateActivation(FreeOp* fop, const JitActivationIterator& activations, bool
case JitFrame_IonICCall:
JitSpew(JitSpew_IonInvalidate, "#%zu ion IC call frame @ %p", frameno, frame.fp());
break;
case JitFrame_Entry:
case JitFrame_CppToJSJit:
JitSpew(JitSpew_IonInvalidate, "#%zu entry frame @ %p", frameno, frame.fp());
break;
case JitFrame_WasmToJSJit:
JitSpew(JitSpew_IonInvalidate, "#%zu wasm frames @ %p", frameno, frame.fp());
break;
}
#endif // JS_JITSPEW

View File

@ -153,15 +153,15 @@ JSJitFrameIter::prevFp() const
void
JSJitFrameIter::operator++()
{
MOZ_ASSERT(type_ != JitFrame_Entry);
MOZ_ASSERT(!isEntry());
frameSize_ = prevFrameLocalSize();
cachedSafepointIndex_ = nullptr;
// If the next frame is the entry frame, just exit. Don't update current_,
// since the entry and first frames overlap.
if (current()->prevType() == JitFrame_Entry) {
type_ = JitFrame_Entry;
if (isEntry(current()->prevType())) {
type_ = current()->prevType();
return;
}
@ -337,7 +337,7 @@ void
JSJitFrameIter::dump() const
{
switch (type_) {
case JitFrame_Entry:
case JitFrame_CppToJSJit:
fprintf(stderr, " Entry frame\n");
fprintf(stderr, " Frame size: %u\n", unsigned(current()->prevFrameLocalSize()));
break;
@ -368,6 +368,10 @@ JSJitFrameIter::dump() const
fprintf(stderr, " Ion IC call\n");
fprintf(stderr, " Frame size: %u\n", unsigned(current()->prevFrameLocalSize()));
break;
case JitFrame_WasmToJSJit:
fprintf(stderr, " Fast wasm-to-JS entry frame\n");
fprintf(stderr, " Frame size: %u\n", unsigned(current()->prevFrameLocalSize()));
break;
case JitFrame_Exit:
fprintf(stderr, " Exit frame\n");
break;

View File

@ -36,7 +36,7 @@ enum FrameType
// The entry frame is the initial prologue block transitioning from the VM
// into the Ion world.
JitFrame_Entry,
JitFrame_CppToJSJit,
// A rectifier frame sits in between two JS frames, adapting argc != nargs
// mismatches in calls.
@ -55,6 +55,11 @@ enum FrameType
// frame is always the last frame in a JitActivation iff the bailout frame
// information is recorded on the JitActivation.
JitFrame_Bailout,
// A wasm to JS frame is constructed during fast calls from wasm to the JS
// jits, used as a marker to interleave JS jit and wasm frames. From the
// point of view of JS JITs, this is just another kind of entry frame.
JitFrame_WasmToJSJit,
};
enum ReadFrameArgsBehavior {
@ -173,9 +178,13 @@ class JSJitFrameIter
bool isBareExit() const;
template <typename T> bool isExitFrameLayout() const;
bool isEntry() const {
return type_ == JitFrame_Entry;
static bool isEntry(FrameType type) {
return type == JitFrame_CppToJSJit || type == JitFrame_WasmToJSJit;
}
bool isEntry() const {
return isEntry(type_);
}
bool isFunctionFrame() const;
bool isConstructing() const;
@ -206,10 +215,10 @@ class JSJitFrameIter
return frameSize_;
}
// Functions used to iterate on frames. When prevType is JitFrame_Entry,
// the current frame is the last frame.
// Functions used to iterate on frames. When prevType is an entry,
// the current frame is the last JS Jit frame.
bool done() const {
return type_ == JitFrame_Entry;
return isEntry();
}
void operator++();

View File

@ -6,7 +6,6 @@
#include "jit/JitFrames-inl.h"
#include "jsfun.h"
#include "jsobj.h"
#include "jsscript.h"
@ -33,6 +32,7 @@
#include "vm/Interpreter.h"
#include "vm/TraceLogging.h"
#include "vm/TypeInference.h"
#include "wasm/WasmBuiltins.h"
#include "jsscriptinlines.h"
#include "gc/Nursery-inl.h"
@ -574,6 +574,7 @@ struct AutoResetLastProfilerFrameOnReturnFromException
void* getLastProfilingFrame() {
switch (rfe->kind) {
case ResumeFromException::RESUME_ENTRY_FRAME:
case ResumeFromException::RESUME_WASM:
return nullptr;
// The following all return into baseline frames.
@ -593,6 +594,19 @@ struct AutoResetLastProfilerFrameOnReturnFromException
}
};
void
HandleExceptionWasm(JSContext* cx, wasm::WasmFrameIter* iter, ResumeFromException* rfe)
{
// Maintain the wasm invariant that we have wasm frames when unwinding.
JitActivation* act = cx->activation()->asJit();
act->setWasmExitFP((const wasm::Frame*) act->jsExitFP());
rfe->kind = ResumeFromException::RESUME_WASM;
rfe->framePointer = (uint8_t*) wasm::FailFP;
rfe->stackPointer = (uint8_t*) wasm::HandleThrow(cx, *iter);
MOZ_ASSERT(iter->done());
}
void
HandleException(ResumeFromException* rfe)
{
@ -626,9 +640,14 @@ HandleException(ResumeFromException* rfe)
// iterating, we need a variant here that is automatically updated should
// on-stack recompilation occur.
DebugModeOSRVolatileJitFrameIter iter(cx);
while (!iter.isJSJit() || !iter.asJSJit().isEntry()) {
while (!iter.isJSJit())
++iter;
while (!iter.done()) {
if (iter.isWasm()) {
HandleExceptionWasm(cx, &iter.asWasm(), rfe);
if (!iter.done())
++iter;
continue;
}
const JSJitFrameIter& frame = iter.asJSJit();
bool overrecursed = false;
@ -742,7 +761,9 @@ HandleException(ResumeFromException* rfe)
}
}
rfe->stackPointer = iter.asJSJit().fp();
// Wasm sets its own value of SP in HandleExceptionWasm.
if (iter.isJSJit())
rfe->stackPointer = iter.asJSJit().fp();
}
// Turns a JitFrameLayout into an ExitFrameLayout. Note that it has to be a
@ -1275,51 +1296,50 @@ TraceJitActivation(JSTracer* trc, JitActivation* activation)
activation->traceRematerializedFrames(trc);
activation->traceIonRecovery(trc);
for (JSJitFrameIter frames(activation); !frames.done(); ++frames) {
switch (frames.type()) {
case JitFrame_Exit:
TraceJitExitFrame(trc, frames);
break;
case JitFrame_BaselineJS:
frames.baselineFrame()->trace(trc, frames);
break;
case JitFrame_IonJS:
TraceIonJSFrame(trc, frames);
break;
case JitFrame_BaselineStub:
TraceBaselineStubFrame(trc, frames);
break;
case JitFrame_Bailout:
TraceBailoutFrame(trc, frames);
break;
case JitFrame_Rectifier:
TraceRectifierFrame(trc, frames);
break;
case JitFrame_IonICCall:
TraceIonICCallFrame(trc, frames);
break;
default:
MOZ_CRASH("unexpected frame type");
for (JitFrameIter frames(activation); !frames.done(); ++frames) {
if (frames.isJSJit()) {
const JSJitFrameIter& jitFrame = frames.asJSJit();
switch (jitFrame.type()) {
case JitFrame_Exit:
TraceJitExitFrame(trc, jitFrame);
break;
case JitFrame_BaselineJS:
jitFrame.baselineFrame()->trace(trc, jitFrame);
break;
case JitFrame_IonJS:
TraceIonJSFrame(trc, jitFrame);
break;
case JitFrame_BaselineStub:
TraceBaselineStubFrame(trc, jitFrame);
break;
case JitFrame_Bailout:
TraceBailoutFrame(trc, jitFrame);
break;
case JitFrame_Rectifier:
TraceRectifierFrame(trc, jitFrame);
break;
case JitFrame_IonICCall:
TraceIonICCallFrame(trc, jitFrame);
break;
case JitFrame_WasmToJSJit:
// Ignore: this is a marked used to let the JitFrameIter the
// frame above is a wasm frame, handled in the next iteration.
break;
default:
MOZ_CRASH("unexpected frame type");
}
} else {
MOZ_ASSERT(frames.isWasm());
frames.asWasm().instance()->trace(trc);
}
}
}
static void
TraceWasmActivation(JSTracer* trc, WasmActivation* act)
{
for (wasm::WasmFrameIter iter(act); !iter.done(); ++iter)
iter.instance()->trace(trc);
}
void
TraceJitActivations(JSContext* cx, const CooperatingContext& target, JSTracer* trc)
{
for (ActivationIterator activations(cx, target); !activations.done(); ++activations) {
if (activations->isJit())
TraceJitActivation(trc, activations->asJit());
if (activations->isWasm())
TraceWasmActivation(trc, activations->asWasm());
}
for (JitActivationIterator activations(cx, target); !activations.done(); ++activations)
TraceJitActivation(trc, activations->asJit());
}
void
@ -2395,7 +2415,7 @@ JitProfilingFrameIterator::JitProfilingFrameIterator(
// If no profilingActivation is live, initialize directly to
// end-of-iteration state.
if (!cx->profilingActivation()) {
type_ = JitFrame_Entry;
type_ = JitFrame_CppToJSJit;
fp_ = nullptr;
returnAddressToFp_ = nullptr;
return;
@ -2409,7 +2429,7 @@ JitProfilingFrameIterator::JitProfilingFrameIterator(
// it's a trivially empty activation, and initialize directly
// to end-of-iteration state.
if (!act->lastProfilingFrame()) {
type_ = JitFrame_Entry;
type_ = JitFrame_CppToJSJit;
fp_ = nullptr;
returnAddressToFp_ = nullptr;
return;
@ -2504,7 +2524,7 @@ JitProfilingFrameIterator::tryInitWithTable(JitcodeGlobalTable* table, void* pc,
// Treat dummy lookups as an empty frame sequence.
if (entry->isDummy()) {
type_ = JitFrame_Entry;
type_ = JitFrame_CppToJSJit;
fp_ = nullptr;
returnAddressToFp_ = nullptr;
return true;
@ -2592,6 +2612,8 @@ JitProfilingFrameIterator::moveToNextFrame(CommonFrameLayout* frame)
* |
* ^--- Baseline Stub <---- Baseline
* |
* ^--- WasmToJSJit <---- (other wasm frames, not handled by this iterator)
* |
* ^--- Argument Rectifier
* | ^
* | |
@ -2673,12 +2695,21 @@ JitProfilingFrameIterator::moveToNextFrame(CommonFrameLayout* frame)
return;
}
if (prevType == JitFrame_Entry) {
// No previous frame, set to null to indicate that OnlyJSJitFrameIter is
// done().
if (prevType == JitFrame_WasmToJSJit) {
// No previous js jit frame, this is a transition frame, used to pass
// a wasm iterator the correct value of FP.
returnAddressToFp_ = nullptr;
fp_ = GetPreviousRawFrame<uint8_t*>(frame);
type_ = JitFrame_WasmToJSJit;
return;
}
if (prevType == JitFrame_CppToJSJit) {
// No previous frame, set to null to indicate that
// JitProfilingFrameIterator is done().
returnAddressToFp_ = nullptr;
fp_ = nullptr;
type_ = JitFrame_Entry;
type_ = JitFrame_CppToJSJit;
return;
}
@ -2710,71 +2741,78 @@ void
AssertJitStackInvariants(JSContext* cx)
{
for (JitActivationIterator activations(cx); !activations.done(); ++activations) {
JitFrameIter iter(activations.activation());
size_t prevFrameSize = 0;
size_t frameSize = 0;
bool isScriptedCallee = false;
for (; !iter.done(); ++iter) {
const JSJitFrameIter& frames = iter.asJSJit();
size_t calleeFp = reinterpret_cast<size_t>(frames.fp());
size_t callerFp = reinterpret_cast<size_t>(frames.prevFp());
MOZ_ASSERT(callerFp >= calleeFp);
prevFrameSize = frameSize;
frameSize = callerFp - calleeFp;
JitFrameIter iter(activations->asJit());
if (iter.isJSJit()) {
JSJitFrameIter& frames = iter.asJSJit();
size_t prevFrameSize = 0;
size_t frameSize = 0;
bool isScriptedCallee = false;
for (; !frames.done(); ++frames) {
size_t calleeFp = reinterpret_cast<size_t>(frames.fp());
size_t callerFp = reinterpret_cast<size_t>(frames.prevFp());
MOZ_ASSERT(callerFp >= calleeFp);
prevFrameSize = frameSize;
frameSize = callerFp - calleeFp;
if (frames.prevType() == JitFrame_Rectifier) {
MOZ_RELEASE_ASSERT(frameSize % JitStackAlignment == 0,
"The rectifier frame should keep the alignment");
if (frames.prevType() == JitFrame_Rectifier) {
MOZ_RELEASE_ASSERT(frameSize % JitStackAlignment == 0,
"The rectifier frame should keep the alignment");
size_t expectedFrameSize = 0
size_t expectedFrameSize = 0
#if defined(JS_CODEGEN_X86)
+ sizeof(void*) /* frame pointer */
+ sizeof(void*) /* frame pointer */
#endif
+ sizeof(Value) * (frames.callee()->nargs() +
1 /* |this| argument */ +
frames.isConstructing() /* new.target */)
+ sizeof(JitFrameLayout);
MOZ_RELEASE_ASSERT(frameSize >= expectedFrameSize,
"The frame is large enough to hold all arguments");
MOZ_RELEASE_ASSERT(expectedFrameSize + JitStackAlignment > frameSize,
"The frame size is optimal");
}
if (frames.isExitFrame()) {
// For the moment, we do not keep the JitStackAlignment
// alignment for exit frames.
frameSize -= ExitFrameLayout::Size();
}
if (frames.isIonJS()) {
// Ideally, we should not have such requirement, but keep the
// alignment-delta as part of the Safepoint such that we can pad
// accordingly when making out-of-line calls. In the mean time,
// let us have check-points where we can garantee that
// everything can properly be aligned before adding complexity.
MOZ_RELEASE_ASSERT(frames.ionScript()->frameSize() % JitStackAlignment == 0,
"Ensure that if the Ion frame is aligned, then the spill base is also aligned");
if (isScriptedCallee) {
MOZ_RELEASE_ASSERT(prevFrameSize % JitStackAlignment == 0,
"The ion frame should keep the alignment");
+ sizeof(Value) * (frames.callee()->nargs() +
1 /* |this| argument */ +
frames.isConstructing() /* new.target */)
+ sizeof(JitFrameLayout);
MOZ_RELEASE_ASSERT(frameSize >= expectedFrameSize,
"The frame is large enough to hold all arguments");
MOZ_RELEASE_ASSERT(expectedFrameSize + JitStackAlignment > frameSize,
"The frame size is optimal");
}
if (frames.isExitFrame()) {
// For the moment, we do not keep the JitStackAlignment
// alignment for exit frames.
frameSize -= ExitFrameLayout::Size();
}
if (frames.isIonJS()) {
// Ideally, we should not have such requirement, but keep the
// alignment-delta as part of the Safepoint such that we can pad
// accordingly when making out-of-line calls. In the mean time,
// let us have check-points where we can garantee that
// everything can properly be aligned before adding complexity.
MOZ_RELEASE_ASSERT(frames.ionScript()->frameSize() % JitStackAlignment == 0,
"Ensure that if the Ion frame is aligned, then the spill base is also aligned");
if (isScriptedCallee) {
MOZ_RELEASE_ASSERT(prevFrameSize % JitStackAlignment == 0,
"The ion frame should keep the alignment");
}
}
// The stack is dynamically aligned by baseline stubs before calling
// any jitted code.
if (frames.prevType() == JitFrame_BaselineStub && isScriptedCallee) {
MOZ_RELEASE_ASSERT(calleeFp % JitStackAlignment == 0,
"The baseline stub restores the stack alignment");
}
isScriptedCallee = frames.isScripted() || frames.type() == JitFrame_Rectifier;
}
// The stack is dynamically aligned by baseline stubs before calling
// any jitted code.
if (frames.prevType() == JitFrame_BaselineStub && isScriptedCallee) {
MOZ_RELEASE_ASSERT(calleeFp % JitStackAlignment == 0,
"The baseline stub restores the stack alignment");
}
isScriptedCallee = frames.isScripted() || frames.type() == JitFrame_Rectifier;
MOZ_RELEASE_ASSERT(JSJitFrameIter::isEntry(frames.type()),
"The first frame of a Jit activation should be an entry frame");
MOZ_RELEASE_ASSERT(reinterpret_cast<size_t>(frames.fp()) % JitStackAlignment == 0,
"The entry frame should be properly aligned");
} else {
MOZ_ASSERT(iter.isWasm());
wasm::WasmFrameIter& frames = iter.asWasm();
while (!frames.done())
++frames;
}
MOZ_RELEASE_ASSERT(iter.asJSJit().type() == JitFrame_Entry,
"The first frame of a Jit activation should be an entry frame");
MOZ_RELEASE_ASSERT(reinterpret_cast<size_t>(iter.asJSJit().fp()) % JitStackAlignment == 0,
"The entry frame should be properly aligned");
}
}

View File

@ -269,6 +269,7 @@ struct ResumeFromException
static const uint32_t RESUME_FINALLY = 2;
static const uint32_t RESUME_FORCED_RETURN = 3;
static const uint32_t RESUME_BAILOUT = 4;
static const uint32_t RESUME_WASM = 5;
uint8_t* framePointer;
uint8_t* stackPointer;
@ -444,6 +445,14 @@ class RectifierFrameLayout : public JitFrameLayout
}
};
class WasmFrameLayout : public JitFrameLayout
{
public:
static inline size_t Size() {
return sizeof(WasmFrameLayout);
}
};
class IonICCallFrameLayout : public CommonFrameLayout
{
protected:

View File

@ -3118,10 +3118,11 @@ void
MacroAssembler::wasmAssertNonExitInvariants(Register activation)
{
#ifdef DEBUG
// WasmActivation.exitFP should be null when outside any exit frame.
// packedExitFP should not be tagged as wasm. Note this includes nullptr.
Label ok;
Address exitFP(activation, WasmActivation::offsetOfExitFP());
branchPtr(Assembler::Equal, exitFP, ImmWord(0), &ok);
Address packedExitFP(activation, JitActivation::offsetOfPackedExitFP());
branchTestPtr(Assembler::Zero, packedExitFP, Imm32(uintptr_t(JitActivation::ExitFpWasmBit)),
&ok);
breakpoint();
bind(&ok);
#endif

View File

@ -3605,6 +3605,7 @@ MacroAssemblerARMCompat::handleFailureWithHandlerTail(void* handler)
Label finally;
Label return_;
Label bailout;
Label wasm;
{
ScratchRegisterScope scratch(asMasm());
@ -3618,6 +3619,7 @@ MacroAssemblerARMCompat::handleFailureWithHandlerTail(void* handler)
asMasm().branch32(Assembler::Equal, r0, Imm32(ResumeFromException::RESUME_FORCED_RETURN),
&return_);
asMasm().branch32(Assembler::Equal, r0, Imm32(ResumeFromException::RESUME_BAILOUT), &bailout);
asMasm().branch32(Assembler::Equal, r0, Imm32(ResumeFromException::RESUME_WASM), &wasm);
breakpoint(); // Invalid kind.
@ -3697,6 +3699,17 @@ MacroAssemblerARMCompat::handleFailureWithHandlerTail(void* handler)
ma_ldr(Address(sp, offsetof(ResumeFromException, target)), r1, scratch);
}
jump(r1);
// If we are throwing and the innermost frame was a wasm frame, reset SP and
// FP; SP is pointing to the unwound return address to the wasm entry, so
// we can just ret().
bind(&wasm);
{
ScratchRegisterScope scratch(asMasm());
ma_ldr(Address(sp, offsetof(ResumeFromException, framePointer)), r11, scratch);
ma_ldr(Address(sp, offsetof(ResumeFromException, stackPointer)), sp, scratch);
}
as_dtr(IsLoad, 32, PostIndex, pc, DTRAddr(sp, DtrOffImm(4)));
}
Assembler::Condition

View File

@ -1547,14 +1547,14 @@ Simulator::exclusiveMonitorClear()
}
void
Simulator::startInterrupt(WasmActivation* activation)
Simulator::startWasmInterrupt(JitActivation* activation)
{
JS::ProfilingFrameIterator::RegisterState state;
state.pc = (void*) get_pc();
state.fp = (void*) get_register(fp);
state.sp = (void*) get_register(sp);
state.lr = (void*) get_register(lr);
activation->startInterrupt(state);
activation->startWasmInterrupt(state);
}
// The signal handler only redirects the PC to the interrupt stub when the PC is
@ -1576,7 +1576,7 @@ Simulator::handleWasmInterrupt()
if (!fp)
return;
startInterrupt(wasm::ActivationIfInnermost(cx_));
startWasmInterrupt(cx_->activation()->asJit());
set_pc(int32_t(cs->interruptCode()));
}
@ -1589,9 +1589,9 @@ Simulator::handleWasmInterrupt()
bool
Simulator::handleWasmFault(int32_t addr, unsigned numBytes)
{
WasmActivation* act = wasm::ActivationIfInnermost(cx_);
if (!act)
if (!cx_->activation() || !cx_->activation()->isJit())
return false;
JitActivation* act = cx_->activation()->asJit();
void* pc = reinterpret_cast<void*>(get_pc());
uint8_t* fp = reinterpret_cast<uint8_t*>(get_register(r11));
@ -1609,7 +1609,7 @@ Simulator::handleWasmFault(int32_t addr, unsigned numBytes)
const wasm::CodeSegment* segment;
const wasm::MemoryAccess* memoryAccess = instance->code().lookupMemoryAccess(pc, &segment);
if (!memoryAccess) {
startInterrupt(act);
startWasmInterrupt(act);
if (!instance->code().containsCodePC(pc, &segment))
MOZ_CRASH("Cannot map PC to trap handler");
set_pc(int32_t(segment->outOfBoundsCode()));

View File

@ -41,11 +41,9 @@
#include "wasm/WasmCode.h"
namespace js {
class WasmActivation;
namespace jit {
class JitActivation;
class Simulator;
class Redirection;
class CachePage;
@ -293,7 +291,7 @@ class Simulator
// Handle a wasm interrupt triggered by an async signal handler.
void handleWasmInterrupt();
void startInterrupt(WasmActivation* act);
void startWasmInterrupt(JitActivation* act);
// Handle any wasm faults, returning true if the fault was handled.
bool handleWasmFault(int32_t addr, unsigned numBytes);

View File

@ -205,7 +205,7 @@ JitRuntime::generateEnterJIT(JSContext* cx, EnterJitType type)
}
masm.ma_sub(r8, sp, r8);
masm.makeFrameDescriptor(r8, JitFrame_Entry, JitFrameLayout::Size());
masm.makeFrameDescriptor(r8, JitFrame_CppToJSJit, JitFrameLayout::Size());
masm.startDataTransferM(IsStore, sp, IB, NoWriteBack);
// [sp] = return address (written later)
@ -1205,7 +1205,10 @@ JitRuntime::generateProfilerExitFrameTailStub(JSContext* cx)
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineStub), &handle_BaselineStub);
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Rectifier), &handle_Rectifier);
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonICCall), &handle_IonICCall);
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Entry), &handle_Entry);
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_CppToJSJit), &handle_Entry);
// The WasmToJSJit is just another kind of entry.
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_WasmToJSJit), &handle_Entry);
masm.assumeUnreachable("Invalid caller frame type when exiting from Ion frame.");
@ -1424,9 +1427,11 @@ JitRuntime::generateProfilerExitFrameTailStub(JSContext* cx)
}
//
// JitFrame_Entry
// JitFrame_CppToJSJit / JitFrame_WasmJSToJit
//
// If at an entry frame, store null into both fields.
// A fast-path wasm->jit transition frame is an entry frame from the point
// of view of the JIT.
//
masm.bind(&handle_Entry);
{

View File

@ -163,7 +163,7 @@ JitRuntime::generateEnterJIT(JSContext* cx, EnterJitType type)
masm.subStackPtrFrom(r19);
// Push the frameDescriptor.
masm.makeFrameDescriptor(r19, JitFrame_Entry, JitFrameLayout::Size());
masm.makeFrameDescriptor(r19, JitFrame_CppToJSJit, JitFrameLayout::Size());
masm.Push(r19);
Label osrReturnPoint;
@ -989,7 +989,10 @@ JitRuntime::generateProfilerExitFrameTailStub(JSContext* cx)
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineStub), &handle_BaselineStub);
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Rectifier), &handle_Rectifier);
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonICCall), &handle_IonICCall);
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Entry), &handle_Entry);
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_CppToJSJit), &handle_Entry);
// The WasmToJSJit is just another kind of entry.
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_WasmToJSJit), &handle_Entry);
masm.assumeUnreachable("Invalid caller frame type when exiting from Ion frame.");
@ -1213,9 +1216,11 @@ JitRuntime::generateProfilerExitFrameTailStub(JSContext* cx)
}
//
// JitFrame_Entry
// JitFrame_CppToJSJit / JitFrame_WasmJSToJit
//
// If at an entry frame, store null into both fields.
// A fast-path wasm->jit transition frame is an entry frame from the point
// of view of the JIT.
//
masm.bind(&handle_Entry);
{

View File

@ -257,7 +257,7 @@ void Simulator::handle_wasm_interrupt() {
state.fp = fp;
state.lr = (uint8_t*) xreg(30);
state.sp = (uint8_t*) xreg(31);
js::wasm::ActivationIfInnermost(cx_)->startInterrupt(state);
cx_->activation_->asJit()->startWasmInterrupt(state);
set_pc((Instruction*)cs->interruptCode());
}

View File

@ -1334,8 +1334,8 @@ class Redirection
next_(nullptr)
{
next_ = SimulatorProcess::redirection();
if (!SimulatorProcess::ICacheCheckingDisableCount) {
FlushICacheLocked(SimulatorProcess::icache(), addressOfSwiInstruction(),
if (!SimulatorProcess::ICacheCheckingDisableCount) {
FlushICacheLocked(SimulatorProcess::icache(), addressOfSwiInstruction(),
SimInstruction::kInstrSize);
}
SimulatorProcess::setRedirection(this);
@ -1619,14 +1619,14 @@ Simulator::get_pc() const
}
void
Simulator::startInterrupt(WasmActivation* activation)
Simulator::startInterrupt(JitActivation* activation)
{
JS::ProfilingFrameIterator::RegisterState state;
state.pc = (void*) get_pc();
state.fp = (void*) getRegister(fp);
state.sp = (void*) getRegister(sp);
state.lr = (void*) getRegister(ra);
activation->startInterrupt(state);
activation->startWasmInterrupt(state);
}
// The signal handler only redirects the PC to the interrupt stub when the PC is
@ -1640,7 +1640,7 @@ Simulator::handleWasmInterrupt()
void* pc = (void*)get_pc();
void* fp = (void*)getRegister(Register::fp);
WasmActivation* activation = wasm::ActivationIfInnermost(TlsContext.get());
JitActivation* activation = TlsContext.get()->activation()->asJit();
const wasm::CodeSegment* segment;
const wasm::Code* code = activation->compartment()->wasm.lookupCode(pc, &segment);
if (!code || !segment->containsFunctionPC(pc))
@ -1665,9 +1665,9 @@ bool
Simulator::handleWasmFault(int32_t addr, unsigned numBytes)
{
JSContext* cx = TlsContext.get();
WasmActivation* act = wasm::ActivationIfInnermost(cx);
if (!act)
if (!cx->activation() || !cx->activation()->isJit())
return false;
JitActivation* act = cx->activation()->asJit();
void* pc = reinterpret_cast<void*>(get_pc());
uint8_t* fp = reinterpret_cast<uint8_t*>(getRegister(Register::fp));

View File

@ -40,10 +40,10 @@
namespace js {
class WasmActivation;
namespace jit {
class JitActivation;
class Simulator;
class Redirection;
class CachePage;
@ -296,7 +296,7 @@ class Simulator {
// Handle a wasm interrupt triggered by an async signal handler.
void handleWasmInterrupt();
void startInterrupt(WasmActivation* act);
void startInterrupt(JitActivation* act);
// Handle any wasm faults, returning true if the fault was handled.
bool handleWasmFault(int32_t addr, unsigned numBytes);

View File

@ -200,7 +200,7 @@ JitRuntime::generateEnterJIT(JSContext* cx, EnterJitType type)
masm.storePtr(s2, Address(StackPointer, 0)); // callee token
masm.subPtr(StackPointer, s4);
masm.makeFrameDescriptor(s4, JitFrame_Entry, JitFrameLayout::Size());
masm.makeFrameDescriptor(s4, JitFrame_CppToJSJit, JitFrameLayout::Size());
masm.push(s4); // descriptor
CodeLabel returnLabel;
@ -1181,7 +1181,10 @@ JitRuntime::generateProfilerExitFrameTailStub(JSContext* cx)
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineStub), &handle_BaselineStub);
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Rectifier), &handle_Rectifier);
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonICCall), &handle_IonICCall);
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Entry), &handle_Entry);
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_CppToJSJit), &handle_Entry);
// The WasmToJSJit is just another kind of entry.
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_WasmToJSJit), &handle_Entry);
masm.assumeUnreachable("Invalid caller frame type when exiting from Ion frame.");
@ -1400,9 +1403,11 @@ JitRuntime::generateProfilerExitFrameTailStub(JSContext* cx)
}
//
// JitFrame_Entry
// JitFrame_CppToJSJit / JitFrame_WasmJSToJit
//
// If at an entry frame, store null into both fields.
// A fast-path wasm->jit transition frame is an entry frame from the point
// of view of the JIT.
//
masm.bind(&handle_Entry);
{

View File

@ -1613,7 +1613,7 @@ Simulator::get_pc() const
}
void
Simulator::startInterrupt(WasmActivation* activation)
Simulator::startInterrupt(JitActivation* activation)
{
MOZ_CRASH("NIY");
}

View File

@ -40,10 +40,10 @@
namespace js {
class WasmActivation;
namespace jit {
class JitActivation;
class Simulator;
class Redirection;
class CachePage;
@ -305,7 +305,7 @@ class Simulator {
// Handle a wasm interrupt triggered by an async signal handler.
void handleWasmInterrupt();
void startInterrupt(WasmActivation* act);
void startInterrupt(JitActivation* act);
// Executes one instruction.
void instructionDecode(SimInstruction* instr);

View File

@ -222,7 +222,7 @@ JitRuntime::generateEnterJIT(JSContext* cx, EnterJitType type)
masm.storePtr(reg_token, Address(StackPointer, 0)); // callee token
masm.subPtr(StackPointer, s4);
masm.makeFrameDescriptor(s4, JitFrame_Entry, JitFrameLayout::Size());
masm.makeFrameDescriptor(s4, JitFrame_CppToJSJit, JitFrameLayout::Size());
masm.push(s4); // descriptor
CodeLabel returnLabel;
@ -1126,7 +1126,10 @@ JitRuntime::generateProfilerExitFrameTailStub(JSContext* cx)
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineStub), &handle_BaselineStub);
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Rectifier), &handle_Rectifier);
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonICCall), &handle_IonICCall);
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Entry), &handle_Entry);
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_CppToJSJit), &handle_Entry);
// The WasmToJSJit is just another kind of entry.
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_WasmToJSJit), &handle_Entry);
masm.assumeUnreachable("Invalid caller frame type when exiting from Ion frame.");
@ -1345,9 +1348,11 @@ JitRuntime::generateProfilerExitFrameTailStub(JSContext* cx)
}
//
// JitFrame_Entry
// JitFrame_CppToJSJit / JitFrame_WasmJSToJit
//
// If at an entry frame, store null into both fields.
// A fast-path wasm->jit transition frame is an entry frame from the point
// of view of the JIT.
//
masm.bind(&handle_Entry);
{

View File

@ -314,6 +314,7 @@ MacroAssemblerX64::handleFailureWithHandlerTail(void* handler)
Label finally;
Label return_;
Label bailout;
Label wasm;
load32(Address(rsp, offsetof(ResumeFromException, kind)), rax);
asMasm().branch32(Assembler::Equal, rax, Imm32(ResumeFromException::RESUME_ENTRY_FRAME), &entryFrame);
@ -321,6 +322,7 @@ MacroAssemblerX64::handleFailureWithHandlerTail(void* handler)
asMasm().branch32(Assembler::Equal, rax, Imm32(ResumeFromException::RESUME_FINALLY), &finally);
asMasm().branch32(Assembler::Equal, rax, Imm32(ResumeFromException::RESUME_FORCED_RETURN), &return_);
asMasm().branch32(Assembler::Equal, rax, Imm32(ResumeFromException::RESUME_BAILOUT), &bailout);
asMasm().branch32(Assembler::Equal, rax, Imm32(ResumeFromException::RESUME_WASM), &wasm);
breakpoint(); // Invalid kind.
@ -380,6 +382,14 @@ MacroAssemblerX64::handleFailureWithHandlerTail(void* handler)
loadPtr(Address(esp, offsetof(ResumeFromException, bailoutInfo)), r9);
mov(ImmWord(BAILOUT_RETURN_OK), rax);
jmp(Operand(rsp, offsetof(ResumeFromException, target)));
// If we are throwing and the innermost frame was a wasm frame, reset SP and
// FP; SP is pointing to the unwound return address to the wasm entry, so
// we can just ret().
bind(&wasm);
loadPtr(Address(rsp, offsetof(ResumeFromException, framePointer)), rbp);
loadPtr(Address(rsp, offsetof(ResumeFromException, stackPointer)), rsp);
masm.ret();
}
void

View File

@ -161,7 +161,7 @@ JitRuntime::generateEnterJIT(JSContext* cx, EnterJitType type)
masm.subq(rsp, r14);
// Create a frame descriptor.
masm.makeFrameDescriptor(r14, JitFrame_Entry, JitFrameLayout::Size());
masm.makeFrameDescriptor(r14, JitFrame_CppToJSJit, JitFrameLayout::Size());
masm.push(r14);
CodeLabel returnLabel;
@ -1102,7 +1102,10 @@ JitRuntime::generateProfilerExitFrameTailStub(JSContext* cx)
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineStub), &handle_BaselineStub);
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Rectifier), &handle_Rectifier);
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonICCall), &handle_IonICCall);
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Entry), &handle_Entry);
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_CppToJSJit), &handle_Entry);
// The WasmToJSJit is just another kind of entry.
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_WasmToJSJit), &handle_Entry);
masm.assumeUnreachable("Invalid caller frame type when exiting from Ion frame.");
@ -1314,9 +1317,11 @@ JitRuntime::generateProfilerExitFrameTailStub(JSContext* cx)
}
//
// JitFrame_Entry
// JitFrame_CppToJSJit / JitFrame_WasmJSToJit
//
// If at an entry frame, store null into both fields.
// A fast-path wasm->jit transition frame is an entry frame from the point
// of view of the JIT.
//
masm.bind(&handle_Entry);
{

View File

@ -213,6 +213,7 @@ MacroAssemblerX86::handleFailureWithHandlerTail(void* handler)
Label finally;
Label return_;
Label bailout;
Label wasm;
loadPtr(Address(esp, offsetof(ResumeFromException, kind)), eax);
asMasm().branch32(Assembler::Equal, eax, Imm32(ResumeFromException::RESUME_ENTRY_FRAME),
@ -222,6 +223,7 @@ MacroAssemblerX86::handleFailureWithHandlerTail(void* handler)
asMasm().branch32(Assembler::Equal, eax, Imm32(ResumeFromException::RESUME_FORCED_RETURN),
&return_);
asMasm().branch32(Assembler::Equal, eax, Imm32(ResumeFromException::RESUME_BAILOUT), &bailout);
asMasm().branch32(Assembler::Equal, eax, Imm32(ResumeFromException::RESUME_WASM), &wasm);
breakpoint(); // Invalid kind.
@ -283,6 +285,14 @@ MacroAssemblerX86::handleFailureWithHandlerTail(void* handler)
loadPtr(Address(esp, offsetof(ResumeFromException, bailoutInfo)), ecx);
movl(Imm32(BAILOUT_RETURN_OK), eax);
jmp(Operand(esp, offsetof(ResumeFromException, target)));
// If we are throwing and the innermost frame was a wasm frame, reset SP and
// FP; SP is pointing to the unwound return address to the wasm entry, so
// we can just ret().
bind(&wasm);
loadPtr(Address(esp, offsetof(ResumeFromException, framePointer)), ebp);
loadPtr(Address(esp, offsetof(ResumeFromException, stackPointer)), esp);
masm.ret();
}
void

View File

@ -157,7 +157,7 @@ JitRuntime::generateEnterJIT(JSContext* cx, EnterJitType type)
*****************************************************************/
// Create a frame descriptor.
masm.subl(esp, esi);
masm.makeFrameDescriptor(esi, JitFrame_Entry, JitFrameLayout::Size());
masm.makeFrameDescriptor(esi, JitFrame_CppToJSJit, JitFrameLayout::Size());
masm.push(esi);
CodeLabel returnLabel;
@ -1133,7 +1133,10 @@ JitRuntime::generateProfilerExitFrameTailStub(JSContext* cx)
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineStub), &handle_BaselineStub);
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Rectifier), &handle_Rectifier);
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonICCall), &handle_IonICCall);
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Entry), &handle_Entry);
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_CppToJSJit), &handle_Entry);
// The WasmToJSJit is just another kind of entry.
masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_WasmToJSJit), &handle_Entry);
masm.assumeUnreachable("Invalid caller frame type when exiting from Ion frame.");
@ -1347,9 +1350,11 @@ JitRuntime::generateProfilerExitFrameTailStub(JSContext* cx)
}
//
// JitFrame_Entry
// JitFrame_CppToJSJit / JitFrame_WasmJSToJit
//
// If at an entry frame, store null into both fields.
// A fast-path wasm->jit transition frame is an entry frame from the point
// of view of the JIT.
//
masm.bind(&handle_Entry);
{

View File

@ -376,7 +376,11 @@ struct JSContext : public JS::RootingContext,
}
static size_t offsetOfProfilingActivation() {
return offsetof(JSContext, profilingActivation_);
}
}
static size_t offsetOfJitActivation() {
return offsetof(JSContext, jitActivation);
}
#ifdef DEBUG
static size_t offsetOfInUnsafeCallWithABI() {

View File

@ -570,15 +570,14 @@ JSContext::currentScript(jsbytecode** ppc,
return nullptr;
if (act->isJit()) {
if (act->hasWasmExitFP())
return nullptr;
JSScript* script = nullptr;
js::jit::GetPcScript(const_cast<JSContext*>(this), &script, ppc);
MOZ_ASSERT(allowCrossCompartment || script->compartment() == compartment());
return script;
}
if (act->isWasm())
return nullptr;
MOZ_ASSERT(act->isInterpreter());
js::InterpreterFrame* fp = act->asInterpreter()->current();

View File

@ -95,7 +95,6 @@ ReportOverRecursed(JSContext* cx);
class Activation;
class ActivationIterator;
class WasmActivation;
namespace jit {
class JitRuntime;
@ -292,7 +291,6 @@ struct JSRuntime : public js::MallocProvider<JSRuntime>
friend class js::Activation;
friend class js::ActivationIterator;
friend class js::jit::JitActivation;
friend class js::WasmActivation;
friend class js::jit::CompileRuntime;
public:

View File

@ -160,7 +160,7 @@ struct SavedFrame::Lookup {
{
MOZ_ASSERT(source);
MOZ_ASSERT_IF(framePtr.isSome(), activation);
MOZ_ASSERT_IF(framePtr.isSome() && !activation->isWasm(), pc);
MOZ_ASSERT_IF(framePtr.isSome() && !activation->hasWasmExitFP(), pc);
#ifdef JS_MORE_DETERMINISTIC
column = 0;

View File

@ -930,11 +930,8 @@ Activation::isProfiling() const
if (isInterpreter())
return asInterpreter()->isProfiling();
if (isJit())
return asJit()->isProfiling();
MOZ_ASSERT(isWasm());
return asWasm()->isProfiling();
MOZ_ASSERT(isJit());
return asJit()->isProfiling();
}
Activation*

View File

@ -500,6 +500,8 @@ JitFrameIter::operator=(const JitFrameIter& another)
{
MOZ_ASSERT(this != &another);
act_ = another.act_;
if (isSome())
iter_.destroy();
if (!another.isSome())
@ -515,15 +517,17 @@ JitFrameIter::operator=(const JitFrameIter& another)
return *this;
}
JitFrameIter::JitFrameIter(Activation* act)
JitFrameIter::JitFrameIter(jit::JitActivation* act)
{
MOZ_ASSERT(act->isJit() || act->isWasm());
if (act->isJit()) {
iter_.construct<jit::JSJitFrameIter>(act->asJit());
act_ = act;
MOZ_ASSERT(act->hasExitFP(), "packedExitFP is used to determine if JSJit or wasm");
if (act->hasJSExitFP()) {
iter_.construct<jit::JSJitFrameIter>(act);
} else {
MOZ_ASSERT(act->isWasm());
iter_.construct<wasm::WasmFrameIter>(act->asWasm());
MOZ_ASSERT(act->hasWasmExitFP());
iter_.construct<wasm::WasmFrameIter>(act);
}
settle();
}
void
@ -549,29 +553,56 @@ JitFrameIter::done() const
MOZ_CRASH("unhandled case");
}
void
JitFrameIter::settle()
{
if (isJSJit()) {
const jit::JSJitFrameIter& jitFrame = asJSJit();
if (jitFrame.type() != jit::JitFrame_WasmToJSJit)
return;
// Transition from js jit frames to wasm frames: we're on the
// wasm-to-jit fast path. The current stack layout is as follows:
// (stack grows downward)
//
// [--------------------]
// [WASM FUNC ]
// [WASM JIT EXIT FRAME ]
// [JIT WASM ENTRY FRAME] <-- we're here.
//
// So prevFP points to the wasm jit exit FP, maintaing the invariant in
// WasmFrameIter that the first frame is an exit frame and can be
// popped.
wasm::Frame* prevFP = (wasm::Frame*) jitFrame.prevFp();
iter_.destroy();
iter_.construct<wasm::WasmFrameIter>(act_, prevFP);
MOZ_ASSERT(!asWasm().done());
return;
}
}
void
JitFrameIter::operator++()
{
MOZ_ASSERT(isSome());
if (isJSJit()) {
if (isJSJit())
++asJSJit();
return;
}
if (isWasm()) {
else if (isWasm())
++asWasm();
return;
}
MOZ_CRASH("unhandled case");
else
MOZ_CRASH("unhandled case");
settle();
}
OnlyJSJitFrameIter::OnlyJSJitFrameIter(Activation* act)
OnlyJSJitFrameIter::OnlyJSJitFrameIter(jit::JitActivation* act)
: JitFrameIter(act)
{
settle();
}
OnlyJSJitFrameIter::OnlyJSJitFrameIter(JSContext* cx)
: OnlyJSJitFrameIter(cx->activation())
: OnlyJSJitFrameIter(cx->activation()->asJit())
{
}
@ -627,8 +658,8 @@ FrameIter::settleOnActivation()
}
}
if (activation->isJit() || activation->isWasm()) {
data_.jitFrames_ = JitFrameIter(activation);
if (activation->isJit()) {
data_.jitFrames_ = JitFrameIter(activation->asJit());
data_.jitFrames_.skipNonScriptedJSFrames();
if (data_.jitFrames_.done()) {
// It's possible to have an JitActivation with no scripted
@ -1142,7 +1173,7 @@ FrameIter::wasmUpdateBytecodeOffset()
wasm::DebugFrame* frame = wasmFrame().debugFrame();
// Relookup the current frame, updating the bytecode offset in the process.
data_.jitFrames_ = JitFrameIter(data_.activations_->asWasm());
data_.jitFrames_ = JitFrameIter(data_.activations_->asJit());
while (wasmFrame().debugFrame() != frame)
++data_.jitFrames_;
@ -1522,6 +1553,8 @@ jit::JitActivation::~JitActivation()
// JitActivations.
MOZ_ASSERT(!bailoutData_);
MOZ_ASSERT(!isWasmInterrupted());
clearRematerializedFrames();
js_delete(rematerializedFrames_);
}
@ -1712,32 +1745,8 @@ jit::JitActivation::traceIonRecovery(JSTracer* trc)
it->trace(trc);
}
WasmActivation::WasmActivation(JSContext* cx)
: Activation(cx, Wasm),
exitFP_(nullptr)
{
// Now that the WasmActivation is fully initialized, make it visible to
// asynchronous profiling.
registerProfiling();
}
WasmActivation::~WasmActivation()
{
// Hide this activation from the profiler before is is destroyed.
unregisterProfiling();
MOZ_ASSERT(!interrupted());
MOZ_ASSERT(exitFP_ == nullptr);
}
void
WasmActivation::unwindExitFP(wasm::Frame* exitFP)
{
exitFP_ = exitFP;
}
void
WasmActivation::startInterrupt(const JS::ProfilingFrameIterator::RegisterState& state)
jit::JitActivation::startWasmInterrupt(const JS::ProfilingFrameIterator::RegisterState& state)
{
MOZ_ASSERT(state.pc);
MOZ_ASSERT(state.fp);
@ -1745,7 +1754,7 @@ WasmActivation::startInterrupt(const JS::ProfilingFrameIterator::RegisterState&
// Execution can only be interrupted in function code. Afterwards, control
// flow does not reenter function code and thus there should be no
// interrupt-during-interrupt.
MOZ_ASSERT(!interrupted());
MOZ_ASSERT(!isWasmInterrupted());
bool ignoredUnwound;
wasm::UnwindState unwindState;
@ -1755,52 +1764,54 @@ WasmActivation::startInterrupt(const JS::ProfilingFrameIterator::RegisterState&
MOZ_ASSERT(compartment()->wasm.lookupCode(unwindPC)->lookupRange(unwindPC)->isFunction());
cx_->runtime()->startWasmInterrupt(state.pc, unwindPC);
exitFP_ = reinterpret_cast<wasm::Frame*>(unwindState.fp);
setWasmExitFP(unwindState.fp);
MOZ_ASSERT(compartment() == exitFP_->tls->instance->compartment());
MOZ_ASSERT(interrupted());
MOZ_ASSERT(compartment() == unwindState.fp->tls->instance->compartment());
MOZ_ASSERT(isWasmInterrupted());
}
void
WasmActivation::finishInterrupt()
jit::JitActivation::finishWasmInterrupt()
{
MOZ_ASSERT(interrupted());
MOZ_ASSERT(exitFP_);
MOZ_ASSERT(hasWasmExitFP());
MOZ_ASSERT(isWasmInterrupted());
cx_->runtime()->finishWasmInterrupt();
exitFP_ = nullptr;
packedExitFP_ = nullptr;
}
bool
WasmActivation::interrupted() const
jit::JitActivation::isWasmInterrupted() const
{
void* pc = cx_->runtime()->wasmUnwindPC();
if (!pc)
return false;
Activation* act = cx_->activation();
while (act && !act->isWasm())
while (act && !act->hasWasmExitFP())
act = act->prev();
if (act->asWasm() != this)
if (act != this)
return false;
DebugOnly<wasm::Frame*> fp = act->asWasm()->exitFP();
DebugOnly<const wasm::Frame*> fp = wasmExitFP();
MOZ_ASSERT(fp && fp->instance()->code().containsCodePC(pc));
return true;
}
void*
WasmActivation::unwindPC() const
jit::JitActivation::wasmUnwindPC() const
{
MOZ_ASSERT(interrupted());
MOZ_ASSERT(hasWasmExitFP());
MOZ_ASSERT(isWasmInterrupted());
return cx_->runtime()->wasmUnwindPC();
}
void*
WasmActivation::resumePC() const
jit::JitActivation::wasmResumePC() const
{
MOZ_ASSERT(interrupted());
MOZ_ASSERT(hasWasmExitFP());
MOZ_ASSERT(isWasmInterrupted());
return cx_->runtime()->wasmResumePC();
}
@ -1926,21 +1937,31 @@ void
JS::ProfilingFrameIterator::operator++()
{
MOZ_ASSERT(!done());
MOZ_ASSERT(activation_->isWasm() || activation_->isJit());
if (activation_->isWasm()) {
MOZ_ASSERT(activation_->isJit());
if (isWasm())
++wasmIter();
settle();
return;
}
++jitIter();
else
++jitIter();
settle();
}
void
JS::ProfilingFrameIterator::settleFrames()
{
// Handle transition frames (see comment in JitFrameIter::operator++).
if (isJit() && !jitIter().done() && jitIter().frameType() == jit::JitFrame_WasmToJSJit) {
wasm::Frame* fp = (wasm::Frame*) jitIter().fp();
iteratorDestroy();
new (storage()) wasm::ProfilingFrameIterator(*activation_->asJit(), fp);
kind_ = Kind::Wasm;
MOZ_ASSERT(!wasmIter().done());
}
}
void
JS::ProfilingFrameIterator::settle()
{
settleFrames();
while (iteratorDone()) {
iteratorDestroy();
activation_ = activation_->prevProfiling();
@ -1952,6 +1973,7 @@ JS::ProfilingFrameIterator::settle()
if (!activation_)
return;
iteratorConstruct();
settleFrames();
}
}
@ -1959,39 +1981,57 @@ void
JS::ProfilingFrameIterator::iteratorConstruct(const RegisterState& state)
{
MOZ_ASSERT(!done());
MOZ_ASSERT(activation_->isWasm() || activation_->isJit());
MOZ_ASSERT(activation_->isJit());
if (activation_->isWasm()) {
new (storage()) wasm::ProfilingFrameIterator(*activation_->asWasm(), state);
jit::JitActivation* activation = activation_->asJit();
MOZ_ASSERT(activation->isActive());
// We want to know if we should start with a wasm profiling frame iterator
// or not. To determine this, there are three possibilities:
// - we've exited to C++ from wasm, in which case the activation
// exitFP low bit is tagged and we can test hasWasmExitFP().
// - we're in wasm code, so we can do a lookup on PC.
// - in all the other cases, we're not in wasm or we haven't exited from
// wasm.
if (activation->hasWasmExitFP() || wasm::InCompiledCode(state.pc)) {
new (storage()) wasm::ProfilingFrameIterator(*activation, state);
kind_ = Kind::Wasm;
return;
}
MOZ_ASSERT(activation_->asJit()->isActive());
new (storage()) jit::JitProfilingFrameIterator(cx_, state);
kind_ = Kind::JSJit;
}
void
JS::ProfilingFrameIterator::iteratorConstruct()
{
MOZ_ASSERT(!done());
MOZ_ASSERT(activation_->isWasm() || activation_->isJit());
MOZ_ASSERT(activation_->isJit());
if (activation_->isWasm()) {
new (storage()) wasm::ProfilingFrameIterator(*activation_->asWasm());
jit::JitActivation* activation = activation_->asJit();
MOZ_ASSERT(activation->isActive());
// The same reasoning as in the above iteratorConstruct variant applies
// here, except that it's even simpler: since this activation is higher up
// on the stack, it can only have exited to C++, through wasm or ion.
if (activation->hasWasmExitFP()) {
new (storage()) wasm::ProfilingFrameIterator(*activation);
kind_ = Kind::Wasm;
return;
}
MOZ_ASSERT(activation_->asJit()->isActive());
new (storage()) jit::JitProfilingFrameIterator(activation_->asJit()->exitFP());
new (storage()) jit::JitProfilingFrameIterator(activation->jsExitFP());
kind_ = Kind::JSJit;
}
void
JS::ProfilingFrameIterator::iteratorDestroy()
{
MOZ_ASSERT(!done());
MOZ_ASSERT(activation_->isWasm() || activation_->isJit());
MOZ_ASSERT(activation_->isJit());
if (activation_->isWasm()) {
if (isWasm()) {
wasmIter().~ProfilingFrameIterator();
return;
}
@ -2003,9 +2043,9 @@ bool
JS::ProfilingFrameIterator::iteratorDone()
{
MOZ_ASSERT(!done());
MOZ_ASSERT(activation_->isWasm() || activation_->isJit());
MOZ_ASSERT(activation_->isJit());
if (activation_->isWasm())
if (isWasm())
return wasmIter().done();
return jitIter().done();
@ -2015,9 +2055,9 @@ void*
JS::ProfilingFrameIterator::stackAddress() const
{
MOZ_ASSERT(!done());
MOZ_ASSERT(activation_->isWasm() || activation_->isJit());
MOZ_ASSERT(activation_->isJit());
if (activation_->isWasm())
if (isWasm())
return wasmIter().stackAddress();
return jitIter().stackAddress();
@ -2108,11 +2148,11 @@ bool
JS::ProfilingFrameIterator::isWasm() const
{
MOZ_ASSERT(!done());
return activation_->isWasm();
return kind_ == Kind::Wasm;
}
bool
JS::ProfilingFrameIterator::isJit() const
{
return activation_->isJit();
return kind_ == Kind::JSJit;
}

View File

@ -1241,7 +1241,6 @@ static_assert(sizeof(LiveSavedFrameCache) == sizeof(uintptr_t),
/*****************************************************************************/
class InterpreterActivation;
class WasmActivation;
namespace jit {
class JitActivation;
@ -1305,7 +1304,7 @@ class Activation
// callFunctionWithAsyncStack.
bool asyncCallIsExplicit_;
enum Kind { Interpreter, Jit, Wasm };
enum Kind { Interpreter, Jit };
Kind kind_;
inline Activation(JSContext* cx, Kind kind);
@ -1330,9 +1329,7 @@ class Activation
bool isJit() const {
return kind_ == Jit;
}
bool isWasm() const {
return kind_ == Wasm;
}
inline bool hasWasmExitFP() const;
inline bool isProfiling() const;
void registerProfiling();
@ -1346,10 +1343,6 @@ class Activation
MOZ_ASSERT(isJit());
return (jit::JitActivation*)this;
}
WasmActivation* asWasm() const {
MOZ_ASSERT(isWasm());
return (WasmActivation*)this;
}
void hideScriptedCaller() {
hideScriptedCallerCount_++;
@ -1491,8 +1484,13 @@ class BailoutFrameInfo;
// A JitActivation is used for frames running in Baseline or Ion.
class JitActivation : public Activation
{
// If Baseline or Ion code is on the stack, and has called into C++, this
// will be aligned to an ExitFrame.
public:
static const uintptr_t ExitFpWasmBit = 0x1;
private:
// If Baseline, Ion or Wasm code is on the stack, and has called into C++,
// this will be aligned to an ExitFrame. The last bit indicates if it's a
// wasm frame (bit set to ExitFpWasmBit) or not (bit set to !ExitFpWasmBit).
uint8_t* packedExitFP_;
JitActivation* prevJitActivation_;
@ -1563,14 +1561,18 @@ class JitActivation : public Activation
return offsetof(JitActivation, prevJitActivation_);
}
uint8_t* packedExitFP() const {
return packedExitFP_;
bool hasExitFP() const {
return !!packedExitFP_;
}
static size_t offsetOfPackedExitFP() {
return offsetof(JitActivation, packedExitFP_);
}
bool hasJSExitFP() const {
return !(uintptr_t(packedExitFP_) & ExitFpWasmBit);
}
uint8_t* jsExitFP() const {
MOZ_ASSERT(hasJSExitFP());
return packedExitFP_;
}
void setJSExitFP(uint8_t* fp) {
@ -1661,6 +1663,33 @@ class JitActivation : public Activation
void setLastProfilingCallSite(void* ptr) {
lastProfilingCallSite_ = ptr;
}
// WebAssembly specific attributes.
bool hasWasmExitFP() const {
return uintptr_t(packedExitFP_) & ExitFpWasmBit;
}
wasm::Frame* wasmExitFP() const {
MOZ_ASSERT(hasWasmExitFP());
return (wasm::Frame*)(uintptr_t(packedExitFP_) & ~ExitFpWasmBit);
}
void setWasmExitFP(const wasm::Frame* fp) {
if (fp) {
MOZ_ASSERT(!(uintptr_t(fp) & ExitFpWasmBit));
packedExitFP_ = (uint8_t*)(uintptr_t(fp) | ExitFpWasmBit);
MOZ_ASSERT(hasWasmExitFP());
} else {
packedExitFP_ = nullptr;
}
}
// Interrupts are started from the interrupt signal handler (or the ARM
// simulator) and cleared by WasmHandleExecutionInterrupt or WasmHandleThrow
// when the interrupt is handled.
void startWasmInterrupt(const JS::ProfilingFrameIterator::RegisterState& state);
void finishWasmInterrupt();
bool isWasmInterrupted() const;
void* wasmUnwindPC() const;
void* wasmResumePC() const;
};
// A filtering of the ActivationIterator to only stop at JitActivations.
@ -1693,6 +1722,12 @@ class JitActivationIterator : public ActivationIterator
} // namespace jit
inline bool
Activation::hasWasmExitFP() const
{
return isJit() && asJit()->hasWasmExitFP();
}
// Iterates over the frames of a single InterpreterActivation.
class InterpreterFrameIterator
{
@ -1735,42 +1770,6 @@ class InterpreterFrameIterator
}
};
// An eventual goal is to remove WasmActivation and to run asm code in a
// JitActivation interleaved with Ion/Baseline jit code. This would allow
// efficient calls back and forth but requires that we can walk the stack for
// all kinds of jit code.
class WasmActivation : public Activation
{
wasm::Frame* exitFP_;
public:
explicit WasmActivation(JSContext* cx);
~WasmActivation();
bool isProfiling() const {
return true;
}
// Returns null or the final wasm::Frame* when wasm exited this
// WasmActivation.
wasm::Frame* exitFP() const { return exitFP_; }
// Written by JIT code:
static unsigned offsetOfExitFP() { return offsetof(WasmActivation, exitFP_); }
// Interrupts are started from the interrupt signal handler (or the ARM
// simulator) and cleared by WasmHandleExecutionInterrupt or WasmHandleThrow
// when the interrupt is handled.
void startInterrupt(const JS::ProfilingFrameIterator::RegisterState& state);
void finishInterrupt();
bool interrupted() const;
void* unwindPC() const;
void* resumePC() const;
// Used by wasm::WasmFrameIter during stack unwinding.
void unwindExitFP(wasm::Frame* exitFP);
};
// A JitFrameIter can iterate over all kind of frames emitted by our code
// generators, be they composed of JS jit frames or wasm frames, interleaved or
// not, in any order.
@ -1798,11 +1797,14 @@ class WasmActivation : public Activation
class JitFrameIter
{
protected:
jit::JitActivation* act_;
mozilla::MaybeOneOf<jit::JSJitFrameIter, wasm::WasmFrameIter> iter_;
void settle();
public:
JitFrameIter() : iter_() {}
explicit JitFrameIter(Activation* activation);
JitFrameIter() : act_(nullptr), iter_() {}
explicit JitFrameIter(jit::JitActivation* activation);
explicit JitFrameIter(const JitFrameIter& another);
JitFrameIter& operator=(const JitFrameIter& another);
@ -1819,6 +1821,7 @@ class JitFrameIter
const wasm::WasmFrameIter& asWasm() const { return iter_.ref<wasm::WasmFrameIter>(); }
// Operations common to all frame iterators.
const jit::JitActivation* activation() const { return act_; }
bool done() const;
void operator++();
@ -1837,7 +1840,7 @@ class OnlyJSJitFrameIter : public JitFrameIter
}
public:
explicit OnlyJSJitFrameIter(Activation* act);
explicit OnlyJSJitFrameIter(jit::JitActivation* act);
explicit OnlyJSJitFrameIter(JSContext* cx);
explicit OnlyJSJitFrameIter(const ActivationIterator& cx);

View File

@ -260,20 +260,27 @@ TraceLoggerThread::enable(JSContext* cx)
int32_t engine = 0;
if (act->isJit()) {
JSJitFrameIter frame(iter->asJit());
JitFrameIter frame(iter->asJit());
while (!frame.isScripted() && !frame.done())
while (!frame.done()) {
if (frame.isWasm()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TRACELOGGER_ENABLE_FAIL,
"not yet supported in wasm code");
return false;
}
if (frame.asJSJit().isScripted())
break;
++frame;
}
MOZ_ASSERT(!frame.done());
MOZ_ASSERT(frame.isIonJS() || frame.isBaselineJS());
script = frame.script();
engine = frame.isIonJS() ? TraceLogger_IonMonkey : TraceLogger_Baseline;
} else if (act->isWasm()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TRACELOGGER_ENABLE_FAIL,
"not yet supported in wasm code");
return false;
const JSJitFrameIter& jitFrame = frame.asJSJit();
MOZ_ASSERT(jitFrame.isIonJS() || jitFrame.isBaselineJS());
script = jitFrame.script();
engine = jitFrame.isIonJS() ? TraceLogger_IonMonkey : TraceLogger_Baseline;
} else {
MOZ_ASSERT(act->isInterpreter());
InterpreterFrame* fp = act->asInterpreter()->current();

View File

@ -506,6 +506,11 @@ static const unsigned MaxMemoryMaximumPages = 65536;
static const unsigned MaxModuleBytes = 1024 * 1024 * 1024;
static const unsigned MaxFunctionBytes = 128 * 1024;
// A magic value of the FramePointer to indicate after a return to the entry
// stub that an exception has been caught and that we should throw.
static const unsigned FailFP = 0xbad;
} // namespace wasm
} // namespace js

View File

@ -61,43 +61,42 @@ __aeabi_uidivmod(int, int);
#endif
// This utility function can only be called for builtins that are called
// directly from wasm code. Note that WasmCall pushes both an outer
// WasmActivation and an inner JitActivation that becomes active when calling
// JIT code.
static WasmActivation*
// directly from wasm code.
static JitActivation*
CallingActivation()
{
Activation* act = TlsContext.get()->activation();
MOZ_ASSERT(!act->asJit()->isActive(), "WasmCall pushes an inactive JitActivation");
return act->prev()->asWasm();
MOZ_ASSERT(act->asJit()->hasWasmExitFP());
MOZ_ASSERT(act->asJit()->isActive(), "WasmCall pushes an active JitActivation");
return act->asJit();
}
static void*
WasmHandleExecutionInterrupt()
{
WasmActivation* activation = CallingActivation();
MOZ_ASSERT(activation->interrupted());
JitActivation* activation = CallingActivation();
MOZ_ASSERT(activation->isWasmInterrupted());
if (!CheckForInterrupt(activation->cx())) {
// If CheckForInterrupt failed, it is time to interrupt execution.
// Returning nullptr to the caller will jump to the throw stub which
// will call WasmHandleThrow. The WasmActivation must stay in the
// will call HandleThrow. The JitActivation must stay in the
// interrupted state until then so that stack unwinding works in
// WasmHandleThrow.
// HandleThrow.
return nullptr;
}
// If CheckForInterrupt succeeded, then execution can proceed and the
// interrupt is over.
void* resumePC = activation->resumePC();
activation->finishInterrupt();
void* resumePC = activation->wasmResumePC();
activation->finishWasmInterrupt();
return resumePC;
}
static bool
WasmHandleDebugTrap()
{
WasmActivation* activation = CallingActivation();
JitActivation* activation = CallingActivation();
MOZ_ASSERT(activation);
JSContext* cx = activation->cx();
@ -162,12 +161,10 @@ WasmHandleDebugTrap()
// is responsible for notifying the debugger of each unwound frame. The return
// value is the new stack address which the calling stub will set to the sp
// register before executing a return instruction.
static void*
WasmHandleThrow()
{
WasmActivation* activation = CallingActivation();
JSContext* cx = activation->cx();
void*
wasm::HandleThrow(JSContext* cx, WasmFrameIter& iter)
{
// WasmFrameIter iterates down wasm frames in the activation starting at
// JitActivation::wasmExitFP(). Pass Unwind::True to pop
// JitActivation::wasmExitFP() once each time WasmFrameIter is incremented,
@ -180,10 +177,11 @@ WasmHandleThrow()
// we'll be able to switch to ion / other wasm state from here, and we'll
// need to do things differently.
WasmFrameIter iter(activation, WasmFrameIter::Unwind::True);
MOZ_ASSERT(CallingActivation() == iter.activation());
MOZ_ASSERT(!iter.done());
iter.setUnwind(WasmFrameIter::Unwind::True);
// Live wasm code on the stack is kept alive (in wasm::TraceActivations) by
// Live wasm code on the stack is kept alive (in TraceJitActivation) by
// marking the instance of every wasm::Frame found by WasmFrameIter.
// However, as explained above, we're popping frames while iterating which
// means that a GC during this loop could collect the code of frames whose
@ -220,12 +218,22 @@ WasmHandleThrow()
JS_ReportErrorASCII(cx, "Unexpected success from onLeaveFrame");
}
frame->leave(cx);
}
}
MOZ_ASSERT(!cx->activation()->asJit()->isWasmInterrupted(), "unwinding clears the interrupt");
MOZ_ASSERT(!activation->interrupted(), "unwinding clears the interrupt");
return iter.unwoundAddressOfReturnAddress();
}
static void*
WasmHandleThrow()
{
JitActivation* activation = CallingActivation();
JSContext* cx = activation->cx();
WasmFrameIter iter(activation);
return HandleThrow(cx, iter);
}
static void
WasmReportTrap(int32_t trapIndex)
{

View File

@ -24,6 +24,8 @@
namespace js {
namespace wasm {
class WasmFrameIter;
// A SymbolicAddress that NeedsBuiltinThunk() will call through a thunk to the
// C++ function. This will be true for all normal calls from normal wasm
// function code. Only calls to C++ from other exits/thunks do not need a thunk.
@ -47,6 +49,9 @@ LookupBuiltinThunk(void* pc, const CodeRange** codeRange, uint8_t** codeBase);
bool
EnsureBuiltinThunksInitialized();
void*
HandleThrow(JSContext* cx, WasmFrameIter& iter);
void*
SymbolicAddressTarget(SymbolicAddress sym);

View File

@ -22,9 +22,6 @@
#include "wasm/WasmJS.h"
namespace js {
class WasmActivation;
namespace wasm {
class Code;
@ -41,8 +38,6 @@ class Compartment
InstanceVector instances_;
volatile bool mutatingInstances_;
friend class js::WasmActivation;
struct AutoMutateInstances {
Compartment &c;
explicit AutoMutateInstances(Compartment& c) : c(c) {

View File

@ -26,7 +26,6 @@
namespace js {
class Debugger;
class WasmActivation;
class WasmBreakpoint;
class WasmBreakpointSite;
class WasmInstanceObject;

View File

@ -32,22 +32,22 @@ using mozilla::Swap;
/*****************************************************************************/
// WasmFrameIter implementation
WasmFrameIter::WasmFrameIter(WasmActivation* activation, Unwind unwind)
WasmFrameIter::WasmFrameIter(JitActivation* activation, wasm::Frame* fp)
: activation_(activation),
code_(nullptr),
callsite_(nullptr),
codeRange_(nullptr),
fp_(activation->exitFP()),
unwind_(unwind)
fp_(fp ? fp : activation->wasmExitFP()),
unwind_(Unwind::False)
{
MOZ_ASSERT(fp_);
// Normally, execution exits wasm code via an exit stub which sets exitFP to
// the exit stub's frame. Thus, in this case, we want to start iteration at
// the caller of the exit frame, whose Code, CodeRange and CallSite are
// Normally, execution exits wasm code via an exit stub which sets exitFP
// to the exit stub's frame. Thus, in this case, we want to start iteration
// at the caller of the exit frame, whose Code, CodeRange and CallSite are
// indicated by the returnAddress of the exit stub's frame.
if (!activation->interrupted()) {
if (!activation->isWasmInterrupted()) {
popFrame();
MOZ_ASSERT(!done());
return;
@ -55,15 +55,16 @@ WasmFrameIter::WasmFrameIter(WasmActivation* activation, Unwind unwind)
// When asynchronously interrupted, exitFP is set to the interrupted frame
// itself and so we do not want to skip it. Instead, we can recover the
// Code and CodeRange from the WasmActivation, which are set when control
// flow was interrupted. There is no CallSite (b/c the interrupt was async),
// but this is fine because CallSite is only used for line number for which
// we can use the beginning of the function from the CodeRange instead.
// Code and CodeRange from the JitActivation, which are set when control
// flow was interrupted. There is no CallSite (b/c the interrupt was
// async), but this is fine because CallSite is only used for line number
// for which we can use the beginning of the function from the CodeRange
// instead.
code_ = &fp_->tls->instance->code();
MOZ_ASSERT(code_ == activation->compartment()->wasm.lookupCode(activation->unwindPC()));
MOZ_ASSERT(code_ == activation->compartment()->wasm.lookupCode(activation->wasmUnwindPC()));
codeRange_ = code_->lookupRange(activation->unwindPC());
codeRange_ = code_->lookupRange(activation->wasmUnwindPC());
MOZ_ASSERT(codeRange_->kind() == CodeRange::Function);
MOZ_ASSERT(!done());
@ -82,20 +83,20 @@ WasmFrameIter::operator++()
{
MOZ_ASSERT(!done());
// When the iterator is set to unwind, each time the iterator pops a
// frame, the WasmActivation is updated so that the just-popped frame
// is no longer visible. This is necessary since Debugger::onLeaveFrame is
// called before popping each frame and, once onLeaveFrame is called for a
// given frame, that frame must not be visible to subsequent stack iteration
// (or it could be added as a "new" frame just as it becomes garbage).
// When the frame is "interrupted", then exitFP is included in the callstack
// When the iterator is set to unwind, each time the iterator pops a frame,
// the JitActivation is updated so that the just-popped frame is no longer
// visible. This is necessary since Debugger::onLeaveFrame is called before
// popping each frame and, once onLeaveFrame is called for a given frame,
// that frame must not be visible to subsequent stack iteration (or it
// could be added as a "new" frame just as it becomes garbage). When the
// frame is "interrupted", then exitFP is included in the callstack
// (otherwise, it is skipped, as explained above). So to unwind the
// innermost frame, we just clear the interrupt state.
if (unwind_ == Unwind::True) {
if (activation_->interrupted())
activation_->finishInterrupt();
activation_->unwindExitFP(fp_);
if (activation_->isWasmInterrupted())
activation_->finishWasmInterrupt();
activation_->setWasmExitFP(fp_);
}
popFrame();
@ -106,6 +107,7 @@ WasmFrameIter::popFrame()
{
Frame* prevFP = fp_;
fp_ = prevFP->callerFP;
MOZ_ASSERT(!(uintptr_t(fp_) & JitActivation::ExitFpWasmBit));
if (!fp_) {
code_ = nullptr;
@ -113,7 +115,8 @@ WasmFrameIter::popFrame()
callsite_ = nullptr;
if (unwind_ == Unwind::True) {
activation_->unwindExitFP(nullptr);
// TODO with bug 1319203, there may be other JIT frames above.
activation_->setWasmExitFP(nullptr);
unwoundAddressOfReturnAddress_ = &prevFP->returnAddress;
}
@ -175,7 +178,7 @@ unsigned
WasmFrameIter::lineOrBytecode() const
{
MOZ_ASSERT(!done());
MOZ_ASSERT_IF(!callsite_, activation_->interrupted());
MOZ_ASSERT_IF(!callsite_, activation_->isWasmInterrupted());
return callsite_ ? callsite_->lineOrBytecode() : codeRange_->funcLineOrBytecode();
}
@ -305,14 +308,28 @@ PushRetAddr(MacroAssembler& masm, unsigned entry)
}
static void
LoadActivation(MacroAssembler& masm, Register dest)
LoadActivation(MacroAssembler& masm, const Register& dest)
{
// WasmCall pushes a WasmActivation and an inactive JitActivation. The
// JitActivation only becomes active when calling into JS from wasm.
// WasmCall pushes a JitActivation.
masm.loadPtr(Address(WasmTlsReg, offsetof(wasm::TlsData, addressOfContext)), dest);
masm.loadPtr(Address(dest, 0), dest);
masm.loadPtr(Address(dest, JSContext::offsetOfActivation()), dest);
masm.loadPtr(Address(dest, Activation::offsetOfPrev()), dest);
}
void
wasm::SetExitFP(MacroAssembler& masm, Register scratch)
{
masm.orPtr(Imm32(JitActivation::ExitFpWasmBit), FramePointer);
LoadActivation(masm, scratch);
masm.storePtr(FramePointer, Address(scratch, JitActivation::offsetOfPackedExitFP()));
masm.andPtr(Imm32(int32_t(~JitActivation::ExitFpWasmBit)), FramePointer);
}
void
wasm::ClearExitFP(MacroAssembler& masm, Register scratch)
{
LoadActivation(masm, scratch);
masm.storePtr(ImmWord(0x0), Address(scratch, JitActivation::offsetOfPackedExitFP()));
}
static void
@ -366,7 +383,9 @@ GenerateCallablePrologue(MacroAssembler& masm, unsigned framePushed, ExitReason
*tierEntry = masm.currentOffset();
}
if (!reason.isNone()) {
// To make the jit exit faster, we don't set exitFP although we set the
// exit reason: the profiling iteration knows how to recover FP.
if (!reason.isNone() && !(reason.isFixed() && reason.fixed() == ExitReason::Fixed::ImportJit)) {
Register act = ABINonArgReg0;
// Native callers expect the native ABI, which assume that non-saved
@ -375,9 +394,7 @@ GenerateCallablePrologue(MacroAssembler& masm, unsigned framePushed, ExitReason
if (reason.isNative() && !act.volatile_())
masm.Push(act);
LoadActivation(masm, act);
masm.wasmAssertNonExitInvariants(act);
masm.storePtr(FramePointer, Address(act, WasmActivation::offsetOfExitFP()));
SetExitFP(masm, act);
if (reason.isNative() && !act.volatile_())
masm.Pop(act);
@ -401,8 +418,7 @@ GenerateCallableEpilogue(MacroAssembler& masm, unsigned framePushed, ExitReason
if (reason.isNative() && !act.volatile_())
masm.Push(act);
LoadActivation(masm, act);
masm.storePtr(ImmWord(0), Address(act, WasmActivation::offsetOfExitFP()));
ClearExitFP(masm, act);
#ifdef DEBUG
// Check the passed exitReason is the same as the one on entry.
@ -518,6 +534,31 @@ wasm::GenerateExitEpilogue(MacroAssembler& masm, unsigned framePushed, ExitReaso
masm.setFramePushed(0);
}
void
wasm::GenerateJitExitPrologue(MacroAssembler& masm, unsigned framePushed, CallableOffsets* offsets)
{
masm.haltingAlign(CodeAlignment);
GenerateCallablePrologue(masm, framePushed, ExitReason(ExitReason::Fixed::ImportJit),
&offsets->begin, nullptr, CompileMode::Once, 0);
masm.setFramePushed(framePushed);
}
void
wasm::GenerateJitExitEpilogue(MacroAssembler& masm, unsigned framePushed, CallableOffsets* offsets)
{
// Inverse of GenerateJitExitPrologue:
MOZ_ASSERT(masm.framePushed() == framePushed);
#ifdef DEBUG
Register scratch = ABINonArgReturnReg0;
LoadActivation(masm, scratch);
masm.wasmAssertNonExitInvariants(scratch);
#endif
GenerateCallableEpilogue(masm, framePushed, ExitReason::None(), &offsets->ret);
masm.setFramePushed(0);
}
/*****************************************************************************/
// ProfilingFrameIterator
@ -533,7 +574,7 @@ ProfilingFrameIterator::ProfilingFrameIterator()
MOZ_ASSERT(done());
}
ProfilingFrameIterator::ProfilingFrameIterator(const WasmActivation& activation)
ProfilingFrameIterator::ProfilingFrameIterator(const JitActivation& activation, const Frame* fp)
: activation_(&activation),
code_(nullptr),
codeRange_(nullptr),
@ -542,11 +583,11 @@ ProfilingFrameIterator::ProfilingFrameIterator(const WasmActivation& activation)
stackAddress_(nullptr),
exitReason_(ExitReason::Fixed::None)
{
initFromExitFP();
initFromExitFP(fp);
}
static inline void
AssertMatchesCallSite(const WasmActivation& activation, void* callerPC, Frame* callerFP)
AssertMatchesCallSite(const JitActivation& activation, void* callerPC, Frame* callerFP)
{
#ifdef DEBUG
const Code* code = activation.compartment()->wasm.lookupCode(callerPC);
@ -566,16 +607,18 @@ AssertMatchesCallSite(const WasmActivation& activation, void* callerPC, Frame* c
}
void
ProfilingFrameIterator::initFromExitFP()
ProfilingFrameIterator::initFromExitFP(const Frame* fp)
{
Frame* fp = activation_->exitFP();
if (!fp)
fp = activation_->wasmExitFP();
void* pc = fp->returnAddress;
// The iterator inserts a pretend innermost frame for ExitReasons.
// This allows the variety of exit reasons to show up in the callstack.
exitReason_ = ExitReason::Decode(fp->encodedExitReason);
stackAddress_ = fp;
stackAddress_ = (void*)fp;
code_ = activation_->compartment()->wasm.lookupCode(pc);
MOZ_ASSERT(code_);
@ -618,18 +661,21 @@ ProfilingFrameIterator::initFromExitFP()
}
bool
js::wasm::StartUnwinding(const WasmActivation& activation, const RegisterState& registers,
js::wasm::StartUnwinding(const JitActivation& activation, const RegisterState& registers,
UnwindState* unwindState, bool* unwoundCaller)
{
// Shorthands.
uint8_t* const pc = (uint8_t*) registers.pc;
Frame* const fp = (Frame*) registers.fp;
void** const sp = (void**) registers.sp;
// The frame pointer might be in the process of tagging/untagging; make
// sure it's untagged.
Frame* const fp = (Frame*) (intptr_t(registers.fp) & ~JitActivation::ExitFpWasmBit);
// Get the CodeRange describing pc and the base address to which the
// CodeRange is relative. If the pc is not in a wasm module or a builtin
// thunk, then execution must be entering from or leaving to the C++ caller
// that pushed the WasmActivation.
// that pushed the JitActivation.
const CodeRange* codeRange;
uint8_t* codeBase;
const Code* code = activation.compartment()->wasm.lookupCode(pc);
@ -731,6 +777,16 @@ js::wasm::StartUnwinding(const WasmActivation& activation, const RegisterState&
fixedFP = fp;
AssertMatchesCallSite(activation, fixedPC, fixedFP);
} else {
if (codeRange->kind() == CodeRange::ImportJitExit) {
// The jit exit contains a range where the value of FP can't be
// trusted. Technically, we could recover fp from sp, but since
// the range is so short, for now just drop the stack.
if (offsetInCode >= codeRange->jitExitUntrustedFPStart() &&
offsetInCode < codeRange->jitExitUntrustedFPEnd())
{
return false;
}
}
// Not in the prologue/epilogue.
fixedPC = pc;
fixedFP = fp;
@ -749,9 +805,9 @@ js::wasm::StartUnwinding(const WasmActivation& activation, const RegisterState&
AssertMatchesCallSite(activation, fp->returnAddress, fp->callerFP);
break;
case CodeRange::Entry:
// The entry trampoline is the final frame in an WasmActivation. The entry
// trampoline also doesn't GeneratePrologue/Epilogue so we can't use
// the general unwinding logic above.
// The entry trampoline is the final frame in an wasm JitActivation. The
// entry trampoline also doesn't GeneratePrologue/Epilogue so we can't
// use the general unwinding logic above.
break;
case CodeRange::Throw:
// The throw stub executes a small number of instructions before popping
@ -772,7 +828,7 @@ js::wasm::StartUnwinding(const WasmActivation& activation, const RegisterState&
return true;
}
ProfilingFrameIterator::ProfilingFrameIterator(const WasmActivation& activation,
ProfilingFrameIterator::ProfilingFrameIterator(const JitActivation& activation,
const RegisterState& state)
: activation_(&activation),
code_(nullptr),
@ -784,7 +840,7 @@ ProfilingFrameIterator::ProfilingFrameIterator(const WasmActivation& activation,
{
// In the case of ImportJitExit, the fp register may be temporarily
// clobbered on return from Ion so always use activation.fp when it is set.
if (activation.exitFP()) {
if (activation.hasWasmExitFP()) {
initFromExitFP();
return;
}
@ -1052,19 +1108,6 @@ wasm::LookupFaultingInstance(const Code& code, void* pc, void* fp)
return instance;
}
WasmActivation*
wasm::ActivationIfInnermost(JSContext* cx)
{
// WasmCall pushes both an outer WasmActivation and an inner JitActivation
// that only becomes active when calling JIT code.
Activation* act = cx->activation();
while (act && act->isJit() && !act->asJit()->isActive())
act = act->prev();
if (!act || !act->isWasm())
return nullptr;
return act->asWasm();
}
bool
wasm::InCompiledCode(void* pc)
{

View File

@ -26,8 +26,10 @@ class JSAtom;
namespace js {
class WasmActivation;
namespace jit { class MacroAssembler; }
namespace jit {
class MacroAssembler;
struct Register;
} // namespace jit
namespace wasm {
@ -42,7 +44,7 @@ struct Frame;
struct FuncOffsets;
struct CallableOffsets;
// Iterates over a linear group of wasm frames of a single WasmActivation,
// Iterates over a linear group of wasm frames of a single wasm JitActivation,
// called synchronously from C++ in the wasm thread. It will stop at the first
// frame that is not of the same kind, or at the end of an activation.
//
@ -61,7 +63,7 @@ class WasmFrameIter
enum class Unwind { True, False };
private:
WasmActivation* activation_;
jit::JitActivation* activation_;
const Code* code_;
const CallSite* callsite_;
const CodeRange* codeRange_;
@ -73,7 +75,9 @@ class WasmFrameIter
public:
// See comment above this class definition.
explicit WasmFrameIter(WasmActivation* activation, Unwind unwind = Unwind::False);
explicit WasmFrameIter(jit::JitActivation* activation, Frame* fp = nullptr);
const jit::JitActivation* activation() const { return activation_; }
void setUnwind(Unwind unwind) { unwind_ = unwind; }
void operator++();
bool done() const;
const char* filename() const;
@ -108,7 +112,7 @@ class ExitReason
ImportJit, // fast-path call directly into JIT code
ImportInterp, // slow-path call into C++ Invoke()
BuiltinNative, // fast-path call directly into native C++ code
Trap, // call to trap handler for the trap in WasmActivation::trap
Trap, // call to trap handler
DebugTrap // call to debug trap handler
};
@ -150,11 +154,11 @@ class ExitReason
}
};
// Iterates over the frames of a single WasmActivation, given an
// Iterates over the frames of a single wasm JitActivation, given an
// asynchronously-interrupted thread's state.
class ProfilingFrameIterator
{
const WasmActivation* activation_;
const jit::JitActivation* activation_;
const Code* code_;
const CodeRange* codeRange_;
Frame* callerFP_;
@ -162,12 +166,13 @@ class ProfilingFrameIterator
void* stackAddress_;
ExitReason exitReason_;
void initFromExitFP();
void initFromExitFP(const Frame* fp = nullptr);
public:
ProfilingFrameIterator();
explicit ProfilingFrameIterator(const WasmActivation& activation);
ProfilingFrameIterator(const WasmActivation& activation,
explicit ProfilingFrameIterator(const jit::JitActivation& activation,
const Frame* fp = nullptr);
ProfilingFrameIterator(const jit::JitActivation& activation,
const JS::ProfilingFrameIterator::RegisterState& state);
void operator++();
bool done() const { return !codeRange_; }
@ -178,6 +183,11 @@ class ProfilingFrameIterator
// Prologue/epilogue code generation
void
SetExitFP(jit::MacroAssembler& masm, jit::Register scratch);
void
ClearExitFP(jit::MacroAssembler& masm, jit::Register scratch);
void
GenerateExitPrologue(jit::MacroAssembler& masm, unsigned framePushed, ExitReason reason,
CallableOffsets* offsets);
@ -185,6 +195,10 @@ void
GenerateExitEpilogue(jit::MacroAssembler& masm, unsigned framePushed, ExitReason reason,
CallableOffsets* offsets);
void
GenerateJitExitPrologue(jit::MacroAssembler& masm, unsigned framePushed, CallableOffsets* offsets);
void
GenerateJitExitEpilogue(jit::MacroAssembler& masm, unsigned framePushed, CallableOffsets* offsets);
void
GenerateFunctionPrologue(jit::MacroAssembler& masm, unsigned framePushed, const SigIdDesc& sigId,
FuncOffsets* offsets, CompileMode mode = CompileMode::Once,
uint32_t funcIndex = 0);
@ -197,11 +211,6 @@ GenerateFunctionEpilogue(jit::MacroAssembler& masm, unsigned framePushed, FuncOf
Instance*
LookupFaultingInstance(const Code& code, void* pc, void* fp);
// If the innermost (active) Activation is a WasmActivation, return it.
WasmActivation*
ActivationIfInnermost(JSContext* cx);
// Return whether the given PC is in wasm code.
bool
@ -232,7 +241,7 @@ typedef JS::ProfilingFrameIterator::RegisterState RegisterState;
// frame should be ignored.
bool
StartUnwinding(const WasmActivation& activation, const RegisterState& registers,
StartUnwinding(const jit::JitActivation& activation, const RegisterState& registers,
UnwindState* unwindState, bool* unwoundCaller);
} // namespace wasm

View File

@ -674,13 +674,7 @@ Instance::callExport(JSContext* cx, uint32_t funcIndex, CallArgs args)
}
{
// Push a WasmActivation to describe the wasm frames we're about to push
// when running this module. Additionally, push a JitActivation so that
// the optimized wasm-to-Ion FFI call path (which we want to be very
// fast) can avoid doing so. The JitActivation is marked as inactive so
// stack iteration will skip over it.
WasmActivation activation(cx);
JitActivation jitActivation(cx, /* active */ false);
JitActivation activation(cx);
// Call the per-exported-function trampoline created by GenerateEntry.
auto funcPtr = JS_DATA_TO_FUNC_PTR(ExportFuncPtr, codeBase(tier) + func.entryOffset());

View File

@ -781,7 +781,7 @@ ComputeAccessAddress(EMULATOR_CONTEXT* context, const Disassembler::ComplexAddre
MOZ_COLD static void
HandleMemoryAccess(EMULATOR_CONTEXT* context, uint8_t* pc, uint8_t* faultingAddress,
const Instance& instance, WasmActivation* activation, uint8_t** ppc)
const Instance& instance, JitActivation* activation, uint8_t** ppc)
{
MOZ_RELEASE_ASSERT(instance.code().containsCodePC(pc));
@ -792,7 +792,7 @@ HandleMemoryAccess(EMULATOR_CONTEXT* context, uint8_t* pc, uint8_t* faultingAddr
// experimental SIMD.js or Atomics. When these are converted to
// non-experimental wasm features, this case, as well as outOfBoundsCode,
// can be removed.
activation->startInterrupt(ToRegisterState(context));
activation->startWasmInterrupt(ToRegisterState(context));
if (!instance.code().containsCodePC(pc, &segment))
MOZ_CRASH("Cannot map PC to trap handler");
*ppc = segment->outOfBoundsCode();
@ -930,7 +930,7 @@ HandleMemoryAccess(EMULATOR_CONTEXT* context, uint8_t* pc, uint8_t* faultingAddr
MOZ_COLD static void
HandleMemoryAccess(EMULATOR_CONTEXT* context, uint8_t* pc, uint8_t* faultingAddress,
const Instance& instance, WasmActivation* activation, uint8_t** ppc)
const Instance& instance, JitActivation* activation, uint8_t** ppc)
{
MOZ_RELEASE_ASSERT(instance.code().containsCodePC(pc));
@ -938,7 +938,7 @@ HandleMemoryAccess(EMULATOR_CONTEXT* context, uint8_t* pc, uint8_t* faultingAddr
const MemoryAccess* memoryAccess = instance.code().lookupMemoryAccess(pc, &segment);
if (!memoryAccess) {
// See explanation in the WASM_HUGE_MEMORY HandleMemoryAccess.
activation->startInterrupt(ToRegisterState(context));
activation->startWasmInterrupt(ToRegisterState(context));
if (!instance.code().containsCodePC(pc, &segment))
MOZ_CRASH("Cannot map PC to trap handler");
*ppc = segment->outOfBoundsCode();
@ -990,9 +990,9 @@ HandleFault(PEXCEPTION_POINTERS exception)
return false;
AutoSetHandlingSegFault handling(cx);
WasmActivation* activation = ActivationIfInnermost(cx);
if (!activation)
if (!cx->activation() || !cx->activation()->isJit())
return false;
JitActivation* activation = cx->activation()->asJit();
const CodeSegment* codeSegment;
const Code* code = activation->compartment()->wasm.lookupCode(pc, &codeSegment);
@ -1013,8 +1013,8 @@ HandleFault(PEXCEPTION_POINTERS exception)
for (auto t : code->tiers()) {
if (pc == code->segment(t).interruptCode() &&
activation->interrupted() &&
code->segment(t).containsCodePC(activation->resumePC()))
activation->isWasmInterrupted() &&
code->segment(t).containsCodePC(activation->wasmResumePC()))
{
return true;
}
@ -1031,14 +1031,14 @@ HandleFault(PEXCEPTION_POINTERS exception)
// Similar to the non-atomic situation above, on Windows, an OOB fault at a
// PC can trigger *after* an async interrupt observed that PC and attempted
// to redirect to the async stub. In this unique case, interrupted() is
// to redirect to the async stub. In this unique case, isWasmInterrupted() is
// already true when the OOB handler is called. Since the point of the async
// interrupt is to get out of an iloop and the OOB trap will do just that,
// we can simply clear the interrupt. (The update to CONTEXT.pc made by
// HandleMemoryAccess will clobber the interrupt's previous update.)
if (activation->interrupted()) {
MOZ_ASSERT(activation->resumePC() == pc);
activation->finishInterrupt();
if (activation->isWasmInterrupted()) {
MOZ_ASSERT(activation->wasmResumePC() == pc);
activation->finishWasmInterrupt();
}
HandleMemoryAccess(context, pc, faultingAddress, *instance, activation, ppc);
@ -1133,9 +1133,9 @@ HandleMachException(JSContext* cx, const ExceptionRequest& request)
// normally only be accessed by the cx's active thread.
AutoNoteSingleThreadedRegion anstr;
WasmActivation* activation = ActivationIfInnermost(cx);
if (!activation)
if (!cx->activation() || !cx->activation()->isJit())
return false;
JitActivation* activation = cx->activation()->asJit();
const Code* code = activation->compartment()->wasm.lookupCode(pc);
if (!code)
@ -1344,9 +1344,9 @@ HandleFault(int signum, siginfo_t* info, void* ctx)
return false;
AutoSetHandlingSegFault handling(cx);
WasmActivation* activation = ActivationIfInnermost(cx);
if (!activation)
if (!cx->activation() || !cx->activation()->isJit())
return false;
JitActivation* activation = cx->activation()->asJit();
const CodeSegment* segment;
const Code* code = activation->compartment()->wasm.lookupCode(pc, &segment);
@ -1384,7 +1384,7 @@ HandleFault(int signum, siginfo_t* info, void* ctx)
// partly overlaps the end of the heap. In this case, it is an out-of-bounds
// error and we should signal that properly, but to do so we must inspect
// the operand of the failed access.
activation->startInterrupt(ToRegisterState(context));
activation->startWasmInterrupt(ToRegisterState(context));
*ppc = segment->unalignedAccessCode();
return true;
}
@ -1491,29 +1491,27 @@ RedirectJitCodeToInterruptCheck(JSContext* cx, CONTEXT* context)
if (!InInterruptibleCode(cx, pc, &codeSegment))
return false;
// Only probe cx->activation() via ActivationIfInnermost after we know the
// pc is in wasm code. This way we don't depend on signal-safe update of
// cx->activation().
WasmActivation* activation = ActivationIfInnermost(cx);
MOZ_ASSERT(activation);
#ifdef JS_SIMULATOR
// The checks performed by the !JS_SIMULATOR path happen in
// Simulator::handleWasmInterrupt.
cx->simulator()->trigger_wasm_interrupt();
#else
// Only probe cx->activation() after we know the pc is in wasm code. This
// way we don't depend on signal-safe update of cx->activation().
JitActivation* activation = cx->activation()->asJit();
// fp may be null when first entering wasm code from an entry stub.
uint8_t* fp = ContextToFP(context);
if (!fp)
return false;
// The out-of-bounds/unaligned trap paths which call startInterrupt() go
// The out-of-bounds/unaligned trap paths which call startWasmInterrupt() go
// through function code, so test if already interrupted. These paths are
// temporary though, so this case can be removed later.
if (activation->interrupted())
if (activation->isWasmInterrupted())
return false;
activation->startInterrupt(ToRegisterState(context));
activation->startWasmInterrupt(ToRegisterState(context));
*ContextToPC(context) = codeSegment->interruptCode();
#endif

View File

@ -35,8 +35,6 @@ namespace js {
extern void
InterruptRunningJitCode(JSContext* cx);
class WasmActivation;
namespace wasm {
// Ensure the given JSRuntime is set up to use signals. Failure to enable signal

View File

@ -249,7 +249,6 @@ static const unsigned NonVolatileRegsPushSize = NonVolatileRegs.gprs().size() *
NonVolatileRegs.fpus().getPushSizeInBytes();
#endif
static const unsigned FramePushedBeforeAlign = NonVolatileRegsPushSize + sizeof(void*);
static const unsigned FailFP = 0xbad;
// Generate a stub that enters wasm from a C++ caller via the native ABI. The
// signature of the entry point is Module::ExportFuncPtr. The exported wasm
@ -699,7 +698,7 @@ GenerateImportInterpExit(MacroAssembler& masm, const FuncImport& fi, uint32_t fu
// having boxed all the ABI arguments into the JIT stack frame layout.
static bool
GenerateImportJitExit(MacroAssembler& masm, const FuncImport& fi, Label* throwLabel,
CallableOffsets* offsets)
JitExitOffsets* offsets)
{
masm.setFramePushed(0);
@ -711,17 +710,19 @@ GenerateImportJitExit(MacroAssembler& masm, const FuncImport& fi, Label* throwLa
// the return address.
static_assert(WasmStackAlignment >= JitStackAlignment, "subsumes");
unsigned sizeOfRetAddr = sizeof(void*);
unsigned jitFrameBytes = 3 * sizeof(void*) + (1 + fi.sig().args().length()) * sizeof(Value);
unsigned totalJitFrameBytes = sizeOfRetAddr + jitFrameBytes;
unsigned sizeOfPreFrame = WasmFrameLayout::Size() - sizeOfRetAddr;
unsigned sizeOfThisAndArgs = (1 + fi.sig().args().length()) * sizeof(Value);
unsigned totalJitFrameBytes = sizeOfRetAddr + sizeOfPreFrame + sizeOfThisAndArgs;
unsigned jitFramePushed = StackDecrementForCall(masm, JitStackAlignment, totalJitFrameBytes) -
sizeOfRetAddr;
unsigned sizeOfThisAndArgsAndPadding = jitFramePushed - sizeOfPreFrame;
GenerateExitPrologue(masm, jitFramePushed, ExitReason::Fixed::ImportJit, offsets);
GenerateJitExitPrologue(masm, jitFramePushed, offsets);
// 1. Descriptor
size_t argOffset = 0;
uint32_t descriptor = MakeFrameDescriptor(jitFramePushed, JitFrame_Entry,
JitFrameLayout::Size());
uint32_t descriptor = MakeFrameDescriptor(sizeOfThisAndArgsAndPadding, JitFrame_WasmToJSJit,
WasmFrameLayout::Size());
masm.storePtr(ImmWord(uintptr_t(descriptor)), Address(masm.getStackPointer(), argOffset));
argOffset += sizeof(size_t);
@ -744,6 +745,7 @@ GenerateImportJitExit(MacroAssembler& masm, const FuncImport& fi, Label* throwLa
unsigned argc = fi.sig().args().length();
masm.storePtr(ImmWord(uintptr_t(argc)), Address(masm.getStackPointer(), argOffset));
argOffset += sizeof(size_t);
MOZ_ASSERT(argOffset == sizeOfPreFrame);
// 4. |this| value
masm.storeValue(UndefinedValue(), Address(masm.getStackPointer(), argOffset));
@ -753,70 +755,21 @@ GenerateImportJitExit(MacroAssembler& masm, const FuncImport& fi, Label* throwLa
unsigned offsetToCallerStackArgs = jitFramePushed + sizeof(Frame);
FillArgumentArray(masm, fi.sig().args(), argOffset, offsetToCallerStackArgs, scratch, ToValue(true));
argOffset += fi.sig().args().length() * sizeof(Value);
MOZ_ASSERT(argOffset == jitFrameBytes);
{
// Enable Activation.
//
// This sequence requires two registers, and needs to preserve the
// 'callee' register, so there are three live registers.
MOZ_ASSERT(callee == WasmIonExitRegCallee);
Register cx = WasmIonExitRegE0;
Register act = WasmIonExitRegE1;
// JitActivation* act = cx->activation();
masm.loadPtr(Address(WasmTlsReg, offsetof(TlsData, addressOfContext)), cx);
masm.loadPtr(Address(cx, 0), cx);
masm.loadPtr(Address(cx, JSContext::offsetOfActivation()), act);
// act.active_ = true;
masm.store8(Imm32(1), Address(act, JitActivation::offsetOfActiveUint8()));
// cx->jitActivation = act;
masm.storePtr(act, Address(cx, offsetof(JSContext, jitActivation)));
// cx->profilingActivation_ = act;
masm.storePtr(act, Address(cx, JSContext::offsetOfProfilingActivation()));
}
MOZ_ASSERT(argOffset == sizeOfThisAndArgs + sizeOfPreFrame);
AssertStackAlignment(masm, JitStackAlignment, sizeOfRetAddr);
masm.callJitNoProfiler(callee);
AssertStackAlignment(masm, JitStackAlignment, sizeOfRetAddr);
// The JIT callee clobbers all registers, including WasmTlsReg and
// FramePointer, so restore those here.
// FramePointer, so restore those here. During this sequence of
// instructions, FP can't be trusted by the profiling frame iterator.
offsets->untrustedFPStart = masm.currentOffset();
AssertStackAlignment(masm, JitStackAlignment, sizeOfRetAddr);
masm.loadWasmTlsRegFromFrame();
masm.moveStackPtrTo(FramePointer);
masm.addPtr(Imm32(masm.framePushed()), FramePointer);
{
// Disable Activation.
//
// This sequence needs three registers and must preserve WasmTlsReg,
// JSReturnReg_Data and JSReturnReg_Type.
MOZ_ASSERT(JSReturnReg_Data == WasmIonExitRegReturnData);
MOZ_ASSERT(JSReturnReg_Type == WasmIonExitRegReturnType);
MOZ_ASSERT(WasmTlsReg == WasmIonExitTlsReg);
Register cx = WasmIonExitRegD0;
Register act = WasmIonExitRegD1;
Register tmp = WasmIonExitRegD2;
// JitActivation* act = cx->activation();
masm.loadPtr(Address(WasmTlsReg, offsetof(TlsData, addressOfContext)), cx);
masm.loadPtr(Address(cx, 0), cx);
masm.loadPtr(Address(cx, JSContext::offsetOfActivation()), act);
// cx->jitActivation = act->prevJitActivation_;
masm.loadPtr(Address(act, JitActivation::offsetOfPrevJitActivation()), tmp);
masm.storePtr(tmp, Address(cx, offsetof(JSContext, jitActivation)));
// cx->profilingActivation = act->prevProfilingActivation_;
masm.loadPtr(Address(act, Activation::offsetOfPrevProfiling()), tmp);
masm.storePtr(tmp, Address(cx, JSContext::offsetOfProfilingActivation()));
// act->active_ = false;
masm.store8(Imm32(0), Address(act, JitActivation::offsetOfActiveUint8()));
}
offsets->untrustedFPEnd = masm.currentOffset();
// As explained above, the frame was aligned for the JIT ABI such that
// (sp + sizeof(void*)) % JitStackAlignment == 0
@ -863,7 +816,7 @@ GenerateImportJitExit(MacroAssembler& masm, const FuncImport& fi, Label* throwLa
Label done;
masm.bind(&done);
GenerateExitEpilogue(masm, masm.framePushed(), ExitReason::Fixed::ImportJit, offsets);
GenerateJitExitEpilogue(masm, masm.framePushed(), offsets);
if (oolConvert.used()) {
masm.bind(&oolConvert);
@ -880,6 +833,14 @@ GenerateImportJitExit(MacroAssembler& masm, const FuncImport& fi, Label* throwLa
// Store return value into argv[0]
masm.storeValue(JSReturnOperand, Address(masm.getStackPointer(), offsetToCoerceArgv));
// From this point, it's safe to reuse the scratch register (which
// might be part of the JSReturnOperand).
// The JIT might have clobbered exitFP at this point. Since there's
// going to be a CoerceInPlace call, pretend we're still doing the JIT
// call by restoring our tagged exitFP.
SetExitFP(masm, scratch);
// argument 0: argv
ABIArgMIRTypeIter i(coerceArgTypes);
Address argv(masm.getStackPointer(), offsetToCoerceArgv);
@ -892,7 +853,8 @@ GenerateImportJitExit(MacroAssembler& masm, const FuncImport& fi, Label* throwLa
i++;
MOZ_ASSERT(i.done());
// Call coercion function
// Call coercion function. Note that right after the call, the value of
// FP is correct because FP is non-volatile in the native ABI.
AssertStackAlignment(masm, ABIStackAlignment);
switch (fi.sig().ret()) {
case ExprType::I32:
@ -915,6 +877,10 @@ GenerateImportJitExit(MacroAssembler& masm, const FuncImport& fi, Label* throwLa
MOZ_CRASH("Unsupported convert type");
}
// Maintain the invariant that exitFP is either unset or not set to a
// wasm tagged exitFP, per the jit exit contract.
ClearExitFP(masm, scratch);
masm.jump(&done);
masm.setFramePushed(0);
}
@ -1015,7 +981,7 @@ wasm::GenerateBuiltinThunk(MacroAssembler& masm, ABIFunctionType abiType, ExitRe
// Generate a stub that calls into ReportTrap with the right trap reason.
// This stub is called with ABIStackAlignment by a trap out-of-line path. An
// exit prologue/epilogue is used so that stack unwinding picks up the
// current WasmActivation. Unwinding will begin at the caller of this trap exit.
// current JitActivation. Unwinding will begin at the caller of this trap exit.
static bool
GenerateTrapExit(MacroAssembler& masm, Trap trap, Label* throwLabel, CallableOffsets* offsets)
{
@ -1280,9 +1246,9 @@ GenerateThrowStub(MacroAssembler& masm, Label* throwLabel, Offsets* offsets)
if (ShadowStackSpace)
masm.subFromStackPtr(Imm32(ShadowStackSpace));
// WasmHandleThrow unwinds WasmActivation::exitFP and returns the address of
// the return address on the stack this stub should return to. Set the
// FramePointer to a magic value to indicate a return by throw.
// WasmHandleThrow unwinds JitActivation::wasmExitFP() and returns the
// address of the return address on the stack this stub should return to.
// Set the FramePointer to a magic value to indicate a return by throw.
masm.call(SymbolicAddress::HandleThrow);
masm.moveToStackPtr(ReturnReg);
masm.move32(Imm32(FailFP), FramePointer);
@ -1357,16 +1323,16 @@ wasm::GenerateStubs(const ModuleEnvironment& env, const FuncImportVector& import
for (uint32_t funcIndex = 0; funcIndex < imports.length(); funcIndex++) {
const FuncImport& fi = imports[funcIndex];
CallableOffsets offsets;
if (!GenerateImportInterpExit(masm, fi, funcIndex, &throwLabel, &offsets))
CallableOffsets interpOffsets;
if (!GenerateImportInterpExit(masm, fi, funcIndex, &throwLabel, &interpOffsets))
return false;
if (!code->codeRanges.emplaceBack(CodeRange::ImportInterpExit, funcIndex, offsets))
if (!code->codeRanges.emplaceBack(CodeRange::ImportInterpExit, funcIndex, interpOffsets))
return false;
if (!GenerateImportJitExit(masm, fi, &throwLabel, &offsets))
JitExitOffsets jitOffsets;
if (!GenerateImportJitExit(masm, fi, &throwLabel, &jitOffsets))
return false;
if (!code->codeRanges.emplaceBack(CodeRange::ImportJitExit, funcIndex, offsets))
if (!code->codeRanges.emplaceBack(funcIndex, jitOffsets))
return false;
}

View File

@ -679,13 +679,10 @@ CodeRange::CodeRange(Kind kind, Offsets offsets)
: begin_(offsets.begin),
ret_(0),
end_(offsets.end),
funcIndex_(0),
funcLineOrBytecode_(0),
funcBeginToNormalEntry_(0),
funcBeginToTierEntry_(0),
kind_(kind)
{
MOZ_ASSERT(begin_ <= end_);
PodZero(&u);
#ifdef DEBUG
switch (kind_) {
case FarJumpIsland:
@ -704,12 +701,12 @@ CodeRange::CodeRange(Kind kind, uint32_t funcIndex, Offsets offsets)
: begin_(offsets.begin),
ret_(0),
end_(offsets.end),
funcIndex_(funcIndex),
funcLineOrBytecode_(0),
funcBeginToNormalEntry_(0),
funcBeginToTierEntry_(0),
kind_(kind)
{
u.funcIndex_ = funcIndex;
u.func.lineOrBytecode_ = 0;
u.func.beginToNormalEntry_ = 0;
u.func.beginToTierEntry_ = 0;
MOZ_ASSERT(kind == Entry);
MOZ_ASSERT(begin_ <= end_);
}
@ -718,14 +715,11 @@ CodeRange::CodeRange(Kind kind, CallableOffsets offsets)
: begin_(offsets.begin),
ret_(offsets.ret),
end_(offsets.end),
funcIndex_(0),
funcLineOrBytecode_(0),
funcBeginToNormalEntry_(0),
funcBeginToTierEntry_(0),
kind_(kind)
{
MOZ_ASSERT(begin_ < ret_);
MOZ_ASSERT(ret_ < end_);
PodZero(&u);
#ifdef DEBUG
switch (kind_) {
case TrapExit:
@ -742,45 +736,56 @@ CodeRange::CodeRange(Kind kind, uint32_t funcIndex, CallableOffsets offsets)
: begin_(offsets.begin),
ret_(offsets.ret),
end_(offsets.end),
funcIndex_(funcIndex),
funcLineOrBytecode_(0),
funcBeginToNormalEntry_(0),
funcBeginToTierEntry_(0),
kind_(kind)
{
MOZ_ASSERT(isImportExit());
MOZ_ASSERT(isImportExit() && !isImportJitExit());
MOZ_ASSERT(begin_ < ret_);
MOZ_ASSERT(ret_ < end_);
u.funcIndex_ = funcIndex;
u.func.lineOrBytecode_ = 0;
u.func.beginToNormalEntry_ = 0;
u.func.beginToTierEntry_ = 0;
}
CodeRange::CodeRange(uint32_t funcIndex, JitExitOffsets offsets)
: begin_(offsets.begin),
ret_(offsets.ret),
end_(offsets.end),
kind_(ImportJitExit)
{
MOZ_ASSERT(isImportJitExit());
MOZ_ASSERT(begin_ < ret_);
MOZ_ASSERT(ret_ < end_);
u.funcIndex_ = funcIndex;
u.jitExit.beginToUntrustedFPStart_ = offsets.untrustedFPStart - begin_;
u.jitExit.beginToUntrustedFPEnd_ = offsets.untrustedFPEnd - begin_;
}
CodeRange::CodeRange(Trap trap, CallableOffsets offsets)
: begin_(offsets.begin),
ret_(offsets.ret),
end_(offsets.end),
trap_(trap),
funcLineOrBytecode_(0),
funcBeginToNormalEntry_(0),
funcBeginToTierEntry_(0),
kind_(TrapExit)
{
MOZ_ASSERT(begin_ < ret_);
MOZ_ASSERT(ret_ < end_);
u.trap_ = trap;
}
CodeRange::CodeRange(uint32_t funcIndex, uint32_t funcLineOrBytecode, FuncOffsets offsets)
: begin_(offsets.begin),
ret_(offsets.ret),
end_(offsets.end),
funcIndex_(funcIndex),
funcLineOrBytecode_(funcLineOrBytecode),
funcBeginToNormalEntry_(offsets.normalEntry - begin_),
funcBeginToTierEntry_(offsets.tierEntry - begin_),
kind_(Function)
{
MOZ_ASSERT(begin_ < ret_);
MOZ_ASSERT(ret_ < end_);
MOZ_ASSERT(offsets.normalEntry - begin_ <= UINT8_MAX);
MOZ_ASSERT(offsets.tierEntry - begin_ <= UINT8_MAX);
u.funcIndex_ = funcIndex;
u.func.lineOrBytecode_ = funcLineOrBytecode;
u.func.beginToNormalEntry_ = offsets.normalEntry - begin_;
u.func.beginToTierEntry_ = offsets.tierEntry - begin_;
}
const CodeRange*

View File

@ -42,7 +42,6 @@
namespace js {
class PropertyName;
class WasmActivation;
class WasmFunctionCallObject;
namespace jit {
struct BaselineScript;
@ -948,6 +947,19 @@ struct CallableOffsets : Offsets
uint32_t ret;
};
struct JitExitOffsets : CallableOffsets
{
MOZ_IMPLICIT JitExitOffsets()
: CallableOffsets(), untrustedFPStart(0), untrustedFPEnd(0)
{}
// There are a few instructions in the Jit exit where FP may be trash
// (because it may have been clobbered by the JS Jit), known as the
// untrusted FP zone.
uint32_t untrustedFPStart;
uint32_t untrustedFPEnd;
};
struct FuncOffsets : CallableOffsets
{
MOZ_IMPLICIT FuncOffsets()
@ -1000,12 +1012,22 @@ class CodeRange
uint32_t ret_;
uint32_t end_;
union {
uint32_t funcIndex_;
struct {
uint32_t funcIndex_;
union {
struct {
uint32_t lineOrBytecode_;
uint8_t beginToNormalEntry_;
uint8_t beginToTierEntry_;
} func;
struct {
uint8_t beginToUntrustedFPStart_;
uint8_t beginToUntrustedFPEnd_;
} jitExit;
};
};
Trap trap_;
};
uint32_t funcLineOrBytecode_;
uint8_t funcBeginToNormalEntry_;
uint8_t funcBeginToTierEntry_;
} u;
Kind kind_ : 8;
public:
@ -1014,6 +1036,7 @@ class CodeRange
CodeRange(Kind kind, uint32_t funcIndex, Offsets offsets);
CodeRange(Kind kind, CallableOffsets offsets);
CodeRange(Kind kind, uint32_t funcIndex, CallableOffsets);
CodeRange(uint32_t funcIndex, JitExitOffsets offsets);
CodeRange(Trap trap, CallableOffsets offsets);
CodeRange(uint32_t funcIndex, uint32_t lineOrBytecode, FuncOffsets offsets);
@ -1045,6 +1068,9 @@ class CodeRange
bool isImportExit() const {
return kind() == ImportJitExit || kind() == ImportInterpExit || kind() == BuiltinThunk;
}
bool isImportJitExit() const {
return kind() == ImportJitExit;
}
bool isTrapExit() const {
return kind() == TrapExit;
}
@ -1072,14 +1098,14 @@ class CodeRange
}
uint32_t funcIndex() const {
MOZ_ASSERT(hasFuncIndex());
return funcIndex_;
return u.funcIndex_;
}
// TrapExit CodeRanges have a Trap field.
Trap trap() const {
MOZ_ASSERT(isTrapExit());
return trap_;
return u.trap_;
}
// Function CodeRanges have two entry points: one for normal calls (with a
@ -1092,15 +1118,27 @@ class CodeRange
}
uint32_t funcNormalEntry() const {
MOZ_ASSERT(isFunction());
return begin_ + funcBeginToNormalEntry_;
return begin_ + u.func.beginToNormalEntry_;
}
uint32_t funcTierEntry() const {
MOZ_ASSERT(isFunction());
return begin_ + funcBeginToTierEntry_;
return begin_ + u.func.beginToTierEntry_;
}
uint32_t funcLineOrBytecode() const {
MOZ_ASSERT(isFunction());
return funcLineOrBytecode_;
return u.func.lineOrBytecode_;
}
// ImportJitExit have a particular range where the value of FP can't be
// trusted for profiling and thus must be ignored.
uint32_t jitExitUntrustedFPStart() const {
MOZ_ASSERT(isImportJitExit());
return begin_ + u.jitExit.beginToUntrustedFPStart_;
}
uint32_t jitExitUntrustedFPEnd() const {
MOZ_ASSERT(isImportJitExit());
return begin_ + u.jitExit.beginToUntrustedFPEnd_;
}
// A sorted array of CodeRanges can be looked up via BinarySearch and
@ -1435,7 +1473,6 @@ struct TlsData
// and are inline in this structure. 16-byte alignment is required for SIMD
// data.
MOZ_ALIGNED_DECL(char globalArea, 16);
};
static_assert(offsetof(TlsData, globalArea) % 16 == 0, "aligned");