b=851964; Odin/OSX, part 3. enable AsmJS on OSX by using new Breakpad user handler, r=luke

This commit is contained in:
Vladimir Vukicevic 2013-03-18 19:07:07 -04:00
parent 5f4c0daded
commit c19c403118
6 changed files with 280 additions and 55 deletions

View File

@ -13,7 +13,7 @@
#if defined(JS_ION) && \
!defined(ANDROID) && \
(defined(JS_CPU_X86) || defined(JS_CPU_X64)) && \
(defined(__linux__) || defined(XP_WIN))
(defined(__linux__) || defined(XP_WIN) || defined(XP_MACOSX))
# define JS_ASMJS
#endif

View File

@ -512,6 +512,11 @@ class AsmJSModule
return code_;
}
bool pcIsInModule(void *pc) const {
return pc >= functionCode() &&
pc < (functionCode() + functionBytes());
}
void setOperationCallbackExit(uint8_t *ptr) {
operationCallbackExit_ = ptr;
}

View File

@ -81,13 +81,6 @@ InnermostAsmJSActivation()
return threadData->asmJSActivationStackFromOwnerThread();
}
static bool
PCIsInModule(const AsmJSModule &module, void *pc)
{
uint8_t *code = module.functionCode();
return pc >= code && pc < (code + module.functionBytes());
}
# if defined(JS_CPU_X64)
template <class T>
static void
@ -113,7 +106,7 @@ SetXMMRegToNaN(bool isFloat32, T *xmm_reg)
static const AsmJSHeapAccess *
LookupHeapAccess(const AsmJSModule &module, uint8_t *pc)
{
JS_ASSERT(PCIsInModule(module, pc));
JS_ASSERT(module.pcIsInModule(pc));
size_t targetOffset = pc - module.functionCode();
if (module.numHeapAccesses() == 0)
@ -221,7 +214,7 @@ HandleException(PEXCEPTION_POINTERS exception)
JS_ASSERT(pc == record->ExceptionAddress);
const AsmJSModule &module = activation->module();
if (!PCIsInModule(module, pc))
if (!module.pcIsInModule(pc))
return false;
if (record->NumberParameters < 2)
@ -234,7 +227,7 @@ HandleException(PEXCEPTION_POINTERS exception)
// execution to a trampoline which will call js_HandleExecutionInterrupt.
// The trampoline will jump to activation->resumePC if execution isn't
// interrupted.
if (PCIsInModule(module, faultingAddress)) {
if (module.pcIsInModule(faultingAddress)) {
activation->setResumePC(pc);
*ppc = module.operationCallbackExit();
DWORD oldProtect;
@ -290,6 +283,27 @@ AsmJSExceptionHandler(LPEXCEPTION_POINTERS exception)
# include <signal.h>
# include <sys/mman.h>
#if defined(XP_MACOSX)
// Oh boy. We get to do fun things to make this work with the Mach
// exception handling installed by breakpad. Note that we still
// use and set up the signal() infrastructure; we'll fall back to
// that if breakpad is not active.
#include <mach/exc.h>
#include <mach/mig.h>
#include <mach/thread_act.h>
extern "C" {
// this must be visible to dlsym(), and must have this name.
// Breakpad will call this before doing its thing if it exists
// in the process.
boolean_t BreakpadUserExceptionHandler64(exception_type_t extype,
mach_exception_data_t code,
mach_msg_type_number_t code_count,
mach_port_t thread)
__attribute__((visibility("default")));
}
#endif
// Unfortunately, we still need OS-specific code to read/write to the thread
// state via the mcontext_t.
# if defined(__linux__)
@ -366,50 +380,61 @@ ContextToPC(mcontext_t context)
}
# if defined(JS_CPU_X64)
static void
SetFloatStateRegisterToCoercedUndefined(x86_float_state64_t *fs, bool isFloat32, AnyRegister reg)
{
switch (reg.fpu().code()) {
case JSC::X86Registers::xmm0: SetXMMRegToNaN(isFloat32, &fs->__fpu_xmm0); break;
case JSC::X86Registers::xmm1: SetXMMRegToNaN(isFloat32, &fs->__fpu_xmm1); break;
case JSC::X86Registers::xmm2: SetXMMRegToNaN(isFloat32, &fs->__fpu_xmm2); break;
case JSC::X86Registers::xmm3: SetXMMRegToNaN(isFloat32, &fs->__fpu_xmm3); break;
case JSC::X86Registers::xmm4: SetXMMRegToNaN(isFloat32, &fs->__fpu_xmm4); break;
case JSC::X86Registers::xmm5: SetXMMRegToNaN(isFloat32, &fs->__fpu_xmm5); break;
case JSC::X86Registers::xmm6: SetXMMRegToNaN(isFloat32, &fs->__fpu_xmm6); break;
case JSC::X86Registers::xmm7: SetXMMRegToNaN(isFloat32, &fs->__fpu_xmm7); break;
case JSC::X86Registers::xmm8: SetXMMRegToNaN(isFloat32, &fs->__fpu_xmm8); break;
case JSC::X86Registers::xmm9: SetXMMRegToNaN(isFloat32, &fs->__fpu_xmm9); break;
case JSC::X86Registers::xmm10: SetXMMRegToNaN(isFloat32, &fs->__fpu_xmm10); break;
case JSC::X86Registers::xmm11: SetXMMRegToNaN(isFloat32, &fs->__fpu_xmm11); break;
case JSC::X86Registers::xmm12: SetXMMRegToNaN(isFloat32, &fs->__fpu_xmm12); break;
case JSC::X86Registers::xmm13: SetXMMRegToNaN(isFloat32, &fs->__fpu_xmm13); break;
case JSC::X86Registers::xmm14: SetXMMRegToNaN(isFloat32, &fs->__fpu_xmm14); break;
case JSC::X86Registers::xmm15: SetXMMRegToNaN(isFloat32, &fs->__fpu_xmm15); break;
default: MOZ_CRASH();
}
}
static void
SetThreadStateRegisterToCoercedUndefined(x86_thread_state64_t *ss, bool isFloat32, AnyRegister reg)
{
switch (reg.gpr().code()) {
case JSC::X86Registers::eax: ss->__rax = 0; break;
case JSC::X86Registers::ecx: ss->__rcx = 0; break;
case JSC::X86Registers::edx: ss->__rdx = 0; break;
case JSC::X86Registers::ebx: ss->__rbx = 0; break;
case JSC::X86Registers::esp: ss->__rsp = 0; break;
case JSC::X86Registers::ebp: ss->__rbp = 0; break;
case JSC::X86Registers::esi: ss->__rsi = 0; break;
case JSC::X86Registers::edi: ss->__rdi = 0; break;
case JSC::X86Registers::r8: ss->__r8 = 0; break;
case JSC::X86Registers::r9: ss->__r9 = 0; break;
case JSC::X86Registers::r10: ss->__r10 = 0; break;
case JSC::X86Registers::r11: ss->__r11 = 0; break;
case JSC::X86Registers::r12: ss->__r12 = 0; break;
case JSC::X86Registers::r13: ss->__r13 = 0; break;
case JSC::X86Registers::r14: ss->__r14 = 0; break;
case JSC::X86Registers::r15: ss->__r15 = 0; break;
default: MOZ_CRASH();
}
}
static void
SetRegisterToCoercedUndefined(mcontext_t &context, bool isFloat32, AnyRegister reg)
{
if (reg.isFloat()) {
switch (reg.fpu().code()) {
case JSC::X86Registers::xmm0: SetXMMRegToNaN(isFloat32, &context->__fs.__fpu_xmm0); break;
case JSC::X86Registers::xmm1: SetXMMRegToNaN(isFloat32, &context->__fs.__fpu_xmm1); break;
case JSC::X86Registers::xmm2: SetXMMRegToNaN(isFloat32, &context->__fs.__fpu_xmm2); break;
case JSC::X86Registers::xmm3: SetXMMRegToNaN(isFloat32, &context->__fs.__fpu_xmm3); break;
case JSC::X86Registers::xmm4: SetXMMRegToNaN(isFloat32, &context->__fs.__fpu_xmm4); break;
case JSC::X86Registers::xmm5: SetXMMRegToNaN(isFloat32, &context->__fs.__fpu_xmm5); break;
case JSC::X86Registers::xmm6: SetXMMRegToNaN(isFloat32, &context->__fs.__fpu_xmm6); break;
case JSC::X86Registers::xmm7: SetXMMRegToNaN(isFloat32, &context->__fs.__fpu_xmm7); break;
case JSC::X86Registers::xmm8: SetXMMRegToNaN(isFloat32, &context->__fs.__fpu_xmm8); break;
case JSC::X86Registers::xmm9: SetXMMRegToNaN(isFloat32, &context->__fs.__fpu_xmm9); break;
case JSC::X86Registers::xmm10: SetXMMRegToNaN(isFloat32, &context->__fs.__fpu_xmm10); break;
case JSC::X86Registers::xmm11: SetXMMRegToNaN(isFloat32, &context->__fs.__fpu_xmm11); break;
case JSC::X86Registers::xmm12: SetXMMRegToNaN(isFloat32, &context->__fs.__fpu_xmm12); break;
case JSC::X86Registers::xmm13: SetXMMRegToNaN(isFloat32, &context->__fs.__fpu_xmm13); break;
case JSC::X86Registers::xmm14: SetXMMRegToNaN(isFloat32, &context->__fs.__fpu_xmm14); break;
case JSC::X86Registers::xmm15: SetXMMRegToNaN(isFloat32, &context->__fs.__fpu_xmm15); break;
default: MOZ_CRASH();
}
} else {
switch (reg.gpr().code()) {
case JSC::X86Registers::eax: context->__ss.__rax = 0; break;
case JSC::X86Registers::ecx: context->__ss.__rcx = 0; break;
case JSC::X86Registers::edx: context->__ss.__rdx = 0; break;
case JSC::X86Registers::ebx: context->__ss.__rbx = 0; break;
case JSC::X86Registers::esp: context->__ss.__rsp = 0; break;
case JSC::X86Registers::ebp: context->__ss.__rbp = 0; break;
case JSC::X86Registers::esi: context->__ss.__rsi = 0; break;
case JSC::X86Registers::edi: context->__ss.__rdi = 0; break;
case JSC::X86Registers::r8: context->__ss.__r8 = 0; break;
case JSC::X86Registers::r9: context->__ss.__r9 = 0; break;
case JSC::X86Registers::r10: context->__ss.__r10 = 0; break;
case JSC::X86Registers::r11: context->__ss.__r11 = 0; break;
case JSC::X86Registers::r12: context->__ss.__r12 = 0; break;
case JSC::X86Registers::r13: context->__ss.__r13 = 0; break;
case JSC::X86Registers::r14: context->__ss.__r14 = 0; break;
case JSC::X86Registers::r15: context->__ss.__r15 = 0; break;
default: MOZ_CRASH();
}
}
if (reg.isFloat())
SetFloatStateRegisterToCoercedUndefined(&context->__fs, isFloat32, reg);
else
SetThreadStateRegisterToCoercedUndefined(&context->__ss, isFloat32, reg);
}
# endif
# endif // end of OS-specific mcontext accessors
@ -428,7 +453,7 @@ HandleSignal(int signum, siginfo_t *info, void *ctx)
uint8_t *pc = *ppc;
const AsmJSModule &module = activation->module();
if (!PCIsInModule(module, pc))
if (!module.pcIsInModule(pc))
return false;
void *faultingAddress = info->si_addr;
@ -438,7 +463,7 @@ HandleSignal(int signum, siginfo_t *info, void *ctx)
// execution to a trampoline which will call js_HandleExecutionInterrupt.
// The trampoline will jump to activation->resumePC if execution isn't
// interrupted.
if (PCIsInModule(module, faultingAddress)) {
if (module.pcIsInModule(faultingAddress)) {
activation->setResumePC(pc);
*ppc = module.operationCallbackExit();
mprotect(module.functionCode(), module.functionBytes(), PROT_EXEC);
@ -499,6 +524,128 @@ AsmJSFaultHandler(int signum, siginfo_t *info, void *context)
exit(signum); // backstop
}
}
#if defined(XP_MACOSX)
// This is the Mach-exceptions-in-conjunction-with-breakpad handler.
// There's an unfortunate amount of code duplicated from HandleSignal
// above, but there are enough differences in paths that unifying
// them is a bit of a pain (differences in how you get or set register
// state, how you look up the AsmJS Activation, etc.)
boolean_t
BreakpadUserExceptionHandler64(exception_type_t extype,
mach_exception_data_t code,
mach_msg_type_number_t code_count,
mach_port_t thread)
{
// we only care about BAD_ACCESS
if (extype != EXC_BAD_ACCESS)
return false;
kern_return_t kr;
# if defined(JS_CPU_X64)
x86_thread_state64_t state;
unsigned int count = x86_THREAD_STATE64_COUNT;
unsigned int state_flavor = x86_THREAD_STATE64;
uint8_t **ppc = reinterpret_cast<uint8_t**>(&state.__rip);
#else
x86_thread_state32_t state;
unsigned int count = x86_THREAD_STATE32_COUNT;
unsigned int state_flavor = x86_THREAD_STATE32;
uint8_t **ppc = reinterpret_cast<uint8_t**>(&state.__eip);
#endif
kr = thread_get_state(thread, state_flavor, (thread_state_t) &state, &count);
if (kr != KERN_SUCCESS) {
// couldn't get state? welp, at least we'll get a breakpad stack!
return false;
}
uint8_t *pc = *ppc;
AsmJSActivation *activation = JSRuntime::findAsmJSActivationForPC(pc);
if (!activation) {
return false;
}
// we know pc is in module because we found it earlier by the pc
const AsmJSModule &module = activation->module();
void *faultingAddress = reinterpret_cast<void*>(code[1]);
// If we faulted trying to execute code in 'module', this must be an
// operation callback (see TriggerOperationCallbackForAsmJSCode). Redirect
// execution to a trampoline which will call js_HandleExecutionInterrupt.
// The trampoline will jump to activation->resumePC if execution isn't
// interrupted.
if (module.pcIsInModule(faultingAddress)) {
activation->setResumePC(pc);
*ppc = module.operationCallbackExit();
mprotect(module.functionCode(), module.functionBytes(), PROT_EXEC);
// count better not have been modified since we called thread_get_state above
kr = thread_set_state(thread, state_flavor, (thread_state_t) &state, count);
if (kr != KERN_SUCCESS) {
return false;
}
return true;
}
# if defined(JS_CPU_X64)
// These checks aren't necessary, but, since we can, check anyway to make
// sure we aren't covering up a real bug.
if (!module.maybeHeap() ||
faultingAddress < module.maybeHeap() ||
faultingAddress >= module.maybeHeap() + AsmJSBufferProtectedSize)
{
return false;
}
const AsmJSHeapAccess *heapAccess = LookupHeapAccess(module, pc);
if (!heapAccess)
return false;
// We now know that this is an out-of-bounds access made by an asm.js
// load/store that we should handle. If this is a load, assign the
// JS-defined result value to the destination register (ToInt32(undefined)
// or ToNumber(undefined), determined by the type of the destination
// register) and set the PC to the next op. Upon return from the handler,
// execution will resume at this next PC.
if (heapAccess->isLoad()) {
AnyRegister reg = heapAccess->loadedReg();
if (reg.isFloat()) {
// We have to grab the floating point registers
x86_float_state64_t fstate;
count = x86_FLOAT_STATE64_COUNT;
kr = thread_get_state(thread, x86_FLOAT_STATE64, (thread_state_t) &fstate, &count);
if (kr != KERN_SUCCESS)
return false;
SetFloatStateRegisterToCoercedUndefined(&fstate, heapAccess->isFloat32Load(), reg);
kr = thread_set_state(thread, x86_FLOAT_STATE64, (thread_state_t) &fstate, x86_FLOAT_STATE64_COUNT);
if (kr != KERN_SUCCESS)
return false;
} else {
SetThreadStateRegisterToCoercedUndefined(&state, heapAccess->isFloat32Load(), reg);
}
}
*ppc += heapAccess->opLength();
kr = thread_set_state(thread, x86_THREAD_STATE64, (thread_state_t) &state, x86_THREAD_STATE64_COUNT);
if (kr != KERN_SUCCESS)
return false;
return true;
# else
return false;
# endif
}
#endif
# endif
#endif // JS_ASMJS

View File

@ -89,8 +89,9 @@
#include "methodjit/Logging.h"
#endif
#ifdef JS_METHODJIT
#ifdef JS_ION
#include "ion/Ion.h"
#include "ion/AsmJSModule.h"
#endif
using namespace js;
@ -967,6 +968,19 @@ JSRuntime::init(uint32_t maxbytes)
return false;
nativeStackBase = GetNativeStackBase();
#ifdef XP_MACOSX
if (!runtimeListLock_) {
runtimeListLock_ = PR_NewLock();
runtimeListHead_ = NULL;
}
PR_Lock(runtimeListLock_);
runtimeListNext_ = runtimeListHead_;
runtimeListHead_ = this;
PR_Unlock(runtimeListLock_);
#endif
return true;
}
@ -976,6 +990,27 @@ JSRuntime::~JSRuntime()
clearOwnerThread();
#endif
#ifdef XP_MACOSX
{
PR_Lock(runtimeListLock_);
JSRuntime *r = runtimeListHead_;
// find ourselves to remove us from the list
if (r == this) {
runtimeListHead_ = runtimeListNext_;
} else {
// look for the node that has us in the next
// pointer
while (r && r->runtimeListNext_ != this)
r = r->runtimeListNext_;
JS_ASSERT(r);
r->runtimeListNext_ = runtimeListNext_;
}
runtimeListNext_ = NULL;
PR_Unlock(runtimeListLock_);
}
#endif
/*
* Even though all objects in the compartment are dead, we may have keep
* some filenames around because of gcKeepAtoms.

View File

@ -49,6 +49,7 @@
#ifdef JS_ION
#include "ion/Ion.h"
#include "ion/IonFrames.h"
#include "ion/AsmJSModule.h"
#endif
#ifdef JS_METHODJIT
@ -1406,6 +1407,31 @@ JSRuntime::onOutOfMemory(void *p, size_t nbytes, JSContext *cx)
return NULL;
}
#ifdef XP_MACOSX
PRLock *JSRuntime::runtimeListLock_ = NULL;
JSRuntime *JSRuntime::runtimeListHead_ = NULL;
AsmJSActivation*
JSRuntime::findAsmJSActivationForPC(void *pc)
{
PR_Lock(runtimeListLock_);
AsmJSActivation *activation = NULL;
JSRuntime *r = runtimeListHead_;
while (r) {
activation = r->mainThread.asmJSActivationStackFromAnyThread();
if (activation && activation->module().pcIsInModule(pc))
break;
activation = NULL;
r = r->runtimeListNext_;
}
PR_Unlock(runtimeListLock_);
return activation;
}
#endif
void
JSContext::purge()
{

View File

@ -677,6 +677,18 @@ struct JSRuntime : js::RuntimeFriendFields,
void assertValidThread() const {}
#endif
#ifdef XP_MACOSX
// On OSX, we need a way to find all the live runtimes from an arbitrary
// thread (specifically, the exception thread, for fixing up Odin
// accesses).
private:
static PRLock *runtimeListLock_;
static JSRuntime *runtimeListHead_;
JSRuntime *runtimeListNext_;
public:
static js::AsmJSActivation* findAsmJSActivationForPC(void *pc);
#endif
/* Keeper of the contiguous stack used by all contexts in this thread. */
js::StackSpace stackSpace;